@camstack/addon-provider-dreame 0.2.75 → 0.2.76

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 +124 -34
  2. package/dist/addon.mjs +124 -34
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -60522,17 +60522,29 @@ var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
60522
60522
  /**
60523
60523
  * DERIVED per-camera storage summary — the single field cheap consumers read
60524
60524
  * (the viewer's status dot, the camera list) instead of walking `bands`:
60525
- * - `off` — no band covers the camera (or it is disabled).
60526
- * - `events` — every band records around triggers only.
60527
- * - `continuous` — at least one band records continuously.
60528
- *
60529
- * NEVER authored: the recorder stamps it from the authoritative `bands` on
60530
- * every save (`activeModeForConfig`). Writing it has no effect.
60525
+ * - `off` — no band covers the camera (or it is disabled).
60526
+ * - `events` — every band records around triggers only.
60527
+ * - `continuous` — at least one band records continuously.
60528
+ * - `on-device-decision`— the DEVICE decides: recording runs for as long as the
60529
+ * camera raises its own `recording-signal` level (a robot that cleans). There
60530
+ * is no schedule to author, because there is no hour to program — see
60531
+ * {@link RecordingConfigSchema}`.deviceDecision`.
60532
+ *
60533
+ * NEVER authored: the recorder stamps it from the authoritative intent
60534
+ * (`bands` + `deviceDecision`) on every save (`activeModeForConfig`). Writing it
60535
+ * has no effect.
60536
+ *
60537
+ * `on-device-decision` is named for WHO decides, not for how the recording is
60538
+ * requested. `on-demand` was rejected: in this repo's vocabulary a "demand" is
60539
+ * something the operator makes (the live gate is the recorder's own "demand
60540
+ * window"), and a knob whose name suggests the operator starts it while the
60541
+ * device actually does is the D62 shape — a control nobody can predict.
60531
60542
  */
60532
60543
  var RecordingStorageModeSchema = _enum([
60533
60544
  "off",
60534
60545
  "events",
60535
- "continuous"
60546
+ "continuous",
60547
+ "on-device-decision"
60536
60548
  ]);
60537
60549
  /**
60538
60550
  * Le macro classi che possono aprire una finestra di registrazione — le stesse
@@ -60582,22 +60594,7 @@ var RecordingTriggersSchema = object({
60582
60594
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
60583
60595
  * registrare.
60584
60596
  */
60585
- sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional(),
60586
- /**
60587
- * La camera registra per tutta la durata del SEGNALE che lei stessa
60588
- * pubblica — la cap `recording-signal` (`active: true` mentre il device è in
60589
- * funzione: un robot che pulisce). È un LIVELLO, non un fronte: la finestra
60590
- * resta aperta finché il segnale è alto e si chiude `postBufferSec` dopo la
60591
- * caduta, così una pulizia di quaranta minuti è UNA clip.
60592
- *
60593
- * Non nomina un device: il segnale è della camera stessa, tenuto fresco dal
60594
- * suo provider (D224); il recorder lo ascolta su `DeviceStateChanged` e lo
60595
- * riconcilia leggendo la slice via RPC, con un tetto oltre il quale un
60596
- * segnale mai abbassato (un evento perso) non può tenere aperta una
60597
- * registrazione (D11). Vedi `recorder/addon/band-decision.ts`
60598
- * (`holdTrigger` / `releaseTrigger`).
60599
- */
60600
- deviceSignal: boolean().optional()
60597
+ sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
60601
60598
  });
60602
60599
  /**
60603
60600
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -60681,8 +60678,29 @@ var RecordingConfigSchema = object({
60681
60678
  * "off" is the absence of a covering band, never a band value.
60682
60679
  */
60683
60680
  bands: array(RecordingBandSchema).default([]),
60681
+ /**
60682
+ * THE device-decided intent: record for as long as the camera itself raises
60683
+ * `recording-signal` (`active: true` while the device is in function — a
60684
+ * robot that cleans). AUTHORED, unlike `mode`, and the only authored
60685
+ * recording intent that is not a band.
60686
+ *
60687
+ * It carries NO schedule on purpose. A band is an HOUR, and this feature has
60688
+ * no hour to program: the device decides. What it costs the operator is one
60689
+ * flag — "this camera can record on its own" — and what it buys is the same
60690
+ * hold/release the recorder already implements (D371, `holdTrigger` /
60691
+ * `releaseTrigger`): the window opens on the rise, stays open for the whole
60692
+ * job however long it is, and closes one pad after the fall.
60693
+ *
60694
+ * EXCLUSIVE with `bands` (below): two authorities deciding when the same
60695
+ * camera records is the D62 failure. `off` remains `enabled: false`, so an
60696
+ * operator switching this camera off is REPORTED off, never as broken.
60697
+ */
60698
+ deviceDecision: boolean().optional(),
60684
60699
  retention: RecordingRetentionSchema.optional()
60685
- }).strict();
60700
+ }).strict().refine((config) => config.deviceDecision !== true || config.bands.length === 0, {
60701
+ message: "deviceDecision is a whole-config mode and carries no schedule: it cannot be combined with bands",
60702
+ path: ["bands"]
60703
+ });
60686
60704
  /**
60687
60705
  * Entity-relocation job state (storage entity-routing spec, Phase 4).
60688
60706
  *
@@ -73715,11 +73733,7 @@ var CameraAudioStatusSchema = object({
73715
73733
  });
73716
73734
  /** Recording block — null when no recording cap is active for this device. */
73717
73735
  var CameraRecordingStatusSchema = object({
73718
- mode: _enum([
73719
- "off",
73720
- "continuous",
73721
- "events"
73722
- ]),
73736
+ mode: RecordingStorageModeSchema,
73723
73737
  active: boolean(),
73724
73738
  storageBytes: number()
73725
73739
  });
@@ -82146,11 +82160,10 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceI
82146
82160
  var RecordingStatusSchema = object({
82147
82161
  deviceId: number(),
82148
82162
  enabled: boolean(),
82149
- activeMode: _enum([
82150
- "off",
82151
- "continuous",
82152
- "events"
82153
- ]),
82163
+ /** THE derived storage mode, from the one definition
82164
+ * (`deriveRecordingMode`) — never a second enum. A duplicated list is how
82165
+ * `on-device-decision` could have reached the recorder and not the status. */
82166
+ activeMode: RecordingStorageModeSchema,
82154
82167
  nodeId: string(),
82155
82168
  storageBytes: number()
82156
82169
  });
@@ -96799,6 +96812,16 @@ var dreameChildSchema = object({
96799
96812
  navigationDisabledEntryIds: array(string()).optional(),
96800
96813
  /** Privacy PIN set at pairing — required to wake a coded camera (camera child only). */
96801
96814
  navigationAccessCode: string().optional(),
96815
+ /**
96816
+ * ms-epoch of the ONE-SHOT recording seed (camera child only, D380).
96817
+ *
96818
+ * A MARK, never the value: it says this camera was seeded, which stays true
96819
+ * after the operator switches recording off — and an operator's `off` is
96820
+ * byte-identical to a camera that was never seeded (`enabled: false,
96821
+ * bands: []`), so nothing but this field can tell the two apart. The
96822
+ * authority for what is recorded remains `RecordingConfig` (D62).
96823
+ */
96824
+ recordingSeededAt: number().optional(),
96802
96825
  mapShowNoGo: boolean().optional(),
96803
96826
  mapShowVirtualWalls: boolean().optional(),
96804
96827
  mapShowObstacles: boolean().optional(),
@@ -97849,6 +97872,51 @@ var DreameNetworkLinkMonitor = class {
97849
97872
  }
97850
97873
  };
97851
97874
  //#endregion
97875
+ //#region src/devices/dreame-recording-seed.ts
97876
+ /**
97877
+ * THE config the provider seeds: recording ON, decided by the device.
97878
+ *
97879
+ * No bands (the mode carries none, D380) and no profiles/retention: those are
97880
+ * cluster-wide defaults the recorder already resolves, and pinning them here
97881
+ * would be the provider deciding storage policy for a camera it does not own.
97882
+ */
97883
+ var DEVICE_DECISION_SEED_CONFIG = Object.freeze({
97884
+ enabled: true,
97885
+ deviceDecision: true,
97886
+ bands: []
97887
+ });
97888
+ /**
97889
+ * Seed `on-device-decision` recording for this camera, once, ever.
97890
+ *
97891
+ * Never throws: it runs on the activation path and a camera that fails to be
97892
+ * seeded must still come up. Every branch that does not write says why — a
97893
+ * silent skip here is indistinguishable from the feature not existing.
97894
+ */
97895
+ async function seedDeviceDecisionRecording(deps) {
97896
+ const { deviceId, logger } = deps;
97897
+ if (deps.alreadySeeded) return "already-seeded";
97898
+ try {
97899
+ await deps.writeConfig(DEVICE_DECISION_SEED_CONFIG);
97900
+ } catch (err) {
97901
+ logger.warn("robot camera: could not seed on-device-decision recording — retried at the next start", {
97902
+ tags: { deviceId },
97903
+ meta: { error: errMsg(err) }
97904
+ });
97905
+ return "failed";
97906
+ }
97907
+ try {
97908
+ await deps.markSeeded();
97909
+ } catch (err) {
97910
+ logger.warn("robot camera: recording seeded but the one-shot mark could not be persisted — a restart may seed it again and override an operator who switched it off", {
97911
+ tags: { deviceId },
97912
+ meta: { error: errMsg(err) }
97913
+ });
97914
+ return "seeded";
97915
+ }
97916
+ logger.info("robot camera: seeded on-device-decision recording (once, at first start)", { tags: { deviceId } });
97917
+ return "seeded";
97918
+ }
97919
+ //#endregion
97852
97920
  //#region src/devices/dreame-recording-signal.ts
97853
97921
  /** Vacuum lifecycle states during which the camera asks to be recorded. */
97854
97922
  var ACTIVE_STATES = new Set(["cleaning", "returning"]);
@@ -143563,6 +143631,28 @@ var DreameCameraDevice = class extends DreameChildDevice {
143563
143631
  this.networkLink.start();
143564
143632
  this.registerStreamCatalogCap();
143565
143633
  this.registerRecordingSignalCap();
143634
+ this.seedRecordingConfig();
143635
+ }
143636
+ /**
143637
+ * Seed `on-device-decision` recording the FIRST time this camera comes up.
143638
+ *
143639
+ * Gated on the durable mark and NOT on the current config: an operator who
143640
+ * switched recording off leaves a config that is byte-identical to one that
143641
+ * was never seeded, so reading the value would re-enable it at every restart.
143642
+ * The value stays the recorder's (D62) — this writes a default through the
143643
+ * same cap method an operator uses and keeps no copy of it.
143644
+ */
143645
+ async seedRecordingConfig() {
143646
+ await seedDeviceDecisionRecording({
143647
+ deviceId: this.id,
143648
+ alreadySeeded: this.config.get("recordingSeededAt") !== void 0,
143649
+ writeConfig: (config) => this.ctx.api.recording.setDeviceConfig.mutate({
143650
+ deviceId: this.id,
143651
+ config
143652
+ }),
143653
+ markSeeded: () => this.config.set("recordingSeededAt", Date.now()),
143654
+ logger: this.ctx.logger
143655
+ });
143566
143656
  }
143567
143657
  /**
143568
143658
  * The camera's own "record me" LEVEL, derived from the vacuum lifecycle
package/dist/addon.mjs CHANGED
@@ -60519,17 +60519,29 @@ var HHMM = /^([01]\d|2[0-3]):[0-5]\d$/;
60519
60519
  /**
60520
60520
  * DERIVED per-camera storage summary — the single field cheap consumers read
60521
60521
  * (the viewer's status dot, the camera list) instead of walking `bands`:
60522
- * - `off` — no band covers the camera (or it is disabled).
60523
- * - `events` — every band records around triggers only.
60524
- * - `continuous` — at least one band records continuously.
60525
- *
60526
- * NEVER authored: the recorder stamps it from the authoritative `bands` on
60527
- * every save (`activeModeForConfig`). Writing it has no effect.
60522
+ * - `off` — no band covers the camera (or it is disabled).
60523
+ * - `events` — every band records around triggers only.
60524
+ * - `continuous` — at least one band records continuously.
60525
+ * - `on-device-decision`— the DEVICE decides: recording runs for as long as the
60526
+ * camera raises its own `recording-signal` level (a robot that cleans). There
60527
+ * is no schedule to author, because there is no hour to program — see
60528
+ * {@link RecordingConfigSchema}`.deviceDecision`.
60529
+ *
60530
+ * NEVER authored: the recorder stamps it from the authoritative intent
60531
+ * (`bands` + `deviceDecision`) on every save (`activeModeForConfig`). Writing it
60532
+ * has no effect.
60533
+ *
60534
+ * `on-device-decision` is named for WHO decides, not for how the recording is
60535
+ * requested. `on-demand` was rejected: in this repo's vocabulary a "demand" is
60536
+ * something the operator makes (the live gate is the recorder's own "demand
60537
+ * window"), and a knob whose name suggests the operator starts it while the
60538
+ * device actually does is the D62 shape — a control nobody can predict.
60528
60539
  */
60529
60540
  var RecordingStorageModeSchema = _enum([
60530
60541
  "off",
60531
60542
  "events",
60532
- "continuous"
60543
+ "continuous",
60544
+ "on-device-decision"
60533
60545
  ]);
60534
60546
  /**
60535
60547
  * Le macro classi che possono aprire una finestra di registrazione — le stesse
@@ -60579,22 +60591,7 @@ var RecordingTriggersSchema = object({
60579
60591
  * il livello: un contatto trovato già aperto al riavvio del runner non fa
60580
60592
  * registrare.
60581
60593
  */
60582
- sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional(),
60583
- /**
60584
- * La camera registra per tutta la durata del SEGNALE che lei stessa
60585
- * pubblica — la cap `recording-signal` (`active: true` mentre il device è in
60586
- * funzione: un robot che pulisce). È un LIVELLO, non un fronte: la finestra
60587
- * resta aperta finché il segnale è alto e si chiude `postBufferSec` dopo la
60588
- * caduta, così una pulizia di quaranta minuti è UNA clip.
60589
- *
60590
- * Non nomina un device: il segnale è della camera stessa, tenuto fresco dal
60591
- * suo provider (D224); il recorder lo ascolta su `DeviceStateChanged` e lo
60592
- * riconcilia leggendo la slice via RPC, con un tetto oltre il quale un
60593
- * segnale mai abbassato (un evento perso) non può tenere aperta una
60594
- * registrazione (D11). Vedi `recorder/addon/band-decision.ts`
60595
- * (`holdTrigger` / `releaseTrigger`).
60596
- */
60597
- deviceSignal: boolean().optional()
60594
+ sensorDeviceIds: array(number().int().positive()).min(1).max(16).refine(noDuplicates, { message: "sensorDeviceIds must not repeat a device" }).optional()
60598
60595
  });
60599
60596
  /**
60600
60597
  * Mode of a single recording band — the recorder per-band vocabulary.
@@ -60678,8 +60675,29 @@ var RecordingConfigSchema = object({
60678
60675
  * "off" is the absence of a covering band, never a band value.
60679
60676
  */
60680
60677
  bands: array(RecordingBandSchema).default([]),
60678
+ /**
60679
+ * THE device-decided intent: record for as long as the camera itself raises
60680
+ * `recording-signal` (`active: true` while the device is in function — a
60681
+ * robot that cleans). AUTHORED, unlike `mode`, and the only authored
60682
+ * recording intent that is not a band.
60683
+ *
60684
+ * It carries NO schedule on purpose. A band is an HOUR, and this feature has
60685
+ * no hour to program: the device decides. What it costs the operator is one
60686
+ * flag — "this camera can record on its own" — and what it buys is the same
60687
+ * hold/release the recorder already implements (D371, `holdTrigger` /
60688
+ * `releaseTrigger`): the window opens on the rise, stays open for the whole
60689
+ * job however long it is, and closes one pad after the fall.
60690
+ *
60691
+ * EXCLUSIVE with `bands` (below): two authorities deciding when the same
60692
+ * camera records is the D62 failure. `off` remains `enabled: false`, so an
60693
+ * operator switching this camera off is REPORTED off, never as broken.
60694
+ */
60695
+ deviceDecision: boolean().optional(),
60681
60696
  retention: RecordingRetentionSchema.optional()
60682
- }).strict();
60697
+ }).strict().refine((config) => config.deviceDecision !== true || config.bands.length === 0, {
60698
+ message: "deviceDecision is a whole-config mode and carries no schedule: it cannot be combined with bands",
60699
+ path: ["bands"]
60700
+ });
60683
60701
  /**
60684
60702
  * Entity-relocation job state (storage entity-routing spec, Phase 4).
60685
60703
  *
@@ -73712,11 +73730,7 @@ var CameraAudioStatusSchema = object({
73712
73730
  });
73713
73731
  /** Recording block — null when no recording cap is active for this device. */
73714
73732
  var CameraRecordingStatusSchema = object({
73715
- mode: _enum([
73716
- "off",
73717
- "continuous",
73718
- "events"
73719
- ]),
73733
+ mode: RecordingStorageModeSchema,
73720
73734
  active: boolean(),
73721
73735
  storageBytes: number()
73722
73736
  });
@@ -82143,11 +82157,10 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceI
82143
82157
  var RecordingStatusSchema = object({
82144
82158
  deviceId: number(),
82145
82159
  enabled: boolean(),
82146
- activeMode: _enum([
82147
- "off",
82148
- "continuous",
82149
- "events"
82150
- ]),
82160
+ /** THE derived storage mode, from the one definition
82161
+ * (`deriveRecordingMode`) — never a second enum. A duplicated list is how
82162
+ * `on-device-decision` could have reached the recorder and not the status. */
82163
+ activeMode: RecordingStorageModeSchema,
82151
82164
  nodeId: string(),
82152
82165
  storageBytes: number()
82153
82166
  });
@@ -96796,6 +96809,16 @@ var dreameChildSchema = object({
96796
96809
  navigationDisabledEntryIds: array(string()).optional(),
96797
96810
  /** Privacy PIN set at pairing — required to wake a coded camera (camera child only). */
96798
96811
  navigationAccessCode: string().optional(),
96812
+ /**
96813
+ * ms-epoch of the ONE-SHOT recording seed (camera child only, D380).
96814
+ *
96815
+ * A MARK, never the value: it says this camera was seeded, which stays true
96816
+ * after the operator switches recording off — and an operator's `off` is
96817
+ * byte-identical to a camera that was never seeded (`enabled: false,
96818
+ * bands: []`), so nothing but this field can tell the two apart. The
96819
+ * authority for what is recorded remains `RecordingConfig` (D62).
96820
+ */
96821
+ recordingSeededAt: number().optional(),
96799
96822
  mapShowNoGo: boolean().optional(),
96800
96823
  mapShowVirtualWalls: boolean().optional(),
96801
96824
  mapShowObstacles: boolean().optional(),
@@ -97846,6 +97869,51 @@ var DreameNetworkLinkMonitor = class {
97846
97869
  }
97847
97870
  };
97848
97871
  //#endregion
97872
+ //#region src/devices/dreame-recording-seed.ts
97873
+ /**
97874
+ * THE config the provider seeds: recording ON, decided by the device.
97875
+ *
97876
+ * No bands (the mode carries none, D380) and no profiles/retention: those are
97877
+ * cluster-wide defaults the recorder already resolves, and pinning them here
97878
+ * would be the provider deciding storage policy for a camera it does not own.
97879
+ */
97880
+ var DEVICE_DECISION_SEED_CONFIG = Object.freeze({
97881
+ enabled: true,
97882
+ deviceDecision: true,
97883
+ bands: []
97884
+ });
97885
+ /**
97886
+ * Seed `on-device-decision` recording for this camera, once, ever.
97887
+ *
97888
+ * Never throws: it runs on the activation path and a camera that fails to be
97889
+ * seeded must still come up. Every branch that does not write says why — a
97890
+ * silent skip here is indistinguishable from the feature not existing.
97891
+ */
97892
+ async function seedDeviceDecisionRecording(deps) {
97893
+ const { deviceId, logger } = deps;
97894
+ if (deps.alreadySeeded) return "already-seeded";
97895
+ try {
97896
+ await deps.writeConfig(DEVICE_DECISION_SEED_CONFIG);
97897
+ } catch (err) {
97898
+ logger.warn("robot camera: could not seed on-device-decision recording — retried at the next start", {
97899
+ tags: { deviceId },
97900
+ meta: { error: errMsg(err) }
97901
+ });
97902
+ return "failed";
97903
+ }
97904
+ try {
97905
+ await deps.markSeeded();
97906
+ } catch (err) {
97907
+ logger.warn("robot camera: recording seeded but the one-shot mark could not be persisted — a restart may seed it again and override an operator who switched it off", {
97908
+ tags: { deviceId },
97909
+ meta: { error: errMsg(err) }
97910
+ });
97911
+ return "seeded";
97912
+ }
97913
+ logger.info("robot camera: seeded on-device-decision recording (once, at first start)", { tags: { deviceId } });
97914
+ return "seeded";
97915
+ }
97916
+ //#endregion
97849
97917
  //#region src/devices/dreame-recording-signal.ts
97850
97918
  /** Vacuum lifecycle states during which the camera asks to be recorded. */
97851
97919
  var ACTIVE_STATES = new Set(["cleaning", "returning"]);
@@ -143560,6 +143628,28 @@ var DreameCameraDevice = class extends DreameChildDevice {
143560
143628
  this.networkLink.start();
143561
143629
  this.registerStreamCatalogCap();
143562
143630
  this.registerRecordingSignalCap();
143631
+ this.seedRecordingConfig();
143632
+ }
143633
+ /**
143634
+ * Seed `on-device-decision` recording the FIRST time this camera comes up.
143635
+ *
143636
+ * Gated on the durable mark and NOT on the current config: an operator who
143637
+ * switched recording off leaves a config that is byte-identical to one that
143638
+ * was never seeded, so reading the value would re-enable it at every restart.
143639
+ * The value stays the recorder's (D62) — this writes a default through the
143640
+ * same cap method an operator uses and keeps no copy of it.
143641
+ */
143642
+ async seedRecordingConfig() {
143643
+ await seedDeviceDecisionRecording({
143644
+ deviceId: this.id,
143645
+ alreadySeeded: this.config.get("recordingSeededAt") !== void 0,
143646
+ writeConfig: (config) => this.ctx.api.recording.setDeviceConfig.mutate({
143647
+ deviceId: this.id,
143648
+ config
143649
+ }),
143650
+ markSeeded: () => this.config.set("recordingSeededAt", Date.now()),
143651
+ logger: this.ctx.logger
143652
+ });
143563
143653
  }
143564
143654
  /**
143565
143655
  * The camera's own "record me" LEVEL, derived from the vacuum lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.2.75",
3
+ "version": "0.2.76",
4
4
  "description": "Dreame robot-vacuum / lawn-mower device-provider addon for CamStack — wraps the @apocaliss92/nodedreame Dreamehome cloud client",
5
5
  "keywords": [
6
6
  "camstack",