<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport/scrypted/package.json, 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:09:51+00:00</updated>
<entry>
<title>release: v1.4.0</title>
<updated>2026-07-18T01:09:51+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-18T01:09:51+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=49c51b4e909053dc71350f4abe215acd29a714c6'/>
<id>urn:sha1:49c51b4e909053dc71350f4abe215acd29a714c6</id>
<content type='text'>
</content>
</entry>
<entry>
<title>release: v1.3.2</title>
<updated>2026-07-16T00:51:41+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:51:41+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=866015678918560a0d0519be8a2549e4fc23b2cd'/>
<id>urn:sha1:866015678918560a0d0519be8a2549e4fc23b2cd</id>
<content type='text'>
Tear-free display path and thermal visibility:
- display: triple buffering + scan tracking via on_refresh_done —
  decode target is never the scanning or pending fb, so the
  flip-vs-scan tear (measured on ~6% of painted frames at full
  stream rate) is impossible by construction, with zero added
  latency. /state tear_guard_engaged counts averted frames.
- temp: on-die TSENS reported as /state temp_c, on the info
  overlay, and on the Scrypted per-stream stats line.
- screens: INFO_MAX_LINES 16 -&gt; 20 (temp line was silently capped).
- docs: README Display strategy rewritten for the triple-buffer
  model; new TCP window + EMAC tuning section.

Set VIEWPORT_VERSION and scrypted/package.json to 1.3.2.
</content>
</entry>
<entry>
<title>release: v1.3.1</title>
<updated>2026-07-16T00:04:56+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:04:56+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=7620b935728ea964ddca09b91ac0be36069a8c64'/>
<id>urn:sha1:7620b935728ea964ddca09b91ac0be36069a8c64</id>
<content type='text'>
Stream throughput tuning, measured on hardware (kitchen panel):
wire 53 -&gt; 74 Mbps, per-frame recv 30.5 -&gt; 21.4 ms, painted fps
19.8 -&gt; 23.6, g2g 73 -&gt; 62 ms, sender backpressure 61% -&gt; 47%.

- lwip: TCP_WND 5760 -&gt; 23040 (16 x MSS), RECVMBOX 6 -&gt; 32
- eth: EMAC RX DMA pool sized above the TCP window (1600B x 24);
  below-window pool caused silent burst tail-drop + ~200-400ms
  sender RTO stalls
- stream: TCP-window decomposition instrumentation (wire kbps,
  hdr_gap, pend_age) in the window log and /state; connect log
  stamps TCP_WND/MSS/RECVMBOX

Set VIEWPORT_VERSION and scrypted/package.json to 1.3.1.
</content>
</entry>
<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>Path B: per-viewport Scrypted devices with UI-driven settings</title>
<updated>2026-06-14T15:53:37+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-06-14T15:53:37+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=52f4105aa059b9d7310d80cfb86abe844f7be05a'/>
<id>urn:sha1:52f4105aa059b9d7310d80cfb86abe844f7be05a</id>
<content type='text'>
Convert the Scripts-plugin script from a hardcoded BINDINGS constant
into a DeviceProvider + DeviceCreator + HttpRequestHandler. Each
viewport is now a child Scrypted device under the parent script with
its own Settings page; operators add, edit, and delete viewports
entirely through the Scrypted UI.

Parent (ScryptedViewportProvider):
- DeviceProvider: getDevice(nativeId) instantiates a Viewport, attaches
  its camera event listener, and posts /config. releaseDevice tears
  down stream + listener + child storage entry.
- DeviceCreator: "+ Add Device" on the parent's page shows a small form
  (name / host / camera picker filtered to Camera interfaces /
  orientation choice). createDevice() pre-populates the child's storage
  via deviceManager.getDeviceStorage(nativeId) and registers it under
  the parent.
- Tracks known child nativeIds in its own storage as a JSON array so it
  can eagerly instantiate every child on plugin start (each
  registration + camera subscription happens at load time, not lazily).
- 5-min re-register loop catches devices that rebooted or got new DHCP
  leases.
- HttpRequestHandler routes POST &lt;base&gt;/state on the parent's endpoint;
  body {viewport, state} is matched against child names. Honors the
  spec race rules: every callback cancels any prior stream + safety
  timer for that viewport before applying the new state, and a /frame
  409 stops the stream without echoing sleep back.
- Global tuning (frame_interval_ms) lives on the parent's Settings.

Child (Viewport):
- Settings: host (string), camera (type=device with deviceFilter for
  the Camera interface — the UX win), orientation (choices), idle
  timeout, brightness. All persisted via this.storage.
- putSetting fires onBindingChanged() on the parent so re-register +
  re-subscribe happen immediately when any field changes.

scrypted/README.md rewritten for the UI-driven flow — install + add
device + edit + remove + global tuning + smoke test — no more "edit
BINDINGS and re-save."

scrypted/package.json + tsconfig.json: optional `npm install` so
editors can resolve @scrypted/sdk types. Nothing here ships — install
remains "paste into Scripts plugin." node_modules ignored.
</content>
</entry>
</feed>
