| Age | Commit message (Collapse) | Author | Files | Lines |
|
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 -> 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.
|
|
Stream throughput tuning, measured on hardware (kitchen panel):
wire 53 -> 74 Mbps, per-frame recv 30.5 -> 21.4 ms, painted fps
19.8 -> 23.6, g2g 73 -> 62 ms, sender backpressure 61% -> 47%.
- lwip: TCP_WND 5760 -> 23040 (16 x MSS), RECVMBOX 6 -> 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.
|
|
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 -> live video) ~6s -> ~0.7s: prebuffered substream by id +
request >= 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.
|
|
Streams the raw .bin to the inactive ota_0/ota_1 slot via esp_ota_*, flips otadata, replies 200, reboots after 500 ms. Single-shot guarded by atomic_flag (409 on concurrent). CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE armed: new images boot pending-verify and ota_arm_healthy_timer marks them valid after 30 s of healthy uptime; otherwise the bootloader reverts on next reset. /state gains ota_state.
|
|
Wire format change: stream frames now carry a 4-byte "VPRT" magic +
4-byte jpeg_len + 4-byte seq + 4-byte event_us_low. Total 16 bytes
(was 8). The firmware sniffs the first 4 bytes per frame: if they
spell VPRT it reads the remaining 12 bytes of v1 header; otherwise
it interprets bytes 0-3 as jpeg_len for the old v0 8-byte format and
reads 4 more for seq. Lets a v1 firmware accept a v0 (legacy)
Scrypted script during the rollout window. v0 will be removed once
all field deployments roll forward.
event_us_low is the low 32 bits of the Scrypted host's monotonic µs
at camera-event arrival. The firmware does NOT interpret it (the
clocks aren't sync'd); it just stamps it on every painted frame and
exposes the most recent value via /state. The script polls /state
every 5s during an active stream, reads last_paint_event_us_low,
and computes glass-to-glass = (now_us_low - last_paint_event_us_low)
with 32-bit wrap. 30s sanity ceiling on the wrap to discard event
timestamps from before the stream started.
Also expose the firmware's just-closed 30-frame window stats via
/state under the "stream" key — frames, bytes, window_us, plus
min/avg/max for recv/dec/paint/idle. Lets external tools (a curl
loop, the Scrypted plugin, etc) poll the firmware's view without
parsing serial logs.
Firmware:
- stream_server.h: 16-byte v1 wire spec, stream_server_stats_t
struct, stream_server_snapshot_stats(out) getter.
- stream_server.c: magic-detect header read path, last_event_us_low
capture into per-connection state, portMUX-protected window-stats
snapshot at every 30-frame roll.
- http_api.c: GET /state JSON gains a "stream" sub-object with the
full snapshot.
- viewport_state.h: VIEWPORT_VERSION 1.0.0 → 1.1.0 (new /state shape).
Scrypted:
- startStream captures eventUsLow = (tEvent * 1000) >>> 0.
- TCP demux loop writes the 16-byte v1 header with the VPRT magic.
- New fwPoller setInterval (5s) fetches /state, parses .stream,
computes g2g, emits one summary line per poll cycle.
|
|
First milestone where the device runs the way it was intended end-
to-end:
- Raw-TCP streaming data plane at ~14-20 fps painted, sub-ms socket
writes, no Nagle/ACK pathology.
- HTTP control plane (/state, /config) plus HTTP /frame for the
one-shot snapshot fast path on wake. Snapshot via sharp → ~700ms
first paint from a cold trigger (down from ~1000ms+ via ffmpeg).
- Firmware-side "always paint the latest" — FIONREAD skip drops
superseded frames before decode, keeping glass-to-glass tight
even when upstream produces faster than we can ingest.
- HW JPEG decode + zero-copy paint into the panel back framebuffer.
- 12 fps goal hit cleanly at q:v 1 (visually lossless). Source
pinned to medium-resolution substream so the camera supplies
enough frames to actually fill the pipe.
- Scrypted side: per-host node:http keep-alive Agent with NODELAY,
the cascading sharp → mediaManager → ffmpeg snapshot transform,
wake-trigger picker on the new-device dialog, and the leaked-
setInterval-across-script-reloads bug squashed.
Bumping VIEWPORT_VERSION 0.1.0 → 1.0.0 so the boot log and info
screen reflect it.
|
|
- Seed viewport_name with the full base MAC, colons stripped
(e8:f6:0a:e0:90:94 → "e8f60ae09094"). Stable across reboots, globally
unique, 12 alphanumeric chars — well inside the mDNS hostname limit
even with the "viewport-" prefix.
- Add a mac_str field to viewport_state and expose it as the new "mac"
key in GET /state and as a "mac" line on the info screen.
- "Configured" no longer requires a viewport_name (it always has the
MAC default); the scrypted URL alone gates outbound POSTs and the
loading-vs-info screen choice.
- Strip viewport_name[0] fallbacks in http_api / mdns / local_screens
now that the field is never empty.
- Replace the wake-on-boot-stays-on behaviour with a ~600 ms flash
followed by sleep — long enough for the FT5426 touch IC to come out
of its initial unresponsive state, short enough that an idle device
doesn't burn the backlight.
- Drop the touch defensive cruft added when we were chasing a PoE-power
theory: the DEV_MODE=0x00 write at init, the touch_reset_pulse() call
at init, and the runtime wedge-self-heal in the polling loop didn't
actually fix anything — the wake-on-boot flash did. Also delete the
display_touch_reset_pulse() helper since it now has zero callers.
|
|
Code-review pass after the M5 colour fix. -126 net lines (-215 / +89).
Dead code removed:
- display_fill, display_test_pattern — M3 bring-up self-tests, no callers
- net_eth_is_up — no callers
- nvs_config_reset — no callers (factory-reset gesture was removed earlier;
NVS wipe now goes through `idf.py erase-flash`)
Helpers inlined or collapsed (each had one call site):
- state_name / orientation_name — ternaries at the cJSON site
- fmt_bytes / fmt_uptime — inline scope in local_screens_show_info
- expected_dims (http_api) + effective_dims (local_screens) — merged into
viewport_state_effective_dims() in viewport_state, one canonical source
state_machine cleanup:
- arm_idle_timer_unlocked / disarm_idle_timer collapsed to one arm_idle_timer(ms)
that takes the snapshotted timeout (ms==0 disables). Removes the misleading
name and the second viewport_state lock acquisition per painted frame.
- state_machine_set + state_machine_frame_painted now snapshot state + idle_ms
under one lock.
mdns_service cleanup:
- snapshot_state / apply_hostname / apply_txt collapsed into a single
apply_state(include_hostname) helper; mdns_service_start grabs hostname
once for hostname_set + log instead of going under the lock three times.
http_api cleanup:
- POST /config brightness-changed path uses the local `bright` it already
validated instead of re-locking + re-reading st->brightness.
- Trimmed verbose bring-up comments (stack-size justification, M5 saga,
DSI shadow struct) to one line each — kept the load-bearing facts.
Smoke-tested on hardware: boot clean, display + JPEG + touch all up
([DJT]), info screen renders, no crashes.
|
|
\`state\` now reports only the screen's runtime state (awake or asleep).
Whether a viewport is set up to talk to Scrypted is a separate
\`configured\` flag, derived from \`viewport_name && scrypted_url\`.
There's no third state.
Behaviour changes:
- POST /state always succeeds; the previous 409 "device unconfigured"
path is gone. The screen toggles regardless of /config status.
- POST /config now sets \`configured\` directly from the derived
predicate instead of mutating the state enum.
- Outbound state-client POST to Scrypted is still gated on a scrypted
URL being present — that's the only thing the configured flag now
actually controls in the runtime path.
GET /state JSON unchanged in shape, but \`state\` is now never
"unconfigured" — that's reported through the existing \`configured\`
boolean instead.
|
|
- viewport_state.{h,c}: shared state struct (config, run-state, counters,
orientation, timestamps) behind a FreeRTOS mutex. Modules update it
under viewport_state_lock(); GET /state serializes a snapshot.
- http_api.{h,c}: starts esp_http_server on :80, registers GET /state.
Returns the full JSON shape from the spec (name, version, configured,
state, uptime_ms, last_frame_ms_ago, frames_received, decode_errors,
state_post_failures, resolution, ip, free_heap, free_psram).
- mdns_service.{h,c}: mdns_init + advertise _scrypted-viewport._tcp.local
on :80 with version/resolution/orientation/name TXT records.
mdns_service_refresh() reapplies hostname + TXT after /config writes
(called from M4 onward).
- main/CMakeLists.txt: add esp_http_server, esp_timer, json, mdns to
REQUIRES. espressif/mdns added as managed component via
main/idf_component.yml.
app_main calls viewport_state_init early so any module can read defaults
before /config arrives.
Also adds TESTING.md tracking per-milestone verification status and an
integration test plan to run after M9 (races, failure modes, longevity,
power cycles, negative protocol, multi-viewport).
M1 promoted to "🟡 builds clean" in TESTING.md; combined HW verification
of M1+M2 will be one flash session. Build verified against ESP-IDF 5.4
for target esp32p4 (binary ~550 KB).
|