* Enhance Otto Robot camera support by adding configuration for OV3660. Updated config.h to define camera types and GPIO settings, modified config.json to include new camera options, and refactored otto_robot.cc for improved camera detection and initialization logic.
* fix: 移除 OttoEmojiDisplay 构造函数中的 SetTheme 调用以修复 LoadProhibited 崩溃
Made-with: Cursor
* refactor: improve audio service error handling and codec timeout management
- Updated AudioService to prevent input task termination on read timeout, introducing a delay instead.
- Enhanced NoAudioCodec to implement a read timeout for I2S channel reads.
- Adjusted WebSocketControlServer to set a control port for improved socket management.
- Added manufacturer information to the config.json for waveshare ESP32-Touch-LCD-3.5.
* fix(otto): WebSocket direct clients not receiving MCP responses
When a browser connects directly to the WebSocket control server (port
8080) and sends a JSON-RPC request, the MCP response was routed through
Application::SendMcpMessage -> protocol_->SendMcpMessage, which sends it
to the cloud protocol channel. As a result, the direct WebSocket client
never received the response, while the WeChat mini-program could because
it communicates via the cloud.
Fix:
- Add BroadcastMessage() to WebSocketControlServer, using
httpd_queue_work + httpd_ws_send_frame_async to asynchronously
send responses back to all connected clients on port 8080
- Add RegisterMcpBroadcastCallback() to Application, allowing an
additional MCP send callback to be registered; SendMcpMessage()
now invokes it alongside the cloud protocol
- Register the broadcast callback in OttoRobot after the WebSocket
server starts successfully
Also add WebSocket direct-connect API documentation to README.md
with complete JSON-RPC 2.0 command examples.
* fix(otto-robot): migrate camera backend and set safe dark default theme
- Migrate `otto-robot` camera backend from `EspVideo` to `Esp32Camera` to improve capture stability after reboot/power cycle.
- Keep runtime sensor detection for both OV2640 and OV3660, and rename PID macros with `OTTO_` prefix to avoid symbol conflicts.
- Configure camera output as `RGB565 + FRAMESIZE_240X240` to match the 240x240 display.
- Rotate OV2640 output by 180 degrees (`VFlip + HMirror`) for correct orientation.
- Simplify `otto-robot` camera sdkconfig options by keeping only:
- `CONFIG_CAMERA_OV2640=y`
- `CONFIG_CAMERA_OV3660=y`
- Move Otto default dark theme setup into `OttoEmojiDisplay::SetupUI()` (after base UI init) to avoid boot-time LVGL null-object crash caused by calling `SetTheme()` too early.
* feat(motion): smooth action ending and adaptive homing on otto/electron-bot
Improve servo motion transitions to reduce abrupt returns to home pose.
Replace linear interpolation with ease-out movement, make homing duration adaptive to angle delta, and skip intermediate homing when queued actions are pending to keep multi-action sequences fluid.
* fix(electron-bot): show chat subtitle on display
ElectronEmojiDisplay overrode SetupChatLabel() to delete the parent's
chat_message_label_ (which lives inside bottom_bar_) and recreate it on the
bottom-layer container_ without alignment. As a result the real subtitle label
was hidden behind emoji_box_/top_bar_, while SetChatMessage() only toggled the
now-empty bottom_bar_, so chat subtitles were never visible.
Reuse the parent label created in bottom_bar_ (matching otto-robot) and only
switch to the dark theme in SetupUI(), removing the custom SetupChatLabel().
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(electron-bot): restore status icons on standby and clean up display
- SetStatus() did not re-show network_label_/battery_label_ when returning
to STANDBY, so the WiFi and battery icons stayed hidden after listening or
speaking. Restore them on standby to match otto-robot.
- Remove the unused InitializeElectronEmojis() method and its declaration.
- Drop unused includes (vector, assets.h, emoji_collection.h, lvgl_image.h)
and add the missing trailing newline in the header.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(esp32-s3-touch-amoled-1.8): keep wake word always-on while charging
The previous logic in GetBatteryLevel() used a static last_discharging
initialized to false. If the device booted on the charger (discharging
already false), the equality check never fired and PowerSaveTimer kept
running, so the device entered light sleep after 60s and stopped wake
word detection — defeating the always-on-while-charging behavior.
Move the tracker to a member with a tri-state initial value (-1 =
unknown) so the very first GetBatteryLevel() tick reconciles the timer
state regardless of boot conditions. Add ESP_LOGI on each transition
so the always-on state is visible in the logs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(esp32-s3-touch-amoled-1.8): keep PowerSaveTimer ticking while charging
The previous patch disabled PowerSaveTimer entirely on the charger,
which also disabled the screen dim and sleep emoji that fire from
OnEnterSleepMode after 60s of idle. Since this board constructs the
timer with cpu_max_freq=-1, it never enters CPU light sleep nor
disables wake word detection in OnEnterSleepMode anyway — the only
charger-incompatible action is the 5-minute pmic_->PowerOff() in
OnShutdownRequest.
Keep the timer running unconditionally and gate just the shutdown
callback on last_discharging_. Also call WakeUp() on the
charging→battery transition so an unplug after 5+ idle minutes
doesn't trigger an immediate power-off.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* Revert "fix(esp32-s3-touch-amoled-1.8): keep PowerSaveTimer ticking while charging"
This reverts commit d7a9b619eb.
* feat(esp32-s3-touch-amoled-1.8): add wake word heartbeat while charging
Brings back the display dim + sleepy emoji UX while plugged in by
keeping PowerSaveTimer running and gating just the shutdown callback
on charging state. To work around the wake word stalling after long
idle (~22 min observed in testing), add a periodic bounce timer that
restarts wake word detection every 5 minutes while idle on the
charger. Skips the bounce mid-conversation, on battery, or when wake
word is already disabled.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(esp32-s3-touch-amoled-1.8): bounce wake word on sleep mode entry
Hardware testing shows wake word detection stops responding shortly
after PowerSaveTimer enters sleep mode (display dim + sleepy emoji).
The 5-minute periodic heartbeat doesn't help — the failure happens
well before the first bounce.
Add a preemptive bounce in OnEnterSleepMode while charging, and
shorten the periodic heartbeat to 2 minutes as a safety net for any
later stalls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(esp32-s3-touch-amoled-1.8): use standalone dim timer on charger
Hardware testing showed that the previous approach (keeping
PowerSaveTimer active with wake-word bounces on the charger) still
left wake word unresponsive after sleep mode entered — even with a
preemptive bounce in OnEnterSleepMode and a 2-min periodic heartbeat,
the AFE / audio pipeline kept stalling, and the user had to wake the
device with the boot button.
Switch to a different split:
- On charger: PowerSaveTimer is fully disabled. Its sleep callbacks
never fire, so wake word detection is undisturbed. A standalone
dim_timer (1s tick) handles the brightness + sleepy-emoji UX
independently — on idle 60s it dims, on activity (conversation,
button) it restores via Application::CanEnterSleepMode() polling.
- On battery: PowerSaveTimer runs as before (60s sleep callbacks,
5min auto-shutdown), dim_timer stays stopped.
Also drops the wake-word bounce timer entirely — without
PowerSaveTimer's sleep mode firing on the charger there's nothing to
recover from.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* Enhance Otto Robot camera support by adding configuration for OV3660. Updated config.h to define camera types and GPIO settings, modified config.json to include new camera options, and refactored otto_robot.cc for improved camera detection and initialization logic.
* fix: 移除 OttoEmojiDisplay 构造函数中的 SetTheme 调用以修复 LoadProhibited 崩溃
Made-with: Cursor
* refactor: improve audio service error handling and codec timeout management
- Updated AudioService to prevent input task termination on read timeout, introducing a delay instead.
- Enhanced NoAudioCodec to implement a read timeout for I2S channel reads.
- Adjusted WebSocketControlServer to set a control port for improved socket management.
- Added manufacturer information to the config.json for waveshare ESP32-Touch-LCD-3.5.
* fix(otto): WebSocket direct clients not receiving MCP responses
When a browser connects directly to the WebSocket control server (port
8080) and sends a JSON-RPC request, the MCP response was routed through
Application::SendMcpMessage -> protocol_->SendMcpMessage, which sends it
to the cloud protocol channel. As a result, the direct WebSocket client
never received the response, while the WeChat mini-program could because
it communicates via the cloud.
Fix:
- Add BroadcastMessage() to WebSocketControlServer, using
httpd_queue_work + httpd_ws_send_frame_async to asynchronously
send responses back to all connected clients on port 8080
- Add RegisterMcpBroadcastCallback() to Application, allowing an
additional MCP send callback to be registered; SendMcpMessage()
now invokes it alongside the cloud protocol
- Register the broadcast callback in OttoRobot after the WebSocket
server starts successfully
Also add WebSocket direct-connect API documentation to README.md
with complete JSON-RPC 2.0 command examples.
* fix(otto-robot): migrate camera backend and set safe dark default theme
- Migrate `otto-robot` camera backend from `EspVideo` to `Esp32Camera` to improve capture stability after reboot/power cycle.
- Keep runtime sensor detection for both OV2640 and OV3660, and rename PID macros with `OTTO_` prefix to avoid symbol conflicts.
- Configure camera output as `RGB565 + FRAMESIZE_240X240` to match the 240x240 display.
- Rotate OV2640 output by 180 degrees (`VFlip + HMirror`) for correct orientation.
- Simplify `otto-robot` camera sdkconfig options by keeping only:
- `CONFIG_CAMERA_OV2640=y`
- `CONFIG_CAMERA_OV3660=y`
- Move Otto default dark theme setup into `OttoEmojiDisplay::SetupUI()` (after base UI init) to avoid boot-time LVGL null-object crash caused by calling `SetTheme()` too early.
* feat(motion): smooth action ending and adaptive homing on otto/electron-bot
Improve servo motion transitions to reduce abrupt returns to home pose.
Replace linear interpolation with ease-out movement, make homing duration adaptive to angle delta, and skip intermediate homing when queued actions are pending to keep multi-action sequences fluid.
* Enhance Otto Robot camera support by adding configuration for OV3660. Updated config.h to define camera types and GPIO settings, modified config.json to include new camera options, and refactored otto_robot.cc for improved camera detection and initialization logic.
* fix: 移除 OttoEmojiDisplay 构造函数中的 SetTheme 调用以修复 LoadProhibited 崩溃
Made-with: Cursor
* refactor: improve audio service error handling and codec timeout management
- Updated AudioService to prevent input task termination on read timeout, introducing a delay instead.
- Enhanced NoAudioCodec to implement a read timeout for I2S channel reads.
- Adjusted WebSocketControlServer to set a control port for improved socket management.
- Added manufacturer information to the config.json for waveshare ESP32-Touch-LCD-3.5.
* fix(otto): WebSocket direct clients not receiving MCP responses
When a browser connects directly to the WebSocket control server (port
8080) and sends a JSON-RPC request, the MCP response was routed through
Application::SendMcpMessage -> protocol_->SendMcpMessage, which sends it
to the cloud protocol channel. As a result, the direct WebSocket client
never received the response, while the WeChat mini-program could because
it communicates via the cloud.
Fix:
- Add BroadcastMessage() to WebSocketControlServer, using
httpd_queue_work + httpd_ws_send_frame_async to asynchronously
send responses back to all connected clients on port 8080
- Add RegisterMcpBroadcastCallback() to Application, allowing an
additional MCP send callback to be registered; SendMcpMessage()
now invokes it alongside the cloud protocol
- Register the broadcast callback in OttoRobot after the WebSocket
server starts successfully
Also add WebSocket direct-connect API documentation to README.md
with complete JSON-RPC 2.0 command examples.
* fix(otto-robot): migrate camera backend and set safe dark default theme
- Migrate `otto-robot` camera backend from `EspVideo` to `Esp32Camera` to improve capture stability after reboot/power cycle.
- Keep runtime sensor detection for both OV2640 and OV3660, and rename PID macros with `OTTO_` prefix to avoid symbol conflicts.
- Configure camera output as `RGB565 + FRAMESIZE_240X240` to match the 240x240 display.
- Rotate OV2640 output by 180 degrees (`VFlip + HMirror`) for correct orientation.
- Simplify `otto-robot` camera sdkconfig options by keeping only:
- `CONFIG_CAMERA_OV2640=y`
- `CONFIG_CAMERA_OV3660=y`
- Move Otto default dark theme setup into `OttoEmojiDisplay::SetupUI()` (after base UI init) to avoid boot-time LVGL null-object crash caused by calling `SetTheme()` too early.
* perf(websocket): switch WiFi to performance mode before connecting
Optimize WebSocket connection speed by switching WiFi to performance
mode before establishing the connection, instead of after.
This reduces network latency significantly:
- TCP connection: 1093ms → 88ms (92% faster)
- WebSocket handshake: 1035ms → 80ms (92% faster)
- Total network layer: 2128ms → 173ms (92% faster)
The issue was caused by WiFi power save mode (MAX_MODEM) which adds
significant latency to packet transmission.
* Adjust formatting
* Enhance Otto Robot camera support by adding configuration for OV3660. Updated config.h to define camera types and GPIO settings, modified config.json to include new camera options, and refactored otto_robot.cc for improved camera detection and initialization logic.
* fix: 移除 OttoEmojiDisplay 构造函数中的 SetTheme 调用以修复 LoadProhibited 崩溃
Made-with: Cursor
* refactor: improve audio service error handling and codec timeout management
- Updated AudioService to prevent input task termination on read timeout, introducing a delay instead.
- Enhanced NoAudioCodec to implement a read timeout for I2S channel reads.
- Adjusted WebSocketControlServer to set a control port for improved socket management.
- Added manufacturer information to the config.json for waveshare ESP32-Touch-LCD-3.5.
* fix(otto): WebSocket direct clients not receiving MCP responses
When a browser connects directly to the WebSocket control server (port
8080) and sends a JSON-RPC request, the MCP response was routed through
Application::SendMcpMessage -> protocol_->SendMcpMessage, which sends it
to the cloud protocol channel. As a result, the direct WebSocket client
never received the response, while the WeChat mini-program could because
it communicates via the cloud.
Fix:
- Add BroadcastMessage() to WebSocketControlServer, using
httpd_queue_work + httpd_ws_send_frame_async to asynchronously
send responses back to all connected clients on port 8080
- Add RegisterMcpBroadcastCallback() to Application, allowing an
additional MCP send callback to be registered; SendMcpMessage()
now invokes it alongside the cloud protocol
- Register the broadcast callback in OttoRobot after the WebSocket
server starts successfully
Also add WebSocket direct-connect API documentation to README.md
with complete JSON-RPC 2.0 command examples.
The guard around the registration of the self.assets.set_download_url tool has been removed, ensuring it is always available for configuration. This change addresses issues on 32MB flash devices where the tool was previously skipped due to partition validation checks.
Fixes#1962
* fix(m5stack-tab5): remove stale esp_video==0.7.0 dependency instructions
The README previously instructed users to override esp_video to 0.7.0
and esp_ipa to 0.1.0, but this causes build failures because:
- esp_video 0.7.0 does not export esp_video_deinit(), resulting in
linker errors ('MAP_FAILED' and 'esp_video_deinit' not declared)
- The project's main/idf_component.yml already pins the correct
version (esp_video==1.3.1) that the source code expects
Users should now use the default dependency versions from idf_component.yml
without modification.
Fixes#1957
* fix(mcp): always register self.assets.set_download_url tool
On 32MB flash devices the assets partition layout differs from the
default, causing partition_valid() to return false and silently
skipping registration of the self.assets.set_download_url MCP tool.
Users see 'Unknown tool: self.assets.set_download_url' from their MCP
client.
The tool writes to Settings storage which works regardless of the
partition map, so the partition_valid() guard is unnecessary.
Move the AddUserOnlyTool call outside the guard so the tool is always
available for explicit configuration via MCP.
Fixes#1962
---------
Co-authored-by: Aayush Pratap Singh <aayushpratap.singh@gmail.com>
Create main/boards/rymcu/bigsmart so future RYMCU boards can live under the same manufacturer directory. Update CMake to set MANUFACTURER to rymcu while preserving BOARD_NAME as rymcu-bigsmart, and adjust config.json so release output remains rymcu-bigsmart.
* fix(blufi): GET_WIFI_LIST triggers real-time scan with guaranteed response
Previously, ESP_BLUFI_EVENT_GET_WIFI_LIST waited for any in-progress scan
to finish and then returned the cached result. When the cache was empty
(e.g. after a config-mode transition that stopped the Wi-Fi driver),
_send_wifi_list() returned silently with no response frame, leaving the
App waiting until timeout.
Changes:
- GET_WIFI_LIST now clears the cache and starts a fresh scan immediately.
- _wifi_scan_event_handler calls _send_wifi_list() after every scan
triggered by a GET_WIFI_LIST request.
- start_wifi_scan() calls esp_wifi_start() before esp_wifi_scan_start()
to handle the case where the driver was stopped during a mode
transition (ESP_ERR_WIFI_STATE is treated as already-started).
- _send_wifi_list() sends ESP_BLUFI_WIFI_SCAN_FAIL when no APs are
found, so the App always receives a terminal response.
- Redundant static_cast in _wifi_scan_event_handler replaced with the
existing local `self` pointer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(blufi): preserve cache fast-path, fall back to live scan only when needed
Address review feedback on always-rescan latency regression.
The GET_WIFI_LIST handler now distinguishes three cases:
1. Scan in flight: defer the response via m_send_list_after_scan; the
scan-done handler dispatches when it fires. Removes the previous
blocking `while (m_scan_in_progress) vTaskDelay(500)` which would
stall the BluFi event task indefinitely if the scan never completed.
2. Cache populated: respond from cache immediately (~50 ms, no latency
change vs original behavior). _send_wifi_list() still kicks off an
async refresh scan as before to keep the cache fresh.
3. Cache empty and no scan running: trigger a live scan and dispatch
from the scan-done handler. If start_wifi_scan() fails, send
ESP_BLUFI_WIFI_SCAN_FAIL so the App exits its wait state.
State variables are also disentangled:
- m_scan_should_save_ssid keeps its original meaning (write scan results
into m_ap_records). Cleared during connect-to-AP so the connect-time
scan does not pollute the cache.
- m_send_list_after_scan is new and tracks "the next scan-done event
should respond to a pending GET_WIFI_LIST request". The previous PR
conflated these two responsibilities onto m_scan_should_save_ssid,
which would have caused init-time scans to spuriously emit a wifi list
to the App.
start_wifi_scan() now returns bool so the caller can distinguish
"scan started or already running" from "could not start a scan".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Yixin Shi <shiyixin@qiniu.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
【中文】
7B 变体使用 v1.x 版本的 ESP32-P4 芯片和 32MB flash,但 config.json 中
对应的 sdkconfig_append 既没声明芯片版本,也没声明 flash 大小。IDF 因此
默认按 rev v3.1 + 16MB flash 构建,bootloader 被链接脚本
bootloader.rev3.ld 放到 0x4ffa_xxxx 的高位 HP_SRAM,这个区域在 v1.x
硅上不可取指,ROM 加载完跳过去,CPU 在第一条指令就
"Guru Meditation Error: Core 0 panic'ed (Illegal instruction)",
bootloader log 一行都打不出来。
添加 CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y + CONFIG_ESP32P4_REV_MIN_100=y
让 bootloader.ld(低位 HP_SRAM 0x4ff2_xxxx)被选中,并声明
CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y + partitions/v2/32m.csv,使 image
header 和分区表和板子实际硬件对齐;CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n
与已知 32MB 板子 sensecap-watcher 保持一致。
在 v1.3 版本的 P4-WIFI6-Touch-LCD-7B 上验证通过:bootloader 段现在加载到
0x4ff33ce0 / 0x4ff29ed0 / 0x4ff2cbd0;启动过程中 MIPI-DSI LCD、GT911
触控、OV5647 摄像头、ES8311+ES7210 音频和 ESP32-C6 SDIO 协处理器
全部正常初始化;状态机进入 wifi_configuring,对外发出 Xiaozhi-XXXX 配网热点。
---
[English]
The 7B variant ships with a v1.x ESP32-P4 silicon and a 32 MB flash, but
the existing sdkconfig_append declared neither. IDF then defaults to rev
v3.1 + 16 MB, and the bootloader is linked via bootloader.rev3.ld into
the 0x4ffa_xxxx upper HP_SRAM window. That range is not fetchable on
v1.x silicon, so the ROM jumps to the loaded bootloader and the CPU
panics on the first opcode with "Guru Meditation Error: Core 0 panic'ed
(Illegal instruction)" before any bootloader log is printed.
Add CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y + CONFIG_ESP32P4_REV_MIN_100=y
so bootloader.ld (low HP_SRAM 0x4ff2_xxxx) is used, and declare
CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y + partitions/v2/32m.csv so the image
header and partition offsets match the board. Disable flash-mode
auto-detect for parity with the other known 32 MB board (sensecap-watcher).
Verified on a v1.3 P4-WIFI6-Touch-LCD-7B: bootloader segments now load
at 0x4ff33ce0 / 0x4ff29ed0 / 0x4ff2cbd0; boot completes through MIPI DSI,
GT911 touch, OV5647 camera, ES8311+ES7210 audio and the ESP32-C6 SDIO
coprocessor; device reaches wifi_configuring and exposes the Xiaozhi-XXXX AP.
- Added a comprehensive guide for creating custom boards in the XiaoZhi AI project, detailing directory structure, configuration files, and initialization code.
- Introduced a new document explaining the MCP protocol for IoT control, including message formats and interaction flows.
- Updated existing documentation to reflect changes in tool registration and usage examples for the MCP protocol.
- Enhanced README files for better clarity and consistency across languages.
Add full keyboard support and keyboard-based WiFi configuration for
M5Stack Cardputer Adv:
- TCA8418 I2C keyboard driver with 56-key matrix, interrupt-driven
key events, and debounce handling
- Keyboard WiFi config UI: scan/select/input SSID and password
directly on the device without needing a phone
- Volume control (up/down arrows) and brightness control (left/right)
via keyboard with fine-step adjustment near bounds
- Enter key to toggle chat state
- Display offset and backlight fixes for ST7789V2
- README with flash parameters and hardware specs
Co-authored-by: bot <bot@localhost>
* Initial plan
* Fix crash when adjusting volume via button while audio device is disabled
When both input and output are disabled, UpdateDeviceState() sets dev_ to
nullptr. Pressing a volume button calls SetOutputVolume() which previously
called esp_codec_dev_set_out_vol(dev_, volume) without null-checking dev_,
causing a crash via ESP_ERROR_CHECK.
Fix: Add null guard for dev_ and mutex lock for thread safety. The volume
is still saved via AudioCodec::SetOutputVolume() and will be applied when
the device is reopened by UpdateDeviceState().
Agent-Logs-Url: https://github.com/78/xiaozhi-esp32/sessions/945c653a-ed16-49af-aefe-5cfb473402c6
Co-authored-by: 78 <4488133+78@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: 78 <4488133+78@users.noreply.github.com>
* Add touch screen support to freenove 2.8
Volume and brightness levels on vertical/horizontal swipe
Listening, chat state and wifi config on press/long press
* Simplified
Removed swipes. Touch quiet unresponsive on Freenove board.
* Remove unused parts
* Modify long tap duration in TouchTask
Updated touch driver logic to change long tap duration from 600ms to 3000ms.
* Cosmetic fix