@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.
Files changed (3) hide show
  1. package/dist/addon.js +346 -212
  2. package/dist/addon.mjs +346 -212
  3. 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-C5xZWqz6.mjs
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-gU6hqv4_.mjs
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-Dq42XWp0.mjs
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
- * Viveva dentro il builtin virtual-doorbell
7548
- * (`@camstack/system` — `builtins/doorbell/trigger-engine.ts`) e i suoi
7549
- * predicati erano privati al modulo. Il recorder ne ha bisogno per il trigger
7550
- * `RecordingTriggers.sensorDeviceIds`: copiarla avrebbe creato la SECONDA
7551
- * tabella, che diverge alla prima cap aggiunta e il cui sintomo — "il sensore
7552
- * fa suonare il campanello ma non registra" — è esattamente D62. Quindi si
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 che il virtual-doorbell usa dal 2026-08-05. Ed è il FRONTE, non
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
- * Doorbell button cap. Two kinds of providers coexist behind this cap
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", [DoorbellSourceSlotValueSchema.extend({ slot: literal("doorbell-source") }), LinkedDevicesSlotValueSchema.extend({ slot: literal("linked-devices") })]);
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). The
16471
- * virtual doorbell's sources ring only while the wrapper is bound to the
16472
- * camera, and that binding belongs to `deviceManager.setWrapperActive`.
16473
- * `unknown` means the gate could not be read; it is not "inactive" (D315).
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. `virtual-doorbell:doorbellSources`. */
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((lists) => new Set(lists.map((l) => l.mode)).size === lists.length, { message: "nonBlocking: at most one list per arm mode" });
21001
+ })).max(8).refine(uniqueModes, { message: "nonBlocking: at most one list per arm mode" });
21061
21002
  /**
21062
- * The three durations the panel's state machine runs on, who hears about its
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: number().int().min(0).max(600),
21014
+ exitDelaySec: NcAlarmDelaySecSchema,
21074
21015
  /** Grace period between a trigger and the alarm firing. 0 = immediate. */
21075
- entryDelaySec: number().int().min(0).max(600),
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: number().int().min(0).max(3600),
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` is re-declared WITHOUT its default: `.partial()` does not
21110
- * remove an inner `.default()`, so a patch that never named the field would
21111
- * parse to `nonBlocking: []` and wipe every mode's list on an unrelated edit
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({ nonBlocking: NcAlarmNonBlockingListSchema }).partial();
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
- /** CS-1 accepts exactly one mode; CS-2 (D673) widens it to confirm / replace. */
21652
- var CameraSensorsModeSchema = _enum(["in-addition"]);
21653
- var CameraSensorsConfigSchema = object({
21654
- mode: CameraSensorsModeSchema,
21655
- sensors: array(CameraSensorRefSchema).min(1).max(8)
21656
- }).superRefine((value, ctx) => {
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
- var RefFields = {
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
- ...RefFields,
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
- ...RefFields,
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
- ...RefFields,
21680
+ ...AttachmentFields,
21689
21681
  state: literal("missing"),
21690
21682
  reason: string()
21691
21683
  }),
21692
21684
  object({
21693
- ...RefFields,
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
- ...RefFields,
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({ state: literal("not-configured") }),
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
- mode: CameraSensorsModeSchema,
21742
+ cameraLevel: CameraRawLevelSchema.optional(),
21722
21743
  sensors: array(CameraSensorAttachmentSchema).readonly(),
21723
- notForwarded: CameraSensorsNotForwardedSchema.optional()
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
- * events; the orchestrator forwards to `reportMotion`.
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` device (D475) — see that
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 runner's phase machine, `active → watching` on
33342
- * `cooldown_expired`, which then writes this slice with
33343
- * `detected: false` (`handlePhaseChanged` in
33344
- * `pipeline-runner/index.ts`). It produces the FALLING edge, which
33345
- * matters most for the sources that only ever push a rising one:
33346
- * Reolink emits `MotionOnMotionChanged { detected: true }` and never
33347
- * a false.
33348
- * - **Sensors** (Home Assistant binary sensors, Homematic) — the
33349
- * provider pushes the false itself, from the upstream system's own
33350
- * state change. No phase machine is involved.
33351
- *
33352
- * ### The phase machine is CANONICAL, not sole — and that is a defect
33353
- *
33354
- * An earlier revision of this docblock (mine, 2026-09-12) claimed the
33355
- * phase machine is the sole writer for a camera. It is not.
33356
- * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
33357
- * `setCapSlice(motionCapability, …)` on their own rising edge, and
33358
- * Hikvision's comment says why: it read THIS docblock, agreed the
33359
- * runner is canonical, and wrote anyway to avoid per-tick churn. So
33360
- * two authorities can disagree about one slice, which this repo
33361
- * forbids, and the doc said otherwise — which is worse than saying
33362
- * nothing, because it reads as verification.
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** — every writer of this slice
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. (On who the writers are, and why there
33442
- * is more than one, see `autoClearAfterMs` above.)
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` and
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, exactly as `virtual-doorbell` is bound.
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
- if (summary === null) return {
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
- connected: true,
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.ctx.registerNativeCap(motionCapability, { isDetected: async ({ deviceId }) => {
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.MotionOnMotionChanged` (`source: 'onboard'`)
171785
- * so the runner's phase machine reacts via `reportMotion`. The
171786
- * runner is the sole writer of the `motion` runtime-state slice
171787
- * (no direct slice writes here — keeps onboard symmetric with the
171788
- * analyzer path). AI subtype also lands on
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(createEvent(EventCategory.MotionOnMotionChanged, eventSource, {
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(createEvent(EventCategory.MotionOnMotionChanged, eventSource, {
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 `MotionOnMotionChanged` for `motion`
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