ESP32联网语音助手
在做的项目需要一个联网语音部分,联网接入大模型或者其他的API,同时给OpenMV发信号,单独做出来好了()。
ESP32回顾
先来点个灯回顾一下ESP32的基础操作。Arduino IDE选ESP32 Dev Module。烧录一个点灯程序:
uint8_t led_pin = 2;
void setup(){ pinMode(led_pin, OUTPUT);}
void loop(){ digitalWrite(led_pin, HIGH); delay(500); digitalWrite(led_pin, LOW); delay(500);}一. 语音模块
例程
选了语音模块是SYN6288。
烧录示例代码,烧录时不能接线,猜测是串口的原因。
void speech(){ unsigned char i = 0; unsigned char head[56];
head[0] = 0xFD; head[1] = 0x00; head[2] = 0x35; head[3] = 0x01; head[4] = 0x00; head[5] = 0x5B; head[6] = 0x6D; head[7] = 0x34; head[8] = 0x5D; head[9] = 0x5B; head[10] = 0x76; head[11] = 0x31; head[12] = 0x36; head[13] = 0x5D; head[14] = 0x5B; head[15] = 0x74; head[16] = 0x35; head[17] = 0x5D; head[18] = 0xBB; head[19] = 0xB6; head[20] = 0xD3; head[21] = 0xAD; head[22] = 0xCA; head[23] = 0xB9; head[24] = 0xD3; head[25] = 0xC3; head[26] = 0xC2; head[27] = 0xCC; head[28] = 0xC9; head[29] = 0xEE; head[30] = 0xC6; head[31] = 0xEC; head[32] = 0xBD; head[33] = 0xA2; head[34] = 0xB5; head[35] = 0xEA; head[36] = 0x53; head[37] = 0x59; head[38] = 0x4E; head[39] = 0x36; head[40] = 0x32; head[41] = 0x38; head[42] = 0x38; head[43] = 0xD3; head[44] = 0xEF; head[45] = 0xD2; head[46] = 0xF4; head[47] = 0xBA; head[48] = 0xCF; head[49] = 0xB3; head[50] = 0xC9; head[51] = 0xC4; head[52] = 0xA3; head[53] = 0xBF; head[54] = 0xE9; head[55] = 0x91;
for(i=0; i<56; i++){ Serial.write(head[i]); }}
void setup() { Serial.begin(9600);}
void loop() { speech(); delay(10000);
}烧录完后接线,就是正常的接VCC (3.3V)和GND,串口选用UART0的TX和RX。会有声音出来。
消息协议
我希望实现一个输入文字能直接输出语音的效果,因此SYN6288模块例程还不够,需要解析一下这个协议是什么。

从文档的协议可以看出来,前三个字节可以不用管,第四个字节也不用管,在播放指令的时候命令字节都是0x01,第六个字节,因为我不加背景音乐,所以只关心最末3位,也就是格式控制位。在播放命令的时候,如果是GB2312格式的数据,就是0x00,测试了一下这个格式比较正常,或者GBK也可以,unicode有一点问题,我懒得解决了,就这样吧。
(arduino ide暂时没找到比较快的改编码格式方案,因此我选择gbk也就是说只能在vscode中写,ide只作为一个烧录工具。当然也可以选unicode格式,格式命令改成0x03就行。
接下来是数据内容。中文一个字符由2个字节表示。下面是一个传入字符串,输出消息格式的代码,测试了一下,和SYN6288厂商提供的上位机的转码是一样的。
#include "stdint.h"#include "string.h"#include "stdio.h"
void speech(char str[]){ static char data[256]; static uint8_t len, str_len;
data[0] = 0xFD; // 固定帧头 data[3] = 0x01; // 播放指令 data[4] = 0x00; // 格式gb2312// data[4] = 0x03; // 格式unicode
str_len = strlen(str); memcpy(data + 5, str, str_len);
data[1] = 0x00; // 数据长度 data[2] = str_len + 3;
len = str_len + 5; data[len] = 0; for (uint8_t i = 0; i < len; i++) data[len] ^= data[i]; len++;
for (uint8_t i = 0; i < len; i++) printf("0x%02X ", (uint8_t)data[i]);}
int main(){ char str[] = "你好"; speech(str); return 0;}语音实现
然后可以开始写实际的代码了,大致如下,烧录时记得要把模块拔了。(这设计真有点有了了,看了下这个模块的VCC和GND还很容易接反。)
第一遍写的代码如下:
#include "stdint.h"#include "string.h"
uint8_t led_pin = 2;
void setup(){ pinMode(led_pin, OUTPUT); Serial.begin(9600);}
void speech(const char str[]){ static char data[256]; static uint8_t len, str_len;
data[0] = 0xFD; // 锟教讹拷帧头 data[3] = 0x01; // 锟斤拷锟斤拷指锟斤拷 data[4] = 0x00; // 锟斤拷式gb2312 // data[4] = 0x03; // 锟斤拷式unicode
str_len = strlen(str); memcpy(data + 5, str, str_len);
data[1] = 0x00; // 锟斤拷锟捷筹拷锟斤拷 data[2] = str_len + 3;
len = str_len + 5; data[len] = 0; for (uint8_t i = 0; i < len; i++) data[len] ^= data[i]; len++;
for (uint8_t i = 0; i < len; i++) Serial.write(data[i]);}
void loop(){ speech("你好!"); digitalWrite(led_pin, HIGH); delay(1000); digitalWrite(led_pin, LOW); delay(1000);}状态查询
但是很容易注意到,这模块在说话的时候又收到指令时,会停止当前说话,因此要要检查一下它话说完了吗。没有命令参数和数据内容,因此数据区长度直接就是2.
可以类似这样查询(下面这段代码没测试,因为有更好的方法):
bool check_speaking(){ static char data[256]; static uint8_t len;
data[0] = 0xFD; // Head data[1] = 0x00, data[2] = 0x02; // len data[3] = 0x21; // Check Speaking data[4] = 0xDE; len = 5;
while (Serial.available() == 0); String receivedData = Serial.readString(); if (receivedData[0] == 0x4F) return true; else return false;}但是一直进行串口通信肯定是不优的,我们还得分个中断来检查收到的内容。SYN6288支持硬件查询,也就是通过查询引脚BY的高低电平来判断。BY引脚是低电平的时候,说明空闲。
通过判断空闲来连续发送的代码如下:
#include "stdint.h"#include "string.h"
uint8_t led_pin = 2;uint8_t busy_pin = 12;
bool check_busy(){ return digitalRead(busy_pin);}
void setup(){ pinMode(led_pin, OUTPUT);
pinMode(busy_pin, INPUT); Serial.begin(9600);}
void speech(const char str[]){ static char data[256]; static uint8_t len, str_len;
data[0] = 0xFD; // Head data[3] = 0x01; // Speak data[4] = 0x00; // gb2312 // data[4] = 0x03; // unicode
str_len = strlen(str); memcpy(data + 5, str, str_len);
data[1] = 0x00; data[2] = str_len + 3;
len = str_len + 5; data[len] = 0; for (uint8_t i = 0; i < len; i++) data[len] ^= data[i]; len++;
for (uint8_t i = 0; i < len; i++) Serial.write(data[i]);}
void loop(){ if (check_busy() == false) speech("你好!"); digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}更换串口
串口0一般我们是用来调试的,所以要更换到其他串口。我换到ESP32的第二个串口,也就是RX、TX分别为16、17。
代码和接线都改一下就行。
#include "stdint.h"#include "string.h"
uint8_t led_pin = 2;uint8_t busy_pin = 12;
HardwareSerial voice_serial(1); // 定义串口
bool check_busy(){ return digitalRead(busy_pin);}
void setup(){ pinMode(led_pin, OUTPUT); pinMode(busy_pin, INPUT); voice_serial.begin(9600, SERIAL_8N1, 16, 17); // 初始化串口,波特率为9600,引脚16为TX,17为RX}
void speech(const char str[]){ static char data[256]; static uint8_t len, str_len;
data[0] = 0xFD; // Head data[3] = 0x01; // Speak data[4] = 0x00; // gb2312 // data[4] = 0x03; // unicode
str_len = strlen(str); memcpy(data + 5, str, str_len);
data[1] = 0x00; data[2] = str_len + 3;
len = str_len + 5; data[len] = 0; for (uint8_t i = 0; i < len; i++) data[len] ^= data[i]; len++;
for (uint8_t i = 0; i < len; i++) voice_serial.write(data[i]); // 使用新的串口发送数据}
void loop(){ if (check_busy() == false) speech("你好!"); digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}模块封装
接着把这玩意封装成c++的类,我个人感觉这样看着比较好看一点()。特别是ESP32支持这个,就比其他只支持C的单片机更爽写ovo
#include "stdint.h"#include "string.h"
class VoiceModule{private: HardwareSerial &voice_serial; uint8_t busy_pin;
public: // 构造函数,传入串口和busy引脚的序号 VoiceModule(HardwareSerial &serial, uint8_t busy_pin) : voice_serial(serial), busy_pin(busy_pin) {}
// 初始化函数,设置引脚模式和串口参数 void begin(uint32_t baudRate, uint8_t txPin, uint8_t rxPin) { pinMode(busy_pin, INPUT); voice_serial.begin(baudRate, SERIAL_8N1, txPin, rxPin); }
// 检查忙碌状态 bool isBusy() { return digitalRead(busy_pin); }
// 发送语音数据 void speech(const char str[]) { static char data[256]; static uint8_t len, str_len;
data[0] = 0xFD; // Head data[3] = 0x01; // Speak data[4] = 0x00; // gb2312 // data[4] = 0x03; // unicode
str_len = strlen(str); memcpy(data + 5, str, str_len);
data[1] = 0x00; data[2] = str_len + 3;
len = str_len + 5; data[len] = 0; for (uint8_t i = 0; i < len; i++) data[len] ^= data[i]; len++;
for (uint8_t i = 0; i < len; i++) voice_serial.write(data[i]); }};
uint8_t led_pin = 2;
HardwareSerial voiceSerial(1); // 定义串口实例VoiceModule voice(voiceSerial, 12); // 创建语音模块实例,busy_pin为12
void setup(){ pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); // 初始化语音模块,波特率为9600,引脚16为TX,17为RX}
void loop(){ if (voice.isBusy() == false) { voice.speech("你好!"); } digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}二. 联网部分
计划采用ESP32连接服务器,然后接大模型。那首先就要实现一个网络接口,能够实现传入一个中文字符串后,能发声。
这里我计划使用MQTT消息协议,选择的原因一个是比较好写,一个是快啊,而且好调试。
首先用我自己的MQTT服务器,确定两个东西:本地WIFI和服务器IP、MQTT框架运行的端口。
SSID = "foresp1" # Network SSIDKEY = "20040724" # Network key
MQTTClient("openmv", "113.45.173.169", port=1883)MQTT服务器测试
先自己写个python测试一下MQTT的收发是否正常,我把服务器和订阅的信息都存在和python同目录的config.yaml文件下,也就是:
server: address: "113.45.173.169" # MQTT服务器地址 port: 1883 # MQTT服务器端口 topics: # 可订阅主题 - "openmv/voice"
client: name: "aicoach" # 监听客户端名称发送的文件是sender.py,测试接收的文件是listener.py,这两个在不同的终端同时运行,就可以发送然后测试是否收到了。
'''Author: wlatenDate: 2024-07-05 20:49:29LastEditTime: 2024-07-23 01:49:12Discription: file content'''import osimport paho.mqtt.client as mqttimport yaml
"""listener.py测试MQTT监听程序"""
current_dir = os.path.dirname(os.path.abspath(__file__))config_path = os.path.join(current_dir, 'config.yaml')
with open('config.yaml', 'r', encoding='utf-8') as file: config = yaml.safe_load(file)MQTT_SERVER = config['server']['address']MQTT_PORT = config['server']['port']MQTT_TOPIC = config['server']['topics']
def on_message(client, userdata, msg): message = msg.payload.decode() topic = msg.topic print(f"Received message '{message}' on topic '{topic}'")
def on_connect(client, userdata, flags, rc): print("Connected with result code "+str(rc)) for topic in MQTT_TOPIC: client.subscribe(topic)
if __name__ == '__main__': client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message
client.username_pw_set(config['client']['name']) client.connect(MQTT_SERVER, MQTT_PORT, 60)
client.loop_forever()""" sender.py 模拟客户端,测试MQTT服务器收发消息"""
import osimport paho.mqtt.client as mqttimport yaml
# 获取当前脚本文件的绝对路径,然后获取同目录下的config.yaml文件的路径current_dir = os.path.dirname(os.path.abspath(__file__))config_path = os.path.join(current_dir, 'config.yaml')
with open(config_path, 'r', encoding='utf-8') as file: config = yaml.safe_load(file)broker_address = config['server']['address']broker_port = config['server']['port']
def on_connect(client, userdata, flags, rc): print(f"Connected with result code {rc}")
client = mqtt.Client()client.on_connect = on_connectclient.connect(broker_address, broker_port, 60)
client.loop_start()
while True: topic = input("请输入主题: ") message = input("请输入消息内容: ") client.publish(topic, message)测试了一下我的MQTT服务器运行正常,好可以开始写ESP32了。
ESP32连接MQTT
先写一个ESP32连接WIFI的代码:
#include "stdint.h"#include "string.h"#include <WiFi.h>
#include "VoiceModule.h" // 把前面的类封装进来
uint8_t led_pin = 2;
HardwareSerial voiceSerial(1); // 定义串口实例VoiceModule voice(voiceSerial, 12); // 创建语音模块实例,busy_pin为12
void network_init(){ const char *ssid = "foresp1"; const char *password = "20040724";
Serial.println(); Serial.print("正在连接到 "); Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.print("."); }
Serial.println(""); Serial.println("WiFi 已连接"); Serial.println("IP 地址: "); Serial.println(WiFi.localIP());}
void setup(){ Serial.begin(115200); // 初始化调试串口 Serial.println("Serial start...");
network_init(); // 初始化网络
pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); // 初始化语音模块,波特率为9600,引脚16为TX,17为RX}
void loop(){ if (voice.isBusy() == false) { voice.speech("你好!"); } digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}然后写连接MQTT服务器的代码。
const char *mqtt_server = "113.45.173.169";const int mqtt_port = 1883;const char *mqtt_user = "voice";const char *mqtt_topic = "openmv/test";
WiFiClient espClient;PubSubClient client(espClient);
void network_init(){ /* 初始化WIFI */ const char *ssid = "foresp1"; const char *password = "20040724";
Serial.println(); Serial.print("正在连接到 "); Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.print("."); }
Serial.println(""); Serial.println("WiFi 已连接"); Serial.println("IP 地址: "); Serial.println(WiFi.localIP());
/* 初始化MQTT */ client.setServer(mqtt_server, mqtt_port); // client.setCallback(mqttCallback); Serial.println("MQTT连接中..."); while (!client.connected()) { if (client.connect(mqtt_user)) break; else { delay(100); Serial.print("."); } }}现在在服务器上查询,可以看见已经连接成功了。
但是现在会发现,尽管连接成功了,但是过一会就会断开,这是因为没有发送心跳包保持连接,所以要在loop函数里面持续调用client.loop()。
或者为了进一步保持连接稳定,加入掉线重连接功能,完整代码:
#include "stdint.h"#include "string.h"
#include <WiFi.h>#include <PubSubClient.h>
#include "VoiceModule.h"
uint8_t led_pin = 2;
HardwareSerial voiceSerial(1); // 定义串口实例VoiceModule voice(voiceSerial, 12); // 创建语音模块实例,busy_pin为12
const char *mqtt_server = "113.45.173.169";const int mqtt_port = 1883;const char *mqtt_user = "voice";const char *mqtt_topic = "openmv/test";
WiFiClient espClient;PubSubClient client(espClient);
void mqtt_connect(){ Serial.println("MQTT连接中..."); while (!client.connected()) { if (client.connect(mqtt_user)) break; else { delay(100); Serial.print("."); } } Serial.println("MQTT已连接");}
void network_init(){ /* 初始化WIFI */ const char *ssid = "foresp1"; const char *password = "20040724";
Serial.println(); Serial.print("正在连接到 "); Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.print("."); }
Serial.println(""); Serial.println("WiFi 已连接"); Serial.println("IP 地址: "); Serial.println(WiFi.localIP());
/* 初始化MQTT */ client.setServer(mqtt_server, mqtt_port); // client.setCallback(mqttCallback); mqtt_connect();}
void setup(){ Serial.begin(115200); Serial.println("Serial start..."); network_init();
pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); // 初始化语音模块,波特率为9600,引脚16为TX,17为RX}
void loop(){ client.loop(); if (!client.connected()) mqtt_connect(); if (voice.isBusy() == false) { voice.speech("你好!"); } digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}ESP32发送MQTT消息
尝试用ESP32发送一条消息到openmv/test这个主题上。只需要随便在哪里这样写一下就好:
String message = "Hello from ESP32 你好!"; client.publish("openmv/test", message.c_str());这时候我们前面的python监听程序要改一下,因为监听默认是utf8格式。
def on_message(client, userdata, msg): try: message = msg.payload.decode('utf-8') # 尝试用UTF-8解码 except UnicodeDecodeError: message = msg.payload.decode('gbk') # 如果失败,改用GBK解码 topic = msg.topic print(f"Received message '{message}' on topic '{topic}'")现在可以成功收到这样的消息了。
ESP32接收MQTT消息
MQTT首先要订阅,然后接收。
首先确定订阅主题:
const char *mqtt_topics_sub[] = { "openmv/test", "openmv/voice", "sport_cmd"};const int num_topics = sizeof(topics) / sizeof(topics[0]);然后修改连接函数,在连接中订阅:
void mqtt_connect(){ Serial.println("MQTT连接中..."); while (!client.connected()) { if (client.connect(mqtt_user)) break; else { delay(100); Serial.print("."); } } for (int i = 0; i < topics_sub_cnt; i++) { client.subscribe(topics[i]); Serial.print("Subscribed to topic: "); Serial.println(topics[i]); } Serial.println("MQTT已连接");}还需要设置回调函数:
client.setCallback(mqtt_callback); // 加在network_init()里void mqtt_callback(char *topic, byte *payload, unsigned int len){ Serial.print("Message arrived on topic: "); Serial.print(topic); Serial.print(". Message: ");
String messageTemp;
for (int i = 0; i < len; i++) messageTemp += (char)payload[i]; Serial.println(messageTemp);
// 处理消息...}现在可以正常收发了,这里还需要修改sender.py文件来测试,保证发送是gbk格式。
while True: topic = input("请输入主题: ") message = input("请输入消息内容: ").encode('gbk') // 编码gbk client.publish(topic, message)可以发现现在是可以正常收发的。
现在整套代码基本实现了通过mqtt协议,网络传入中文字符串,语音模块发声。
#include "stdint.h"#include "string.h"
#include <WiFi.h>#include <PubSubClient.h>
#include "VoiceModule.h"
uint8_t led_pin = 2;
HardwareSerial voiceSerial(1); // 定义串口实例VoiceModule voice(voiceSerial, 12); // 创建语音模块实例,busy_pin为12
const char *mqtt_server = "113.45.173.169";const int mqtt_port = 1883;const char *mqtt_user = "voice";const char *mqtt_topic = "openmv/test";
WiFiClient espClient;PubSubClient client(espClient);
const char *mqtt_topics_sub[] = { "openmv/test", "openmv/voice", "sport_cmd"};const int topics_sub_cnt = sizeof(mqtt_topics_sub) / sizeof(mqtt_topics_sub[0]);
void mqtt_connect(){ Serial.println("MQTT连接中..."); while (!client.connected()) { if (client.connect(mqtt_user)) break; else { delay(100); Serial.print("."); } } for (int i = 0; i < topics_sub_cnt; i++) { client.subscribe(mqtt_topics_sub[i]); Serial.print("Subscribed to topic: "); Serial.println(mqtt_topics_sub[i]); } Serial.println("MQTT已连接");}
void mqtt_callback(char *topic, byte *payload, unsigned int len){ Serial.print("Message arrived on topic: "); Serial.print(topic); Serial.print(". Message: ");
String rec; for (int i = 0; i < len; i++) rec += (char)payload[i]; Serial.println(rec);
if (strcmp(topic, "openmv/voice") == 0){ if (voice.isBusy() == false) voice.speech(rec.c_str()); }}
void network_init(){ /* 初始化WIFI */ const char *ssid = "foresp1"; const char *password = "20040724";
Serial.println(); Serial.print("正在连接到 "); Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.print("."); }
Serial.println(""); Serial.println("WiFi 已连接"); Serial.println("IP 地址: "); Serial.println(WiFi.localIP());
/* 初始化MQTT */ client.setServer(mqtt_server, mqtt_port); client.setCallback(mqtt_callback); mqtt_connect();}
void setup(){ Serial.begin(115200); Serial.println("Serial start..."); network_init();
pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); // 初始化语音模块,波特率为9600,引脚16为TX,17为RX}
void loop(){ client.loop(); if (!client.connected()) mqtt_connect(); // if (voice.isBusy() == false) // { // voice.speech("你好!"); // } digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}队列实现缓冲区
可以发现还是有一个问题,就是因为这里我没有实现打断说话功能,因此正在说话时收到的消息就被丢弃了。
可以用一个queue实现缓冲区(c++真爽写吧,换其他只支持c的单片机就要写好多)。此外,针对字节上限,也要做一个分割。
#include "stdint.h"#include "string.h"
#include "queue"using namespace std;
#include <WiFi.h>#include <PubSubClient.h>
#include "VoiceModule.h"
uint8_t led_pin = 2;
HardwareSerial voiceSerial(1); // 定义串口实例VoiceModule voice(voiceSerial, 12); // 创建语音模块实例,busy_pin为12
const char *mqtt_server = "113.45.173.169";const int mqtt_port = 1883;const char *mqtt_user = "voice";const char *mqtt_topic = "openmv/test";
WiFiClient espClient;PubSubClient client(espClient);
const char *mqtt_topics_sub[] = { "openmv/test", "openmv/voice", "sport_cmd"};const int topics_sub_cnt = sizeof(mqtt_topics_sub) / sizeof(mqtt_topics_sub[0]);
queue<String> voice_queue; // 语音队列
void mqtt_connect(){ Serial.println("MQTT连接中..."); while (!client.connected()) { if (client.connect(mqtt_user)) break; else { delay(100); Serial.print("."); } } for (int i = 0; i < topics_sub_cnt; i++) { client.subscribe(mqtt_topics_sub[i]); Serial.print("Subscribed to topic: "); Serial.println(mqtt_topics_sub[i]); } Serial.println("MQTT已连接");}
void mqtt_callback(char *topic, byte *payload, unsigned int len){ Serial.print("Message arrived on topic: "); Serial.print(topic); Serial.print(". Message: ");
String rec; for (int i = 0; i < len; i++) rec += (char)payload[i]; Serial.println(rec);
if (strcmp(topic, "openmv/voice") == 0) voice_queue.push(rec);}
void network_init(){ /* 初始化WIFI */ const char *ssid = "foresp1"; const char *password = "20040724";
Serial.println(); Serial.print("正在连接到 "); Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.print("."); }
Serial.println(""); Serial.println("WiFi 已连接"); Serial.println("IP 地址: "); Serial.println(WiFi.localIP());
/* 初始化MQTT */ client.setServer(mqtt_server, mqtt_port); client.setCallback(mqtt_callback); mqtt_connect();}
void setup(){ Serial.begin(115200); Serial.println("Serial start..."); network_init();
pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); // 初始化语音模块,波特率为9600,引脚16为TX,17为RX}
void loop(){ client.loop(); if (!client.connected()) mqtt_connect(); if (voice.isBusy() == false) { if (!voice_queue.empty()) { voice.speech(voice_queue.front().c_str()); voice_queue.pop(); } } digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100);}