Compare commits
11 Commits
73ad50c732
...
417f52d759
| Author | SHA1 | Date | |
|---|---|---|---|
| 417f52d759 | |||
| 67bf599149 | |||
| ba27c12494 | |||
| c1d520d700 | |||
| 1847b58935 | |||
| 2be3c2cb1a | |||
| e12e7351d9 | |||
| 20175fa059 | |||
| 8cbbf3f357 | |||
| 79a482a09e | |||
| 07e2a11253 |
@ -158,6 +158,13 @@ elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
|
||||
elseif(CONFIG_BOARD_TYPE_RYMCU_BIGSMART)
|
||||
set(MANUFACTURER "rymcu")
|
||||
set(BOARD_TYPE "bigsmart")
|
||||
set(BOARD_NAME "rymcu-bigsmart")
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_EDA_TV_PRO)
|
||||
set(MANUFACTURER "lceda-course-examples")
|
||||
set(BOARD_TYPE "eda-tv-pro")
|
||||
|
||||
@ -215,6 +215,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_LICHUANG_DEV_C3
|
||||
bool "立创·实战派 ESP32-C3"
|
||||
depends on IDF_TARGET_ESP32C3
|
||||
config BOARD_TYPE_RYMCU_BIGSMART
|
||||
bool "RYMCU BigSmart"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_EDA_TV_PRO
|
||||
bool "EDA课程案例 EDA-TV-Pro"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@ -711,7 +714,7 @@ choice DISPLAY_STYLE
|
||||
config USE_EMOTE_MESSAGE_STYLE
|
||||
bool "Emote animation style"
|
||||
depends on BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_3 \
|
||||
|| BOARD_TYPE_ESP_VOCAT || BOARD_TYPE_LICHUANG_DEV_S3 \
|
||||
|| BOARD_TYPE_ESP_VOCAT || BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_RYMCU_BIGSMART \
|
||||
|| BOARD_TYPE_ESP_SENSAIRSHUTTLE
|
||||
endchoice
|
||||
|
||||
@ -808,7 +811,7 @@ config USE_DEVICE_AEC
|
||||
bool "Enable Device-Side AEC"
|
||||
default n
|
||||
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE \
|
||||
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83\
|
||||
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_RYMCU_BIGSMART || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83\
|
||||
|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_2_06 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3 \
|
||||
|| BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B \
|
||||
|| BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \
|
||||
|
||||
@ -716,6 +716,10 @@ void Application::ContinueOpenAudioChannel(ListeningMode mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch to performance mode before connecting to reduce latency
|
||||
auto& board = Board::GetInstance();
|
||||
board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
|
||||
|
||||
if (!protocol_->IsAudioChannelOpened()) {
|
||||
if (!protocol_->OpenAudioChannel()) {
|
||||
return;
|
||||
@ -825,6 +829,10 @@ void Application::ContinueWakeWordInvoke(const std::string& wake_word) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch to performance mode before connecting to reduce latency
|
||||
auto& board = Board::GetInstance();
|
||||
board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
|
||||
|
||||
if (!protocol_->IsAudioChannelOpened()) {
|
||||
if (!protocol_->OpenAudioChannel()) {
|
||||
audio_service_.EnableWakeWordDetection(true);
|
||||
@ -1063,12 +1071,19 @@ bool Application::CanEnterSleepMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::RegisterMcpBroadcastCallback(std::function<void(const std::string&)> callback) {
|
||||
mcp_broadcast_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void Application::SendMcpMessage(const std::string& payload) {
|
||||
// Always schedule to run in main task for thread safety
|
||||
Schedule([this, payload = std::move(payload)]() {
|
||||
Schedule([this, payload](){
|
||||
if (protocol_) {
|
||||
protocol_->SendMcpMessage(payload);
|
||||
}
|
||||
if (mcp_broadcast_callback_) {
|
||||
mcp_broadcast_callback_(payload);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <mutex>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
#include "protocol.h"
|
||||
#include "ota.h"
|
||||
@ -108,6 +109,7 @@ public:
|
||||
bool UpgradeFirmware(const std::string& url, const std::string& version = "");
|
||||
bool CanEnterSleepMode();
|
||||
void SendMcpMessage(const std::string& payload);
|
||||
void RegisterMcpBroadcastCallback(std::function<void(const std::string&)> callback);
|
||||
void SetAecMode(AecMode mode);
|
||||
AecMode GetAecMode() const { return aec_mode_; }
|
||||
void PlaySound(const std::string_view& sound);
|
||||
@ -136,6 +138,8 @@ private:
|
||||
AudioService audio_service_;
|
||||
std::unique_ptr<Ota> ota_;
|
||||
|
||||
std::function<void(const std::string&)> mcp_broadcast_callback_;
|
||||
|
||||
bool has_server_time_ = false;
|
||||
bool aborted_ = false;
|
||||
bool assets_version_checked_ = false;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "NoAudioCodec"
|
||||
@ -239,10 +240,10 @@ int NoAudioCodec::Write(const int16_t* data, int samples) {
|
||||
|
||||
int NoAudioCodec::Read(int16_t* dest, int samples) {
|
||||
size_t bytes_read;
|
||||
constexpr TickType_t kReadTimeoutTicks = pdMS_TO_TICKS(200);
|
||||
constexpr uint32_t kReadTimeoutMs = 200;
|
||||
|
||||
std::vector<int32_t> bit32_buffer(samples);
|
||||
if (i2s_channel_read(rx_handle_, bit32_buffer.data(), samples * sizeof(int32_t), &bytes_read, kReadTimeoutTicks) != ESP_OK) {
|
||||
if (i2s_channel_read(rx_handle_, bit32_buffer.data(), samples * sizeof(int32_t), &bytes_read, kReadTimeoutMs) != ESP_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -533,13 +533,13 @@ int Blufi::_get_softap_conn_num() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Blufi::start_wifi_scan() {
|
||||
bool Blufi::start_wifi_scan() {
|
||||
ESP_LOGI(BLUFI_TAG, "Starting dedicated WiFi scan");
|
||||
|
||||
// Check if a scan is already in progress
|
||||
// Already running: caller can rely on the in-flight scan and await its done event.
|
||||
if (m_scan_in_progress) {
|
||||
ESP_LOGW(BLUFI_TAG, "Scan already in progress, skipping");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_scan_in_progress = true;
|
||||
@ -555,14 +555,14 @@ void Blufi::start_wifi_scan() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to set WiFi mode to STA: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// Need to restart WiFi for mode change to take effect
|
||||
err = esp_wifi_start();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi after mode switch: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
// Register scan event handler
|
||||
esp_event_handler_instance_t scan_event_instance;
|
||||
@ -575,28 +575,36 @@ void Blufi::start_wifi_scan() {
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} else if (current_mode == WIFI_MODE_STA || current_mode == WIFI_MODE_APSTA) {
|
||||
// Ensure WiFi driver is started (may have been stopped during config mode transition)
|
||||
err = esp_wifi_start();
|
||||
if (err != ESP_OK && err != ESP_ERR_WIFI_STATE) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi before scan: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return false;
|
||||
}
|
||||
} else if (current_mode == WIFI_MODE_STA) {
|
||||
// Start scan
|
||||
err = esp_wifi_scan_start(NULL, false);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(BLUFI_TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(BLUFI_TAG, "Unexpected WiFi mode: %d", current_mode);
|
||||
m_scan_in_progress = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(BLUFI_TAG, "WiFi scan started");
|
||||
return true;
|
||||
}
|
||||
|
||||
void Blufi::_send_wifi_list() {
|
||||
if (m_ap_records.empty()) {
|
||||
ESP_LOGW(BLUFI_TAG, "No AP records available to send");
|
||||
ESP_LOGW(BLUFI_TAG, "No AP records available, sending WiFi scan fail");
|
||||
esp_blufi_send_error_info(ESP_BLUFI_WIFI_SCAN_FAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -631,7 +639,7 @@ void Blufi::_wifi_scan_event_handler(void* arg, esp_event_base_t event_base, int
|
||||
ESP_LOGW(BLUFI_TAG, "No APs found");
|
||||
self->m_ap_records.clear();
|
||||
} else {
|
||||
if (static_cast<Blufi*>(arg)->m_scan_should_save_ssid == true) {
|
||||
if (self->m_scan_should_save_ssid) {
|
||||
self->m_ap_records.resize(ap_num);
|
||||
esp_wifi_scan_get_ap_records(&ap_num, self->m_ap_records.data());
|
||||
|
||||
@ -643,6 +651,11 @@ void Blufi::_wifi_scan_event_handler(void* arg, esp_event_base_t event_base, int
|
||||
}
|
||||
}
|
||||
self->m_scan_in_progress = false;
|
||||
// Dispatch a pending GET_WIFI_LIST response if one is waiting on this scan.
|
||||
if (self->m_send_list_after_scan) {
|
||||
self->m_send_list_after_scan = false;
|
||||
self->_send_wifi_list();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -865,10 +878,29 @@ void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* para
|
||||
break;
|
||||
case ESP_BLUFI_EVENT_GET_WIFI_LIST: {
|
||||
ESP_LOGI(BLUFI_TAG, "BLUFI get wifi list");
|
||||
while (m_scan_in_progress) {
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
// Case 1: a scan is already in flight (init scan or refresh scan started by
|
||||
// the previous _send_wifi_list()). Defer the response to its done handler
|
||||
// instead of blocking the BluFi task.
|
||||
if (m_scan_in_progress) {
|
||||
m_send_list_after_scan = true;
|
||||
break;
|
||||
}
|
||||
// Case 2: cache is populated. Respond immediately; _send_wifi_list() also
|
||||
// kicks off an async refresh scan to keep the cache fresh.
|
||||
if (!m_ap_records.empty()) {
|
||||
_send_wifi_list();
|
||||
break;
|
||||
}
|
||||
// Case 3: no cache (e.g. driver was stopped during a config-mode transition,
|
||||
// init scan never completed). Trigger a real scan and dispatch from the
|
||||
// scan-done handler. If the scan cannot start, return an error frame so the
|
||||
// App exits its wait state instead of timing out.
|
||||
m_scan_should_save_ssid = true;
|
||||
m_send_list_after_scan = true;
|
||||
if (!start_wifi_scan()) {
|
||||
m_send_list_after_scan = false;
|
||||
esp_blufi_send_error_info(ESP_BLUFI_WIFI_SCAN_FAIL);
|
||||
}
|
||||
_send_wifi_list();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@ -25,8 +25,9 @@ public:
|
||||
* This method intelligently handles WiFi scanning based on current WiFi state:
|
||||
* - If WiFi config mode is active, it uses the existing scan results from WifiConfigurationAp
|
||||
* - Otherwise, it performs a dedicated scan without interfering with normal WiFi operations
|
||||
* @return true if a scan was started (or was already in progress); false on failure.
|
||||
*/
|
||||
void start_wifi_scan();
|
||||
bool start_wifi_scan();
|
||||
|
||||
/**
|
||||
* @brief Initializes the Bluetooth controller, host, and Blufi profile.
|
||||
@ -143,5 +144,13 @@ private:
|
||||
// WiFi scan related
|
||||
std::vector<wifi_ap_record_t> m_ap_records;
|
||||
bool m_scan_in_progress = false;
|
||||
// When true, scan results are stored in m_ap_records on scan completion.
|
||||
// Cleared during connect-to-AP so that the connect-time scan does not
|
||||
// overwrite the cache with results gathered for connection purposes.
|
||||
bool m_scan_should_save_ssid = true;
|
||||
// When true, the next scan-done event responds to a pending GET_WIFI_LIST
|
||||
// request from the App. Set by the GET_WIFI_LIST handler when no cache is
|
||||
// available or a scan is already in flight; cleared by the scan-done
|
||||
// handler after dispatching the response.
|
||||
bool m_send_list_after_scan = false;
|
||||
};
|
||||
|
||||
@ -110,6 +110,8 @@ void Nt26Board::StartNetwork() {
|
||||
case UartEthModem::UartEthModemEvent::InFlightMode:
|
||||
ESP_LOGW(TAG, "Modem in flight mode");
|
||||
break;
|
||||
case UartEthModem::UartEthModemEvent::RequestingPdpContext:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -2,4 +2,6 @@
|
||||
|
||||
[product](https://store.freenove.com/products/fnk0104)
|
||||
|
||||
Official github: [freenove-esp32s3-display-2.8-lcd](https://github.com/Freenove/Freenove_ESP32_S3_Display)
|
||||
Official github: [freenove-esp32s3-display-2.8-lcd](https://github.com/Freenove/Freenove_ESP32_S3_Display)
|
||||
|
||||
Likely the same hardware design as [LCD wiki ES3C28P/ES3N28P](https://www.lcdwiki.com/2.8inch_ESP32-S3_Display)
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"",
|
||||
"CONFIG_LANGUAGE_EN_US=y"
|
||||
"CONFIG_LANGUAGE_EN_US=y",
|
||||
"CONFIG_SR_WN_WN9S_HIESP=y",
|
||||
"CONFIG_SR_WN_WN9_HIESP=y"
|
||||
]
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "mcp_server.h"
|
||||
#include "adc_battery_monitor.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
@ -65,6 +66,11 @@ private:
|
||||
LcdDisplay *display_;
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
TouchDriver touch_;
|
||||
AdcBatteryMonitor* adc_battery_monitor_;
|
||||
|
||||
void InitializeBatteryMonitor() {
|
||||
adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_8, 200000, 200000, GPIO_NUM_NC);
|
||||
}
|
||||
|
||||
static void TouchTask(void *arg) {
|
||||
auto *self = static_cast<FreenoveESP32S3Display*>(arg);
|
||||
@ -197,6 +203,7 @@ public:
|
||||
FreenoveESP32S3Display(): boot_button_(BOOT_BUTTON_GPIO)
|
||||
{
|
||||
InitializeI2c();
|
||||
InitializeBatteryMonitor();
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
InitializeTouch();
|
||||
@ -224,6 +231,13 @@ public:
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
|
||||
charging = adc_battery_monitor_->IsCharging();
|
||||
discharging = adc_battery_monitor_->IsDischarging();
|
||||
level = adc_battery_monitor_->GetBatteryLevel();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(FreenoveESP32S3Display);
|
||||
|
||||
@ -8,27 +8,9 @@
|
||||
|
||||
## 基础使用
|
||||
|
||||
* idf version: v6.0-dev
|
||||
* idf version: v5.5.2 or above (recommended: v6.0-dev)
|
||||
|
||||
1. 调整 idf_component.yml
|
||||
|
||||
将
|
||||
```yaml
|
||||
espressif/esp_video:
|
||||
version: ==1.3.1 # for compatibility. update version may need to modify this project code.
|
||||
rules:
|
||||
- if: target not in [esp32]
|
||||
```
|
||||
修改为
|
||||
```yaml
|
||||
espressif/esp_video:
|
||||
version: '==0.7.0'
|
||||
rules:
|
||||
- if: target not in [esp32]
|
||||
espressif/esp_ipa: '==0.1.0'
|
||||
```
|
||||
|
||||
* idf version: v5.5.3
|
||||
* No dependency override needed — the project already specifies the correct `esp_video` and `esp_ipa` versions in `main/idf_component.yml`. Do NOT change the dependency versions unless you are also modifying the source code to match the older API.
|
||||
|
||||
针对 ESP32-P4 Rev <3.0 用户:
|
||||
确保你的 sdkconfig.defaults 包含:
|
||||
@ -37,7 +19,7 @@ CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y
|
||||
|
||||
否则烧写的时候会出现:'bootloader/bootloader.bin' requires chip revision in range [v3.0 - v3.99] (this chip is revision v1.x)
|
||||
|
||||
2. 使用 release.py 编译
|
||||
1. 使用 release.py 编译
|
||||
|
||||
```shell
|
||||
python ./scripts/release.py m5stack-tab5
|
||||
@ -45,7 +27,7 @@ python ./scripts/release.py m5stack-tab5
|
||||
|
||||
如需手动编译,请参考 `m5stack-tab5/config.json` 修改 menuconfig 对应选项。
|
||||
|
||||
3. 编译烧录程序
|
||||
2. 编译烧录程序
|
||||
|
||||
```shell
|
||||
idf.py flash monitor
|
||||
@ -62,5 +44,3 @@ idf.py flash monitor
|
||||
1. listening... 需要等几秒才能获取语音输入???
|
||||
2. 亮度调节不对
|
||||
3. 音量调节不对
|
||||
|
||||
## TODO
|
||||
|
||||
@ -205,3 +205,212 @@ otto 机器人具有丰富的动作能力,包括行走、转向、跳跃、摇
|
||||
|
||||
**说明**: 小智控制机器人动作是创建新的任务在后台控制,动作执行期间仍可接受新的语音指令。可以通过"停止"语音指令立即停下Otto。
|
||||
|
||||
---
|
||||
|
||||
## WebSocket 直连调试接口
|
||||
|
||||
Otto 机器人内置 WebSocket 服务器,可在局域网内直接调试,无需经过云端。
|
||||
|
||||
**连接地址:** `ws://<设备IP>:8080/ws`
|
||||
|
||||
> 协议格式:JSON-RPC 2.0,`id` 字段自行递增即可。
|
||||
|
||||
### 连接方式
|
||||
|
||||
1. 确认 Otto 已连上 WiFi,获取 IP 地址(可通过小程序或串口日志查看)
|
||||
2. 打开任意 WebSocket 调试工具(如 [websocket.org/echo](https://websocket.org/echo) 或浏览器控制台)
|
||||
3. 连接 `ws://192.168.x.x:8080/ws`(注意末尾必须有 `/ws`)
|
||||
4. 发送 JSON 命令,响应会直接返回到同一连接
|
||||
|
||||
---
|
||||
|
||||
### 一、协议初始化(首次连接建议先发)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}},"id":1}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 二、获取工具列表
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 三、Otto 机器人工具命令
|
||||
|
||||
#### 获取舵机微调值
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.get_trims","arguments":{}},"id":3}
|
||||
```
|
||||
|
||||
#### 设置单个舵机微调(永久保存)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.set_trim","arguments":{"servo_type":"left_leg","trim_value":5}},"id":4}
|
||||
```
|
||||
|
||||
`servo_type` 可选值:`left_leg` / `right_leg` / `left_foot` / `right_foot` / `left_hand` / `right_hand`,`trim_value` 范围 `-50` ~ `50`
|
||||
|
||||
#### 行走(前进3步)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"walk","steps":3,"speed":700,"direction":1}},"id":5}
|
||||
```
|
||||
|
||||
#### 后退
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"walk","steps":3,"speed":700,"direction":-1}},"id":6}
|
||||
```
|
||||
|
||||
#### 左转
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"turn","steps":3,"speed":700,"direction":-1}},"id":7}
|
||||
```
|
||||
|
||||
#### 跳跃
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"jump","steps":1,"speed":500}},"id":8}
|
||||
```
|
||||
|
||||
#### 摇摆
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"swing","steps":5,"speed":600,"amount":30}},"id":9}
|
||||
```
|
||||
|
||||
#### 太空步
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"moonwalk","steps":3,"speed":800,"direction":1,"amount":30}},"id":10}
|
||||
```
|
||||
|
||||
#### 坐下
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"sit"}},"id":11}
|
||||
```
|
||||
|
||||
#### 复位
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"home"}},"id":12}
|
||||
```
|
||||
|
||||
#### 展示动作
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"showcase"}},"id":13}
|
||||
```
|
||||
|
||||
#### 举手(需手部舵机)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"hands_up","speed":500,"direction":1}},"id":14}
|
||||
```
|
||||
|
||||
#### 挥手(需手部舵机)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.action","arguments":{"action":"hand_wave","direction":1}},"id":15}
|
||||
```
|
||||
|
||||
#### 立即停止所有动作
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.stop","arguments":{}},"id":16}
|
||||
```
|
||||
|
||||
#### 获取运动状态(返回 `"moving"` 或 `"idle"`)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.get_status","arguments":{}},"id":17}
|
||||
```
|
||||
|
||||
#### 获取 IP 地址
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.get_ip","arguments":{}},"id":18}
|
||||
```
|
||||
|
||||
#### 获取电池电量
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.battery.get_level","arguments":{}},"id":19}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 四、系统通用工具
|
||||
|
||||
#### 获取设备状态(音量/网络/电池等)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.get_device_status","arguments":{}},"id":20}
|
||||
```
|
||||
|
||||
#### 设置音量(0~100)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.audio_speaker.set_volume","arguments":{"volume":70}},"id":21}
|
||||
```
|
||||
|
||||
#### 重启设备
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.reboot","arguments":{}},"id":22}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 五、自定义舵机序列
|
||||
|
||||
#### 普通移动模式(逐步移动各舵机)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.servo_sequences","arguments":{"sequence":"{\"a\":[{\"s\":{\"ll\":110,\"rl\":70},\"v\":800},{\"s\":{\"ll\":90,\"rl\":90},\"v\":800}],\"d\":0}"}},"id":23}
|
||||
```
|
||||
|
||||
#### 振荡器模式(双臂摆动)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.servo_sequences","arguments":{"sequence":"{\"a\":[{\"osc\":{\"a\":{\"lh\":30,\"rh\":30},\"o\":{\"lh\":90,\"rh\":90},\"ph\":{\"rh\":180},\"p\":500,\"c\":5.0}}]}"}},"id":24}
|
||||
```
|
||||
|
||||
#### 振荡器模式(左右摇摆波浪)
|
||||
|
||||
```json
|
||||
{"jsonrpc":"2.0","method":"tools/call","params":{"name":"self.otto.servo_sequences","arguments":{"sequence":"{\"a\":[{\"osc\":{\"a\":{\"ll\":20,\"rl\":20},\"o\":{\"ll\":90,\"rl\":90},\"ph\":{\"rl\":180},\"p\":600,\"c\":5.0}}]}"}},"id":25}
|
||||
```
|
||||
|
||||
**序列舵机键名说明:**
|
||||
|
||||
| 键名 | 舵机 | 说明 |
|
||||
|------|------|------|
|
||||
| `ll` | 左腿 | 0=完全外展,90=中立,180=完全内收 |
|
||||
| `rl` | 右腿 | 0=完全内收,90=中立,180=完全外展 |
|
||||
| `lf` | 左脚 | 0=完全向上,90=水平,180=完全向下 |
|
||||
| `rf` | 右脚 | 0=完全向下,90=水平,180=完全向上 |
|
||||
| `lh` | 左手 | 0=完全向下,90=水平,180=完全向上 |
|
||||
| `rh` | 右手 | 0=完全向上,90=水平,180=完全向下 |
|
||||
|
||||
---
|
||||
|
||||
### 六、动作参数速查
|
||||
|
||||
| 参数 | 说明 | 范围 | 默认 |
|
||||
|------|------|------|------|
|
||||
| `steps` | 动作步数 | 1~100 | 3 |
|
||||
| `speed` | 速度(毫秒,越小越快) | 100~3000 | 700 |
|
||||
| `direction` | 方向(1=前/左,-1=后/右) | -1~1 | 1 |
|
||||
| `amount` | 幅度 | 0~170 | 30 |
|
||||
| `arm_swing` | 手臂摆动幅度 | 0~170 | 50 |
|
||||
| `trim_value` | 舵机微调 | -50~50 | 0 |
|
||||
|
||||
|
||||
@ -230,7 +230,16 @@ private:
|
||||
if (!ws_control_server_->Start(8080)) {
|
||||
delete ws_control_server_;
|
||||
ws_control_server_ = nullptr;
|
||||
return;
|
||||
}
|
||||
// 将 MCP 响应同时广播回连接到 8080 端口的 WebSocket 客户端
|
||||
Application::GetInstance().RegisterMcpBroadcastCallback(
|
||||
[this](const std::string& payload) {
|
||||
if (ws_control_server_) {
|
||||
ws_control_server_->BroadcastMessage(payload);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void StartNetwork() override {
|
||||
|
||||
@ -190,3 +190,61 @@ void WebSocketControlServer::RemoveClient(httpd_req_t *req) {
|
||||
size_t WebSocketControlServer::GetClientCount() const {
|
||||
return clients_.size();
|
||||
}
|
||||
|
||||
struct WsBroadcastJob {
|
||||
httpd_handle_t server;
|
||||
int fd;
|
||||
char* payload;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static void ws_broadcast_send_job(void* arg) {
|
||||
WsBroadcastJob* job = static_cast<WsBroadcastJob*>(arg);
|
||||
|
||||
httpd_ws_frame_t ws_pkt = {};
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
ws_pkt.payload = reinterpret_cast<uint8_t*>(job->payload);
|
||||
ws_pkt.len = job->len;
|
||||
ws_pkt.final = true;
|
||||
|
||||
esp_err_t ret = httpd_ws_send_frame_async(job->server, job->fd, &ws_pkt);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE("WSControl", "BroadcastMessage: send failed fd=%d err=%d", job->fd, ret);
|
||||
}
|
||||
|
||||
free(job->payload);
|
||||
free(job);
|
||||
}
|
||||
|
||||
void WebSocketControlServer::BroadcastMessage(const std::string& message) {
|
||||
if (!server_handle_ || clients_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& [fd, req] : clients_) {
|
||||
WsBroadcastJob* job = static_cast<WsBroadcastJob*>(malloc(sizeof(WsBroadcastJob)));
|
||||
if (!job) {
|
||||
ESP_LOGE(TAG, "BroadcastMessage: failed to allocate job");
|
||||
continue;
|
||||
}
|
||||
|
||||
job->server = server_handle_;
|
||||
job->fd = fd;
|
||||
job->len = message.length();
|
||||
job->payload = static_cast<char*>(malloc(message.length() + 1));
|
||||
if (!job->payload) {
|
||||
ESP_LOGE(TAG, "BroadcastMessage: failed to allocate payload");
|
||||
free(job);
|
||||
continue;
|
||||
}
|
||||
memcpy(job->payload, message.c_str(), message.length());
|
||||
job->payload[message.length()] = '\0';
|
||||
|
||||
esp_err_t ret = httpd_queue_work(server_handle_, ws_broadcast_send_job, job);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "BroadcastMessage: httpd_queue_work failed fd=%d err=%d", fd, ret);
|
||||
free(job->payload);
|
||||
free(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ public:
|
||||
|
||||
size_t GetClientCount() const;
|
||||
|
||||
void BroadcastMessage(const std::string& message);
|
||||
|
||||
private:
|
||||
httpd_handle_t server_handle_;
|
||||
std::map<int, httpd_req_t*> clients_;
|
||||
|
||||
31
main/boards/rymcu/bigsmart/README.md
Normal file
31
main/boards/rymcu/bigsmart/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# RYMCU BigSmart
|
||||
|
||||
该目录为 `RYMCU BigSmart` 开发板适配,并按以下硬件资源完成映射:
|
||||
|
||||
- 主控:ESP32-S3-WROOM-1-N16R8
|
||||
- 显示:ST7789(320x240,SPI)
|
||||
- 触摸:GT911(I2C)
|
||||
- 音频:ES8311 + ES7210(I2S + I2C)
|
||||
- IO扩展:PCA9557(I2C 地址 `0x19`)
|
||||
- 摄像头:GC0308(DVP)
|
||||
|
||||
参考硬件文档:
|
||||
|
||||
- https://github.com/rymcu/BigSmart-Open/blob/main/docs/rymcu-bigsmart-hardware.md
|
||||
|
||||
## 编译
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
在菜单中选择:
|
||||
|
||||
`Xiaozhi Assistant -> Board Type -> RYMCU BigSmart`
|
||||
|
||||
然后执行:
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
69
main/boards/rymcu/bigsmart/config.h
Normal file
69
main/boards/rymcu/bigsmart/config.h
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
|
||||
|
||||
#define AUDIO_CODEC_USE_PCA9557
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
|
||||
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
|
||||
#define AUDIO_CODEC_ES7210_ADDR 0x82
|
||||
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_NC
|
||||
#define RGB_LED_GPIO GPIO_NUM_43
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define CUSTOM_BUTTON_GPIO GPIO_NUM_10
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
|
||||
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_MIRROR_X true
|
||||
#define DISPLAY_MIRROR_Y false
|
||||
#define DISPLAY_SWAP_XY true
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
|
||||
|
||||
#define TOUCH_INT_GPIO GPIO_NUM_NC
|
||||
#define TOUCH_RST_GPIO GPIO_NUM_NC
|
||||
|
||||
/* Camera pins */
|
||||
#define CAMERA_PIN_PWDN GPIO_NUM_NC
|
||||
#define CAMERA_PIN_RESET GPIO_NUM_NC
|
||||
#define CAMERA_PIN_XCLK GPIO_NUM_5
|
||||
#define CAMERA_PIN_SIOD GPIO_NUM_1
|
||||
#define CAMERA_PIN_SIOC GPIO_NUM_2
|
||||
|
||||
#define CAMERA_PIN_D7 GPIO_NUM_9
|
||||
#define CAMERA_PIN_D6 GPIO_NUM_4
|
||||
#define CAMERA_PIN_D5 GPIO_NUM_6
|
||||
#define CAMERA_PIN_D4 GPIO_NUM_15
|
||||
#define CAMERA_PIN_D3 GPIO_NUM_17
|
||||
#define CAMERA_PIN_D2 GPIO_NUM_8
|
||||
#define CAMERA_PIN_D1 GPIO_NUM_18
|
||||
#define CAMERA_PIN_D0 GPIO_NUM_16
|
||||
#define CAMERA_PIN_VSYNC GPIO_NUM_44
|
||||
#define CAMERA_PIN_HREF GPIO_NUM_46
|
||||
#define CAMERA_PIN_PCLK GPIO_NUM_7
|
||||
|
||||
#define XCLK_FREQ_HZ 16000000
|
||||
|
||||
#define BATTERY_CHARGING_PIN GPIO_NUM_3
|
||||
#define BATTERY_ADC_PIN GPIO_NUM_11
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
12
main/boards/rymcu/bigsmart/config.json
Normal file
12
main/boards/rymcu/bigsmart/config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"manufacturer": "rymcu",
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "bigsmart",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_USE_DEVICE_AEC=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
284
main/boards/rymcu/bigsmart/rymcu_bigsmart_board.cc
Normal file
284
main/boards/rymcu/bigsmart/rymcu_bigsmart_board.cc
Normal file
@ -0,0 +1,284 @@
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/box_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "display/emote_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#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 <esp_lcd_touch_gt911.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
#define TAG "RymcuBigsmartBoard"
|
||||
|
||||
class Pca9557 : public I2cDevice {
|
||||
public:
|
||||
Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
WriteReg(0x01, 0x03);
|
||||
WriteReg(0x03, 0xf8);
|
||||
}
|
||||
|
||||
void SetOutputState(uint8_t bit, uint8_t level) {
|
||||
uint8_t data = ReadReg(0x01);
|
||||
data = (data & ~(1 << bit)) | (level << bit);
|
||||
WriteReg(0x01, data);
|
||||
}
|
||||
};
|
||||
|
||||
class CustomAudioCodec : public BoxAudioCodec {
|
||||
private:
|
||||
Pca9557* pca9557_;
|
||||
|
||||
public:
|
||||
CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557)
|
||||
: BoxAudioCodec(i2c_bus,
|
||||
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,
|
||||
GPIO_NUM_NC,
|
||||
AUDIO_CODEC_ES8311_ADDR,
|
||||
AUDIO_CODEC_ES7210_ADDR,
|
||||
AUDIO_INPUT_REFERENCE),
|
||||
pca9557_(pca9557) {
|
||||
}
|
||||
|
||||
virtual void EnableOutput(bool enable) override {
|
||||
BoxAudioCodec::EnableOutput(enable);
|
||||
if (enable) {
|
||||
pca9557_->SetOutputState(1, 1);
|
||||
} else {
|
||||
pca9557_->SetOutputState(1, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class RymcuBigsmartBoard : public WifiBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
Button boot_button_;
|
||||
Display* display_;
|
||||
Pca9557* pca9557_;
|
||||
Esp32Camera* camera_;
|
||||
|
||||
void InitializeI2c() {
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = (i2c_port_t)1,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_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(&i2c_bus_cfg, &i2c_bus_));
|
||||
|
||||
pca9557_ = new Pca9557(i2c_bus_, 0x19);
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = GPIO_NUM_40;
|
||||
buscfg.miso_io_num = GPIO_NUM_NC;
|
||||
buscfg.sclk_io_num = GPIO_NUM_41;
|
||||
buscfg.quadwp_io_num = GPIO_NUM_NC;
|
||||
buscfg.quadhd_io_num = GPIO_NUM_NC;
|
||||
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
#if CONFIG_USE_DEVICE_AEC
|
||||
boot_button_.OnDoubleClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateIdle) {
|
||||
app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void InitializeSt7789Display() {
|
||||
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 = GPIO_NUM_NC;
|
||||
io_config.dc_gpio_num = GPIO_NUM_39;
|
||||
io_config.spi_mode = 2;
|
||||
io_config.pclk_hz = 80 * 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(SPI3_HOST, &io_config, &panel_io));
|
||||
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = GPIO_NUM_NC;
|
||||
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
|
||||
panel_config.bits_per_pixel = 16;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||
|
||||
esp_lcd_panel_reset(panel);
|
||||
pca9557_->SetOutputState(0, 0);
|
||||
|
||||
esp_lcd_panel_init(panel);
|
||||
esp_lcd_panel_invert_color(panel, true);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
esp_lcd_panel_disp_on_off(panel, true);
|
||||
|
||||
#if 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, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
#endif
|
||||
}
|
||||
|
||||
void InitializeTouch() {
|
||||
esp_lcd_touch_handle_t tp;
|
||||
esp_lcd_touch_config_t tp_cfg = {
|
||||
.x_max = DISPLAY_HEIGHT,
|
||||
.y_max = DISPLAY_WIDTH,
|
||||
.rst_gpio_num = TOUCH_RST_GPIO,
|
||||
.int_gpio_num = TOUCH_INT_GPIO,
|
||||
.levels = {
|
||||
.reset = 0,
|
||||
.interrupt = 0,
|
||||
},
|
||||
.flags = {
|
||||
.swap_xy = 1,
|
||||
.mirror_x = 1,
|
||||
.mirror_y = 0,
|
||||
},
|
||||
};
|
||||
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
|
||||
esp_lcd_panel_io_i2c_config_t tp_io_config = {
|
||||
.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
|
||||
.control_phase_bytes = 1,
|
||||
.dc_bit_offset = 0,
|
||||
.lcd_cmd_bits = 16,
|
||||
.flags = {
|
||||
.disable_control_phase = 1,
|
||||
}
|
||||
};
|
||||
tp_io_config.scl_speed_hz = 400000;
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
|
||||
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
|
||||
assert(tp);
|
||||
|
||||
const lvgl_port_touch_cfg_t touch_cfg = {
|
||||
.disp = lv_display_get_default(),
|
||||
.handle = tp,
|
||||
};
|
||||
|
||||
if (touch_cfg.disp) {
|
||||
lvgl_port_add_touch(&touch_cfg);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Touch display is not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeCamera() {
|
||||
pca9557_->SetOutputState(2, 0);
|
||||
|
||||
camera_config_t config = {};
|
||||
config.ledc_channel = LEDC_CHANNEL_2;
|
||||
config.ledc_timer = LEDC_TIMER_2;
|
||||
config.pin_d0 = CAMERA_PIN_D0;
|
||||
config.pin_d1 = CAMERA_PIN_D1;
|
||||
config.pin_d2 = CAMERA_PIN_D2;
|
||||
config.pin_d3 = CAMERA_PIN_D3;
|
||||
config.pin_d4 = CAMERA_PIN_D4;
|
||||
config.pin_d5 = CAMERA_PIN_D5;
|
||||
config.pin_d6 = CAMERA_PIN_D6;
|
||||
config.pin_d7 = CAMERA_PIN_D7;
|
||||
config.pin_xclk = CAMERA_PIN_XCLK;
|
||||
config.pin_pclk = CAMERA_PIN_PCLK;
|
||||
config.pin_vsync = CAMERA_PIN_VSYNC;
|
||||
config.pin_href = CAMERA_PIN_HREF;
|
||||
config.pin_sccb_sda = -1;
|
||||
config.pin_sccb_scl = CAMERA_PIN_SIOC;
|
||||
config.sccb_i2c_port = 1;
|
||||
config.pin_pwdn = CAMERA_PIN_PWDN;
|
||||
config.pin_reset = CAMERA_PIN_RESET;
|
||||
config.xclk_freq_hz = XCLK_FREQ_HZ;
|
||||
config.pixel_format = PIXFORMAT_RGB565;
|
||||
config.frame_size = FRAMESIZE_QVGA;
|
||||
config.jpeg_quality = 12;
|
||||
config.fb_count = 1;
|
||||
config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
|
||||
camera_ = new Esp32Camera(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:
|
||||
RymcuBigsmartBoard() : boot_button_(BOOT_BUTTON_GPIO) {
|
||||
InitializeI2c();
|
||||
InitializeSpi();
|
||||
InitializeSt7789Display();
|
||||
InitializeTouch();
|
||||
InitializeButtons();
|
||||
InitializeCamera();
|
||||
InitializeTools();
|
||||
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static CustomAudioCodec audio_codec(i2c_bus_, pca9557_);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display* GetDisplay() override {
|
||||
return display_;
|
||||
}
|
||||
|
||||
virtual Backlight* GetBacklight() override {
|
||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||
return &backlight;
|
||||
}
|
||||
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(RymcuBigsmartBoard);
|
||||
@ -20,12 +20,12 @@ dependencies:
|
||||
waveshare/custom_io_expander_ch32v003: ^1.0.0
|
||||
espressif/esp_lcd_panel_io_additions: ^1.0.1
|
||||
78/esp_lcd_nv3023: ~1.0.0
|
||||
78/esp-wifi-connect: ~3.1.2
|
||||
78/esp-wifi-connect: ~3.1.3
|
||||
espressif/esp_audio_effects: ~1.2.1
|
||||
espressif/esp_audio_codec: ~2.4.1
|
||||
78/esp-ml307: ~3.6.5
|
||||
78/uart-eth-modem:
|
||||
version: ~0.3.4
|
||||
version: ~0.4.0
|
||||
rules:
|
||||
- if: target not in [esp32]
|
||||
78/xiaozhi-fonts: ~1.6.0
|
||||
|
||||
@ -284,10 +284,8 @@ void McpServer::AddUserOnlyTools() {
|
||||
}
|
||||
#endif // HAVE_LVGL
|
||||
|
||||
// Assets download url
|
||||
auto& assets = Assets::GetInstance();
|
||||
if (assets.partition_valid()) {
|
||||
AddUserOnlyTool("self.assets.set_download_url", "Set the download url for the assets",
|
||||
// Assets download url (always registered — Settings storage works regardless of partition layout)
|
||||
AddUserOnlyTool("self.assets.set_download_url", "Set the download url for the assets",
|
||||
PropertyList({
|
||||
Property("url", kPropertyTypeString)
|
||||
}),
|
||||
@ -297,7 +295,6 @@ void McpServer::AddUserOnlyTools() {
|
||||
settings.SetString("download_url", url);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void McpServer::AddTool(McpTool* tool) {
|
||||
|
||||
Reference in New Issue
Block a user