ESP32联网语音助手
在做的项目需要一个联网语音部分,联网接入大模型或者其他的API,同时给OpenMV发信号,单独做出来好了()。
ESP32回顾
先来点个灯回顾一下ESP32的基础操作。Arduino IDE选ESP32 Dev Module
。烧录一个点灯程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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。
烧录示例代码,烧录时不能接线,猜测是串口的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| 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厂商提供的上位机的转码是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #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;
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还很容易接反。)
第一遍写的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #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;
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.
可以类似这样查询(下面这段代码没测试,因为有更好的方法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| bool check_speaking(){ static char data[256]; static uint8_t len;
data[0] = 0xFD; data[1] = 0x00, data[2] = 0x02; data[3] = 0x21; 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引脚是低电平的时候,说明空闲。
通过判断空闲来连续发送的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #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; data[3] = 0x01; data[4] = 0x00;
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
。
代码和接线都改一下就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #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); }
void speech(const char str[]) { static char data[256]; static uint8_t len, str_len;
data[0] = 0xFD; data[3] = 0x01; data[4] = 0x00;
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include "stdint.h" #include "string.h"
class VoiceModule { private: HardwareSerial &voice_serial; uint8_t busy_pin;
public: 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; data[3] = 0x01; data[4] = 0x00;
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);
void setup() { pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); }
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框架运行的端口。
1 2 3 4
| SSID = "foresp1" # Network SSID KEY = "20040724" # Network key
MQTTClient("openmv", "113.45.173.169", port=1883)
|
MQTT服务器测试
先自己写个python测试一下MQTT的收发是否正常,我把服务器和订阅的信息都存在和python同目录的config.yaml
文件下,也就是:
1 2 3 4 5 6 7 8
| server: address: "113.45.173.169" port: 1883 topics: - "openmv/voice"
client: name: "aicoach"
|
发送的文件是sender.py
,测试接收的文件是listener.py
,这两个在不同的终端同时运行,就可以发送然后测试是否收到了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| ''' Author: wlaten Date: 2024-07-05 20:49:29 LastEditTime: 2024-07-23 01:49:12 Discription: file content ''' import os import paho.mqtt.client as mqtt import 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()
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| """ sender.py 模拟客户端,测试MQTT服务器收发消息 """
import os import paho.mqtt.client as mqtt import 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_connect client.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的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include "stdint.h" #include "string.h" #include <WiFi.h>
#include "VoiceModule.h"
uint8_t led_pin = 2;
HardwareSerial voiceSerial(1); VoiceModule voice(voiceSerial, 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); }
void loop() { if (voice.isBusy() == false) { voice.speech("你好!"); } digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100); }
|
然后写连接MQTT服务器的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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() { 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());
client.setServer(mqtt_server, mqtt_port); Serial.println("MQTT连接中..."); while (!client.connected()) { if (client.connect(mqtt_user)) break; else { delay(100); Serial.print("."); } } }
|
现在在服务器上查询,可以看见已经连接成功了。
但是现在会发现,尽管连接成功了,但是过一会就会断开,这是因为没有发送心跳包保持连接,所以要在loop
函数里面持续调用client.loop()
。
或者为了进一步保持连接稳定,加入掉线重连接功能,完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| #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);
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() { 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());
client.setServer(mqtt_server, mqtt_port); mqtt_connect(); }
void setup() { Serial.begin(115200); Serial.println("Serial start..."); network_init();
pinMode(led_pin, OUTPUT); voice.begin(9600, 16, 17); }
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
这个主题上。只需要随便在哪里这样写一下就好:
1 2
| String message = "Hello from ESP32 你好!"; client.publish("openmv/test", message.c_str());
|
这时候我们前面的python监听程序要改一下,因为监听默认是utf8格式。
1 2 3 4 5 6 7
| def on_message(client, userdata, msg): try: message = msg.payload.decode('utf-8') except UnicodeDecodeError: message = msg.payload.decode('gbk') topic = msg.topic print(f"Received message '{message}' on topic '{topic}'")
|
现在可以成功收到这样的消息了。
ESP32接收MQTT消息
MQTT首先要订阅,然后接收。
首先确定订阅主题:
1 2 3 4 5
| const char *mqtt_topics_sub[] = { "openmv/test", "openmv/voice", "sport_cmd"}; const int num_topics = sizeof(topics) / sizeof(topics[0]);
|
然后修改连接函数,在连接中订阅:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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已连接"); }
|
还需要设置回调函数:
1
| client.setCallback(mqtt_callback);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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格式。
1 2 3 4
| while True: topic = input("请输入主题: ") message = input("请输入消息内容: ").encode('gbk') client.publish(topic, message)
|
可以发现现在是可以正常收发的。
现在整套代码基本实现了通过mqtt协议,网络传入中文字符串,语音模块发声。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| #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);
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() { 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());
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); }
void loop() { client.loop(); if (!client.connected()) mqtt_connect(); digitalWrite(led_pin, HIGH); delay(100); digitalWrite(led_pin, LOW); delay(100); }
|
队列实现缓冲区
可以发现还是有一个问题,就是因为这里我没有实现打断说话功能,因此正在说话时收到的消息就被丢弃了。
可以用一个queue
实现缓冲区(c++真爽写吧,换其他只支持c的单片机就要写好多)。此外,针对字节上限,也要做一个分割。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| #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);
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() { 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());
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); }
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); }
|