<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport, branch v1.3.0</title>
<subtitle>ESP32-POE Scrypted viewport (private)
</subtitle>
<id>https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=v1.3.0</id>
<link rel='self' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=v1.3.0'/>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/'/>
<updated>2026-07-01T19:23:49+00:00</updated>
<entry>
<title>release: v1.3.0</title>
<updated>2026-07-01T19:23:49+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T19:23:49+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=bdf497172a62c4ac318fa087bc59ef95813af61a'/>
<id>urn:sha1:bdf497172a62c4ac318fa087bc59ef95813af61a</id>
<content type='text'>
Unify firmware and plugin versions for the production release. Highlights
since v1.1.0/1.2.0:
- event wake fixed (self-healing camera-listener re-attach after the reload/
  add storage race); doorbell/motion/person confirmed on hardware
- stop() fully tears down (stop+start == fresh load); stale childId pruning;
  live-stream events ignored (no queue/relaunch)
- triggers default to person+doorbell; doorbell hidden for non-doorbell cams
- cold-start (wake -&gt; live video) ~6s -&gt; ~0.7s: prebuffered substream by id +
  request &gt;= GOP + burst-friendly ffmpeg flags; snapshot removed
- firmware: Loading screen on new stream connection (no stale-frame flash)
- logging trimmed for chatty cameras
- README/protocol docs rewritten for the TCP-socket streaming model

Set VIEWPORT_VERSION and scrypted/package.json to 1.3.0.
</content>
</entry>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = 0be8f18</title>
<updated>2026-07-01T19:19:01+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T19:19:01+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=ad29fa22e8fc9c15c2cda5232a2b269594475d16'/>
<id>urn:sha1:ad29fa22e8fc9c15c2cda5232a2b269594475d16</id>
<content type='text'>
</content>
</entry>
<entry>
<title>scrypted: trim stream logging volume</title>
<updated>2026-07-01T18:22:05+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T18:22:05+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=0be8f188b46bd073e4c6836dedf79a5282250828'/>
<id>urn:sha1:0be8f188b46bd073e4c6836dedf79a5282250828</id>
<content type='text'>
A chatty camera keeps a viewport streaming for long stretches, and the old
logging emitted ~5 cold-start stamps + a 10s window line per ~10s per stream.
Cut it down:
- keep a single cold-start stamp (first ffmpeg frame); drop spawned / first
  stdout byte / first socket.write.
- gate the 10s window line on noteworthy windows only (socket-not-ready drops,
  a buffer-cap flush this window, firmware shedding &gt;=2fps, or painted &lt;20fps);
  healthy 24fps windows print nothing — the per-wake lines already show liveness.
- log "registered" only when the pushed config actually changes, not on every
  5-minute reregister cycle. lastRegisterSig cleared on stop() for fresh-load
  parity.
No behavior change beyond logging.
</content>
</entry>
<entry>
<title>docs: reframe protocol sections for the TCP-socket streaming model</title>
<updated>2026-07-01T02:22:22+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T02:22:22+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=ec49dcd0ac9bf73b8be56127eef30428af5824a4'/>
<id>urn:sha1:ec49dcd0ac9bf73b8be56127eef30428af5824a4</id>
<content type='text'>
The Wake/Sleep, race-handling, and idempotency sections still described the
old model where Scrypted streamed via POST /frame and learned of sleep from a
/frame 409. Streaming now runs over the TCP data socket (:81); the firmware
/frame endpoint remains only for one-shot snapshots/debug. Reframe the recovery
model accordingly:
- race-free property: frames arriving while asleep are discarded by the decode
  task (stream socket) / rejected 409 (/frame) — neither re-wakes; sleep is
  learned from the device's state=sleep callback + Scrypted's safety timer
- drop every "recovered by the next /frame returning 409" clause in favor of
  the safety timer + discard-while-asleep
- idempotency table: add the stream-socket row (paints if awake, discarded if
  asleep, skip-oldest)
- Loading screen: shown on wake AND each new stream connection until first paint
- Philosophy: list the MJPEG stream server (:81) and /firmware
Left the POST /frame endpoint's own API/semantics intact — it still exists.
</content>
</entry>
<entry>
<title>docs: update main README streaming section to the shipped TCP-socket path</title>
<updated>2026-07-01T02:18:43+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T02:18:43+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=a84f17ec5da36a704636c45aaa5a67c4b8a2bd5b'/>
<id>urn:sha1:a84f17ec5da36a704636c45aaa5a67c4b8a2bd5b</id>
<content type='text'>
The Scrypted Integration section still framed the snapshot POST /frame path as
v1 and MJPEG streaming as a future POST /stream milestone, contradicting the
already-current "What's next" perf section. Bring it in line:
- v1 is the live ffmpeg MJPEG stream over the raw TCP data socket (:81) with
  prebuffer fast-start (~0.7s wake-to-video); POST /stream was never the shape
- replace the /frame streaming code example with the TCP socket + framed-MJPEG
  sketch; brightness default 100 (was 80)
- sleep is learned via the device's sleep callback, not a /frame 409
- Status table: streaming + end-to-end are done/verified, not "not started"
- Idle: timer resets on any painted frame, not specifically a /frame POST

Left intact: the POST /frame API + protocol/idempotency sections — that
firmware endpoint still exists for one-shot snapshots/debug and its 409-when-
asleep semantics are unchanged.
</content>
</entry>
<entry>
<title>docs: bring scrypted/README.md in line with current streaming behavior</title>
<updated>2026-07-01T02:14:43+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T02:14:43+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=05f9e07014c6aa3f0c8e0b23c283ce6d52c383a1'/>
<id>urn:sha1:05f9e07014c6aa3f0c8e0b23c283ce6d52c383a1</id>
<content type='text'>
The v1 README still described the old snapshot-poll path. Update to match the
shipped script: live MJPEG over a TCP data socket (port 81, ~24fps) via ffmpeg,
not ~1fps snapshots. Fixes:
- intro + v1 limitations: MJPEG streaming is done, not a v2 "POST /stream" TODO
- Settings: replace the removed "Frame push interval" with the real Display
  fields (JPEG quality, Max Scrypted-side buffer, Stream prebuffer); add the
  Actions group; correct brightness default (100, was 80)
- Wake triggers default is person+doorbell (was "all three"), doorbell only for
  doorbell-capable cameras
- drop the stale snapshot-source / snapshot-interval / POST /frame 409 mentions
- host field: no mDNS auto-resolve (was contradictory)
- smoke test: Loading -&gt; live video, not "snapshots flowing"
</content>
</entry>
<entry>
<title>docs: document required Medium-stream prebuffer for fast wake</title>
<updated>2026-07-01T02:11:17+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T02:11:17+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=0fd9ac83342451efdf0c7b0a1911037a426c6236'/>
<id>urn:sha1:0fd9ac83342451efdf0c7b0a1911037a426c6236</id>
<content type='text'>
Wake-to-live is ~0.7s only when the streamed substream keeps a rebroadcast
prebuffer. Add a "Fast wake — camera prebuffer (required)" section to
scrypted/README.md: enable Prebuffer on the STREAM: MEDIUM tab (default only
prebuffers High), keep duration &gt;= the detected keyframe interval (~5s), leave
the viewport's Stream prebuffer (ms) at 6000, and how to verify via the log.
</content>
</entry>
<entry>
<title>firmware: clear panel to Loading screen on new stream connection</title>
<updated>2026-07-01T01:50:34+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T01:50:34+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=c5f1631b93456e3a084e6b72e55fc442d1e2817c'/>
<id>urn:sha1:c5f1631b93456e3a084e6b72e55fc442d1e2817c</id>
<content type='text'>
The panel used to hold its last framebuffer during the connect→first-frame gap,
flashing a stale old frame before video appeared — more visible now that the
Scrypted side no longer sends a bridging snapshot. The wake path already paints
the Loading screen, but state_machine_set(AWAKE) is a no-op when the device is
already awake (e.g. a stream restart), so the clear didn't happen there.

Tie the clear to the stream connection instead: recv-task paints
local_screens_show_loading() once per new conn_id, guarded by the decoder lock
so the RGB565 present can't race a trailing decode-task paint, and gated on
AWAKE so it never draws on a sleeping panel.
</content>
</entry>
<entry>
<title>scrypted: stamp SCRIPT_VERSION = c6610c1</title>
<updated>2026-07-01T01:44:00+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T01:44:00+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=16dd6ca15b037d20fba8db873da064f58e0d0574'/>
<id>urn:sha1:16dd6ca15b037d20fba8db873da064f58e0d0574</id>
<content type='text'>
</content>
</entry>
<entry>
<title>scrypted: remove the pre-stream snapshot entirely</title>
<updated>2026-07-01T01:43:52+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-01T01:43:52+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=c6610c1047514292b8deee08cbd27d92d9fe6950'/>
<id>urn:sha1:c6610c1047514292b8deee08cbd27d92d9fe6950</id>
<content type='text'>
The prebuffered stream now paints in ~0.7s, so the takePicture→transform→POST
first-paint bridge was consistently slower than the video it was meant to
cover, while adding camera load and a stale-overpaint risk. Drop it: remove
pushSnapshot and its call, the shouldPaint gate, and the firstStreamFrameSeen
plumbing. On the slow paths (no/cold prebuffer) the panel holds its prior frame
until the stream's first frame lands. buildVf stays (used by the live path).
</content>
</entry>
</feed>
