diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 62d9fbc7..ae1b3303 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -257,6 +257,12 @@ if(CONFIG_USE_AUDIO_CODEC_ENCODE_OPUS OR CONFIG_USE_AUDIO_CODEC_DECODE_OPUS OR C "audio_codecs/es8388_audio_codec.cc") endif() +if(CONFIG_USE_EMOTION_REMOTE) + list(APPEND SOURCES "remote/emotion_remote.cc" + "remote/gapRemote.cc") + list(APPEND INCLUDE_DIRS "remote") +endif() + idf_component_register(SRCS ${SOURCES} EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS} INCLUDE_DIRS ${INCLUDE_DIRS} diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index fff31b75..eb3b2004 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -435,6 +435,13 @@ config SPEAKING_FIRST_MUTE_TIME default 0 help 讲话之前静音时间 + +config USE_EMOTION_REMOTE + bool "启用远程表情控制(doit_ai_two_eye眼部表情)" + default y + depends on !IDF_TARGET_ESP32C2 + help + 可用于控制 doit_ai_two_eye 做出对应的表情 config USE_AUDIO_CODEC_ENCODE_OPUS depends on BOARD_TYPE_DOIT_AI_01_KIT || BOARD_TYPE_DOIT_AI_01_KIT_LCD || BOARD_TYPE_DOIT_AI_02_KIT_LCD diff --git a/main/display/display.cc b/main/display/display.cc index 3f34ede0..5cb96a9d 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -11,6 +11,9 @@ #include "audio_codec.h" #include "settings.h" #include "assets/lang_config.h" +#ifdef CONFIG_USE_EMOTION_REMOTE +#include "emotion_remote.h" +#endif #define TAG "Display" @@ -176,6 +179,11 @@ void Display::UpdateStatusBar(bool update_all) { void Display::SetEmotion(const char* emotion) { + +#ifdef CONFIG_USE_EMOTION_REMOTE + EmotionRemote::GetInstance().SetEmotion(emotion); +#endif + struct Emotion { const char* icon; const char* text; diff --git a/main/display/lcd_display.cc b/main/display/lcd_display.cc index c304e08d..20055a7c 100644 --- a/main/display/lcd_display.cc +++ b/main/display/lcd_display.cc @@ -13,6 +13,10 @@ #include "board.h" +#ifdef CONFIG_USE_EMOTION_REMOTE +#include "emotion_remote.h" +#endif + #define TAG "LcdDisplay" // Color definitions for dark theme @@ -835,6 +839,11 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) { #endif void LcdDisplay::SetEmotion(const char* emotion) { + +#ifdef CONFIG_USE_EMOTION_REMOTE + EmotionRemote::GetInstance().SetEmotion(emotion); +#endif + struct Emotion { const char* icon; const char* text; diff --git a/main/remote/emotion_remote.cc b/main/remote/emotion_remote.cc new file mode 100644 index 00000000..ec16f0e6 --- /dev/null +++ b/main/remote/emotion_remote.cc @@ -0,0 +1,55 @@ +#include "emotion_remote.h" + +#include "esp_log.h" + +#include "font_awesome_symbols.h" + +#define TAG "emzy" + +void EmotionRemote::SetEmotion(const char* emotion){ + struct Emotion { + uint8_t em_id; + const char* text; + }; + static const std::vector emotions = { + {0x01, "neutral"}, + {0x02, "happy"}, + {0x03, "laughing"}, + {0x04, "funny"}, + {0x05, "sad"}, + {0x06, "angry"}, + {0x07, "crying"}, + {0x08, "loving"}, + {0x09, "embarrassed"}, + {0x0a, "surprised"}, + {0x0b, "shocked"}, + {0x0c, "thinking"}, + {0x0d, "winking"}, + {0x0e, "cool"}, + {0x0f, "relaxed"}, + {0x10, "delicious"}, + {0x11, "kissy"}, + {0x12, "confident"}, + {0x13, "sleepy"}, + {0x14, "silly"}, + {0x15, "confused"} + }; + std::string_view emotion_view(emotion); + auto it = std::find_if(emotions.begin(), emotions.end(), + [&emotion_view](const Emotion& e) { return e.text == emotion_view; }); + if (it != emotions.end()) { + sendGapData(it->em_id); + } else { + sendGapData(0x01); + } +} + +void EmotionRemote::sendGapData(uint8_t code){ + gapData[9]++; + gapData[11] = code; + remote.start_adv(gapData, gapDateLen, 20000); +} + +EmotionRemote::EmotionRemote(){ + +} \ No newline at end of file diff --git a/main/remote/emotion_remote.h b/main/remote/emotion_remote.h new file mode 100644 index 00000000..11b5c4dd --- /dev/null +++ b/main/remote/emotion_remote.h @@ -0,0 +1,46 @@ +#ifndef _EMOTION_REMOTE_H_ +#define _EMOTION_REMOTE_H_ + +#include "gapRemote.h" +#include +#include + +class EmotionRemote { +private: + struct RemotFun + { + std::string funName; + uint8_t code; + std::function func; // 使用 std::function 存储函数 + }; + + const uint8_t gapDateLen = 12; + uint8_t gapData[12] = { + 0x0B, // 数据长度 + 0xFF, // 厂商自定义类型 + 0x77, 0x66, // 厂商 ID + 0x01, // 协议 + 0xFF, // 标志 + 0x95, 0x03, // 设备 ID + 0xFF, // 组 ID + 0x00, // 序列号 + 0x01, // 命令 + 0x01 // 参数 + }; + + + GapRemote& remote = GapRemote::GetInstance(); + void sendGapData(uint8_t code); + public: + static EmotionRemote& GetInstance() { + static EmotionRemote instance; + return instance; + } + // 删除拷贝构造函数和赋值运算符 + EmotionRemote(const EmotionRemote&) = delete; + EmotionRemote& operator=(const EmotionRemote&) = delete; + explicit EmotionRemote(); + void SetEmotion(const char* emotion); +}; + +#endif \ No newline at end of file diff --git a/main/remote/gapRemote.cc b/main/remote/gapRemote.cc new file mode 100644 index 00000000..f93a6914 --- /dev/null +++ b/main/remote/gapRemote.cc @@ -0,0 +1,98 @@ +#include "gapRemote.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TAG "gap_remote" +static uint32_t start_time = 0; +void GapRemote::ble_sync_callback(void) +{ + auto &remote = GapRemote::GetInstance(); + /* 获取设备地址类型 */ + int rc = ble_hs_id_infer_auto(0, &remote.ble_addr_type); + if (rc != 0) + { + ESP_LOGE(TAG, "无法推断设备地址类型"); + return; + } + + uint8_t addr[6]; + ble_hs_id_copy_addr(remote.ble_addr_type, addr, NULL); + ESP_LOGI(TAG, "device gap mac: %02X:%02X:%02X:%02X:%02X:%02X", + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + + struct ble_gap_adv_params adv_params; + // 设置广播参数 + memset(&adv_params, 0, sizeof(adv_params)); + adv_params.conn_mode = BLE_GAP_CONN_MODE_NON; + adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; + adv_params.itvl_min = 0x20; // 默认最小间隔 + adv_params.itvl_max = 0x40; // 默认最大间隔 + adv_params.channel_map = 0x07; // 37信道 +} + +int GapRemote::ble_gap_event(struct ble_gap_event *event, void *arg) { + auto& remote = GapRemote::GetInstance(); + switch (event->type) { + case BLE_GAP_EVENT_ADV_COMPLETE: // 广播完成事件 + ESP_LOGW(TAG, "broadcast finish, %ld", pdMS_TO_TICKS(xTaskGetTickCount()-start_time)); + remote.is_broadcast = false; + break; + default: + break; + } + return 1; +} + +int GapRemote::start_adv(uint8_t *adv_data, uint8_t len, uint32_t duration_ms, bool force){ + if (is_broadcast && force==false) + { + ESP_LOGW(TAG, "The last broadcast is not over yet"); + return -1; + }else if (is_broadcast && force) + { + ble_gap_adv_stop(); + is_broadcast=false; + } + + int rc = ble_gap_adv_set_data((unsigned char *)adv_data, len); + if (rc != 0) { + ESP_LOGE(TAG, "fail set adv data, err: %d", rc); + return -2; + } + + /* 广播参数 */ + struct ble_gap_adv_params adv_params = { + .conn_mode = BLE_GAP_CONN_MODE_NON, // 不允许连接 + .disc_mode = BLE_GAP_DISC_MODE_GEN, // 一般可发现模式 + .itvl_min = 0x20, // 最小间隔 + .itvl_max = 0x40 , // 最大间隔 + .channel_map = 0x07 , // 使用信道 37 + }; + + rc = ble_gap_adv_start(ble_addr_type, NULL, duration_ms, &adv_params, ble_gap_event, NULL); + if (rc != 0) { + ESP_LOGE(TAG, "ble_gap_adv_start fail, err: %d", rc); + return -2; + } + start_time = xTaskGetTickCount(); + is_broadcast = true; + return 1; +} + +GapRemote::GapRemote() { + /* 初始化 NimBLE 核心 */ + nimble_port_init(); + /* 配置 Host */ + ble_hs_cfg.sync_cb = ble_sync_callback; + nimble_port_freertos_init([](void *param) + { nimble_port_run(); }); + ESP_LOGI(TAG, "NimBLE init Finsh"); +} \ No newline at end of file diff --git a/main/remote/gapRemote.h b/main/remote/gapRemote.h new file mode 100644 index 00000000..971fc4a5 --- /dev/null +++ b/main/remote/gapRemote.h @@ -0,0 +1,26 @@ +#ifndef _GAP_REMOTE_H_ +#define _GAP_REMOTE_H_ + +#include + +class GapRemote{ +public: + static GapRemote& GetInstance() { + static GapRemote instance; + return instance; + } + // 删除拷贝构造函数和赋值运算符 + GapRemote(const GapRemote&) = delete; + GapRemote& operator=(const GapRemote&) = delete; + int start_adv(uint8_t *adv_data, uint8_t len, uint32_t duration_ms=1000, bool force=true); +private: + GapRemote(); + bool is_broadcast = false; + uint8_t broadcast_count; + uint8_t ble_addr_type; + static void ble_sync_callback(void); + static int ble_gap_event(struct ble_gap_event *event, void *arg); + +}; + +#endif \ No newline at end of file diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 7fd5e624..0c38ecd3 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -73,3 +73,15 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144 + +CONFIG_MQTT_USE_CUSTOM_CONFIG=y +CONFIG_MQTT_TASK_STACK_SIZE=4096 + +CONFIG_TACKGROUND_TASK_STACK_SIZE=28672 + +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_LOG_LEVEL_ERROR=y +CONFIG_BT_CTRL_RUN_IN_FLASH_ONLY=y + diff --git a/sdkconfig.defaults.esp32c2 b/sdkconfig.defaults.esp32c2 index 9cf6908f..3e6fbd3f 100644 --- a/sdkconfig.defaults.esp32c2 +++ b/sdkconfig.defaults.esp32c2 @@ -25,4 +25,6 @@ CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0 CONFIG_LWIP_IPV6=n -CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16 \ No newline at end of file +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16 + +CONFIG_BT_ENABLED=n \ No newline at end of file diff --git a/sdkconfig.defaults.esp32s3 b/sdkconfig.defaults.esp32s3 index 40036e44..197c0b1f 100644 --- a/sdkconfig.defaults.esp32s3 +++ b/sdkconfig.defaults.esp32s3 @@ -19,3 +19,5 @@ CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 + +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=32 \ No newline at end of file diff --git a/sdkconfig.defaults.mem_opt.esp32c2 b/sdkconfig.defaults.mem_opt.esp32c2 index 318e1633..842c24f7 100644 --- a/sdkconfig.defaults.mem_opt.esp32c2 +++ b/sdkconfig.defaults.mem_opt.esp32c2 @@ -35,3 +35,5 @@ CONFIG_AUDIO_LOOP_TASK_STACK_SIZE=1280 CONFIG_VB6824_UART_TASK_STACK_SIZE=2560 CONFIG_VB6824_SEND_USE_TASK=n # CONFIG_VB6824_SEND_TASK_STACK_SIZE=1280 + +CONFIG_BT_ENABLED=n