src.nth.io/

summaryrefslogtreecommitdiff
AgeCommit message (Collapse)AuthorFilesLines
3 daysrelease: v1.4.0v1.4.0Luke Hoersten2-2/+2
3 daystools: make targets + ota.sh for the build/OTA/verify loopLuke Hoersten2-0/+111
make build / cleanbuild / ota / verify / check wrap the recurring dev loop, sourcing the ESP-IDF env per-recipe. `make ota` reconfigures first (the embedded git hash stamps at configure time only), builds, pushes, and verifies. tools/ota.sh encodes the OTA acceptance criterion — the new image must show pending-verify on a fresh boot before flipping to valid; "valid" with pending-verify never seen means the device silently booted the old slot — and re-pushes once automatically on the known first-push rollback quirk.
3 daysscrypted: stamp SCRIPT_VERSION = 118a04fLuke Hoersten1-1/+1
3 daysscrypted: subscribe only to selected wake-trigger interfacesLuke Hoersten1-31/+50
attachListener listened on BinarySensor+MotionSensor+ObjectDetector unconditionally and relied on handleCameraEvent to filter by trigger — correct, but a doorbell-only viewport still took a callback per motion re-assert on a chatty camera, and the "subscribed to [...]" log implied all three were wake sources. The listener set now follows the selected triggers (zero triggers = tap-only: no listeners at all), and the idempotency key becomes a "cameraId|triggers" signature (attachedCameraId -> attachedListenerSig) so a trigger change forces a re-attach through the existing rebind path. Event-time filtering stays as defense in depth (ObjectDetector still needs the person-class check).
3 daysscrypted: name viewports from discovery; editable name on settings pageLuke Hoersten1-2/+43
Creating a viewport with the name field blank fell back to "viewport" with no way to change it afterward — display_name is deliberately the canonical name (v.name drifts on reload), so the Scrypted device-name pencil never propagated. Two fixes: - createDevice: an empty name now inherits the discovered device's advertised TXT name, so picking "10.0.13.83 — kitchen (…)" from the host dropdown names the viewport "kitchen" without retyping. - child settings gain a "Viewport name" field: updates display_name, mirrors the name onto the Scrypted device record, and re-registers so the firmware name + mDNS hostname follow.
3 daysscrypted: stamp SCRIPT_VERSION = 3821512Luke Hoersten1-1/+1
3 daysscrypted: mdns discovery — browse, host choices, auto-healLuke Hoersten2-6/+381
Browse for _scrypted-viewport._tcp with a plain dgram socket on an EPHEMERAL port: per RFC 6762 §6.7 a query from a non-5353 source port is a legacy unicast query and responders reply unicast to that port — verified in the ESP-IDF responder (mdns_send.c answers to the querier's addr/port whenever src_port != 5353). No :5353 bind means no conflict with Scrypted's own HomeKit mDNS or a host avahi-daemon, and it works under Docker host-networking or native alike. One response packet carries PTR+SRV+TXT+A (RFC 6763 §12.1), so parsing is per-packet with no follow-up queries; the wire codec (name compression included) is ~150 lines, no dependencies. Surfaced three ways: - add-device form: host field is a combobox listing discovered viewports as "ip — name (vX, WxH)"; manual entry still works and only the address token is stored. - child settings page: same choices on the existing host field. - auto-heal: when a /config register fails, re-browse and match by MAC (seeded from /state and discovery; survives renames) then by name; on a hit at a new address, rewrite host and retry once. Runs only on register failure, so it's rate-limited to the 5-min cycle and removes the need for a DHCP reservation. Browses are best-effort ([] on any failure) and cached 30s so settings re-renders don't spam the LAN. diagnostic.ts gains the same browse as a paste-and-run probe that validates dgram + multicast reachability from inside the real Scrypted sandbox before trusting the feature.
3 daysmain: advertise mac in mDNS TXTLuke Hoersten1-0/+5
Stable identity for the Scrypted plugin's discovery/auto-heal: the viewport name is user-editable and the IP can renumber, but the MAC is fixed. Discovery matches on it to relocate a device after a DHCP change.
3 daysscrypted: stamp SCRIPT_VERSION = 0ac000eLuke Hoersten1-1/+1
3 daysscrypted: code-review fixes — reload drain order, reconnect storm, wake guardsLuke Hoersten3-78/+191
- bootstrap: drain the shutdown-cleaner array FIRST, not after the child re-discovery loop. The old order tore down the listeners the loop had just attached, and since the instance maps still referenced them the attachListener fast-path blocked the 5-min register-cycle self-heal from ever re-attaching after a warm re-paste. The attach cleaner now also invalidates listeners/attachedCameraId (one cleaner per attach), so a drain by another instance can't strand dead registrations either. - stream socket: single-flight reconnect scheduled from 'close' only. A failed connect emits 'error' then 'close'; scheduling from both doubled outstanding attempts every 500ms against a rebooting device. openStreamSocket also destroys the previous socket first. - onRequest wake: same streams/streamStarting guard as every other start path (a concurrent second startStream overwrote the streams entry and orphaned the first ffmpeg), respond 204 immediately (the device's POST times out at 1s — awaiting startStream turned every tap-wake into a firmware state_post_failure), and skip the redundant wake POST back. - startStream: bail-out paths after the wake POST (camera missing, no usable stream, ffmpeg-input conversion failure) now send a compensating sleep instead of stranding the panel on Loading; the last-ditch getVideoStream fallback no longer rejects out of the call. - key streams/streamStarting/stopStream by nativeId — v.name drifts to the nativeId on reload, stranding or duplicating streams keyed under the drifted value; findByName matches display_name first for the same reason. - idle_timeout_ms=0: the Scrypted-side safety timeout still reclaims ffmpeg/socket but no longer POSTs sleep over the always-on setting. - pending bindingDebounce timers cleared across re-paste (they fired against the dead instance and attached duplicate listeners); releaseDevice drops lastRegisterSig/streamStarting/debounce entries. - start() single-flights bootstrap; createDevice drops its redundant second registerViewport; streamLogger rolls its window on quiet ticks (post-lull rates were diluted) and lastLogUs -> lastLogMs; hoist the JPEG EOI needle + resume-scan offset in the demux loop; postJSON drops the dead 204 check; remove unused sandbox declares and the legacy streams.interval field. - tsconfig: moduleResolution Node -> Bundler (removed in TS 6).
3 daysmain: code-review fixes — brightness units, OTA stall cap, stats + lockingLuke Hoersten14-102/+143
- display: s_last_pwm cached the raw 0-100 percentage at init but display_wake writes it straight to REG_PWM (0-255 duty) — first wake ran visibly dim until a /config brightness change. Shared pct_to_duty helper now converts in both paths. - http_api: cap consecutive OTA recv timeouts (a stalled client spun forever holding s_ota_in_progress, wedging OTA until reboot); cap viewport name at 54 chars so viewport-<name> fits the 63-byte mDNS label; log mdns_service_refresh failures; /state builds JSON from a snapshot instead of holding the state lock across ~25 cJSON allocs; respond_400 delegates to respond_status. - stream_server: bytes_in_window now counts painted frames only (frames discarded while asleep inflated the first post-wake window's MB/s, avg-jpeg and chunk/wire averages; recv_bytes folded in); so_rcvbuf carried into the stats snapshot under the mux; drop dead HEADER_BYTES; merge read_body_instrumented into read_n. - state_machine: transition mutex serializes concurrent wake/sleep (display side-effects ran after the state lock dropped, so racing callers could leave the backlight contradicting st->state); wake-path placeholder paint now takes the decoder lock like the stream path (concurrent esp_lcd_panel_draw_bitmap from two tasks isn't safe). - local_screens: overlay paint takes the decoder lock too; check the overlay timer create; panel dims from viewport_state.h. - jpeg_decoder: try_lock before init returns busy instead of passing a NULL semaphore to xSemaphoreTake. - net_eth: clear cached IP string on link down (stale /state + info). - dead code: display_present_bgr888, TOUCH_FT5426_ADDR, touch s_task, JPEG_DECODER_MAX_OUTPUT_BYTES; doc drift in nvs_config.h / jpeg_decoder.h / app_main flag legend.
5 daysrelease: v1.3.2v1.3.2Luke Hoersten2-2/+2
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.
5 daysscreens: raise INFO_MAX_LINES 16 -> 20 (temp line was silently dropped)Luke Hoersten1-1/+3
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.
5 daysscrypted: stamp SCRIPT_VERSION = 2af69f7Luke Hoersten1-1/+1
5 daysscrypted: temp_c on the stats line; docs: triple-buffer model + window tuningLuke Hoersten2-6/+24
- 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).
5 daystemp: report on-die temperature in /state and the info screenLuke Hoersten6-3/+70
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.
5 daysdisplay: tear-free frame path via triple buffering + scan trackingLuke Hoersten3-23/+101
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 -> 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.
5 daysrelease: v1.3.1v1.3.1Luke Hoersten2-2/+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.
5 dayseth: size EMAC RX DMA pool above the TCP window (1600B x 24)Luke Hoersten1-0/+9
WND=23040 alone regressed: sender fps 20 -> 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 >= window + slack. ~44KB more internal RAM (491KB free).
5 dayslwip: raise TCP_WND 5760->23040, RECVMBOX 6->32 (window step 1)Luke Hoersten1-10/+14
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 -> ~20ms, wire toward line rate, sender bp%/drop-oldest down, g2g 73ms -> ~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.
5 daysstream: instrument TCP-window decomposition (wire kbps, hdr_gap, pend_age)Luke Hoersten3-4/+117
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->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.
2026-07-01scrypted: stamp SCRIPT_VERSION = 01a588eLuke Hoersten1-1/+1
2026-07-01scrypted: fix stale Streaming header comment (TCP socket, q:v default 1, ↵Luke Hoersten1-5/+7
skip-oldest)
2026-07-01release: v1.3.0v1.3.0Luke Hoersten2-2/+2
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.
2026-07-01scrypted: stamp SCRIPT_VERSION = 0be8f18Luke Hoersten1-1/+1
2026-07-01scrypted: trim stream logging volumeLuke Hoersten1-18/+30
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 >=2fps, or painted <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.
2026-06-30docs: reframe protocol sections for the TCP-socket streaming modelLuke Hoersten1-10/+12
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.
2026-06-30docs: update main README streaming section to the shipped TCP-socket pathLuke Hoersten1-16/+17
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.
2026-06-30docs: bring scrypted/README.md in line with current streaming behaviorLuke Hoersten1-16/+22
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 -> live video, not "snapshots flowing"
2026-06-30docs: document required Medium-stream prebuffer for fast wakeLuke Hoersten1-0/+11
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 >= the detected keyframe interval (~5s), leave the viewport's Stream prebuffer (ms) at 6000, and how to verify via the log.
2026-06-30firmware: clear panel to Loading screen on new stream connectionLuke Hoersten1-0/+15
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.
2026-06-30scrypted: stamp SCRIPT_VERSION = c6610c1Luke Hoersten1-1/+1
2026-06-30scrypted: remove the pre-stream snapshot entirelyLuke Hoersten1-156/+12
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).
2026-06-30scrypted: stamp SCRIPT_VERSION = 39aeeffLuke Hoersten1-1/+1
2026-06-30scrypted: gate snapshot POST on the live stream not yet paintingLuke Hoersten1-5/+23
Now that the prebuffered stream paints in ~0.7s, the parallel snapshot usually loses the race and, if slow (a Unifi takePicture cache-miss can be several seconds), would land after live video and overpaint it with a staler still. pushSnapshot takes a shouldPaint predicate; startStream passes () => !firstStreamFrameSeen (flipped by the ffmpeg first-frame handler), so a snapshot that finishes after the stream is already painting is dropped before the POST. Preserves the snapshot as a gap-filler for the slow paths (live-edge fallback / cold prebuffer) where the stream hasn't painted yet.
2026-06-30scrypted: stamp SCRIPT_VERSION = 860f539Luke Hoersten1-1/+1
2026-06-30scrypted: strip cold-start/event diagnosticsLuke Hoersten1-110/+7
Remove the temporary instrumentation used to diagnose the event-wake and cold-start work: the evtscan system-wide listener, the attach/trace/streamopt/ src/inputArgs logs, the StartStop entry logs, and ffmpeg -loglevel info/-nostats (back to error). Keep the lightweight one-shot cold-start stamps (spawned → first byte → first frame → first socket.write) and usingPrebuffer in the config line — cheap regression signals — plus all the actual fixes and the prebuffered- stream selection logic.
2026-06-30scrypted: stamp SCRIPT_VERSION = 24d6e90Luke Hoersten1-1/+1
2026-06-30scrypted: fix event wake, teardown, triggers; cut stream cold-start ~6s→~0.7sLuke Hoersten1-47/+390
Event/lifecycle fixes: - attachListener now self-heals: it's idempotent (tracks the bound camera per viewport) and re-runs on registerViewport success, so the camera subscription survives the reload/add storage-attach race that previously left a bound viewport with no listener until a manual re-save. - stop() always drains the global cleaner array (never gates on this.running) and also cancels bindingDebounce timers + clears the stream idle timeout on abort, so StartStop.stop()+start() == a fresh load with no orphaned ffmpeg, sockets, listeners, or intervals. - ignore all camera events while a stream is live or starting (guard moved to the top of handleCameraEvent) — the wake window is anchored to the first event; later events don't queue, relaunch, or extend it. - prune stale childIds whose storage container is gone (deleted device). Triggers: - default to person + doorbell (motion opt-in; doorbell cameras are chatty); doorbell only offered for doorbell-capable cameras. Reconcile stored triggers + re-render settings live when the camera binding changes. Cold-start (wake -> first live frame): ~6s -> ~0.7s. - Root cause: Unifi GOP is ~5s and the rebroadcast RTSP serves live-edge, so ffmpeg waited for the next keyframe. - Fix: select the prebuffered substream by id (smallest that covers the panel, not the 5MP High), request prebuffer >= GOP, and drop -avioflags direct / -fflags nobuffer on the prebuffer path so ffmpeg gulps the buffered-keyframe burst instead of trickling it. Live-edge fallback keeps the low-latency flags. Steady-state g2g unchanged (~50-100ms). Includes temporary diagnostics (evtscan, attach/trace/streamopt/src logs, ffmpeg -loglevel info) to be stripped in the follow-up commit.
2026-06-21scrypted: stamp SCRIPT_VERSION = 1bd5e9fLuke Hoersten1-1/+1
2026-06-21scrypted: subscribe log includes the interface listLuke Hoersten1-1/+1
2026-06-21scrypted: stamp SCRIPT_VERSION = 3a7ddbdLuke Hoersten1-1/+1
2026-06-21scrypted: fix listen() — one call per interface (was passing array, filter ↵Luke Hoersten1-51/+17
broken) ScryptedDevice.listen() takes a single ScryptedInterface | string | EventListenerOptions, not an array. We were passing [BinarySensor, MotionSensor, ObjectDetector] which stringifies to a comma-joined garbage filter, leaking unrelated events (Settings, ...) through and apparently dropping BinarySensor entirely — bell rings never surfaced. Also drops the child-device traversal: confirmed via unifi-protect/src/main.ts that the bell BinarySensor lives on the camera device itself for doorbells.
2026-06-21scrypted: stamp SCRIPT_VERSION = d2b75bdLuke Hoersten1-1/+1
2026-06-21scrypted: diagnostic — enumerate every BinarySensor deviceLuke Hoersten1-10/+10
2026-06-21scrypted: stamp SCRIPT_VERSION = c901984Luke Hoersten1-1/+1
2026-06-21scrypted: log every onBindingChanged re-attach with host/cameraIdLuke Hoersten1-0/+2
2026-06-21scrypted: stamp SCRIPT_VERSION = 5d7549bLuke Hoersten1-1/+1
2026-06-21scrypted: widen doorbell traversal — include BinarySensor siblings + ↵Luke Hoersten1-1/+26
diagnostic
2026-06-21scrypted: stamp SCRIPT_VERSION = f6d8e75Luke Hoersten1-1/+1