mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-01-14 01:07:30 +08:00
Merge branch '78:main' into akoroneHk-patch-1
This commit is contained in:
commit
374b579150
@ -4,7 +4,7 @@
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(PROJECT_VER "2.0.5")
|
||||
set(PROJECT_VER "2.1.0")
|
||||
|
||||
# Add this line to disable the specific warning
|
||||
add_compile_options(-Wno-missing-field-initializers)
|
||||
|
||||
36
docs/blufi.md
Normal file
36
docs/blufi.md
Normal file
@ -0,0 +1,36 @@
|
||||
# BluFi 配网(集成 esp-wifi-connect)
|
||||
|
||||
本文档说明如何在小智固件中启用和使用 BluFi(BLE Wi‑Fi 配网),并结合项目内置的 `esp-wifi-connect` 组件完成 Wi‑Fi 连接与存储。官方
|
||||
BluFi
|
||||
协议说明请参考 [Espressif 文档](https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-guides/ble/blufi.html)。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 需要支持 BLE 的芯片与固件配置。
|
||||
- 在 `idf.py menuconfig` 中启用 `WiFi Configuration Method -> Esp Blufi`(`CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING=y`
|
||||
)。如果只想用 BluFi,可关闭同一菜单下的 Hotspot/Acoustic 选项。
|
||||
|
||||
- 保持默认的 NVS 与事件循环初始化(项目的 `app_main` 已处理)。
|
||||
- CONFIG_BT_BLUEDROID_ENABLED、CONFIG_BT_NIMBLE_ENABLED这两个宏应二选一,不能同时启用。
|
||||
## 工作流程
|
||||
|
||||
1) 手机端通过 BluFi(如官方 EspBlufi App 或自研客户端)连接设备,发送 Wi‑Fi SSID/密码。
|
||||
2) 设备侧在 `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP` 中将凭据写入 `SsidManager`(存储到 NVS,属于 `esp-wifi-connect` 组件)。
|
||||
3) 随后启动 `WifiStation` 扫描并连接;状态通过 BluFi 返回。
|
||||
4) 配网成功后设备会自动连接新 Wi‑Fi;失败则返回失败状态。
|
||||
|
||||
## 使用步骤
|
||||
|
||||
1. 配置:在 menuconfig 开启 `Esp Blufi`。编译并烧录固件。
|
||||
2. 触发配网:设备首次启动且没有已保存的 Wi‑Fi 时会自动进入配网。
|
||||
3. 手机端操作:打开 EspBlufi App(或其他 BluFi 客户端),搜索并连接设备,可以选择是否加密,按提示输入 Wi‑Fi SSID/密码并发送。
|
||||
4. 观察结果:
|
||||
- 成功:BluFi 报告连接成功,设备自动连接 Wi‑Fi。
|
||||
- 失败:BluFi 返回失败状态,可重新发送或检查路由器。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- BluFi 与 Hotspot/声波配网可以同时编译,但会同时启动,增加内存占用。建议在 menuconfig 中只保留一种方式。
|
||||
- 若多次测试,建议清除或覆盖存储的 SSID(`wifi` 命名空间),避免旧配置干扰。
|
||||
- 如果使用自定义 BluFi 客户端,需遵循官方协议帧格式,参考上文官方文档链接。
|
||||
- 官方文档中已提供EspBlufi APP下载地址
|
||||
@ -197,8 +197,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -33,7 +33,7 @@ set(SOURCES "audio/audio_codec.cc"
|
||||
"application.cc"
|
||||
"ota.cc"
|
||||
"settings.cc"
|
||||
"device_state_event.cc"
|
||||
"device_state_machine.cc"
|
||||
"assets.cc"
|
||||
"main.cc"
|
||||
)
|
||||
@ -212,6 +212,11 @@ elseif(CONFIG_BOARD_TYPE_ECHOEAR)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_SENSAIRSHUTTLE)
|
||||
set(BOARD_TYPE "esp-sensairshuttle")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_16_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_AUDIO_BOARD)
|
||||
set(BOARD_TYPE "waveshare-s3-audio-board")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
@ -286,6 +291,11 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_LCD_1_69)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-lcd-1.83")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_16_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-amoled-1.43")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@ -367,6 +377,11 @@ elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI2_ESP32C5)
|
||||
set(BOARD_TYPE "movecall-moji2-esp32c5")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
|
||||
set(BOARD_TYPE "movecall-cuican-esp32s3")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
@ -445,6 +460,11 @@ elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_METAL_1_54_WIFI)
|
||||
set(BOARD_TYPE "xingzhi-metal-1.54-wifi")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER)
|
||||
set(BOARD_TYPE "sensecap-watcher")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@ -553,6 +573,10 @@ elseif(CONFIG_BOARD_TYPE_AIPI_LITE)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
|
||||
elseif(CONFIG_BOARD_TYPE_HU_087)
|
||||
set(BOARD_TYPE "hu-087")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
endif()
|
||||
|
||||
file(GLOB BOARD_SOURCES
|
||||
@ -574,6 +598,10 @@ else()
|
||||
list(APPEND SOURCES "audio/wake_words/esp_wake_word.cc")
|
||||
endif()
|
||||
|
||||
# Auto Select Additional Sources
|
||||
if (CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING)
|
||||
list(APPEND SOURCES "boards/common/blufi.cpp")
|
||||
endif ()
|
||||
# Select language directory according to Kconfig
|
||||
if(CONFIG_LANGUAGE_ZH_CN)
|
||||
set(LANG_DIR "zh-CN")
|
||||
|
||||
@ -150,6 +150,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_ESP_SPARKBOT
|
||||
bool "Espressif SparkBot"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP_SENSAIRSHUTTLE
|
||||
bool "Espressif ESP-SensairShuttle"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_ESP_SPOT_S3
|
||||
bool "Espressif Spot-S3"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@ -273,6 +276,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_C6_LCD_1_69
|
||||
bool "Waveshare ESP32-C6-LCD-1.69"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83
|
||||
bool "Waveshare ESP32-C6-Touch-LCD-1.83"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
@ -330,6 +336,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_MOVECALL_MOJI_ESP32S3
|
||||
bool "Movecall Moji 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_MOVECALL_MOJI2_ESP32C5
|
||||
bool "Movecall Moji2.0 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3
|
||||
bool "Movecall CuiCan 璀璨·AI吊坠"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@ -378,6 +387,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307
|
||||
bool "无名科技星智1.54(ML307)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_XINGZHI_METAL_1_54_WIFI
|
||||
bool "无名科技星智1.54 METAL(wifi)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER
|
||||
bool "Seeed Studio SenseCAP Watcher"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@ -438,6 +450,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_AIPI_LITE
|
||||
bool "AIPI-Lite"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_HU_087
|
||||
bool "HU-087"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
endchoice
|
||||
|
||||
choice
|
||||
@ -471,7 +486,7 @@ choice ESP_S3_LCD_EV_Board_Version_TYPE
|
||||
endchoice
|
||||
|
||||
choice DISPLAY_OLED_TYPE
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
|
||||
prompt "OLED Type"
|
||||
default OLED_SSD1306_128X32
|
||||
help
|
||||
@ -570,7 +585,7 @@ choice DISPLAY_STYLE
|
||||
|
||||
config USE_EMOTE_MESSAGE_STYLE
|
||||
bool "Emote animation style"
|
||||
depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3
|
||||
depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_SENSAIRSHUTTLE
|
||||
endchoice
|
||||
|
||||
choice WAKE_WORD_TYPE
|
||||
@ -665,6 +680,29 @@ config USE_AUDIO_DEBUGGER
|
||||
help
|
||||
Enable audio debugger, send audio data through UDP to the host machine
|
||||
|
||||
menu "WiFi Configuration Method"
|
||||
help
|
||||
WiFi Configuration Method Selection
|
||||
config USE_HOTSPOT_WIFI_PROVISIONING
|
||||
bool "Hotspot"
|
||||
default y
|
||||
help
|
||||
Use WiFi Hotspot to transmit WiFi configuration data
|
||||
config USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
bool "Acoustic"
|
||||
help
|
||||
Use audio signal to transmit WiFi configuration data
|
||||
|
||||
config USE_ESP_BLUFI_WIFI_PROVISIONING
|
||||
bool "Esp Blufi"
|
||||
help
|
||||
Use esp blufi protocol to transmit WiFi configuration data
|
||||
select BT_ENABLED
|
||||
select BT_BLE_42_FEATURES_SUPPORTED
|
||||
select BT_BLE_BLUFI_ENABLE
|
||||
select MBEDTLS_DHM_C
|
||||
endmenu
|
||||
|
||||
config AUDIO_DEBUG_UDP_SERVER
|
||||
string "Audio Debug UDP Server Address"
|
||||
default "192.168.2.100:8000"
|
||||
@ -672,12 +710,6 @@ config AUDIO_DEBUG_UDP_SERVER
|
||||
help
|
||||
UDP server address, format: IP:PORT, used to receive audio debugging data
|
||||
|
||||
config USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
bool "Enable Acoustic WiFi Provisioning"
|
||||
default n
|
||||
help
|
||||
Enable acoustic WiFi provisioning, use audio signal to transmit WiFi configuration data
|
||||
|
||||
config RECEIVE_CUSTOM_MESSAGE
|
||||
bool "Enable Custom Message Reception"
|
||||
default n
|
||||
@ -688,7 +720,7 @@ menu "Camera Configuration"
|
||||
depends on !IDF_TARGET_ESP32
|
||||
|
||||
comment "Warning: Please read the help text before modifying these settings."
|
||||
|
||||
|
||||
config XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
|
||||
bool "Allow JPEG Input"
|
||||
default n
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -14,16 +14,23 @@
|
||||
#include "protocol.h"
|
||||
#include "ota.h"
|
||||
#include "audio_service.h"
|
||||
#include "device_state_event.h"
|
||||
#include "device_state.h"
|
||||
#include "device_state_machine.h"
|
||||
|
||||
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
// Main event bits
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_ACTIVATION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
#define MAIN_EVENT_NETWORK_CONNECTED (1 << 7)
|
||||
#define MAIN_EVENT_NETWORK_DISCONNECTED (1 << 8)
|
||||
#define MAIN_EVENT_TOGGLE_CHAT (1 << 9)
|
||||
#define MAIN_EVENT_START_LISTENING (1 << 10)
|
||||
#define MAIN_EVENT_STOP_LISTENING (1 << 11)
|
||||
#define MAIN_EVENT_STATE_CHANGED (1 << 12)
|
||||
|
||||
|
||||
enum AecMode {
|
||||
@ -38,31 +45,80 @@ public:
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
// Delete copy constructor and assignment operator
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
void Start();
|
||||
void MainEventLoop();
|
||||
DeviceState GetDeviceState() const { return device_state_; }
|
||||
/**
|
||||
* Initialize the application
|
||||
* This sets up display, audio, network callbacks, etc.
|
||||
* Network connection starts asynchronously.
|
||||
*/
|
||||
void Initialize();
|
||||
|
||||
/**
|
||||
* Run the main event loop
|
||||
* This function runs in the main task and never returns.
|
||||
* It handles all events including network, state changes, and user interactions.
|
||||
*/
|
||||
void Run();
|
||||
|
||||
DeviceState GetDeviceState() const { return state_machine_.GetState(); }
|
||||
bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); }
|
||||
void Schedule(std::function<void()> callback);
|
||||
void SetDeviceState(DeviceState state);
|
||||
|
||||
/**
|
||||
* Request state transition
|
||||
* Returns true if transition was successful
|
||||
*/
|
||||
bool SetDeviceState(DeviceState state);
|
||||
|
||||
/**
|
||||
* Schedule a callback to be executed in the main task
|
||||
*/
|
||||
void Schedule(std::function<void()>&& callback);
|
||||
|
||||
/**
|
||||
* Alert with status, message, emotion and optional sound
|
||||
*/
|
||||
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");
|
||||
void DismissAlert();
|
||||
|
||||
void AbortSpeaking(AbortReason reason);
|
||||
|
||||
/**
|
||||
* Toggle chat state (event-based, thread-safe)
|
||||
* Sends MAIN_EVENT_TOGGLE_CHAT to be handled in Run()
|
||||
*/
|
||||
void ToggleChatState();
|
||||
|
||||
/**
|
||||
* Start listening (event-based, thread-safe)
|
||||
* Sends MAIN_EVENT_START_LISTENING to be handled in Run()
|
||||
*/
|
||||
void StartListening();
|
||||
|
||||
/**
|
||||
* Stop listening (event-based, thread-safe)
|
||||
* Sends MAIN_EVENT_STOP_LISTENING to be handled in Run()
|
||||
*/
|
||||
void StopListening();
|
||||
|
||||
void Reboot();
|
||||
void WakeWordInvoke(const std::string& wake_word);
|
||||
bool UpgradeFirmware(Ota& ota, const std::string& url = "");
|
||||
bool UpgradeFirmware(const std::string& url, const std::string& version = "");
|
||||
bool CanEnterSleepMode();
|
||||
void SendMcpMessage(const std::string& payload);
|
||||
void SetAecMode(AecMode mode);
|
||||
AecMode GetAecMode() const { return aec_mode_; }
|
||||
void PlaySound(const std::string_view& sound);
|
||||
AudioService& GetAudioService() { return audio_service_; }
|
||||
|
||||
/**
|
||||
* Reset protocol resources (thread-safe)
|
||||
* Can be called from any task to release resources allocated after network connected
|
||||
* This includes closing audio channel, resetting protocol and ota objects
|
||||
*/
|
||||
void ResetProtocol();
|
||||
|
||||
private:
|
||||
Application();
|
||||
@ -73,23 +129,43 @@ private:
|
||||
std::unique_ptr<Protocol> protocol_;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
esp_timer_handle_t clock_timer_handle_ = nullptr;
|
||||
volatile DeviceState device_state_ = kDeviceStateUnknown;
|
||||
DeviceStateMachine state_machine_;
|
||||
ListeningMode listening_mode_ = kListeningModeAutoStop;
|
||||
AecMode aec_mode_ = kAecOff;
|
||||
std::string last_error_message_;
|
||||
AudioService audio_service_;
|
||||
std::unique_ptr<Ota> ota_;
|
||||
|
||||
bool has_server_time_ = false;
|
||||
bool aborted_ = false;
|
||||
bool assets_version_checked_ = false;
|
||||
bool play_popup_on_listening_ = false; // Flag to play popup sound after state changes to listening
|
||||
int clock_ticks_ = 0;
|
||||
TaskHandle_t check_new_version_task_handle_ = nullptr;
|
||||
TaskHandle_t main_event_loop_task_handle_ = nullptr;
|
||||
TaskHandle_t activation_task_handle_ = nullptr;
|
||||
|
||||
void OnWakeWordDetected();
|
||||
void CheckNewVersion(Ota& ota);
|
||||
|
||||
// Event handlers
|
||||
void HandleStateChangedEvent();
|
||||
void HandleToggleChatEvent();
|
||||
void HandleStartListeningEvent();
|
||||
void HandleStopListeningEvent();
|
||||
void HandleNetworkConnectedEvent();
|
||||
void HandleNetworkDisconnectedEvent();
|
||||
void HandleActivationDoneEvent();
|
||||
void HandleWakeWordDetectedEvent();
|
||||
|
||||
// Activation task (runs in background)
|
||||
void ActivationTask();
|
||||
|
||||
// Helper methods
|
||||
void CheckAssetsVersion();
|
||||
void CheckNewVersion();
|
||||
void InitializeProtocol();
|
||||
void ShowActivationCode(const std::string& code, const std::string& message);
|
||||
void SetListeningMode(ListeningMode mode);
|
||||
|
||||
// State change handler called by state machine
|
||||
void OnStateChanged(DeviceState old_state, DeviceState new_state);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -7,7 +7,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
@ -136,9 +135,9 @@ class AIPILite : public WifiBoard {
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting &&
|
||||
!WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -154,7 +153,7 @@ class AIPILite : public WifiBoard {
|
||||
app.SetDeviceState(kDeviceStateWifiConfiguring);
|
||||
|
||||
// 重置WiFi配置以确保进入配网模式
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
});
|
||||
|
||||
power_button_.OnClick([this]() { power_save_timer_->WakeUp(); });
|
||||
@ -236,11 +235,11 @@ class AIPILite : public WifiBoard {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
@ -244,8 +243,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
@ -227,8 +226,9 @@ private:
|
||||
|
||||
middle_button_.OnLongPress([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
@ -377,11 +377,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
@ -280,9 +279,10 @@ private:
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (self->GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,11 +463,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
DualNetworkBoard::SetPowerSaveMode(enabled);
|
||||
DualNetworkBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
@ -262,8 +261,9 @@ private:
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->power_status_ == kDeviceBatterySupply) {
|
||||
@ -442,11 +442,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3"
|
||||
|
||||
@ -87,8 +86,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -60,8 +59,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "AtomEchoS3R"
|
||||
|
||||
@ -57,8 +56,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "led/circular_strip.h"
|
||||
|
||||
#define TAG "XX+EchoBase"
|
||||
@ -91,8 +90,9 @@ private:
|
||||
|
||||
ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
@ -180,8 +179,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#define TAG "AtomS3R CAM/M12 + EchoBase"
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
@ -258,8 +257,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include "config.h"
|
||||
#include "led/single_led.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -138,10 +137,11 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 1);
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "display/oled_display.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
@ -106,10 +105,11 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 1);
|
||||
|
||||
@ -14,7 +14,6 @@
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "CompactMl307Board"
|
||||
|
||||
@ -96,10 +95,11 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
app.ToggleChatState();
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "lamp_controller.h"
|
||||
#include "led/single_led.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -126,8 +125,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -173,8 +172,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
@ -104,8 +103,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include "esp_log.h"
|
||||
#include "display.h"
|
||||
#include "ssid_manager.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
@ -13,7 +14,7 @@ namespace audio_wifi_config
|
||||
static const char *kLogTag = "AUDIO_WIFI_CONFIG";
|
||||
|
||||
void ReceiveWifiCredentialsFromAudio(Application *app,
|
||||
WifiConfigurationAp *wifi_ap,
|
||||
WifiManager *wifi_manager,
|
||||
Display *display,
|
||||
size_t input_channels
|
||||
)
|
||||
@ -90,13 +91,16 @@ namespace audio_wifi_config
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) {
|
||||
wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials
|
||||
esp_restart(); // Restart device to apply new WiFi configuration
|
||||
} else {
|
||||
ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials");
|
||||
}
|
||||
// Save WiFi credentials using SsidManager
|
||||
auto& ssid_manager = SsidManager::GetInstance();
|
||||
ssid_manager.AddSsid(wifi_ssid, wifi_password);
|
||||
ESP_LOGI(kLogTag, "WiFi credentials saved successfully");
|
||||
|
||||
// Exit config mode (triggers ConfigModeExit event)
|
||||
wifi_manager->StopConfigAp();
|
||||
|
||||
data_buffer.decoded_text.reset(); // Clear processed data
|
||||
return; // Exit the function
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <cmath>
|
||||
#include "wifi_configuration_ap.h"
|
||||
#include "wifi_manager.h"
|
||||
#include "application.h"
|
||||
|
||||
// Audio signal processing constants for WiFi configuration via audio
|
||||
@ -19,7 +19,7 @@ const size_t kWindowSize = 64;
|
||||
namespace audio_wifi_config
|
||||
{
|
||||
// Main function to receive WiFi credentials through audio signal
|
||||
void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display,
|
||||
void ReceiveWifiCredentialsFromAudio(Application *app, WifiManager *wifi_manager, Display *display,
|
||||
size_t input_channels = 1);
|
||||
|
||||
/**
|
||||
|
||||
777
main/boards/common/blufi.cpp
Normal file
777
main/boards/common/blufi.cpp
Normal file
@ -0,0 +1,777 @@
|
||||
#include "blufi.h"
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "application.h"
|
||||
#include "esp_bt.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
// Bluedroid specific
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#endif
|
||||
|
||||
// NimBLE specific
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
#include "console/console.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
extern void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
|
||||
extern int esp_blufi_gatt_svr_init(void);
|
||||
extern void esp_blufi_gatt_svr_deinit(void);
|
||||
extern void esp_blufi_btc_init(void);
|
||||
extern void esp_blufi_btc_deinit(void);
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
// Blufi Advertising & Connection
|
||||
void esp_blufi_adv_start(void);
|
||||
|
||||
void esp_blufi_adv_stop(void);
|
||||
|
||||
void esp_blufi_disconnect(void);
|
||||
|
||||
// Internal BTC layer functions needed for error reporting
|
||||
void btc_blufi_report_error(esp_blufi_error_state_t state);
|
||||
|
||||
// Bluedroid specific GAP event handler
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
void esp_blufi_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
#endif
|
||||
|
||||
// NimBLE specific internal functions
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
|
||||
int esp_blufi_gatt_svr_init(void);
|
||||
void esp_blufi_gatt_svr_deinit(void);
|
||||
void esp_blufi_btc_init(void);
|
||||
void esp_blufi_btc_deinit(void);
|
||||
#endif
|
||||
}
|
||||
|
||||
// mbedTLS for security
|
||||
#include <wifi_station.h>
|
||||
#include "esp_crc.h"
|
||||
#include "esp_random.h"
|
||||
#include "mbedtls/md5.h"
|
||||
#include "ssid_manager.h"
|
||||
|
||||
// Logging Tag
|
||||
static const char *BLUFI_TAG = "BLUFI_CLASS";
|
||||
|
||||
static wifi_mode_t GetWifiModeWithFallback(const WifiManager &wifi) {
|
||||
if (wifi.IsConfigMode()) {
|
||||
return WIFI_MODE_AP;
|
||||
}
|
||||
if (wifi.IsInitialized() && wifi.IsConnected()) {
|
||||
return WIFI_MODE_STA;
|
||||
}
|
||||
|
||||
wifi_mode_t mode = WIFI_MODE_STA;
|
||||
esp_wifi_get_mode(&mode);
|
||||
return mode;
|
||||
}
|
||||
|
||||
Blufi &Blufi::GetInstance() {
|
||||
static Blufi instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
Blufi::Blufi()
|
||||
: m_sec(nullptr),
|
||||
m_ble_is_connected(false),
|
||||
m_sta_connected(false),
|
||||
m_sta_got_ip(false),
|
||||
m_provisioned(false),
|
||||
m_deinited(false),
|
||||
m_sta_ssid_len(0),
|
||||
m_sta_is_connecting(false) {
|
||||
// Initialize member variables
|
||||
memset(&m_sta_config, 0, sizeof(m_sta_config));
|
||||
memset(&m_ap_config, 0, sizeof(m_ap_config));
|
||||
memset(m_sta_bssid, 0, sizeof(m_sta_bssid));
|
||||
memset(m_sta_ssid, 0, sizeof(m_sta_ssid));
|
||||
memset(&m_sta_conn_info, 0, sizeof(m_sta_conn_info));
|
||||
}
|
||||
|
||||
Blufi::~Blufi() {
|
||||
if (m_sec) {
|
||||
_security_deinit();
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t Blufi::init() {
|
||||
esp_err_t ret;
|
||||
inited_ = true;
|
||||
m_provisioned = false;
|
||||
m_deinited = false;
|
||||
|
||||
#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
|
||||
ret = _controller_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "BLUFI controller init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = _host_and_cb_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "BLUFI host and cb init failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI VERSION %04x", esp_blufi_get_version());
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::deinit() {
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (inited_) {
|
||||
if (m_deinited) {
|
||||
return ESP_OK;
|
||||
}
|
||||
m_deinited = true;
|
||||
ret = _host_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "Host deinit failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
|
||||
ret = _controller_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "Controller deinit failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
esp_err_t Blufi::_host_init() {
|
||||
esp_err_t ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s init bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ESP_LOGI(BLUFI_TAG, "BD ADDR: " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(esp_bt_dev_get_address()));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_deinit() {
|
||||
esp_err_t ret = esp_blufi_profile_deinit();
|
||||
if (ret != ESP_OK)
|
||||
return ret;
|
||||
|
||||
ret = esp_bluedroid_disable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s disable bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ret = esp_bluedroid_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s deinit bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_gap_register_callback() {
|
||||
esp_err_t rc = esp_ble_gap_register_callback(esp_blufi_gap_event_handler);
|
||||
if (rc) {
|
||||
return rc;
|
||||
}
|
||||
return esp_blufi_profile_init();
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_and_cb_init() {
|
||||
static esp_blufi_callbacks_t blufi_callbacks = {
|
||||
.event_cb = &_event_callback_trampoline,
|
||||
.negotiate_data_handler = &_negotiate_data_handler_trampoline,
|
||||
.encrypt_func = &_encrypt_func_trampoline,
|
||||
.decrypt_func = &_decrypt_func_trampoline,
|
||||
.checksum_func = &_checksum_func_trampoline,
|
||||
};
|
||||
|
||||
esp_err_t ret = _host_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s initialise host failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
ret = esp_blufi_register_callbacks(&blufi_callbacks);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s blufi register failed, error code = %x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
ret = _gap_register_callback();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s gap register failed, error code = %x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif /* CONFIG_BT_BLUEDROID_ENABLED */
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
// Stubs for NimBLE specific store functionality
|
||||
void ble_store_config_init();
|
||||
|
||||
void Blufi::_nimble_on_reset(int reason) {
|
||||
ESP_LOGE(BLUFI_TAG, "NimBLE Resetting state; reason=%d", reason);
|
||||
}
|
||||
|
||||
void Blufi::_nimble_on_sync() {
|
||||
// This is called when the host and controller are synced.
|
||||
// It's a good place to initialize the Blufi profile.
|
||||
esp_blufi_profile_init();
|
||||
}
|
||||
|
||||
void Blufi::_nimble_host_task(void *param) {
|
||||
ESP_LOGI(BLUFI_TAG, "BLE Host Task Started");
|
||||
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_init() {
|
||||
// esp_nimble_init() is called by controller_init for NimBLE
|
||||
ble_hs_cfg.reset_cb = _nimble_on_reset;
|
||||
ble_hs_cfg.sync_cb = _nimble_on_sync;
|
||||
ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb;
|
||||
|
||||
// Security Manager settings (can be customized)
|
||||
ble_hs_cfg.sm_io_cap = 4; // IO capability: No Input, No Output
|
||||
#ifdef CONFIG_EXAMPLE_BONDING
|
||||
ble_hs_cfg.sm_bonding = 1;
|
||||
#endif
|
||||
|
||||
int rc = esp_blufi_gatt_svr_init();
|
||||
assert(rc == 0);
|
||||
|
||||
ble_store_config_init(); // Configure the BLE storage
|
||||
esp_blufi_btc_init();
|
||||
|
||||
esp_err_t err = esp_nimble_enable(_nimble_host_task);
|
||||
if (err) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s failed: %s", __func__, esp_err_to_name(err));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_deinit(void) {
|
||||
esp_err_t ret = nimble_port_stop();
|
||||
if (ret == ESP_OK) {
|
||||
esp_nimble_deinit();
|
||||
}
|
||||
esp_blufi_gatt_svr_deinit();
|
||||
ret = esp_blufi_profile_deinit();
|
||||
esp_blufi_btc_deinit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_gap_register_callback(void) {
|
||||
return ESP_OK; // For NimBLE, GAP callbacks are handled differently
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_host_and_cb_init() {
|
||||
static esp_blufi_callbacks_t blufi_callbacks = {
|
||||
.event_cb = &_event_callback_trampoline,
|
||||
.negotiate_data_handler = &_negotiate_data_handler_trampoline,
|
||||
.encrypt_func = &_encrypt_func_trampoline,
|
||||
.decrypt_func = &_decrypt_func_trampoline,
|
||||
.checksum_func = &_checksum_func_trampoline,
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_blufi_register_callbacks(&blufi_callbacks);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s blufi register failed, error code = %x", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Host init must be called after registering callbacks for NimBLE
|
||||
ret = _host_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s initialise host failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif /* CONFIG_BT_NIMBLE_ENABLED */
|
||||
|
||||
#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
|
||||
esp_err_t Blufi::_controller_init() {
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
esp_err_t ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
// For NimBLE, host init needs to be done after controller init
|
||||
ret = esp_nimble_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "esp_nimble_init() failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t Blufi::_controller_deinit() {
|
||||
esp_err_t ret = esp_bt_controller_disable();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s disable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
}
|
||||
ret = esp_bt_controller_deinit();
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "%s deinit controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif // Generic controller init
|
||||
|
||||
static int myrand(void *rng_state, unsigned char *output, size_t len) {
|
||||
esp_fill_random(output, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Blufi::_security_init() {
|
||||
m_sec = new BlufiSecurity();
|
||||
if (m_sec == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to allocate security context");
|
||||
return;
|
||||
}
|
||||
memset(m_sec, 0, sizeof(BlufiSecurity));
|
||||
m_sec->dhm = new mbedtls_dhm_context();
|
||||
m_sec->aes = new mbedtls_aes_context();
|
||||
|
||||
mbedtls_dhm_init(m_sec->dhm);
|
||||
mbedtls_aes_init(m_sec->aes);
|
||||
|
||||
memset(m_sec->iv, 0x0, sizeof(m_sec->iv));
|
||||
}
|
||||
|
||||
void Blufi::_security_deinit() {
|
||||
if (m_sec == nullptr)
|
||||
return;
|
||||
|
||||
if (m_sec->dh_param) {
|
||||
free(m_sec->dh_param);
|
||||
}
|
||||
mbedtls_dhm_free(m_sec->dhm);
|
||||
mbedtls_aes_free(m_sec->aes);
|
||||
delete m_sec->dhm;
|
||||
delete m_sec->aes;
|
||||
delete m_sec;
|
||||
m_sec = nullptr;
|
||||
}
|
||||
|
||||
void Blufi::_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data,
|
||||
int *output_len, bool *need_free) {
|
||||
if (m_sec == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "Security not initialized in DH handler");
|
||||
btc_blufi_report_error(ESP_BLUFI_INIT_SECURITY_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len < 1) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH handler: data too short");
|
||||
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t type = data[0];
|
||||
switch (type) {
|
||||
case 0x00: /* DH_PARAM_LEN */
|
||||
if (len < 3) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH_PARAM_LEN packet too short");
|
||||
btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
m_sec->dh_param_len = (data[1] << 8) | data[2];
|
||||
if (m_sec->dh_param) {
|
||||
free(m_sec->dh_param);
|
||||
m_sec->dh_param = nullptr;
|
||||
}
|
||||
m_sec->dh_param = (uint8_t *)malloc(m_sec->dh_param_len);
|
||||
if (m_sec->dh_param == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH malloc failed");
|
||||
btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR);
|
||||
}
|
||||
break;
|
||||
case 0x01: /* DH_PARAM_DATA */ {
|
||||
if (m_sec->dh_param == nullptr) {
|
||||
ESP_LOGE(BLUFI_TAG, "DH param not allocated");
|
||||
btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR);
|
||||
return;
|
||||
}
|
||||
uint8_t *param = m_sec->dh_param;
|
||||
memcpy(m_sec->dh_param, &data[1], m_sec->dh_param_len);
|
||||
int ret = mbedtls_dhm_read_params(m_sec->dhm, ¶m, ¶m[m_sec->dh_param_len]);
|
||||
if (ret) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_read_params failed %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_READ_PARAM_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
const int dhm_len = mbedtls_dhm_get_len(m_sec->dhm);
|
||||
|
||||
ret = mbedtls_dhm_make_public(m_sec->dhm, dhm_len, m_sec->self_public_key, dhm_len,
|
||||
myrand, NULL);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_make_public failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_MAKE_PUBLIC_ERROR);
|
||||
return;
|
||||
}
|
||||
ret = mbedtls_dhm_calc_secret(m_sec->dhm, m_sec->share_key, SHARE_KEY_LEN,
|
||||
&m_sec->share_len, myrand, NULL);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_calc_secret failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mbedtls_md5(m_sec->share_key, m_sec->share_len, m_sec->psk);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_md5 failed: %d", ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_CALC_MD5_ERROR);
|
||||
return;
|
||||
}
|
||||
ret = mbedtls_aes_setkey_enc(m_sec->aes, m_sec->psk, PSK_LEN * 8);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "mbedtls_aes_setkey_enc failed: -0x%04X", -ret);
|
||||
btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR);
|
||||
return;
|
||||
}
|
||||
*output_data = m_sec->self_public_key;
|
||||
*output_len = dhm_len;
|
||||
*need_free = false;
|
||||
ESP_LOGI(BLUFI_TAG, "DH negotiation completed successfully");
|
||||
|
||||
free(m_sec->dh_param);
|
||||
m_sec->dh_param = nullptr;
|
||||
m_sec->dh_param_len = 0;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(BLUFI_TAG, "DH handler unknown type: %d", type);
|
||||
}
|
||||
}
|
||||
|
||||
int Blufi::_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) {
|
||||
if (!m_sec || !m_sec->aes || !crypt_data || crypt_len <= 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "Invalid parameters for AES encryption");
|
||||
return -ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t iv_offset = 0;
|
||||
uint8_t iv0[16];
|
||||
memcpy(iv0, m_sec->iv, 16);
|
||||
iv0[0] = iv8;
|
||||
int ret = mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0,
|
||||
crypt_data, crypt_data);
|
||||
|
||||
if (ret == 0) {
|
||||
return crypt_len;
|
||||
} else {
|
||||
ESP_LOGE(BLUFI_TAG, "AES encrypt failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int Blufi::_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) {
|
||||
if (!m_sec || !m_sec->aes || !crypt_data || crypt_len < 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "Invalid parameters for AES decryption %p %p %d", m_sec->aes,
|
||||
crypt_data, crypt_len);
|
||||
return -ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t iv_offset = 0;
|
||||
uint8_t iv0[16];
|
||||
memcpy(iv0, m_sec->iv, 16);
|
||||
iv0[0] = iv8;
|
||||
int ret = mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0,
|
||||
crypt_data, crypt_data);
|
||||
if (ret != 0) {
|
||||
ESP_LOGE(BLUFI_TAG, "AES decrypt failed: %d", ret);
|
||||
return ret;
|
||||
} else {
|
||||
return crypt_len;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Blufi::_crc_checksum(uint8_t iv8, uint8_t *data, int len) {
|
||||
return esp_crc16_be(0, data, len);
|
||||
}
|
||||
|
||||
int Blufi::_get_softap_conn_num() {
|
||||
auto &wifi = WifiManager::GetInstance();
|
||||
if (!wifi.IsInitialized() || !wifi.IsConfigMode()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
wifi_sta_list_t sta_list{};
|
||||
if (esp_wifi_ap_get_sta_list(&sta_list) == ESP_OK) {
|
||||
return sta_list.num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_BLUFI_EVENT_INIT_FINISH:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI init finish");
|
||||
esp_blufi_adv_start();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_DEINIT_FINISH:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI deinit finish");
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_BLE_CONNECT:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI ble connect");
|
||||
m_ble_is_connected = true;
|
||||
esp_blufi_adv_stop();
|
||||
_security_init();
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_BLE_DISCONNECT:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI ble disconnect");
|
||||
m_ble_is_connected = false;
|
||||
_security_deinit();
|
||||
if (!m_provisioned) {
|
||||
esp_blufi_adv_start();
|
||||
} else {
|
||||
esp_blufi_adv_stop();
|
||||
if (!m_deinited) {
|
||||
// Deinit BLE stack after provisioning completes to free resources.
|
||||
xTaskCreate(
|
||||
[](void *ctx) {
|
||||
static_cast<Blufi *>(ctx)->deinit();
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"blufi_deinit", 4096, this, 5, nullptr);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: {
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI Set WIFI opmode %d", param->wifi_mode.op_mode);
|
||||
auto &wifi_manager = WifiManager::GetInstance();
|
||||
if (!wifi_manager.IsInitialized() && !wifi_manager.Initialize()) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to initialize WifiManager for opmode change");
|
||||
break;
|
||||
}
|
||||
switch (param->wifi_mode.op_mode) {
|
||||
case WIFI_MODE_STA:
|
||||
wifi_manager.StartStation();
|
||||
break;
|
||||
case WIFI_MODE_AP:
|
||||
wifi_manager.StartConfigAp();
|
||||
break;
|
||||
case WIFI_MODE_APSTA:
|
||||
ESP_LOGW(BLUFI_TAG, "APSTA mode not supported, starting station only");
|
||||
wifi_manager.StartStation();
|
||||
break;
|
||||
default:
|
||||
wifi_manager.StopStation();
|
||||
wifi_manager.StopConfigAp();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: {
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI request wifi connect to AP via esp-wifi-connect");
|
||||
std::string ssid(reinterpret_cast<const char *>(m_sta_config.sta.ssid));
|
||||
std::string password(reinterpret_cast<const char *>(m_sta_config.sta.password));
|
||||
|
||||
// Save credentials through SsidManager
|
||||
SsidManager::GetInstance().AddSsid(ssid, password);
|
||||
auto &wifi_manager = WifiManager::GetInstance();
|
||||
if (!wifi_manager.IsInitialized() && !wifi_manager.Initialize()) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to initialize WifiManager");
|
||||
break;
|
||||
}
|
||||
|
||||
// Track SSID for BLUFI status reporting.
|
||||
m_sta_ssid_len = static_cast<int>(std::min(ssid.size(), sizeof(m_sta_ssid)));
|
||||
memcpy(m_sta_ssid, ssid.c_str(), m_sta_ssid_len);
|
||||
memset(m_sta_bssid, 0, sizeof(m_sta_bssid));
|
||||
m_sta_connected = false;
|
||||
m_sta_got_ip = false;
|
||||
m_sta_is_connecting = true;
|
||||
m_sta_conn_info = {}; // Reset connection info
|
||||
m_sta_conn_info.sta_ssid = m_sta_ssid;
|
||||
m_sta_conn_info.sta_ssid_len = m_sta_ssid_len;
|
||||
|
||||
wifi_manager.StartStation();
|
||||
|
||||
// Wait for connection in a separate task to avoid blocking the BLUFI handler.
|
||||
xTaskCreate(
|
||||
[](void *ctx) {
|
||||
auto *self = static_cast<Blufi *>(ctx);
|
||||
auto &wifi = WifiManager::GetInstance();
|
||||
constexpr int kConnectTimeoutMs = 10000; // 10s
|
||||
constexpr TickType_t kDelayTick = pdMS_TO_TICKS(200);
|
||||
int waited_ms = 0;
|
||||
|
||||
while (waited_ms < kConnectTimeoutMs && !wifi.IsConnected()) {
|
||||
vTaskDelay(kDelayTick);
|
||||
waited_ms += 200;
|
||||
}
|
||||
|
||||
wifi_mode_t mode = GetWifiModeWithFallback(wifi);
|
||||
const int softap_conn_num = _get_softap_conn_num();
|
||||
|
||||
if (wifi.IsConnected()) {
|
||||
self->m_sta_is_connecting = false;
|
||||
self->m_sta_connected = true;
|
||||
self->m_sta_got_ip = true;
|
||||
self->m_provisioned = true;
|
||||
|
||||
auto current_ssid = wifi.GetSsid();
|
||||
if (!current_ssid.empty()) {
|
||||
self->m_sta_ssid_len = static_cast<int>(
|
||||
std::min(current_ssid.size(), sizeof(self->m_sta_ssid)));
|
||||
memcpy(self->m_sta_ssid, current_ssid.c_str(), self->m_sta_ssid_len);
|
||||
}
|
||||
|
||||
wifi_ap_record_t ap_info{};
|
||||
if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
|
||||
memcpy(self->m_sta_bssid, ap_info.bssid, sizeof(self->m_sta_bssid));
|
||||
}
|
||||
|
||||
esp_blufi_extra_info_t info = {};
|
||||
memcpy(info.sta_bssid, self->m_sta_bssid, sizeof(self->m_sta_bssid));
|
||||
info.sta_bssid_set = true;
|
||||
info.sta_ssid = self->m_sta_ssid;
|
||||
info.sta_ssid_len = self->m_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS,
|
||||
softap_conn_num, &info);
|
||||
ESP_LOGI(BLUFI_TAG, "connected to WiFi");
|
||||
|
||||
// Close BluFi session after successful provisioning to free resources.
|
||||
if (self->m_ble_is_connected) {
|
||||
esp_blufi_disconnect();
|
||||
}
|
||||
} else {
|
||||
self->m_sta_is_connecting = false;
|
||||
self->m_sta_connected = false;
|
||||
self->m_sta_got_ip = false;
|
||||
|
||||
esp_blufi_extra_info_t info = {};
|
||||
info.sta_ssid = self->m_sta_ssid;
|
||||
info.sta_ssid_len = self->m_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL,
|
||||
softap_conn_num, &info);
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to connect to WiFi via esp-wifi-connect");
|
||||
}
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"blufi_wifi_conn", 4096, this, 5, nullptr);
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI request wifi disconnect from AP");
|
||||
if (WifiManager::GetInstance().IsInitialized()) {
|
||||
WifiManager::GetInstance().StopStation();
|
||||
}
|
||||
m_sta_is_connecting = false;
|
||||
m_sta_connected = false;
|
||||
m_sta_got_ip = false;
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_GET_WIFI_STATUS: {
|
||||
auto &wifi = WifiManager::GetInstance();
|
||||
wifi_mode_t mode = GetWifiModeWithFallback(wifi);
|
||||
const int softap_conn_num = _get_softap_conn_num();
|
||||
|
||||
if (wifi.IsInitialized() && wifi.IsConnected()) {
|
||||
m_sta_connected = true;
|
||||
m_sta_got_ip = true;
|
||||
|
||||
auto current_ssid = wifi.GetSsid();
|
||||
if (!current_ssid.empty()) {
|
||||
m_sta_ssid_len =
|
||||
static_cast<int>(std::min(current_ssid.size(), sizeof(m_sta_ssid)));
|
||||
memcpy(m_sta_ssid, current_ssid.c_str(), m_sta_ssid_len);
|
||||
}
|
||||
|
||||
esp_blufi_extra_info_t info;
|
||||
memset(&info, 0, sizeof(esp_blufi_extra_info_t));
|
||||
memcpy(info.sta_bssid, m_sta_bssid, 6);
|
||||
info.sta_ssid = m_sta_ssid;
|
||||
info.sta_ssid_len = m_sta_ssid_len;
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, softap_conn_num,
|
||||
&info);
|
||||
} else if (m_sta_is_connecting) {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONNECTING, softap_conn_num,
|
||||
&m_sta_conn_info);
|
||||
} else {
|
||||
esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, softap_conn_num,
|
||||
&m_sta_conn_info);
|
||||
}
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI get wifi status");
|
||||
break;
|
||||
}
|
||||
case ESP_BLUFI_EVENT_RECV_STA_BSSID:
|
||||
memcpy(m_sta_config.sta.bssid, param->sta_bssid.bssid, 6);
|
||||
m_sta_config.sta.bssid_set = true;
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA BSSID");
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_SSID:
|
||||
strncpy((char *)m_sta_config.sta.ssid, (char *)param->sta_ssid.ssid,
|
||||
param->sta_ssid.ssid_len);
|
||||
m_sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA SSID: %s", m_sta_config.sta.ssid);
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_RECV_STA_PASSWD:
|
||||
strncpy((char *)m_sta_config.sta.password, (char *)param->sta_passwd.passwd,
|
||||
param->sta_passwd.passwd_len);
|
||||
m_sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
|
||||
ESP_LOGI(BLUFI_TAG, "Recv STA PASSWORD : %s", m_sta_config.sta.password);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(BLUFI_TAG, "Unhandled event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Blufi::_event_callback_trampoline(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) {
|
||||
GetInstance()._handle_event(event, param);
|
||||
}
|
||||
|
||||
void Blufi::_negotiate_data_handler_trampoline(uint8_t *data, int len, uint8_t **output_data,
|
||||
int *output_len, bool *need_free) {
|
||||
GetInstance()._dh_negotiate_data_handler(data, len, output_data, output_len, need_free);
|
||||
}
|
||||
|
||||
int Blufi::_encrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len) {
|
||||
return GetInstance()._aes_encrypt(iv8, crypt_data, crypt_len);
|
||||
}
|
||||
|
||||
int Blufi::_decrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len) {
|
||||
return GetInstance()._aes_decrypt(iv8, crypt_data, crypt_len);
|
||||
}
|
||||
|
||||
uint16_t Blufi::_checksum_func_trampoline(uint8_t iv8, uint8_t *data, int len) {
|
||||
return _crc_checksum(iv8, data, len);
|
||||
}
|
||||
123
main/boards/common/blufi.h
Normal file
123
main/boards/common/blufi.h
Normal file
@ -0,0 +1,123 @@
|
||||
#pragma once
|
||||
|
||||
#include <aes/esp_aes.h>
|
||||
#include "mbedtls/dhm.h"
|
||||
#include "mbedtls/aes.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_blufi_api.h"
|
||||
#include "esp_wifi_types.h"
|
||||
|
||||
|
||||
class Blufi {
|
||||
public:
|
||||
/**
|
||||
* @brief Get the singleton instance of the Blufi class.
|
||||
*/
|
||||
static Blufi &GetInstance();
|
||||
|
||||
/**
|
||||
* @brief Initializes the Bluetooth controller, host, and Blufi profile.
|
||||
* This is the main entry point to start the Blufi process.
|
||||
* @return ESP_OK on success, otherwise an error code.
|
||||
*/
|
||||
esp_err_t init();
|
||||
|
||||
/**
|
||||
* @brief Deinitializes Blufi and the Bluetooth stack.
|
||||
* @return ESP_OK on success, otherwise an error code.
|
||||
*/
|
||||
esp_err_t deinit();
|
||||
|
||||
// Delete copy constructor and assignment operator for singleton
|
||||
Blufi(const Blufi &) = delete;
|
||||
|
||||
Blufi &operator=(const Blufi &) = delete;
|
||||
|
||||
private:
|
||||
bool inited_ = false;
|
||||
|
||||
Blufi();
|
||||
|
||||
~Blufi();
|
||||
|
||||
|
||||
// Initialization logic
|
||||
static esp_err_t _controller_init();
|
||||
|
||||
static esp_err_t _controller_deinit();
|
||||
|
||||
static esp_err_t _host_init();
|
||||
|
||||
static esp_err_t _host_deinit();
|
||||
|
||||
static esp_err_t _gap_register_callback();
|
||||
|
||||
static esp_err_t _host_and_cb_init();
|
||||
|
||||
void _security_init();
|
||||
|
||||
void _security_deinit();
|
||||
|
||||
void _dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free);
|
||||
|
||||
int _aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
int _aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
static uint16_t _crc_checksum(uint8_t iv8, uint8_t *data, int len);
|
||||
|
||||
void _handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
|
||||
|
||||
static int _get_softap_conn_num();
|
||||
|
||||
// These C-style functions are registered with ESP-IDF and call the corresponding instance methods.
|
||||
|
||||
static void _event_callback_trampoline(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
|
||||
|
||||
static void _negotiate_data_handler_trampoline(uint8_t *data, int len, uint8_t **output_data, int *output_len,
|
||||
bool *need_free);
|
||||
|
||||
static int _encrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
static int _decrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
|
||||
|
||||
static uint16_t _checksum_func_trampoline(uint8_t iv8, uint8_t *data, int len);
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
static void _nimble_on_reset(int reason);
|
||||
static void _nimble_on_sync();
|
||||
static void _nimble_host_task(void *param);
|
||||
#endif
|
||||
|
||||
// Security context, formerly blufi_sec struct
|
||||
struct BlufiSecurity {
|
||||
#define DH_SELF_PUB_KEY_LEN 128
|
||||
uint8_t self_public_key[DH_SELF_PUB_KEY_LEN];
|
||||
#define SHARE_KEY_LEN 128
|
||||
uint8_t share_key[SHARE_KEY_LEN];
|
||||
size_t share_len;
|
||||
#define PSK_LEN 16
|
||||
uint8_t psk[PSK_LEN];
|
||||
uint8_t *dh_param;
|
||||
int dh_param_len;
|
||||
uint8_t iv[16];
|
||||
mbedtls_dhm_context *dhm;
|
||||
esp_aes_context *aes;
|
||||
};
|
||||
|
||||
BlufiSecurity *m_sec;
|
||||
|
||||
// State variables
|
||||
wifi_config_t m_sta_config{};
|
||||
wifi_config_t m_ap_config{};
|
||||
bool m_ble_is_connected;
|
||||
bool m_sta_connected;
|
||||
bool m_sta_got_ip;
|
||||
bool m_provisioned;
|
||||
bool m_deinited;
|
||||
uint8_t m_sta_bssid[6]{};
|
||||
uint8_t m_sta_ssid[32]{};
|
||||
int m_sta_ssid_len;
|
||||
bool m_sta_is_connecting;
|
||||
esp_blufi_extra_info_t m_sta_conn_info{};
|
||||
};
|
||||
@ -6,6 +6,7 @@
|
||||
#include <mqtt.h>
|
||||
#include <udp.h>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <network_interface.h>
|
||||
|
||||
#include "led/led.h"
|
||||
@ -13,6 +14,34 @@
|
||||
#include "camera.h"
|
||||
#include "assets.h"
|
||||
|
||||
/**
|
||||
* Network events for unified callback
|
||||
*/
|
||||
enum class NetworkEvent {
|
||||
Scanning, // Network is scanning (WiFi scanning, etc.)
|
||||
Connecting, // Network is connecting (data: SSID/network name)
|
||||
Connected, // Network connected successfully (data: SSID/network name)
|
||||
Disconnected, // Network disconnected
|
||||
WifiConfigModeEnter, // Entered WiFi configuration mode
|
||||
WifiConfigModeExit, // Exited WiFi configuration mode
|
||||
// Cellular modem specific events
|
||||
ModemDetecting, // Detecting modem (baud rate, module type)
|
||||
ModemErrorNoSim, // No SIM card detected
|
||||
ModemErrorRegDenied, // Network registration denied
|
||||
ModemErrorInitFailed, // Modem initialization failed
|
||||
ModemErrorTimeout // Operation timeout
|
||||
};
|
||||
|
||||
// Power save level enumeration
|
||||
enum class PowerSaveLevel {
|
||||
LOW_POWER, // Maximum power saving (lowest power consumption)
|
||||
BALANCED, // Medium power saving (balanced)
|
||||
PERFORMANCE, // No power saving (maximum power consumption / full performance)
|
||||
};
|
||||
|
||||
// Network event callback type (event, data)
|
||||
// data contains additional info like SSID for Connecting/Connected events
|
||||
using NetworkEventCallback = std::function<void(NetworkEvent event, const std::string& data)>;
|
||||
|
||||
void* create_board();
|
||||
class AudioCodec;
|
||||
@ -46,10 +75,11 @@ public:
|
||||
virtual Camera* GetCamera();
|
||||
virtual NetworkInterface* GetNetwork() = 0;
|
||||
virtual void StartNetwork() = 0;
|
||||
virtual void SetNetworkEventCallback(NetworkEventCallback callback) { (void)callback; }
|
||||
virtual const char* GetNetworkStateIcon() = 0;
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
|
||||
virtual std::string GetSystemInfoJson();
|
||||
virtual void SetPowerSaveMode(bool enabled) = 0;
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) = 0;
|
||||
virtual std::string GetBoardJson() = 0;
|
||||
virtual std::string GetDeviceStatusJson() = 0;
|
||||
};
|
||||
|
||||
@ -72,6 +72,11 @@ void DualNetworkBoard::StartNetwork() {
|
||||
current_board_->StartNetwork();
|
||||
}
|
||||
|
||||
void DualNetworkBoard::SetNetworkEventCallback(NetworkEventCallback callback) {
|
||||
// Forward the callback to the current board
|
||||
current_board_->SetNetworkEventCallback(std::move(callback));
|
||||
}
|
||||
|
||||
NetworkInterface* DualNetworkBoard::GetNetwork() {
|
||||
return current_board_->GetNetwork();
|
||||
}
|
||||
@ -80,8 +85,8 @@ const char* DualNetworkBoard::GetNetworkStateIcon() {
|
||||
return current_board_->GetNetworkStateIcon();
|
||||
}
|
||||
|
||||
void DualNetworkBoard::SetPowerSaveMode(bool enabled) {
|
||||
current_board_->SetPowerSaveMode(enabled);
|
||||
void DualNetworkBoard::SetPowerSaveLevel(PowerSaveLevel level) {
|
||||
current_board_->SetPowerSaveLevel(level);
|
||||
}
|
||||
|
||||
std::string DualNetworkBoard::GetBoardJson() {
|
||||
|
||||
@ -49,9 +49,10 @@ public:
|
||||
// 重写Board接口
|
||||
virtual std::string GetBoardType() override;
|
||||
virtual void StartNetwork() override;
|
||||
virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
|
||||
virtual NetworkInterface* GetNetwork() override;
|
||||
virtual const char* GetNetworkStateIcon() override;
|
||||
virtual void SetPowerSaveMode(bool enabled) override;
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
|
||||
virtual std::string GetBoardJson() override;
|
||||
virtual std::string GetDeviceStatusJson() override;
|
||||
};
|
||||
|
||||
@ -410,7 +410,7 @@ bool Esp32Camera::Capture() {
|
||||
frame_.len = buf.bytesused;
|
||||
frame_.data = (uint8_t*)heap_caps_malloc(frame_.len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (!frame_.data) {
|
||||
ESP_LOGE(TAG, "alloc frame copy failed: need allocate %d bytes", buf.bytesused);
|
||||
ESP_LOGE(TAG, "alloc frame copy failed: need allocate %lu bytes", buf.bytesused);
|
||||
if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
|
||||
ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
|
||||
}
|
||||
@ -482,7 +482,7 @@ bool Esp32Camera::Capture() {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(TAG, "unsupported sensor format: 0x%08x", sensor_format_);
|
||||
ESP_LOGE(TAG, "unsupported sensor format: 0x%08lx", sensor_format_);
|
||||
if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
|
||||
ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
|
||||
}
|
||||
@ -524,7 +524,7 @@ bool Esp32Camera::Capture() {
|
||||
rotate_cfg.in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888;
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "unsupported sensor format: 0x%08x", sensor_format_);
|
||||
ESP_LOGE(TAG, "unsupported sensor format: 0x%08lx", sensor_format_);
|
||||
if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
|
||||
ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
|
||||
}
|
||||
@ -639,7 +639,7 @@ bool Esp32Camera::Capture() {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGE(TAG, "unsupported sensor format for PPA rotation: 0x%08x", sensor_format_);
|
||||
ESP_LOGE(TAG, "unsupported sensor format for PPA rotation: 0x%08lx", sensor_format_);
|
||||
if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
|
||||
ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
|
||||
}
|
||||
|
||||
@ -6,11 +6,19 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <font_awesome.h>
|
||||
#include <opus_encoder.h>
|
||||
#include <utility>
|
||||
|
||||
static const char *TAG = "Ml307Board";
|
||||
|
||||
// Maximum retry count for modem detection
|
||||
static constexpr int MODEM_DETECT_MAX_RETRIES = 30;
|
||||
// Maximum retry count for network registration
|
||||
static constexpr int NETWORK_REG_MAX_RETRIES = 6;
|
||||
|
||||
Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) {
|
||||
}
|
||||
|
||||
@ -18,47 +26,106 @@ std::string Ml307Board::GetBoardType() {
|
||||
return "ml307";
|
||||
}
|
||||
|
||||
void Ml307Board::StartNetwork() {
|
||||
auto& application = Application::GetInstance();
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->SetStatus(Lang::Strings::DETECTING_MODULE);
|
||||
void Ml307Board::SetNetworkEventCallback(NetworkEventCallback callback) {
|
||||
network_event_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
void Ml307Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
|
||||
switch (event) {
|
||||
case NetworkEvent::ModemDetecting:
|
||||
ESP_LOGI(TAG, "Detecting modem...");
|
||||
break;
|
||||
case NetworkEvent::Connecting:
|
||||
ESP_LOGI(TAG, "Registering network...");
|
||||
break;
|
||||
case NetworkEvent::Connected:
|
||||
ESP_LOGI(TAG, "Network connected");
|
||||
break;
|
||||
case NetworkEvent::Disconnected:
|
||||
ESP_LOGW(TAG, "Network disconnected");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorNoSim:
|
||||
ESP_LOGE(TAG, "No SIM card detected");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorRegDenied:
|
||||
ESP_LOGE(TAG, "Network registration denied");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorInitFailed:
|
||||
ESP_LOGE(TAG, "Modem initialization failed");
|
||||
break;
|
||||
case NetworkEvent::ModemErrorTimeout:
|
||||
ESP_LOGE(TAG, "Operation timeout");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Notify external callback if set
|
||||
if (network_event_callback_) {
|
||||
network_event_callback_(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
void Ml307Board::NetworkTask() {
|
||||
auto& application = Application::GetInstance();
|
||||
|
||||
// Notify modem detection started
|
||||
OnNetworkEvent(NetworkEvent::ModemDetecting);
|
||||
|
||||
// Try to detect modem with retry limit
|
||||
int detect_retries = 0;
|
||||
while (detect_retries < MODEM_DETECT_MAX_RETRIES) {
|
||||
modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600);
|
||||
if (modem_ != nullptr) {
|
||||
break;
|
||||
}
|
||||
detect_retries++;
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
|
||||
if (modem_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to detect modem after %d retries", MODEM_DETECT_MAX_RETRIES);
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Modem detected successfully");
|
||||
|
||||
// Set up network state change callback
|
||||
// Note: Don't call GetCarrierName() here as it sends AT command and will block ReceiveTask
|
||||
modem_->OnNetworkStateChanged([this, &application](bool network_ready) {
|
||||
if (network_ready) {
|
||||
ESP_LOGI(TAG, "Network is ready");
|
||||
OnNetworkEvent(NetworkEvent::Connected);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Network is down");
|
||||
auto device_state = application.GetDeviceState();
|
||||
if (device_state == kDeviceStateListening || device_state == kDeviceStateSpeaking) {
|
||||
application.Schedule([this, &application]() {
|
||||
application.SetDeviceState(kDeviceStateIdle);
|
||||
});
|
||||
}
|
||||
OnNetworkEvent(NetworkEvent::Disconnected);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for network ready
|
||||
display->SetStatus(Lang::Strings::REGISTERING_NETWORK);
|
||||
while (true) {
|
||||
// Notify network registration started
|
||||
OnNetworkEvent(NetworkEvent::Connecting);
|
||||
|
||||
// Wait for network ready with retry limit
|
||||
int reg_retries = 0;
|
||||
while (reg_retries < NETWORK_REG_MAX_RETRIES) {
|
||||
auto result = modem_->WaitForNetworkReady();
|
||||
if (result == NetworkStatus::ErrorInsertPin) {
|
||||
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN);
|
||||
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
|
||||
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
|
||||
} else {
|
||||
if (result == NetworkStatus::Ready) {
|
||||
break;
|
||||
} else if (result == NetworkStatus::ErrorInsertPin) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorNoSim);
|
||||
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorRegDenied);
|
||||
} else if (result == NetworkStatus::ErrorTimeout) {
|
||||
OnNetworkEvent(NetworkEvent::ModemErrorTimeout);
|
||||
}
|
||||
reg_retries++;
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
}
|
||||
|
||||
if (!modem_->network_ready()) {
|
||||
ESP_LOGE(TAG, "Failed to register network after %d retries", NETWORK_REG_MAX_RETRIES);
|
||||
return;
|
||||
}
|
||||
|
||||
// Print the ML307 modem information
|
||||
std::string module_revision = modem_->GetModuleRevision();
|
||||
std::string imei = modem_->GetImei();
|
||||
@ -68,6 +135,15 @@ void Ml307Board::StartNetwork() {
|
||||
ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
|
||||
}
|
||||
|
||||
void Ml307Board::StartNetwork() {
|
||||
// Create network initialization task and return immediately
|
||||
xTaskCreate([](void* arg) {
|
||||
Ml307Board* board = static_cast<Ml307Board*>(arg);
|
||||
board->NetworkTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "ml307_net", 4096, this, 5, NULL);
|
||||
}
|
||||
|
||||
NetworkInterface* Ml307Board::GetNetwork() {
|
||||
return modem_.get();
|
||||
}
|
||||
@ -79,13 +155,13 @@ const char* Ml307Board::GetNetworkStateIcon() {
|
||||
int csq = modem_->GetCsq();
|
||||
if (csq == -1) {
|
||||
return FONT_AWESOME_SIGNAL_OFF;
|
||||
} else if (csq >= 0 && csq <= 14) {
|
||||
} else if (csq >= 0 && csq <= 9) {
|
||||
return FONT_AWESOME_SIGNAL_WEAK;
|
||||
} else if (csq >= 15 && csq <= 19) {
|
||||
} else if (csq >= 10 && csq <= 14) {
|
||||
return FONT_AWESOME_SIGNAL_FAIR;
|
||||
} else if (csq >= 20 && csq <= 24) {
|
||||
} else if (csq >= 15 && csq <= 19) {
|
||||
return FONT_AWESOME_SIGNAL_GOOD;
|
||||
} else if (csq >= 25 && csq <= 31) {
|
||||
} else if (csq >= 20 && csq <= 31) {
|
||||
return FONT_AWESOME_SIGNAL_STRONG;
|
||||
}
|
||||
|
||||
@ -106,8 +182,9 @@ std::string Ml307Board::GetBoardJson() {
|
||||
return board_json;
|
||||
}
|
||||
|
||||
void Ml307Board::SetPowerSaveMode(bool enabled) {
|
||||
// TODO: Implement power save mode for ML307
|
||||
void Ml307Board::SetPowerSaveLevel(PowerSaveLevel level) {
|
||||
// TODO: Implement power save level for ML307
|
||||
(void)level;
|
||||
}
|
||||
|
||||
std::string Ml307Board::GetDeviceStatusJson() {
|
||||
|
||||
@ -12,16 +12,25 @@ protected:
|
||||
gpio_num_t tx_pin_;
|
||||
gpio_num_t rx_pin_;
|
||||
gpio_num_t dtr_pin_;
|
||||
NetworkEventCallback network_event_callback_;
|
||||
|
||||
virtual std::string GetBoardJson() override;
|
||||
|
||||
// Internal helper to trigger network event callback
|
||||
void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
|
||||
|
||||
// Network initialization task (runs in FreeRTOS task)
|
||||
static void NetworkTaskEntry(void* arg);
|
||||
void NetworkTask();
|
||||
|
||||
public:
|
||||
Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC);
|
||||
virtual std::string GetBoardType() override;
|
||||
virtual void StartNetwork() override;
|
||||
virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
|
||||
virtual NetworkInterface* GetNetwork() override;
|
||||
virtual const char* GetNetworkStateIcon() override;
|
||||
virtual void SetPowerSaveMode(bool enabled) override;
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
|
||||
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
|
||||
virtual std::string GetDeviceStatusJson() override;
|
||||
};
|
||||
|
||||
@ -10,21 +10,38 @@
|
||||
#include <freertos/task.h>
|
||||
#include <esp_network.h>
|
||||
#include <esp_log.h>
|
||||
#include <utility>
|
||||
|
||||
#include <font_awesome.h>
|
||||
#include <wifi_manager.h>
|
||||
#include <wifi_station.h>
|
||||
#include <wifi_configuration_ap.h>
|
||||
#include <ssid_manager.h>
|
||||
#include "afsk_demod.h"
|
||||
#ifdef CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
|
||||
#include "blufi.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "WifiBoard";
|
||||
|
||||
// Connection timeout in seconds
|
||||
static constexpr int CONNECT_TIMEOUT_SEC = 60;
|
||||
|
||||
WifiBoard::WifiBoard() {
|
||||
Settings settings("wifi", true);
|
||||
wifi_config_mode_ = settings.GetInt("force_ap") == 1;
|
||||
if (wifi_config_mode_) {
|
||||
ESP_LOGI(TAG, "force_ap is set to 1, reset to 0");
|
||||
settings.SetInt("force_ap", 0);
|
||||
// Create connection timeout timer
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = OnWifiConnectTimeout,
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "wifi_connect_timer",
|
||||
.skip_unhandled_events = true
|
||||
};
|
||||
esp_timer_create(&timer_args, &connect_timer_);
|
||||
}
|
||||
|
||||
WifiBoard::~WifiBoard() {
|
||||
if (connect_timer_) {
|
||||
esp_timer_stop(connect_timer_);
|
||||
esp_timer_delete(connect_timer_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,87 +49,198 @@ std::string WifiBoard::GetBoardType() {
|
||||
return "wifi";
|
||||
}
|
||||
|
||||
void WifiBoard::EnterWifiConfigMode() {
|
||||
auto& application = Application::GetInstance();
|
||||
application.SetDeviceState(kDeviceStateWifiConfiguring);
|
||||
void WifiBoard::StartNetwork() {
|
||||
auto& wifi_manager = WifiManager::GetInstance();
|
||||
|
||||
auto& wifi_ap = WifiConfigurationAp::GetInstance();
|
||||
wifi_ap.SetLanguage(Lang::CODE);
|
||||
wifi_ap.SetSsidPrefix("Xiaozhi");
|
||||
wifi_ap.Start();
|
||||
// Initialize WiFi manager
|
||||
WifiManagerConfig config;
|
||||
config.ssid_prefix = "Xiaozhi";
|
||||
config.language = Lang::CODE;
|
||||
wifi_manager.Initialize(config);
|
||||
|
||||
// Wait 1.5 seconds to display board information
|
||||
vTaskDelay(pdMS_TO_TICKS(1500));
|
||||
// Set unified event callback - forward to NetworkEvent with SSID data
|
||||
wifi_manager.SetEventCallback([this, &wifi_manager](WifiEvent event) {
|
||||
std::string ssid = wifi_manager.GetSsid();
|
||||
switch (event) {
|
||||
case WifiEvent::Scanning:
|
||||
OnNetworkEvent(NetworkEvent::Scanning);
|
||||
break;
|
||||
case WifiEvent::Connecting:
|
||||
OnNetworkEvent(NetworkEvent::Connecting, ssid);
|
||||
break;
|
||||
case WifiEvent::Connected:
|
||||
OnNetworkEvent(NetworkEvent::Connected, ssid);
|
||||
break;
|
||||
case WifiEvent::Disconnected:
|
||||
OnNetworkEvent(NetworkEvent::Disconnected);
|
||||
break;
|
||||
case WifiEvent::ConfigModeEnter:
|
||||
OnNetworkEvent(NetworkEvent::WifiConfigModeEnter);
|
||||
break;
|
||||
case WifiEvent::ConfigModeExit:
|
||||
OnNetworkEvent(NetworkEvent::WifiConfigModeExit);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Display WiFi configuration AP SSID and web server URL
|
||||
std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT;
|
||||
hint += wifi_ap.GetSsid();
|
||||
hint += Lang::Strings::ACCESS_VIA_BROWSER;
|
||||
hint += wifi_ap.GetWebServerUrl();
|
||||
|
||||
// Announce WiFi configuration prompt
|
||||
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
|
||||
// Try to connect or enter config mode
|
||||
TryWifiConnect();
|
||||
}
|
||||
|
||||
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
int channel = 1;
|
||||
if (codec) {
|
||||
channel = codec->input_channels();
|
||||
}
|
||||
ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel);
|
||||
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel);
|
||||
#endif
|
||||
|
||||
// Wait forever until reset after configuration
|
||||
while (true) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
void WifiBoard::TryWifiConnect() {
|
||||
auto& ssid_manager = SsidManager::GetInstance();
|
||||
bool have_ssid = !ssid_manager.GetSsidList().empty();
|
||||
|
||||
if (have_ssid) {
|
||||
// Start connection attempt with timeout
|
||||
ESP_LOGI(TAG, "Starting WiFi connection attempt");
|
||||
esp_timer_start_once(connect_timer_, CONNECT_TIMEOUT_SEC * 1000000ULL);
|
||||
WifiManager::GetInstance().StartStation();
|
||||
} else {
|
||||
// No SSID configured, enter config mode
|
||||
// Wait for the board version to be shown
|
||||
vTaskDelay(pdMS_TO_TICKS(1500));
|
||||
StartWifiConfigMode();
|
||||
}
|
||||
}
|
||||
|
||||
void WifiBoard::StartNetwork() {
|
||||
// User can press BOOT button while starting to enter WiFi configuration mode
|
||||
if (wifi_config_mode_) {
|
||||
EnterWifiConfigMode();
|
||||
void WifiBoard::OnNetworkEvent(NetworkEvent event, const std::string& data) {
|
||||
switch (event) {
|
||||
case NetworkEvent::Connected:
|
||||
// Stop timeout timer
|
||||
esp_timer_stop(connect_timer_);
|
||||
#ifdef CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
|
||||
// make sure blufi resources has been released
|
||||
Blufi::GetInstance().deinit();
|
||||
#endif
|
||||
in_config_mode_ = false;
|
||||
ESP_LOGI(TAG, "Connected to WiFi: %s", data.c_str());
|
||||
break;
|
||||
case NetworkEvent::Scanning:
|
||||
ESP_LOGI(TAG, "WiFi scanning");
|
||||
break;
|
||||
case NetworkEvent::Connecting:
|
||||
ESP_LOGI(TAG, "WiFi connecting to %s", data.c_str());
|
||||
break;
|
||||
case NetworkEvent::Disconnected:
|
||||
ESP_LOGW(TAG, "WiFi disconnected");
|
||||
break;
|
||||
case NetworkEvent::WifiConfigModeEnter:
|
||||
ESP_LOGI(TAG, "WiFi config mode entered");
|
||||
in_config_mode_ = true;
|
||||
break;
|
||||
case NetworkEvent::WifiConfigModeExit:
|
||||
ESP_LOGI(TAG, "WiFi config mode exited");
|
||||
in_config_mode_ = false;
|
||||
// Try to connect with the new credentials
|
||||
TryWifiConnect();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Notify external callback if set
|
||||
if (network_event_callback_) {
|
||||
network_event_callback_(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
void WifiBoard::SetNetworkEventCallback(NetworkEventCallback callback) {
|
||||
network_event_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void WifiBoard::OnWifiConnectTimeout(void* arg) {
|
||||
auto* board = static_cast<WifiBoard*>(arg);
|
||||
ESP_LOGW(TAG, "WiFi connection timeout, entering config mode");
|
||||
|
||||
WifiManager::GetInstance().StopStation();
|
||||
board->StartWifiConfigMode();
|
||||
}
|
||||
|
||||
void WifiBoard::StartWifiConfigMode() {
|
||||
in_config_mode_ = true;
|
||||
// Transition to wifi configuring state
|
||||
Application::GetInstance().SetDeviceState(kDeviceStateWifiConfiguring);
|
||||
#ifdef CONFIG_USE_HOTSPOT_WIFI_PROVISIONING
|
||||
auto& wifi_manager = WifiManager::GetInstance();
|
||||
|
||||
wifi_manager.StartConfigAp();
|
||||
|
||||
// Show config prompt after a short delay
|
||||
Application::GetInstance().Schedule([&wifi_manager]() {
|
||||
std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT;
|
||||
hint += wifi_manager.GetApSsid();
|
||||
hint += Lang::Strings::ACCESS_VIA_BROWSER;
|
||||
hint += wifi_manager.GetApWebUrl();
|
||||
|
||||
Application::GetInstance().Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
|
||||
});
|
||||
#endif
|
||||
#if CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
|
||||
auto &blufi = Blufi::GetInstance();
|
||||
// initialize esp-blufi protocol
|
||||
blufi.init();
|
||||
#endif
|
||||
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
// Start acoustic provisioning task
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
int channel = codec ? codec->input_channels() : 1;
|
||||
ESP_LOGI(TAG, "Starting acoustic WiFi provisioning, channels: %d", channel);
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto ch = reinterpret_cast<intptr_t>(arg);
|
||||
auto& app = Application::GetInstance();
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
auto disp = Board::GetInstance().GetDisplay();
|
||||
audio_wifi_config::ReceiveWifiCredentialsFromAudio(&app, &wifi, disp, ch);
|
||||
vTaskDelete(NULL);
|
||||
}, "acoustic_wifi", 4096, reinterpret_cast<void*>(channel), 2, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WifiBoard::EnterWifiConfigMode() {
|
||||
ESP_LOGI(TAG, "EnterWifiConfigMode called");
|
||||
GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
auto state = app.GetDeviceState();
|
||||
|
||||
if (state == kDeviceStateSpeaking || state == kDeviceStateListening || state == kDeviceStateIdle) {
|
||||
// Reset protocol (close audio channel, reset protocol)
|
||||
Application::GetInstance().ResetProtocol();
|
||||
|
||||
xTaskCreate([](void* arg) {
|
||||
auto* board = static_cast<WifiBoard*>(arg);
|
||||
|
||||
// Wait for 1 second to allow speaking to finish gracefully
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
|
||||
// Stop any ongoing connection attempt
|
||||
esp_timer_stop(board->connect_timer_);
|
||||
WifiManager::GetInstance().StopStation();
|
||||
|
||||
// Enter config mode
|
||||
board->StartWifiConfigMode();
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}, "wifi_cfg_delay", 4096, this, 2, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
// If no WiFi SSID is configured, enter WiFi configuration mode
|
||||
auto& ssid_manager = SsidManager::GetInstance();
|
||||
auto ssid_list = ssid_manager.GetSsidList();
|
||||
if (ssid_list.empty()) {
|
||||
wifi_config_mode_ = true;
|
||||
EnterWifiConfigMode();
|
||||
if (state != kDeviceStateStarting) {
|
||||
ESP_LOGE(TAG, "EnterWifiConfigMode called but device state is not starting or speaking, device state: %d", state);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
wifi_station.OnScanBegin([this]() {
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000);
|
||||
});
|
||||
wifi_station.OnConnect([this](const std::string& ssid) {
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
std::string notification = Lang::Strings::CONNECT_TO;
|
||||
notification += ssid;
|
||||
notification += "...";
|
||||
display->ShowNotification(notification.c_str(), 30000);
|
||||
});
|
||||
wifi_station.OnConnected([this](const std::string& ssid) {
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
std::string notification = Lang::Strings::CONNECTED_TO;
|
||||
notification += ssid;
|
||||
display->ShowNotification(notification.c_str(), 30000);
|
||||
});
|
||||
wifi_station.Start();
|
||||
// Stop any ongoing connection attempt
|
||||
esp_timer_stop(connect_timer_);
|
||||
WifiManager::GetInstance().StopStation();
|
||||
|
||||
// Try to connect to WiFi, if failed, launch the WiFi configuration AP
|
||||
if (!wifi_station.WaitForConnected(60 * 1000)) {
|
||||
wifi_station.Stop();
|
||||
wifi_config_mode_ = true;
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
StartWifiConfigMode();
|
||||
}
|
||||
|
||||
bool WifiBoard::IsInWifiConfigMode() const {
|
||||
return WifiManager::GetInstance().IsConfigMode();
|
||||
}
|
||||
|
||||
NetworkInterface* WifiBoard::GetNetwork() {
|
||||
@ -121,147 +249,111 @@ NetworkInterface* WifiBoard::GetNetwork() {
|
||||
}
|
||||
|
||||
const char* WifiBoard::GetNetworkStateIcon() {
|
||||
if (wifi_config_mode_) {
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
|
||||
if (wifi.IsConfigMode()) {
|
||||
return FONT_AWESOME_WIFI;
|
||||
}
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
if (!wifi_station.IsConnected()) {
|
||||
if (!wifi.IsConnected()) {
|
||||
return FONT_AWESOME_WIFI_SLASH;
|
||||
}
|
||||
int8_t rssi = wifi_station.GetRssi();
|
||||
if (rssi >= -60) {
|
||||
|
||||
int rssi = wifi.GetRssi();
|
||||
if (rssi >= -65) {
|
||||
return FONT_AWESOME_WIFI;
|
||||
} else if (rssi >= -70) {
|
||||
} else if (rssi >= -75) {
|
||||
return FONT_AWESOME_WIFI_FAIR;
|
||||
} else {
|
||||
return FONT_AWESOME_WIFI_WEAK;
|
||||
}
|
||||
return FONT_AWESOME_WIFI_WEAK;
|
||||
}
|
||||
|
||||
std::string WifiBoard::GetBoardJson() {
|
||||
// Set the board type for OTA
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
std::string board_json = R"({)";
|
||||
board_json += R"("type":")" + std::string(BOARD_TYPE) + R"(",)";
|
||||
board_json += R"("name":")" + std::string(BOARD_NAME) + R"(",)";
|
||||
if (!wifi_config_mode_) {
|
||||
board_json += R"("ssid":")" + wifi_station.GetSsid() + R"(",)";
|
||||
board_json += R"("rssi":)" + std::to_string(wifi_station.GetRssi()) + R"(,)";
|
||||
board_json += R"("channel":)" + std::to_string(wifi_station.GetChannel()) + R"(,)";
|
||||
board_json += R"("ip":")" + wifi_station.GetIpAddress() + R"(",)";
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
std::string json = R"({"type":")" + std::string(BOARD_TYPE) + R"(",)";
|
||||
json += R"("name":")" + std::string(BOARD_NAME) + R"(",)";
|
||||
|
||||
if (!wifi.IsConfigMode()) {
|
||||
json += R"("ssid":")" + wifi.GetSsid() + R"(",)";
|
||||
json += R"("rssi":)" + std::to_string(wifi.GetRssi()) + R"(,)";
|
||||
json += R"("channel":)" + std::to_string(wifi.GetChannel()) + R"(,)";
|
||||
json += R"("ip":")" + wifi.GetIpAddress() + R"(",)";
|
||||
}
|
||||
board_json += R"("mac":")" + SystemInfo::GetMacAddress() + R"(")";
|
||||
board_json += R"(})";
|
||||
return board_json;
|
||||
|
||||
json += R"("mac":")" + SystemInfo::GetMacAddress() + R"("})";
|
||||
return json;
|
||||
}
|
||||
|
||||
void WifiBoard::SetPowerSaveMode(bool enabled) {
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
wifi_station.SetPowerSaveMode(enabled);
|
||||
}
|
||||
|
||||
void WifiBoard::ResetWifiConfiguration() {
|
||||
// Set a flag and reboot the device to enter the network configuration mode
|
||||
{
|
||||
Settings settings("wifi", true);
|
||||
settings.SetInt("force_ap", 1);
|
||||
void WifiBoard::SetPowerSaveLevel(PowerSaveLevel level) {
|
||||
WifiPowerSaveLevel wifi_level;
|
||||
switch (level) {
|
||||
case PowerSaveLevel::LOW_POWER:
|
||||
wifi_level = WifiPowerSaveLevel::LOW_POWER;
|
||||
break;
|
||||
case PowerSaveLevel::BALANCED:
|
||||
wifi_level = WifiPowerSaveLevel::BALANCED;
|
||||
break;
|
||||
case PowerSaveLevel::PERFORMANCE:
|
||||
default:
|
||||
wifi_level = WifiPowerSaveLevel::PERFORMANCE;
|
||||
break;
|
||||
}
|
||||
GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
// Reboot the device
|
||||
esp_restart();
|
||||
WifiManager::GetInstance().SetPowerSaveLevel(wifi_level);
|
||||
}
|
||||
|
||||
std::string WifiBoard::GetDeviceStatusJson() {
|
||||
/*
|
||||
* Return device status JSON
|
||||
*
|
||||
* The returned JSON structure is as follows:
|
||||
* {
|
||||
* "audio_speaker": {
|
||||
* "volume": 70
|
||||
* },
|
||||
* "screen": {
|
||||
* "brightness": 100,
|
||||
* "theme": "light"
|
||||
* },
|
||||
* "battery": {
|
||||
* "level": 50,
|
||||
* "charging": true
|
||||
* },
|
||||
* "network": {
|
||||
* "type": "wifi",
|
||||
* "ssid": "Xiaozhi",
|
||||
* "rssi": -60
|
||||
* },
|
||||
* "chip": {
|
||||
* "temperature": 25
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
auto& board = Board::GetInstance();
|
||||
auto root = cJSON_CreateObject();
|
||||
|
||||
// Audio speaker
|
||||
auto audio_speaker = cJSON_CreateObject();
|
||||
auto audio_codec = board.GetAudioCodec();
|
||||
if (audio_codec) {
|
||||
cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
|
||||
if (auto codec = board.GetAudioCodec()) {
|
||||
cJSON_AddNumberToObject(audio_speaker, "volume", codec->output_volume());
|
||||
}
|
||||
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
|
||||
|
||||
// Screen brightness
|
||||
auto backlight = board.GetBacklight();
|
||||
// Screen
|
||||
auto screen = cJSON_CreateObject();
|
||||
if (backlight) {
|
||||
if (auto backlight = board.GetBacklight()) {
|
||||
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
|
||||
}
|
||||
auto display = board.GetDisplay();
|
||||
if (display && display->height() > 64) { // For LCD display only
|
||||
auto theme = display->GetTheme();
|
||||
if (theme != nullptr) {
|
||||
if (auto display = board.GetDisplay(); display && display->height() > 64) {
|
||||
if (auto theme = display->GetTheme()) {
|
||||
cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
|
||||
}
|
||||
}
|
||||
cJSON_AddItemToObject(root, "screen", screen);
|
||||
|
||||
// Battery
|
||||
int battery_level = 0;
|
||||
bool charging = false;
|
||||
bool discharging = false;
|
||||
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
|
||||
cJSON* battery = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(battery, "level", battery_level);
|
||||
int level = 0;
|
||||
bool charging = false, discharging = false;
|
||||
if (board.GetBatteryLevel(level, charging, discharging)) {
|
||||
auto battery = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(battery, "level", level);
|
||||
cJSON_AddBoolToObject(battery, "charging", charging);
|
||||
cJSON_AddItemToObject(root, "battery", battery);
|
||||
}
|
||||
|
||||
// Network
|
||||
auto& wifi = WifiManager::GetInstance();
|
||||
auto network = cJSON_CreateObject();
|
||||
auto& wifi_station = WifiStation::GetInstance();
|
||||
cJSON_AddStringToObject(network, "type", "wifi");
|
||||
cJSON_AddStringToObject(network, "ssid", wifi_station.GetSsid().c_str());
|
||||
int rssi = wifi_station.GetRssi();
|
||||
if (rssi >= -60) {
|
||||
cJSON_AddStringToObject(network, "signal", "strong");
|
||||
} else if (rssi >= -70) {
|
||||
cJSON_AddStringToObject(network, "signal", "medium");
|
||||
} else {
|
||||
cJSON_AddStringToObject(network, "signal", "weak");
|
||||
}
|
||||
cJSON_AddStringToObject(network, "ssid", wifi.GetSsid().c_str());
|
||||
int rssi = wifi.GetRssi();
|
||||
const char* signal = rssi >= -60 ? "strong" : (rssi >= -70 ? "medium" : "weak");
|
||||
cJSON_AddStringToObject(network, "signal", signal);
|
||||
cJSON_AddItemToObject(root, "network", network);
|
||||
|
||||
// Chip
|
||||
float esp32temp = 0.0f;
|
||||
if (board.GetTemperature(esp32temp)) {
|
||||
// Chip temperature
|
||||
float temp = 0.0f;
|
||||
if (board.GetTemperature(temp)) {
|
||||
auto chip = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(chip, "temperature", esp32temp);
|
||||
cJSON_AddNumberToObject(chip, "temperature", temp);
|
||||
cJSON_AddItemToObject(root, "chip", chip);
|
||||
}
|
||||
|
||||
auto json_str = cJSON_PrintUnformatted(root);
|
||||
std::string json(json_str);
|
||||
cJSON_free(json_str);
|
||||
auto str = cJSON_PrintUnformatted(root);
|
||||
std::string result(str);
|
||||
cJSON_free(str);
|
||||
cJSON_Delete(root);
|
||||
return json;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2,23 +2,68 @@
|
||||
#define WIFI_BOARD_H
|
||||
|
||||
#include "board.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/event_groups.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
class WifiBoard : public Board {
|
||||
protected:
|
||||
bool wifi_config_mode_ = false;
|
||||
void EnterWifiConfigMode();
|
||||
esp_timer_handle_t connect_timer_ = nullptr;
|
||||
bool in_config_mode_ = false;
|
||||
NetworkEventCallback network_event_callback_ = nullptr;
|
||||
|
||||
virtual std::string GetBoardJson() override;
|
||||
|
||||
/**
|
||||
* Handle network event (called from WiFi manager callbacks)
|
||||
* @param event The network event type
|
||||
* @param data Additional data (e.g., SSID for Connecting/Connected events)
|
||||
*/
|
||||
void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
|
||||
|
||||
/**
|
||||
* Start WiFi connection attempt
|
||||
*/
|
||||
void TryWifiConnect();
|
||||
|
||||
/**
|
||||
* Enter WiFi configuration mode
|
||||
*/
|
||||
void StartWifiConfigMode();
|
||||
|
||||
/**
|
||||
* WiFi connection timeout callback
|
||||
*/
|
||||
static void OnWifiConnectTimeout(void* arg);
|
||||
|
||||
public:
|
||||
WifiBoard();
|
||||
virtual ~WifiBoard();
|
||||
|
||||
virtual std::string GetBoardType() override;
|
||||
|
||||
/**
|
||||
* Start network connection asynchronously
|
||||
* This function returns immediately. Network events are notified through the callback set by SetNetworkEventCallback().
|
||||
*/
|
||||
virtual void StartNetwork() override;
|
||||
|
||||
virtual NetworkInterface* GetNetwork() override;
|
||||
virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
|
||||
virtual const char* GetNetworkStateIcon() override;
|
||||
virtual void SetPowerSaveMode(bool enabled) override;
|
||||
virtual void ResetWifiConfiguration();
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
|
||||
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
|
||||
virtual std::string GetDeviceStatusJson() override;
|
||||
|
||||
/**
|
||||
* Enter WiFi configuration mode (thread-safe, can be called from any task)
|
||||
*/
|
||||
void EnterWifiConfigMode();
|
||||
|
||||
/**
|
||||
* Check if in WiFi config mode
|
||||
*/
|
||||
bool IsInWifiConfigMode() const;
|
||||
};
|
||||
|
||||
#endif // WIFI_BOARD_H
|
||||
|
||||
@ -15,7 +15,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include "esp_io_expander_tca95xx_16bit.h"
|
||||
|
||||
@ -119,8 +118,9 @@ private:
|
||||
iot_button_register_cb(btn_a, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<Df_K10Board*>(usr_data);
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
}, this);
|
||||
@ -149,8 +149,9 @@ private:
|
||||
iot_button_register_cb(btn_b, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<Df_K10Board*>(usr_data);
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
}, this);
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include "led/gpio_led.h"
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/gpio.h>
|
||||
@ -22,8 +21,9 @@ class DfrobotEsp32S3AiCam : public WifiBoard {
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "led/gpio_led.h"
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/gpio.h>
|
||||
@ -36,8 +35,9 @@ private:
|
||||
check_time = 0;
|
||||
}
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -46,13 +46,13 @@ private:
|
||||
ESP_LOGI(TAG, "DoubleClick times %d", click_times);
|
||||
if(click_times==3) {
|
||||
click_times = 0;
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
}
|
||||
});
|
||||
|
||||
boot_button_.OnLongPress([this]() {
|
||||
if(click_times>=3) {
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
} else {
|
||||
click_times = 0;
|
||||
check_time = 0;
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "power_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -105,8 +104,9 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -153,11 +153,11 @@ public:
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "backlight.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
@ -380,7 +380,7 @@ private:
|
||||
SemaphoreHandle_t touch_isr_mux_;
|
||||
};
|
||||
|
||||
class EspS3Cat : public WifiBoard {
|
||||
class EchoEar : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Cst816s* cst816s_;
|
||||
@ -390,6 +390,7 @@ private:
|
||||
PwmBacklight* backlight_ = nullptr;
|
||||
esp_timer_handle_t touchpad_timer_;
|
||||
esp_lcd_touch_handle_t tp; // LCD touch handle
|
||||
Esp32Camera* camera_ = nullptr;
|
||||
|
||||
void InitializeI2c()
|
||||
{
|
||||
@ -468,16 +469,15 @@ private:
|
||||
while (true) {
|
||||
if (touchpad->WaitForTouchEvent()) {
|
||||
auto &app = Application::GetInstance();
|
||||
auto &board = (EspS3Cat &)Board::GetInstance();
|
||||
auto &board = (EchoEar &)Board::GetInstance();
|
||||
|
||||
ESP_LOGI(TAG, "Touch event, TP_PIN_NUM_INT: %d", gpio_get_level(TP_PIN_NUM_INT));
|
||||
touchpad->UpdateTouchPoint();
|
||||
auto touch_event = touchpad->CheckTouchEvent();
|
||||
|
||||
if (touch_event == Cst816s::TOUCH_RELEASE) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting &&
|
||||
!WifiStation::GetInstance().IsConnected()) {
|
||||
board.ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
board.EnterWifiConfigMode();
|
||||
} else {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
@ -507,7 +507,7 @@ private:
|
||||
gpio_config(&int_gpio_config);
|
||||
gpio_install_isr_service(0);
|
||||
gpio_intr_enable(TP_PIN_NUM_INT);
|
||||
gpio_isr_handler_add(TP_PIN_NUM_INT, EspS3Cat::touch_isr_callback, cst816s_);
|
||||
gpio_isr_handler_add(TP_PIN_NUM_INT, EchoEar::touch_isr_callback, cst816s_);
|
||||
}
|
||||
|
||||
void InitializeSpi()
|
||||
@ -567,9 +567,10 @@ private:
|
||||
{
|
||||
boot_button_.OnClick([this]() {
|
||||
auto &app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode");
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -583,8 +584,33 @@ private:
|
||||
gpio_set_level(POWER_CTRL, 0);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
void InitializeCamera() {
|
||||
esp_video_init_usb_uvc_config_t usb_uvc_config = {
|
||||
.uvc = {
|
||||
.uvc_dev_num = 1,
|
||||
.task_stack = 4096,
|
||||
.task_priority = 5,
|
||||
.task_affinity = -1,
|
||||
},
|
||||
.usb = {
|
||||
.init_usb_host_lib = true,
|
||||
.task_stack = 4096,
|
||||
.task_priority = 5,
|
||||
.task_affinity = -1,
|
||||
},
|
||||
};
|
||||
|
||||
esp_video_init_config_t video_config = {
|
||||
.usb_uvc = &usb_uvc_config,
|
||||
};
|
||||
|
||||
camera_ = new Esp32Camera(video_config);
|
||||
}
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
|
||||
public:
|
||||
EspS3Cat() : boot_button_(BOOT_BUTTON_GPIO)
|
||||
EchoEar() : boot_button_(BOOT_BUTTON_GPIO)
|
||||
{
|
||||
InitializeI2c();
|
||||
uint8_t pcb_verison = DetectPcbVersion();
|
||||
@ -594,6 +620,9 @@ public:
|
||||
InitializeSpi();
|
||||
Initializest77916Display(pcb_verison);
|
||||
InitializeButtons();
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
InitializeCamera();
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override
|
||||
@ -628,6 +657,10 @@ public:
|
||||
{
|
||||
return backlight_;
|
||||
}
|
||||
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(EspS3Cat);
|
||||
DECLARE_BOARD(EchoEar);
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_log.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include "application.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
@ -76,9 +75,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting &&
|
||||
!WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "EspBox3Board"
|
||||
|
||||
@ -74,8 +73,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "EspBoxBoardLite"
|
||||
|
||||
@ -98,9 +97,10 @@ private:
|
||||
|
||||
void TogleState() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
}
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "EspBoxBoard"
|
||||
|
||||
@ -72,8 +71,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "mcp_server.h"
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
@ -23,7 +22,7 @@
|
||||
#include "servo_dog_ctrl.h"
|
||||
#include "led_strip.h"
|
||||
#include "driver/rmt_tx.h"
|
||||
#include "device_state_event.h"
|
||||
#include "device_state.h"
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
@ -171,8 +170,10 @@ private:
|
||||
|
||||
boot_button_.OnClick([this]() {
|
||||
auto &app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
// During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include "config.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <inttypes.h>
|
||||
#include <driver/i2c_master.h>
|
||||
@ -81,10 +80,12 @@ private:
|
||||
boot_button_.OnClick([this]()
|
||||
{
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState(); });
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeTouch()
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
#include "button.h"
|
||||
#include "led/single_led.h"
|
||||
#include "pin_config.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include "esp_lcd_gc9503.h"
|
||||
@ -27,6 +27,8 @@ private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
//add support ev board lcd
|
||||
esp_io_expander_handle_t expander = NULL;
|
||||
|
||||
@ -153,8 +155,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
@ -195,12 +198,39 @@ private:
|
||||
lvgl_port_add_touch(&touch_cfg);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
void InitializeCamera() {
|
||||
esp_video_init_usb_uvc_config_t usb_uvc_config = {
|
||||
.uvc = {
|
||||
.uvc_dev_num = 1,
|
||||
.task_stack = 4096,
|
||||
.task_priority = 5,
|
||||
.task_affinity = -1,
|
||||
},
|
||||
.usb = {
|
||||
.init_usb_host_lib = true,
|
||||
.task_stack = 4096,
|
||||
.task_priority = 5,
|
||||
.task_affinity = -1,
|
||||
},
|
||||
};
|
||||
}
|
||||
esp_video_init_config_t video_config = {
|
||||
.usb_uvc = &usb_uvc_config,
|
||||
};
|
||||
camera_ = new Esp32Camera(video_config);
|
||||
}
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
|
||||
public:
|
||||
ESP_S3_LCD_EV_Board_2() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeCodecI2c();
|
||||
InitializeButtons();
|
||||
InitializeRGB_GC9503V_Display();
|
||||
InitializeTouch();
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
InitializeCamera();
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
@ -230,6 +260,12 @@ public:
|
||||
return &led;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
|
||||
};
|
||||
|
||||
DECLARE_BOARD(ESP_S3_LCD_EV_Board_2);
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
#include "button.h"
|
||||
#include "led/single_led.h"
|
||||
#include "pin_config.h"
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include "esp_lcd_gc9503.h"
|
||||
@ -25,6 +25,7 @@ private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
//add support ev board lcd
|
||||
esp_io_expander_handle_t expander = NULL;
|
||||
@ -153,8 +154,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
@ -165,11 +167,39 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
void InitializeCamera() {
|
||||
esp_video_init_usb_uvc_config_t usb_uvc_config = {
|
||||
.uvc = {
|
||||
.uvc_dev_num = 1,
|
||||
.task_stack = 4096,
|
||||
.task_priority = 5,
|
||||
.task_affinity = -1,
|
||||
},
|
||||
.usb = {
|
||||
.init_usb_host_lib = true,
|
||||
.task_stack = 4096,
|
||||
.task_priority = 5,
|
||||
.task_affinity = -1,
|
||||
},
|
||||
};
|
||||
|
||||
esp_video_init_config_t video_config = {
|
||||
.usb_uvc = &usb_uvc_config,
|
||||
};
|
||||
|
||||
camera_ = new Esp32Camera(video_config);
|
||||
}
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
|
||||
public:
|
||||
ESP_S3_LCD_EV_Board() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeCodecI2c();
|
||||
InitializeButtons();
|
||||
InitializeRGB_GC9503V_Display();
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
InitializeCamera();
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
@ -199,6 +229,12 @@ public:
|
||||
return &led;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
|
||||
|
||||
};
|
||||
|
||||
DECLARE_BOARD(ESP_S3_LCD_EV_Board);
|
||||
|
||||
39
main/boards/esp-sensairshuttle/README.md
Normal file
39
main/boards/esp-sensairshuttle/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# ESP-SensairShuttle
|
||||
|
||||
## 简介
|
||||
|
||||
<div align="center">
|
||||
<a href="https://docs.espressif.com/projects/esp-dev-kits/zh_CN/latest/esp32c5/esp-sensairshuttle/index.html">
|
||||
<b> 开发版文档 </b>
|
||||
</a>
|
||||
|
|
||||
<a href="#传感器--shuttleboard-子板支持">
|
||||
<b> 传感器 & <i>ShuttleBoard</i> 文档 </b>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
ESP-SensairShuttle 是乐鑫携手 Bosch Sensortec 面向**动作感知**与**大模型人机交互**场景联合推出的开发板。
|
||||
|
||||
ESP-SensairShuttle 主控采用乐鑫 ESP32-C5-WROOM-1-N16R8 模组,具有 2.4 & 5 GHz 双频 Wi-Fi 6 (802.11ax)、Bluetooth® 5 (LE)、Zigbee 及 Thread (802.15.4) 无线通信能力。
|
||||
|
||||
## 传感器 & _ShuttleBoard_ 子板支持
|
||||
|
||||
即将推出,敬请期待。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
由于 ESP-SensairShuttle 需要配置较多的 sdkconfig 选项,推荐使用编译脚本编译。
|
||||
|
||||
**编译**
|
||||
|
||||
```bash
|
||||
python ./scripts/release.py esp-sensairshuttle
|
||||
```
|
||||
|
||||
如需手动编译,请参考 `main/boards/esp-sensairshuttle/config.json` 修改 menuconfig 对应选项。
|
||||
|
||||
**烧录**
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
249
main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc
Normal file
249
main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc
Normal file
@ -0,0 +1,249 @@
|
||||
#include "adc_pdm_audio_codec.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <driver/i2c.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/i2s_tdm.h>
|
||||
#include "adc_mic.h"
|
||||
#include "driver/i2s_pdm.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "hal/rtc_io_hal.h"
|
||||
#include "hal/gpio_ll.h"
|
||||
#include "settings.h"
|
||||
#include "config.h"
|
||||
|
||||
static const char TAG[] = "AdcPdmAudioCodec";
|
||||
|
||||
#define BSP_I2S_GPIO_CFG(_dout) \
|
||||
{ \
|
||||
.clk = GPIO_NUM_NC, \
|
||||
.dout = _dout, \
|
||||
.invert_flags = { \
|
||||
.clk_inv = false, \
|
||||
}, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mono Duplex I2S configuration structure
|
||||
*
|
||||
* This configuration is used by default in bsp_audio_init()
|
||||
*/
|
||||
#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
|
||||
{ \
|
||||
.clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
|
||||
.slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
|
||||
.gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
|
||||
}
|
||||
|
||||
AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
|
||||
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
|
||||
|
||||
input_reference_ = false;
|
||||
input_sample_rate_ = input_sample_rate;
|
||||
output_sample_rate_ = output_sample_rate;
|
||||
|
||||
uint8_t adc_channel[1] = {0};
|
||||
adc_channel[0] = adc_mic_channel;
|
||||
|
||||
audio_codec_adc_cfg_t cfg = {
|
||||
.handle = NULL,
|
||||
.max_store_buf_size = 1024 * 2,
|
||||
.conv_frame_size = 1024,
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.adc_channel_list = adc_channel,
|
||||
.adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]),
|
||||
.sample_rate_hz = (uint32_t)input_sample_rate,
|
||||
};
|
||||
const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
|
||||
|
||||
esp_codec_dev_cfg_t codec_dev_cfg = {
|
||||
.dev_type = ESP_CODEC_DEV_TYPE_IN,
|
||||
.data_if = adc_if,
|
||||
};
|
||||
input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
|
||||
if (!input_dev_) {
|
||||
ESP_LOGE(TAG, "Failed to create codec device");
|
||||
return;
|
||||
}
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
|
||||
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
|
||||
|
||||
i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
|
||||
pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
|
||||
pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
|
||||
pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
|
||||
pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
|
||||
pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
|
||||
const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
|
||||
|
||||
ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
|
||||
|
||||
audio_codec_i2s_cfg_t i2s_cfg = {
|
||||
.port = I2S_NUM_0,
|
||||
.rx_handle = NULL,
|
||||
.tx_handle = tx_handle_,
|
||||
};
|
||||
|
||||
const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
|
||||
|
||||
codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
|
||||
codec_dev_cfg.codec_if = NULL;
|
||||
codec_dev_cfg.data_if = i2s_data_if;
|
||||
output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
|
||||
|
||||
output_volume_ = 100;
|
||||
if(pa_ctl != GPIO_NUM_NC) {
|
||||
pa_ctrl_pin_ = pa_ctl;
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
}
|
||||
gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
|
||||
|
||||
if(pdm_speak_n != GPIO_NUM_NC){
|
||||
PIN_FUNC_SELECT(IO_MUX_GPIO10_REG, PIN_FUNC_GPIO);
|
||||
gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
|
||||
esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //反转输出 SD OUT 信号
|
||||
gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
|
||||
}
|
||||
|
||||
// 初始化输出定时器
|
||||
esp_timer_create_args_t output_timer_args = {
|
||||
.callback = &AdcPdmAudioCodec::OutputTimerCallback,
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "output_timer"
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
|
||||
|
||||
ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
|
||||
}
|
||||
|
||||
AdcPdmAudioCodec::~AdcPdmAudioCodec() {
|
||||
// 删除定时器
|
||||
if (output_timer_) {
|
||||
esp_timer_stop(output_timer_);
|
||||
esp_timer_delete(output_timer_);
|
||||
output_timer_ = nullptr;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
esp_codec_dev_delete(output_dev_);
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
esp_codec_dev_delete(input_dev_);
|
||||
}
|
||||
|
||||
void AdcPdmAudioCodec::SetOutputVolume(int volume) {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
|
||||
AudioCodec::SetOutputVolume(volume);
|
||||
}
|
||||
|
||||
void AdcPdmAudioCodec::EnableInput(bool enable) {
|
||||
if (enable == input_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
|
||||
.sample_rate = (uint32_t)input_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
|
||||
} else {
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
|
||||
}
|
||||
AudioCodec::EnableInput(enable);
|
||||
}
|
||||
|
||||
void AdcPdmAudioCodec::EnableOutput(bool enable) {
|
||||
if (enable == output_enabled_) {
|
||||
return;
|
||||
}
|
||||
if (enable) {
|
||||
// Play 16bit 1 channel
|
||||
esp_codec_dev_sample_info_t fs = {
|
||||
.bits_per_sample = 16,
|
||||
.channel = 1,
|
||||
.channel_mask = 0,
|
||||
.sample_rate = (uint32_t)output_sample_rate_,
|
||||
.mclk_multiple = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
|
||||
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
|
||||
|
||||
// 强制按板卡配置重配PDM TX时钟,覆盖第三方库在set_fmt中的默认up_sample_fs
|
||||
// 若通道已启用,先禁用再重配,最后再启用
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
|
||||
i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
|
||||
clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
|
||||
ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
|
||||
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
|
||||
if(pa_ctrl_pin_ != GPIO_NUM_NC){
|
||||
gpio_set_level(pa_ctrl_pin_, 1);
|
||||
}
|
||||
// 启用输出时启动定时器
|
||||
if (output_timer_) {
|
||||
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
|
||||
}
|
||||
|
||||
} else {
|
||||
// 禁用输出时停止定时器
|
||||
if (output_timer_) {
|
||||
esp_timer_stop(output_timer_);
|
||||
}
|
||||
if(pa_ctrl_pin_ != GPIO_NUM_NC){
|
||||
gpio_set_level(pa_ctrl_pin_, 0);
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
|
||||
}
|
||||
AudioCodec::EnableOutput(enable);
|
||||
}
|
||||
|
||||
int AdcPdmAudioCodec::Read(int16_t* dest, int samples) {
|
||||
if (input_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
int AdcPdmAudioCodec::Write(const int16_t* data, int samples) {
|
||||
if (output_enabled_) {
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
|
||||
// 重置输出定时器
|
||||
if (output_timer_) {
|
||||
esp_timer_stop(output_timer_);
|
||||
esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
|
||||
}
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
void AdcPdmAudioCodec::Start() {
|
||||
Settings settings("audio", false);
|
||||
output_volume_ = settings.GetInt("output_volume", output_volume_);
|
||||
if (output_volume_ <= 0) {
|
||||
ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
|
||||
output_volume_ = 10;
|
||||
}
|
||||
|
||||
EnableInput(true);
|
||||
EnableOutput(true);
|
||||
ESP_LOGI(TAG, "Audio codec started");
|
||||
}
|
||||
|
||||
// 定时器回调函数实现
|
||||
void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
|
||||
AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
|
||||
if (codec && codec->output_enabled_) {
|
||||
codec->EnableOutput(false);
|
||||
}
|
||||
}
|
||||
37
main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h
Normal file
37
main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef _BOX_AUDIO_CODEC_H
|
||||
#define _BOX_AUDIO_CODEC_H
|
||||
|
||||
#include "audio_codec.h"
|
||||
|
||||
#include <esp_codec_dev.h>
|
||||
#include <esp_codec_dev_defaults.h>
|
||||
#include <esp_timer.h>
|
||||
|
||||
class AdcPdmAudioCodec : public AudioCodec {
|
||||
private:
|
||||
esp_codec_dev_handle_t output_dev_ = nullptr;
|
||||
esp_codec_dev_handle_t input_dev_ = nullptr;
|
||||
gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC;
|
||||
|
||||
// 定时器相关成员变量
|
||||
esp_timer_handle_t output_timer_ = nullptr;
|
||||
static constexpr uint64_t TIMER_TIMEOUT_US = 120000; // 120ms = 120000us
|
||||
|
||||
// 定时器回调函数
|
||||
static void OutputTimerCallback(void* arg);
|
||||
|
||||
virtual int Read(int16_t* dest, int samples) override;
|
||||
virtual int Write(const int16_t* data, int samples) override;
|
||||
|
||||
public:
|
||||
AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
|
||||
uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl);
|
||||
virtual ~AdcPdmAudioCodec();
|
||||
|
||||
virtual void SetOutputVolume(int volume) override;
|
||||
virtual void EnableInput(bool enable) override;
|
||||
virtual void EnableOutput(bool enable) override;
|
||||
void Start();
|
||||
};
|
||||
|
||||
#endif // _BOX_AUDIO_CODEC_H
|
||||
40
main/boards/esp-sensairshuttle/config.h
Normal file
40
main/boards/esp-sensairshuttle/config.h
Normal file
@ -0,0 +1,40 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_PDM_UPSAMPLE_FS 480
|
||||
|
||||
#define AUDIO_ADC_MIC_CHANNEL 5
|
||||
#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_7
|
||||
#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_8
|
||||
#define AUDIO_PA_CTL_GPIO GPIO_NUM_1
|
||||
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_28
|
||||
#define DISPLAY_MOSI_PIN GPIO_NUM_23
|
||||
#define DISPLAY_CLK_PIN GPIO_NUM_24
|
||||
#define DISPLAY_DC_PIN GPIO_NUM_26
|
||||
#define DISPLAY_RST_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_CS_PIN GPIO_NUM_25
|
||||
|
||||
#define LCD_TP_SCL GPIO_NUM_3
|
||||
#define LCD_TP_SDA GPIO_NUM_2
|
||||
|
||||
#define LCD_TYPE_ST7789_SERIAL
|
||||
#define DISPLAY_WIDTH 284
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_INVERT_COLOR true
|
||||
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
|
||||
#define DISPLAY_OFFSET_X 36
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
#define DISPLAY_SPI_MODE 0
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
28
main/boards/esp-sensairshuttle/config.json
Normal file
28
main/boards/esp-sensairshuttle/config.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"target": "esp32c5",
|
||||
"builds": [
|
||||
{
|
||||
"name": "esp-sensairshuttle",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_IDF_TARGET=\"esp32c5\"",
|
||||
"CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6",
|
||||
"CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n",
|
||||
"CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n",
|
||||
"CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
|
||||
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
|
||||
"CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
|
||||
"CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048",
|
||||
"CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y",
|
||||
"CONFIG_SPIRAM=y",
|
||||
"CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=3072",
|
||||
"CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y",
|
||||
"CONFIG_LWIP_IPV6=n",
|
||||
"CONFIG_USE_ESP_WAKE_WORD=y",
|
||||
"CONFIG_SR_WN_WN9S_HIESP=y",
|
||||
"CONFIG_USE_EMOTE_MESSAGE_STYLE=y",
|
||||
"CONFIG_FLASH_CUSTOM_ASSETS=y",
|
||||
"CONFIG_CUSTOM_ASSETS_FILE=\"https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-echoear.bin\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
315
main/boards/esp-sensairshuttle/esp-sensairshuttle.cc
Normal file
315
main/boards/esp-sensairshuttle/esp-sensairshuttle.cc
Normal file
@ -0,0 +1,315 @@
|
||||
#include "wifi_board.h"
|
||||
#include "adc_pdm_audio_codec.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "mcp_server.h"
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_event.h>
|
||||
|
||||
#include "display/lcd_display.h"
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include "esp_lcd_ili9341.h"
|
||||
|
||||
#include "display/emote_display.h"
|
||||
|
||||
#include "assets/lang_config.h"
|
||||
#include "anim_player.h"
|
||||
#include "led_strip.h"
|
||||
#include "driver/rmt_tx.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
constexpr char TAG[] = "ESP_SensairShuttle";
|
||||
|
||||
static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
|
||||
// {cmd, { data }, data_size, delay_ms}
|
||||
{0x11, NULL, 0, 120}, // Sleep Out
|
||||
{0x36, (uint8_t []){0x00}, 1, 0}, // Memory Data Access Control
|
||||
{0x3A, (uint8_t []){0x05}, 1, 0}, // Interface Pixel Format (16-bit)
|
||||
{0xB2, (uint8_t []){0x0C, 0x0C, 0x00, 0x33, 0x33}, 5, 0}, // Porch Setting
|
||||
{0xB7, (uint8_t []){0x05}, 1, 0}, // Gate Control
|
||||
{0xBB, (uint8_t []){0x21}, 1, 0}, // VCOM Setting
|
||||
{0xC0, (uint8_t []){0x2C}, 1, 0}, // LCM Control
|
||||
{0xC2, (uint8_t []){0x01}, 1, 0}, // VDV and VRH Command Enable
|
||||
{0xC3, (uint8_t []){0x15}, 1, 0}, // VRH Set
|
||||
{0xC6, (uint8_t []){0x0F}, 1, 0}, // Frame Rate Control
|
||||
{0xD0, (uint8_t []){0xA7}, 1, 0}, // Power Control 1
|
||||
{0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0}, // Power Control 1
|
||||
{0xD6, (uint8_t []){0xA1}, 1, 0}, // Gate output GND in sleep mode
|
||||
{
|
||||
0xE0, (uint8_t [])
|
||||
{
|
||||
0xF0, 0x05, 0x0E, 0x08, 0x0A, 0x17, 0x39, 0x54,
|
||||
0x4E, 0x37, 0x12, 0x12, 0x31, 0x37
|
||||
}, 14, 0
|
||||
}, // Positive Gamma Control
|
||||
{
|
||||
0xE1, (uint8_t [])
|
||||
{
|
||||
0xF0, 0x10, 0x14, 0x0D, 0x0B, 0x05, 0x39, 0x44,
|
||||
0x4D, 0x38, 0x14, 0x14, 0x2E, 0x35
|
||||
}, 14, 0
|
||||
}, // Negative Gamma Control
|
||||
{0xE4, (uint8_t []){0x23, 0x00, 0x00}, 3, 0}, // Gate position control
|
||||
{0x21, NULL, 0, 0}, // Display Inversion On
|
||||
{0x29, NULL, 0, 0}, // Display On
|
||||
{0x2C, NULL, 0, 0}, // Memory Write
|
||||
};
|
||||
|
||||
class Cst816d : public I2cDevice {
|
||||
public:
|
||||
struct TouchPoint_t {
|
||||
int num = 0;
|
||||
int x = -1;
|
||||
int y = -1;
|
||||
};
|
||||
|
||||
enum TouchEvent {
|
||||
TOUCH_NONE,
|
||||
TOUCH_PRESS,
|
||||
TOUCH_RELEASE,
|
||||
TOUCH_HOLD
|
||||
};
|
||||
|
||||
Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr)
|
||||
{
|
||||
read_buffer_ = new uint8_t[6];
|
||||
was_touched_ = false;
|
||||
press_count_ = 0;
|
||||
}
|
||||
|
||||
~Cst816d()
|
||||
{
|
||||
delete[] read_buffer_;
|
||||
}
|
||||
|
||||
void UpdateTouchPoint()
|
||||
{
|
||||
ReadRegs(0x02, read_buffer_, 6);
|
||||
tp_.num = read_buffer_[0] & 0x0F;
|
||||
tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
|
||||
tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
|
||||
}
|
||||
|
||||
const TouchPoint_t &GetTouchPoint()
|
||||
{
|
||||
return tp_;
|
||||
}
|
||||
|
||||
TouchEvent CheckTouchEvent()
|
||||
{
|
||||
bool is_touched = (tp_.num > 0);
|
||||
TouchEvent event = TOUCH_NONE;
|
||||
|
||||
if (is_touched && !was_touched_) {
|
||||
// Press event (transition from not touched to touched)
|
||||
press_count_++;
|
||||
event = TOUCH_PRESS;
|
||||
ESP_LOGI(TAG, "TOUCH PRESS - count: %d, x: %d, y: %d", press_count_, tp_.x, tp_.y);
|
||||
} else if (!is_touched && was_touched_) {
|
||||
// Release event (transition from touched to not touched)
|
||||
event = TOUCH_RELEASE;
|
||||
ESP_LOGI(TAG, "TOUCH RELEASE - total presses: %d", press_count_);
|
||||
} else if (is_touched && was_touched_) {
|
||||
// Continuous touch (hold)
|
||||
event = TOUCH_HOLD;
|
||||
ESP_LOGD(TAG, "TOUCH HOLD - x: %d, y: %d", tp_.x, tp_.y);
|
||||
}
|
||||
|
||||
// Update previous state
|
||||
was_touched_ = is_touched;
|
||||
return event;
|
||||
}
|
||||
|
||||
int GetPressCount() const
|
||||
{
|
||||
return press_count_;
|
||||
}
|
||||
|
||||
void ResetPressCount()
|
||||
{
|
||||
press_count_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t* read_buffer_ = nullptr;
|
||||
TouchPoint_t tp_;
|
||||
|
||||
// Touch state tracking
|
||||
bool was_touched_;
|
||||
int press_count_;
|
||||
};
|
||||
|
||||
class EspSensairShuttle : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Cst816d* cst816d_;
|
||||
Display* display_ = nullptr;
|
||||
Button boot_button_;
|
||||
|
||||
void InitializeI2c()
|
||||
{
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = LCD_TP_SDA,
|
||||
.scl_io_num = LCD_TP_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
static void touch_event_task(void* arg)
|
||||
{
|
||||
Cst816d* touchpad = static_cast<Cst816d*>(arg);
|
||||
if (touchpad == nullptr) {
|
||||
ESP_LOGE(TAG, "Invalid touchpad pointer in touch_event_task");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
touchpad->UpdateTouchPoint();
|
||||
auto touch_event = touchpad->CheckTouchEvent();
|
||||
|
||||
if (touch_event == Cst816d::TOUCH_RELEASE) {
|
||||
auto &app = Application::GetInstance();
|
||||
auto &board = (EspSensairShuttle &)Board::GetInstance();
|
||||
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
board.EnterWifiConfigMode();
|
||||
} else {
|
||||
app.ToggleChatState();
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); // Poll every 50ms
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeCst816dTouchPad()
|
||||
{
|
||||
cst816d_ = new Cst816d(i2c_bus_, 0x15);
|
||||
xTaskCreate(touch_event_task, "touch_task", 2 * 1024, cst816d_, 5, NULL);
|
||||
}
|
||||
|
||||
void InitializeButtons()
|
||||
{
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode");
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
}
|
||||
|
||||
void InitializeSpi()
|
||||
{
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = DISPLAY_CLK_PIN;
|
||||
buscfg.quadwp_io_num = GPIO_NUM_NC;
|
||||
buscfg.quadhd_io_num = GPIO_NUM_NC;
|
||||
buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t);
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeLcdDisplay()
|
||||
{
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_CS_PIN;
|
||||
io_config.dc_gpio_num = DISPLAY_DC_PIN;
|
||||
io_config.spi_mode = DISPLAY_SPI_MODE;
|
||||
io_config.pclk_hz = 40 * 1000 * 1000;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
|
||||
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
const ili9341_vendor_config_t vendor_config = {
|
||||
.init_cmds = &vendor_specific_init[0],
|
||||
.init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
|
||||
};
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
panel_config.vendor_config = (void *) &vendor_config;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
|
||||
esp_lcd_panel_set_gap(panel, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
ESP_LOGI(TAG, "LCD panel create success, %p", panel);
|
||||
|
||||
#ifdef CONFIG_USE_EMOTE_MESSAGE_STYLE
|
||||
display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||
#else
|
||||
display_ = new SpiLcdDisplay(panel_io, panel,
|
||||
DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, 0, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
EspSensairShuttle() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeCst816dTouchPad();
|
||||
InitializeButtons();
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override
|
||||
{
|
||||
static AdcPdmAudioCodec audio_codec(
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_ADC_MIC_CHANNEL,
|
||||
AUDIO_PDM_SPEAK_P_GPIO,
|
||||
AUDIO_PDM_SPEAK_N_GPIO,
|
||||
AUDIO_PA_CTL_GPIO);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override
|
||||
{
|
||||
return display_;
|
||||
}
|
||||
|
||||
Cst816d* GetTouchpad()
|
||||
{
|
||||
return cst816d_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(EspSensairShuttle);
|
||||
@ -7,7 +7,6 @@
|
||||
#include "mcp_server.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
@ -80,8 +79,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
#include "config.h"
|
||||
#include "sleep_timer.h"
|
||||
#include "wifi_board.h"
|
||||
#include "wifi_station.h"
|
||||
|
||||
#ifdef IMU_INT_GPIO
|
||||
#include <esp_sleep.h>
|
||||
@ -217,7 +216,7 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
HandleUserActivity();
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
});
|
||||
|
||||
key_button_.OnClick([this]() {
|
||||
@ -394,11 +393,11 @@ public:
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (sleep_timer_) {
|
||||
sleep_timer_->SetEnabled(enabled);
|
||||
sleep_timer_->SetEnabled(level == PowerSaveLevel::LOW_POWER);
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
@ -124,8 +123,9 @@ void InitializePowerManager() {
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -181,11 +181,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "lamp_controller.h"
|
||||
#include "led/single_led.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -127,8 +126,9 @@ private:
|
||||
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
#include "power_save_timer.h"
|
||||
#include "axp2101.h"
|
||||
#include "i2c_device.h"
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -189,8 +188,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -274,10 +274,10 @@ private:
|
||||
void InitializeTools() {
|
||||
auto &mcp_server = McpServer::GetInstance();
|
||||
mcp_server.AddTool("self.system.reconfigure_wifi",
|
||||
"Reboot the device and enter WiFi configuration mode.\n"
|
||||
"End this conversation and enter WiFi configuration mode.\n"
|
||||
"**CAUTION** You must ask the user to confirm this action.",
|
||||
PropertyList(), [this](const PropertyList& properties) {
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@ -324,11 +324,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_spd2010.h>
|
||||
@ -169,8 +168,9 @@ private:
|
||||
iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<CustomBoard*>(usr_data);
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
}, this);
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_st77916.h>
|
||||
@ -385,8 +384,9 @@ private:
|
||||
iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
|
||||
auto self = static_cast<CustomBoard*>(usr_data);
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
}, this);
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_st77916.h>
|
||||
@ -358,8 +357,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/ledc.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
@ -290,8 +289,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -301,10 +301,10 @@ private:
|
||||
void InitializeTools() {
|
||||
auto &mcp_server = McpServer::GetInstance();
|
||||
mcp_server.AddTool("self.system.reconfigure_wifi",
|
||||
"Reboot the device and enter WiFi configuration mode.\n"
|
||||
"End this conversation and enter WiFi configuration mode.\n"
|
||||
"**CAUTION** You must ask the user to confirm this action.",
|
||||
PropertyList(), [this](const PropertyList& properties) {
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@ -358,11 +358,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
|
||||
virtual Camera* GetCamera() override {
|
||||
|
||||
@ -3,7 +3,15 @@
|
||||
"builds": [
|
||||
{
|
||||
"name": "esp32s3-korvo2-v3",
|
||||
"sdkconfig_append": []
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_CAMERA_OV2640=y",
|
||||
"CONFIG_CAMERA_OV3660=y",
|
||||
"CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||
"CONFIG_CAMERA_OV3660_DVP_RGB565_240X240_24FPS=y",
|
||||
"CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||
"CONFIG_CAMERA_OV2640_DVP_RGB565_240X240_25FPS=y",
|
||||
"CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -13,8 +13,9 @@
|
||||
#include <esp_lcd_ili9341.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
#include "esp32_camera.h"
|
||||
#include "power_manager.h"
|
||||
#include "power_save_timer.h"
|
||||
|
||||
#define TAG "esp32s3_korvo2_v3"
|
||||
/* ADC Buttons */
|
||||
@ -60,6 +61,24 @@ private:
|
||||
LcdDisplay* display_;
|
||||
esp_io_expander_handle_t io_expander_ = NULL;
|
||||
Esp32Camera* camera_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
void InitializePowerManager() {
|
||||
// PowerManager需要复用按钮的ADC句柄,所以在InitializeButtons之后调用
|
||||
// 传入按钮的ADC句柄指针,让PowerManager复用
|
||||
power_manager_ = new PowerManager(GPIO_NUM_NC, &bsp_adc_handle);
|
||||
}
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@ -231,18 +250,19 @@ private:
|
||||
|
||||
auto set_button = adc_button_[BSP_ADC_BUTTON_SET];
|
||||
set_button->OnClick([this]() {
|
||||
ESP_LOGI(TAG, "TODO %s:%d\n", __func__, __LINE__);
|
||||
EnterWifiConfigMode();
|
||||
});
|
||||
|
||||
auto rec_button = adc_button_[BSP_ADC_BUTTON_REC];
|
||||
rec_button->OnClick([this]() {
|
||||
ESP_LOGI(TAG, "TODO %s:%d\n", __func__, __LINE__);
|
||||
Application::GetInstance().ToggleChatState();
|
||||
});
|
||||
boot_button_.OnClick([this]() {});
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -375,12 +395,14 @@ private:
|
||||
public:
|
||||
Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board");
|
||||
InitializePowerSaveTimer();
|
||||
InitializeI2c();
|
||||
I2cDetect();
|
||||
InitializeTca9554();
|
||||
InitializeCamera();
|
||||
InitializeSpi();
|
||||
InitializeButtons();
|
||||
InitializeButtons(); // 先初始化按钮(创建ADC1句柄)
|
||||
InitializePowerManager(); // 后初始化PowerManager(复用ADC1句柄)
|
||||
#ifdef LCD_TYPE_ILI9341_SERIAL
|
||||
InitializeIli9341Display();
|
||||
#else
|
||||
@ -411,6 +433,24 @@ public:
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static bool last_discharging = false;
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
if (discharging != last_discharging) {
|
||||
power_save_timer_->SetEnabled(discharging);
|
||||
last_discharging = discharging;
|
||||
}
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(Esp32S3Korvo2V3Board);
|
||||
|
||||
250
main/boards/esp32s3-korvo2-v3/power_manager.h
Normal file
250
main/boards/esp32s3-korvo2-v3/power_manager.h
Normal file
@ -0,0 +1,250 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_adc/adc_cali.h>
|
||||
#include <esp_adc/adc_cali_scheme.h>
|
||||
|
||||
|
||||
class PowerManager {
|
||||
private:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
std::function<void(bool)> on_charging_status_changed_;
|
||||
std::function<void(bool)> on_low_battery_status_changed_;
|
||||
|
||||
gpio_num_t charging_pin_ = GPIO_NUM_NC;
|
||||
std::vector<uint16_t> adc_values_;
|
||||
uint32_t battery_level_ = 0;
|
||||
bool is_charging_ = false;
|
||||
bool is_low_battery_ = false;
|
||||
int ticks_ = 0;
|
||||
const int kBatteryAdcInterval = 60;
|
||||
const int kBatteryAdcDataCount = 3;
|
||||
const int kLowBatteryLevel = 20;
|
||||
|
||||
adc_oneshot_unit_handle_t adc_handle_;
|
||||
bool adc_handle_owned_ = false; // 标记ADC句柄是否由本类创建
|
||||
adc_cali_handle_t adc_cali_handle_ = nullptr; // ADC校准句柄
|
||||
|
||||
void CheckBatteryStatus() {
|
||||
// Get charging status
|
||||
bool new_charging_status = gpio_get_level(charging_pin_) == 1;
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (on_charging_status_changed_) {
|
||||
on_charging_status_changed_(is_charging_);
|
||||
}
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据不足,则读取电池电量数据
|
||||
if (adc_values_.size() < kBatteryAdcDataCount) {
|
||||
ReadBatteryAdcData();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
|
||||
ticks_++;
|
||||
if (ticks_ % kBatteryAdcInterval == 0) {
|
||||
ReadBatteryAdcData();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadBatteryAdcData() {
|
||||
int adc_raw = 0;
|
||||
int voltage_mv = 0; // ADC校准后的电压(mV)
|
||||
|
||||
// 多次采样取平均,提高稳定性
|
||||
uint32_t adc_sum = 0;
|
||||
const int sample_count = 10;
|
||||
for (int i = 0; i < sample_count; i++) {
|
||||
int temp_raw = 0;
|
||||
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &temp_raw));
|
||||
adc_sum += temp_raw;
|
||||
vTaskDelay(pdMS_TO_TICKS(10)); // 每次采样间隔10ms
|
||||
}
|
||||
adc_raw = adc_sum / sample_count;
|
||||
|
||||
// 使用ADC校准获取准确电压
|
||||
if (adc_cali_handle_) {
|
||||
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_mv));
|
||||
} else {
|
||||
// 如果没有校准,使用线性计算
|
||||
voltage_mv = (int)(adc_raw * 3300.0f / 4095.0f);
|
||||
}
|
||||
|
||||
// 根据分压比计算实际电池电压
|
||||
// 电路分压比: R21/(R20+R21) = 100K/300K = 1/3
|
||||
// 实际电池电压 = ADC测量电压 × 3
|
||||
int battery_voltage_mv = voltage_mv * 3;
|
||||
|
||||
// 将电压值添加到队列中用于平滑
|
||||
adc_values_.push_back(battery_voltage_mv);
|
||||
if (adc_values_.size() > kBatteryAdcDataCount) {
|
||||
adc_values_.erase(adc_values_.begin());
|
||||
}
|
||||
|
||||
uint32_t average_voltage = 0;
|
||||
for (auto value : adc_values_) {
|
||||
average_voltage += value;
|
||||
}
|
||||
average_voltage /= adc_values_.size();
|
||||
|
||||
// 定义电池电量区间(基于实际电池电压,单位mV)
|
||||
const struct {
|
||||
uint16_t voltage_mv; // 电池电压(mV)
|
||||
uint8_t level; // 电量百分比
|
||||
} levels[] = {
|
||||
{3500, 0}, // 3.5V
|
||||
{3640, 20}, // 3.64V
|
||||
{3760, 40}, // 3.76V
|
||||
{3880, 60}, // 3.88V
|
||||
{4000, 80}, // 4.0V
|
||||
{4200, 100} // 4.2V
|
||||
};
|
||||
|
||||
// 低于最低值时
|
||||
if (average_voltage < levels[0].voltage_mv) {
|
||||
battery_level_ = 0;
|
||||
}
|
||||
// 高于最高值时
|
||||
else if (average_voltage >= levels[5].voltage_mv) {
|
||||
battery_level_ = 100;
|
||||
} else {
|
||||
// 线性插值计算中间值
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (average_voltage >= levels[i].voltage_mv && average_voltage < levels[i+1].voltage_mv) {
|
||||
float ratio = static_cast<float>(average_voltage - levels[i].voltage_mv) /
|
||||
(levels[i+1].voltage_mv - levels[i].voltage_mv);
|
||||
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check low battery status
|
||||
if (adc_values_.size() >= kBatteryAdcDataCount) {
|
||||
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
|
||||
if (new_low_battery_status != is_low_battery_) {
|
||||
is_low_battery_ = new_low_battery_status;
|
||||
if (on_low_battery_status_changed_) {
|
||||
on_low_battery_status_changed_(is_low_battery_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI("PowerManager", "ADC raw: %d, ADC voltage: %dmV, Battery: %ldmV (%.2fV), level: %ld%%",
|
||||
adc_raw, voltage_mv, average_voltage, average_voltage/1000.0f, battery_level_);
|
||||
}
|
||||
|
||||
public:
|
||||
// 构造函数:使用外部ADC句柄(用于复用已存在的ADC)
|
||||
PowerManager(gpio_num_t pin, adc_oneshot_unit_handle_t* external_adc_handle = nullptr)
|
||||
: charging_pin_(pin), adc_handle_owned_(false) {
|
||||
if(charging_pin_ != GPIO_NUM_NC){
|
||||
// 初始化充电引脚
|
||||
gpio_config_t io_conf = {};
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_INPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << charging_pin_);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
}
|
||||
|
||||
// 创建电池电量检查定时器
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = [](void* arg) {
|
||||
PowerManager* self = static_cast<PowerManager*>(arg);
|
||||
self->CheckBatteryStatus();
|
||||
},
|
||||
.arg = this,
|
||||
.dispatch_method = ESP_TIMER_TASK,
|
||||
.name = "battery_check_timer",
|
||||
.skip_unhandled_events = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
|
||||
|
||||
// 初始化或复用 ADC
|
||||
if (external_adc_handle != nullptr && *external_adc_handle != nullptr) {
|
||||
// 复用外部ADC句柄
|
||||
adc_handle_ = *external_adc_handle;
|
||||
adc_handle_owned_ = false;
|
||||
} else {
|
||||
// 创建新的ADC句柄
|
||||
adc_oneshot_unit_init_cfg_t init_config = {
|
||||
.unit_id = ADC_UNIT_1, // GPIO6 对应 ADC1
|
||||
.ulp_mode = ADC_ULP_MODE_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
|
||||
adc_handle_owned_ = true;
|
||||
}
|
||||
|
||||
// 配置ADC通道
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config)); // GPIO6 = ADC1_CHANNEL_5
|
||||
|
||||
// 初始化ADC校准
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.chan = ADC_CHANNEL_5,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI("PowerManager", "ADC calibration initialized successfully");
|
||||
} else {
|
||||
ESP_LOGW("PowerManager", "ADC calibration failed, using linear calculation");
|
||||
adc_cali_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~PowerManager() {
|
||||
if (timer_handle_) {
|
||||
esp_timer_stop(timer_handle_);
|
||||
esp_timer_delete(timer_handle_);
|
||||
}
|
||||
// 删除ADC校准句柄
|
||||
if (adc_cali_handle_) {
|
||||
adc_cali_delete_scheme_curve_fitting(adc_cali_handle_);
|
||||
}
|
||||
// 只有当ADC句柄是本类创建的时候才删除
|
||||
if (adc_handle_ && adc_handle_owned_) {
|
||||
adc_oneshot_del_unit(adc_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsCharging() {
|
||||
// 如果电量已经满了,则不再显示充电中
|
||||
if (battery_level_ == 100) {
|
||||
return false;
|
||||
}
|
||||
return is_charging_;
|
||||
}
|
||||
|
||||
bool IsDischarging() {
|
||||
// 没有区分充电和放电,所以直接返回相反状态
|
||||
return !is_charging_;
|
||||
}
|
||||
|
||||
uint8_t GetBatteryLevel() {
|
||||
return battery_level_;
|
||||
}
|
||||
|
||||
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
|
||||
on_low_battery_status_changed_ = callback;
|
||||
}
|
||||
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
on_charging_status_changed_ = callback;
|
||||
}
|
||||
};
|
||||
@ -10,7 +10,6 @@
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_efuse_table.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
@ -109,8 +108,9 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -248,11 +248,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
7
main/boards/hu-087/README.md
Normal file
7
main/boards/hu-087/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
[product](https://fr.aliexpress.com/item/1005010207331462.html)
|
||||
|
||||
One of the cheapest kit in a watch form factor.
|
||||
|
||||
## Programming ##
|
||||
Before assembling the product, unglue the battery from the board to access the programming header. You'll need a ESP32 programming dongle with control of the EN and IO0 pins. The USB connector is not wired to the USB pins of the ESP32-S3 USB interface and can only be used for charging.
|
||||
28
main/boards/hu-087/config.h
Normal file
28
main/boards/hu-087/config.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
|
||||
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
|
||||
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
|
||||
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
|
||||
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
|
||||
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
|
||||
#define AUDIO_I2S_SPK_GPIO_CTLR GPIO_NUM_17
|
||||
|
||||
|
||||
#define TOUCH_BUTTON_GPIO GPIO_NUM_18
|
||||
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_41
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_42
|
||||
#define DISPLAY_WIDTH 128
|
||||
#define DISPLAY_HEIGHT 64
|
||||
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
9
main/boards/hu-087/config.json
Normal file
9
main/boards/hu-087/config.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "hu-087",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
151
main/boards/hu-087/hu_087_board.cc
Normal file
151
main/boards/hu-087/hu_087_board.cc
Normal file
@ -0,0 +1,151 @@
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_log.h>
|
||||
|
||||
#include "application.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "boards/common/wifi_board.h"
|
||||
#include "button.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "config.h"
|
||||
#include "display/oled_display.h"
|
||||
#include "lamp_controller.h"
|
||||
#include "led/single_led.h"
|
||||
#include "mcp_server.h"
|
||||
#include "system_reset.h"
|
||||
|
||||
#define TAG "Hu087Board"
|
||||
|
||||
class Hu087Board : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
|
||||
esp_lcd_panel_handle_t panel_ = nullptr;
|
||||
Display* display_ = nullptr;
|
||||
Button touch_button_;
|
||||
|
||||
void InitializeDisplayI2c() {
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = (i2c_port_t)0,
|
||||
.sda_io_num = DISPLAY_SDA_PIN,
|
||||
.scl_io_num = DISPLAY_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags =
|
||||
{
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeSsd1306Display() {
|
||||
// SSD1306 config
|
||||
esp_lcd_panel_io_i2c_config_t io_config = {
|
||||
.dev_addr = 0x3C,
|
||||
.on_color_trans_done = nullptr,
|
||||
.user_ctx = nullptr,
|
||||
.control_phase_bytes = 1,
|
||||
.dc_bit_offset = 6,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
.flags =
|
||||
{
|
||||
.dc_low_on_data = 0,
|
||||
.disable_control_phase = 0,
|
||||
},
|
||||
.scl_speed_hz = 400 * 1000,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(
|
||||
esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
|
||||
|
||||
ESP_LOGI(TAG, "Install SSD1306 driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = -1;
|
||||
panel_config.bits_per_pixel = 1;
|
||||
|
||||
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
|
||||
.height = static_cast<uint8_t>(DISPLAY_HEIGHT),
|
||||
};
|
||||
panel_config.vendor_config = &ssd1306_config;
|
||||
|
||||
ESP_ERROR_CHECK(
|
||||
esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
|
||||
ESP_LOGI(TAG, "SSD1306 driver installed");
|
||||
|
||||
// Reset the display
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
|
||||
if (esp_lcd_panel_init(panel_) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize display");
|
||||
display_ = new NoDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the display to on
|
||||
ESP_LOGI(TAG, "Turning display on");
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
|
||||
|
||||
display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT,
|
||||
DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
}
|
||||
|
||||
void initializeAmpCtrl() {
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << AUDIO_I2S_SPK_GPIO_CTLR), // Select GPIO 2
|
||||
.mode = GPIO_MODE_OUTPUT, // Set as output
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE, // Disable pull-up
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
|
||||
.intr_type = GPIO_INTR_DISABLE // Disable interrupts
|
||||
};
|
||||
gpio_config(&io_conf);
|
||||
gpio_set_level(AUDIO_I2S_SPK_GPIO_CTLR, 1);
|
||||
};
|
||||
|
||||
void InitializeButtons() {
|
||||
touch_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
touch_button_.OnLongPress([this]() {
|
||||
auto codec = GetAudioCodec();
|
||||
auto volume = codec->output_volume() + 10;
|
||||
if (volume > 100) {
|
||||
volume = 100;
|
||||
} // Need to implement logic to lower volume
|
||||
codec->SetOutputVolume(volume);
|
||||
GetDisplay()->ShowNotification(Lang::Strings::VOLUME +
|
||||
std::to_string(volume));
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
Hu087Board() : touch_button_(TOUCH_BUTTON_GPIO) {
|
||||
InitializeDisplayI2c();
|
||||
InitializeSsd1306Display();
|
||||
InitializeButtons();
|
||||
initializeAmpCtrl(); // Could control the amp ctrl pin throught voice
|
||||
// detection i guess
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static NoAudioCodecSimplex audio_codec(
|
||||
AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK,
|
||||
AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_RIGHT, AUDIO_I2S_MIC_GPIO_SCK,
|
||||
AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override { return display_; }
|
||||
};
|
||||
|
||||
DECLARE_BOARD(Hu087Board);
|
||||
@ -10,7 +10,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
#include "led/single_led.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "esp_lcd_panel_gc9301.h"
|
||||
@ -230,11 +229,11 @@ private:
|
||||
} });
|
||||
|
||||
// 电源键三击:重置WiFi
|
||||
pwr_button_.OnMultipleClick([this]()
|
||||
{
|
||||
pwr_button_.OnMultipleClick([this]() {
|
||||
ESP_LOGI(TAG, "Power button triple click: 重置WiFi");
|
||||
power_save_timer_->WakeUp();
|
||||
ResetWifiConfiguration(); }, 3);
|
||||
EnterWifiConfigMode();
|
||||
}, 3);
|
||||
|
||||
wifi_button.OnPressDown([this]()
|
||||
{
|
||||
@ -372,11 +371,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
#include "axp2101.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/i2c_master.h>
|
||||
@ -173,10 +172,10 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
#include "led/circular_strip.h"
|
||||
#include "led_strip_control.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_efuse_table.h>
|
||||
#include <driver/i2c_master.h>
|
||||
@ -47,8 +46,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
|
||||
@ -12,7 +12,6 @@
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#define TAG "kevin-sp-v3"
|
||||
@ -39,8 +38,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
|
||||
@ -3,7 +3,15 @@
|
||||
"builds": [
|
||||
{
|
||||
"name": "kevin-sp-v4-dev",
|
||||
"sdkconfig_append": []
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_CAMERA_OV2640=y",
|
||||
"CONFIG_CAMERA_OV3660=y",
|
||||
"CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||
"CONFIG_CAMERA_OV3660_DVP_RGB565_240X240_24FPS=y",
|
||||
"CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||
"CONFIG_CAMERA_OV2640_DVP_RGB565_240X240_25FPS=y",
|
||||
"CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -10,17 +10,15 @@
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#define TAG "kevin-sp-v4"
|
||||
|
||||
class KEVIN_SP_V4Board : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t display_i2c_bus_;
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
@ -37,7 +35,7 @@ private:
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
@ -54,8 +52,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
@ -118,12 +117,8 @@ private:
|
||||
};
|
||||
|
||||
esp_video_init_sccb_config_t sccb_config = {
|
||||
.init_sccb = true,
|
||||
.i2c_config = {
|
||||
.port = 1,
|
||||
.scl_pin = CAMERA_PIN_SIOC,
|
||||
.sda_pin = GPIO_NUM_NC,
|
||||
},
|
||||
.init_sccb = false,
|
||||
.i2c_handle = i2c_bus_,
|
||||
.freq = 100000,
|
||||
};
|
||||
|
||||
@ -159,7 +154,7 @@ public:
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
static Es8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR);
|
||||
return &audio_codec;
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include "esp_lcd_gc9503.h"
|
||||
@ -123,8 +122,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "labplus_ledong_v2"
|
||||
|
||||
@ -56,8 +55,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "mpython_v3"
|
||||
|
||||
@ -53,8 +52,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
});
|
||||
boot_button_.OnPressDown([this]() {
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "LichuangC3DevBoard"
|
||||
|
||||
@ -51,8 +50,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "esp32_camera.h"
|
||||
#include "mcp_server.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_touch_ft5x06.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <lvgl.h>
|
||||
@ -107,8 +107,10 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
// During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@ -245,6 +247,17 @@ private:
|
||||
camera_ = new Esp32Camera(video_config);
|
||||
}
|
||||
|
||||
void InitializeTools() {
|
||||
auto &mcp_server = McpServer::GetInstance();
|
||||
mcp_server.AddTool("self.system.reconfigure_wifi",
|
||||
"End this conversation and enter WiFi configuration mode.\n"
|
||||
"**CAUTION** You must ask the user to confirm this action.",
|
||||
PropertyList(), [this](const PropertyList& properties) {
|
||||
EnterWifiConfigMode();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
@ -253,6 +266,7 @@ public:
|
||||
InitializeTouch();
|
||||
InitializeButtons();
|
||||
InitializeCamera();
|
||||
InitializeTools();
|
||||
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user