Funpack2-5 ESP32-S3-BOX-LITE获取bilibili粉丝数并进行LVGL展示和TTS语音合成播报
使用ESP32-BOX-LITE实现一个联网获取哔哩哔哩网站粉丝数并通过LVGL在LCD展示,TTS语音合成进行播报
标签
Funpack活动
ESP32
ESP32-S3-BOX-LITE
Funpack2-5
联网获取粉丝数
TTS语音播报
Snapdragon
更新2023-08-04
296

一、项目描述

    1、硬件介绍

        微控制器:本次Funpack活动使用的微控制器是ESP32-S3,是一款集成2.4GHz WiFi和Bluetooth5(LE)的MCU芯片,支持远距离模式。ESP32-S3搭载Xtensa®32位LR7双核处理器,主频高达240MHz,内置512KB的高速SRAM,有45个可编程GPIO管脚。

        板卡:本次活动使用的板卡是ESP32-S3-BOX-LITE。板卡板载Octal SPI 8MB SRAM和Quad SPI 16MB FLash,比单线SPI快了8倍和4倍,性能上可以保证很多项目的需求。显示使用了一块2.4寸、分辨率为320x240的液晶LCD,显示效果非常不错。有三个用户按键,1个复位按键和1个启动模式按键。外扩接口有2个Pmod™接口,可以外接众多外设。外壳材质为3D打印,对于开发者来说足够使用。除了硬件资源外,乐鑫在软件方面开发的也足够多,支持声学前端算法、支持远场噪音环境;离线语音支持自定义200+语音命令,打断唤醒、连续识别等;支持对接多种平台,如DuerOS、Alexa、天猫精灵等。

    2、项目介绍

        本次活动我选择的任务是任务一:使用ESP32的WiFi和TTS功能,实现一个语音播报系统,如联网获取粉丝数并播报或者获取天气并播报。

        我做的具体项目为:使用ESP32的WiFi连接到网络,获取哔哩哔哩某一用户的粉丝数、关注数并通过LVGL同步到LCD显示,同时TTS语音播报粉丝数量。

    3、项目思路

        哔哩哔哩开放了一个查询用户公开信息的API:https://api.bilibili.com/x/relation/stat?vmid={uid},通过填写用户的uid就可以查询对应的关注数、粉丝数。获取到的数据如下(以硬禾学堂的哔哩哔哩账号为例):“{"code":0,"message":"0","ttl":1,"data":{"mid":399210127,"following":9,"whisper":0,"black":0,"follower":52830}}”,很明显这是一个json数据,里面包含的“following”、“follower”对应关注数和粉丝数。可以通过JSON库将其分离出来,但是我在项目里面是通过字符串操作获取的,因为这个json串相对简单,不会出现重复性的内容。

        获取数据的前提是联上外网,ESP32有2.4GHz的WiFi功能,可以通过WiFi进行联网,设定SSID及其密码,就可以通过库去连接WiFi网络。

        ESP32-S3-BOX-LITE本身就TTS语音播报,初始化完成后,可以调用相对应的库去完成播报。

        关于显示部分,则使用LVGL进行图形设计并显示。

    3、项目基本流程图

       Fl8tRNQbfGsIxLcowVUAQzusaUkU

二、开发流程及代码说明

    1、开发前准备工作

        首先我们要先搭建开发环境,ESP32底层开发环境为esp-idf,ESP32-S3-BOX-LITE的板级开发环境为esp-box,TTS语音合成的开发环境是esp-skainet;由于Windows下搭建比较麻烦,这里使用Ubuntu23.04虚拟机进行环境搭建。

        三个开发环境均可在GitHub下载,链接如下:

        esp-idf:https://github.com/espressif/esp-idf

        esp-box:https://github.com/espressif/esp-box

        esp-skainet:https://github.com/espressif/esp-skainet

        使用git将以上三个库同步到本地电脑,并根据官方文档搭建好esp-idf的环境,其他两个只是库以及例程,不用做其他操作。

    2、选择对应的例程

        由于我没有使用过cmake环境,所以这里的开发均以官方例程为基础,进行改动及适配。首先WiFi连接使用esp-idf下的/examples/wifi/getting_started/station例程;获取API的例程使用esp-idf下的/examples/protocols/https_request例程;TTS部分使用esp-skainet下的/examples/chinese_tts例程;整体架构使用esp-box下的/examples/lv_demos例程,注意,这里最好将其复制出来,比如,我这里将其复制后重命名为funpack2-5。

    3、适配过程

        ① WiFi连接

               将station_example_main.c复制到工程目录下,并添加WiFi账号和密码

#define EXAMPLE_ESP_WIFI_SSID      "ssid" // 账号
#define EXAMPLE_ESP_WIFI_PASS      "pass" // 密码
#define EXAMPLE_ESP_MAXIMUM_RETRY  10 // 重试次数

#ifndef CONFIG_ESP_WIFI_AUTH_OPEN // 增加默认
#define CONFIG_ESP_WIFI_AUTH_OPEN  1
#endif

......

void wifi_connect(void)
// void app_main(void) // 更改函数名
{
    //Initialize NVS
    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_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();

    ESP_LOGI(TAG, "wifi_connect end");

    // while (1) // 此函数在此仅作初始化用
    // {
    //     static uint32_t count = 0;
    //     ESP_LOGI(TAG, "%u", count++);
    //     vTaskDelay(1000);
    // }
}

        ② 获取api数据

              根据

/* Root cert for howsmyssl.com, taken from server_root_cert.pem

   The PEM file was extracted from the output of this command:
   openssl s_client -showcerts -connect www.howsmyssl.com:443 </dev/null

   The CA root cert is the last cert given in the chain of certs.

   To embed it in the app binary, the PEM file is named
   in the component.mk COMPONENT_EMBED_TXTFILES variable.
*/

              获取api.bilibili.com的server_root_cert.pem,并将其复制到文件,放到工程目录,在CMakeLists.txt里面添加

idf_component_register(
    SRCS
        "station_example_main.c"
        "lv_demos.c"
        "https_request_example_main.c"
        "tts.c"
        EMBED_TXTFILES server_root_cert.pem)

              并对https_request_example_main.c进行如下修改

// 导入全局变量符号
extern volatile char follow_num_str[11];
extern volatile char fans_num_str[11];
extern volatile int  http_read;
...
...
        // 使用字符串操作函数对其进行分割,并保存到全局变量
        char *stmp = strstr(buf, "following");
        int index = 0;
        if (!stmp) {
            strcpy(follow_num_str, "error");
        } else {
            stmp += 11;
            while (*stmp != '\0' && *stmp != ',') {
                follow_num_str[index++] = *stmp;
                stmp++;
            }
            follow_num_str[index] = '\0';
        }

        stmp = strstr(buf, "follower");
        index = 0;
        if (!stmp) {
            strcpy(fans_num_str, "error");
        } else {
            stmp += 10;
            while (*stmp != '\0' && *stmp != '}') {
                fans_num_str[index++] = *stmp;
                stmp++;
            }
            fans_num_str[index] = '\0';
        }

        http_read++;

        ESP_LOGI(TAG, "follow: %s", follow_num_str);
        ESP_LOGI(TAG, "fans: %s", fans_num_str);

// 修改例程的app_main.c将其作为初始化的函数
// void app_main(void)
void bilibili_request_init()
{
    // ESP_ERROR_CHECK( nvs_flash_init() );
    // ESP_ERROR_CHECK(esp_netif_init());
    // ESP_ERROR_CHECK(esp_event_loop_create_default());

    /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
     * Read "Establishing Wi-Fi or Ethernet Connection" section in
     * examples/protocols/README.md for more information about this function.
     */
//    ESP_ERROR_CHECK(example_connect());

    xTaskCreate(&https_request_task, "https_get_task", 8192, NULL, 5, NULL);
}

        ③  TTS部分

               将例程中的main.c修改为tts.c

// TTS语音播报函数
void tts_speak(char *string)
{
    if (esp_tts_parse_chinese(tts_handle, string))
    {
        int len = 0;
        size_t bytes_write = 0;
        do
        {
            short *pcm_data = esp_tts_stream_play(tts_handle, &len, 0);
            i2s_write(I2S_NUM_0, pcm_data, len * 2, &bytes_write, portMAX_DELAY);
        } while (len > 0);
    }
    else
    {
        ESP_LOGE("tts", "Parse %s failed!\n", string);
        return ESP_FAIL;
    }
    esp_tts_stream_reset(tts_handle);

    return ESP_OK;
}
// 初始化,将tts_main除初始化部分删掉即可
    char *prompt1="欢迎使用乐鑫语音合成";  
    printf("%s\n", prompt1);

    tts_speak(prompt1);

    return 0;
}

                由于语音合成需要分区去存储声音文件,这里要对分区进行修改,修改CMakeLists.txt文件,增加如下内容。

add_definitions(-w)

set(voice_data_image ${PROJECT_DIR}/../../../esp-box/components/esp-sr/esp-tts/esp_tts_chinese/esp_tts_voice_data_xiaole.dat)
add_custom_target(voice_data ALL DEPENDS ${voice_data_image})
add_dependencies(flash voice_data)

partition_table_get_partition_info(size "--partition-name voice_data" "size")
partition_table_get_partition_info(offset "--partition-name voice_data" "offset")

if("${size}" AND "${offset}")
    esptool_py_flash_to_partition(flash "voice_data" "${voice_data_image}")
else()
    set(message "Failed to find model in partition table file"
                "Please add a line(Name=voice_data, Type=data, Size=3890K) to the partition file.")
endif()

        ④ 主函数

            LVGL界面初始化

    title = lv_obj_create(lv_scr_act());
    lv_obj_set_style_bg_color(title, lv_color_hex(0xA5A5A5), LV_STATE_DEFAULT);
    lv_obj_set_size(title, WIDTH, 20);
    title_label = lv_label_create(title);
    static char* title_str = "Funpack2-5";
    lv_label_set_text( title_label, title_str );
    lv_obj_align( title_label, LV_ALIGN_CENTER, 0, 0 );

    /*uid*/
    uid = lv_obj_create(lv_scr_act());
    lv_obj_set_style_bg_color(uid, lv_color_hex(0xFF0000), LV_STATE_DEFAULT);
    lv_obj_set_size(uid, WIDTH, 60);
    lv_obj_set_pos(uid, 0, 40);
    uid_label = lv_label_create(uid);
    lv_label_set_text( uid_label, uid_str );
    lv_obj_align( uid_label, LV_ALIGN_CENTER, 0, 0 );

    /*粉丝数量*/
    fans = lv_obj_create(lv_scr_act());
    lv_obj_set_style_bg_color(fans, lv_color_hex(0x00FF00), LV_STATE_DEFAULT);
    lv_obj_set_size(fans, WIDTH, 60);
    lv_obj_set_pos(fans, 0, 110);
    fans_label = lv_label_create(fans);
    lv_label_set_text( fans_label, fans_str );
    lv_obj_align( fans_label, LV_ALIGN_CENTER, 0, 0 );

    /*关注数量*/
    follow = lv_obj_create(lv_scr_act());
    lv_obj_set_style_bg_color(follow, lv_color_hex(0x0000FF), LV_STATE_DEFAULT);
    lv_obj_set_size(follow, WIDTH, 60);
    lv_obj_set_pos(follow, 0, 180);
    follow_label = lv_label_create(follow);
    lv_label_set_text( follow_label, follow_str );
    lv_obj_align( follow_label, LV_ALIGN_CENTER, 0, 0 );

        各部分初始化

    wifi_connect();
    bilibili_request_init();
    tts_main();

        业务循环

   do {
        lv_task_handler();
        if (http_read > 0) {
            http_read = 0;
            strcpy(follow_str, follow_title);
            strcat(follow_str, follow_num_str);
            strcpy(fans_str, fans_title);
            strcat(fans_str, fans_num_str);
            lv_label_set_text( follow_label, follow_str );
            lv_label_set_text( fans_label, fans_str );
            char tts_fans_str[32];
            strcpy(tts_fans_str, "粉丝数");
            strcat(tts_fans_str, fans_num_str);
            tts_speak(tts_fans_str);
        }
    } while (vTaskDelay(1), true);

三、功能展示说明

Fq0H9ox9_YwhvrYn-11rnuEL0vTb

    这里只能展示下LCD显示的内容,如上图,最上面是Funpack2-5的标题;下面红色框内为此次读取的UID(以硬禾学堂的为例),绿色框内为粉丝数量(52829)、蓝色框内为关注数量(9)。

四、对本活动的心得体会

    esp-idf的环境虽然搭建起来比较麻烦,但是使用起来确实很方便。另外通过此次活动也认识到了自己的不足,对很多框架比如cmake之类的知之甚少,今后还要加强学习。

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