<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport/main/state_client.c, 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-06-14T04:01:23+00:00</updated>
<entry>
<title>M7: touch + outbound /state POST to Scrypted</title>
<updated>2026-06-14T04:01:23+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-14T04:01:23+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=1d6cbb222ced226fcb482c0b19130774d181c8f8'/>
<id>urn:sha1:1d6cbb222ced226fcb482c0b19130774d181c8f8</id>
<content type='text'>
state_client.{h,c}:
- Worker task drains a depth-1 queue (xQueueOverwrite gives the
  replace-on-full semantics from the spec — in-flight POST is never
  cancelled; the next queued entry is overwritten by newer state).
- esp_http_client POST to &lt;scrypted&gt;/state with Content-Type application/
  json, User-Agent ScryptedViewport/&lt;version&gt;, Connection: close, 1s
  timeout. Body: {"viewport":"&lt;name&gt;","state":"wake"|"sleep"}.
- Any non-2xx or transport error increments state_post_failures and is
  otherwise ignored. Silently drops if no Scrypted URL is configured.

touch.{h,c}:
- FT5426 capacitive touch on the shared I2C bus at 0x38.
- 30ms polling task; tracks down/up transitions, detects taps as
  down-then-up within 500ms with a 150ms debounce.
- On tap, toggles wake/sleep via state_machine_set_local(), which drives
  the local transition AND fires state_client_post() at Scrypted.

state_machine adds state_machine_set_local(): runs the same transition
as state_machine_set() then enqueues an outbound POST. Idle-timer
expiry now uses this path so Scrypted sees idle-driven sleeps too
(the spec's "tighten the race with /frame 409" path stays in place as
the fallback).

display.h exposes display_i2c_bus(); touch.c uses it instead of
re-initializing the same I2C port.

app_main starts state_client right after the state machine and starts
touch after display init (touch is skipped if display isn't up since
they share the bus).

CMakeLists.txt: add esp_http_client to REQUIRES.

Build clean against ESP-IDF 5.4 (binary ~860 KB; jumped ~210 KB from
M6 because esp_http_client + cJSON path pulls in tcp_transport, mbedtls,
http_parser).

TESTING.md M7: flask-based test receiver, tap dispatch verification,
failure-path check (kill receiver, confirm counter increments),
queue-coalescing behavior, no-POST-on-Scrypted-initiated, no-POST-
when-unconfigured.
</content>
</entry>
</feed>
