Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
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

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>
This commit is contained in:
2026-07-15 09:50:27 +02:00
co-authored by Claude Fable 5
parent ac9d24e0f9
commit cb5826e02b
666 changed files with 216925 additions and 0 deletions
@@ -0,0 +1,6 @@
# 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)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hosted_events)
@@ -0,0 +1,287 @@
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
| Supported Co-Processors | ESP32 | ESP32-C Series | ESP32-S Series |
| ----------------------- | ----- | -------------- | -------------- |
# ESP-Hosted Events Example
This host example uses ESP-Hosted events to detect that the co-processor has reset or hanged. It then takes action to reinitialised ESP-Hosted to restore connectivity with the co-processor.
This has been tested with the ESP32-C6 co-processor, flashed with the standard [ESP-Hosted slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave) from the ESP Component Registry, or built from the `slave` directory from ESP-Hosted Github.
It makes use of ESP-Hosted events, with event base of `ESP_HOSTED_EVENT`:
- `ESP_HOSTED_EVENT_CP_INIT`: this event is sent by the co-processor after it has started and ESP-Hosted transport has been setup. Receiving this event for a second time indicates that the co-process has restarted. An `esp_reset_reason_t` value is provided so the host can discover why the co-processor sent the event
- `ESP_HOSTED_EVENT_CP_HEARTBEAT`: when configured by the host, the co-processor will periodically send a heartbeat event at the required interval (in seconds). If this event is not received after the interval, it indicates the the co-processor may have hanged and is unable to send the event.
- `ESP_HOSTED_EVENT_TRANSPORT_FAILURE`: indicates a ESP-Hosted transport failure
This example sets up the detection of ESP-Hosted events, then connects to a station. After connecting, if the co-processor reboots (INIT event received) or hangs (timeout getting HEARTBEAT), it will tell NETIF that the connection is lost, then proceed to reinitialise the ESP-Hosted transport.
## How to Use This Example on ESP Hosts
Before project configuration and build, be sure to set the correct
chip target using:
```bash
idf.py set-target <ESP Host chip_name>
```
### Configure the project
Open the project configuration menu:
```bash
idf.py menuconfig
```
In the `Example Configuration` menu:
* set `Heartbeat Interval in Seconds`. Default is 5 seconds
* toggle `Enable heartbeat timeout detection` to enable heartbeat timeout detection
* if heartbeat timeout detection is enabled, you can set `Heartbeat Timeout in Seconds`. This value should be larger than `Heartbeat Interval in Seconds`. A value should be set based on app requirements (how long to wait after a timeout before triggering a timeout alert).
Configure the Station parameters at needed:
- Set WiFi SSID.
- Set WiFi Password.
Optional: If you need, change the other Station options according to your requirements.
### Build and Flash
Run `idf.py -p HOST_PORT flash monitor` to build, flash and monitor the
project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://idf.espressif.com/) for full
steps to configure and use ESP-IDF to build projects.
## Example Output
> [!NOTE]
> To simulate a co-processor reset or hang, a ESP-Prog is connected to the ESP32-C6 on the ESP32-P4 Dev Board. The `RST` and `BOOT` buttons on the ESP-Prog is then used to reset or put the C6 into boot mode (to simulate a hang).
### Example Output using SDIO interface
```
I (6327) esp_netif_handlers: sta ip: 192.168.50.217, mask: 255.255.255.0, gw: 192.168.50.1
I (6327) wifi station: got ip:192.168.50.217
I (6327) wifi station: connected to ap SSID:xxx password:yyy
I (7377) hosted_events: *** Heartbeat 0***
W (13377) hosted_events: *** HEARTBEAT timeout ***
I (14327) hosted_events: heartbeat timeout, transport failure or got INIT event: reinit Hosted
E (14327) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x107
E (14327) H_SDIO_DRV: sdio_write_task: 0: Failed to send data: 263 54 54
E (14327) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x107
E (14337) H_SDIO_DRV: sdio_write_task: 1: Failed to send data: 263 54 54
E (14347) H_SDIO_DRV: Unrecoverable host sdio state
I (14347) wifi station: do not reconnect
I (14347) hosted_events: *** Transport Failure ***
I (14527) H_API: ESP-Hosted deinit
I (14527) serial_ll: Clean-up serial queue
W (14527) H_SDIO_DRV: free buffer[0] 0x4ff3d8c0
W (14527) H_SDIO_DRV: free buffer[1] 0x4ff3e1c0
I (14527) transport: TRANSPORT_INACTIVE
I (14527) hosted_events: restarting ESP-Hosted
I (14537) hosted_events: init ESP-Hosted
I (14537) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
W (14547) H_API: Transport already initialized, skipping initialization
I (14557) H_API: ** add_esp_wifi_remote_channels **
I (14557) H_SDIO_DRV: sdio_data_to_rx_buf_task started
I (14567) H_API: ESP-Hosted Try to communicate with ESP-Hosted slave
I (14567) transport: Attempt connection with slave: retry[0]
W (14577) H_SDIO_DRV: Reset slave using GPIO[54]
I (14577) os_wrapper_esp: GPIO [54] configured
I (16107) sdio_wrapper: SDIO master: Slot 1, Data-Lines: 4-bit Freq(KHz)[40000 KHz]
I (16107) sdio_wrapper: GPIOs: CLK[18] CMD[19] D0[14] D1[15] D2[16] D3[17] Slave_Reset[54]
I (16107) sdio_wrapper: Queues: Tx[20] Rx[20] SDIO-Rx-Mode[1]
I (16147) sdio_wrapper: Function 0 Blocksize: 512
I (16147) sdio_wrapper: Function 1 Blocksize: 512
I (16247) H_SDIO_DRV: Card init success, TRANSPORT_RX_ACTIVE
I (16247) transport: set_transport_state: 1
I (16247) transport: Waiting for esp_hosted slave to be ready
I (16267) H_SDIO_DRV: SDIO Host operating in STREAMING MODE
I (16267) H_SDIO_DRV: Open data path at slave
I (16267) H_SDIO_DRV: Starting SDIO process rx task
I (16287) H_SDIO_DRV: Received ESP_PRIV_IF type message
I (16287) transport: Received INIT event from ESP32 peripheral
I (16287) transport: EVENT: 12
I (16287) transport: Identified slave [esp32c6]
I (16297) transport: EVENT: 11
I (16297) transport: capabilities: 0xd
I (16307) transport: Features supported are:
I (16307) transport: * WLAN
I (16307) transport: - HCI over SDIO
I (16317) transport: - BLE only
I (16317) transport: EVENT: 13
I (16317) transport: ESP board type is : 13
I (16327) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (16327) H_API: Transport active
I (16327) transport: Slave chip Id[12]
I (16337) transport: raw_tp_dir[-], flow_ctrl: low[60] high[80]
I (16337) transport: transport_delayed_init
I (16347) esp_cli: Remove any existing deep_sleep cmd in cli
I (16347) esp_cli: Registering command: crash
I (16357) esp_cli: Registering command: reboot
I (16357) esp_cli: Registering command: mem-dump
I (16367) esp_cli: Registering command: task-dump
I (16367) esp_cli: Registering command: cpu-dump
I (16367) esp_cli: Registering command: heap-trace
I (16377) esp_cli: Registering command: sock-dump
I (16377) esp_cli: Registering command: host-power-save
I (16387) hci_stub_drv: Host BT Support: Disabled
I (16387) H_SDIO_DRV: Received INIT event
I (16397) H_SDIO_DRV: Event type: 0x22
I (16397) H_SDIO_DRV: Write thread started
I (16447) hosted_events: getting fw version
I (16567) hosted_events: *** got INIT event from co-processor ***
I (16567) hosted_events: *** Co-processor Reset Reason 1***
I (16567) hosted_events: Expected INIT event
I (16567) hosted_events: FW Version: 2.8.1
I (16577) hosted_events: heartbeat timer started
I (16577) transport: Attempt connection with slave: retry[0]
I (16577) transport: Transport is already up
I (16877) wifi station: wifi_init_sta finished.
I (16877) RPC_WRAP: ESP Event: wifi station started
I (16887) RPC_WRAP: ESP Event: wifi station started
I (19327) RPC_WRAP: ESP Event: Station mode: Disconnected
I (19327) H_API: esp_wifi_remote_connect
I (19347) wifi station: retry to connect to the AP
I (21567) hosted_events: *** Heartbeat 0***
I (21757) RPC_WRAP: ESP Event: Station mode: Disconnected
I (21757) H_API: esp_wifi_remote_connect
I (21767) wifi station: retry to connect to the AP
I (24377) RPC_WRAP: ESP Event: Station mode: Connected
I (24377) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x400477f6
--- 0x400477f6: wifi_sta_receive at /home/kysoh/esp/gitlab_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (25387) esp_netif_handlers: sta ip: 192.168.50.217, mask: 255.255.255.0, gw: 192.168.50.1
I (25387) wifi station: got ip:192.168.50.217
I (25387) wifi station: connected to ap SSID:xxx password:yyy
I (26577) hosted_events: *** Heartbeat 1***
```
### Example Output using SPI-FD interface
```
I (5323) esp_netif_handlers: sta ip: 192.168.50.82, mask: 255.255.255.0, gw: 192.168.50.1
I (5323) wifi station: got ip:192.168.50.82
I (5323) wifi station: connected to ap SSID:xxx password:yyy
I (6383) hosted_events: *** Heartbeat 0***
I (11383) hosted_events: *** Heartbeat 1***
I (16383) hosted_events: *** Heartbeat 2***
I (18523) spi: rx packet ignored: len [65535], rcvd_offset[65535], exp_offset[12]
I (19173) transport: Received INIT event from ESP32 peripheral
I (19173) transport: EVENT: 12
I (19173) transport: Identified slave [esp32c6]
I (19173) transport: EVENT: 11
I (19183) transport: capabilities: 0xe8
I (19183) transport: Features supported are:
I (19183) transport: - HCI over SPI
I (19193) transport: - BLE only
I (19193) transport: EVENT: 13
I (19193) transport: ESP board type is : 13
I (19203) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (19203) H_API: Transport active
I (19213) transport: Slave chip Id[12]
I (19213) transport: raw_tp_dir[-], flow_ctrl: low[60] high[90]
I (19223) transport: transport_delayed_init
I (19223) esp_cli: Remove any existing deep_sleep cmd in cli
I (19233) esp_cli: Registering command: crash
I (19233) esp_cli: Registering command: reboot
I (19233) esp_cli: Registering command: mem-dump
I (19243) esp_cli: Registering command: task-dump
I (19243) esp_cli: Registering command: cpu-dump
I (19253) esp_cli: Registering command: heap-trace
I (19253) esp_cli: Registering command: sock-dump
I (19263) esp_cli: Registering command: host-power-save
I (19263) hci_stub_drv: Host BT Support: Disabled
I (19263) spi: Received INIT event
I (19273) hosted_events: *** got INIT event from co-processor ***
I (19273) hosted_events: *** Co-processor Reset Reason 1***
W (19283) hosted_events: *** Unexpected INIT event
I (19323) hosted_events: heartbeat timeout, transport failure or got INIT event: reinit Hosted
I (19323) wifi station: do not reconnect
I (19523) H_API: ESP-Hosted deinit
I (19523) serial_ll: Clean-up serial queue
I (19523) spi: Deinitializing SPI bus
I (19523) spi_wrapper: SPI deinitialized
I (19523) spi: SPI bus deinitialized
I (19523) transport: TRANSPORT_INACTIVE
I (19523) hosted_events: restarting ESP-Hosted
I (19533) hosted_events: init ESP-Hosted
I (19533) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
W (19543) H_API: Transport already initialized, skipping initialization
I (19553) H_API: ** add_esp_wifi_remote_channels **
I (19553) spi_wrapper: Transport: SPI, Mode:3 Freq:40MHz TxQ:20 RxQ:20
GPIOs: CLK:18 MOSI:14 MISO:15 CS:19 HS:16 DR:17 SlaveReset:54
I (19563) spi: Staring SPI task
I (19573) os_wrapper_esp: GPIO [16] configuring as Interrupt
I (19573) os_wrapper_esp: GPIO [17] configuring as Interrupt
I (19583) H_API: ESP-Hosted Try to communicate with ESP-Hosted slave
I (19583) transport: Attempt connection with slave: retry[0]
I (19593) spi: Resetting slave
I (19593) spi: Resetting slave on SPI bus with pin 54
I (19603) os_wrapper_esp: GPIO [54] configured
I (20123) transport: Waiting for esp_hosted slave to be ready
I (20183) transport: Received INIT event from ESP32 peripheral
I (20183) transport: EVENT: 12
I (20183) transport: Identified slave [esp32c6]
I (20183) transport: EVENT: 11
I (20183) transport: capabilities: 0xe8
I (20183) transport: Features supported are:
I (20193) transport: - HCI over SPI
I (20193) transport: - BLE only
I (20193) transport: EVENT: 13
I (20203) transport: ESP board type is : 13
I (20203) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (20213) H_API: Transport active
I (20213) transport: Slave chip Id[12]
I (20213) transport: raw_tp_dir[-], flow_ctrl: low[60] high[90]
I (20223) transport: transport_delayed_init
I (20223) esp_cli: Remove any existing deep_sleep cmd in cli
I (20233) esp_cli: Registering command: crash
I (20233) esp_cli: Registering command: reboot
I (20243) esp_cli: Registering command: mem-dump
I (20243) esp_cli: Registering command: task-dump
I (20253) esp_cli: Registering command: cpu-dump
I (20253) esp_cli: Registering command: heap-trace
I (20253) esp_cli: Registering command: sock-dump
I (20263) esp_cli: Registering command: host-power-save
I (20263) hci_stub_drv: Host BT Support: Disabled
I (20273) spi: Received INIT event
I (20323) hosted_events: getting fw version
I (20583) hosted_events: *** got INIT event from co-processor ***
I (20583) hosted_events: *** Co-processor Reset Reason 1***
I (20583) hosted_events: Expected INIT event
I (20583) hosted_events: FW Version: 2.7.4
I (20593) transport: Attempt connection with slave: retry[0]
I (20593) transport: Transport is already up
I (20893) wifi station: wifi_init_sta finished.
I (20893) RPC_WRAP: ESP Event: wifi station started
I (20903) RPC_WRAP: ESP Event: wifi station started
I (23343) RPC_WRAP: ESP Event: Station mode: Disconnected
I (23343) H_API: esp_wifi_remote_connect
I (23373) wifi station: retry to connect to the AP
I (25593) hosted_events: *** Heartbeat 0***
I (25783) RPC_WRAP: ESP Event: Station mode: Disconnected
I (25783) H_API: esp_wifi_remote_connect
I (25793) wifi station: retry to connect to the AP
I (28403) RPC_WRAP: ESP Event: Station mode: Connected
I (28403) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x400408f2
--- 0x400408f2: wifi_sta_receive at /home/kysoh/esp/gitlab_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (29423) esp_netif_handlers: sta ip: 192.168.50.82, mask: 255.255.255.0, gw: 192.168.50.1
I (29423) wifi station: got ip:192.168.50.82
I (29423) wifi station: connected to ap SSID:xxx password:yyy
I (30593) hosted_events: *** Heartbeat 1***
I (32213) spi: rx packet ignored: len [65535], rcvd_offset[65535], exp_offset[12]
```
@@ -0,0 +1,4 @@
set(srcs "main.c" "station_example.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")
@@ -0,0 +1,110 @@
menu "Example Configuration"
config EXAMPLE_HEARTBEAT_INTERVAL_SEC
int "Heartbeat Interval in Seconds"
default 5
help
Set the co-processor heartbeat interval
config EXAMPLE_ENABLE_HEARTBEAT_MONITOR
bool "Enable heartbeat timeout monitor"
default y
help
Detect if there is a Heartbeat timeout
config EXAMPLE_HEARTBEAT_TIMEOUT_SEC
depends on EXAMPLE_ENABLE_HEARTBEAT_MONITOR
int "Heartbeat Timeout in Seconds"
default 6
help
Set the co-processor heartbeat timeout interval (minimum value should be the Heartbeat interval)
choice EXAMPLE_HOSTED_RECOVERY_METHOD
bool "Co-processor Recovery method"
default EXAMPLE_DO_HOSTED_RECOVERY
help
Select the recovery method to use
config EXAMPLE_DO_HOSTED_RECOVERY
bool "Restart ESP-Hosted transport"
help
Restart the ESP-Hosted transport to attempt recovery
config EXAMPLE_DO_HOST_RESET
bool "Restart the Host"
help
Restart the Host to recover the system
config EXAMPLE_DO_NOTHING
bool "Do nothing"
help
Don't do anything when error is received
endchoice
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
choice ESP_WIFI_SAE_MODE
prompt "WPA3 SAE mode selection"
default ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
help
Select mode for SAE as Hunt and Peck, H2E or both.
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HUNT_AND_PECK
bool "HUNT AND PECK"
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT
bool "H2E"
depends on ESP_WIFI_ENABLE_SAE_H2E
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
bool "BOTH"
depends on ESP_WIFI_ENABLE_SAE_H2E
endchoice
config ESP_WIFI_PW_ID
string "PASSWORD IDENTIFIER"
depends on ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT|| ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
default ""
help
password identifier for SAE H2E
config ESP_MAXIMUM_RETRY
int "Maximum retry"
default 5
help
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
choice ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD
prompt "WiFi Scan auth mode threshold"
default ESP_WIFI_AUTH_WPA2_PSK
help
The weakest authmode to accept in the scan mode.
This value defaults to ESP_WIFI_AUTH_WPA2_PSK in case password is present and ESP_WIFI_AUTH_OPEN is used.
Please select ESP_WIFI_AUTH_WEP/ESP_WIFI_AUTH_WPA_PSK in case AP is operating in WEP/WPA mode.
config ESP_WIFI_AUTH_OPEN
bool "OPEN"
config ESP_WIFI_AUTH_WEP
bool "WEP"
config ESP_WIFI_AUTH_WPA_PSK
bool "WPA PSK"
config ESP_WIFI_AUTH_WPA2_PSK
bool "WPA2 PSK"
config ESP_WIFI_AUTH_WPA_WPA2_PSK
bool "WPA/WPA2 PSK"
config ESP_WIFI_AUTH_WPA3_PSK
bool "WPA3 PSK"
config ESP_WIFI_AUTH_WPA2_WPA3_PSK
bool "WPA2/WPA3 PSK"
config ESP_WIFI_AUTH_WAPI_PSK
bool "WAPI PSK"
endchoice
endmenu
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __APP_COMMON_H__
#define __APP_COMMON_H__
void example_wifi_init_once(void);
void example_wifi_init_sta(void);
void example_wifi_deinit_sta(void);
void example_wifi_sta_netif_close(void);
#endif
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,332 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_event.h"
#include "esp_hosted.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "app_common.h"
#define HEARTBEAT_INTERVAL_SEC CONFIG_EXAMPLE_HEARTBEAT_INTERVAL_SEC
#define ENABLE_HEARTBEAT_MONITOR CONFIG_EXAMPLE_ENABLE_HEARTBEAT_MONITOR
#define DO_HOSTED_RECOVERY CONFIG_EXAMPLE_DO_HOSTED_RECOVERY
#define DO_HOST_RESET CONFIG_EXAMPLE_DO_HOST_RESET
#define DO_NOTHING CONFIG_EXAMPLE_DO_NOTHING
#if ENABLE_HEARTBEAT_MONITOR
#define HEARTBEAT_TIMEOUT_SEC CONFIG_EXAMPLE_HEARTBEAT_TIMEOUT_SEC
#define HEARTBEAT_TIMEOUT_NSEC (HEARTBEAT_TIMEOUT_SEC * 1000 * 1000)
static esp_timer_handle_t heartbeat_timer_handle = NULL;
#endif
#define ESP_HOSTED_RESET_BIT BIT0
static EventGroupHandle_t s_esp_hosted_event_group;
static bool resetting_esp_hosted_transport = false;
static bool first_init_event = false;
static bool first_heartbeat = false;
static uint32_t prev_heartbeat = 0;
static SemaphoreHandle_t sem_hosted_is_up;
static const char* TAG = "hosted_events";
#if ENABLE_HEARTBEAT_MONITOR
void my_timer_cb(void *arg)
{
ESP_LOGI(TAG, "*** TIMER: HEARTBEAT timeout ***");
if (!resetting_esp_hosted_transport) {
xEventGroupSetBits(s_esp_hosted_event_group, ESP_HOSTED_RESET_BIT);
}
// destroy the timer
esp_timer_delete(heartbeat_timer_handle);
heartbeat_timer_handle = NULL;
}
#endif
static void init_cp_error_detection(void)
{
first_init_event = false;
first_heartbeat = false;
prev_heartbeat = 0;
}
static void deinit_cp_error_detection(void)
{
// currently does nothing
}
/**
* This event handler handles ESP-Hosted events
*
* See station_example.c for the event handler for Wi-Fi and IP events
*/
static void esp_hosted_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == ESP_HOSTED_EVENT) {
if (event_id == ESP_HOSTED_EVENT_CP_INIT) {
ESP_LOGI(TAG, "*** got INIT event from co-processor ***");
esp_hosted_event_init_t *event = (esp_hosted_event_init_t *)event_data;
ESP_LOGI(TAG, "*** Co-processor Reset Reason %"PRIu16" ***", event->reason);
if (!first_init_event) {
// this is the first init event and is expected
first_init_event = true;
ESP_LOGI(TAG, "Expected INIT event");
} else if (!resetting_esp_hosted_transport) {
// unexpected init event
xEventGroupSetBits(s_esp_hosted_event_group, ESP_HOSTED_RESET_BIT);
ESP_LOGI(TAG, "*** Unexpected INIT event");
}
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_UP) {
ESP_LOGI(TAG, "ESP-Hosted Transport is UP");
xSemaphoreGive(sem_hosted_is_up);
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_DOWN) {
ESP_LOGI(TAG, "ESP-Hosted Transport is DOWN");
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_FAILURE) {
if (!resetting_esp_hosted_transport) {
xEventGroupSetBits(s_esp_hosted_event_group, ESP_HOSTED_RESET_BIT);
ESP_LOGI(TAG, "*** Transport Failure ***");
}
} else if (event_id == ESP_HOSTED_EVENT_CP_HEARTBEAT) {
esp_hosted_event_heartbeat_t *event = (esp_hosted_event_heartbeat_t *)event_data;
ESP_LOGI(TAG, "*** Heartbeat %"PRIu32" ***", event->heartbeat);
uint32_t curr_heartbeat = event->heartbeat;
#if ENABLE_HEARTBEAT_MONITOR
if (heartbeat_timer_handle) {
if (esp_timer_is_active(heartbeat_timer_handle)) {
// restart the timer
if (ESP_OK != esp_timer_restart(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC)) {
ESP_LOGE(TAG, "Failed to restart the timer");
}
} else {
// start the timer
if (ESP_OK != esp_timer_start_once(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC)) {
ESP_LOGE(TAG, "Failed to start the timer");
}
}
}
#endif
if (!first_heartbeat) {
first_heartbeat = true;
} else if (curr_heartbeat != (prev_heartbeat + 1)) {
ESP_LOGW(TAG, "heartbeat: expected %"PRIu32", but got %"PRIu32, prev_heartbeat + 1, curr_heartbeat);
}
prev_heartbeat = curr_heartbeat;
} else {
ESP_LOGI(TAG, "Got UNKNOWN ESP_HOSTED event");
}
}
}
static void app_init_once(void)
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_event_handler_instance_t instance_got_hosted_event;
ESP_ERROR_CHECK(esp_event_handler_instance_register(ESP_HOSTED_EVENT,
ESP_EVENT_ANY_ID,
&esp_hosted_event_handler,
NULL,
&instance_got_hosted_event));
// set up event group, used to signal that ESP-Hosted transport has gone down
s_esp_hosted_event_group = xEventGroupCreate();
// create a binary semaphore for app to wait on until ESP-Hosted transport is up
sem_hosted_is_up = xSemaphoreCreateBinary();
assert(sem_hosted_is_up);
xSemaphoreGive(sem_hosted_is_up);
xSemaphoreTake(sem_hosted_is_up, portMAX_DELAY);
}
static void app_esp_hosted_init(void)
{
esp_hosted_init();
esp_hosted_connect_to_slave();
}
#if DO_HOSTED_RECOVERY
static void app_esp_hosted_deinit(void)
{
esp_hosted_deinit();
}
#endif
static bool app_esp_hosted_verify_up(void)
{
// to verify ESP-Hosted is UP, we get the co-processor fw info
ESP_LOGI(TAG, "getting fw version");
esp_hosted_coprocessor_fwver_t fwver;
if (ESP_OK == esp_hosted_get_coprocessor_fwversion(&fwver)) {
ESP_LOGI(TAG, "FW Version: %" PRIu32 ".%" PRIu32 ".%" PRIu32,
fwver.major1, fwver.minor1, fwver.patch1);
return true;
} else {
ESP_LOGE(TAG, "failed to get fw version");
return false;
}
}
#if ENABLE_HEARTBEAT_MONITOR
static esp_err_t app_setup_heartbeat_monitor(void)
{
esp_err_t res;
if (!heartbeat_timer_handle) {
// create heartbeat timer
esp_timer_create_args_t my_timer = {
.callback = my_timer_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "Heartbeat Timer",
.skip_unhandled_events = true,
};
res = esp_timer_create(&my_timer, &heartbeat_timer_handle);
if (res == ESP_OK) {
// start the timer
res = esp_timer_start_once(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC);
if (res == ESP_OK) {
ESP_LOGI(TAG, "heartbeat timer started");
} else {
ESP_LOGE(TAG, "failed to start heartbeat timer");
return res;
}
} else {
ESP_LOGE(TAG, "failed to create heartbeat timer");
heartbeat_timer_handle = NULL;
return res;
}
} else {
// heartbeat timer already created: restart it
if (esp_timer_is_active(heartbeat_timer_handle)) {
// restart the timer
res = esp_timer_restart(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to restart the timer");
return res;
}
} else {
// start the timer
res = esp_timer_start_once(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to start the timer");
return res;
}
}
}
return ESP_OK;
}
#endif
/**
* this function handles what to do when a co-processor error is encountered
*
* here, we either try to recover from the error, or reset the host
*/
static bool app_do_cp_recovery(void)
{
#if DO_HOSTED_RECOVERY
example_wifi_sta_netif_close(); // inform netif for sta that connection was lost
example_wifi_deinit_sta(); // deinit the station
app_esp_hosted_deinit();
return true;
#elif DO_HOST_RESET
ESP_LOGI(TAG, "********* Restarting host to avoid sync issues **********************");
vTaskDelay(2000 / portTICK_PERIOD_MS);
esp_restart();
return true;
#elif DO_NOTHING
ESP_LOGI(TAG, "Nothing done to recover");
return false;
#else
#error No co-processor recovery method selected
#endif
}
void app_main(void)
{
app_init_once(); // one time app init
example_wifi_init_once(); // one time wifi init
while (true) {
init_cp_error_detection();
ESP_LOGI(TAG, "init ESP-Hosted");
app_esp_hosted_init();
// wait until ESP-Hosted is up before continuing
xSemaphoreTake(sem_hosted_is_up, portMAX_DELAY);
ESP_LOGI(TAG, "ESP-Hosted is ready");
bool esp_hosted_is_okay = app_esp_hosted_verify_up();
if (esp_hosted_is_okay) {
// configure heartbeat
if (ESP_OK != esp_hosted_configure_heartbeat(true, HEARTBEAT_INTERVAL_SEC)) {
ESP_LOGE(TAG, "failed to set heartbeat");
}
#if ENABLE_HEARTBEAT_MONITOR
if (ESP_OK != app_setup_heartbeat_monitor()) {
ESP_LOGE(TAG, "failed to setup heartbeat monitor");
}
#endif
// connect to an AP
example_wifi_init_sta();
// here, you can start a thread to do data transfers with the AP
// we wait until we encounter an error and need to reset the transport
EventBits_t bits = xEventGroupWaitBits(s_esp_hosted_event_group,
ESP_HOSTED_RESET_BIT,
pdTRUE, // clear on exit
pdTRUE, // wait for all bits to be set
portMAX_DELAY);
if (bits & !ESP_HOSTED_RESET_BIT) {
ESP_LOGW(TAG, "*** Unexpected group event event bits ***");
return;
}
// here, you should tell the data transfer thread to abort data transfer
} else {
ESP_LOGE(TAG, "Failed to start up ESP-Hosted");
return;
}
/**
* should only come here if ESP-Hosted encountered a transport error
* or failed to connect
*/
// connection to ESP-Hosted has now failed
ESP_LOGI(TAG, "heartbeat timeout, transport failure or unexpected INIT event: reinit Hosted");
// restart the ESP-Hosted transport
resetting_esp_hosted_transport = true;
bool result = app_do_cp_recovery();
resetting_esp_hosted_transport = false;
deinit_cp_error_detection();
if (!result) {
// do cp recovery failed
ESP_LOGI(TAG, "Did not recover from co-processor failure. Exiting app");
break;
}
ESP_LOGI(TAG, "restarting ESP-Hosted");
}
}
@@ -0,0 +1,240 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* WiFi station Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "app_common.h"
/* The examples use WiFi configuration that you can set via project configuration menu
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY
#if CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HUNT_AND_PECK
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
#define EXAMPLE_H2E_IDENTIFIER ""
#elif CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HASH_TO_ELEMENT
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#elif CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#endif
#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi station";
static int s_retry_num = 0;
static bool do_not_reconnect = false;
static esp_event_handler_instance_t instance_any_id = NULL;
static esp_event_handler_instance_t instance_got_ip = NULL;
static esp_netif_t *sta_netif = NULL;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (!do_not_reconnect) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
ESP_LOGI(TAG,"connect to the AP fail");
}
} else {
ESP_LOGI(TAG,"do not reconnect");
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
/**
* these events will inform netif to close its current network connection
* so it is ready for the next sta connection
*/
void example_wifi_sta_netif_close(void)
{
// broadcast a wi-fi disconnect event so NETIF is aware that the connection went down
do_not_reconnect = true; // we don't try to reconnect
wifi_event_sta_disconnected_t event = { 0 };
event.reason = WIFI_REASON_CONNECTION_FAIL;
esp_event_post(WIFI_EVENT,
WIFI_EVENT_STA_DISCONNECTED,
&event,
sizeof(wifi_event_sta_disconnected_t),
portMAX_DELAY);
// sleep for a while to give NETIF time to react to the event
vTaskDelay(100 / portTICK_PERIOD_MS);
// this informs the event handler not to try to reconnect
do_not_reconnect = false;
// broadcast a wi-fi stop event so NETIF is aware the station has stopped
esp_event_post(WIFI_EVENT,
WIFI_EVENT_STA_STOP,
NULL,
0,
portMAX_DELAY);
// sleep for a while to give NETIF time to react to the event
vTaskDelay(100 / portTICK_PERIOD_MS);
}
void example_wifi_deinit_sta(void)
{
if (sta_netif) {
esp_netif_destroy_default_wifi(sta_netif);
sta_netif = NULL;
}
if (!instance_any_id) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT,
ESP_EVENT_ANY_ID,
instance_any_id));
instance_any_id = NULL;
}
if (!instance_got_ip) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT,
ESP_EVENT_ANY_ID,
instance_got_ip));
instance_got_ip = NULL;
}
vEventGroupDelete(s_wifi_event_group);
}
void example_wifi_init_once(void)
{
ESP_ERROR_CHECK(esp_netif_init());
}
void example_wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
if (!instance_any_id) {
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
}
if (!instance_got_ip) {
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
}
if (!sta_netif) {
sta_netif = esp_netif_create_default_wifi_sta();
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (password len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = ESP_WIFI_SAE_MODE,
.sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,
#ifdef CONFIG_ESP_WIFI_WPA3_COMPATIBLE_SUPPORT
.disable_wpa3_compatible_mode = 0,
#endif
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
@@ -0,0 +1,2 @@
### Disable transport option to restart on failure, so application will handle transport failures
CONFIG_ESP_HOSTED_TRANSPORT_RESTART_ON_FAILURE=n