项目通过 ESP32 联网获取心知天气的实时数据,将文本信息经 Web 服务器转发至 PC 端,并调用科大讯飞 TTS 接口生成语音文件,最终由 ESP32 下载音频并通过 I2S + DAC 播放出来。
整个实现过程横跨嵌入式联网、HTTP 通信、JSON 解析、服务器文件操作以及音频播放链路,并不是单一模块的堆砌,而是一次从“数据获取”到“语音输出”的完整系统实践。本文将围绕整体设计思路、关键代码实现以及实际踩坑经验,对该项目进行一次较为完整的记录与复盘。

项目介绍
本次实现的是一个基于 ESP32-S3-BOX_Lite 开发板,使用 MicroPython 编程的天气信息语音播报系统。通过搭建一个小型文件操作服务器,并集成心知天气 API 与科大讯飞 TTS 合成服务,完成了一个从数据采集到语音输出的完整链路。
设计思路
系统设计主要分为硬件端(ESP32)和软件端(PC服务器)两部分,具体流程如下:
- Wi-Fi连接:ESP32 连接至 Wi-Fi 网络,获取互联网访问能力。
- 获取天气:ESP32 向心知天气 API 发送 HTTP 请求,获取 JSON 格式的实时天气数据。
- 转发数据:ESP32 将解析后的天气文本,通过 HTTP POST 请求发送给本地运行的 Web 服务器。
- 服务器写入:Web 服务器接收文本,并将其写入本地的
input.txt 文件。
- TTS转换:Web 服务器读取
input.txt 内容,调用科大讯飞 TTS API,将文本转换为音频文件。
- 文件托管:Web 服务器将生成的音频文件(WAV格式)存储在特定位置,并提供下载 URL。
- 下载音频:ESP32 向服务器发送 HTTP GET 请求,下载生成的音频文件到本地存储。
- 硬件连接:ESP32 通过 I2S 总线连接外部 DAC 芯片(ES8156)和功放(NS4150)。
- 音频播放:ESP32 从存储中读取音频数据,通过 I2S 发送给 DAC,经功放驱动扬声器播放。
- 循环执行:播放完毕后,等待一段时间,重新开始从步骤1执行,实现定时播报。
硬件介绍
项目核心硬件为 ESP32-S3-BOX_Lite 开发板,主控为 ESP32-S3,并集成了外部 DAC 模块(ES8156)和音频功放(NS4150)以及一个扬声器。本项目实现的功能相对简单,每20秒自动获取并播报一次天气,因此未使用板载的按键、ST7789显示屏及麦克风阵列功能。
该板卡硬件资源丰富,值得一提的是其 ADC 按键设计:三个按键通过一个 ADC 通道的电压值来区分,实现不同的按键功能,这是一个非常巧妙的设计。


软件流程与代码实现
1. 系统流程图
下图清晰地展示了从 ESP32 联网到最终播放语音的完整数据流与处理流程。

2. 主要代码片段说明
以下是各功能模块的核心代码,均已添加详细注释。在实际项目中,建议将不同功能模块化到单独的文件中。
1)连接 Wi-Fi
# WIFI配置(请替换为自己的Wi-Fi信息)
# ssid = ‘your_ssid’
# password =‘your_password’
def ConnectNet(ssid ,password):
mynetwork=network.WLAN(network.STA_IF) # 配置为站点模式
mynetwork.active(False) # 先关闭WiFi
mynetwork.active(True) # 再打开WiFi
mynetwork.connect(ssid,password) # 连接网络,上述步骤为避免某些异常
while True:
if(mynetwork.isconnected()):
break
else :
time.sleep(1)
#print(mynetwork.ifconfig()) # 可打印IP地址等信息
print(“WIFI is connect”) # 串口打印连接成功信息
2)访问心知天气 API 并解析 JSON
# 请求天气API(请替换为自己的API Key和城市)
result1=urequests.get(‘https://api.seniverse.com/v3/weather/now.json?key=YOUR_API_KEY&location=nanjing&language=zh-Hans&unit=c’)
j1=ujson.loads(result1.text) # 解析JSON响应
# 提取所需字段
city = j1[‘results’][0][‘location’][‘name’]
weather = j1[‘results’][0][‘now’][‘text’]
temp = j1[‘results’][0][‘now’][‘temperature’]
fresh_time = j1[‘results’][0][‘last_update’]
print(city)
print(weather)
print(temp,“度”)
print(fresh_time)
3)与服务器通信:发送文本与下载音频
def send_content_to_server(content):
url = “http://SERVER_IP:PORT/receive_content” # 替换为你的服务器IP和端口
content_utf8 = content.encode(‘utf-8’)
headers = {‘Content-Type’: ‘text/plain’}
response = urequests.post(url, data=content_utf8, headers=headers)
print(“Content sent to server. Response status:“, response.status_code)
response.close()
def download_wav_file(url, save_path):
response = urequests.get(url)
with open(save_path, ‘wb’) as file:
file.write(response.content)
# 配置服务器音频文件URL和本地保存路径
server_wav_url = ‘http://SERVER_IP:PORT/get_wav_file’
esp32_wav_path = ‘/output.wav’
4)I2S 与 DAC 初始化配置
sck_pin = Pin(17) # 串行时钟输出,I2S_SCLK
ws_pin = Pin(47) # 字时钟,I2S_LRCK
sd_pin = Pin(15) # 串行数据输出,GPIO15=I2S_DAC_SDIN
pa_pin = Pin(46, Pin.OUT) # 创建用于控制功率放大器的Pin对象
pa_pin.value(1) # 将引脚设置为高电平以启用放大器
print(“ADC IS OK”) # 调试信息
audio_out = I2S(0,sck=sck_pin,
ws=ws_pin,
sd=sd_pin,
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=16000,
ibuf=20000)
print(“IIS IS OK”) # 调试信息
5)主循环函数
while True:
# 1. 获取天气
result1=urequests.get(‘https://api.seniverse.com/v3/weather/now.json?key=YOUR_KEY&location=nanjing&language=zh-Hans&unit=c’)
j1=ujson.loads(result1.text)
city = j1[‘results’][0][‘location’][‘name’]
weather = j1[‘results’][0][‘now’][‘text’]
temp = j1[‘results’][0][‘now’][‘temperature’]
fresh_time = j1[‘results’][0][‘last_update’]
print(city)
print(weather)
print(temp,“度”)
print(fresh_time)
# 2. 拼接并发送文本到服务器
content = city + “ “ + weather + “ “ + str(temp) + “度 “
print(content)
send_content_to_server(content)
# 3. 从服务器下载音频文件
download_wav_file(server_wav_url, esp32_wav_path)
print(“download is ok”)
# 4. 播放音频
wavtempfile = “output.wav”
wav = open(wavtempfile,‘rb’)
print(‘播放音频’)
start_time = time.ticks_us()
buf = wav.read()
bufhead = 44 # WAV文件头长度
bufsize = len(buf) - bufhead
bufcap = 4096 # 缓冲区大小
# 计算总时长 (微秒): 数据字节数 / (采样率 * 位宽 * 通道数 / 8 / 1e6)
all_time = bufsize / 0.032
bunum = 1
bufstart = bufhead
while bufsize:
bufend = bufcap * bunum
if bufend > len(buf):
bufend = len(buf)
bufwrite = buf[bufstart:bufend]
num_written = audio_out.write(bufwrite)
bufsize -= len(bufwrite)
bufstart = bufend
bunum = bunum + 1
# 等待音频播放完毕
while 1:
end_time = time.ticks_us()
if (end_time - start_time) > all_time:
audio_out.deinit() # 取消初始化 I2S 总线
break
wav.close()
print(“OVER”) # 单次播报结束
time.sleep(20) # 等待20秒后进入下一轮循环
6)服务器端代码 (Python on PC)
此部分代码运行在PC的PyCharm等环境中,负责接收文本、调用讯飞TTS并生成音频文件。以下为关键逻辑摘要,完整代码较长,核心是搭建一个简单的HTTP服务器并集成讯飞WebSocket TTS客户端。
服务器需要实现两个端点:
/receive_content (POST): 接收ESP32发来的天气文本,写入input.txt。
/get_wav_file (GET): 提供output.wav音频文件的下载。
TTS转换部分,使用科大讯飞官方WebSocket API示例代码(需自行填入APPID、APIKey、APISecret),其工作流程为:读取input.txt -> 调用讯飞API生成PCM数据 -> 将PCM转换为WAV格式并保存为output.wav。
功能展示
1. ESP32 串口输出
串口日志显示了完整的执行流程:Wi-Fi连接成功、获取到的天气信息(南京、多云、27度)、与服务器的通信状态、音频下载成功以及播放开始与结束的标识。

2. 服务器访问日志
PC端的服务器日志显示,成功处理了来自ESP32的文本写入请求和后续的音频文件下载请求,状态码均为200。

3. 文本文件写入
服务器成功将接收到的天气文本“南京 多云 27度”写入到了本地的input.txt文件中。

4. 讯飞TTS服务调用
在讯飞开放平台的服务管理界面,可以查看到TTS接口的调用记录和用量情况。

心得体会与踩坑记录
- 开发环境选择:乐鑫官方的IDF(C语言)环境功能强大,但配置过程对新手可能有一定门槛。本次因环境配置问题,临时转向了MicroPython,其上手速度快,适合原型验证。但对于追求极致性能和底层控制的场景,仍建议使用官方IDF。
- 系统架构设计:本项目采用“ESP32 + PC服务器”的架构,将计算密集的TTS任务卸载到服务器端,降低了嵌入式端的资源压力。这种客户端-服务器的思维在物联网项目中很常见,也让我实践了HTTP通信和简单的服务端编程。
- 知识整合:最大的收获在于将多个独立的技术点(网络连接、API调用、JSON解析、文件操作、音频播放)串联成了一个可工作的系统。特别是搭建和使用简易Web服务器进行数据中转的思路,为未来更复杂的物联网项目提供了参考。
- MicroPython的局限:虽然开发便捷,但MicroPython在硬件底层操作、内存管理以及第三方库生态上不如C/IDF灵活。例如,直接将讯飞TTS的Python SDK移植到ESP32 MicroPython中可能会遇到依赖和性能问题。
- 总结:这是一个很好的嵌入式全栈入门实践。它不仅仅关乎代码,更关乎系统性的设计和问题分解能力。在云栈社区也有许多开发者分享过类似的嵌入式与物联网项目,这种从传感器到云端再到执行器的完整链路,是物联网应用的典型模式。建议初学者可以从这样的项目入手,理解每个模块的作用,再逐步深入优化和扩展。