基于乐鑫SmartConfig的智能配网诗词助手
硬件使用ESP-BOX-LITE,使用乐鑫的SMART config智能配网技术,结合TTS以及HTTPS组件,获取网络诗词后,朗读出来。
标签
ESP32-S3
Funpack2-5
TTS
Smart Config
JSON
HTTPS
playlikework
更新2023-08-01
942

大家好!我来介绍一下参加本次FUNPACK活动的项目:基于乐鑫SmartConfig的智能配网诗词助手。项目的思路如下:

  1. 使用乐鑫的smart config智能配网功能来发送连接互联网所需要的SSID和PASSWORD;
  2. 使用乐鑫的IDF框架下的WIFI组件来实现连接互联网;
  3. 使用乐鑫的IDF框架下的HTTPS组件并使用今日诗词API来获取JSON格式的诗词;
  4. 使用乐鑫的IDF框架下的cJSON组件来解析Json格式数据并存储在UTF-8数据中;
  5. 使用乐鑫的skainet框架下的TTS功能来朗读步骤四中的数据

经过上述步骤,即可在任意的网络环境中都可以让ESP-BOX-LITE化身为可联网的诗词小助手。程序主流程图如下:

FuJdXQBnRBScOiSvy3IB6cg9rOtB

一、硬件介绍

ESP32-S3-BOX-Lite搭载 ESP32-S3 AI SoC,在芯片内置的 512 KB SRAM 之外,还集成了16 MB QSPI flash 和 8 MB Octal PSRAM。它板载一块2.4 寸显示屏(分辨率 320 x 240),双麦克风,一个扬声器和两个用于硬件拓展的 Pmod™ 兼容接口;采用 Type-C USB 连接器,提供 5 V 电源输入和串口/JTAG 调试接口。ESP32-S3-BOX-Lite的优秀表现离不开其主控芯片ESP32-S3,ESP32-S3是一款低功耗的 MCU 系统级芯片 (SoC),支持 2.4 GHz Wi-Fi 和低功耗蓝牙 (Bluetooth® LE) 双模无 线通信。芯片集成了高性能的 Xtensa® 32 位 LX7 双核处理器、超低功耗协处理器、Wi-Fi 基带、蓝牙基带、RF 模块以及外设,其功能框图如下所示:

FiSiiqi4IvSUaQ_Mjf9qmG1ptxIe

二、基于smart config的联网功能开发

乐鑫自主研发的 ESP-TOUCH 协议采用的是 Smart Config(智能配置)技术来帮助用户将嵌入了 ESP8266EX 的设备连接至 Wi-Fi 网络。用户只需在手机上进行简单操作即可实现智能配置。

Frwjmsvtj1dqb33NgFnaebdFzLfZ

下载乐鑫官方App:

 

 

FjGnlxyRrWTaOGFOpYzI4n4e_l2m

上面是手机app端,下面简要介绍一下wifi初始化以及代码层面如何使用smart config一键配网功能。

(1)初始化LwIP:创建LwIP核心任务并初始化与LwIP相关的工作。下面的函数在代码中由static void initialise_wifi(void)调用。

static void initialise_wifi(void) --- >  ESP_ERROR_CHECK(esp_netif_init());

(2)初始化事件。Wi-Fi驱动程序会将事件发送到默认事件循环。应用程序可以在相应的回调函数中处理这些事件。

    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
    ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
    ESP_ERROR_CHECK( esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );

(3)初始化Wi-Fi

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

(4)配置ESP32S3-BOX-LITE为STA模式,并启动Wi-Fi.

    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
    ESP_ERROR_CHECK( esp_wifi_start() );

(5)等待Wi-Fi驱动程序上报相应的Wi-Fi事件,并在回调函数中进行处理。

在步骤四中,启动了Wi-Fi,因此根据下面的代码,会创建一个名称为“smartconfig_example_task”的任务。在这个任务里,会进行基于ESP-Touch技术的smart config一键配网功能的实现。

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) {
        xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
    }
.......


static void smartconfig_example_task(void * parm)
{
    EventBits_t uxBits;
    ESP_ERROR_CHECK( esp_smartconfig_set_type(SC_TYPE_ESPTOUCH) );
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_smartconfig_start(&cfg) );
    while (1) {
        uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
        if(uxBits & CONNECTED_BIT) {
            ESP_LOGI(TAG, "WiFi Connected to ap");
        }
        if(uxBits & ESPTOUCH_DONE_BIT) {
            ESP_LOGI(TAG, "smartconfig over");
            esp_smartconfig_stop();
            vTaskDelete(NULL);
        }
    }
}

在没有打开手机app进行配网的时候,控制台输出窗口如下:

I (0) cpu_start: App cpu up.
I (302) cpu_start: Pro cpu start user code
I (302) cpu_start: cpu freq: 160000000
I (302) cpu_start: Application information:
I (305) cpu_start: Project name:     smart_config
I (310) cpu_start: App version:      1
I (315) cpu_start: Compile time:     Jun 24 2023 10:35:56
I (321) cpu_start: ELF file SHA256:  0dc4089d76d58dab...
I (327) cpu_start: ESP-IDF:          v4.4.4-dirty
I (332) heap_init: Initializing. RAM available for dynamic allocation:
I (339) heap_init: At 3FCA2348 len 000473C8 (284 KiB): D/IRAM
I (346) heap_init: At 3FCE9710 len 00005724 (21 KiB): STACK/DRAM
I (352) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAM
I (359) heap_init: At 600FE000 len 00002000 (8 KiB): RTCRAM
I (365) spi_flash: detected chip: gd
I (369) spi_flash: flash io: qio
I (374) sleep: Configure to isolate all GPIO pins in sleep state
I (380) sleep: Enable automatic switching of GPIO sleep configuration
I (387) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (409) I2S: DMA Malloc info, datalen=blocksize=1280, dma_buf_count=6
I (409) I2S: DMA Malloc info, datalen=blocksize=1280, dma_buf_count=6
I (419) I2S: I2S1, MCLK output by GPIO2
I (419) gpio: GPIO[46]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (449) pp: pp rom version: e7ae62f
I (449) net80211: net80211 rom version: e7ae62f
I (459) wifi:wifi driver task: 3fced6f4, prio:23, stack:6656, core=0
I (459) system_api: Base MAC address is not set
I (459) system_api: read default base MAC address from EFUSE
I (529) wifi_init: udp mbox: 6
I (529) wifi_init: tcp mbox: 6
I (529) wifi_init: tcp tx win: 5744
I (539) wifi_init: tcp rx win: 5744
I (539) wifi_init: tcp mss: 1440
I (549) wifi_init: WiFi IRAM OP enabled
I (549) wifi_init: WiFi RX IRAM OP enabled
I (559) phy_init: phy_version 540,a5d905b,Oct 20 2022,19:36:11
I (599) wifi:mode : sta (f4:12:fa:d6:6d:64)
I (599) wifi:enable tsf
I (599) Inside app_main: good job
I (649) smartconfig: SC version: V3.0.1
I (5459) wifi:ic_enable_sniffer
I (5459) smartconfig: Start to find channel...
I (5459) smartconfig_example: Scan done
I (10599) Inside app_main: good job
I (20599) Inside app_main: good job

在通过ESP-TOUCH成功配网后,控制台输出如下:

FpLs9u5s0hRnKc2tmw4noypyfLae

I (110599) Inside app_main: good job
I (120599) Inside app_main: good job
I (127379) smartconfig: TYPE: ESPTOUCH
I (127379) smartconfig: T|AP MAC: 20:6b:e7:bb:8c:3b
I (127379) smartconfig: Found channel on 11-2. Start to get ssid and password...
I (127389) smartconfig_example: Found channel
I (130599) Inside app_main: good job
I (135559) smartconfig: T|pswd: xxx
I (135569) smartconfig: T|ssid: TP-LINK_168
I (135569) smartconfig: T|bssid: 20:6b:e7:bb:8c:3b
I (135569) wifi:ic_disable_sniffer
I (135569) smartconfig_example: Got SSID and password
I (135579) smartconfig_example: SSID:TP-LINK_168
I (135579) smartconfig_example: PASSWORD:xxxxxx
I (135589) wifi:new:<11,2>, old:<11,2>, ap:<255,255>, sta:<11,2>, prof:1
I (135589) wifi:state: init -> auth (b0)
I (135599) wifi:state: auth -> assoc (0)
I (135609) wifi:state: assoc -> run (10)
I (135619) wifi:connected with TP-LINK_168, aid = 6, channel 11, 40D, bssid = 20:6b:e7:bb:8c:3b
I (135619) wifi:security: WPA2-PSK, phy: bgn, rssi: -44
I (135619) wifi:pm start, type: 1

I (135619) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 0, mt_pti: 25000, mt_time: 10000
I (135629) wifi:<ba-add>idx:0 (ifx:0, 20:6b:e7:bb:8c:3b), tid:0, ssn:0, winSize:64
I (135669) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (136629) esp_netif_handlers: sta ip: 192.168.0.106, mask: 255.255.255.0, gw: 192.168.0.1
I (136629) smartconfig_example: Hi Debug: got ip:192.168.0.106
I (136629) smartconfig_example: WiFi Connected to ap

至此,已经实现了通过ESP-TOUCH的智能配网功能,让ESP-BOX-LITE连接到了互联网上。接下来会通过HTTPS组件来获取json格式的诗词数据。

三、通过HTTPS组件获取json格式诗词

在事件回调函数中,设计了如果获取IP地址成功后,会创建一个新的任务:poem_read_task。

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);

        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "Hi Debug: got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        xTaskCreate(poem_read_task, "poem_read_task", 4096, NULL, 3, NULL);

    }

static void poem_read_task(void * parm)
{
    VoiceSetInit();
    SpeakThisText("欢迎使用在线诗词小助手");
    vTaskDelay(pdMS_TO_TICKS(2000));

    while (1) {
        ESP_LOGI("Inside poem_read_task", "well done");
		http_native_request();
        vTaskDelay(pdMS_TO_TICKS(1*10*1000)); 
    }
}

一个主要的目的是每10s中获取一次诗词,并朗读。

static void http_native_request(void)
{
    memset(output_buffer, 0, sizeof(output_buffer));
    //char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};   // Buffer to store response of http request
    int content_length = 0;
    esp_http_client_config_t config = {
        .url = "http://v1.jinrishici.com/all.json",
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    // GET Request
    esp_http_client_set_method(client, HTTP_METHOD_GET);
    esp_err_t err = esp_http_client_open(client, 0);
    if (err != ESP_OK) {
        ESP_LOGE(TAG_HTTP_CLIENT, "Failed to open HTTP connection: %s", esp_err_to_name(err));
    } else {
        content_length = esp_http_client_fetch_headers(client);
        if (content_length < 0) {
            ESP_LOGE(TAG_HTTP_CLIENT, "HTTP client fetch headers failed");
        } else {
            int data_read = esp_http_client_read_response(client, output_buffer, MAX_HTTP_OUTPUT_BUFFER);
            if (data_read >= 0) {
                ESP_LOGI(TAG_HTTP_CLIENT, "HTTP GET Status = %d, content_length = %d",
                esp_http_client_get_status_code(client),
                esp_http_client_get_content_length(client));
                //ESP_LOG_BUFFER_HEX(TAG, output_buffer, data_read);
                ESP_LOGI( TAG_HTTP_CLIENT, "Data %s \r\n",  output_buffer);
                parse_poem_json(output_buffer);
            } else {
                ESP_LOGE(TAG_HTTP_CLIENT, "Failed to read response");
            }
        }
    }
    esp_http_client_close(client);
}

四、利用cJson组件解析诗词

解析今日诗词返回的json格式的数据,乐鑫的IDF有个cjson组件,可以非常方便的进行json操作。

#include "cJSON.h"

static bool parse_poem_json(char *analysis_buf)
{
    if( analysis_buf == NULL )
    {
        ESP_LOGI("parse_poem_json: ", "Parse Fail\r\n" );
        return false;
    }

    cJSON * json_root = cJSON_Parse(analysis_buf);
    if( json_root != NULL )
    {
        cJSON *cjson_category = cJSON_GetObjectItem(json_root,"category");
        cJSON *cjson_author = cJSON_GetObjectItem(json_root,"author");
        cJSON *cjson_origin = cJSON_GetObjectItem(json_root,"origin");
        cJSON *cjson_content = cJSON_GetObjectItem(json_root,"content");

        ESP_LOGI("parse_poem_json: ", "类别 -> %s\r\n", cjson_category->valuestring );
        SpeakThisText(cjson_category->valuestring); vTaskDelay(pdMS_TO_TICKS(1*1*1000)); 

        ESP_LOGI("parse_poem_json: ", "作者 -> %s\r\n", cjson_author->valuestring );
        SpeakThisText("作者"); vTaskDelay(pdMS_TO_TICKS(1*1*1000));
        SpeakThisText(cjson_author->valuestring); vTaskDelay(pdMS_TO_TICKS(1*1*1000));

        ESP_LOGI("parse_poem_json: ", "来源 -> %s\r\n", cjson_origin->valuestring );
        SpeakThisText(cjson_origin->valuestring); vTaskDelay(pdMS_TO_TICKS(1*1*1000));

        ESP_LOGI("parse_poem_json: ", "内容 -> %s\r\n", cjson_content->valuestring );
        SpeakThisText(cjson_content->valuestring); vTaskDelay(pdMS_TO_TICKS(1*1*1000));

        cJSON_Delete(json_root);
    }

    //free(analysis_buf);

    return true;
}

五、使用TTS功能朗读诗词

步骤四中会调用“SpeakThisText”对获取到诗词进行朗读。

void SpeakThisText(char * prompt1){
    printf("%s\n", prompt1);
    if (esp_tts_parse_chinese(tts_handle, prompt1)) {
            int len[1]={0};
            do {
                short *pcm_data=esp_tts_stream_play(tts_handle, len, 3);

                esp_audio_play(pcm_data, len[0]*2, portMAX_DELAY);
            } while(len[0]>0);
    }
    esp_tts_stream_reset(tts_handle);    	
	
}

控制台输出:

古诗文-山水-西湖
I (853229) tts_parser: unicode:0x53e4 -> gu3
I (853239) tts_parser: unicode:0x8bd7 -> shi1
I (853239) tts_parser: unicode:0x6587 -> wen2
I (853249) tts_parser: -
 : I (853249) tts_parser: jian3 
I (853259) tts_parser:

I (853259) tts_parser: unicode:0x5c71 -> shan1
I (853269) tts_parser: unicode:0x6c34 -> shui3
I (853269) tts_parser: -
 : I (853269) tts_parser: jian3
I (853279) tts_parser:

I (853279) tts_parser: unicode:0x897f -> xi1
I (853289) tts_parser: unicode:0x6e56 -> hu2
I (856199) parse_poem_json: : 作者 -> 仲殊

作者
I (856199) tts_parser: unicode:0x4f5c -> zuo4
I (856199) tts_parser: unicode:0x8005 -> zhe3
仲殊
I (857619) tts_parser: unicode:0x4ef2 -> zhong4
I (857619) tts_parser: unicode:0x6b8a -> shu1
I (859079) parse_poem_json: : 来源 -> 诉衷情·宝月山作

诉衷情·宝月山作
I (859079) tts_parser: unicode:0x8bc9 -> su4
I (859079) tts_parser: unicode:0x8877 -> zhong1
I (859079) tts_parser: unicode:0x60c5 -> qing2
W (859089) tts_parser: Warning:skip unknown word
I (859089) tts_parser: unicode:0x5b9d -> bao3
I (859099) tts_parser: unicode:0x6708 -> yue4
I (859099) tts_parser: unicode:0x5c71 -> shan1
I (859109) tts_parser: unicode:0x4f5c -> zuo4
I (860809) Inside app_main: good job
I (861589) parse_poem_json: : 内容 -> 西湖又还春晚,水树乱莺啼。

西湖又还春晚,水树乱莺啼。
I (861589) tts_parser: unicode:0x897f -> xi1
I (861589) tts_parser: unicode:0x6e56 -> hu2
I (861599) tts_parser: unicode:0x53c8 -> you4
I (861599) tts_parser: unicode:0x8fd8 -> hai2
I (861609) tts_parser: unicode:0x6625 -> chun1
I (861609) tts_parser: unicode:0x665a -> wan3
I (861619) tts_parser: unicode:0xff0c -> short pause
I (861619) tts_parser: unicode:0x6c34 -> shui3
I (861629) tts_parser: unicode:0x6811 -> shu4
I (861629) tts_parser: unicode:0x4e71 -> luan4
I (861639) tts_parser: unicode:0x83ba -> ying1
I (861639) tts_parser: unicode:0x557c -> ti2
I (870809) Inside app_main: good job

六、遇到的问题:

问题1:

Fknkgat2APVXIZVQbW6W3jD9BTRY

解决办法:

Fjsps-4US_3dl4cpuxCyj04ujuq3

问题2:

FpKmfkoS5rEKn0SLCAUoTsHpl3lX

解决办法:把Flash Size调节到16MB

问题3:

Fs0yGhkrv8kmHuUZ2dvZwcJq5Md9

解决办法:

FtseisjaF7BSfG85NIpcvyG1UZqh

七、总结:

经过本次funpack,比较深入的学习了乐鑫的IDF框架,还接触了esp-touch智能配网,以及通过https获取json格式数据并解析,最后使用tts功能进行朗读。

收货非常大,这都是以前没有尝试的。自己之前对于语音播放比较好奇,这次也算是明白到底怎么读出来的。

感谢硬禾学堂,祝funpack越来越好。

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