v2.2.0: Add bread-compact-nt26 board (#1663)

* Refactor application error handling and improve network task logic

- Updated error handling for modem initialization failure in Application::Initialize().
- Added new error message for modem initialization in English and Chinese language files.
- Simplified lambda captures in NetworkTask to avoid unnecessary references.
- Set main task priority in Application::Run() for better performance.

* Add support for Bread Compact NT26 board

- Introduced new board configuration for Bread Compact NT26 in CMakeLists.txt and Kconfig.
- Added board-specific implementation in compact_nt26_board.cc and nt26_board.cc.
- Created configuration files for NT26, including config.h and config.json.
- Updated dependencies in idf_component.yml to include uart-eth-modem.
- Translated error messages in config.h for OLED display type selection to English.
- Enhanced display and button initialization logic for NT26 board.

* Update project version and improve build configuration

- Updated project version from 2.1.0 to 2.2.0 in CMakeLists.txt.
- Enabled minimal build configuration to include only essential components.
- Updated README files to replace QQ group links with Discord links for community engagement.

* Update Bread Compact NT26 board configuration name in config.json

* fix compile errors

* Update uart-eth-modem dependency format in idf_component.yml

* fix esp32 compiling errors

* Update CMakeLists.txt to change component dependency from REQUIRES to PRIV_REQUIRES for esp_pm, esp_psram, and esp_driver_gpio

* Refactor CMakeLists.txt to explicitly list board common source files and update include directories for better clarity and organization.

* Add esp_driver_ppa as a dependency in CMakeLists.txt
This commit is contained in:
Xiaoxia
2026-01-19 21:46:21 +08:00
committed by GitHub
parent ed51705240
commit 89674f8838
19 changed files with 654 additions and 27 deletions

View File

@@ -1,14 +1,13 @@
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "2.1.0")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(xiaozhi)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
set(PROJECT_VER "2.2.0")
project(xiaozhi)

View File

@@ -159,7 +159,7 @@ This is an open-source ESP32 project, released under the MIT license, allowing a
We hope this project helps everyone understand AI hardware development and apply rapidly evolving large language models to real hardware devices.
If you have any ideas or suggestions, please feel free to raise Issues or join the QQ group: 1011329060
If you have any ideas or suggestions, please feel free to raise Issues or join our [Discord](https://discord.gg/x3S4jgXHk3) or QQ group: 994694848
## Star History

View File

@@ -155,7 +155,7 @@ Feishuドキュメントチュートリアルをご覧ください
このプロジェクトを通じて、AIハードウェア開発を理解し、急速に進化する大規模言語モデルを実際のハードウェアデバイスに応用できるようになることを目指しています。
ご意見やご提案があれば、いつでもIssueを提出するか、QQグループ1011329060 にご参加ください。
ご意見やご提案があれば、いつでもIssueを提出するか、[Discord](https://discord.gg/x3S4jgXHk3) または QQグループ1011329060 にご参加ください。
## スター履歴

View File

@@ -155,7 +155,7 @@ v1 的稳定版本为 1.9.2,可以通过 `git checkout v1` 来切换到 v1 版
我们希望通过这个项目,能够帮助大家了解 AI 硬件开发,将当下飞速发展的大语言模型应用到实际的硬件设备中。
如果你有任何想法或建议,请随时提出 Issues 或加入 QQ 群1011329060
如果你有任何想法或建议,请随时提出 Issues 或加入 [Discord](https://discord.gg/x3S4jgXHk3) 或 QQ 群1011329060
## Star History

View File

@@ -41,10 +41,29 @@ set(SOURCES "audio/audio_codec.cc"
set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "protocols")
# Add board common files
file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc)
list(APPEND SOURCES ${BOARD_COMMON_SOURCES})
list(APPEND SOURCES
"boards/common/board.cc"
"boards/common/wifi_board.cc"
"boards/common/ml307_board.cc"
"boards/common/nt26_board.cc"
"boards/common/dual_network_board.cc"
"boards/common/adc_battery_monitor.cc"
"boards/common/afsk_demod.cc"
"boards/common/axp2101.cc"
"boards/common/backlight.cc"
"boards/common/button.cc"
"boards/common/esp32_camera.cc"
"boards/common/i2c_device.cc"
"boards/common/knob.cc"
"boards/common/power_save_timer.cc"
"boards/common/press_to_talk_mcp_tool.cc"
"boards/common/sleep_timer.cc"
"boards/common/sy6970.cc"
"boards/common/system_reset.cc"
)
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common)
idf_build_get_property(build_components BUILD_COMPONENTS)
# Function to find component dynamically by pattern
function(find_component_by_pattern PATTERN COMPONENT_VAR PATH_VAR)
@@ -74,6 +93,10 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
set(BOARD_TYPE "bread-compact-ml307")
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_NT26)
set(BOARD_TYPE "bread-compact-nt26")
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32)
set(BOARD_TYPE "bread-compact-esp32")
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
@@ -732,9 +755,10 @@ if(CONFIG_IDF_TARGET_ESP32)
"audio/codecs/es8388_audio_codec.cc"
"audio/codecs/es8389_audio_codec.cc"
"led/gpio_led.cc"
"${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_camera.cc"
"display/lvgl_display/jpg/image_to_jpeg.cpp"
"display/lvgl_display/jpg/jpeg_to_image.c"
"boards/common/esp32_camera.cc"
"boards/common/nt26_board.cc"
)
endif()
@@ -742,6 +766,21 @@ idf_component_register(SRCS ${SOURCES}
EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS}
INCLUDE_DIRS ${INCLUDE_DIRS}
WHOLE_ARCHIVE
PRIV_REQUIRES
esp_pm
esp_psram
esp_driver_gpio
esp_driver_uart
esp_driver_spi
esp_driver_i2c
esp_driver_i2s
esp_driver_jpeg
esp_driver_ppa
esp_app_format
app_update
spi_flash
console
efuse
)
# Use target_compile_definitions to define BOARD_TYPE, BOARD_NAME

View File

@@ -134,6 +134,9 @@ choice BOARD_TYPE
config BOARD_TYPE_BREAD_COMPACT_ML307
bool "Bread Compact ML307/EC801E (面包板 4G)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_BREAD_COMPACT_NT26
bool "Bread Compact NT26 (面包板 4G)"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_BREAD_COMPACT_ESP32
bool "Bread Compact ESP32 DevKit (面包板)"
depends on IDF_TARGET_ESP32
@@ -497,7 +500,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 || BOARD_TYPE_HU_087
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_NT26 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
prompt "OLED Type"
default OLED_SSD1306_128X32
help

View File

@@ -147,8 +147,7 @@ void Application::Initialize() {
Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
break;
case NetworkEvent::ModemErrorInitFailed:
display->SetStatus(Lang::Strings::DETECTING_MODULE);
display->SetChatMessage("system", Lang::Strings::DETECTING_MODULE);
Alert(Lang::Strings::ERROR, Lang::Strings::MODEM_INIT_ERROR, "triangle_exclamation", Lang::Sounds::OGG_EXCLAMATION);
break;
case NetworkEvent::ModemErrorTimeout:
display->SetStatus(Lang::Strings::REGISTERING_NETWORK);
@@ -164,6 +163,9 @@ void Application::Initialize() {
}
void Application::Run() {
// Set the priority of the main task to 10
vTaskPrioritySet(nullptr, 10);
const EventBits_t ALL_EVENTS =
MAIN_EVENT_SCHEDULE |
MAIN_EVENT_SEND_AUDIO |
@@ -538,7 +540,7 @@ void Application::InitializeProtocol() {
auto text = cJSON_GetObjectItem(root, "text");
if (cJSON_IsString(text)) {
ESP_LOGI(TAG, "<< %s", text->valuestring);
Schedule([this, display, message = std::string(text->valuestring)]() {
Schedule([display, message = std::string(text->valuestring)]() {
display->SetChatMessage("assistant", message.c_str());
});
}
@@ -547,14 +549,14 @@ void Application::InitializeProtocol() {
auto text = cJSON_GetObjectItem(root, "text");
if (cJSON_IsString(text)) {
ESP_LOGI(TAG, ">> %s", text->valuestring);
Schedule([this, display, message = std::string(text->valuestring)]() {
Schedule([display, message = std::string(text->valuestring)]() {
display->SetChatMessage("user", message.c_str());
});
}
} else if (strcmp(type->valuestring, "llm") == 0) {
auto emotion = cJSON_GetObjectItem(root, "emotion");
if (cJSON_IsString(emotion)) {
Schedule([this, display, emotion_str = std::string(emotion->valuestring)]() {
Schedule([display, emotion_str = std::string(emotion->valuestring)]() {
display->SetEmotion(emotion_str.c_str());
});
}

View File

@@ -11,6 +11,7 @@
"INITIALIZING": "Initializing...",
"PIN_ERROR": "Please insert SIM card",
"REG_ERROR": "Unable to access network, please check SIM card status",
"MODEM_INIT_ERROR": "Modem initialization failed",
"DETECTING_MODULE": "Detecting module...",
"REGISTERING_NETWORK": "Waiting for network...",
"CHECKING_NEW_VERSION": "Checking for new version...",

View File

@@ -11,6 +11,7 @@
"INITIALIZING": "正在初始化...",
"PIN_ERROR": "请插入 SIM 卡",
"REG_ERROR": "无法接入网络,请检查流量卡状态",
"MODEM_INIT_ERROR": "模组初始化失败",
"DETECTING_MODULE": "检测模组...",
"REGISTERING_NETWORK": "等待网络...",
"CHECKING_NEW_VERSION": "检查新版本...",

View File

@@ -45,7 +45,7 @@
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "未选择 OLED 屏幕类型"
#error "OLED display type is not selected"
#endif
#define DISPLAY_MIRROR_X true

View File

@@ -42,7 +42,7 @@
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "未选择 OLED 屏幕类型"
#error "OLED display type is not selected"
#endif
#define DISPLAY_MIRROR_X true

View File

@@ -0,0 +1,181 @@
#include "board.h"
#include "nt26_board.h"
#include "codecs/no_audio_codec.h"
#include "display/oled_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "lamp_controller.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#define TAG "CompactNt26Board"
class CompactNt26Board : public Nt26Board {
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 boot_button_;
Button touch_button_;
Button volume_up_button_;
Button volume_down_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 InitializeButtons() {
boot_button_.OnClick([]() {
Application::GetInstance().ToggleChatState();
});
touch_button_.OnPressDown([]() {
Application::GetInstance().StartListening();
});
touch_button_.OnPressUp([]() {
Application::GetInstance().StopListening();
});
volume_up_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeTools() {
static LampController lamp(LAMP_GPIO);
}
public:
CompactNt26Board() :
Nt26Board(NT26_TX_PIN, NT26_RX_PIN, NT26_DTR_PIN, NT26_RI_PIN),
boot_button_(BOOT_BUTTON_GPIO),
touch_button_(TOUCH_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
InitializeDisplayI2c();
InitializeSsd1306Display();
InitializeButtons();
InitializeTools();
}
virtual void StartNetwork() override {
GetDisplay()->SetStatus(Lang::Strings::DETECTING_MODULE);
Nt26Board::StartNetwork();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
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, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(CompactNt26Board);

View File

@@ -0,0 +1,61 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#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
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_47
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
#define DISPLAY_SDA_PIN GPIO_NUM_41
#define DISPLAY_SCL_PIN GPIO_NUM_42
#define DISPLAY_WIDTH 128
#if CONFIG_OLED_SSD1306_128X32
#define DISPLAY_HEIGHT 32
#elif CONFIG_OLED_SSD1306_128X64
#define DISPLAY_HEIGHT 64
#else
#error "OLED display type is not selected"
#endif
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define NT26_DTR_PIN GPIO_NUM_9
#define NT26_RI_PIN GPIO_NUM_10
#define NT26_RX_PIN GPIO_NUM_11
#define NT26_TX_PIN GPIO_NUM_12
// A MCP Test: Control a lamp
#define LAMP_GPIO GPIO_NUM_18
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,11 @@
{
"target": "esp32s3",
"builds": [
{
"name": "bread-compact-nt26",
"sdkconfig_append": [
"CONFIG_OLED_SSD1306_128X32=y"
]
}
]
}

View File

@@ -46,7 +46,7 @@
#define DISPLAY_HEIGHT 64
#define SH1106
#else
#error "未选择 OLED 屏幕类型"
#error "OLED display type is not selected"
#endif
#define DISPLAY_MIRROR_X true

View File

@@ -1,8 +1,7 @@
#include "ml307_board.h"
#include "application.h"
#include "audio_codec.h"
#include "display.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_timer.h>
@@ -66,8 +65,6 @@ void Ml307Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
}
void Ml307Board::NetworkTask() {
auto& application = Application::GetInstance();
// Notify modem detection started
OnNetworkEvent(NetworkEvent::ModemDetecting);
@@ -92,7 +89,7 @@ void Ml307Board::NetworkTask() {
// 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) {
modem_->OnNetworkStateChanged([this](bool network_ready) {
if (network_ready) {
OnNetworkEvent(NetworkEvent::Connected);
} else {

View File

@@ -0,0 +1,266 @@
#include "nt26_board.h"
#include "display.h"
#include "application.h"
#include "audio_codec.h"
#include <esp_log.h>
#include <font_awesome.h>
#include <cJSON.h>
#define TAG "Nt26Board"
Nt26Board::Nt26Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, gpio_num_t ri_pin, gpio_num_t reset_pin)
: tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin), ri_pin_(ri_pin), reset_pin_(reset_pin) {
gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
esp_event_loop_create_default();
esp_netif_init();
// Create PM lock handle
esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "nt26_cpu", &pm_lock_cpu_max_);
// Create network ready timeout timer
esp_timer_create_args_t timer_args = {
.callback = OnNetworkReadyTimeout,
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "nt26_net_timer",
.skip_unhandled_events = true
};
esp_timer_create(&timer_args, &network_ready_timer_);
}
Nt26Board::~Nt26Board() {
if (current_power_level_ != PowerSaveLevel::LOW_POWER) {
SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
}
if (network_ready_timer_) {
esp_timer_stop(network_ready_timer_);
esp_timer_delete(network_ready_timer_);
}
if (modem_) {
modem_->Stop();
}
if (pm_lock_cpu_max_) {
esp_pm_lock_delete(pm_lock_cpu_max_);
}
}
std::string Nt26Board::GetBoardType() {
return "nt26";
}
void Nt26Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
if (network_event_callback_) {
network_event_callback_(event, data);
}
}
void Nt26Board::OnNetworkReadyTimeout(void* arg) {
auto* self = static_cast<Nt26Board*>(arg);
ESP_LOGW(TAG, "Network ready timeout");
self->OnNetworkEvent(NetworkEvent::ModemErrorTimeout, "网络连接超时");
}
void Nt26Board::StartNetwork() {
OnNetworkEvent(NetworkEvent::ModemDetecting);
UartEthModem::Config config = {
.uart_num = UART_NUM_1,
.baud_rate = 3000000,
.tx_pin = tx_pin_,
.rx_pin = rx_pin_,
.mrdy_pin = dtr_pin_,
.srdy_pin = ri_pin_
};
modem_ = std::make_unique<UartEthModem>(config);
modem_->SetDebug(false);
modem_->SetNetworkEventCallback([this](UartEthModem::UartEthModemEvent event) {
switch (event) {
case UartEthModem::UartEthModemEvent::Connected:
esp_timer_stop(network_ready_timer_);
OnNetworkEvent(NetworkEvent::Connected);
break;
case UartEthModem::UartEthModemEvent::Disconnected:
OnNetworkEvent(NetworkEvent::Disconnected);
break;
case UartEthModem::UartEthModemEvent::ErrorNoSim:
esp_timer_stop(network_ready_timer_);
ScheduleAsyncStop();
OnNetworkEvent(NetworkEvent::ModemErrorNoSim);
break;
case UartEthModem::UartEthModemEvent::ErrorRegistrationDenied:
esp_timer_stop(network_ready_timer_);
ScheduleAsyncStop();
OnNetworkEvent(NetworkEvent::ModemErrorRegDenied);
break;
case UartEthModem::UartEthModemEvent::Connecting:
OnNetworkEvent(NetworkEvent::Connecting);
break;
case UartEthModem::UartEthModemEvent::ErrorInitFailed:
case UartEthModem::UartEthModemEvent::ErrorNoCarrier:
esp_timer_stop(network_ready_timer_);
ScheduleAsyncStop();
OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
break;
}
});
if (modem_->Start() != ESP_OK) {
OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
return;
}
esp_timer_start_once(network_ready_timer_, 30000 * 1000ULL);
OnNetworkEvent(NetworkEvent::Connecting);
}
void Nt26Board::ScheduleAsyncStop() {
Application::GetInstance().Schedule([this]() {
if (modem_) {
modem_->Stop();
}
});
}
void Nt26Board::SetNetworkEventCallback(NetworkEventCallback callback) {
network_event_callback_ = std::move(callback);
}
NetworkInterface* Nt26Board::GetNetwork() {
static EspNetwork network;
return &network;
}
const char* Nt26Board::GetNetworkStateIcon() {
if (modem_ == nullptr || !modem_->IsInitialized()) {
return FONT_AWESOME_SIGNAL_OFF;
}
int csq = modem_->GetSignalStrength();
if (csq == 99 || csq == -1) {
return FONT_AWESOME_SIGNAL_OFF;
} else if (csq >= 0 && csq <= 9) {
return FONT_AWESOME_SIGNAL_WEAK;
} else if (csq >= 10 && csq <= 14) {
return FONT_AWESOME_SIGNAL_FAIR;
} else if (csq >= 15 && csq <= 19) {
return FONT_AWESOME_SIGNAL_GOOD;
} else if (csq >= 20 && csq <= 31) {
return FONT_AWESOME_SIGNAL_STRONG;
}
return FONT_AWESOME_SIGNAL_OFF;
}
void Nt26Board::SetPowerSaveLevel(PowerSaveLevel level) {
if (level == current_power_level_) return;
if (current_power_level_ == PowerSaveLevel::BALANCED ||
current_power_level_ == PowerSaveLevel::PERFORMANCE) {
if (pm_lock_cpu_max_) {
esp_pm_lock_release(pm_lock_cpu_max_);
}
}
if (level == PowerSaveLevel::BALANCED || level == PowerSaveLevel::PERFORMANCE) {
if (pm_lock_cpu_max_) {
esp_pm_lock_acquire(pm_lock_cpu_max_);
}
}
current_power_level_ = level;
}
std::string Nt26Board::GetBoardJson() {
// Set the board type for OTA
std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");
board_json += "\"name\":\"" BOARD_NAME "\",";
if (modem_) {
board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\",";
board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\",";
board_json += "\"csq\":\"" + std::to_string(modem_->GetSignalStrength()) + "\",";
board_json += "\"imei\":\"" + modem_->GetImei() + "\",";
board_json += "\"iccid\":\"" + modem_->GetIccid() + "\",";
board_json += "\"cereg\":" + GetRegistrationState().ToString() + "}";
} else {
board_json += "\"status\":\"offline\"}";
}
return board_json;
}
Nt26CeregState Nt26Board::GetRegistrationState() {
Nt26CeregState state;
if (modem_) {
auto cell_info = modem_->GetCellInfo();
state.stat = cell_info.stat;
state.tac = cell_info.tac;
state.ci = cell_info.ci;
state.AcT = cell_info.act;
}
return state;
}
std::string Nt26Board::GetDeviceStatusJson() {
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());
}
cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
// Screen
auto backlight = board.GetBacklight();
auto screen = cJSON_CreateObject();
if (backlight) {
cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
}
auto display = board.GetDisplay();
if (display && display->height() > 64) {
auto theme = display->GetTheme();
if (theme != nullptr) {
cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
}
}
cJSON_AddItemToObject(root, "screen", screen);
// Battery
int battery_level = 0;
bool charging = false, discharging = false;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
auto battery = cJSON_CreateObject();
cJSON_AddNumberToObject(battery, "level", battery_level);
cJSON_AddBoolToObject(battery, "charging", charging);
cJSON_AddItemToObject(root, "battery", battery);
}
// Network
auto network = cJSON_CreateObject();
cJSON_AddStringToObject(network, "type", "cellular");
if (modem_) {
cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str());
int csq = modem_->GetSignalStrength();
if (csq == 99 || csq == -1) {
cJSON_AddStringToObject(network, "signal", "unknown");
} else if (csq >= 0 && csq <= 14) {
cJSON_AddStringToObject(network, "signal", "weak");
} else if (csq >= 15 && csq <= 24) {
cJSON_AddStringToObject(network, "signal", "medium");
} else if (csq >= 25 && csq <= 31) {
cJSON_AddStringToObject(network, "signal", "strong");
}
}
cJSON_AddItemToObject(root, "network", network);
auto json_str = cJSON_PrintUnformatted(root);
std::string json(json_str);
cJSON_free(json_str);
cJSON_Delete(root);
return json;
}

View File

@@ -0,0 +1,62 @@
#ifndef NT26_BOARD_H
#define NT26_BOARD_H
#include <memory>
#include <uart_eth_modem.h>
#include <esp_network.h>
#include <esp_pm.h>
#include <esp_timer.h>
#include "board.h"
struct Nt26CeregState {
int stat = 0;
std::string tac;
std::string ci;
int AcT = -1;
std::string ToString() const {
std::string json = "{";
json += "\"stat\":" + std::to_string(stat);
if (!tac.empty()) json += ",\"tac\":\"" + tac + "\"";
if (!ci.empty()) json += ",\"ci\":\"" + ci + "\"";
if (AcT >= 0) json += ",\"AcT\":" + std::to_string(AcT);
json += "}";
return json;
}
};
class Nt26Board : public Board {
protected:
std::unique_ptr<UartEthModem> modem_;
gpio_num_t tx_pin_;
gpio_num_t rx_pin_;
gpio_num_t dtr_pin_; // mrdy_pin
gpio_num_t ri_pin_; // srdy_pin
gpio_num_t reset_pin_;
NetworkEventCallback network_event_callback_;
esp_pm_lock_handle_t pm_lock_cpu_max_ = nullptr;
PowerSaveLevel current_power_level_ = PowerSaveLevel::LOW_POWER;
esp_timer_handle_t network_ready_timer_ = nullptr;
virtual std::string GetBoardJson() override;
void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
static void OnNetworkReadyTimeout(void* arg);
void ScheduleAsyncStop();
public:
Nt26Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, gpio_num_t ri_pin, gpio_num_t reset_pin = GPIO_NUM_NC);
virtual ~Nt26Board();
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 SetPowerSaveLevel(PowerSaveLevel level) override;
virtual AudioCodec* GetAudioCodec() override { return nullptr; }
virtual std::string GetDeviceStatusJson() override;
Nt26CeregState GetRegistrationState();
};
#endif // NT26_BOARD_H

View File

@@ -22,6 +22,10 @@ dependencies:
espressif/esp_audio_effects: ~1.2.0
espressif/esp_audio_codec: ~2.4.0
78/esp-ml307: ~3.5.3
78/uart-eth-modem:
version: ~0.1.3
rules:
- if: target not in [esp32]
78/xiaozhi-fonts: ~1.5.5
espressif/led_strip: ~3.0.1
espressif/esp_codec_dev: ~1.5
@@ -107,5 +111,5 @@ dependencies:
## Required IDF version
idf:
version: '>=5.4.0'
version: '>=5.5.2'
espressif/esp_lcd_touch_st7123: ^1.0.0