@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 +100 -9
- package/dist/addon.mjs +100 -9
- package/package.json +4 -1
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
|
|
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`
|
|
73223
|
-
* library `VacuumDevice` getters → the cap
|
|
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`.
|
|
73237
|
-
*
|
|
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) ??
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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`
|
|
73223
|
-
* library `VacuumDevice` getters → the cap
|
|
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`.
|
|
73237
|
-
*
|
|
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) ??
|
|
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,
|
|
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
|
|
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,
|
|
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
|
+
"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
|
}
|