@camstack/addon-provider-dreame 0.1.3 → 0.1.5

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 +112 -10
  2. package/dist/addon.mjs +112 -10
  3. package/package.json +4 -1
package/dist/addon.js CHANGED
@@ -4680,7 +4680,7 @@ function preprocess(fn, schema) {
4680
4680
  });
4681
4681
  }
4682
4682
  //#endregion
4683
- //#region ../types/dist/sleep-NOH4yRwj.mjs
4683
+ //#region ../types/dist/sleep-DVmKHFGi.mjs
4684
4684
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4685
4685
  EventCategory["SystemBoot"] = "system.boot";
4686
4686
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -6480,6 +6480,17 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
6480
6480
  DeviceRole["HumiditySensor"] = "humidity-sensor";
6481
6481
  DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
6482
6482
  DeviceRole["PressureSensor"] = "pressure-sensor";
6483
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
6484
+ DeviceRole["WindSensor"] = "wind-sensor";
6485
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
6486
+ DeviceRole["RainSensor"] = "rain-sensor";
6487
+ /** UV index (weather-station `uv-sensor` cap). */
6488
+ DeviceRole["UvSensor"] = "uv-sensor";
6489
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
6490
+ * Distinct from AmbientLightSensor (lux). */
6491
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
6492
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
6493
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
6483
6494
  DeviceRole["PowerSensor"] = "power-sensor";
6484
6495
  DeviceRole["EnergySensor"] = "energy-sensor";
6485
6496
  DeviceRole["VoltageSensor"] = "voltage-sensor";
@@ -73204,10 +73215,66 @@ function decodeVacuumTanks(caps) {
73204
73215
  dustBin: caps?.canAutoEmpty ? present : null
73205
73216
  };
73206
73217
  }
73218
+ /**
73219
+ * Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
73220
+ * value is absent / non-finite. The library reports remaining life as a 0..100
73221
+ * integer; we clamp defensively so an out-of-range firmware value never violates
73222
+ * the cap schema.
73223
+ */
73224
+ function clampConsumablePct(value) {
73225
+ if (value === null || !Number.isFinite(value)) return null;
73226
+ return Math.max(0, Math.min(100, value));
73227
+ }
73228
+ /**
73229
+ * Build the `consumables` cap runtime-state slice from a live {@link VacuumDevice}
73230
+ * handle. The returned object satisfies the cap's `runtimeState` schema
73231
+ * (`ConsumablesStatusSchema.extend({ lastFetchedAt: z.number() })`).
73232
+ *
73233
+ * Always present: main-brush / filter (every Dreame vacuum has both).
73234
+ * Conditional: side-brush — only included when the model capability record
73235
+ * (`vacuumCapabilities.hasSideBrush`) confirms hardware presence, so a brush-less
73236
+ * model does not report a phantom consumable.
73237
+ *
73238
+ * The library exposes NO per-consumable reset action (no MIoT action id for
73239
+ * resetting brushes/filter in the current property maps), so all items report
73240
+ * `resettable: false`.
73241
+ */
73242
+ function buildVacuumConsumables(vacuum) {
73243
+ const baseItems = [{
73244
+ key: "main-brush",
73245
+ label: "Main Brush",
73246
+ level: clampConsumablePct(vacuum.mainBrushLeftPct),
73247
+ status: null,
73248
+ lastResetAt: null,
73249
+ resettable: false
73250
+ }, {
73251
+ key: "filter",
73252
+ label: "Filter",
73253
+ level: clampConsumablePct(vacuum.filterLeftPct),
73254
+ status: null,
73255
+ lastResetAt: null,
73256
+ resettable: false
73257
+ }];
73258
+ const sideBrushItem = vacuum.vacuumCapabilities.hasSideBrush ? {
73259
+ key: "side-brush",
73260
+ label: "Side Brush",
73261
+ level: clampConsumablePct(vacuum.sideBrushLeftPct),
73262
+ status: null,
73263
+ lastResetAt: null,
73264
+ resettable: false
73265
+ } : null;
73266
+ const now = Date.now();
73267
+ return {
73268
+ items: sideBrushItem !== null ? [...baseItems, sideBrushItem] : [...baseItems],
73269
+ lastChangedAt: now,
73270
+ lastFetchedAt: now
73271
+ };
73272
+ }
73207
73273
  //#endregion
73208
73274
  //#region src/devices/dreame-vacuum-device.ts
73209
73275
  var CAP_NAME$1 = "vacuum-control";
73210
- var COLD_START$1 = {
73276
+ var CONSUMABLES_CAP_NAME = "consumables";
73277
+ var CONTROL_COLD_START = {
73211
73278
  state: "idle",
73212
73279
  batteryLevel: null,
73213
73280
  fanSpeed: null,
@@ -73218,9 +73285,15 @@ var COLD_START$1 = {
73218
73285
  dustBin: null,
73219
73286
  lastChangedAt: 0
73220
73287
  };
73288
+ var CONSUMABLES_COLD_START = {
73289
+ items: [],
73290
+ lastChangedAt: 0,
73291
+ lastFetchedAt: 0
73292
+ };
73221
73293
  /**
73222
- * Dreame robot-vacuum device. Registers the `vacuum-control` cap and maps the
73223
- * library `VacuumDevice` getters → the cap slice on every `stateChanged` push.
73294
+ * Dreame robot-vacuum device. Registers the `vacuum-control` and `consumables`
73295
+ * caps and maps the library `VacuumDevice` getters → the cap slices on every
73296
+ * `stateChanged` push.
73224
73297
  *
73225
73298
  * Commands route to the live handle: `start`→`startCleaning()`, `pause`→
73226
73299
  * `pause()`, `stop`→`stop()`, `returnToBase`→`dock()`, `locate`→`locate()`,
@@ -73233,8 +73306,12 @@ var COLD_START$1 = {
73233
73306
  * level, not a tank fill) plus brush/filter consumable life. We therefore map
73234
73307
  * tank PRESENCE only (from the model capability flags via `decodeVacuumTanks`):
73235
73308
  * 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.
73309
+ * and an absent tank reports `null`.
73310
+ *
73311
+ * Consumables: main-brush / filter (always present), side-brush (conditional on
73312
+ * `vacuumCapabilities.hasSideBrush`). The library exposes no MIoT reset action
73313
+ * for consumables, so all items report `resettable: false` and `reset()` is a
73314
+ * no-op that throws.
73238
73315
  *
73239
73316
  * Map data: the library exposes `lastMap` / `currentSegmentId` (a room/segment
73240
73317
  * list), but no existing camstack cap models a vacuum room map — surfacing it
@@ -73252,7 +73329,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73252
73329
  }
73253
73330
  registerControlCap() {
73254
73331
  this.ctx.registerNativeCap(vacuumControlCapability, {
73255
- getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? COLD_START$1,
73332
+ getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? CONTROL_COLD_START,
73256
73333
  start: async () => {
73257
73334
  await this.requireVacuum().startCleaning();
73258
73335
  },
@@ -73275,13 +73352,36 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73275
73352
  await vacuum.setSuction(level);
73276
73353
  }
73277
73354
  });
73278
- this.runtimeState.setCapState(CAP_NAME$1, COLD_START$1);
73355
+ this.runtimeState.setCapState(CAP_NAME$1, CONTROL_COLD_START);
73356
+ this.registerConsumablesCap();
73357
+ }
73358
+ /**
73359
+ * Register the `consumables` cap. The provider surfaces remaining life % for
73360
+ * main-brush / filter (always present) and side-brush (model-conditional).
73361
+ *
73362
+ * The library exposes no MIoT reset action for consumables, so `reset()` throws
73363
+ * an informative error on every call — all items are marked `resettable: false`
73364
+ * so the UI suppresses the reset button and the mutation is a guard-only path.
73365
+ */
73366
+ registerConsumablesCap() {
73367
+ this.ctx.registerNativeCap(consumablesCapability, {
73368
+ getStatus: async () => this.runtimeState.getCapState(CONSUMABLES_CAP_NAME) ?? CONSUMABLES_COLD_START,
73369
+ reset: async ({ key }) => {
73370
+ throw new Error(`dreame vacuum: consumable reset not supported (item="${key}", device=${this.dreameDeviceId})`);
73371
+ }
73372
+ });
73373
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, CONSUMABLES_COLD_START);
73279
73374
  }
73375
+ /**
73376
+ * `registerControlCap()` (called by `super.onActivate()`) already registers
73377
+ * the consumables cap before the parent seeds the first slice via
73378
+ * `recomputeSlice()`. No additional work needed here; the override is removed.
73379
+ */
73280
73380
  recomputeSlice() {
73281
73381
  const vacuum = this.resolveVacuum();
73282
73382
  if (vacuum === null) return;
73283
73383
  const tanks = decodeVacuumTanks(vacuum.vacuumCapabilities);
73284
- const slice = {
73384
+ const controlSlice = {
73285
73385
  state: mapMiotStateToVacuumState(vacuum.status),
73286
73386
  batteryLevel: clampBatteryLevel$1(vacuum.battery),
73287
73387
  fanSpeed: suctionLevelToken(vacuum.suction),
@@ -73292,7 +73392,9 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73292
73392
  dustBin: tanks.dustBin,
73293
73393
  lastChangedAt: Date.now()
73294
73394
  };
73295
- this.runtimeState.setCapState(CAP_NAME$1, slice);
73395
+ this.runtimeState.setCapState(CAP_NAME$1, controlSlice);
73396
+ const consumablesSlice = buildVacuumConsumables(vacuum);
73397
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, consumablesSlice);
73296
73398
  }
73297
73399
  /** The live typed vacuum handle, or throw a clear error when disconnected. */
73298
73400
  requireVacuum() {
package/dist/addon.mjs CHANGED
@@ -4680,7 +4680,7 @@ function preprocess(fn, schema) {
4680
4680
  });
4681
4681
  }
4682
4682
  //#endregion
4683
- //#region ../types/dist/sleep-NOH4yRwj.mjs
4683
+ //#region ../types/dist/sleep-DVmKHFGi.mjs
4684
4684
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4685
4685
  EventCategory["SystemBoot"] = "system.boot";
4686
4686
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -6480,6 +6480,17 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
6480
6480
  DeviceRole["HumiditySensor"] = "humidity-sensor";
6481
6481
  DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
6482
6482
  DeviceRole["PressureSensor"] = "pressure-sensor";
6483
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
6484
+ DeviceRole["WindSensor"] = "wind-sensor";
6485
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
6486
+ DeviceRole["RainSensor"] = "rain-sensor";
6487
+ /** UV index (weather-station `uv-sensor` cap). */
6488
+ DeviceRole["UvSensor"] = "uv-sensor";
6489
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
6490
+ * Distinct from AmbientLightSensor (lux). */
6491
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
6492
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
6493
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
6483
6494
  DeviceRole["PowerSensor"] = "power-sensor";
6484
6495
  DeviceRole["EnergySensor"] = "energy-sensor";
6485
6496
  DeviceRole["VoltageSensor"] = "voltage-sensor";
@@ -73204,10 +73215,66 @@ function decodeVacuumTanks(caps) {
73204
73215
  dustBin: caps?.canAutoEmpty ? present : null
73205
73216
  };
73206
73217
  }
73218
+ /**
73219
+ * Clamp a raw consumable percentage to `[0, 100]`, or return `null` when the
73220
+ * value is absent / non-finite. The library reports remaining life as a 0..100
73221
+ * integer; we clamp defensively so an out-of-range firmware value never violates
73222
+ * the cap schema.
73223
+ */
73224
+ function clampConsumablePct(value) {
73225
+ if (value === null || !Number.isFinite(value)) return null;
73226
+ return Math.max(0, Math.min(100, value));
73227
+ }
73228
+ /**
73229
+ * Build the `consumables` cap runtime-state slice from a live {@link VacuumDevice}
73230
+ * handle. The returned object satisfies the cap's `runtimeState` schema
73231
+ * (`ConsumablesStatusSchema.extend({ lastFetchedAt: z.number() })`).
73232
+ *
73233
+ * Always present: main-brush / filter (every Dreame vacuum has both).
73234
+ * Conditional: side-brush — only included when the model capability record
73235
+ * (`vacuumCapabilities.hasSideBrush`) confirms hardware presence, so a brush-less
73236
+ * model does not report a phantom consumable.
73237
+ *
73238
+ * The library exposes NO per-consumable reset action (no MIoT action id for
73239
+ * resetting brushes/filter in the current property maps), so all items report
73240
+ * `resettable: false`.
73241
+ */
73242
+ function buildVacuumConsumables(vacuum) {
73243
+ const baseItems = [{
73244
+ key: "main-brush",
73245
+ label: "Main Brush",
73246
+ level: clampConsumablePct(vacuum.mainBrushLeftPct),
73247
+ status: null,
73248
+ lastResetAt: null,
73249
+ resettable: false
73250
+ }, {
73251
+ key: "filter",
73252
+ label: "Filter",
73253
+ level: clampConsumablePct(vacuum.filterLeftPct),
73254
+ status: null,
73255
+ lastResetAt: null,
73256
+ resettable: false
73257
+ }];
73258
+ const sideBrushItem = vacuum.vacuumCapabilities.hasSideBrush ? {
73259
+ key: "side-brush",
73260
+ label: "Side Brush",
73261
+ level: clampConsumablePct(vacuum.sideBrushLeftPct),
73262
+ status: null,
73263
+ lastResetAt: null,
73264
+ resettable: false
73265
+ } : null;
73266
+ const now = Date.now();
73267
+ return {
73268
+ items: sideBrushItem !== null ? [...baseItems, sideBrushItem] : [...baseItems],
73269
+ lastChangedAt: now,
73270
+ lastFetchedAt: now
73271
+ };
73272
+ }
73207
73273
  //#endregion
73208
73274
  //#region src/devices/dreame-vacuum-device.ts
73209
73275
  var CAP_NAME$1 = "vacuum-control";
73210
- var COLD_START$1 = {
73276
+ var CONSUMABLES_CAP_NAME = "consumables";
73277
+ var CONTROL_COLD_START = {
73211
73278
  state: "idle",
73212
73279
  batteryLevel: null,
73213
73280
  fanSpeed: null,
@@ -73218,9 +73285,15 @@ var COLD_START$1 = {
73218
73285
  dustBin: null,
73219
73286
  lastChangedAt: 0
73220
73287
  };
73288
+ var CONSUMABLES_COLD_START = {
73289
+ items: [],
73290
+ lastChangedAt: 0,
73291
+ lastFetchedAt: 0
73292
+ };
73221
73293
  /**
73222
- * Dreame robot-vacuum device. Registers the `vacuum-control` cap and maps the
73223
- * library `VacuumDevice` getters → the cap slice on every `stateChanged` push.
73294
+ * Dreame robot-vacuum device. Registers the `vacuum-control` and `consumables`
73295
+ * caps and maps the library `VacuumDevice` getters → the cap slices on every
73296
+ * `stateChanged` push.
73224
73297
  *
73225
73298
  * Commands route to the live handle: `start`→`startCleaning()`, `pause`→
73226
73299
  * `pause()`, `stop`→`stop()`, `returnToBase`→`dock()`, `locate`→`locate()`,
@@ -73233,8 +73306,12 @@ var COLD_START$1 = {
73233
73306
  * level, not a tank fill) plus brush/filter consumable life. We therefore map
73234
73307
  * tank PRESENCE only (from the model capability flags via `decodeVacuumTanks`):
73235
73308
  * 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.
73309
+ * and an absent tank reports `null`.
73310
+ *
73311
+ * Consumables: main-brush / filter (always present), side-brush (conditional on
73312
+ * `vacuumCapabilities.hasSideBrush`). The library exposes no MIoT reset action
73313
+ * for consumables, so all items report `resettable: false` and `reset()` is a
73314
+ * no-op that throws.
73238
73315
  *
73239
73316
  * Map data: the library exposes `lastMap` / `currentSegmentId` (a room/segment
73240
73317
  * list), but no existing camstack cap models a vacuum room map — surfacing it
@@ -73252,7 +73329,7 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73252
73329
  }
73253
73330
  registerControlCap() {
73254
73331
  this.ctx.registerNativeCap(vacuumControlCapability, {
73255
- getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? COLD_START$1,
73332
+ getStatus: async () => this.runtimeState.getCapState(CAP_NAME$1) ?? CONTROL_COLD_START,
73256
73333
  start: async () => {
73257
73334
  await this.requireVacuum().startCleaning();
73258
73335
  },
@@ -73275,13 +73352,36 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73275
73352
  await vacuum.setSuction(level);
73276
73353
  }
73277
73354
  });
73278
- this.runtimeState.setCapState(CAP_NAME$1, COLD_START$1);
73355
+ this.runtimeState.setCapState(CAP_NAME$1, CONTROL_COLD_START);
73356
+ this.registerConsumablesCap();
73357
+ }
73358
+ /**
73359
+ * Register the `consumables` cap. The provider surfaces remaining life % for
73360
+ * main-brush / filter (always present) and side-brush (model-conditional).
73361
+ *
73362
+ * The library exposes no MIoT reset action for consumables, so `reset()` throws
73363
+ * an informative error on every call — all items are marked `resettable: false`
73364
+ * so the UI suppresses the reset button and the mutation is a guard-only path.
73365
+ */
73366
+ registerConsumablesCap() {
73367
+ this.ctx.registerNativeCap(consumablesCapability, {
73368
+ getStatus: async () => this.runtimeState.getCapState(CONSUMABLES_CAP_NAME) ?? CONSUMABLES_COLD_START,
73369
+ reset: async ({ key }) => {
73370
+ throw new Error(`dreame vacuum: consumable reset not supported (item="${key}", device=${this.dreameDeviceId})`);
73371
+ }
73372
+ });
73373
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, CONSUMABLES_COLD_START);
73279
73374
  }
73375
+ /**
73376
+ * `registerControlCap()` (called by `super.onActivate()`) already registers
73377
+ * the consumables cap before the parent seeds the first slice via
73378
+ * `recomputeSlice()`. No additional work needed here; the override is removed.
73379
+ */
73280
73380
  recomputeSlice() {
73281
73381
  const vacuum = this.resolveVacuum();
73282
73382
  if (vacuum === null) return;
73283
73383
  const tanks = decodeVacuumTanks(vacuum.vacuumCapabilities);
73284
- const slice = {
73384
+ const controlSlice = {
73285
73385
  state: mapMiotStateToVacuumState(vacuum.status),
73286
73386
  batteryLevel: clampBatteryLevel$1(vacuum.battery),
73287
73387
  fanSpeed: suctionLevelToken(vacuum.suction),
@@ -73292,7 +73392,9 @@ var DreameVacuumDevice = class extends DreameChildDevice {
73292
73392
  dustBin: tanks.dustBin,
73293
73393
  lastChangedAt: Date.now()
73294
73394
  };
73295
- this.runtimeState.setCapState(CAP_NAME$1, slice);
73395
+ this.runtimeState.setCapState(CAP_NAME$1, controlSlice);
73396
+ const consumablesSlice = buildVacuumConsumables(vacuum);
73397
+ this.runtimeState.setCapState(CONSUMABLES_CAP_NAME, consumablesSlice);
73296
73398
  }
73297
73399
  /** The live typed vacuum handle, or throw a clear error when disconnected. */
73298
73400
  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.5",
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
  }