src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/app_main.c
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-14 12:11:43 -0500
committerLuke Hoersten <[email protected]>2026-06-14 12:11:43 -0500
commit220ee4cdc9b372e2a2b41ee48471ad0893b3a68a (patch)
tree50129bdfb13a298659025d3db31ca64e4241f1fe /main/app_main.c
parentb22d05901dee4000ee012af470ddf898579257ae (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/app_main.c')
-rw-r--r--main/app_main.c84
1 files changed, 50 insertions, 34 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)");
}