@camstack/addon-provider-reolink 1.2.166 → 1.2.168
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon.js +346 -212
- package/dist/addon.mjs +346 -212
- package/package.json +1 -1
package/dist/addon.mjs
CHANGED
|
@@ -25,7 +25,7 @@ import { mkdir as mkdir$1 } from "fs/promises";
|
|
|
25
25
|
import { spawn as spawn$1 } from "node:child_process";
|
|
26
26
|
import os, { tmpdir } from "node:os";
|
|
27
27
|
import { createReadStream } from "node:fs";
|
|
28
|
-
//#region ../types/dist/event-category-
|
|
28
|
+
//#region ../types/dist/event-category-BGmz_oxW.mjs
|
|
29
29
|
var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
30
30
|
EventCategory["SystemBoot"] = "system.boot";
|
|
31
31
|
EventCategory["SystemAddonsReady"] = "system.addons-ready";
|
|
@@ -558,6 +558,15 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
|
|
|
558
558
|
*/
|
|
559
559
|
EventCategory["PipelineAudioInferenceResult"] = "pipeline.audio-inference-result";
|
|
560
560
|
EventCategory["DetectionPhaseTransition"] = "detection.phase-transition";
|
|
561
|
+
/**
|
|
562
|
+
* A camera provider's RAW onboard motion push (D673): the `MotionOnMotionChanged`
|
|
563
|
+
* data shape without `triggeredBy`, built by `providerMotionEvent`. Its one
|
|
564
|
+
* consumer is the orchestrator's camera-sensors bridge, which re-emits the
|
|
565
|
+
* camera's EFFECTIVE onboard motion as `MotionOnMotionChanged` (a pass-through
|
|
566
|
+
* for a camera with no attached sensor). Providers must not emit
|
|
567
|
+
* `MotionOnMotionChanged` themselves; `scripts/check-onboard-motion-emitters.ts`
|
|
568
|
+
* keeps it so (camera-grid is a named, deferred exception).
|
|
569
|
+
*/
|
|
561
570
|
EventCategory["ProviderMotion"] = "provider.motion";
|
|
562
571
|
EventCategory["ProviderDetection"] = "provider.detection";
|
|
563
572
|
EventCategory["EnrichmentEmbeddingStored"] = "enrichment.embedding.stored";
|
|
@@ -5396,7 +5405,7 @@ var ZodIssueCode = {
|
|
|
5396
5405
|
var ZodFirstPartyTypeKind;
|
|
5397
5406
|
ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
|
|
5398
5407
|
//#endregion
|
|
5399
|
-
//#region ../types/dist/sleep-
|
|
5408
|
+
//#region ../types/dist/sleep-CDCONfkt.mjs
|
|
5400
5409
|
/**
|
|
5401
5410
|
* The audio chunk plane's byte format, and the ONE expansion from a coded
|
|
5402
5411
|
* window to float samples (D455).
|
|
@@ -7487,7 +7496,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
|
|
|
7487
7496
|
return DeviceRole;
|
|
7488
7497
|
}({});
|
|
7489
7498
|
//#endregion
|
|
7490
|
-
//#region ../types/dist/composition-
|
|
7499
|
+
//#region ../types/dist/composition-43aqsZQj.mjs
|
|
7491
7500
|
/**
|
|
7492
7501
|
* The ffmpeg build every CamStack node downloads, per platform and arch.
|
|
7493
7502
|
*
|
|
@@ -7544,13 +7553,12 @@ portable("linux64"), portable("linuxarm64"), portable("macarm64"), portable("mac
|
|
|
7544
7553
|
* LA tabella "quale booleano di questo tipo di device conta come ALTO", e il
|
|
7545
7554
|
* valutatore puro del suo FRONTE.
|
|
7546
7555
|
*
|
|
7547
|
-
*
|
|
7548
|
-
*
|
|
7549
|
-
*
|
|
7550
|
-
*
|
|
7551
|
-
*
|
|
7552
|
-
* fa suonare il campanello ma non registra" — è esattamente D62.
|
|
7553
|
-
* SPOSTA qui e il doorbell la ri-esporta.
|
|
7556
|
+
* UNA tabella per tutti quelli che chiedono "questo device è ALTO?": il
|
|
7557
|
+
* trigger `RecordingTriggers.sensorDeviceIds` del recorder, i marker dei
|
|
7558
|
+
* sensori della post-analysis, il bridge camera-sensors dell'orchestratore e
|
|
7559
|
+
* le sorgenti di un campanello composto (`doorbellFeatureSources`, D671).
|
|
7560
|
+
* Una seconda copia divergerebbe alla prima cap aggiunta, e il sintomo — "il
|
|
7561
|
+
* sensore fa suonare il campanello ma non registra" — è esattamente D62.
|
|
7554
7562
|
*
|
|
7555
7563
|
* ⚠ NON è `DEVICE_STATE_READERS` (`catalogs/device-state-vocabulary.ts`), e le
|
|
7556
7564
|
* due non vanno unificate: quella risponde a "qual è la PAROLA di stato per una
|
|
@@ -7576,8 +7584,6 @@ var SOURCE_CAP_ACTIVE_FIELD = {
|
|
|
7576
7584
|
vibration: "detected",
|
|
7577
7585
|
tamper: "tampered"
|
|
7578
7586
|
};
|
|
7579
|
-
/** Cap names whose presence in a device's bindings qualify it as a source. */
|
|
7580
|
-
var SOURCE_CAPS = Object.keys(SOURCE_CAP_ACTIVE_FIELD);
|
|
7581
7587
|
/**
|
|
7582
7588
|
* Error types for the safe expression engine. Two distinct classes so callers
|
|
7583
7589
|
* can tell a compile-time (grammar) failure from a runtime (evaluation)
|
|
@@ -9307,7 +9313,7 @@ var RecordingTriggersSchema = object({
|
|
|
9307
9313
|
*
|
|
9308
9314
|
* Cosa vuol dire "alto" dipende dal TIPO di device e non è deciso qui:
|
|
9309
9315
|
* `SOURCE_CAP_ACTIVE_FIELD` (`catalogs/sensor-active-state.ts`) è LA tabella,
|
|
9310
|
-
* la stessa
|
|
9316
|
+
* la stessa del campanello composto (D671). Ed è il FRONTE, non
|
|
9311
9317
|
* il livello: un contatto trovato già aperto al riavvio del runner non fa
|
|
9312
9318
|
* registrare.
|
|
9313
9319
|
*/
|
|
@@ -15449,7 +15455,17 @@ var SiteZoneSchema = object({
|
|
|
15449
15455
|
var ProviderStatusSchema = object({
|
|
15450
15456
|
connected: boolean(),
|
|
15451
15457
|
deviceCount: number(),
|
|
15452
|
-
error: string().optional()
|
|
15458
|
+
error: string().optional(),
|
|
15459
|
+
/**
|
|
15460
|
+
* `'orchestrator'`: this provider writes NO camera's `motion` runtime slice
|
|
15461
|
+
* — the orchestrator is its only writer (D696). Set by `BaseDeviceProvider`,
|
|
15462
|
+
* which every provider bundles, so only a provider BUILT from D696-amended
|
|
15463
|
+
* code says it. ABSENT means an older provider that may still write it (raw
|
|
15464
|
+
* VMD writes, a boot seed), and the orchestrator's provider gate keeps that
|
|
15465
|
+
* provider's cameras unwritten (`camera-sensors/motion-slice-provider-gate.ts`)
|
|
15466
|
+
* — the provider twin of the runner's `getLocalLoad().motionSliceWriter`.
|
|
15467
|
+
*/
|
|
15468
|
+
motionSliceWriter: literal("orchestrator").optional()
|
|
15453
15469
|
});
|
|
15454
15470
|
object({
|
|
15455
15471
|
externalId: string(),
|
|
@@ -16328,109 +16344,10 @@ method(object({
|
|
|
16328
16344
|
deviceIds: array(number()).readonly(),
|
|
16329
16345
|
caps: array(string()).readonly().optional()
|
|
16330
16346
|
}), record(string(), record(string(), unknown().nullable())));
|
|
16331
|
-
|
|
16332
|
-
|
|
16333
|
-
* name (same pattern as `snapshot`):
|
|
16334
|
-
*
|
|
16335
|
-
* - **Native** providers: registered per-device by device-driver
|
|
16336
|
-
* addons via `ctx.registerNativeCap` — either on a
|
|
16337
|
-
* `DeviceType.Button` accessory with `role: DeviceRole.Doorbell`,
|
|
16338
|
-
* or directly on the camera (Reolink registers at camera level).
|
|
16339
|
-
* Emits an `onPressed` event every time the firmware pushes a
|
|
16340
|
-
* ring; status tracks the last press and a pressCount since start
|
|
16341
|
-
* (diagnostic).
|
|
16342
|
-
*
|
|
16343
|
-
* - **Wrapper** provider: the `virtual-doorbell` system builtin
|
|
16344
|
-
* (`@camstack/system/builtins/doorbell`). Turns ANY binary-ish
|
|
16345
|
-
* device (contact / switch / event-emitter …) into a doorbell for
|
|
16346
|
-
* a camera. `defaultActive: false` — the operator explicitly binds
|
|
16347
|
-
* it per camera in the device-bindings UI, then picks the source
|
|
16348
|
-
* device + trigger in the per-device settings.
|
|
16349
|
-
*
|
|
16350
|
-
* The DeviceEventPropagator re-emits `onPressed` on the camera parent
|
|
16351
|
-
* — subscribers listening at the camera level receive ring events
|
|
16352
|
-
* with `via[]` populated. No code on the parent needed.
|
|
16353
|
-
*/
|
|
16354
|
-
var DoorbellStatusSchema = object({
|
|
16355
|
-
/** Ms epoch of the last press. null = never observed since this provider started. */
|
|
16356
|
-
lastPressedAt: number().nullable(),
|
|
16357
|
-
/** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
|
|
16358
|
-
pressCountSinceStart: number()
|
|
16359
|
-
});
|
|
16360
|
-
var DoorbellPressEventSchema = object({
|
|
16361
|
-
deviceId: number(),
|
|
16362
|
-
timestamp: number()
|
|
16363
|
-
});
|
|
16364
|
-
/**
|
|
16365
|
-
* The `doorbell-source` extension slot's editable value (D648): the camera's
|
|
16366
|
-
* virtual-doorbell trigger devices, exactly what the wrapper's
|
|
16367
|
-
* `doorbellSources` persists (as `{ deviceId }` records).
|
|
16368
|
-
*/
|
|
16369
|
-
var DoorbellSourceSlotValueSchema = object({ deviceIds: array(number().int().positive()) });
|
|
16370
|
-
/**
|
|
16371
|
-
* The `doorbell-source` slot — which devices ring this camera's VIRTUAL
|
|
16372
|
-
* doorbell, owned by the `virtual-doorbell` wrapper over its
|
|
16373
|
-
* `doorbellSources` store (D648). A source rings when one of its
|
|
16374
|
-
* `SOURCE_CAPS` rises. Whether the wrapper is bound to the camera at all is a
|
|
16375
|
-
* different authority (`deviceManager.setWrapperActive`, D62) — the slot
|
|
16376
|
-
* reports it, it never writes it.
|
|
16377
|
-
*/
|
|
16378
|
-
var DOORBELL_SOURCE_SLOT = {
|
|
16379
|
-
id: "doorbell-source",
|
|
16380
|
-
label: "Doorbell trigger devices",
|
|
16381
|
-
description: "Any of these becoming active rings this camera's virtual doorbell: a binary sensor when it goes active, a switch when it turns on.",
|
|
16382
|
-
shape: "association",
|
|
16383
|
-
hostDeviceTypes: [DeviceType.Camera],
|
|
16384
|
-
sourceCaps: SOURCE_CAPS,
|
|
16385
|
-
cardinality: "many",
|
|
16386
|
-
value: DoorbellSourceSlotValueSchema
|
|
16387
|
-
};
|
|
16388
|
-
var doorbellCapability = {
|
|
16389
|
-
name: "doorbell",
|
|
16390
|
-
scope: "device",
|
|
16391
|
-
deviceNative: true,
|
|
16392
|
-
mode: "singleton",
|
|
16393
|
-
kind: "wrapper",
|
|
16394
|
-
defaultActive: false,
|
|
16395
|
-
deviceTypes: [DeviceType.Button, DeviceType.Camera],
|
|
16396
|
-
extensionSlots: [DOORBELL_SOURCE_SLOT],
|
|
16397
|
-
methods: {},
|
|
16398
|
-
events: {
|
|
16399
|
-
/**
|
|
16400
|
-
* Fires once per physical press. Reolink delivers via Baichuan
|
|
16401
|
-
* push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
|
|
16402
|
-
* release/duration — it's a pulse.
|
|
16403
|
-
*/
|
|
16404
|
-
onPressed: {
|
|
16405
|
-
data: DoorbellPressEventSchema,
|
|
16406
|
-
fromState: {
|
|
16407
|
-
field: "lastPressedAt",
|
|
16408
|
-
on: "advance"
|
|
16409
|
-
}
|
|
16410
|
-
} },
|
|
16411
|
-
status: {
|
|
16412
|
-
schema: DoorbellStatusSchema,
|
|
16413
|
-
kind: "push"
|
|
16414
|
-
},
|
|
16415
|
-
/**
|
|
16416
|
-
* Runtime-state slice — last press timestamp + lifetime press count.
|
|
16417
|
-
* Mirrored by the kernel and readable via
|
|
16418
|
-
* `device.state.doorbell.value`. UIs can show "last ring 5m ago"
|
|
16419
|
-
* without subscribing.
|
|
16420
|
-
*/
|
|
16421
|
-
runtimeState: DoorbellStatusSchema,
|
|
16422
|
-
/**
|
|
16423
|
-
* Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
|
|
16424
|
-
*
|
|
16425
|
-
* See `RuntimeStateDurability`. Enforced by
|
|
16426
|
-
* `scripts/check-runtime-state-durability.ts`.
|
|
16427
|
-
*/
|
|
16428
|
-
durability: "restored"
|
|
16429
|
-
};
|
|
16430
|
-
var ExtensionSlotIdSchema = _enum(["doorbell-source", "linked-devices"]);
|
|
16431
|
-
DeviceType.Camera, DeviceType.Camera;
|
|
16347
|
+
var ExtensionSlotIdSchema = _enum(["linked-devices"]);
|
|
16348
|
+
DeviceType.Camera;
|
|
16432
16349
|
/** A slot's editable value, discriminated on `slot`. */
|
|
16433
|
-
var ExtensionSlotValueSchema = discriminatedUnion("slot", [
|
|
16350
|
+
var ExtensionSlotValueSchema = discriminatedUnion("slot", [LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
|
|
16434
16351
|
DeviceType.Camera;
|
|
16435
16352
|
var ExtensionSourceAvailabilitySchema = discriminatedUnion("state", [object({ state: literal("ok") }), object({
|
|
16436
16353
|
state: literal("unreachable"),
|
|
@@ -16467,10 +16384,11 @@ var ExtensionSlotSourceSchema = object({
|
|
|
16467
16384
|
});
|
|
16468
16385
|
/**
|
|
16469
16386
|
* A DIFFERENT authority that gates whether the slot's sources do anything —
|
|
16470
|
-
* reported read-only, named, never written through this contract (D62).
|
|
16471
|
-
*
|
|
16472
|
-
*
|
|
16473
|
-
* `unknown` means the gate could
|
|
16387
|
+
* reported read-only, named, never written through this contract (D62) — e.g.
|
|
16388
|
+
* a slot whose sources act only while a wrapper is bound to the host, a
|
|
16389
|
+
* binding that belongs to `deviceManager.setWrapperActive`. No slot reports
|
|
16390
|
+
* one today (`linked-devices` answers `null`). `unknown` means the gate could
|
|
16391
|
+
* not be read; it is not "inactive" (D315).
|
|
16474
16392
|
*/
|
|
16475
16393
|
var ExtensionSlotActivationSchema = object({
|
|
16476
16394
|
state: _enum([
|
|
@@ -16486,7 +16404,7 @@ var ExtensionSlotRowSchema = discriminatedUnion("state", [object({
|
|
|
16486
16404
|
/** `<source>:<slotId>` — the routing key for `listCandidates` / `setSlot`. */
|
|
16487
16405
|
slotRef: string(),
|
|
16488
16406
|
slot: ExtensionSlotIdSchema,
|
|
16489
|
-
/** Names the store written (D62), e.g. `
|
|
16407
|
+
/** Names the store written (D62), e.g. `device-manager:_linkedDeviceIds`. */
|
|
16490
16408
|
authority: string(),
|
|
16491
16409
|
activation: ExtensionSlotActivationSchema.nullable(),
|
|
16492
16410
|
value: ExtensionSlotValueSchema,
|
|
@@ -21049,6 +20967,29 @@ object({
|
|
|
21049
20967
|
firstAt: number(),
|
|
21050
20968
|
lastAt: number()
|
|
21051
20969
|
});
|
|
20970
|
+
var NC_ALARM_TRIGGERED_MAX_SEC = 3600;
|
|
20971
|
+
var NcAlarmDelaySecSchema = number().int().min(0).max(600);
|
|
20972
|
+
var NcAlarmTriggeredSecSchema = number().int().min(0).max(NC_ALARM_TRIGGERED_MAX_SEC);
|
|
20973
|
+
var uniqueModes = (entries) => new Set(entries.map((e) => e.mode)).size === entries.length;
|
|
20974
|
+
var NcAlarmModeDelaysListSchema = array(object({
|
|
20975
|
+
mode: AlarmArmModeSchema,
|
|
20976
|
+
exitDelaySec: NcAlarmDelaySecSchema.optional(),
|
|
20977
|
+
entryDelaySec: NcAlarmDelaySecSchema.optional(),
|
|
20978
|
+
triggeredDurationSec: NcAlarmTriggeredSecSchema.optional()
|
|
20979
|
+
})).max(8).refine(uniqueModes, { message: "modeDelays: at most one entry per arm mode" });
|
|
20980
|
+
var NcAlarmModeDelaysEditListSchema = array(object({
|
|
20981
|
+
mode: AlarmArmModeSchema,
|
|
20982
|
+
exitDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
|
|
20983
|
+
entryDelaySec: NcAlarmDelaySecSchema.nullable().optional(),
|
|
20984
|
+
triggeredDurationSec: NcAlarmTriggeredSecSchema.nullable().optional()
|
|
20985
|
+
})).max(8).refine(uniqueModes, { message: "modeDelays: at most one edit per arm mode" });
|
|
20986
|
+
/** One switch of the alarm table: put ONE device on, or take it off, ONE mode's list. */
|
|
20987
|
+
var NcAlarmNonBlockingEditSchema = object({
|
|
20988
|
+
mode: AlarmArmModeSchema,
|
|
20989
|
+
deviceId: number().int().positive(),
|
|
20990
|
+
/** true = on the list (may be open, does not block); false = off the list (blocks). */
|
|
20991
|
+
nonBlocking: boolean()
|
|
20992
|
+
});
|
|
21052
20993
|
/**
|
|
21053
20994
|
* The per-mode lists, at most one per mode. Two lists for one mode have no
|
|
21054
20995
|
* meaning a reader could agree on (union? the last one?), so they are refused
|
|
@@ -21057,10 +20998,10 @@ object({
|
|
|
21057
20998
|
var NcAlarmNonBlockingListSchema = array(object({
|
|
21058
20999
|
mode: AlarmArmModeSchema,
|
|
21059
21000
|
deviceIds: array(number().int()).max(200)
|
|
21060
|
-
})).max(8).refine(
|
|
21001
|
+
})).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
|
|
21061
21002
|
/**
|
|
21062
|
-
* The three durations
|
|
21063
|
-
* transitions, and which openings each mode tolerates.
|
|
21003
|
+
* The three durations — global, and per mode since D697 — the panel's state
|
|
21004
|
+
* machine runs on, who hears about its transitions, and which openings each mode tolerates.
|
|
21064
21005
|
*
|
|
21065
21006
|
* They live on the NOTIFICATION-RULES cap, not on `alarm-panel`, on purpose:
|
|
21066
21007
|
* `alarm-panel` is `deviceNative` and its other provider mirrors somebody
|
|
@@ -21070,15 +21011,15 @@ var NcAlarmNonBlockingListSchema = array(object({
|
|
|
21070
21011
|
*/
|
|
21071
21012
|
var NcAlarmSettingsSchema = object({
|
|
21072
21013
|
/** Grace period between arming and the mode taking effect. 0 = immediate. */
|
|
21073
|
-
exitDelaySec:
|
|
21014
|
+
exitDelaySec: NcAlarmDelaySecSchema,
|
|
21074
21015
|
/** Grace period between a trigger and the alarm firing. 0 = immediate. */
|
|
21075
|
-
entryDelaySec:
|
|
21016
|
+
entryDelaySec: NcAlarmDelaySecSchema,
|
|
21076
21017
|
/**
|
|
21077
21018
|
* How long `triggered` lasts before the panel re-arms itself.
|
|
21078
21019
|
* **0 = until an operator disarms it** — the behaviour before this field
|
|
21079
21020
|
* existed, and therefore what an untouched install keeps doing.
|
|
21080
21021
|
*/
|
|
21081
|
-
triggeredDurationSec:
|
|
21022
|
+
triggeredDurationSec: NcAlarmTriggeredSecSchema,
|
|
21082
21023
|
/**
|
|
21083
21024
|
* Deprecated by D630 — every transition is announced. Parsed, never read;
|
|
21084
21025
|
* remove after one release. Optional so an older admin's patch (which still
|
|
@@ -21101,17 +21042,30 @@ var NcAlarmSettingsSchema = object({
|
|
|
21101
21042
|
* per mode is meaningful. Defaulted to `[]` so a blob stored before D630
|
|
21102
21043
|
* parses as "every opening blocks" — the behaviour it was written under.
|
|
21103
21044
|
*/
|
|
21104
|
-
nonBlocking: NcAlarmNonBlockingListSchema.default([])
|
|
21045
|
+
nonBlocking: NcAlarmNonBlockingListSchema.default([]),
|
|
21046
|
+
/**
|
|
21047
|
+
* Per arm mode, the delays that mode OVERRIDES (D697). A mode with no entry, or an entry
|
|
21048
|
+
* without a field, uses the global value above — so `[]`, the default, is exactly the
|
|
21049
|
+
* behaviour of a blob stored before this field existed. Read through `resolveModeDelays`.
|
|
21050
|
+
*/
|
|
21051
|
+
modeDelays: NcAlarmModeDelaysListSchema.default([])
|
|
21105
21052
|
});
|
|
21106
21053
|
/**
|
|
21107
21054
|
* Every field optional — a tab edits one control at a time.
|
|
21108
21055
|
*
|
|
21109
|
-
* `nonBlocking`
|
|
21110
|
-
* remove an inner `.default()`, so a patch that never
|
|
21111
|
-
* parse to `
|
|
21112
|
-
* (the same trap {@link NcRulePatchSchema} documents).
|
|
21056
|
+
* `nonBlocking` and `modeDelays` are re-declared WITHOUT their defaults:
|
|
21057
|
+
* `.partial()` does not remove an inner `.default()`, so a patch that never
|
|
21058
|
+
* named the field would parse to `[]` and wipe every mode's list on an
|
|
21059
|
+
* unrelated edit (the same trap {@link NcRulePatchSchema} documents). Here
|
|
21060
|
+
* `modeDelays` means EDITS, not the stored overrides.
|
|
21113
21061
|
*/
|
|
21114
|
-
var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
|
|
21062
|
+
var NcAlarmSettingsPatchSchema = NcAlarmSettingsSchema.extend({
|
|
21063
|
+
nonBlocking: NcAlarmNonBlockingListSchema,
|
|
21064
|
+
/** Per-mode, per-field edits — NOT the whole list (see NcAlarmModeDelaysEditSchema). */
|
|
21065
|
+
modeDelays: NcAlarmModeDelaysEditListSchema,
|
|
21066
|
+
/** ONE membership change; the panel applies it to the list it holds at its turn. */
|
|
21067
|
+
nonBlockingEdit: NcAlarmNonBlockingEditSchema
|
|
21068
|
+
}).partial().refine((p) => p.nonBlocking === void 0 || p.nonBlockingEdit === void 0, { message: "nonBlocking and nonBlockingEdit are exclusive — send the whole lists or one edit" });
|
|
21115
21069
|
/**
|
|
21116
21070
|
* What one arm mode actually arms, DERIVED from the enabled rules gated on it.
|
|
21117
21071
|
* Never authored, never stored — see `alarm-mode-coverage.ts` for why a stored
|
|
@@ -21211,6 +21165,28 @@ var NcAlarmConfigSchema = object({
|
|
|
21211
21165
|
*/
|
|
21212
21166
|
live: NcAlarmLiveSchema.optional()
|
|
21213
21167
|
});
|
|
21168
|
+
var NcAlarmSensorReadingSchema = _enum([
|
|
21169
|
+
"open",
|
|
21170
|
+
"closed",
|
|
21171
|
+
"unknown",
|
|
21172
|
+
"not-opening"
|
|
21173
|
+
]);
|
|
21174
|
+
/** One device's state as the alarm reads it — the classifier the arm check uses. */
|
|
21175
|
+
var NcAlarmSensorStateSchema = object({
|
|
21176
|
+
deviceId: number().int().positive(),
|
|
21177
|
+
reading: NcAlarmSensorReadingSchema,
|
|
21178
|
+
/** The word as read (`open`, `closed`, `unlocked`, `stopped`, …); null when unknown / not an opening. */
|
|
21179
|
+
state: string().nullable(),
|
|
21180
|
+
/** Why `unknown`: the read failed, or the slice holds no word its cap defines (a placeholder included). */
|
|
21181
|
+
unknownWhy: _enum(["read-failed", "unknown-word"]).nullable(),
|
|
21182
|
+
/** The device manager's liveness as the centre mirrors it; null = not known. */
|
|
21183
|
+
online: boolean().nullable()
|
|
21184
|
+
});
|
|
21185
|
+
var NcAlarmSensorsInputSchema = object({ deviceIds: array(number().int().positive()).max(500) });
|
|
21186
|
+
var NcAlarmSensorsSchema = object({
|
|
21187
|
+
sensors: array(NcAlarmSensorStateSchema),
|
|
21188
|
+
readAt: number().int().nonnegative()
|
|
21189
|
+
});
|
|
21214
21190
|
/**
|
|
21215
21191
|
* ONE rule's demand on ONE camera's clip ring.
|
|
21216
21192
|
*
|
|
@@ -21301,7 +21277,7 @@ method(object({}), object({ rules: array(NcRuleSchema) }), { auth: "admin" }), m
|
|
|
21301
21277
|
}), method(object({}), NcAlarmConfigSchema, { auth: "admin" }), method(object({ patch: NcAlarmSettingsPatchSchema }), NcAlarmConfigSchema, {
|
|
21302
21278
|
kind: "mutation",
|
|
21303
21279
|
auth: "admin"
|
|
21304
|
-
});
|
|
21280
|
+
}), method(NcAlarmSensorsInputSchema, NcAlarmSensorsSchema, { auth: "admin" });
|
|
21305
21281
|
/**
|
|
21306
21282
|
* Zod schemas for persisted record types.
|
|
21307
21283
|
*
|
|
@@ -21648,12 +21624,26 @@ var CameraSensorRefSchema = object({
|
|
|
21648
21624
|
function cameraSensorRefKey(ref) {
|
|
21649
21625
|
return `${ref.addonId}/${ref.stableId}`;
|
|
21650
21626
|
}
|
|
21651
|
-
/**
|
|
21652
|
-
|
|
21653
|
-
|
|
21654
|
-
|
|
21655
|
-
|
|
21656
|
-
|
|
21627
|
+
/**
|
|
21628
|
+
* A sensor's mode (D673). `in-addition`: its motion is the camera's motion too.
|
|
21629
|
+
* `replace`: its motion is the camera's, and while one is READY the camera's
|
|
21630
|
+
* own motion no longer counts on its own. `confirm`: while one is READY the
|
|
21631
|
+
* camera's own motion counts only while a confirm sensor is active — with or
|
|
21632
|
+
* without a replace sensor (spec C2, the controller's ruling on Q1).
|
|
21633
|
+
*/
|
|
21634
|
+
var CameraSensorModeSchema = _enum([
|
|
21635
|
+
"in-addition",
|
|
21636
|
+
"confirm",
|
|
21637
|
+
"replace"
|
|
21638
|
+
]);
|
|
21639
|
+
/** A stored sensor with no mode — every CS-1 row — is in addition. */
|
|
21640
|
+
var DEFAULT_CAMERA_SENSOR_MODE = "in-addition";
|
|
21641
|
+
/**
|
|
21642
|
+
* A camera's attached sensors. A CS-1 row carried an envelope
|
|
21643
|
+
* `mode: 'in-addition'`: `z.object` drops it on parse, and the PARSED value is
|
|
21644
|
+
* what the write stores (D672 rule 3), so it is never written again.
|
|
21645
|
+
*/
|
|
21646
|
+
var CameraSensorsConfigSchema = object({ sensors: array(CameraSensorRefSchema.extend({ mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE) })).min(1).max(8) }).superRefine((value, ctx) => {
|
|
21657
21647
|
const seen = /* @__PURE__ */ new Set();
|
|
21658
21648
|
value.sensors.forEach((ref, index) => {
|
|
21659
21649
|
const key = cameraSensorRefKey(ref);
|
|
@@ -21665,39 +21655,41 @@ var CameraSensorsConfigSchema = object({
|
|
|
21665
21655
|
seen.add(key);
|
|
21666
21656
|
});
|
|
21667
21657
|
});
|
|
21668
|
-
|
|
21658
|
+
/** Every attachment names its mode; an older orchestrator that does not is in addition. */
|
|
21659
|
+
var AttachmentFields = {
|
|
21669
21660
|
addonId: string(),
|
|
21670
|
-
stableId: string()
|
|
21661
|
+
stableId: string(),
|
|
21662
|
+
mode: CameraSensorModeSchema.default(DEFAULT_CAMERA_SENSOR_MODE)
|
|
21671
21663
|
};
|
|
21672
21664
|
var CameraSensorAttachmentSchema = discriminatedUnion("state", [
|
|
21673
21665
|
object({
|
|
21674
|
-
...
|
|
21666
|
+
...AttachmentFields,
|
|
21675
21667
|
state: literal("ready"),
|
|
21676
21668
|
deviceId: number(),
|
|
21677
21669
|
name: string(),
|
|
21678
21670
|
caps: array(string()).readonly()
|
|
21679
21671
|
}),
|
|
21680
21672
|
object({
|
|
21681
|
-
...
|
|
21673
|
+
...AttachmentFields,
|
|
21682
21674
|
state: literal("offline"),
|
|
21683
21675
|
deviceId: number(),
|
|
21684
21676
|
name: string(),
|
|
21685
21677
|
reason: _enum(["offline", "disabled"])
|
|
21686
21678
|
}),
|
|
21687
21679
|
object({
|
|
21688
|
-
...
|
|
21680
|
+
...AttachmentFields,
|
|
21689
21681
|
state: literal("missing"),
|
|
21690
21682
|
reason: string()
|
|
21691
21683
|
}),
|
|
21692
21684
|
object({
|
|
21693
|
-
...
|
|
21685
|
+
...AttachmentFields,
|
|
21694
21686
|
state: literal("unsupported"),
|
|
21695
21687
|
deviceId: number(),
|
|
21696
21688
|
name: string(),
|
|
21697
21689
|
reason: string()
|
|
21698
21690
|
}),
|
|
21699
21691
|
object({
|
|
21700
|
-
...
|
|
21692
|
+
...AttachmentFields,
|
|
21701
21693
|
state: literal("unreadable"),
|
|
21702
21694
|
reason: string()
|
|
21703
21695
|
})
|
|
@@ -21710,17 +21702,49 @@ var CameraSensorAttachmentSchema = discriminatedUnion("state", [
|
|
|
21710
21702
|
* say.
|
|
21711
21703
|
*/
|
|
21712
21704
|
var CameraSensorsNotForwardedSchema = _enum(["onboard-not-listed", "no-active-detection"]);
|
|
21705
|
+
/** Whether the camera's OWN motion counts on its own (D673 C2), from its READY sensors (C3). */
|
|
21706
|
+
var CameraOwnMotionSchema = _enum([
|
|
21707
|
+
"always",
|
|
21708
|
+
"when-confirmed",
|
|
21709
|
+
"never"
|
|
21710
|
+
]);
|
|
21711
|
+
/** Confirm / Replace cannot suppress this camera's motion: its provider still emits it directly (D673 C13). */
|
|
21712
|
+
var CameraSensorsNotEnforcedSchema = _enum(["provider-not-split"]);
|
|
21713
|
+
/**
|
|
21714
|
+
* The camera's OWN onboard motion (D696): the raw input the bridge combines,
|
|
21715
|
+
* never the effective value. The camera's `motion` slice is the effective one,
|
|
21716
|
+
* so a view of the camera's own row reads this.
|
|
21717
|
+
* - `atMs`: when the bridge received the camera's last own edge.
|
|
21718
|
+
* - `detected`: evaluated WHEN THE STATUS IS ASKED — the last edge said `true`
|
|
21719
|
+
* AND it is younger than the window the bridge counts the camera's own motion
|
|
21720
|
+
* for (`motionCooldownMs`, floored at two sustain ticks). A camera that never
|
|
21721
|
+
* sends a clear (heartbeats only) therefore reads `false` once no push came
|
|
21722
|
+
* within the window; a status read is a snapshot, so a live view re-asks.
|
|
21723
|
+
* Absent = this orchestrator has not received the camera's own motion since it
|
|
21724
|
+
* started (unknown, never "quiet"), or an orchestrator older than D696.
|
|
21725
|
+
*/
|
|
21726
|
+
var CameraRawLevelSchema = object({
|
|
21727
|
+
detected: boolean(),
|
|
21728
|
+
atMs: number()
|
|
21729
|
+
});
|
|
21713
21730
|
var CameraSensorsStatusSchema = discriminatedUnion("state", [
|
|
21714
|
-
object({
|
|
21731
|
+
object({
|
|
21732
|
+
state: literal("not-configured"),
|
|
21733
|
+
cameraLevel: CameraRawLevelSchema.optional()
|
|
21734
|
+
}),
|
|
21715
21735
|
object({
|
|
21716
21736
|
state: literal("unreadable"),
|
|
21717
|
-
reason: string()
|
|
21737
|
+
reason: string(),
|
|
21738
|
+
cameraLevel: CameraRawLevelSchema.optional()
|
|
21718
21739
|
}),
|
|
21719
21740
|
object({
|
|
21720
21741
|
state: literal("configured"),
|
|
21721
|
-
|
|
21742
|
+
cameraLevel: CameraRawLevelSchema.optional(),
|
|
21722
21743
|
sensors: array(CameraSensorAttachmentSchema).readonly(),
|
|
21723
|
-
|
|
21744
|
+
/** Absent = an orchestrator older than CS-2: it does not say (D315). */
|
|
21745
|
+
cameraMotion: CameraOwnMotionSchema.optional(),
|
|
21746
|
+
notForwarded: CameraSensorsNotForwardedSchema.optional(),
|
|
21747
|
+
notEnforced: CameraSensorsNotEnforcedSchema.optional()
|
|
21724
21748
|
})
|
|
21725
21749
|
]);
|
|
21726
21750
|
Object.freeze({
|
|
@@ -24951,8 +24975,10 @@ var RunnerCameraConfigSchema = object({
|
|
|
24951
24975
|
* - `analyzer`: ML/frame-diff motion detection (motion-wasm or other
|
|
24952
24976
|
* analyzer providers) running on decoded frames in this runner.
|
|
24953
24977
|
* - `onboard`: camera-native motion pushes (Reolink Baichuan, ONVIF
|
|
24954
|
-
* analytics). The provider emits ProviderMotion
|
|
24955
|
-
*
|
|
24978
|
+
* analytics). The provider emits its raw push as `ProviderMotion`
|
|
24979
|
+
* (D673); the orchestrator's camera-sensors bridge re-emits the camera's
|
|
24980
|
+
* effective motion as `MotionOnMotionChanged`, which is what reaches
|
|
24981
|
+
* `reportMotion`.
|
|
24956
24982
|
*/
|
|
24957
24983
|
motionSources: MotionSourcesSchema.default(["analyzer"]),
|
|
24958
24984
|
/**
|
|
@@ -25129,7 +25155,15 @@ var RunnerLocalLoadSchema = object({
|
|
|
25129
25155
|
hasGpu: boolean(),
|
|
25130
25156
|
inferenceBackend: string().optional(),
|
|
25131
25157
|
cpuCores: number().optional()
|
|
25132
|
-
})
|
|
25158
|
+
}),
|
|
25159
|
+
/**
|
|
25160
|
+
* Who writes a camera's `motion` runtime slice (D696). `'orchestrator'`: this
|
|
25161
|
+
* runner never writes it. ABSENT = a runner older than D696, which still
|
|
25162
|
+
* writes the slice from its phase machine; the orchestrator does not write
|
|
25163
|
+
* while any runner node is absent-or-unanswered, so one slice never has two
|
|
25164
|
+
* writers.
|
|
25165
|
+
*/
|
|
25166
|
+
motionSliceWriter: literal("orchestrator").optional()
|
|
25133
25167
|
});
|
|
25134
25168
|
/**
|
|
25135
25169
|
* Aggregate runtime metrics for the runner's whole local pool. Mirrors the
|
|
@@ -31172,6 +31206,70 @@ onStatusChanged: { data: object({
|
|
|
31172
31206
|
* whether persisting is worth a SQLite commit. */
|
|
31173
31207
|
volatileStateFields: ["lastChangedAt"]
|
|
31174
31208
|
};
|
|
31209
|
+
var DoorbellStatusSchema = object({
|
|
31210
|
+
/** Ms epoch of the last press. null = never observed since this provider started. */
|
|
31211
|
+
lastPressedAt: number().nullable(),
|
|
31212
|
+
/** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
|
|
31213
|
+
pressCountSinceStart: number()
|
|
31214
|
+
});
|
|
31215
|
+
var DoorbellPressEventSchema = object({
|
|
31216
|
+
deviceId: number(),
|
|
31217
|
+
timestamp: number()
|
|
31218
|
+
});
|
|
31219
|
+
/**
|
|
31220
|
+
* Doorbell button cap. Two kinds of provider serve it, and to every consumer
|
|
31221
|
+
* they are the same cap:
|
|
31222
|
+
*
|
|
31223
|
+
* - **Native** providers, registered per device by a driver addon through
|
|
31224
|
+
* `ctx.registerNativeCap` — a `DeviceType.Button` accessory with
|
|
31225
|
+
* `role: DeviceRole.Doorbell`, or the camera itself (Reolink registers at
|
|
31226
|
+
* camera level). They emit `onPressed` on every firmware ring.
|
|
31227
|
+
* - **A composed doorbell** (D670, D671): a camera's customization gives it
|
|
31228
|
+
* the cap, its `lastPressedAt` rising from the sources the operator
|
|
31229
|
+
* assigned in Customize → Doorbell, and the composer emits `onPressed`
|
|
31230
|
+
* from that field's advance (`fromState`).
|
|
31231
|
+
*
|
|
31232
|
+
* The DeviceEventPropagator re-emits `onPressed` on the camera parent.
|
|
31233
|
+
*/
|
|
31234
|
+
var doorbellCapability = {
|
|
31235
|
+
name: "doorbell",
|
|
31236
|
+
scope: "device",
|
|
31237
|
+
deviceNative: true,
|
|
31238
|
+
mode: "singleton",
|
|
31239
|
+
deviceTypes: [DeviceType.Button, DeviceType.Camera],
|
|
31240
|
+
methods: {},
|
|
31241
|
+
events: {
|
|
31242
|
+
/**
|
|
31243
|
+
* Fires once per physical press. Reolink delivers via Baichuan
|
|
31244
|
+
* push (`ReolinkSimpleEvent.type === 'doorbell'`). There is no
|
|
31245
|
+
* release/duration — it's a pulse.
|
|
31246
|
+
*/
|
|
31247
|
+
onPressed: {
|
|
31248
|
+
data: DoorbellPressEventSchema,
|
|
31249
|
+
fromState: {
|
|
31250
|
+
field: "lastPressedAt",
|
|
31251
|
+
on: "advance"
|
|
31252
|
+
}
|
|
31253
|
+
} },
|
|
31254
|
+
status: {
|
|
31255
|
+
schema: DoorbellStatusSchema,
|
|
31256
|
+
kind: "push"
|
|
31257
|
+
},
|
|
31258
|
+
/**
|
|
31259
|
+
* Runtime-state slice — last press timestamp + lifetime press count.
|
|
31260
|
+
* Mirrored by the kernel and readable via
|
|
31261
|
+
* `device.state.doorbell.value`. UIs can show "last ring 5m ago"
|
|
31262
|
+
* without subscribing.
|
|
31263
|
+
*/
|
|
31264
|
+
runtimeState: DoorbellStatusSchema,
|
|
31265
|
+
/**
|
|
31266
|
+
* Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
|
|
31267
|
+
*
|
|
31268
|
+
* See `RuntimeStateDurability`. Enforced by
|
|
31269
|
+
* `scripts/check-runtime-state-durability.ts`.
|
|
31270
|
+
*/
|
|
31271
|
+
durability: "restored"
|
|
31272
|
+
};
|
|
31175
31273
|
/**
|
|
31176
31274
|
* Enum-state sensor — a string value picked from a finite option set.
|
|
31177
31275
|
* Drives HA `sensor` entries with `state_class: enum` (HVAC action
|
|
@@ -33316,9 +33414,9 @@ var MotionStatusSchema = object({
|
|
|
33316
33414
|
/** Ms epoch of the last detected-true observation. Null if never detected. */
|
|
33317
33415
|
lastDetectedAt: number().nullable(),
|
|
33318
33416
|
/**
|
|
33319
|
-
* `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device
|
|
33320
|
-
* `null` while false and on every `Sensor`
|
|
33321
|
-
* constant's doc for the one-authority rule.
|
|
33417
|
+
* `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device (the
|
|
33418
|
+
* orchestrator writes it, D696), `null` while false and on every `Sensor`
|
|
33419
|
+
* device (D475) — see that constant's doc for the one-authority rule.
|
|
33322
33420
|
*
|
|
33323
33421
|
* ## Reading this field still arms nothing
|
|
33324
33422
|
*
|
|
@@ -33336,34 +33434,31 @@ var MotionStatusSchema = object({
|
|
|
33336
33434
|
* `addon-export-alexa/src/motion-clear-hold.ts` and
|
|
33337
33435
|
* `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
|
|
33338
33436
|
*
|
|
33339
|
-
* ## Who writes it
|
|
33340
|
-
*
|
|
33341
|
-
* - **Cameras** — the
|
|
33342
|
-
*
|
|
33343
|
-
* `
|
|
33344
|
-
* `
|
|
33345
|
-
*
|
|
33346
|
-
*
|
|
33347
|
-
* a
|
|
33348
|
-
*
|
|
33349
|
-
*
|
|
33350
|
-
*
|
|
33351
|
-
*
|
|
33352
|
-
*
|
|
33353
|
-
*
|
|
33354
|
-
*
|
|
33355
|
-
*
|
|
33356
|
-
*
|
|
33357
|
-
* `
|
|
33358
|
-
*
|
|
33359
|
-
*
|
|
33360
|
-
*
|
|
33361
|
-
*
|
|
33362
|
-
*
|
|
33363
|
-
*
|
|
33364
|
-
* This predates D475 and is not fixed there: the fix touches every
|
|
33365
|
-
* camera provider. Recorded in D475's Consequences. Do not restore the
|
|
33366
|
-
* "sole writer" wording without also removing the other writers.
|
|
33437
|
+
* ## Who writes it (D696)
|
|
33438
|
+
*
|
|
33439
|
+
* - **Cameras** — the orchestrator, and nothing else. It writes the camera's
|
|
33440
|
+
* EFFECTIVE motion (`addon-pipeline-orchestrator`
|
|
33441
|
+
* `camera-sensors/motion-slice.ts`), alongside the effective
|
|
33442
|
+
* `MotionOnMotionChanged` it emits or receives: the camera-sensors bridge
|
|
33443
|
+
* for a split camera, an unsplit emitter's direct event, the analyzer and
|
|
33444
|
+
* device activity, as the camera's `motionSources` counts them. A source
|
|
33445
|
+
* that never sends a clear falls one window after its last `true`.
|
|
33446
|
+
* `lastDetectedAt` moves only on an effective rise. No camera provider
|
|
33447
|
+
* writes this slice (not even a boot seed), and neither does the runner's
|
|
33448
|
+
* phase machine: its phase is "a session is attached" for every on-motion
|
|
33449
|
+
* session, not motion, and it now has its own `DetectionPhaseTransition`.
|
|
33450
|
+
* A provider's `isDetected` answers from the hub's slice.
|
|
33451
|
+
* - **Sensors** (Home Assistant binary sensors, Homematic, Matter, Alexa
|
|
33452
|
+
* imports) — the provider pushes both edges itself, from the upstream
|
|
33453
|
+
* system's own state change. That is the sensor's own fact.
|
|
33454
|
+
*
|
|
33455
|
+
* `scripts/check-onboard-motion-emitters.ts` refuses any other writer of a
|
|
33456
|
+
* `motion` slice; each sensor writer is a named exemption.
|
|
33457
|
+
*
|
|
33458
|
+
* This replaces the D475 §4 rule ("the phase machine is the sole writer").
|
|
33459
|
+
* That rule was never true: Hikvision and Amcrest wrote the slice raw beside
|
|
33460
|
+
* it, every provider seeded it at boot, and every session left it
|
|
33461
|
+
* `detected: true` after its detach.
|
|
33367
33462
|
*/
|
|
33368
33463
|
autoClearAfterMs: number().nullable()
|
|
33369
33464
|
});
|
|
@@ -33435,11 +33530,12 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
|
|
|
33435
33530
|
*/
|
|
33436
33531
|
runtimeState: MotionStatusSchema,
|
|
33437
33532
|
/**
|
|
33438
|
-
* Runtime-state durability: **session** —
|
|
33533
|
+
* Runtime-state durability: **session** — the writer of this slice
|
|
33439
33534
|
* writes only on an EDGE, so a restored `detected: true` would stay
|
|
33440
33535
|
* frozen until the next one instead of being corrected. The next edge
|
|
33441
|
-
* re-publishes the real state
|
|
33442
|
-
*
|
|
33536
|
+
* re-publishes the real state, and the orchestrator re-publishes every
|
|
33537
|
+
* camera when it starts writing (D696; on who writes it, see
|
|
33538
|
+
* `autoClearAfterMs` above).
|
|
33443
33539
|
*
|
|
33444
33540
|
* See `RuntimeStateDurability`. Enforced by
|
|
33445
33541
|
* `scripts/check-runtime-state-durability.ts`.
|
|
@@ -35954,8 +36050,7 @@ var ptzAutotrackCapability = {
|
|
|
35954
36050
|
deviceNative: true,
|
|
35955
36051
|
mode: "singleton",
|
|
35956
36052
|
/**
|
|
35957
|
-
* Two kinds of provider behind one name, the same shape `snapshot`
|
|
35958
|
-
* `doorbell` use:
|
|
36053
|
+
* Two kinds of provider behind one name, the same shape `snapshot` uses:
|
|
35959
36054
|
*
|
|
35960
36055
|
* - **Native**: the camera's own firmware autotrack, registered per device by
|
|
35961
36056
|
* the driver when the probe says it exists (Reolink: yes; Hikvision:
|
|
@@ -35963,7 +36058,7 @@ var ptzAutotrackCapability = {
|
|
|
35963
36058
|
* - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
|
|
35964
36059
|
* which follows a subject with `ptz.move` pulses driven by the detection
|
|
35965
36060
|
* pipeline. `defaultActive: false` — the operator binds it per camera in
|
|
35966
|
-
* the bindings UI
|
|
36061
|
+
* the bindings UI.
|
|
35967
36062
|
*
|
|
35968
36063
|
* The binding chooses WHICH implementation answers; `setEnabled` stays the
|
|
35969
36064
|
* one switch for the function either way. A camera never has both driving its
|
|
@@ -41468,13 +41563,14 @@ var BaseDeviceProvider = class extends BaseAddon {
|
|
|
41468
41563
|
async getStatus() {
|
|
41469
41564
|
const all = await this.ctx.kernel.devices?.getAll() ?? [];
|
|
41470
41565
|
const summary = this.restoreFailureSummary();
|
|
41471
|
-
|
|
41566
|
+
const base = {
|
|
41472
41567
|
connected: true,
|
|
41473
|
-
deviceCount: all.length
|
|
41568
|
+
deviceCount: all.length,
|
|
41569
|
+
motionSliceWriter: "orchestrator"
|
|
41474
41570
|
};
|
|
41571
|
+
if (summary === null) return base;
|
|
41475
41572
|
return {
|
|
41476
|
-
|
|
41477
|
-
deviceCount: all.length,
|
|
41573
|
+
...base,
|
|
41478
41574
|
error: summary
|
|
41479
41575
|
};
|
|
41480
41576
|
}
|
|
@@ -47073,6 +47169,12 @@ Object.freeze({
|
|
|
47073
47169
|
addonId: null,
|
|
47074
47170
|
access: "view"
|
|
47075
47171
|
},
|
|
47172
|
+
"notificationRules.getAlarmSensors": {
|
|
47173
|
+
capName: "notification-rules",
|
|
47174
|
+
capScope: "system",
|
|
47175
|
+
addonId: null,
|
|
47176
|
+
access: "view"
|
|
47177
|
+
},
|
|
47076
47178
|
"notificationRules.getClipRetentionAsks": {
|
|
47077
47179
|
capName: "notification-rules",
|
|
47078
47180
|
capScope: "system",
|
|
@@ -51556,6 +51658,11 @@ Object.freeze({
|
|
|
51556
51658
|
form: "single",
|
|
51557
51659
|
optional: false
|
|
51558
51660
|
}],
|
|
51661
|
+
"notificationRules.getAlarmSensors": [{
|
|
51662
|
+
name: "deviceIds",
|
|
51663
|
+
form: "array",
|
|
51664
|
+
optional: false
|
|
51665
|
+
}],
|
|
51559
51666
|
"notificationRules.setDeviceMuted": [{
|
|
51560
51667
|
name: "deviceId",
|
|
51561
51668
|
form: "single",
|
|
@@ -53491,6 +53598,25 @@ TimelapseRuleInputSchema.extend({
|
|
|
53491
53598
|
createdAt: number(),
|
|
53492
53599
|
updatedAt: number()
|
|
53493
53600
|
});
|
|
53601
|
+
async function readHubMotionDetected(deviceState, deviceId) {
|
|
53602
|
+
let slice;
|
|
53603
|
+
try {
|
|
53604
|
+
slice = await deviceState.getCapSlice.query({
|
|
53605
|
+
deviceId,
|
|
53606
|
+
capName: "motion"
|
|
53607
|
+
});
|
|
53608
|
+
} catch (err) {
|
|
53609
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
53610
|
+
throw new Error(`motion state of device ${deviceId} is unreadable: ${reason}`, { cause: err });
|
|
53611
|
+
}
|
|
53612
|
+
const detected = slice?.["detected"];
|
|
53613
|
+
if (typeof detected !== "boolean") throw new Error(`motion state of device ${deviceId} is not published yet (unknown)`);
|
|
53614
|
+
return detected;
|
|
53615
|
+
}
|
|
53616
|
+
MotionOnMotionChangedDataSchema.omit({ triggeredBy: true });
|
|
53617
|
+
function providerMotionEvent(source, data) {
|
|
53618
|
+
return createEvent(EventCategory.ProviderMotion, source, data);
|
|
53619
|
+
}
|
|
53494
53620
|
object({
|
|
53495
53621
|
/**
|
|
53496
53622
|
* Fraction of the box's own size added on EACH side before cutting.
|
|
@@ -165046,6 +165172,22 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
165046
165172
|
* writers are {@link refreshNetworkLinkFromApi}. Seeded unknown so the
|
|
165047
165173
|
* restored slice (if any) stays valid and a fresh device draws no link.
|
|
165048
165174
|
*/
|
|
165175
|
+
/**
|
|
165176
|
+
* The native `motion` cap. This provider writes NO `motion` slice (D696):
|
|
165177
|
+
* `handleSimpleEvent` emits its raw push as `ProviderMotion` (`source:
|
|
165178
|
+
* 'onboard'`, D673), the orchestrator's camera-sensors bridge turns it into
|
|
165179
|
+
* the effective `MotionOnMotionChanged`, and the orchestrator is the slice's
|
|
165180
|
+
* only writer, with that effective value, on the hub. `isDetected`
|
|
165181
|
+
* therefore answers from the HUB's slice; an unreadable hub, or a slice
|
|
165182
|
+
* nobody has published since the hub started, fails the call. It never
|
|
165183
|
+
* answers `false` for "unknown" (D49, D393).
|
|
165184
|
+
*/
|
|
165185
|
+
registerMotionCap() {
|
|
165186
|
+
this.ctx.registerNativeCap(motionCapability, { isDetected: async ({ deviceId }) => {
|
|
165187
|
+
if (deviceId !== this.id) throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
|
|
165188
|
+
return readHubMotionDetected(this.ctx.api.deviceState, this.id);
|
|
165189
|
+
} });
|
|
165190
|
+
}
|
|
165049
165191
|
registerNetworkLink() {
|
|
165050
165192
|
this.ctx.registerNativeCap(networkLinkCapability, { getStatus: async ({ deviceId }) => {
|
|
165051
165193
|
if (deviceId !== this.id) throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
|
|
@@ -165774,15 +165916,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
165774
165916
|
};
|
|
165775
165917
|
} });
|
|
165776
165918
|
this.registerNetworkLink();
|
|
165777
|
-
this.
|
|
165778
|
-
if (deviceId !== this.id) throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);
|
|
165779
|
-
return this.state.motion.detected ?? false;
|
|
165780
|
-
} });
|
|
165781
|
-
this.setCapSlice(motionCapability, {
|
|
165782
|
-
detected: false,
|
|
165783
|
-
lastDetectedAt: null,
|
|
165784
|
-
autoClearAfterMs: null
|
|
165785
|
-
});
|
|
165919
|
+
this.registerMotionCap();
|
|
165786
165920
|
this.registerDoorbellIfSupported();
|
|
165787
165921
|
this.registerPtzIfSupported();
|
|
165788
165922
|
this.registerPtzAutotrackIfSupported();
|
|
@@ -171781,11 +171915,11 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
171781
171915
|
* axes, each driven exclusively by their own firmware push events.
|
|
171782
171916
|
*
|
|
171783
171917
|
* - 'motion' + AI types (people, vehicle, animal, face, package):
|
|
171784
|
-
* emit `EventCategory.
|
|
171785
|
-
*
|
|
171786
|
-
*
|
|
171787
|
-
*
|
|
171788
|
-
*
|
|
171918
|
+
* emit the RAW push as `EventCategory.ProviderMotion`
|
|
171919
|
+
* (`source: 'onboard'`, D673). The orchestrator's camera-sensors bridge
|
|
171920
|
+
* re-emits it as the effective `MotionOnMotionChanged`, and the
|
|
171921
|
+
* orchestrator is the only writer of the camera's `motion` runtime-state
|
|
171922
|
+
* slice (D696; no slice write here). AI subtype also lands on
|
|
171789
171923
|
* `EventCategory.DetectionCameraNative`.
|
|
171790
171924
|
* - 'awake' / 'sleeping': emit DeviceAwake / DeviceSleeping. Battery
|
|
171791
171925
|
* power-state only — does NOT touch online state.
|
|
@@ -171838,7 +171972,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
171838
171972
|
} });
|
|
171839
171973
|
if (event.type === "motion") {
|
|
171840
171974
|
const now = Date.now();
|
|
171841
|
-
this.ctx.eventBus.emit(
|
|
171975
|
+
this.ctx.eventBus.emit(providerMotionEvent(eventSource, {
|
|
171842
171976
|
deviceId: this.id,
|
|
171843
171977
|
detected: true,
|
|
171844
171978
|
timestamp: now,
|
|
@@ -171866,7 +172000,7 @@ var ReolinkCamera = class ReolinkCamera extends BaseDevice {
|
|
|
171866
172000
|
}]
|
|
171867
172001
|
}));
|
|
171868
172002
|
} else this.noteNativeDetectionSuppressed(aiClass);
|
|
171869
|
-
this.ctx.eventBus.emit(
|
|
172003
|
+
this.ctx.eventBus.emit(providerMotionEvent(eventSource, {
|
|
171870
172004
|
deviceId: this.id,
|
|
171871
172005
|
detected: true,
|
|
171872
172006
|
timestamp: now,
|
|
@@ -173715,7 +173849,7 @@ var AutodetectCache = class {
|
|
|
173715
173849
|
* silently dropped.
|
|
173716
173850
|
*
|
|
173717
173851
|
* Returns a LIST rather than a single type because of `doorbell`. The
|
|
173718
|
-
* camera's `handleSimpleEvent` emits `
|
|
173852
|
+
* camera's `handleSimpleEvent` emits `ProviderMotion` for `motion`
|
|
173719
173853
|
* and for every AI class, but the `doorbell` branch emits ONLY
|
|
173720
173854
|
* `DoorbellOnPressed` and returns. An email is the sole signal a sleeping
|
|
173721
173855
|
* battery camera can send, so a doorbell-classified email mapped to
|