<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport/main/nvs_config.h, branch main</title>
<subtitle>ESP32-POE Scrypted viewport (private)
</subtitle>
<id>https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=main</id>
<link rel='self' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/'/>
<updated>2026-07-17T23:56:03+00:00</updated>
<entry>
<title>main: code-review fixes — brightness units, OTA stall cap, stats + locking</title>
<updated>2026-07-17T23:56:03+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-17T23:56:03+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=9d94f710f7b00d109bf6f6ee57996f43bce5c67f'/>
<id>urn:sha1:9d94f710f7b00d109bf6f6ee57996f43bce5c67f</id>
<content type='text'>
- display: s_last_pwm cached the raw 0-100 percentage at init but
  display_wake writes it straight to REG_PWM (0-255 duty) — first wake
  ran visibly dim until a /config brightness change. Shared pct_to_duty
  helper now converts in both paths.
- http_api: cap consecutive OTA recv timeouts (a stalled client spun
  forever holding s_ota_in_progress, wedging OTA until reboot); cap
  viewport name at 54 chars so viewport-&lt;name&gt; fits the 63-byte mDNS
  label; log mdns_service_refresh failures; /state builds JSON from a
  snapshot instead of holding the state lock across ~25 cJSON allocs;
  respond_400 delegates to respond_status.
- stream_server: bytes_in_window now counts painted frames only (frames
  discarded while asleep inflated the first post-wake window's MB/s,
  avg-jpeg and chunk/wire averages; recv_bytes folded in); so_rcvbuf
  carried into the stats snapshot under the mux; drop dead HEADER_BYTES;
  merge read_body_instrumented into read_n.
- state_machine: transition mutex serializes concurrent wake/sleep
  (display side-effects ran after the state lock dropped, so racing
  callers could leave the backlight contradicting st-&gt;state); wake-path
  placeholder paint now takes the decoder lock like the stream path
  (concurrent esp_lcd_panel_draw_bitmap from two tasks isn't safe).
- local_screens: overlay paint takes the decoder lock too; check the
  overlay timer create; panel dims from viewport_state.h.
- jpeg_decoder: try_lock before init returns busy instead of passing a
  NULL semaphore to xSemaphoreTake.
- net_eth: clear cached IP string on link down (stale /state + info).
- dead code: display_present_bgr888, TOUCH_FT5426_ADDR, touch s_task,
  JPEG_DECODER_MAX_OUTPUT_BYTES; doc drift in nvs_config.h /
  jpeg_decoder.h / app_main flag legend.
</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>M4: NVS-backed /config with partial updates + validation</title>
<updated>2026-06-14T03:42:14+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-14T03:42:14+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=e7feac61e5ea275f303574fedd942ebed8fc8e73'/>
<id>urn:sha1:e7feac61e5ea275f303574fedd942ebed8fc8e73</id>
<content type='text'>
nvs_config.{h,c} — persist the runtime config (viewport, scrypted,
idle_timeout_ms, orientation, brightness) under a single NVS namespace.
nvs_config_load() applies persisted values over the in-RAM defaults on
boot and flips state from UNCONFIGURED to ASLEEP once both name and
Scrypted URL are present. nvs_config_save() commits the whole record
atomically.

http_api.c — add GET /config and POST /config:
  - GET serializes viewport_state to the spec's JSON shape, with null
    for unset string fields and defaults filled in for the rest.
  - POST is partial: each field is optional; only present fields are
    validated and applied. Validation runs on a staged copy and errors
    short-circuit with 400 + reason before any state mutation, so a
    rejected request leaves the device untouched.
  - Validation rules: viewport non-empty &lt;64 chars; scrypted starts with
    http:// and &lt;256 chars; idle_timeout_ms 0 or &gt;=5000; orientation in
    {portrait,landscape}; brightness 0..100.
  - Side-effects fire after the lock + save: brightness change pushes
    PWM to the panel MCU; viewport/orientation change reapplies mDNS
    hostname + TXT.
  - 204 on success; 400 with a single-line reason on validation error.

app_main calls nvs_config_load() right after viewport_state_init(), so
mdns_service_start() and display_init() see the persisted hostname,
orientation, and brightness from the first packet/PWM.

Build clean against ESP-IDF 5.4 (binary ~620 KB).

TESTING.md M3 now documents the Hosyond jumper wiring (5V/GND/SDA=GPIO7/
SCL=GPIO8 from board to panel header; DSI FPC carries only the high-
speed lanes). M4 entry expands the validation matrix and side-effects
to verify.
</content>
</entry>
</feed>
