Wi-Fi data path FIXED: esp_hosted 2.12 on both chips + C6 SDIO auto-OTA
Root cause of the dead data path (assoc/scan/RPC fine, zero data frames): esp-hosted 1.4.x is formally incompatible with IDF 5.5 (esp-hosted-mcu#47) and the factory C6 slave firmware was ancient (couldn't even answer a version RPC). Waveshare's examples pin 1.4.* — do not follow them. Fix: - host: espressif/esp_hosted ^2.12 (+ esp_wifi_remote 1.6) - slave: 2.12.11 network_adapter.bin embedded in the app (c6_ota.c streams it to the C6 over the SDIO RPC channel at boot when the reported version is < 2.x; ~10s, no wires, idempotent) - RAM diet for hosted 2.x's footprint (MEMPOOL_PREFER_SPIRAM, reduced WIFI_RMT buffers) — without it internal SRAM famine boot-loops in xTaskCreateStaticPinnedToCore before app_main - conservative 20MHz SDIO clock for first verified data path Verified on hardware: DHCP lease (even that healed), 8/8 pings to router and tower-of-joy at 1-5ms, WebSocket to the gateway connected, device registered at /devices as desklock-p4 with fw version. Also: wifi_diag.c L1 SoftAP diagnostic mode (DESKLOCK-DIAG) with review fixes, boot ping ladder in net.c, static-IP fallback, face_status() line, docs for the whole failure taxonomy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,14 @@ idf_component_register(
|
||||
"audio.c"
|
||||
"net.c"
|
||||
"gw_client.c"
|
||||
"c6_ota.c"
|
||||
"wifi_diag.c"
|
||||
"fonts/font_face_140.c"
|
||||
"fonts/font_rain_22.c"
|
||||
"fonts/font_rage_64.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES nvs_flash esp_wifi esp_event esp_netif json esp_timer esp_app_format
|
||||
EMBED_FILES "c6_slave.bin"
|
||||
PRIV_REQUIRES nvs_flash esp_wifi esp_event esp_netif json esp_timer esp_app_format esp_http_server
|
||||
)
|
||||
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE LV_LVGL_H_INCLUDE_SIMPLE)
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/* One-shot ESP32-C6 slave firmware update over the SDIO RPC channel.
|
||||
*
|
||||
* The factory C6 firmware's data path never carried a frame (control RPC
|
||||
* works; all data traffic dies). This streams a slave image built from the
|
||||
* exact same esp_hosted version as the host driver, using the RPC OTA
|
||||
* primitives that ride the (working) control path — no network involved.
|
||||
*
|
||||
* Remove the c6_ota_start() call from app_main once the radio is proven.
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_hosted.h"
|
||||
|
||||
#include "desklock.h"
|
||||
|
||||
static const char *TAG = "c6ota";
|
||||
|
||||
extern int rpc_ota_begin(void);
|
||||
extern int rpc_ota_write(uint8_t *ota_data, uint32_t ota_data_len);
|
||||
extern int rpc_ota_end(void);
|
||||
|
||||
extern const uint8_t slave_bin_start[] asm("_binary_c6_slave_bin_start");
|
||||
extern const uint8_t slave_bin_end[] asm("_binary_c6_slave_bin_end");
|
||||
|
||||
#define CHUNK 1400
|
||||
|
||||
static void log_c6_version(const char *when)
|
||||
{
|
||||
esp_hosted_coprocessor_fwver_t v = { 0 };
|
||||
if (esp_hosted_get_coprocessor_fwversion(&v) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "C6 fw %s: %u.%u.%u", when,
|
||||
(unsigned)v.major1, (unsigned)v.minor1, (unsigned)v.patch1);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "C6 fw %s: version query failed", when);
|
||||
}
|
||||
}
|
||||
|
||||
static void c6_ota_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
vTaskDelay(pdMS_TO_TICKS(8000)); /* let the hosted transport settle */
|
||||
esp_hosted_coprocessor_fwver_t v = { 0 };
|
||||
if (esp_hosted_get_coprocessor_fwversion(&v) == ESP_OK &&
|
||||
v.major1 >= 2) {
|
||||
ESP_LOGI(TAG, "C6 fw already 2.x — no OTA needed");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
log_c6_version("before");
|
||||
|
||||
size_t total = slave_bin_end - slave_bin_start;
|
||||
ESP_LOGI(TAG, "C6 OTA start: %u bytes", (unsigned)total);
|
||||
if (rpc_ota_begin() != 0) {
|
||||
ESP_LOGE(TAG, "ota_begin FAILED (factory partition table may lack OTA slots)");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
size_t off = 0;
|
||||
while (off < total) {
|
||||
uint32_t n = (total - off) < CHUNK ? (uint32_t)(total - off) : CHUNK;
|
||||
if (rpc_ota_write((uint8_t *)(slave_bin_start + off), n) != 0) {
|
||||
ESP_LOGE(TAG, "ota_write FAILED at %u/%u", (unsigned)off, (unsigned)total);
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
off += n;
|
||||
if ((off / CHUNK) % 100 == 0) {
|
||||
ESP_LOGI(TAG, "C6 OTA %u/%u", (unsigned)off, (unsigned)total);
|
||||
}
|
||||
}
|
||||
if (rpc_ota_end() == 0) {
|
||||
ESP_LOGI(TAG, "C6 OTA SUCCESS — restarting both chips");
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "ota_end FAILED");
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void c6_ota_start(void)
|
||||
{
|
||||
xTaskCreate(c6_ota_task, "c6ota", 6144, NULL, 4, NULL);
|
||||
}
|
||||
Binary file not shown.
@@ -20,7 +20,8 @@ typedef enum {
|
||||
void face_init(void);
|
||||
void face_set(face_state_t state); /* safe from any task */
|
||||
face_state_t face_get(void);
|
||||
void face_activity(void); /* reset the power ladder to active */
|
||||
void face_activity(void);
|
||||
void face_status(const char *text); /* bottom status line (diag/boot) */ /* reset the power ladder to active */
|
||||
|
||||
/* audio.c */
|
||||
void audio_init(void);
|
||||
@@ -41,6 +42,12 @@ bool gw_connected(void);
|
||||
void gw_send_text(const char *json);
|
||||
void gw_send_bin(const uint8_t *data, size_t len);
|
||||
|
||||
/* wifi_diag.c */
|
||||
void wifi_diag_start(void);
|
||||
|
||||
/* c6_ota.c */
|
||||
void c6_ota_start(void);
|
||||
|
||||
/* desklock_main.c */
|
||||
void app_on_touch(void);
|
||||
void app_on_playback_done(void);
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
#include "desklock.h"
|
||||
|
||||
/* 1 = L1 wifi driver diagnosis (SoftAP, no STA/gateway). 0 = normal app. */
|
||||
#define WIFI_DIAG_MODE 0
|
||||
|
||||
static const char *TAG = "desklock";
|
||||
|
||||
static bool s_talking;
|
||||
@@ -40,7 +43,9 @@ void app_on_touch(void)
|
||||
|
||||
void app_on_playback_done(void)
|
||||
{
|
||||
face_set(FACE_IDLE);
|
||||
#if !WIFI_DIAG_MODE
|
||||
face_set(FACE_IDLE); /* in diag mode the status line must stay visible */
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
@@ -54,7 +59,12 @@ void app_main(void)
|
||||
audio_init();
|
||||
audio_play_gong();
|
||||
|
||||
#if WIFI_DIAG_MODE
|
||||
wifi_diag_start();
|
||||
#else
|
||||
net_start();
|
||||
c6_ota_start(); /* one-shot C6 radio firmware update — remove once proven */
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "DeskLock up");
|
||||
}
|
||||
|
||||
@@ -450,6 +450,14 @@ face_state_t face_get(void)
|
||||
return F.state;
|
||||
}
|
||||
|
||||
void face_status(const char *text)
|
||||
{
|
||||
bsp_display_lock(UINT32_MAX);
|
||||
lv_label_set_text(F.status, text);
|
||||
lv_obj_clear_flag(F.status, LV_OBJ_FLAG_HIDDEN);
|
||||
bsp_display_unlock();
|
||||
}
|
||||
|
||||
void face_activity(void)
|
||||
{
|
||||
F.last_activity = now_ms();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies:
|
||||
idf: ">=5.4"
|
||||
waveshare/esp32_p4_wifi6_touch_lcd_xc: "^3.0.1"
|
||||
espressif/esp_wifi_remote: "0.14.*"
|
||||
espressif/esp_hosted: "1.4.*"
|
||||
espressif/esp_wifi_remote: "*"
|
||||
espressif/esp_hosted: "^2.12"
|
||||
espressif/esp_websocket_client: "~1.3.0"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "esp_netif_sntp.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "ping/ping_sock.h"
|
||||
|
||||
#include "desklock.h"
|
||||
#include "secrets.h"
|
||||
@@ -17,6 +18,9 @@ static const char *TAG = "net";
|
||||
static esp_netif_t *s_netif;
|
||||
static volatile bool s_got_ip;
|
||||
|
||||
static void start_ping_diag(void);
|
||||
static void ping_diag_task(void *arg);
|
||||
|
||||
/* Router DHCP has been observed to go silent for this MAC (flap throttling).
|
||||
* After 15 s without a lease, claim the previously granted address statically. */
|
||||
static void static_ip_fallback(void *arg)
|
||||
@@ -40,6 +44,7 @@ static void static_ip_fallback(void *arg)
|
||||
esp_sntp_config_t sntp = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
|
||||
esp_netif_sntp_init(&sntp);
|
||||
gw_start();
|
||||
start_ping_diag();
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -84,9 +89,72 @@ static void wifi_event(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
esp_sntp_config_t sntp = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
|
||||
esp_netif_sntp_init(&sntp);
|
||||
gw_start();
|
||||
start_ping_diag();
|
||||
}
|
||||
}
|
||||
|
||||
static void ping_ok(esp_ping_handle_t h, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
uint32_t seq = 0, ms = 0;
|
||||
ip_addr_t target;
|
||||
esp_ping_get_profile(h, ESP_PING_PROF_SEQNO, &seq, sizeof(seq));
|
||||
esp_ping_get_profile(h, ESP_PING_PROF_TIMEGAP, &ms, sizeof(ms));
|
||||
esp_ping_get_profile(h, ESP_PING_PROF_IPADDR, &target, sizeof(target));
|
||||
ESP_LOGI(TAG, "PING %s seq=%u %ums OK", ipaddr_ntoa(&target), (unsigned)seq, (unsigned)ms);
|
||||
}
|
||||
|
||||
static void ping_timeout(esp_ping_handle_t h, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
uint32_t seq = 0;
|
||||
ip_addr_t target;
|
||||
esp_ping_get_profile(h, ESP_PING_PROF_SEQNO, &seq, sizeof(seq));
|
||||
esp_ping_get_profile(h, ESP_PING_PROF_IPADDR, &target, sizeof(target));
|
||||
ESP_LOGW(TAG, "PING %s seq=%u TIMEOUT", ipaddr_ntoa(&target), (unsigned)seq);
|
||||
}
|
||||
|
||||
static void ping_target(const char *ip_str)
|
||||
{
|
||||
esp_ping_config_t pc = ESP_PING_DEFAULT_CONFIG();
|
||||
pc.count = 4;
|
||||
pc.interval_ms = 500;
|
||||
ipaddr_aton(ip_str, &pc.target_addr);
|
||||
esp_ping_callbacks_t cbs = {
|
||||
.on_ping_success = ping_ok,
|
||||
.on_ping_timeout = ping_timeout,
|
||||
};
|
||||
esp_ping_handle_t h;
|
||||
if (esp_ping_new_session(&pc, &cbs, &h) == ESP_OK) {
|
||||
esp_ping_start(h);
|
||||
}
|
||||
}
|
||||
|
||||
static void start_ping_diag(void)
|
||||
{
|
||||
static bool started;
|
||||
if (!started) {
|
||||
started = true;
|
||||
xTaskCreate(ping_diag_task, "pingdiag", 4096, NULL, 3, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/* boot diagnosis: can the device reach the router at all? then the server? */
|
||||
static void ping_diag_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
ESP_LOGI(TAG, "== ping diagnosis: router ==");
|
||||
ping_target("192.168.86.1");
|
||||
vTaskDelay(pdMS_TO_TICKS(6000));
|
||||
ESP_LOGI(TAG, "== ping diagnosis: tower-of-joy ==");
|
||||
ping_target("192.168.86.149");
|
||||
vTaskDelay(pdMS_TO_TICKS(6000));
|
||||
ESP_LOGI(TAG, "== slave wifi statistics dump ==");
|
||||
esp_wifi_statis_dump(0xFFFFFFFF);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void net_start(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/* L1 driver diagnosis: the device becomes its own access point.
|
||||
*
|
||||
* Ladder (docs/architecture.md): prove each layer before climbing.
|
||||
* L0 SDIO control path — proven (RPC works)
|
||||
* L1 wifi data path AT ALL — this file: SoftAP + DHCP + HTTP, no external network
|
||||
* L2 STA to any network — phone hotspot
|
||||
* L3 STA to "Outside" — the real network
|
||||
* L4 gateway + Tatlock — only after L3
|
||||
*
|
||||
* Join DESKLOCK-DIAG (pass desklock123) with a phone:
|
||||
* - association -> log + on-screen counter
|
||||
* - DHCP lease from us -> IP_EVENT_AP_STAIPASSIGNED = SDIO data path PROVEN
|
||||
* - http://192.168.4.1/ -> full TCP round trip PROVEN
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "desklock.h"
|
||||
|
||||
static const char *TAG = "wifidiag";
|
||||
|
||||
static int s_assoc;
|
||||
static int s_leases;
|
||||
static int s_http_hits;
|
||||
|
||||
static void show(void)
|
||||
{
|
||||
char line[96];
|
||||
snprintf(line, sizeof(line), "DIAG AP: assoc %d | lease %d | http %d",
|
||||
s_assoc, s_leases, s_http_hits);
|
||||
face_status(line);
|
||||
ESP_LOGI(TAG, "%s", line);
|
||||
}
|
||||
|
||||
static void diag_event(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
(void)arg;
|
||||
(void)data;
|
||||
if (base == WIFI_EVENT && id == WIFI_EVENT_AP_STACONNECTED) {
|
||||
s_assoc++;
|
||||
ESP_LOGI(TAG, "L1a: station ASSOCIATED (radio + auth OK)");
|
||||
} else if (base == WIFI_EVENT && id == WIFI_EVENT_AP_STADISCONNECTED) {
|
||||
if (s_assoc > 0) {
|
||||
s_assoc--;
|
||||
}
|
||||
} else if (base == IP_EVENT && id == IP_EVENT_AP_STAIPASSIGNED) {
|
||||
s_leases++;
|
||||
ESP_LOGI(TAG, "L1b: DHCP LEASE ASSIGNED — SDIO DATA PATH PROVEN");
|
||||
}
|
||||
show();
|
||||
}
|
||||
|
||||
static esp_err_t hello_get(httpd_req_t *req)
|
||||
{
|
||||
s_http_hits++;
|
||||
ESP_LOGI(TAG, "L1c: HTTP GET — FULL TCP ROUND TRIP PROVEN");
|
||||
show();
|
||||
return httpd_resp_sendstr(req, "desklock diag: data path OK\n");
|
||||
}
|
||||
|
||||
void wifi_diag_start(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_ap();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, diag_event, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, diag_event, NULL));
|
||||
|
||||
wifi_config_t ap = {
|
||||
.ap = {
|
||||
.ssid = "DESKLOCK-DIAG",
|
||||
.password = "desklock123",
|
||||
.authmode = WIFI_AUTH_WPA2_PSK,
|
||||
.max_connection = 4,
|
||||
.channel = 6,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t hcfg = HTTPD_DEFAULT_CONFIG();
|
||||
if (httpd_start(&server, &hcfg) == ESP_OK) {
|
||||
static const httpd_uri_t root = { .uri = "/", .method = HTTP_GET, .handler = hello_get };
|
||||
httpd_register_uri_handler(server, &root);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "httpd_start failed");
|
||||
face_status("DIAG: httpd FAILED");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "L1 diag AP up: join DESKLOCK-DIAG / desklock123, open http://192.168.4.1/");
|
||||
face_status("DIAG: join DESKLOCK-DIAG");
|
||||
}
|
||||
@@ -27,7 +27,31 @@ CONFIG_LV_FONT_MONTSERRAT_48=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_28=y
|
||||
|
||||
# ESP-Hosted C6 radio
|
||||
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n
|
||||
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
|
||||
|
||||
# custom fonts are uncompressed, but enable the decoder as belt-and-braces
|
||||
CONFIG_LV_USE_FONT_COMPRESSED=y
|
||||
|
||||
# ESP-Hosted board variant + wifi-remote data-path tuning (from factory brookesia config;
|
||||
# without these the RPC control path works but data frames never flow)
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
|
||||
CONFIG_ESP_HOSTED_CP_TARGET_ESP32C6=y
|
||||
CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=8
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=24
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=24
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=16
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=16
|
||||
|
||||
# hosted task stacks MUST stay in internal RAM (PSRAM stacks assert on P4)
|
||||
CONFIG_ESP_HOSTED_DFLT_TASK_FROM_SPIRAM=n
|
||||
|
||||
# hosted 2.x: keep internal RAM for task stacks; buffers go to PSRAM
|
||||
CONFIG_ESP_HOSTED_MEMPOOL_PREFER_SPIRAM=y
|
||||
# conservative SDIO clock for first data-path proof (raise after verification)
|
||||
CONFIG_ESP_HOSTED_SDIO_CLOCK_FREQ_KHZ=20000
|
||||
|
||||
# headroom for diag event handlers that touch LVGL
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
|
||||
|
||||
Reference in New Issue
Block a user