ESP32实现物联网语音助手

21k words

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,串口选用UART0TXRX。会有声音出来。

消息协议

我希望实现一个输入文字能直接输出语音的效果,因此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; // 格式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还很容易接反。)

第一遍写的代码如下:

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; // 锟斤拷式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.

可以类似这样查询(下面这段代码没测试,因为有更好的方法):

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; // 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引脚是低电平的时候,说明空闲。

通过判断空闲来连续发送的代码如下:

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; // 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的第二个串口,也就是RXTX分别为1617

代码和接线都改一下就行。

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); // 初始化串口,波特率为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

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:
// 构造函数,传入串口和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框架运行的端口。

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" # MQTT服务器地址
port: 1883 # MQTT服务器端口
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

# 获取当前脚本文件的绝对路径,然后获取同目录下的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_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); // 创建语音模块实例,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服务器的代码。

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()
{
/* 初始化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()

或者为了进一步保持连接稳定,加入掉线重连接功能,完整代码:

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); // 创建语音模块实例,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这个主题上。只需要随便在哪里这样写一下就好:

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') # 尝试用UTF-8解码
except UnicodeDecodeError:
message = msg.payload.decode('gbk') # 如果失败,改用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);	// 加在network_init()里
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') // 编码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); // 创建语音模块实例,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的单片机就要写好多)。此外,针对字节上限,也要做一个分割。

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); // 创建语音模块实例,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);
}