| Age | Commit message (Collapse) | Author | Files | Lines |
|
120-col reflow
Now that the repo is public: title becomes "Scrypted Viewport" with a
what-it-is intro (0.7s wake-to-video, 24fps, double-digit-ms g2g) and
up-front links to the write-up (nth.io/luke/projects/esp32-ethernet-
display), the canonical src.nth.io repo, and the GitHub mirror. The
"v1 Technical Design Specification" framing is gone (the v1/v2 script
references now read as "the script" + the plugin-conversion plan), the
Boot flow diagram is fenced, and all prose is reflowed to 120 columns
(markdown tables exempt — they can't wrap).
|
|
The 730-line milestone-by-milestone bring-up log served its purpose —
every milestone (M1-M9) is verified on hardware, and the per-run
narratives are preserved in git history (last full version: 8eebc0e).
What survives, restructured (~230 lines):
- New-unit bring-up: hardware prereqs (all unknowns resolved), the
flash -> panel -> touch -> Scrypted sequence, and the panel
failure-mode signals table (the content you actually need the day a
second viewport gets provisioned).
- Regression recipes: condensed curl checks for config validation (incl.
the 54-char name cap), frame push, state machine, OTA acceptance, and
device-initiated POSTs.
- Outstanding tests: the honestly-never-run backlog (mid-frame cable
pull, 24h soak, frame storm, brown-out, multi-viewport).
- Performance playbook: kept, with example log formats refreshed to the
current stream instrumentation (the old examples showed the removed
pre-stream snapshot path) and thresholds updated to the post-tuning
baseline (24fps painted, pend_age as the backlog tripwire).
README: pointers at TESTING's old structure updated; the Hardware status
row no longer claims schematic confirmation is pending (those unknowns
were resolved during bring-up — only the per-unit flash-size check
remains).
|
|
M9 still carried the abandoned chunked-HTTP design and a pending status
long after the raw TCP data-socket stream was verified at painted =
sent = 24 fps (recv/decode task split d1c8d45 + TCP window/EMAC tuning).
Rewrite the milestone to describe what shipped, flip it ✅, and bump the
README's "M1 – M8" line to M9.
|
|
Drift accumulated across the review-fix, mDNS-discovery, and tooling
work:
- Discovery: the design-era "use a Node mDNS library" flow is replaced
with what shipped — the sandbox has no such libraries; the script does
a dependency-free legacy-unicast dgram browse (no :5353 bind), host
dropdowns, blank-name inheritance, and auto-heal on register failure
(matched by the new mac TXT record) instead of periodic re-browsing.
- /state example: add mac, ota_state, panel dims, tear_guard_engaged,
temp_c, and the stream stats object; correct `configured` semantics
(scrypted URL only) and the never-null MAC-derived name in /config.
- /config: document the 54-char name cap (mDNS label limit).
- scrypted/README: add-device + settings tables gain the discovered-host
dropdown, Wake triggers on create, and the canonical "Viewport name"
rename field (the Scrypted pencil doesn't propagate — by design);
trigger-scoped subscriptions; device-wake immediate-204 semantics;
idle=0 no longer force-slept by the safety timer; "manual IP" dropped
from limitations; discovery section reframed with the diagnostic.ts
probe and bridge-networking caveat.
- Source map: dedupe the doubled touch.{h,c} row, add stream_server /
ota / chip_temp rows, fix nvs_config + endpoint-count drift.
- Build/TESTING: point iteration at `make ota` (USB stays for first
flash); TXT expectations include mac; DHCP-renumber failure mode now
documents the verified auto-heal path.
|
|
PLUGIN-CONVERSION.md captures the full plan so a future session can pick
it up cold: why (the Scripts sandbox's reload-leak machinery — cleaner
registry, drain ordering, name-drift workarounds — exists only because
of the paste-deploy model), the port map (what moves 1:1, what changes,
what dies), open questions to verify (orphaned ffmpeg on worker restart,
storage attach at startup), the cutover order (never run script + plugin
together), and the verification suite. Linked from the README's Related
docs and Status table.
|
|
|
|
- Scrypted per-stream stats line gains temp=<c>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 >= 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).
|
|
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.
|
|
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.
|
|
painted<sent gap
Document the architecture pivot. The per-frame budget table now
reflects the long-lived TCP stream socket on port 81 (replaced the
HTTP /frame pattern) and the new recv/decode/paint timings:
recv 14-18ms on the wire (pure body), dec ~5ms, paint <35us, with
decode-task idle ~30ms per frame waiting on recv.
The critical change wasn't a TCP tuning knob — it was splitting
recv off its own FreeRTOS task with a 3-buffer PSRAM ring and a
1-deep latest-frame slot to the decode/paint task (d1c8d45). The
single-task loop blocked the socket for 6ms during decode+paint,
which against the IDF-default 5760-byte window forced a stop-go
cycle that capped painted at ~17fps. Raising the window to 65535
made it worse (g2g grew to 17s) because lwIP RX couldn't drain
the bursts and there's no way to skip-oldest on the kernel queue.
The task split eliminated the coupling: recv-task drains
continuously regardless of decode timing, the kernel buffer stays
near-empty, and painted now matches sent at the source rate.
Backlog entries for body shrink and OTA marked done.
|
|
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.
|
|
Bumping WND/SND_BUF to 32 KiB + RECVMBOX to 16 + SACK on regressed
ttfb from ~350µs to ~1.2ms across every frame and introduced periodic
250+ms body stalls (TCP retransmit on the larger window). Mean body
unchanged. Reason: Scrypted's per-frame fetch() opens a fresh TCP
socket, so the larger receive window just slows slow-start on every
new connection rather than helping.
Reverted to lwIP defaults. The real fix is HTTP keep-alive on the
Scrypted side; will revisit window tuning once persistent connections
are in place. README backlog updated with what we learned.
|
|
body is now the only big lever
|
|
Two fixes plus a README refresh:
1. scrypted: pushStreamFrame previously reset the per-stream idle timer
on every successful /frame response. That made the timer anchored to
"frames are flowing" rather than to "the camera event that triggered
the stream", so a continuously-streaming source would never let the
stream time out. Removed the reset. The startStream → stopStream(false)
cancel-and-replace path on repeated events still keeps the stream
alive while the event keeps firing; idle (no new events) now actually
ends the stream at idle_timeout_ms.
2. firmware: break the previous coarse recv/dec/paint timing into
lock : try_lock returned
ttfb : first httpd_req_recv chunk landed
body : remaining bytes received
dec : hardware JPEG decode
paint : esp_lcd_panel_draw_bitmap returned
post : state-counter bookkeeping + unlock
Logged every 10 frames at INFO. Splits the previously-fat recv bucket
into TCP/HTTP handshake overhead (ttfb) vs wire-time (body), and
surfaces any tail bookkeeping cost.
3. README: replace the stale "5 fps ceiling caused by CPU RGB conversion"
guess with the actual measured per-phase budget and re-rank the
backlog accordingly. Double-buffering the panel (paint 24 ms → ~2 ms)
is now the highest-value next move; the previously-listed DMA-2D
rewrite is moot because the CPU loop is already gone.
|
|
first, OTA next, ...)
|
|
\`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.
|
|
Previously the 4-line "identity" screen showed only viewport name, mDNS
host, IP, and state. Expanded to 15 lines covering the full GET /config
+ GET /state output (name, host, ip, state, configured, scrypted,
orientation, brightness, idle, fw, uptime, frames, errs, free heap,
free PSRAM), label/value pairs left-aligned with auto-scaled font.
Renames "identity" → "info" throughout — symbol, log messages,
README/TESTING references.
Also:
- Move the info-screen render's big locals (~1.6 KiB: lines[16][80] +
scrypt[256] + vp_name[64]) to BSS. The touch task's 3 KiB stack
was overflowing on every long-press, leaving whatever frame was
previously on the panel — gave the appearance of a "blue screen"
after the M5 test pattern.
- Drop the ≥5 s touch factory-reset gesture and remove all stale
references to it in docs and comments. NVS wipe is now a USB-side
`idf.py erase-flash` operation only.
- M5 follow-up TODO in jpeg_decoder.c: solid green renders ~black and
solid blue renders green; not a byte-order issue (BGR setting turns
red into blue). Tracked as a known M5 gap.
|
|
First end-to-end bench verification. Board is the Waveshare
ESP32-P4-ETH (Amazon B0FN7JQ2V8), 32 MB PSRAM, 32 MB flash silkscreen,
ESP-IDF v5.4.1. No panel attached, just board on USB power + LAN.
What passed
-----------
M1 — Ethernet driver up; DHCP lease landed at 10.0.13.83 (gw
10.0.13.1, netmask 255.255.255.0). MAC e8:f6:0a:e0:90:94. 31.7 MB
PSRAM free. Boot completes in ~4s after the kernel handoff.
M2 — GET http://10.0.13.83/state returns the full spec JSON shape
(name=null, configured=false, state=unconfigured, resolution=480x800,
ip=10.0.13.83, free_heap=520995, free_psram=31730048, all counters at
0). mDNS browse from macOS (dns-sd -B _scrypted-viewport._tcp local.)
finds one instance named "viewport" within ~100ms.
Bare-board behavior (Ethernet unplugged) verified too: boot completes
cleanly with summary line `boot complete — subsystems [EMHdJ-B]
ip=(no link)`. Lowercase d = display down because no panel attached;
- for touch because it shares the panel I²C bus.
README status snapshot updated to reflect M1+M2 ✅ on hardware;
TESTING.md M1 and M2 sections gain the 2026-06-14 verification note
plus the three fixes that bring-up exposed (RXD0/RXD1 swap, mDNS
hostname order, elegant subsystem degradation — already shipped in
commit 220ee4c).
|
|
First flash to real hardware (Waveshare ESP32-P4-ETH on USB power,
no LAN cable, no panel attached) exposed three bugs and the need to
degrade gracefully when peripherals are missing.
1. net_eth.c: RXD0 / RXD1 GPIO swap. ESP32-P4's EMAC iomux table
(components/soc/esp32p4/emac_periph.c) fixes RXD0 to GPIO 29 and
RXD1 to GPIO 30. The Waveshare-wiki/ESPHome research had them
transposed. Symptom was:
E (esp.emac.gpio): invalid RXD0 GPIO number
E (esp.emac): esp_eth_mac_new_esp32 failed
-> ESP_ERROR_CHECK abort
Fix: swap the two #defines. CRS_DV / TXD0 / TXD1 / TX_EN / REF_CLK
pinout was already correct.
2. mdns_service.c: mdns_service_add() rejects with INVALID_ARG when
the hostname isn't set yet (see mdns_responder.c:771 — first guard
in mdns_service_add_for_host). We were setting hostname AFTER
service_add inside apply_records(). Restructure mdns_service_start
to: init -> hostname -> service_add -> txt. apply_hostname() and
apply_txt() helpers reuse the same snapshot under viewport_state's
mutex; mdns_service_refresh() re-applies both.
3. app_main.c: every subsystem now best-effort instead of
ESP_ERROR_CHECK abort. A single missing peripheral can't take down
the rest of the firmware. End-of-boot summary line:
boot complete — subsystems [EMHdJ-B] ip=(no link)
Uppercase letter = up, lowercase = down. E=Ethernet M=mDNS H=HTTP
D=Display J=JPEG T=Touch B=BootButton. Touch shows '-' when display
didn't come up (it shares the panel I2C bus). On a stripped board
(just ESP32-P4-ETH on USB, no panel, no LAN) the line above prints
and the device serves /state, /config, and mDNS over the loopback;
plugging Ethernet in later picks up DHCP without a reboot.
Side-effects: the DHCP timeout in app_main shortened from 30s to 15s
so a no-cable boot finishes quickly. Reaching the "ready" state with
no link is fine — the driver keeps the link-up event handler armed and
gets the IP whenever a cable appears.
Verified on hardware (bare ESP32-P4-ETH, no LAN, no panel):
- ESP-IDF v5.4.1, esp32p4, 32 MB PSRAM detected, 16 MB flash config
(actual 32 MB silkscreen — keeping 16 MB until access-beyond-16MB
support lands in flash driver).
- Ethernet driver started — MAC e8:f6:0a:e0:90:94.
- mDNS up advertising viewport.local on _scrypted-viewport._tcp:80.
- HTTP up listening :80.
- JPEG decoder ready.
- Display reports "panel MCU @0x45 unreachable" — exactly correct
for no-panel state.
- Touch skipped as designed.
- BOOT button registered on GPIO 0 (still a guess; harmless if wrong).
This is enough to flip M1's "code" status to ✅ on hardware once
Ethernet is plugged in. M2 (mDNS browse + GET /state) ready to verify.
|
|
local_screens_show_ip() now renders four lines instead of two —
all the device's runtime identity at once:
line 1: viewport name ("mudroom" / "viewport" if unconfigured)
line 2: mDNS hostname ("viewport-mudroom.local" / "viewport.local")
line 3: IP ("192.168.1.42" / "no network")
line 4: state ("awake" / "asleep" / "unconfigured")
Same screen is shown on first boot, after factory reset, and as a
15s BOOT short-press overlay — operator always has the device's full
identity one button-press away (find it on the LAN, confirm name +
configuration without curling).
Font scale is auto-picked: the largest integer scale (1×–6×) where
the longest line fits within 90% of width AND all four lines plus
inter-line spacing fit within 90% of height. Works for both portrait
(480×800) and landscape (800×480) without separate code paths.
Font expanded to cover the new strings:
- All lowercase a–z (added b, f, h, j, k, m, q, s, u, x, y, z).
- Punctuation: dash, slash (digits, period, colon already covered).
Fallback policy unchanged: unsupported chars render as blank, which
keeps the table tight and the failure mode visible rather than
crashing.
README "Local rendering" + TESTING.md M8 updated to describe the
four-line identity layout and show both the unconfigured-boot and
configured-overlay example outputs.
Build verified clean against ESP-IDF 5.4 (binary ~873 KB, +1.6 KB
for the expanded font).
|
|
DESIGN.md was ~80% redundant with README.md (project summary, hardware,
API contract, callback contract, scrypted integration, milestone
acceptance criteria) and ~20% implementation guidance that's now better
served by the source code itself + TESTING.md. Removed it and folded
the unique parts into README.
README.md additions:
- "Status" table at the top: firmware, Scrypted side, hardware — each
with where-we-are + what's-pending columns.
- "Firmware implementation notes" section: source map (one-line per
main/*.{h,c} module), memory strategy, display strategy, error
handling, coding standards. Replaces DESIGN.md sections 7-11, 15, 17.
- "What's next" section before Philosophy: nine items in priority
order, each linking the specific TESTING.md or scrypted/ section.
Makes "pick up cold" trivial.
- Philosophy section absorbed DESIGN.md's "Guiding Rule" as a
blockquote at the bottom.
- "Related docs" rewritten — TESTING.md is now described as *the*
self-contained verification reference.
TESTING.md additions:
- Preamble now describes the five-layer structure and the
⬜/🟡/✅ legend.
- New "Status snapshot" table — milestones + code + HW at a glance.
- New "Hardware prerequisites" — table of what to confirm/order,
plus a numbered list of the four open unknowns (DSI FPC pin count,
I²C jumper destinations, BOOT GPIO, flash silkscreen).
- New "Recommended bench order" — seven stages from "board on
Ethernet over USB" through "integration suite," each with the
commands or where-to-look.
DESIGN.md deleted. Single source of truth is now README.md (spec +
implementation notes + roadmap) and TESTING.md (verification
reference).
|
|
scrypted/scrypted-viewport.ts — single-file TypeScript script for
Scrypted's Scripts plugin. Binds N viewports to N Scrypted cameras and
implements the full Scrypted side of the protocol:
Outbound (Scrypted -> device):
- POST /config on script load + every 5 min (re-syncs after device
reboot or DHCP renumber).
- POST /state {wake} when a bound camera fires an event or when the
device's own POST /state {wake} arrives (operator tap).
- POST /frame at FRAME_INTERVAL_MS (1 fps default) via
camera.takePicture({picture:{width,height}}).
- POST /state {sleep} when the Scrypted-side per-stream timer
expires.
Inbound (device -> Scrypted POST <base>/state):
- {state:wake} -> startStream() (cancels pending sleep timer + any
prior interval, then begins pushing frames).
- {state:sleep} -> stopStream(sendSleep=false) (no echo back).
- Unknown viewport -> 404. Bad body -> 400.
Race rules from the spec are honored:
- Every callback cancels prior pending state for that viewport
before applying the new state (cancelPendingSleep semantics).
- startStream / stopStream are idempotent — re-entering is safe.
- On /frame returning 409, stream stops immediately without
echoing /state {sleep} back (the device already slept).
Configuration is a single BINDINGS array at the top of the file —
edit name/host/cameraId/orientation per viewport and save in the
Scrypted UI. No package install. Tuning constants
(IDLE_TIMEOUT_MS, FRAME_INTERVAL_MS, REREGISTER_INTERVAL_MS, etc.)
are right above the class.
scrypted/README.md walks through Scripts-plugin install, the BINDINGS
schema, what each constant controls, an event-by-event description of
the script's behavior, the v1 limitations (snapshot-rate, manual IP,
camera must respect picture dims), and an end-to-end smoke test.
Top-level README's Scrypted Integration section now links the script
file and the install README directly.
|
|
|
|
Three structural shifts:
1. Discovery section spells out mDNS-SD. The ESP32 runs the ESP-IDF
mdns responder and serves all .local records itself — A, SRV, PTR,
TXT. Scrypted browses _scrypted-viewport._tcp.local with a Node
mDNS-SD library that hits the multicast layer directly. Scrypted
uses the IP from the SRV/A record, NOT OS-level .local hostname
resolution. Re-browse every few minutes for DHCP renumbering.
Manual host:port fallback for non-mDNS deployments.
2. Drop "callback" framing. The device and Scrypted are HTTP peers,
both exposing POST /state with the same body shape {viewport,state}.
Either side can push to the other to set state. Section is renamed
"Device -> Scrypted POST /state". No new vocabulary or semantics —
just the truth about what the protocol is.
- state_post_failures (was: callback_failures)
- state_client.c (was: callback_client.c)
- "callback" replaced with "outbound POST" / "inbound POST" / just
"POST /state" throughout.
3. Explicit no-application-level-ack clause. HTTP 2xx is transport-
only. Device does not retry, does not block subsequent state
changes on the response, does not treat 5xx as anything beyond a
counter increment. Idempotency + /frame 409 + each side's idle
timer cover every failure mode. Scrypted-side logic must not wait
for the device to confirm a state change.
Also: Scrypted Integration code in v2 guide rewritten to discover via
bonjour-service browse rather than hardcoded viewport-<name>.local
URLs. urlFor(name) function resolves the discovered IP per call.
mdns_service.c module spec now explicitly notes Scrypted uses the
browse, not the hostname.
|
|
API surface is now:
GET /state runtime status (replaces /health)
GET /config persisted config
POST /config partial update; brightness is now a field here
POST /state {state: wake|sleep} (replaces /wake and /sleep)
POST /frame unchanged
POST /config now does a partial merge — only present fields are
written. To tweak only brightness, POST {"brightness": 50}.
POST /state replaces POST /wake and POST /sleep with a single
endpoint that takes {state: "wake"|"sleep"}. Idempotent: no-op if
already in the requested state.
Brightness is no longer its own endpoint; it lives in /config with
all the other persisted settings. PWM remains gamma-corrected.
Callback body renamed:
was: {event: wake|sleep, type: tap|timeout}
now: {state: wake|sleep, event: tap|timeout}
The new naming matches POST /state: "state" is the resulting wake/
sleep state and the imperative for Scrypted; "event" is the cause
(tap, timeout, future swipes).
Doc sync: README API/Wake-Sleep/Idempotency/Integration sections and
v2 impl guide API contract, modules, milestones, acceptance criteria
all updated to the new model.
|
|
State model is now strictly wake/sleep, owned by the device:
- Local triggers: tap (toggle), idle timeout (sleep only).
- Remote triggers: POST /wake, POST /sleep. Scrypted decides why
(doorbell, motion, person, schedule) — the device does not know.
- /frame is pure paint: 204 when awake, 409 when asleep, no implicit
wake. Eliminates the race where an in-flight frame could re-wake a
device that just slept on a tap.
Orientation:
- Configurable via /config; default portrait.
- Effective resolution: 480x800 portrait, 800x480 landscape.
- Scrypted must render at the effective resolution. Device does not
rotate or scale JPEG content.
- Orientation flows through mDNS TXT, /health, IP/Loading screens.
Callbacks:
- Add `type` field carrying the cause: tap or timeout (forward-compat
for swipes etc).
- Drop wall-clock timestamp (no RTC, no SNTP).
Other:
- Boot: configured device boots to sleep (backlight off).
- /health adds state, last_frame_ms_ago, frame counters, error
counters, brightness, orientation, idle_timeout_ms.
- idle_timeout_ms: 0 disables; non-zero must be >= 5000; else 400.
- First-boot brightness default 80; apply gamma curve to PWM.
- Watchdog reboots hung tasks; recovers state from NVS.
- On-board status LED indicates network/config state.
- mDNS hostname-collision warning; ops notes.
- Idempotency table summarizing endpoint safety.
- Each viewport is bound 1:1 to a camera in Scrypted-side code.
- Reframe "no rendering engine" honestly as "no general-purpose UI
framework"; IP and Loading screens are the only locally-drawn UI.
Doc sync: v2 implementation guide endpoints, modules, milestones,
acceptance criteria, error handling, and tests all updated to match.
|
|
- Couple backlight and Scrypted streaming as a single wake/sleep state owned by the device.
- Callback events are idempotent imperatives: wake = "start streaming", sleep = "stop streaming". Scrypted does not track viewport state.
- Each viewport is bound to one camera in Scrypted-side code; camera events (doorbell, person, motion) drive streaming.
- idle_timeout_ms set by Scrypted via /config (default 60000). Same value on both sides; timers run independently and either can end a session.
- Tap input only (long_press/swipes out of scope for v1). Tap is internal; callback carries the resulting state.
- IP screen on first boot, after factory reset, and as a 15s BOOT short-press overlay.
- Loading screen between wake and the next /frame.
- BOOT: short press = IP overlay, hold 5s = factory reset.
- Note that Scrypted side is code (Script for v1, custom plugin for v2 with /stream), not configuration.
|
|
Expand README with per-endpoint contracts (response codes, persistence,
size limits, idle/wake semantics) and a Scrypted integration section
covering registration, frame push, and touch handling. Add v2
implementation guide adapted from the DoorView spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
|
Plain ESP-IDF project targeting Waveshare ESP32-P4-ETH-POE with a 5" 800x480
MIPI-DSI capacitive touch panel. Stub app_main with TODOs for Ethernet, mDNS,
HTTP API (/health, /config, /frame, /sleep, /brightness), JPEG decode, and
touch callback delivery per the v1 spec in README.md.
|