diff options
| author | Luke Hoersten <[email protected]> | 2026-06-14 12:11:43 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-06-14 12:11:43 -0500 |
| commit | 220ee4cdc9b372e2a2b41ee48471ad0893b3a68a (patch) | |
| tree | 50129bdfb13a298659025d3db31ca64e4241f1fe /main | |
| parent | b22d05901dee4000ee012af470ddf898579257ae (diff) | |
M1+M2 hardware bring-up: 3 fixes + elegant subsystem degradation
First flash to real hardware (Waveshare ESP32-P4-ETH on USB power,
no LAN cable, no panel attached) exposed three bugs and the need to
degrade gracefully when peripherals are missing.
1. net_eth.c: RXD0 / RXD1 GPIO swap. ESP32-P4's EMAC iomux table
(components/soc/esp32p4/emac_periph.c) fixes RXD0 to GPIO 29 and
RXD1 to GPIO 30. The Waveshare-wiki/ESPHome research had them
transposed. Symptom was:
E (esp.emac.gpio): invalid RXD0 GPIO number
E (esp.emac): esp_eth_mac_new_esp32 failed
-> ESP_ERROR_CHECK abort
Fix: swap the two #defines. CRS_DV / TXD0 / TXD1 / TX_EN / REF_CLK
pinout was already correct.
2. mdns_service.c: mdns_service_add() rejects with INVALID_ARG when
the hostname isn't set yet (see mdns_responder.c:771 — first guard
in mdns_service_add_for_host). We were setting hostname AFTER
service_add inside apply_records(). Restructure mdns_service_start
to: init -> hostname -> service_add -> txt. apply_hostname() and
apply_txt() helpers reuse the same snapshot under viewport_state's
mutex; mdns_service_refresh() re-applies both.
3. app_main.c: every subsystem now best-effort instead of
ESP_ERROR_CHECK abort. A single missing peripheral can't take down
the rest of the firmware. End-of-boot summary line:
boot complete — subsystems [EMHdJ-B] ip=(no link)
Uppercase letter = up, lowercase = down. E=Ethernet M=mDNS H=HTTP
D=Display J=JPEG T=Touch B=BootButton. Touch shows '-' when display
didn't come up (it shares the panel I2C bus). On a stripped board
(just ESP32-P4-ETH on USB, no panel, no LAN) the line above prints
and the device serves /state, /config, and mDNS over the loopback;
plugging Ethernet in later picks up DHCP without a reboot.
Side-effects: the DHCP timeout in app_main shortened from 30s to 15s
so a no-cable boot finishes quickly. Reaching the "ready" state with
no link is fine — the driver keeps the link-up event handler armed and
gets the IP whenever a cable appears.
Verified on hardware (bare ESP32-P4-ETH, no LAN, no panel):
- ESP-IDF v5.4.1, esp32p4, 32 MB PSRAM detected, 16 MB flash config
(actual 32 MB silkscreen — keeping 16 MB until access-beyond-16MB
support lands in flash driver).
- Ethernet driver started — MAC e8:f6:0a:e0:90:94.
- mDNS up advertising viewport.local on _scrypted-viewport._tcp:80.
- HTTP up listening :80.
- JPEG decoder ready.
- Display reports "panel MCU @0x45 unreachable" — exactly correct
for no-panel state.
- Touch skipped as designed.
- BOOT button registered on GPIO 0 (still a guess; harmless if wrong).
This is enough to flip M1's "code" status to ✅ on hardware once
Ethernet is plugged in. M2 (mDNS browse + GET /state) ready to verify.
Diffstat (limited to 'main')
| -rw-r--r-- | main/app_main.c | 84 | ||||
| -rw-r--r-- | main/mdns_service.c | 74 | ||||
| -rw-r--r-- | main/net_eth.c | 4 |
3 files changed, 104 insertions, 58 deletions
diff --git a/main/app_main.c b/main/app_main.c index 4f7a949..0897649 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -18,6 +18,16 @@ static const char *TAG = "viewport"; +// Log a status line for one subsystem and accumulate a one-letter flag into +// `status` (uppercase = up, lowercase = degraded/unavailable). Lets the +// boot log end with a single line summarizing what's actually live, so +// running with no LAN / no panel is a self-describing degraded mode rather +// than a panic. +static inline void mark(esp_err_t err, char up, char *slot) +{ + *slot = (err == ESP_OK) ? up : (char)(up + 0x20); // 'A' -> 'a' on failure +} + void app_main(void) { ESP_ERROR_CHECK(nvs_flash_init()); @@ -28,57 +38,63 @@ void app_main(void) nvs_config_load(); // apply persisted config over defaults (best-effort) ESP_LOGI(TAG, "Scrypted Viewport boot (v%s)", VIEWPORT_VERSION); - ESP_ERROR_CHECK(net_eth_init()); - if (net_eth_wait_for_ip(30 * 1000) == ESP_OK) { - ESP_LOGI(TAG, "online at %s", net_eth_get_ip_str()); - } else { - ESP_LOGW(TAG, "no DHCP lease after 30s, will keep retrying in the background"); + // ------------------------------------------------------------------ + // Networking. Driver starts even with no cable plugged in; we just + // don't get a DHCP lease. mDNS + HTTP advertise / bind anyway and will + // start serving the moment the link comes up. + // ------------------------------------------------------------------ + char flags[8] = { '-','-','-','-','-','-','-', 0 }; // E M H D J T B + esp_err_t eth_err = net_eth_init(); + mark(eth_err, 'E', &flags[0]); + + bool got_ip = false; + if (eth_err == ESP_OK) { + got_ip = (net_eth_wait_for_ip(15 * 1000) == ESP_OK); + if (got_ip) { + ESP_LOGI(TAG, "online at %s", net_eth_get_ip_str()); + } else { + ESP_LOGW(TAG, "no DHCP lease after 15s — Ethernet driver will keep " + "retrying in the background; mDNS + HTTP up anyway"); + } } ESP_ERROR_CHECK(state_machine_init()); ESP_ERROR_CHECK(state_client_init()); - ESP_ERROR_CHECK(mdns_service_start()); - ESP_ERROR_CHECK(http_api_start()); + mark(mdns_service_start(), 'M', &flags[1]); + mark(http_api_start(), 'H', &flags[2]); - // Display is best-effort — a missing/miswired panel must not kill - // networking + /state. - if (display_init() == ESP_OK) { + // ------------------------------------------------------------------ + // Display + I²C-bound peripherals. Missing/miswired panel must not + // kill networking + /state. Failures here log a warning and continue. + // ------------------------------------------------------------------ + esp_err_t dsp_err = display_init(); + mark(dsp_err, 'D', &flags[3]); + if (dsp_err == ESP_OK) { local_screens_init(); - - // Reconcile panel with current run-state: - // UNCONFIGURED -> IP screen (so operator can register from Scrypted) - // ASLEEP -> backlight off (configured device booted asleep) - // AWAKE -> leave on (not reached on fresh boot) viewport_state_lock(); viewport_run_state_t s = viewport_state_get()->state; viewport_state_unlock(); - if (s == VIEWPORT_STATE_ASLEEP) { display_sleep(); - ESP_LOGI(TAG, "display up — configured, backlight off (asleep)"); } else { local_screens_show_ip(); - ESP_LOGI(TAG, "display up — IP screen (unconfigured)"); } - } else { - ESP_LOGW(TAG, "display init failed — continuing without panel"); } - // JPEG decoder is the M5 dependency for POST /frame. Best-effort. - if (jpeg_decoder_init() != ESP_OK) { - ESP_LOGW(TAG, "jpeg decoder init failed — /frame will be unavailable"); - } + mark(jpeg_decoder_init(), 'J', &flags[4]); - // Touch shares the panel I2C bus; only meaningful if display came up. - if (display_is_up()) { - if (touch_init() != ESP_OK) { - ESP_LOGW(TAG, "touch init failed — taps won't work"); - } + if (dsp_err == ESP_OK) { + mark(touch_init(), 'T', &flags[5]); + } else { + ESP_LOGI(TAG, "touch skipped (display not up)"); } - // BOOT button — short press = IP overlay, hold 5s = factory reset. - // GPIO is a guess until confirmed against the Waveshare schematic. - if (button_init() != ESP_OK) { - ESP_LOGW(TAG, "BOOT button init failed — overlay + factory reset disabled"); - } + mark(button_init(), 'B', &flags[6]); + + // ------------------------------------------------------------------ + // Boot summary. Uppercase letter = subsystem live; lowercase = down. + // E=Ethernet M=mDNS H=HTTP D=Display J=JPEG T=Touch B=BootButton + // ------------------------------------------------------------------ + ESP_LOGI(TAG, "boot complete — subsystems [%s] ip=%s", + flags, got_ip ? net_eth_get_ip_str() : "(no link)"); } diff --git a/main/mdns_service.c b/main/mdns_service.c index 761ade5..d389161 100644 --- a/main/mdns_service.c +++ b/main/mdns_service.c @@ -14,52 +14,82 @@ static const char *SERVICE = "_scrypted-viewport"; static const char *PROTO = "_tcp"; static const uint16_t PORT = 80; -static esp_err_t apply_records(void) +// Pull the current hostname + TXT values from viewport_state under the lock. +static void snapshot_state(char *hostname, size_t host_cap, + const char **resolution, + const char **orientation, + char *name, size_t name_cap) { viewport_state_lock(); viewport_state_t *st = viewport_state_get(); - char hostname[80]; if (st->viewport_name[0]) { - snprintf(hostname, sizeof(hostname), "viewport-%s", st->viewport_name); + snprintf(hostname, host_cap, "viewport-%s", st->viewport_name); } else { - snprintf(hostname, sizeof(hostname), "viewport"); + snprintf(hostname, host_cap, "viewport"); } - - const char *resolution = viewport_state_resolution_str(); - const char *orientation = (st->orientation == VIEWPORT_ORIENTATION_PORTRAIT) - ? "portrait" : "landscape"; - const char *name_value = st->viewport_name[0] ? st->viewport_name : ""; + *resolution = viewport_state_resolution_str(); + *orientation = (st->orientation == VIEWPORT_ORIENTATION_PORTRAIT) + ? "portrait" : "landscape"; + snprintf(name, name_cap, "%s", st->viewport_name[0] ? st->viewport_name : ""); viewport_state_unlock(); +} + +static esp_err_t apply_hostname(void) +{ + char hostname[80]; + const char *resolution, *orientation; + char name[64]; + snapshot_state(hostname, sizeof(hostname), + &resolution, &orientation, + name, sizeof(name)); + return mdns_hostname_set(hostname); +} + +static esp_err_t apply_txt(void) +{ + char hostname[80]; + const char *resolution, *orientation; + char name[64]; + snapshot_state(hostname, sizeof(hostname), + &resolution, &orientation, + name, sizeof(name)); mdns_txt_item_t txt[] = { { "version", VIEWPORT_VERSION }, { "resolution", resolution }, { "orientation", orientation }, - { "name", name_value }, + { "name", name }, }; - const size_t txt_count = sizeof(txt) / sizeof(txt[0]); - - ESP_RETURN_ON_ERROR(mdns_hostname_set(hostname), TAG, "hostname_set"); - ESP_RETURN_ON_ERROR(mdns_service_txt_set(SERVICE, PROTO, txt, txt_count), - TAG, "txt_set"); - - ESP_LOGI(TAG, "advertising %s.local on %s.%s.local:%u", - hostname, SERVICE, PROTO, PORT); - return ESP_OK; + return mdns_service_txt_set(SERVICE, PROTO, txt, + sizeof(txt) / sizeof(txt[0])); } esp_err_t mdns_service_start(void) { - ESP_RETURN_ON_ERROR(mdns_init(), TAG, "mdns_init"); + // Order matters: mdns_service_add() rejects with INVALID_ARG if the + // hostname isn't set yet. So: init -> hostname -> service_add -> TXT. + ESP_RETURN_ON_ERROR(mdns_init(), TAG, "mdns_init"); + ESP_RETURN_ON_ERROR(apply_hostname(), TAG, "hostname_set"); ESP_RETURN_ON_ERROR( mdns_service_add(NULL, SERVICE, PROTO, PORT, NULL, 0), TAG, "service_add"); - return apply_records(); + ESP_RETURN_ON_ERROR(apply_txt(), TAG, "txt_set"); + + char hostname[80]; + const char *resolution, *orientation; + char name[64]; + snapshot_state(hostname, sizeof(hostname), + &resolution, &orientation, + name, sizeof(name)); + ESP_LOGI(TAG, "mDNS up — %s.local advertising %s.%s.local on :%u", + hostname, SERVICE, PROTO, PORT); + return ESP_OK; } esp_err_t mdns_service_refresh(void) { - return apply_records(); + ESP_RETURN_ON_ERROR(apply_hostname(), TAG, "hostname_set"); + return apply_txt(); } diff --git a/main/net_eth.c b/main/net_eth.c index 640d880..f4dad8f 100644 --- a/main/net_eth.c +++ b/main/net_eth.c @@ -24,8 +24,8 @@ #define PIN_PHY_TXD0 34 #define PIN_PHY_TXD1 35 #define PIN_PHY_CRS_DV 28 -#define PIN_PHY_RXD0 30 -#define PIN_PHY_RXD1 29 +#define PIN_PHY_RXD0 29 +#define PIN_PHY_RXD1 30 #define PIN_PHY_RESET 51 #define PHY_ADDR 1 |
