Funpack2-5---基于ESP32-S3-BOX-LITE的天气播报系统
使用ESP32的WiFi和TTS功能,实现一个语音播报系统,联网获取获取天气并播报。
标签
ESP32-S3
ESP32-S3-BOX-LITE
Funpack2-5
天气播报
冷月烟
更新2023-08-01
645

任务目的

使用ESP32的WiFi和TTS功能,实现一个语音播报系统,联网获取获取天气并播报。

设计思路

本项目大致可分为三部分设计

  1. 天气状况获取。
  2. 天气状态显示。
  3. 语音播报。

具体如下:

天气状况获取

  1. 访问网络:通过esp32 wifi接口去连接家中路由器,进一步去访问网络。
  2. 获取天气:通过心知天气接口获取当前天气状况。
  3. JSON解析:解析收到的心知天气数据返回,解析出具体内容并使用。

天气状态显示

  1. 屏幕驱动:直接使用box驱动库去初始化屏幕并使用。
  2. 图片显示:使用lvgl驱动屏幕,初始化spifs文件系统,将天气图片放入其中。

语音播报

  1. 声卡驱动:直接使用box驱动库去初始化声卡并使用。
  2. 播报音频:使用esp32 tts功能生成具体音频并播放出来。

硬件介绍

FgNyhpc6qFQ0t2a_W0-zKvPNFBW2

ESP32-S3-BOX-LITE

ESP32-S3-BOX AI语音开发套件是乐鑫打造的一个智能语音设备开发平台

ESP32-S3-BOX 既可以用于构建智能音箱,也可以赋能更多物联网设备实现人机语音交互。同时,它还集触摸屏控制、传感器、红外控制器和智能网关等多功能于一体,能够作为全屋设备的控制中枢,支持用户通过语音命令控制或触屏控制,轻松实现圈内智能联动。

ESP32-S3-BOX-Lite 是目前对应的 AIoT 应用开发板,搭载支持 AI 加速的 ESP32-S3 Wi-Fi + Bluetooth 5 (LE) SoC。为用户提供了一个基于语音助手、传感器、红外控制器和智能 Wi-Fi 网关等功能开发和控制智能家居设备的平台。开发板出厂支持离线语音交互功能,用户通过乐鑫丰富的 SDK 和解决方案,能够轻松构建在线和离线语音助手、智能语音设备、HMI 人机交互设备、控制面板、多协议网关等多样的应用。

支持特性:

  • 双麦克风支持远场语音交互
  • 高唤醒率的离线语音唤醒
  • 高识别率的离线中英文命令词识别
  • 可动态配置 200+ 中英文命令词
  • 连续识别和唤醒打断
  • 灵活可复用的 GUI 框架
  • 端到端一站式接入云平台
  • Pmod™ 兼容接口支持多种外设扩展

核心微控制器:ESP32-S3

ESP32-S3 是一款集成 2.4 GHz Wi-Fi 和 Bluetooth 5 (LE) 的 MCU 芯片,支持远距离模式 (Long Range)。ESP32-S3 搭载 Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz,内置 512 KB SRAM (TCM),具有 45 个可编程 GPIO 管脚和丰富的通信接口。ESP32-S3 支持更大容量的高速 Octal SPI flash 和片外 RAM,支持用户配置数据缓存与指令缓存。

  • Xtensa® 32 位 LX7 双核处理器,主频高达 240 MHz
  • 内置 512 KB SRAM、384 KB ROM 存储空间,并支持多个外部 SPI、Dual SPI、 Quad SPI、Octal SPI、QPI、OPI flash 和片外 RAM
  • 额外增加用于加速神经网络计算和信号处理等工作的向量指令 (vector instructions)
  • 45 个可编程 GPIO,支持常用外设接口如 SPI、I2S、I2C、PWM、RMT、ADC、UART、SD/MMC 主机控制器和 TWAITM 控制器等
  • 基于 AES-XTS 算法的 Flash 加密和基于 RSA 算法的安全启动,数字签名和 HMAC 模块,“世界控制器 (World Controller)”模块

代码流程图

Fr3RkT3IrWmMK8QPDIsFeJQidy65

主要代码

main代码

void app_main(void)
{
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(bsp_board_init());
    ESP_ERROR_CHECK(bsp_board_power_ctrl(POWER_MODULE_AUDIO, true));
    ESP_ERROR_CHECK(bsp_spiffs_init_default());
    ESP_ERROR_CHECK(lv_port_init());
    bsp_lcd_set_backlight(true);
    bsp_codec_set_voice_volume(100);

    user_init();
    app_event_group = xEventGroupCreate();
    wifi_init_sta();    
    xTaskCreate(sntp_task, "sntp_task", 2048, NULL, 9, NULL);
    xTaskCreate(weather_task, "weather_task", 3072, NULL, 8, NULL);
    xTaskCreate(user_task, "user_task", 4096, NULL, 10, NULL);

    do {
        lv_task_handler();
    } while (vTaskDelay(1), true);
}

wifi连接部分

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { 
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < WIFI_MAX_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_netif_init());

    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = WIFI_SSID,
            .password = WIFI_PASSWORD,
	        .threshold.authmode = WIFI_AUTH_WPA2_PSK,
            .pmf_cfg = {
                .capable = true,
                .required = false
            },
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); 
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_start() );
    ESP_LOGI(TAG, "wifi_init_sta finished.");      

    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 WIFI_SSID, WIFI_PASSWORD);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 WIFI_SSID, WIFI_PASSWORD);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }

    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);

    xEventGroupSetBits(app_event_group, WIFI_INIT_BIT);
}

天气获取部分

void weather_task(void *param)
{
    int content_length = 0;
    esp_http_client_config_t http_client_cfg = {
        .url = weather_url,
    };
    esp_http_client_handle_t http_client_handle = esp_http_client_init(&http_client_cfg);
    esp_http_client_set_method(http_client_handle, HTTP_METHOD_GET); 
    memset(&weather, 0, sizeof(weather_t));
    memset(&response_body, 0, sizeof(response_body));
    while (1)
    {
        esp_err_t err = esp_http_client_open(http_client_handle, 0);

        if (err != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
            goto __fail_delay;
        }
        content_length = esp_http_client_fetch_headers(http_client_handle);
        if (content_length < 0 || content_length > RESPONSE_BODY_MAX_SIZE) 
        {
            ESP_LOGE(TAG, "HTTP client fetch headers failed");
            goto __fail_delay;
        }

        if (esp_http_client_read_response(http_client_handle, response_body, RESPONSE_BODY_MAX_SIZE) < 0)
        {
            ESP_LOGE(TAG, "Failed to read response");
            goto __fail_delay;
        }

        ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %d",
                    esp_http_client_get_status_code(http_client_handle),
                    esp_http_client_get_content_length(http_client_handle)); 
        printf("%s\n", response_body);

        cjson_parse_xinzhi_weather(response_body);
        esp_http_client_close(http_client_handle);
        memset(&response_body, 0, sizeof(response_body));
        
    __fail_delay:
        for (size_t i = 0; i < REQUEST_INTERVAL * 60; i++)
            vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

显示与播报部分

void user_task(void *param)
{
    const esp_partition_t* part=esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, "voice_data");
    if (part==0) printf("Couldn't find voice data partition!\n");
    spi_flash_mmap_handle_t mmap;
    uint16_t* voicedata;
    esp_partition_mmap(part, 0, part->size, SPI_FLASH_MMAP_DATA, (const void**)&voicedata, &mmap); //3*1024*1024
    esp_tts_voice_t *voice=esp_tts_voice_set_init(&esp_tts_voice_xiaole, voicedata); 
    esp_tts_handle_t *tts_handle=esp_tts_create(voice);

    char *text="欢迎使用天气小助手";	
    size_t bytes_written = 0;
    if (esp_tts_parse_chinese(tts_handle, text)) {  // parse text into pinyin list
        int len[1]={0};
        do {
            short *data=esp_tts_stream_play(tts_handle, len, 4); // streaming synthesis   
            i2s_write(I2S_NUM_0, data, len[0]*2, &bytes_written, portMAX_DELAY);          
        } while(len[0]>0);
        i2s_zero_dma_buffer(I2S_NUM_0);
    }
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    while (1)
    {   
        EventBits_t app_event_bit = xEventGroupWaitBits(app_event_group,
                                    NTP_UPDATE_BIT | WEATHER_UPDATE_BIT,
                                    pdTRUE, 
                                    pdFALSE,
                                    portMAX_DELAY);
        
        if (app_event_bit & WEATHER_UPDATE_BIT) 
        {
            char text[45];

            sprintf(text, "S:/spiffs/%d@2x.png", weather.code_day[0]);
            lv_obj_t *img = lv_img_create(lv_scr_act());
            lv_img_set_src(img, text);
            lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);

            sprintf(text, "今天白天天气:%s", weather.text_day[0]);
            size_t bytes_written = 0;
            if (esp_tts_parse_chinese(tts_handle, text)) {  // parse text into pinyin list
                int len[1]={0};
                do {
                    short *data=esp_tts_stream_play(tts_handle, len, 4); // streaming synthesis   
                    i2s_write(I2S_NUM_0, data, len[0]*2, &bytes_written, portMAX_DELAY);          
                } while(len[0]>0);
                i2s_zero_dma_buffer(I2S_NUM_0);
            }
            vTaskDelay(300 / portTICK_PERIOD_MS);

            sprintf(text, "夜晚天气:%s", weather.text_night[0]);
            bytes_written = 0;
            if (esp_tts_parse_chinese(tts_handle, text)) {  // parse text into pinyin list
                int len[1]={0};
                do {
                    short *data=esp_tts_stream_play(tts_handle, len, 4); // streaming synthesis   
                    i2s_write(I2S_NUM_0, data, len[0]*2, &bytes_written, portMAX_DELAY);          
                } while(len[0]>0);
                i2s_zero_dma_buffer(I2S_NUM_0);
            }
            vTaskDelay(300 / portTICK_PERIOD_MS);

            sprintf(text, "最高气温:%d度", weather.degree_high[0]);
            bytes_written = 0;
            if (esp_tts_parse_chinese(tts_handle, text)) {  // parse text into pinyin list
                int len[1]={0};
                do {
                    short *data=esp_tts_stream_play(tts_handle, len, 4); // streaming synthesis   
                    i2s_write(I2S_NUM_0, data, len[0]*2, &bytes_written, portMAX_DELAY);          
                } while(len[0]>0);
                i2s_zero_dma_buffer(I2S_NUM_0);
            }
            vTaskDelay(300 / portTICK_PERIOD_MS);

            sprintf(text, "最低气温:%d度", weather.degree_low[0]);
            bytes_written = 0;
            if (esp_tts_parse_chinese(tts_handle, text)) {  // parse text into pinyin list
                int len[1]={0};
                do {
                    short *data=esp_tts_stream_play(tts_handle, len, 4); // streaming synthesis   
                    i2s_write(I2S_NUM_0, data, len[0]*2, &bytes_written, portMAX_DELAY);          
                } while(len[0]>0);
                i2s_zero_dma_buffer(I2S_NUM_0);
            }
            vTaskDelay(300 / portTICK_PERIOD_MS);

            sprintf(text, "湿度:百分之%d", weather.humidity[0]);
            bytes_written = 0;
            if (esp_tts_parse_chinese(tts_handle, text)) {  // parse text into pinyin list
                int len[1]={0};
                do {
                    short *data=esp_tts_stream_play(tts_handle, len, 4); // streaming synthesis   
                    i2s_write(I2S_NUM_0, data, len[0]*2, &bytes_written, portMAX_DELAY);          
                } while(len[0]>0);
                i2s_zero_dma_buffer(I2S_NUM_0);
            }
        }
        vTaskDelay(5 / portTICK_PERIOD_MS);
    }
}

功能演示

显示多云图片

FgV3vtc4fgCx57WtWNpUwkvvPcmf

获取心知天气返回

FtQPAOvN6UDNg89icw1LSDPL4z6V

语音播报”欢迎使用天气小助手“

FsbE3sCwH3wQ0LsaTyZDKI65mJt8

语音播报”今天白天天气多云 夜晚天气多云“

FpsJfxXA0PRJqn_3ntaHTXHqgl0V

代码使用说明

需要下载音频数据包,设置如下。

FigFHQbrHYyxKgJzDzJSwPXQbaWk

由于此代码大量依赖了box的驱动代码,因此需要放到xxxx\esp-box\examples\demo目录下。

FoANVuogr254M65qVbg19gO6m9rb

因为tts生成的音频数据为16KHz,单声道的,因此需要修改box音频驱动。

FioODM2H3mYjaiDnxYdznFm-bvE5

需要修改wifi名称与密码为自己可连接的wifi

Fi9VUbNnaQKrZKn80bHs_TYUl73s

需要修改心知天气私钥,用于访问心知天气服务。

FjjgzqSI4iJdHGM2WbetxmxjWO10

心得体会

代码中也设计了ntp时间获取功能,下一步计划设计白天显示白天的天气,夜晚显示夜晚的天气。

5分钟播报一次还是太吵了,下一步计划将语音识别加进去,识别到播报天气命令后再播报今天的天气情况。

活动真不错,板子也挺好,题目也有趣,学习了不少新知识,挺好,新一期也下单了,马上第二期就要结束了,希望以后继续办下去。

附件下载
代码.zip
esp_tts_voice_data_xiaole.dat
团队介绍
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号