Merge branch '78:main' into main

This commit is contained in:
胡巴 2025-12-19 14:31:42 +08:00 committed by GitHub
commit 976dba60fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1052 additions and 70 deletions

36
docs/blufi.md Normal file
View File

@ -0,0 +1,36 @@
# BluFi 配网(集成 esp-wifi-connect
本文档说明如何在小智固件中启用和使用 BluFiBLE WiFi 配网),并结合项目内置的 `esp-wifi-connect` 组件完成 WiFi 连接与存储。官方
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 或自研客户端)连接设备,发送 WiFi SSID/密码。
2) 设备侧在 `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP` 中将凭据写入 `SsidManager`(存储到 NVS属于 `esp-wifi-connect` 组件)。
3) 随后启动 `WifiStation` 扫描并连接;状态通过 BluFi 返回。
4) 连接成功后当前固件会延时 1 秒并重启,使主应用在下次启动时直接使用新 WiFi失败则返回失败状态。
## 使用步骤
1. 配置:在 menuconfig 开启 `Esp Blufi`。编译并烧录固件。
2. 触发配网:设备首次启动且没有已保存的 WiFi 时会自动进入配网。
3. 手机端操作:打开 EspBlufi App或其他 BluFi 客户端),搜索并连接设备,按提示输入 WiFi SSID/密码并发送。
4. 观察结果:
- 成功BluFi 报告连接成功,设备自动使用新的 WiFi。
- 失败BluFi 返回失败状态,可重新发送或检查路由器。
## 注意事项
- BluFi 与 Hotspot/声波配网可以同时编译,但会同时启动,增加内存占用。建议在 menuconfig 中只保留一种方式。
- 若多次测试,建议清除或覆盖存储的 SSID`wifi` 命名空间),避免旧配置干扰。
- 如果使用自定义 BluFi 客户端,需遵循官方协议帧格式,参考上文官方文档链接。
- 官方文档中已提供EspBlufi APP下载地址

View File

@ -583,6 +583,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")

View File

@ -671,6 +671,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"
@ -678,12 +701,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
@ -694,7 +711,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

View File

@ -780,9 +780,10 @@ void Application::HandleWakeWordDetectedEvent() {
protocol_->SendWakeWordDetected(wake_word);
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
#else
// Set flag to play popup sound after state changes to listening
// (PlaySound here would be cleared by ResetDecoder in EnableVoiceProcessing)
play_popup_on_listening_ = true;
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
// Play the pop up sound to indicate the wake word is detected
audio_service_.PlaySound(Lang::Sounds::OGG_POPUP);
#endif
} else if (state == kDeviceStateSpeaking) {
AbortSpeaking(kAbortReasonWakeWordDetected);
@ -825,6 +826,12 @@ void Application::HandleStateChangedEvent() {
audio_service_.EnableVoiceProcessing(true);
audio_service_.EnableWakeWordDetection(false);
}
// Play popup sound after ResetDecoder (in EnableVoiceProcessing) has been called
if (play_popup_on_listening_) {
play_popup_on_listening_ = false;
audio_service_.PlaySound(Lang::Sounds::OGG_POPUP);
}
break;
case kDeviceStateSpeaking:
display->SetStatus(Lang::Strings::SPEAKING);
@ -846,7 +853,7 @@ void Application::HandleStateChangedEvent() {
}
}
void Application::Schedule(std::function<void()> callback) {
void Application::Schedule(std::function<void()>&& callback) {
{
std::lock_guard<std::mutex> lock(mutex_);
main_tasks_.push_back(std::move(callback));
@ -960,9 +967,10 @@ void Application::WakeWordInvoke(const std::string& wake_word) {
protocol_->SendWakeWordDetected(wake_word);
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
#else
// Set flag to play popup sound after state changes to listening
// (PlaySound here would be cleared by ResetDecoder in EnableVoiceProcessing)
play_popup_on_listening_ = true;
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
// Play the pop up sound to indicate the wake word is detected
audio_service_.PlaySound(Lang::Sounds::OGG_POPUP);
#endif
} else if (state == kDeviceStateSpeaking) {
Schedule([this]() {

View File

@ -75,7 +75,7 @@ public:
/**
* Schedule a callback to be executed in the main task
*/
void Schedule(std::function<void()> callback);
void Schedule(std::function<void()>&& callback);
/**
* Alert with status, message, emotion and optional sound
@ -139,6 +139,7 @@ private:
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 activation_task_handle_ = nullptr;

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.

View File

@ -0,0 +1,725 @@
#include "blufi.h"
#include <string>
#include <cstring>
#include <cassert>
#include <algorithm>
#include "application.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_mac.h"
#include "esp_bt.h"
#include "wifi_manager.h"
// Bluedroid specific
#ifdef CONFIG_BT_BLUEDROID_ENABLED
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_gap_ble_api.h"
#endif
// NimBLE specific
#ifdef CONFIG_BT_NIMBLE_ENABLED
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "console/console.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 "mbedtls/md5.h"
#include "esp_crc.h"
#include "esp_random.h"
#include "ssid_manager.h"
#include <wifi_station.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() {
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() {
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;
}
uint8_t type = data[0];
switch (type) {
case 0x00: /* DH_PARAM_LEN */
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, &param, &param[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, DH_SELF_PUB_KEY_LEN, myrand,
NULL);
if (ret) {
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) {
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) {
ESP_LOGE(BLUFI_TAG, "mbedtls_md5 failed %d", ret);
btc_blufi_report_error(ESP_BLUFI_CALC_MD5_ERROR);
return;
}
mbedtls_aes_setkey_enc(m_sec->aes, m_sec->psk, PSK_LEN * 8);
*output_data = &m_sec->self_public_key[0];
*output_len = dhm_len;
*need_free = false;
free(m_sec->dh_param);
m_sec->dh_param = NULL;
}
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) return -1;
size_t iv_offset = 0;
uint8_t iv0[16];
memcpy(iv0, m_sec->iv, 16);
iv0[0] = iv8;
return mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0, crypt_data,
crypt_data);
}
int Blufi::_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) {
if (!m_sec) return -1;
size_t iv_offset = 0;
uint8_t iv0[16];
memcpy(iv0, m_sec->iv, 16);
iv0[0] = iv8;
return mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0, crypt_data,
crypt_data);
}
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_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");
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
View 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{};
};

View File

@ -155,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;
}

View File

@ -17,6 +17,9 @@
#include <wifi_station.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";
@ -48,13 +51,13 @@ std::string WifiBoard::GetBoardType() {
void WifiBoard::StartNetwork() {
auto& wifi_manager = WifiManager::GetInstance();
// Initialize WiFi manager
WifiManagerConfig config;
config.ssid_prefix = "Xiaozhi";
config.language = Lang::CODE;
wifi_manager.Initialize(config);
// Set unified event callback - forward to NetworkEvent with SSID data
wifi_manager.SetEventCallback([this, &wifi_manager](WifiEvent event) {
std::string ssid = wifi_manager.GetSsid();
@ -79,7 +82,7 @@ void WifiBoard::StartNetwork() {
break;
}
});
// Try to connect or enter config mode
TryWifiConnect();
}
@ -87,7 +90,7 @@ void WifiBoard::StartNetwork() {
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");
@ -103,18 +106,22 @@ void WifiBoard::TryWifiConnect() {
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::Connected:
// Stop timeout timer
esp_timer_stop(connect_timer_);
in_config_mode_ = false;
ESP_LOGI(TAG, "Connected to WiFi: %s", data.c_str());
break;
case NetworkEvent::Disconnected:
ESP_LOGW(TAG, "WiFi disconnected");
break;
@ -130,9 +137,8 @@ void WifiBoard::OnNetworkEvent(NetworkEvent event, const std::string& data) {
break;
default:
break;
}
// Notify external callback if set
if (network_event_callback_) {
network_event_callback_(event, data);
@ -146,36 +152,41 @@ void WifiBoard::SetNetworkEventCallback(NetworkEventCallback 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;
auto& wifi_manager = WifiManager::GetInstance();
// 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([this, &wifi_manager]() {
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();
@ -190,32 +201,32 @@ void WifiBoard::StartWifiConfigMode() {
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 (state != kDeviceStateStarting) {
ESP_LOGE(TAG, "EnterWifiConfigMode called but device state is not starting or speaking, device state: %d", state);
return;
@ -224,7 +235,7 @@ void WifiBoard::EnterWifiConfigMode() {
// Stop any ongoing connection attempt
esp_timer_stop(connect_timer_);
WifiManager::GetInstance().StopStation();
StartWifiConfigMode();
}
@ -239,18 +250,18 @@ NetworkInterface* WifiBoard::GetNetwork() {
const char* WifiBoard::GetNetworkStateIcon() {
auto& wifi = WifiManager::GetInstance();
if (wifi.IsConfigMode()) {
return FONT_AWESOME_WIFI;
}
if (!wifi.IsConnected()) {
return FONT_AWESOME_WIFI_SLASH;
}
int rssi = wifi.GetRssi();
if (rssi >= -60) {
if (rssi >= -65) {
return FONT_AWESOME_WIFI;
} else if (rssi >= -70) {
} else if (rssi >= -75) {
return FONT_AWESOME_WIFI_FAIR;
}
return FONT_AWESOME_WIFI_WEAK;
@ -260,14 +271,14 @@ std::string WifiBoard::GetBoardJson() {
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"(",)";
}
json += R"("mac":")" + SystemInfo::GetMacAddress() + R"("})";
return json;
}

View File

@ -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"
]
}
]
}

View File

@ -230,12 +230,12 @@ 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]() {

View File

@ -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"
]
}
]
}

View File

@ -16,10 +16,9 @@
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() {
@ -36,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() {
@ -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;

View File

@ -369,7 +369,11 @@ bool EmoteEngine::OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io,
esp_lcd_panel_io_event_data_t* const edata,
void* const user_ctx)
{
return true;
gfx_handle_t handle = static_cast<gfx_handle_t>(user_ctx);
if (handle) {
gfx_emote_flush_ready(handle, true);
}
return false;
}
void EmoteEngine::OnFlush(const gfx_handle_t handle, const int x_start, const int y_start,
@ -379,7 +383,6 @@ void EmoteEngine::OnFlush(const gfx_handle_t handle, const int x_start, const in
if (panel) {
esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
}
gfx_emote_flush_ready(handle, true);
}
// ============================================================================

View File

@ -18,9 +18,9 @@ dependencies:
espressif/esp_io_expander_tca9554: ==2.0.0
espressif/esp_lcd_panel_io_additions: ^1.0.1
78/esp_lcd_nv3023: ~1.0.0
78/esp-wifi-connect: ~3.0.1
78/esp-wifi-connect: ~3.0.2
78/esp-opus-encoder: ~2.4.1
78/esp-ml307: ~3.5.2
78/esp-ml307: ~3.5.3
78/xiaozhi-fonts: ~1.5.5
espressif/led_strip: ~3.0.1
espressif/esp_codec_dev: ~1.5
@ -44,7 +44,7 @@ dependencies:
esp_lvgl_port: ~2.6.0
espressif/esp_io_expander_tca95xx_16bit: ^2.0.0
espressif2022/image_player: ^1.1.1
espressif2022/esp_emote_gfx: ^1.1.2
espressif2022/esp_emote_gfx: ==2.0.0
espressif/adc_mic: ^0.2.1
espressif/esp_mmap_assets: '>=1.2'
espressif/bq27220: ^0.1.1

View File

@ -113,6 +113,48 @@ def _find_board_config(board_type: str) -> Optional[str]:
return config
return None
# Kconfig "select" entries are not automatically applied when we simply append
# sdkconfig lines from config.json, so add the required dependencies here to
# mimic menuconfig behaviour.
_AUTO_SELECT_RULES: dict[str, list[str]] = {
"CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING": [
"CONFIG_BT_ENABLED=y",
"CONFIG_BT_BLUEDROID_ENABLED=y",
"CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y",
"CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n",
"CONFIG_BT_BLE_BLUFI_ENABLE=y",
"CONFIG_MBEDTLS_DHM_C=y",
],
}
def _apply_auto_selects(sdkconfig_append: list[str]) -> list[str]:
"""Apply hardcoded auto-select rules to sdkconfig_append."""
items: list[str] = []
existing_keys: set[str] = set()
def _append_if_missing(entry: str) -> None:
key = entry.split("=", 1)[0]
if key not in existing_keys:
items.append(entry)
existing_keys.add(key)
# Preserve original order while tracking keys
for entry in sdkconfig_append:
_append_if_missing(entry)
# Apply auto-select rules
for key, deps in _AUTO_SELECT_RULES.items():
for entry in sdkconfig_append:
name, _, value = entry.partition("=")
if name == key and value.lower().startswith("y"):
for dep in deps:
_append_if_missing(dep)
break
return items
################################################################################
# Check board_type in CMakeLists
################################################################################
@ -167,6 +209,7 @@ def release(board_type: str, config_filename: str = "config.json", *, filter_nam
board_type_config = _find_board_config(board_type)
sdkconfig_append = [f"{board_type_config}=y"]
sdkconfig_append.extend(build.get("sdkconfig_append", []))
sdkconfig_append = _apply_auto_selects(sdkconfig_append)
print("-" * 80)
print(f"name: {name}")