项目要求
该项目要求基于ESP32-S3-BOX-LITE开发套件实现一个实时天气播报系统,使用ESP32的WiFi和TTS功能。
板卡介绍
ESP32-S3-BOX-LITE是乐鑫发布的一款AIoT开发套件,具备以下主要特性:
- 配备了一块2.4寸LCD显示屏,可实现人机交互界面。
- 内置双麦克风,支持远场语音交互。
- 配备一个扬声器,可用于语音播放。
- 提供两个Pmod™兼容接口,支持硬件拓展。
- 搭载乐鑫自研的高性能声学前端算法,可实现离线语音唤醒和命令词识别。
- 支持在线离线语音助手、智能语音设备、HMI人机交互设备、多协议网关等多种应用。
实现思路
- 使用按键或唤醒词“Hello ESP”来唤醒系统,进入待唤醒状态。
- 在待唤醒状态下,监听WiFi连接状态,一旦连接成功则开始获取实时天气信息。
- 通过天气API发送请求,获取当地的实时天气信息。
- 将获取到的天气信息进行解析,并通过TTS技术将其转换为语音。
- 使用扬声器进行语音播放,将实时天气信息播报出来。
- 播放完成后,系统进入待唤醒状态,等待下一次唤醒。
实现方法
1. 配置WiFi连接:在代码中配置WiFi的SSID和密码,确保能够成功连接到WiFi网络。
CONFIG_ESP_WIFI_SSID="HUAWEI P30 Pro 5G"
CONFIG_ESP_WIFI_PASSWORD="password"
CONFIG_OPENAI_API_KEY=""
CONFIG_VOICE_ID="qiumum_0gushi"
CONFIG_VOLUME_LEVEL=70
CONFIG_MAX_TOKEN=500
CONFIG_ESP_MAXIMUM_RETRY=5
CONFIG_MESSAGE_CONTENT_SIZE=3072
CONFIG_ESP_WIFI_AUTH_OPEN=y
WiFi 配置和以往的配置方式都不同,这个配置是在 sdkconfig 中的。代码中会通过宏定义的方式来引用。
2. 配置天气API:根据天气API的要求,配置相应的请求参数和地址。
esp_err_t create_caiyun_request(const char *content) {
char url[128] = "https://api.caiyunapp.com/v2.6/TAkhjf8d1nlSlspN/"
"120.247718,30.203515/realtime";
esp_http_client_config_t config = {
.url = url,
.method = HTTP_METHOD_GET, // 使用 GET 请求
.event_handler = response_handler,
.buffer_size = MAX_HTTP_RECV_BUFFER,
.timeout_ms = 30000,
.crt_bundle_attach = esp_crt_bundle_attach,
};
uint32_t starttime = esp_log_timestamp();
// Set the headers
esp_http_client_handle_t client = esp_http_client_init(&config);
// Send the request
esp_err_t err = esp_http_client_perform(client);
if (err != ESP_OK) {
ESP_LOGW(TAG, "HTTP GET request failed: %s\n", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
return err;
}
3. 编写唤醒逻辑:使用按键或唤醒词“Hello ESP”来唤醒系统,并进入待唤醒状态。
这里设置唤醒动作为跳转到唤醒界面。如果按键按下或者语音匹配,那么就会进入这个跳转函数。在函数跳转后,当前页面就从睡眠状态切换到了唤醒状态。
if (WAKENET_DETECTED == result.wakenet_mode) {
// UI show listen
ui_ctrl_guide_jump();
ui_ctrl_show_panel(UI_CTRL_PANEL_LISTEN, 0);
audio_play_task("/spiffs/echo_en_wake.wav");
start_openai();
continue;
}
4. 解析天气信息:对获取到的天气信息进行解析,提取所需的天气数据。
由彩云天气返回的 JSON 格式并不能直接输出到 TTS 进行文字转语音,需要先解析出需要的内容,拼接成完整的字符串,然后再送入 TTS 进行文字转语音。
/* Parsing response coming from server */
void parse_response(const char *data, int len) {
jparse_ctx_t jctx;
int ret = json_parse_start(&jctx, data, len);
if (ret != OS_SUCCESS) {
ESP_LOGE(TAG, "Parser failed");
return;
}
if (json_obj_get_object(&jctx, "result") == OS_SUCCESS) {
if (json_obj_get_object(&jctx, "realtime") == OS_SUCCESS) {
float temperature = 0, humidity = 0, cloudrate = 0;
char skycon[32] = {};
if (json_obj_get_float(&jctx, "temperature", &temperature) ==
OS_SUCCESS &&
json_obj_get_float(&jctx, "humidity", &humidity) == OS_SUCCESS &&
json_obj_get_float(&jctx, "cloudrate", &cloudrate) == OS_SUCCESS &&
json_obj_get_string(&jctx, "skycon", skycon, sizeof(skycon)) ==
OS_SUCCESS) {
// 将英文的 skycon 转换为中文
const char *chinese_skycon = translate_skycon_to_chinese(skycon);
sprintf(
message_content,
"根据最新的天气数据,现在预计为%s。当前气温为 %.1f "
"摄氏度,湿度为百分之"
"%.1f,云量为百分之"
"%.1f"
"。建议您在外出时注意天气情况,可能需要携带伞具或选择适当的服装。",
chinese_skycon, temperature, humidity * 100, cloudrate * 100);
// 输出日志
ESP_LOGI(TAG, "%s", message_content);
} else {
ESP_LOGE(TAG, "Error in parse response data");
}
} else {
ESP_LOGE(TAG, "%d, %s", len, data);
ESP_LOGE(TAG, "Error in parse realtime data");
}
} else {
ESP_LOGE(TAG, "%d, %s", len, data);
ESP_LOGE(TAG, "Error in parse result data");
}
}
这里提取了天气、温度、湿度、云量的信息,并拼接成完整的内容输出。
5. TTS转换:使用TTS技术将提取到的天气数据转换为语音。
通过 WiFi 请求 TTS 服务器,获取到文字转语音的音频文件。
esp_err_t text_to_speech_request(const char *message, AUDIO_CODECS_FORMAT code_format)
{
size_t message_len = strlen(message);
char *encoded_message;
char *codec_format_str;
encoded_message = heap_caps_malloc((3 * message_len + 1), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
url_encode(message, encoded_message);
if (AUDIO_CODECS_MP3 == code_format) {
codec_format_str = "MP3";
} else {
codec_format_str = "WAV";
}
int url_size = snprintf(NULL, 0, "https://dds.dui.ai/runtime/v1/synthesize?voiceId=%s&text=%s&speed=1&volume=%d&audiotype=%s", \
VOICE_ID, \
encoded_message, \
VOLUME, \
codec_format_str);
// Allocate memory for the URL buffer
char *url = heap_caps_malloc((url_size + 1), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
if (url == NULL) {
ESP_LOGE(TAG, "Failed to allocate memory for URL");
return ESP_ERR_NO_MEM;
}
snprintf(url, url_size + 1, "https://dds.dui.ai/runtime/v1/synthesize?voiceId=%s&text=%s&speed=1&volume=%d&audiotype=%s", \
VOICE_ID, \
encoded_message, \
VOLUME, \
codec_format_str);
esp_http_client_config_t config = {
.url = url,
.method = HTTP_METHOD_GET,
.event_handler = http_event_handler,
.buffer_size = 128000,
.buffer_size_tx = 4000,
.timeout_ms = 30000,
.crt_bundle_attach = esp_crt_bundle_attach,
};
uint32_t starttime = esp_log_timestamp();
ESP_LOGE(TAG, "[Start] create_TTS_request, timestamp:%"PRIu32, starttime);
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "HTTP GET request failed: %s", esp_err_to_name(err));
}
ESP_LOGE(TAG, "[End] create_TTS_request, + offset:%"PRIu32, esp_log_timestamp() - starttime);
heap_caps_free(url);
heap_caps_free(encoded_message);
esp_http_client_cleanup(client);
return err;
}
6. 播放语音:通过扬声器进行语音播放,将实时天气信息播报出来。
这里直接通过传递信号量的方式,直接唤起音频播放线程。
ui_ctrl_reply_set_audio_start_flag(true);
7. 循环等待:播放完成后,系统进入待唤醒状态,等待下一次唤醒。
这里一样使用信号量,把音频播放信息传递到主线程,主线程完成状态的切换。
static void audio_play_finish_cb(void) {
ESP_LOGI(TAG, "replay audio end");
if (ui_ctrl_reply_get_audio_start_flag()) {
ui_ctrl_reply_set_audio_end_flag(true);
}
}
实物展示
遇到问题
- 配置WiFi连接时遇到了一些困难,需要确保正确输入SSID和密码,以及检查网络环境是否正常。
- 天气API的使用和解析过程中可能会遇到一些问题,需要仔细查阅API文档,并进行适当的错误处理。
- TTS转换和语音播放的质量问题,可能需要对TTS引擎进行调优或选择更好的语音合成技术。
总结感想
非常感谢硬禾学堂举办的活动,通过完成这个项目,我对ESP32-S3-BOX-LITE的功能和特性有了更深入的了解。同时,我也学习到了如何使用WiFi进行网络连接,如何通过API获取数据并进行解析,以及如何使用TTS技术进行语音合成和播放。在项目开发过程中,我遇到了一些困难和问题,但通过不断的尝试和学习,最终成功完成了这个实时天气播报系统。这个项目的完成不仅提升了我的技术能力,也增加了我的项目开发经验,让我更加熟悉了ESP32-S3-BOX-LITE的使用。我相信这个项目对于实际生活中的天气播报和语音交互应用有一定的实用价值,希望能够进一步优化和拓展这个系统的功能。