Files
desklock/firmware/components/esp_hosted/examples/host_openthread_cli
jpmschweitzerandClaude Fable 5 cb5826e02b
Test, Build and Push / test-gateway (push) Successful in 12s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped
Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
Root cause (verified against our exact IDF tree, not the community guess):
the "258" in "sdio_write_task: Failed to send data: 258" is NOT a timeout
(that is 263). 258 = 0x102 = ESP_ERR_INVALID_ARG. On the ESP32-P4, block-
mode CMD53 writes require the SOURCE buffer to be 64-byte (cache-line)
aligned; the IDF sdmmc driver rejects a misaligned source with INVALID_ARG
BEFORE any bus activity. esp_hosts write loop then declares "Unrecoverable
host sdio state" and reboots the whole P4. The audio TX payload is not
64-aligned, so streaming mic audio wedged on the very FIRST frame (which is
exactly what we saw: listening -> instant Failed to send -> reboot).

This also explains why buffer/queue/clock/retry tuning all did nothing: the
write never reached the bus. And why our symptom was instant, not after
~100 writes (the community block-mode-desync theory) — it is the first
misaligned buffer, every time.

Fix: vendored esp_hosted 2.12.11 as an editable local component (overrides
the registry copy) and bounce a misaligned TX payload through one aligned
DMA scratch buffer in hosted_sdio_write_block (port_esp_hosted_host_sdio.c).
TX is serialized by the bus lock so a single static bounce buffer is safe;
freed in hosted_sdio_deinit. Host-only change — no C6 reflash.

VERIFIED ON HARDWARE (autonomous self-test): 40s of continuous mic-audio
upstream streaming — the traffic that previously wedged on the first frame
— ran clean, zero timeouts, zero reboots. A guarded SDIO_TX_SELFTEST harness
is kept (compiled out) for future SDIO stress testing.

Credit: root cause + patch designed via multi-agent investigation; the
precise 258=INVALID_ARG decode (correcting the upstream community timeout
assumption) came from checking our actual esp_err.h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:50:27 +02:00
..

Supported Hosts ESP32-P4
Supported Co-processors ESP32-C5 ESP32-C6 ESP32-H2

OpenThread Command Line Example

This example demonstrates an OpenThread CLI, with some additional features such as iperf.

Example 1: OpenThread Host communicating with Openthread RCP over UART

The example runs on an ESP32-P4, connected to a ESP32-C6 DevKit as the co-processor via the GPIO Header. ESP-Hosted transport is SPI-FD (Full duplex) while UART is used for OpenThread communications.

Setting up the hardware

This table shows the Hardware Connections between the ESP32-P4 and C6.

SPI-FD Connection Between ESP32-P4 and ESP32-C6 co-processor

ESP32-P4 GPIO ESP32-C6 GPIO
MOSI 4 7
MISO 5 2
CLK 26 6
CS 6 10
Handshake 20 3
Data Ready 32 4
C6 Reset 2 RST

UART Connection Between ESP32-P4 and ESP32-C6 co-processor

ESP32-P4 GPIO ESP32-C6 GPIO
(To RCP Tx) 24 (Tx) 21
(To RCP Rx) 25 (Rx) 20

Configure the project for ESP32-P4

On the command-line:

idf.py set-target esp32p4
idf.py menuconfig

Configure the project:

Component config
└── ESP-Hosted config
    ├── Configure GPIOs for Development Board ──> No development board
    ├── Transport layer ──> SPI Full-duplex
    ├── SPI Configuration ──> (Configure GPIOs)
    │   ⋮
    └── [*] Enable OpenThread Host Support
        └── OpenThread RCP Configuration
            ├── OpenThread Transport ──> UART
            └── (configure UART parameters)

Configure the project for ESP32-C6

In the ESP-Hosted project slave directory:

Edit sdkconfig.defaults.esp32c6 and enable the OpenThread section:

#
# OpenThread
#
CONFIG_OPENTHREAD_ENABLED=y
CONFIG_OPENTHREAD_RADIO=y
CONFIG_OPENTHREAD_DIAG=n
CONFIG_OPENTHREAD_COMMISSIONER=n
CONFIG_OPENTHREAD_JOINER=n
CONFIG_OPENTHREAD_BORDER_ROUTER=n
CONFIG_OPENTHREAD_CLI=n
CONFIG_OPENTHREAD_SRP_CLIENT=n
CONFIG_OPENTHREAD_DNS_CLIENT=n
CONFIG_OPENTHREAD_TASK_SIZE=3072
CONFIG_OPENTHREAD_CONSOLE_ENABLE=n
CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC=n

CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y
# end of OpenThread

On the command-line:

idf.py set-target esp32c6
idf.py menuconfig

Configure the project:

Example Configuration
├── Bus Config in between Host and Co-processor
│   ├── Transport layer ──> SPI Full-duplex
│   └── SPI Full-duplex Configuration ──> (Configure GPIOs)
│   ⋮
└── [*] Enable OpenThread RCP (Radio Co-Processor)
    └── OpenThread RCP Configuration
        ├── OpenThread Transport ──> UART
        └── (configure UART parameters)

If you did not edit sdkconfig.defaults.esp32c6 to enable OpenThread (above), modify these settings:

Component config
├── Wireless Coexistence
│   └── [*] Software controls WiFi/Bluetooth coexistence
└── OpenThread
    ├── [*] OpenThread
    ├── Thread Task Parameters
    │   └── (3072) Size of OpenThread task
    ├── Thread Console
    │   ├── [ ] Enable OpenThread console
    │   └── [ ] Enable Openthread Command-Line Interface
    ├── Thread Core Features
    │   ├── Thread device type ──> Radio Only Device
    │   ├── [ ] Enable Commissioner
    │   ├── [ ] Enable Joiner
    │   ├── [ ] Enable SRP Client
    │   ├── [ ] Enable DNS Client
    │   ├── [ ] Enable diag Client
    └── Thread Log
        └── [ ] Enable dynamic log level control

Build, Flash, and Run

Build the projects and flash them to the appropriate boards, then run monitor tool to view serial output:

idf.py -p PORT build flash monitor

On the ESP32-P4 you'll get an OpenThread command line shell.

Example Output

The help command will print all of the supported commands.

esp32h2> ot help
I(7058) OPENTHREAD:[INFO]-CLI-----: execute command: help
bbr
bufferinfo
ccathreshold
channel
child
childip
childmax
childsupervision
childtimeout
coap
contextreusedelay
counters
dataset
delaytimermin
diag
discover
dns
domainname
eidcache
eui64
extaddr
extpanid
factoryreset
...

Set Up Network

To run this example, one more board (for example, an ESP32-H2) with the ESP-IDF ot_cli example is required..

On the ESP32-P4, run the following commands:

esp32p4> ot factoryreset
... # the device will reboot

esp32p4> ot dataset init new
Done
esp32p4> ot dataset commit active
Done
esp32p4> ot ifconfig up
Done
esp32p4> ot thread start
Done

# After some seconds

esp32p4> ot state
leader
Done

Now the ESP32-P4 has formed a Thread network as a leader. Get some information which will be used in next steps:

esp32p4> ot ipaddr
fdde:ad00:beef:0:0:ff:fe00:fc00
fdde:ad00:beef:0:0:ff:fe00:8000
fdde:ad00:beef:0:a7c6:6311:9c8c:271b
fe80:0:0:0:5c27:a723:7115:c8f8

# Get the Active Dataset
esp32p4> ot dataset active -x
0e080000000000010000000300001835060004001fffe00208fe7bb701f5f1125d0708fd75cbde7c6647bd0510b3914792d44f45b6c7d76eb9306eec94030f4f70656e5468726561642d35383332010258320410e35c581af5029b054fc904a24c2b27700c0402a0fff8

On the ESP32-H2, set the active dataset from leader, and start Thread interface:

esp32h2> ot factoryreset
... # the device will reboot

esp32h2> ot dataset set active 0e080000000000010000000300001835060004001fffe00208fe7bb701f5f1125d0708fd75cbde7c6647bd0510b3914792d44f45b6c7d76eb9306eec94030f4f70656e5468726561642d35383332010258320410e35c581af5029b054fc904a24c2b27700c0402a0fff8
esp32h2> ot ifconfig up
Done
esp32h2> ot thread start
Done

# After some seconds

esp32h2> ot state
router  # child is also a valid state
Done

The second device has joined the Thread network as a router (or a child).

Extension commands

You can refer to the extension command about the extension commands.

The following examples are supported by ot_cli:

  • TCP and UDP Example

Using iPerf to measure bandwidth

iPerf is a tool used to obtain TCP or UDP throughput on the Thread network. To run iPerf, you need to have two Thread devices on the same network.

Refer to the iperf-cmd component for details on specific configurations.

Typical usage on a thread network

Note

The ML-EID address is used for iperf.

For measuring the TCP throughput, first get the ML-EID address, then create an iperf service on one node:

> ot ipaddr mleid
fdde:ad00:beef:0:a7c6:6311:9c8c:271b
Done

> iperf -V -s -t 20 -i 3 -p 5001 -f k
Done

Then create an iperf client connecting to the service on another node.

> iperf -V -c fdde:ad00:beef:0:a7c6:6311:9c8c:271b -t 20 -i 1 -p 5001 -l 85 -f k
Done
[ ID] Interval		Transfer	Bandwidth
[  1]  0.0- 1.0 sec	3.15 KBytes	25.16 Kbits/sec
[  1]  1.0- 2.0 sec	2.89 KBytes	23.12 Kbits/sec
[  1]  2.0- 3.0 sec	2.98 KBytes	23.80 Kbits/sec
...
[  1]  9.0-10.0 sec	2.55 KBytes	20.40 Kbits/sec
[  1]  0.0-10.0 sec	27.80 KBytes	22.24 Kbits/sec