<feed xmlns='http://www.w3.org/2005/Atom'>
<title>luke/esp32-poe-scrypted-viewport, branch v1.3.2</title>
<subtitle>ESP32-POE Scrypted viewport (private)
</subtitle>
<id>https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=v1.3.2</id>
<link rel='self' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/atom?h=v1.3.2'/>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/'/>
<updated>2026-07-16T00:51:41+00:00</updated>
<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>screens: raise INFO_MAX_LINES 16 -&gt; 20 (temp line was silently dropped)</title>
<updated>2026-07-16T00:41:41+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:41:41+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=739e6ff1afe6b5b80cc28afa19aa4b1b42282765'/>
<id>urn:sha1:739e6ff1afe6b5b80cc28afa19aa4b1b42282765</id>
<content type='text'>
The info overlay already had exactly 16 ADD lines; the temp line
added in f59cea3 was the 17th, and the ADD macro's bounds guard
drops overflow lines without any diagnostic — so temp never
rendered. Give the cap headroom and document the silent-drop
behavior at the definition.
</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>temp: report on-die temperature in /state and the info screen</title>
<updated>2026-07-16T00:18:32+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:18:32+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=f59cea3be9eed8d63fa44d4fd360c9c68e9318f6'/>
<id>urn:sha1:f59cea3be9eed8d63fa44d4fd360c9c68e9318f6</id>
<content type='text'>
New chip_temp module wraps the ESP32-P4 TSENS driver (20-100C range
for best accuracy in the warm band a PoE + 200MHz-PSRAM device
lives in). /state gains temp_c (0.1C resolution, omitted when the
sensor is unavailable); the long-press info overlay gains a temp
line (lowercase c suffix — the local 8x8 font has no uppercase C).
Junction temperature, ~10-20C above ambient under load.
</content>
</entry>
<entry>
<title>display: tear-free frame path via triple buffering + scan tracking</title>
<updated>2026-07-16T00:10:57+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-16T00:10:57+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=659da7f36473a21494693f031a39fb6bb977b90a'/>
<id>urn:sha1:659da7f36473a21494693f031a39fb6bb977b90a</id>
<content type='text'>
draw_bitmap on a direct fb pointer only updates the driver's
cur_fb_index; the DPI DMA reloads that index at the END of the
in-progress frame scan (~21ms period at ~47Hz). Under double
buffering, flipping and immediately decoding the next frame into
the other fb writes a buffer the DMA may still be scanning out —
a torn frame. This regime is common now that the TCP window fix
delivers frames back-to-back (decode starts ~6ms after flip).

Fix with zero added latency: num_fbs 2 -&gt; 3 (+1.15MB PSRAM of 25MB
free), track the actually-scanning fb via on_refresh_done (fires in
the DMA-done ISR exactly when the DMA reloads cur_fb_index), and
pick the decode target as the fb that is neither pending display
nor scanning. Three buffers minus at most two excluded roles =
always a free one; no waiting on vsync anywhere.

Instrumented: /state tear_guard_engaged counts back-buffer picks
made while the previous fb was still mid-scan — each one is a
frame that would have torn under double buffering.
</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>eth: size EMAC RX DMA pool above the TCP window (1600B x 24)</title>
<updated>2026-07-15T23:38:26+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-15T23:38:26+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=8c05eddbd12c6a78a7247763d98f2e2a8ac77e1e'/>
<id>urn:sha1:8c05eddbd12c6a78a7247763d98f2e2a8ac77e1e</id>
<content type='text'>
WND=23040 alone regressed: sender fps 20 -&gt; 14, drop-oldest 3x,
recv bimodal (min 20ms but 200-450ms stalls; firmware wire_min
6.2Mbps / wire_max 75Mbps, recv_max 253ms). The stalls are RTO
recovery: the default EMAC RX pool (20 x 512B = 10KB) is smaller
than the 23KB the window now invites in flight, so a full-window
burst overruns the RX descriptors, the burst tail is dropped with
no dup-ACKs behind it, and the sender waits out a ~200ms min-RTO.

1600B buffers fit one MSS frame per buffer (1 descriptor/packet
instead of 3); 24 of them = 38.4KB &gt;= window + slack. ~44KB more
internal RAM (491KB free).
</content>
</entry>
<entry>
<title>lwip: raise TCP_WND 5760-&gt;23040, RECVMBOX 6-&gt;32 (window step 1)</title>
<updated>2026-07-15T23:15:36+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-15T23:15:36+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=143c03f1b0fe35484d410f97539af1d7fe28b930'/>
<id>urn:sha1:143c03f1b0fe35484d410f97539af1d7fe28b930</id>
<content type='text'>
Instrumented baseline at WND=5760 (kitchen, ~202KB frames, 20fps
sender) confirmed the receive window as the dominant throttle:

- wire 53.0/58.8 Mbps avg/max = the WND/RTT ceiling, vs ~94 Mbps
  line rate on the 10/100 PHY
- recv_chunk_max pinned at exactly 5760 (drains window-quantized)
- recv_calls avg 60/frame; queued_at_body always 0
- sender backpressured 44-61% of frames, drop-oldest 17-54/window
- meanwhile pend_age avg 58us: decode idles, receive starves it

Expected: recv_avg ~30ms -&gt; ~20ms, wire toward line rate, sender
bp%/drop-oldest down, g2g 73ms -&gt; ~60ms. Tripwires (revert if hit):
pend_age or recv_dropped_oldest growing window-over-window, g2g
regressing. RECVMBOX scales with the window: 23040 = 16 segments
in flight, a 6-deep mbox would silently become the new cap.
</content>
</entry>
<entry>
<title>stream: instrument TCP-window decomposition (wire kbps, hdr_gap, pend_age)</title>
<updated>2026-07-15T23:04:31+00:00</updated>
<author>
<name>Luke Hoersten</name>
<email>luke@hoersten.org</email>
</author>
<published>2026-07-15T23:04:31+00:00</published>
<link rel='alternate' type='text/html' href='https://src.nth.io/luke/esp32-poe-scrypted-viewport/commit/?id=6ee12595d339eeafc7fdc7dbb9ba4c4c757f3565'/>
<id>urn:sha1:6ee12595d339eeafc7fdc7dbb9ba4c4c757f3565</id>
<content type='text'>
Before touching CONFIG_LWIP_TCP_WND_DEFAULT, make the window question
decidable from the logs. New per-window metrics in the stream log,
/state, and stats struct:

- wire min/avg/max kbps: instantaneous throughput while each body
  drained (jpeg_len/recv_us). Ceiling ~= TCP_WND/RTT, so it scales
  with the window iff the window is the limiter.
- hdr_gap min/avg/max us: time blocked waiting for the next header
  after finishing a body. Large = sender-paced; ~0 = receive path
  is the bottleneck.
- pend_age min/avg/max us: publish-&gt;claim latency of painted frames.
  Growing across windows = queue backlog building, the failure mode
  that killed the previous WND=65535 attempt.

Together with recv/dec/paint the frame interval is now fully
decomposable: interval ~= hdr_gap + recv + pend_age + dec + paint.

Also stamp TCP_WND/TCP_MSS/RECVMBOX into the client-connect log line
so every capture is self-labeled with the config it ran under.
</content>
</entry>
</feed>
