| Age | Commit message (Collapse) | Author | Files | Lines |
|
|
|
After the first camera event fires on a freshly-created viewport, v.name
sometimes resolves as "" (Scrypted device-record load racing with event
delivery). registerViewport then POSTs /config with viewport="" and the
firmware returns 400 "viewport must be a non-empty string". The handler
retries on the next callback so it eventually self-heals, but the log is
noisy and we'd needlessly fail one register cycle.
Mirror the canonical display name into device storage (display_name)
on createDevice + on every successful register, and have registerViewport
+ attachListener fall back to that storage value when v.name is empty.
No more empty-name POSTs to the firmware.
|
|
script reload
|
|
endpointManager.getInsecurePublicLocalEndpoint
|
|
|
|
they're injected
The probe in scrypted/diagnostic.ts confirmed @scrypted/core 0.3.147
injects every name we need (ScryptedDeviceBase, ScryptedDeviceType,
ScryptedInterface, systemManager, deviceManager, mediaManager,
endpointManager, log, device, require, exports) as top-level scope
variables. Notably sdk.ScryptedDeviceBase IS undefined — the classes
are separate injections, not properties on the sdk object.
So the correct pattern is `declare const X: any` for each (declarations
erase at compile time → free identifiers at runtime → bound to the
scryptedEval-injected values), not `const X = sdk.X` (which was
undefined and tripped "Class extends value undefined").
|
|
being a separate injection
|
|
The scryptedEval sandbox pre-injects SDK names (ScryptedDeviceBase,
ScryptedDeviceType, ScryptedInterface, systemManager, deviceManager,
mediaManager, endpointManager, log, device, sdk, require) into the
script's scope. The previous attempt destructured them from `sdk`,
but on user's Scrypted 0.143.0 / @scrypted/core 0.3.147 something in
the compile pipeline was still emitting a `require('@scrypted/sdk')`
that failed to resolve.
Switch to `declare const <name>: any` for each runtime value — the
declarations fully erase at compile time, no require is emitted, and
the values come straight from the sandbox scope.
|
|
@scrypted/sdk import
The Scripts plugin sandbox (in @scrypted/core) doesn't resolve ESM
imports of npm modules — saving the script with `import sdk, {...} from
'@scrypted/sdk'` produced:
Error: Cannot find module '@scrypted/sdk'
...
at t.scryptedEval (.../scrypted-eval.ts:102:29)
Fix: destructure runtime values from the `sdk` global the plugin
injects, use `require('dns').promises` for the Node mDNS lookup, and
declare runtime type aliases as `any` so the file still parses on a
machine without @scrypted/sdk installed locally.
Also drop the `as ScryptedInterface` cast in handleCameraEvent — it was
the only place an SDK enum was used as a type rather than a value, and
just storing the string is fine.
Editor-only diagnostics (NodeJS namespace, Buffer) remain — they
resolve in the Scrypted Node runtime and only show up in clangd/tsc
without @types/node locally.
|
|
The host field is now optional in "+ Add Device". The script tries
dns.promises.lookup("viewport-<name>.local") via the OS resolver
(Bonjour on macOS, nss-mdns on Linux, host networking on Docker) on:
- every registerViewport call (plugin start, child instantiation,
settings change, periodic 5-min refresh),
- right after createDevice so a fresh viewport's host field is
populated by the time the operator opens its settings page.
On a successful lookup that differs from the stored host, the
resolved IP is written back to the child's storage. POST /config and
POST /frame then use the resolved value.
Falls back gracefully to the operator-entered host (with a one-line
warning if both are empty). A per-viewport "Auto-resolve via mDNS"
toggle (default on) opts out — useful for cross-VLAN setups or hosts
where mDNS doesn't reach.
scrypted/README.md adds a "How mDNS auto-resolve works" section
covering the per-OS resolver requirements and the Docker host-
networking note.
|
|
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 <base>/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.
|
|
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.
|