src.nth.io/

summaryrefslogtreecommitdiff
path: root/scrypted
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-21 11:28:04 -0500
committerLuke Hoersten <[email protected]>2026-06-21 11:28:04 -0500
commit101fb3e28ab56361a613b7b25adab1f1f4a03483 (patch)
tree57da45f9120d1945487e0637d1889e0bbd13fd30 /scrypted
parent344843f3a3db50a8ea9f762979ae7fb780e5ad90 (diff)
scrypted: add OnOff alongside StartStop + lifecycle logging to identify panel binding
The 'Status and Controls' panel in @scrypted/core 0.3.147 was rendering Unknown despite the previous commit adding StartStop. Reading the SDK (sdk/src/index.ts:195 + plugins/core/src/script.ts:47) confirmed how it should work: - ScryptedDeviceBase installs Object.defineProperty getters/setters for every interface property at runtime, so this.running = true propagates through _lazyLoadDeviceState → getDeviceState proxy → system state. - Scripts plugin's mergeHandler auto-detects interfaces by mapping method names: start/stop → StartStop, turnOn/turnOff → OnOff, putSetting/getSettings → Settings, etc. In theory the previous commit was sufficient. To pin down whether the panel is calling something different (older Scrypted UIs lean toward OnOff), this commit: - Implements OnOff alongside StartStop. Both pairs are bound to the same drain/bootstrap logic; whichever the panel calls, the user's intent succeeds. - Initialises this.running = false and this.on = false synchronously in the constructor so the device state record carries a defined value at registration time, instead of Scrypted defaulting to Unknown on undefined. - Logs 'lifecycle: X() called (...)' on every method entry. After the user reloads + clicks STOP / START, the console will reveal exactly which method Scrypted is invoking — or whether neither is. If one of the methods fires, the other is dead code we can drop. If neither fires on the panel's STOP/START button, the panel is a Scripts-runtime control unrelated to device interfaces and we need to look for a different lifecycle hook.
Diffstat (limited to 'scrypted')
-rw-r--r--scrypted/scrypted-viewport.ts58
1 files changed, 41 insertions, 17 deletions
diff --git a/scrypted/scrypted-viewport.ts b/scrypted/scrypted-viewport.ts
index 09a8cbd..e8a950f 100644
--- a/scrypted/scrypted-viewport.ts
+++ b/scrypted/scrypted-viewport.ts
@@ -83,6 +83,7 @@ type Setting = any;
type Settings = any;
type SettingValue = any;
type StartStop = any;
+type OnOff = any;
// ---------------------------------------------------------------------------
// Cross-reload shutdown cleaners.
@@ -372,7 +373,7 @@ class Viewport extends ScryptedDeviceBase implements Settings {
// ============================================================================
class ScryptedViewportProvider extends ScryptedDeviceBase
- implements DeviceProvider, DeviceCreator, HttpRequestHandler, Settings, StartStop {
+ implements DeviceProvider, DeviceCreator, HttpRequestHandler, Settings, StartStop, OnOff {
private viewports = new Map<string, Viewport>(); // nativeId -> child instance
private listeners = new Map<string, EventListenerRegister[]>(); // nativeId -> all event listeners for this viewport (camera + child devices)
@@ -385,46 +386,69 @@ class ScryptedViewportProvider extends ScryptedDeviceBase
constructor(nativeId?: string) {
super(nativeId);
- // Auto-start on script load. StartStop.start() is idempotent — if the
- // user later clicks STOP in the Scripts UI, .stop() drains everything
+ // Initialise both lifecycle-interface state fields synchronously
+ // so the device record has values for them at registration time.
+ // Without this Scrypted's "Status and Controls" panel reads
+ // undefined and renders 'Unknown' regardless of what our async
+ // bootstrap eventually sets.
+ this.running = false;
+ this.on = false;
+ // Auto-start on script load. start() is idempotent — if the user
+ // later clicks STOP in the Scripts UI, stop() drains everything
// and start() doesn't re-fire until they click START. Across a
- // script re-paste the constructor runs again, .start() drains any
+ // script re-paste the constructor runs again, start() drains any
// prior load's resources first and re-bootstraps cleanly.
this.start().catch(e => this.console.error("start failed", e));
}
// ------------------------------------------------------------------------
- // StartStop — public lifecycle controls exposed to the Scrypted UI
+ // Lifecycle interfaces (StartStop + OnOff) — both bound to bootstrap/drain
// ------------------------------------------------------------------------
//
- // Scrypted renders this device's "Status and Controls" panel from the
- // StartStop interface: `running` drives the displayed status and the
- // STOP / START buttons call .stop() / .start() respectively.
+ // Scrypted renders this device's "Status and Controls" panel from a
+ // lifecycle interface. Which one — StartStop, OnOff, both, or
+ // something Scripts-plugin-specific — varies across versions, so we
+ // expose both and keep them in lockstep. Whichever the UI calls,
+ // the user's STOP button drains resources and START re-bootstraps.
//
- // Semantics:
- // start(): drain any leftover resources, then bootstrap. Idempotent
- // (no-op if already running). Auto-called from the
- // constructor on script load.
- // stop(): drain every resource (streams, listeners, intervals) the
- // provider holds. No-op if already stopped.
+ // Semantics (shared):
+ // start() / turnOn(): drain any leftover resources, then bootstrap.
+ // Idempotent (no-op if already running).
+ // Auto-called from the constructor.
+ // stop() / turnOff(): drain every resource (streams, listeners,
+ // intervals) and clear in-memory maps. No-op
+ // if already stopped.
//
- // The "Unknown" status seen before this commit was Scrypted's fallback
- // for a device that exposes no lifecycle interface. With StartStop in
- // place the UI shows Running / Stopped accurately.
+ // Every call logs so we can confirm from the Scrypted console which
+ // method the Status panel actually wires to. Once one is confirmed
+ // we can drop the other (and the logging).
async start() {
+ this.console.log(`lifecycle: start() called (running=${this.running})`);
if (this.running) return;
await this.bootstrap();
this.running = true;
+ this.on = true;
}
async stop() {
+ this.console.log(`lifecycle: stop() called (running=${this.running})`);
if (!this.running) return;
drainShutdownCleaners(this.console, "stop");
this.viewports.clear();
this.listeners.clear();
this.streams.clear();
this.running = false;
+ this.on = false;
+ }
+
+ async turnOn() {
+ this.console.log(`lifecycle: turnOn() called (on=${this.on})`);
+ return this.start();
+ }
+ async turnOff() {
+ this.console.log(`lifecycle: turnOff() called (on=${this.on})`);
+ return this.stop();
}
// ------------------------------------------------------------------------