<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport/scrypted/scrypted-viewport.ts, 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-18T01:04:05+00:00</updated>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = 118a04f</title>
<updated>2026-07-18T01:04:05+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-18T01:04:05+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=81306bf3039f24f7ace8ce9cafca31ae747ee630'/>
<id>urn:sha1:81306bf3039f24f7ace8ce9cafca31ae747ee630</id>
<content type='text'>
</content>
</entry>
<entry>
<title>scrypted: subscribe only to selected wake-trigger interfaces</title>
<updated>2026-07-18T01:04:05+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-18T01:04:05+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=118a04f9a56ab338da746f566fca8799fbd3e13d'/>
<id>urn:sha1:118a04f9a56ab338da746f566fca8799fbd3e13d</id>
<content type='text'>
attachListener listened on BinarySensor+MotionSensor+ObjectDetector
unconditionally and relied on handleCameraEvent to filter by trigger —
correct, but a doorbell-only viewport still took a callback per motion
re-assert on a chatty camera, and the "subscribed to [...]" log implied
all three were wake sources. The listener set now follows the selected
triggers (zero triggers = tap-only: no listeners at all), and the
idempotency key becomes a "cameraId|triggers" signature
(attachedCameraId -&gt; attachedListenerSig) so a trigger change forces a
re-attach through the existing rebind path. Event-time filtering stays
as defense in depth (ObjectDetector still needs the person-class check).
</content>
</entry>
<entry>
<title>scrypted: name viewports from discovery; editable name on settings page</title>
<updated>2026-07-18T01:01:45+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-18T01:01:45+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=3350c4b1541d626189bc6554a774c6503e4c2f52'/>
<id>urn:sha1:3350c4b1541d626189bc6554a774c6503e4c2f52</id>
<content type='text'>
Creating a viewport with the name field blank fell back to "viewport"
with no way to change it afterward — display_name is deliberately the
canonical name (v.name drifts on reload), so the Scrypted device-name
pencil never propagated. Two fixes:

- createDevice: an empty name now inherits the discovered device's
  advertised TXT name, so picking "10.0.13.83 — kitchen (…)" from the
  host dropdown names the viewport "kitchen" without retyping.
- child settings gain a "Viewport name" field: updates display_name,
  mirrors the name onto the Scrypted device record, and re-registers so
  the firmware name + mDNS hostname follow.
</content>
</entry>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = 3821512</title>
<updated>2026-07-18T00:49:57+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-18T00:49:57+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=d73622e3c1da423dbad2cab302a5b9d2f711f830'/>
<id>urn:sha1:d73622e3c1da423dbad2cab302a5b9d2f711f830</id>
<content type='text'>
</content>
</entry>
<entry>
<title>scrypted: mdns discovery — browse, host choices, auto-heal</title>
<updated>2026-07-18T00:49:47+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-18T00:49:47+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=3821512b18be69cad002faf77f1e5844bda13c95'/>
<id>urn:sha1:3821512b18be69cad002faf77f1e5844bda13c95</id>
<content type='text'>
Browse for _scrypted-viewport._tcp with a plain dgram socket on an
EPHEMERAL port: per RFC 6762 §6.7 a query from a non-5353 source port is
a legacy unicast query and responders reply unicast to that port —
verified in the ESP-IDF responder (mdns_send.c answers to the querier's
addr/port whenever src_port != 5353). No :5353 bind means no conflict
with Scrypted's own HomeKit mDNS or a host avahi-daemon, and it works
under Docker host-networking or native alike. One response packet
carries PTR+SRV+TXT+A (RFC 6763 §12.1), so parsing is per-packet with
no follow-up queries; the wire codec (name compression included) is
~150 lines, no dependencies.

Surfaced three ways:
- add-device form: host field is a combobox listing discovered
  viewports as "ip — name (vX, WxH)"; manual entry still works and
  only the address token is stored.
- child settings page: same choices on the existing host field.
- auto-heal: when a /config register fails, re-browse and match by MAC
  (seeded from /state and discovery; survives renames) then by name;
  on a hit at a new address, rewrite host and retry once. Runs only on
  register failure, so it's rate-limited to the 5-min cycle and removes
  the need for a DHCP reservation.

Browses are best-effort ([] on any failure) and cached 30s so settings
re-renders don't spam the LAN. diagnostic.ts gains the same browse as a
paste-and-run probe that validates dgram + multicast reachability from
inside the real Scrypted sandbox before trusting the feature.
</content>
</entry>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = 0ac000e</title>
<updated>2026-07-17T23:56:24+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-17T23:56:24+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=c9f0297dc86eaee7ef222a066587f4eb737d0ea8'/>
<id>urn:sha1:c9f0297dc86eaee7ef222a066587f4eb737d0ea8</id>
<content type='text'>
</content>
</entry>
<entry>
<title>scrypted: code-review fixes — reload drain order, reconnect storm, wake guards</title>
<updated>2026-07-17T23:56:17+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-17T23:56:17+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=0ac000e0db3feb235fae37389d0af18609e91c50'/>
<id>urn:sha1:0ac000e0db3feb235fae37389d0af18609e91c50</id>
<content type='text'>
- bootstrap: drain the shutdown-cleaner array FIRST, not after the child
  re-discovery loop. The old order tore down the listeners the loop had
  just attached, and since the instance maps still referenced them the
  attachListener fast-path blocked the 5-min register-cycle self-heal
  from ever re-attaching after a warm re-paste. The attach cleaner now
  also invalidates listeners/attachedCameraId (one cleaner per attach),
  so a drain by another instance can't strand dead registrations either.
- stream socket: single-flight reconnect scheduled from 'close' only.
  A failed connect emits 'error' then 'close'; scheduling from both
  doubled outstanding attempts every 500ms against a rebooting device.
  openStreamSocket also destroys the previous socket first.
- onRequest wake: same streams/streamStarting guard as every other start
  path (a concurrent second startStream overwrote the streams entry and
  orphaned the first ffmpeg), respond 204 immediately (the device's POST
  times out at 1s — awaiting startStream turned every tap-wake into a
  firmware state_post_failure), and skip the redundant wake POST back.
- startStream: bail-out paths after the wake POST (camera missing, no
  usable stream, ffmpeg-input conversion failure) now send a
  compensating sleep instead of stranding the panel on Loading; the
  last-ditch getVideoStream fallback no longer rejects out of the call.
- key streams/streamStarting/stopStream by nativeId — v.name drifts to
  the nativeId on reload, stranding or duplicating streams keyed under
  the drifted value; findByName matches display_name first for the same
  reason.
- idle_timeout_ms=0: the Scrypted-side safety timeout still reclaims
  ffmpeg/socket but no longer POSTs sleep over the always-on setting.
- pending bindingDebounce timers cleared across re-paste (they fired
  against the dead instance and attached duplicate listeners);
  releaseDevice drops lastRegisterSig/streamStarting/debounce entries.
- start() single-flights bootstrap; createDevice drops its redundant
  second registerViewport; streamLogger rolls its window on quiet ticks
  (post-lull rates were diluted) and lastLogUs -&gt; lastLogMs; hoist the
  JPEG EOI needle + resume-scan offset in the demux loop; postJSON drops
  the dead 204 check; remove unused sandbox declares and the legacy
  streams.interval field.
- tsconfig: moduleResolution Node -&gt; Bundler (removed in TS 6).
</content>
</entry>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = 2af69f7</title>
<updated>2026-07-16T00:37:16+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:37:16+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=abcc844a5a5251b28c1f7a4f7cc30ec29119c9fc'/>
<id>urn:sha1:abcc844a5a5251b28c1f7a4f7cc30ec29119c9fc</id>
<content type='text'>
</content>
</entry>
<entry>
<title>scrypted: temp_c on the stats line; docs: triple-buffer model + window tuning</title>
<updated>2026-07-16T00:36:56+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:36:56+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=2af69f78a6179334c6f59d4273bd72ae170fb821'/>
<id>urn:sha1:2af69f78a6179334c6f59d4273bd72ae170fb821</id>
<content type='text'>
- Scrypted per-stream stats line gains temp=&lt;c&gt;C from /state temp_c —
  free thermal trending under streaming load.
- README Display strategy rewritten for the tear-free triple-buffer
  model: why the deferred fb-index reload tears under double
  buffering, why scan-tracked buffer roles beat vsync-waiting, and
  the tear_guard_engaged counter.
- New 'TCP window + EMAC tuning' section documenting the measured
  window raise, the EMAC-RX-pool-below-window RTO regression, and
  the pool &gt;= TCP_WND invariant; stale 'window bump is safe but
  won't help' backlog text updated.
- Memory strategy updated (3 fbs, decoder writes into them directly,
  stream body ring).
</content>
</entry>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = 01a588e</title>
<updated>2026-07-01T19:33:44+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T19:33:44+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=55303088ea499017c75203eef71588d18609f124'/>
<id>urn:sha1:55303088ea499017c75203eef71588d18609f124</id>
<content type='text'>
</content>
</entry>
</feed>
