<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport/main/mdns_service.c, branch v1.3.2</title>
<subtitle>ESP32-POE Scrypted viewport (private)
</subtitle>
<id>https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=v1.3.2</id>
<link rel='self' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=v1.3.2'/>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/'/>
<updated>2026-06-15T01:11:21+00:00</updated>
<entry>
<title>Default name = MAC; brief wake on boot; drop dead touch defences</title>
<updated>2026-06-15T01:11:21+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-15T01:11:21+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=865c4859d710870245ae7954e729edcaf442a921'/>
<id>urn:sha1:865c4859d710870245ae7954e729edcaf442a921</id>
<content type='text'>
- Seed viewport_name with the full base MAC, colons stripped
  (e8:f6:0a:e0:90:94 → "e8f60ae09094"). Stable across reboots, globally
  unique, 12 alphanumeric chars — well inside the mDNS hostname limit
  even with the "viewport-" prefix.
- Add a mac_str field to viewport_state and expose it as the new "mac"
  key in GET /state and as a "mac" line on the info screen.
- "Configured" no longer requires a viewport_name (it always has the
  MAC default); the scrypted URL alone gates outbound POSTs and the
  loading-vs-info screen choice.
- Strip viewport_name[0] fallbacks in http_api / mdns / local_screens
  now that the field is never empty.
- Replace the wake-on-boot-stays-on behaviour with a ~600 ms flash
  followed by sleep — long enough for the FT5426 touch IC to come out
  of its initial unresponsive state, short enough that an idle device
  doesn't burn the backlight.
- Drop the touch defensive cruft added when we were chasing a PoE-power
  theory: the DEV_MODE=0x00 write at init, the touch_reset_pulse() call
  at init, and the runtime wedge-self-heal in the polling loop didn't
  actually fix anything — the wake-on-boot flash did. Also delete the
  display_touch_reset_pulse() helper since it now has zero callers.
</content>
</entry>
<entry>
<title>Tidy: delete dead code, inline single-use helpers, fix double-lock</title>
<updated>2026-06-14T23:30:44+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-14T23:30:44+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=186fd85422f99dfb59d89ae9338dc50627f7c3e5'/>
<id>urn:sha1:186fd85422f99dfb59d89ae9338dc50627f7c3e5</id>
<content type='text'>
Code-review pass after the M5 colour fix. -126 net lines (-215 / +89).

Dead code removed:
- display_fill, display_test_pattern — M3 bring-up self-tests, no callers
- net_eth_is_up — no callers
- nvs_config_reset — no callers (factory-reset gesture was removed earlier;
  NVS wipe now goes through `idf.py erase-flash`)

Helpers inlined or collapsed (each had one call site):
- state_name / orientation_name — ternaries at the cJSON site
- fmt_bytes / fmt_uptime — inline scope in local_screens_show_info
- expected_dims (http_api) + effective_dims (local_screens) — merged into
  viewport_state_effective_dims() in viewport_state, one canonical source

state_machine cleanup:
- arm_idle_timer_unlocked / disarm_idle_timer collapsed to one arm_idle_timer(ms)
  that takes the snapshotted timeout (ms==0 disables). Removes the misleading
  name and the second viewport_state lock acquisition per painted frame.
- state_machine_set + state_machine_frame_painted now snapshot state + idle_ms
  under one lock.

mdns_service cleanup:
- snapshot_state / apply_hostname / apply_txt collapsed into a single
  apply_state(include_hostname) helper; mdns_service_start grabs hostname
  once for hostname_set + log instead of going under the lock three times.

http_api cleanup:
- POST /config brightness-changed path uses the local `bright` it already
  validated instead of re-locking + re-reading st-&gt;brightness.
- Trimmed verbose bring-up comments (stack-size justification, M5 saga,
  DSI shadow struct) to one line each — kept the load-bearing facts.

Smoke-tested on hardware: boot clean, display + JPEG + touch all up
([DJT]), info screen renders, no crashes.
</content>
</entry>
<entry>
<title>M1+M2 hardware bring-up: 3 fixes + elegant subsystem degradation</title>
<updated>2026-06-14T17:11:43+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-14T17:11:43+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=220ee4cdc9b372e2a2b41ee48471ad0893b3a68a'/>
<id>urn:sha1:220ee4cdc9b372e2a2b41ee48471ad0893b3a68a</id>
<content type='text'>
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
     -&gt; 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 -&gt; hostname -&gt; service_add -&gt; 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.
</content>
</entry>
<entry>
<title>M2: HTTP server + mDNS service discovery + shared state</title>
<updated>2026-06-14T03:08:02+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-14T03:08:02+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=4756daedaefd18919e96fadaabc7f1a00b383b4c'/>
<id>urn:sha1:4756daedaefd18919e96fadaabc7f1a00b383b4c</id>
<content type='text'>
- viewport_state.{h,c}: shared state struct (config, run-state, counters,
  orientation, timestamps) behind a FreeRTOS mutex. Modules update it
  under viewport_state_lock(); GET /state serializes a snapshot.
- http_api.{h,c}: starts esp_http_server on :80, registers GET /state.
  Returns the full JSON shape from the spec (name, version, configured,
  state, uptime_ms, last_frame_ms_ago, frames_received, decode_errors,
  state_post_failures, resolution, ip, free_heap, free_psram).
- mdns_service.{h,c}: mdns_init + advertise _scrypted-viewport._tcp.local
  on :80 with version/resolution/orientation/name TXT records.
  mdns_service_refresh() reapplies hostname + TXT after /config writes
  (called from M4 onward).
- main/CMakeLists.txt: add esp_http_server, esp_timer, json, mdns to
  REQUIRES. espressif/mdns added as managed component via
  main/idf_component.yml.

app_main calls viewport_state_init early so any module can read defaults
before /config arrives.

Also adds TESTING.md tracking per-milestone verification status and an
integration test plan to run after M9 (races, failure modes, longevity,
power cycles, negative protocol, multi-viewport).

M1 promoted to "🟡 builds clean" in TESTING.md; combined HW verification
of M1+M2 will be one flash session. Build verified against ESP-IDF 5.4
for target esp32p4 (binary ~550 KB).
</content>
</entry>
</feed>
