@camstack/addon-provider-dreame 0.1.3 → 0.1.4

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 CHANGED
@@ -73204,10 +73204,66 @@ function decodeVacuumTanks(caps) {
73204
73204
  dustBin: caps?.canAutoEmpty ? present : null
73205
73205
  };
73206
73206
  }
73207
+ /**
73208
+ * Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
73209
+ * value is absent / non-finite. The library reports remaining life as a 0..100
73210
+ * integer; we clamp defensively so an out-of-range firmware value never violates
73211
+ * the cap schema.
73212
+ */
73213
+ function clampConsumablePct(value) {
73214
+ if (value === null || !Number.isFinite(value)) return null;
73215
+ return Math.max(0, Math.min(100, value));
73216
+ }
73217
+ /**
73218
+ * Build the `consumables` cap runtime-state slice from a live {@link VacuumDevice}
73219
+ * handle. The returned object satisfies the cap's `runtimeState` schema
73220
+ * (`ConsumablesStatusSchema.extend({ lastFetchedAt: z.number() })`).
73221
+ *
73222
+ * Always present: main-brush / filter (every Dreame vacuum has both).
73223
+ * Conditional: side-brush — only included when the model capability record
73224
+ * (`vacuumCapabilities.hasSideBrush`) confirms hardware presence, so a brush-less
73225
+ * model does not report a phantom consumable.
73226
+ *
73227
+ * The library exposes NO per-consumable reset action (no MIoT action id for
73228
+ * resetting brushes/filter in the current property maps), so all items report
73229
+ * `resettable: false`.
73230
+ */
73231
+ function buildVacuumConsumables(vacuum) {
73232
+ const baseItems = [{
73233
+ key: "main-brush",
73234
+ label: "Main Brush",
73235
+ level: clampConsumablePct(vacuum.mainBrushLeftPct),
73236
+ status: null,
73237
+ lastResetAt: null,
73238
+ resettable: false
73239
+ }, {
73240
+ key: "filter",
73241
+ label: "Filter",
73242
+ level: clampConsumablePct(vacuum.filterLeftPct),
73243
+ status: null,
73244
+ lastResetAt: null,
73245
+ resettable: false
73246
+ }];
73247
+ const sideBrushItem = vacuum.vacuumCapabilities.hasSideBrush ? {
73248
+ key: "side-brush",
73249
+ label: "Side Brush",
73250
+ level: clampConsumablePct(vacuum.sideBrushLeftPct),
73251
+ status: null,
73252
+ lastResetAt: null,
73253
+ resettable: false
73254
+ } : null;
73255
+ const now = Date.now();
73256
+ return {
73257
+ items: sideBrushItem !== null ? [...baseItems, sideBrushItem] : [...baseItems],
73258
+ lastChangedAt: now,
73259
+ lastFetchedAt: now
73260
+ };
73261
+ }
73207
73262
  //#endregion
73208
73263
  //#region src/devices/dreame-vacuum-device.ts
73209
73264
  var CAP_NAME$1 = "vacuum-control";
73210
- var COLD_START$1 = {
73265
+ var CONSUMABLES_CAP_NAME = "consumables";
73266
+ var CONTROL_COLD_START = {
73211
73267
  state: "idle",
73212
73268
  batteryLevel: null,
73213
73269
  fanSpeed: null,
@@ -73218,9 +73274,15 @@ var COLD_START$1 = {
73218
73274
  dustBin: null,
73219
73275
  lastChangedAt: 0
73220
73276
  };
73277
+ var CONSUMABLES_COLD_START = {
73278
+ items: [],
73279
+ lastChangedAt: 0,
73280
+ lastFetchedAt: 0
73281
+ };
73221
73282
  /**
73222
- * Dreame robot-vacuum device. Registers the `vacuum-control` cap and maps the
73223
- * library `VacuumDevice` getters → the cap slice on every `stateChanged` push.
73283
+ * Dreame robot-vacuum device. Registers the `vacuum-control` and `consumables`
73284
+ * caps and maps the library `VacuumDevice` getters → the cap slices on every
73285
+ * `stateChanged` push.
73224
73286
  *
73225
73287
  * Commands route to the live handle: `start`→`startCleaning()`, `pause`→
73226
73288
  * `pause()`, `stop`→`stop()`, `returnToBase`→`dock()`, `locate`→`locate()`,
@@ -73233,8 +73295,12 @@ var COLD_START$1 = {
73233
73295
  * level, not a tank fill) plus brush/filter consumable life. We therefore map
73234
73296
  * tank PRESENCE only (from the model capability flags via `decodeVacuumTanks`):
73235
73297
  * a present tank reports `{ level: null, status: null }` (exists, level unknown)
73236
- * and an absent tank reports `null`. Consumable life (brush/filter %) has no
73237
- * field on the `vacuum-control` cap, so it is intentionally not surfaced.
73298
+ * and an absent tank reports `null`.
73299
+ *
73300
+ * Consumables: main-brush / filter (always present), side-brush (conditional on
73301
+ * `vacuumCapabilities.hasSideBrush`). The library exposes no MIoT reset action
73302
+ * for consumables, so all items report `resettable: false` and `reset()` is a
73303
+ * no-op that throws.
73238
73304
  *
73239
73305
  * Map data: the library exposes `lastMap` / `currentSegmentId` (a room/segment
73240
73306
  * list), but no existing camstack cap models a vacuum room map — surfacing it
@@ -73252,7 +73318,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73252
73318
  }
73253
73319
  registerControlCap() {
73254
73320
  this.ctx.registerNativeCap(vacuumControlCapability, {
73255
- getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? COLD_START$1,
73321
+ getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? CONTROL_COLD_START,
73256
73322
  start: async () => {
73257
73323
  await this.requireVacuum().startCleaning();
73258
73324
  },
@@ -73275,13 +73341,36 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73275
73341
  await vacuum.setSuction(level);
73276
73342
  }
73277
73343
  });
73278
- this.runtimeState.setCapState(CAP_NAME$1, COLD_START$1);
73344
+ this.runtimeState.setCapState(CAP_NAME$1, CONTROL_COLD_START);
73345
+ this.registerConsumablesCap();
73346
+ }
73347
+ /**
73348
+ * Register the `consumables` cap. The provider surfaces remaining life % for
73349
+ * main-brush / filter (always present) and side-brush (model-conditional).
73350
+ *
73351
+ * The library exposes no MIoT reset action for consumables, so `reset()` throws
73352
+ * an informative error on every call — all items are marked `resettable: false`
73353
+ * so the UI suppresses the reset button and the mutation is a guard-only path.
73354
+ */
73355
+ registerConsumablesCap() {
73356
+ this.ctx.registerNativeCap(consumablesCapability, {
73357
+ getStatus: async () => this.runtimeState.getCapState(CONSUMABLES_CAP_NAME) ?? CONSUMABLES_COLD_START,
73358
+ reset: async ({ key }) => {
73359
+ throw new Error(`dreame vacuum: consumable reset not supported (item="${key}", device=${this.dreameDeviceId})`);
73360
+ }
73361
+ });
73362
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, CONSUMABLES_COLD_START);
73279
73363
  }
73364
+ /**
73365
+ * `registerControlCap()` (called by `super.onActivate()`) already registers
73366
+ * the consumables cap before the parent seeds the first slice via
73367
+ * `recomputeSlice()`. No additional work needed here; the override is removed.
73368
+ */
73280
73369
  recomputeSlice() {
73281
73370
  const vacuum = this.resolveVacuum();
73282
73371
  if (vacuum === null) return;
73283
73372
  const tanks = decodeVacuumTanks(vacuum.vacuumCapabilities);
73284
- const slice = {
73373
+ const controlSlice = {
73285
73374
  state: mapMiotStateToVacuumState(vacuum.status),
73286
73375
  batteryLevel: clampBatteryLevel$1(vacuum.battery),
73287
73376
  fanSpeed: suctionLevelToken(vacuum.suction),
@@ -73292,7 +73381,9 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73292
73381
  dustBin: tanks.dustBin,
73293
73382
  lastChangedAt: Date.now()
73294
73383
  };
73295
- this.runtimeState.setCapState(CAP_NAME$1, slice);
73384
+ this.runtimeState.setCapState(CAP_NAME$1, controlSlice);
73385
+ const consumablesSlice = buildVacuumConsumables(vacuum);
73386
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, consumablesSlice);
73296
73387
  }
73297
73388
  /** The live typed vacuum handle, or throw a clear error when disconnected. */
73298
73389
  requireVacuum() {
package/dist/addon.mjs CHANGED
@@ -73204,10 +73204,66 @@ function decodeVacuumTanks(caps) {
73204
73204
  dustBin: caps?.canAutoEmpty ? present : null
73205
73205
  };
73206
73206
  }
73207
+ /**
73208
+ * Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
73209
+ * value is absent / non-finite. The library reports remaining life as a 0..100
73210
+ * integer; we clamp defensively so an out-of-range firmware value never violates
73211
+ * the cap schema.
73212
+ */
73213
+ function clampConsumablePct(value) {
73214
+ if (value === null || !Number.isFinite(value)) return null;
73215
+ return Math.max(0, Math.min(100, value));
73216
+ }
73217
+ /**
73218
+ * Build the `consumables` cap runtime-state slice from a live {@link VacuumDevice}
73219
+ * handle. The returned object satisfies the cap's `runtimeState` schema
73220
+ * (`ConsumablesStatusSchema.extend({ lastFetchedAt: z.number() })`).
73221
+ *
73222
+ * Always present: main-brush / filter (every Dreame vacuum has both).
73223
+ * Conditional: side-brush — only included when the model capability record
73224
+ * (`vacuumCapabilities.hasSideBrush`) confirms hardware presence, so a brush-less
73225
+ * model does not report a phantom consumable.
73226
+ *
73227
+ * The library exposes NO per-consumable reset action (no MIoT action id for
73228
+ * resetting brushes/filter in the current property maps), so all items report
73229
+ * `resettable: false`.
73230
+ */
73231
+ function buildVacuumConsumables(vacuum) {
73232
+ const baseItems = [{
73233
+ key: "main-brush",
73234
+ label: "Main Brush",
73235
+ level: clampConsumablePct(vacuum.mainBrushLeftPct),
73236
+ status: null,
73237
+ lastResetAt: null,
73238
+ resettable: false
73239
+ }, {
73240
+ key: "filter",
73241
+ label: "Filter",
73242
+ level: clampConsumablePct(vacuum.filterLeftPct),
73243
+ status: null,
73244
+ lastResetAt: null,
73245
+ resettable: false
73246
+ }];
73247
+ const sideBrushItem = vacuum.vacuumCapabilities.hasSideBrush ? {
73248
+ key: "side-brush",
73249
+ label: "Side Brush",
73250
+ level: clampConsumablePct(vacuum.sideBrushLeftPct),
73251
+ status: null,
73252
+ lastResetAt: null,
73253
+ resettable: false
73254
+ } : null;
73255
+ const now = Date.now();
73256
+ return {
73257
+ items: sideBrushItem !== null ? [...baseItems, sideBrushItem] : [...baseItems],
73258
+ lastChangedAt: now,
73259
+ lastFetchedAt: now
73260
+ };
73261
+ }
73207
73262
  //#endregion
73208
73263
  //#region src/devices/dreame-vacuum-device.ts
73209
73264
  var CAP_NAME$1 = "vacuum-control";
73210
- var COLD_START$1 = {
73265
+ var CONSUMABLES_CAP_NAME = "consumables";
73266
+ var CONTROL_COLD_START = {
73211
73267
  state: "idle",
73212
73268
  batteryLevel: null,
73213
73269
  fanSpeed: null,
@@ -73218,9 +73274,15 @@ var COLD_START$1 = {
73218
73274
  dustBin: null,
73219
73275
  lastChangedAt: 0
73220
73276
  };
73277
+ var CONSUMABLES_COLD_START = {
73278
+ items: [],
73279
+ lastChangedAt: 0,
73280
+ lastFetchedAt: 0
73281
+ };
73221
73282
  /**
73222
- * Dreame robot-vacuum device. Registers the `vacuum-control` cap and maps the
73223
- * library `VacuumDevice` getters → the cap slice on every `stateChanged` push.
73283
+ * Dreame robot-vacuum device. Registers the `vacuum-control` and `consumables`
73284
+ * caps and maps the library `VacuumDevice` getters → the cap slices on every
73285
+ * `stateChanged` push.
73224
73286
  *
73225
73287
  * Commands route to the live handle: `start`→`startCleaning()`, `pause`→
73226
73288
  * `pause()`, `stop`→`stop()`, `returnToBase`→`dock()`, `locate`→`locate()`,
@@ -73233,8 +73295,12 @@ var COLD_START$1 = {
73233
73295
  * level, not a tank fill) plus brush/filter consumable life. We therefore map
73234
73296
  * tank PRESENCE only (from the model capability flags via `decodeVacuumTanks`):
73235
73297
  * a present tank reports `{ level: null, status: null }` (exists, level unknown)
73236
- * and an absent tank reports `null`. Consumable life (brush/filter %) has no
73237
- * field on the `vacuum-control` cap, so it is intentionally not surfaced.
73298
+ * and an absent tank reports `null`.
73299
+ *
73300
+ * Consumables: main-brush / filter (always present), side-brush (conditional on
73301
+ * `vacuumCapabilities.hasSideBrush`). The library exposes no MIoT reset action
73302
+ * for consumables, so all items report `resettable: false` and `reset()` is a
73303
+ * no-op that throws.
73238
73304
  *
73239
73305
  * Map data: the library exposes `lastMap` / `currentSegmentId` (a room/segment
73240
73306
  * list), but no existing camstack cap models a vacuum room map — surfacing it
@@ -73252,7 +73318,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73252
73318
  }
73253
73319
  registerControlCap() {
73254
73320
  this.ctx.registerNativeCap(vacuumControlCapability, {
73255
- getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? COLD_START$1,
73321
+ getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? CONTROL_COLD_START,
73256
73322
  start: async () => {
73257
73323
  await this.requireVacuum().startCleaning();
73258
73324
  },
@@ -73275,13 +73341,36 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73275
73341
  await vacuum.setSuction(level);
73276
73342
  }
73277
73343
  });
73278
- this.runtimeState.setCapState(CAP_NAME$1, COLD_START$1);
73344
+ this.runtimeState.setCapState(CAP_NAME$1, CONTROL_COLD_START);
73345
+ this.registerConsumablesCap();
73346
+ }
73347
+ /**
73348
+ * Register the `consumables` cap. The provider surfaces remaining life % for
73349
+ * main-brush / filter (always present) and side-brush (model-conditional).
73350
+ *
73351
+ * The library exposes no MIoT reset action for consumables, so `reset()` throws
73352
+ * an informative error on every call — all items are marked `resettable: false`
73353
+ * so the UI suppresses the reset button and the mutation is a guard-only path.
73354
+ */
73355
+ registerConsumablesCap() {
73356
+ this.ctx.registerNativeCap(consumablesCapability, {
73357
+ getStatus: async () => this.runtimeState.getCapState(CONSUMABLES_CAP_NAME) ?? CONSUMABLES_COLD_START,
73358
+ reset: async ({ key }) => {
73359
+ throw new Error(`dreame vacuum: consumable reset not supported (item="${key}", device=${this.dreameDeviceId})`);
73360
+ }
73361
+ });
73362
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, CONSUMABLES_COLD_START);
73279
73363
  }
73364
+ /**
73365
+ * `registerControlCap()` (called by `super.onActivate()`) already registers
73366
+ * the consumables cap before the parent seeds the first slice via
73367
+ * `recomputeSlice()`. No additional work needed here; the override is removed.
73368
+ */
73280
73369
  recomputeSlice() {
73281
73370
  const vacuum = this.resolveVacuum();
73282
73371
  if (vacuum === null) return;
73283
73372
  const tanks = decodeVacuumTanks(vacuum.vacuumCapabilities);
73284
- const slice = {
73373
+ const controlSlice = {
73285
73374
  state: mapMiotStateToVacuumState(vacuum.status),
73286
73375
  batteryLevel: clampBatteryLevel$1(vacuum.battery),
73287
73376
  fanSpeed: suctionLevelToken(vacuum.suction),
@@ -73292,7 +73381,9 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73292
73381
  dustBin: tanks.dustBin,
73293
73382
  lastChangedAt: Date.now()
73294
73383
  };
73295
- this.runtimeState.setCapState(CAP_NAME$1, slice);
73384
+ this.runtimeState.setCapState(CAP_NAME$1, controlSlice);
73385
+ const consumablesSlice = buildVacuumConsumables(vacuum);
73386
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, consumablesSlice);
73296
73387
  }
73297
73388
  /** The live typed vacuum handle, or throw a clear error when disconnected. */
73298
73389
  requireVacuum() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreame",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",
@@ -54,6 +54,9 @@
54
54
  },
55
55
  {
56
56
  "name": "device-adoption"
57
+ },
58
+ {
59
+ "name": "consumables"
57
60
  }
58
61
  ]
59
62
  }