@apocaliss92/nodedreame 1.9.0 → 1.11.0
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/index.cjs +400 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -1
- package/dist/index.d.ts +62 -1
- package/dist/index.js +397 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -69,9 +69,12 @@ __export(index_exports, {
|
|
|
69
69
|
decodeAutoSwitchAll: () => decodeAutoSwitchAll,
|
|
70
70
|
encodeAiFeatureWrite: () => encodeAiFeatureWrite,
|
|
71
71
|
encodeAutoSwitchWrite: () => encodeAutoSwitchWrite,
|
|
72
|
+
extractMowerConsumableValues: () => extractMowerConsumableValues,
|
|
72
73
|
getMowerCapabilities: () => getMowerCapabilities,
|
|
73
74
|
getVacuumCapabilities: () => getVacuumCapabilities,
|
|
74
75
|
isDreameConsumableKey: () => isDreameConsumableKey,
|
|
76
|
+
mowerConsumableIndex: () => mowerConsumableIndex,
|
|
77
|
+
parseMowerConsumables: () => parseMowerConsumables,
|
|
75
78
|
renderMowerSvg: () => renderMowerSvg,
|
|
76
79
|
renderVacuumPng: () => renderVacuumPng,
|
|
77
80
|
resolveCapabilities: () => resolveCapabilities,
|
|
@@ -81,7 +84,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
81
84
|
|
|
82
85
|
// src/support/version.ts
|
|
83
86
|
var LIBRARY_NAME = "nodedreame";
|
|
84
|
-
var LIBRARY_VERSION = "1.
|
|
87
|
+
var LIBRARY_VERSION = "1.11.0";
|
|
85
88
|
|
|
86
89
|
// src/transport/errors.ts
|
|
87
90
|
var DreameError = class extends Error {
|
|
@@ -2633,6 +2636,157 @@ function decodeCleanedAreaPixels(pixels, width, height) {
|
|
|
2633
2636
|
return { cleaned, dirty };
|
|
2634
2637
|
}
|
|
2635
2638
|
|
|
2639
|
+
// src/models/vacuum/map/merge.ts
|
|
2640
|
+
var OutOfOrderFrameError = class extends Error {
|
|
2641
|
+
expectedFrameId;
|
|
2642
|
+
actualFrameId;
|
|
2643
|
+
constructor(expectedFrameId, actualFrameId) {
|
|
2644
|
+
super(`map: out-of-order P-frame (expected frame_id=${expectedFrameId}, got ${actualFrameId})`);
|
|
2645
|
+
this.name = "OutOfOrderFrameError";
|
|
2646
|
+
this.expectedFrameId = expectedFrameId;
|
|
2647
|
+
this.actualFrameId = actualFrameId;
|
|
2648
|
+
}
|
|
2649
|
+
};
|
|
2650
|
+
function mergePFrame(prevInflated, pFrameInflated) {
|
|
2651
|
+
const prevHeader = parseMapHeader(prevInflated);
|
|
2652
|
+
const pHeader = parseMapHeader(pFrameInflated);
|
|
2653
|
+
if (pHeader.frameType !== "P") {
|
|
2654
|
+
throw new MapDecodeError(`mergePFrame: expected P-frame, got frame_type=${pHeader.frameType}`);
|
|
2655
|
+
}
|
|
2656
|
+
if (pHeader.mapId !== prevHeader.mapId) {
|
|
2657
|
+
throw new MapDecodeError(
|
|
2658
|
+
`mergePFrame: map_id mismatch (prev=${prevHeader.mapId}, P=${pHeader.mapId}) \u2014 request a fresh I-frame`
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
if (pHeader.frameId !== prevHeader.frameId + 1) {
|
|
2662
|
+
throw new OutOfOrderFrameError(prevHeader.frameId + 1, pHeader.frameId);
|
|
2663
|
+
}
|
|
2664
|
+
if (prevHeader.gridSize !== pHeader.gridSize && pHeader.width > 0 && pHeader.height > 0) {
|
|
2665
|
+
throw new MapDecodeError(
|
|
2666
|
+
`mergePFrame: grid_size changed mid-stream (${prevHeader.gridSize} \u2192 ${pHeader.gridSize})`
|
|
2667
|
+
);
|
|
2668
|
+
}
|
|
2669
|
+
const prevTail = parseMapJsonTail(sliceTailText(prevInflated, prevHeader));
|
|
2670
|
+
const pTail = parseMapJsonTail(sliceTailText(pFrameInflated, pHeader));
|
|
2671
|
+
const prevLeft = prevTail.origin?.[0] ?? prevHeader.left;
|
|
2672
|
+
const prevTop = prevTail.origin?.[1] ?? prevHeader.top;
|
|
2673
|
+
const grid = prevHeader.gridSize;
|
|
2674
|
+
const prevRight = prevLeft + prevHeader.width * grid;
|
|
2675
|
+
const prevBottom = prevTop + prevHeader.height * grid;
|
|
2676
|
+
const hasPixelDelta = pHeader.width > 0 && pHeader.height > 0;
|
|
2677
|
+
let unionLeft = prevLeft;
|
|
2678
|
+
let unionTop = prevTop;
|
|
2679
|
+
let unionWidth = prevHeader.width;
|
|
2680
|
+
let unionHeight = prevHeader.height;
|
|
2681
|
+
let pLeft = 0;
|
|
2682
|
+
let pTop = 0;
|
|
2683
|
+
if (hasPixelDelta) {
|
|
2684
|
+
pLeft = pTail.origin?.[0] ?? pHeader.left;
|
|
2685
|
+
pTop = pTail.origin?.[1] ?? pHeader.top;
|
|
2686
|
+
const pRight = pLeft + pHeader.width * grid;
|
|
2687
|
+
const pBottom = pTop + pHeader.height * grid;
|
|
2688
|
+
if ((pLeft - prevLeft) % grid !== 0 || (pTop - prevTop) % grid !== 0) {
|
|
2689
|
+
throw new MapDecodeError(
|
|
2690
|
+
`mergePFrame: P-frame origin not aligned to prev grid (offset=${pLeft - prevLeft},${pTop - prevTop} vs grid=${grid})`
|
|
2691
|
+
);
|
|
2692
|
+
}
|
|
2693
|
+
unionLeft = Math.min(prevLeft, pLeft);
|
|
2694
|
+
unionTop = Math.min(prevTop, pTop);
|
|
2695
|
+
const unionRight = Math.max(prevRight, pRight);
|
|
2696
|
+
const unionBottom = Math.max(prevBottom, pBottom);
|
|
2697
|
+
unionWidth = (unionRight - unionLeft) / grid;
|
|
2698
|
+
unionHeight = (unionBottom - unionTop) / grid;
|
|
2699
|
+
}
|
|
2700
|
+
const newPixels = Buffer.alloc(unionWidth * unionHeight);
|
|
2701
|
+
const prevPixelEnd = HEADER_SIZE + prevHeader.width * prevHeader.height;
|
|
2702
|
+
if (prevInflated.length < prevPixelEnd) {
|
|
2703
|
+
throw new MapDecodeError(
|
|
2704
|
+
`mergePFrame: prev buffer truncated (need ${prevPixelEnd} bytes for header+pixels, got ${prevInflated.length})`
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
const prevPixels = prevInflated.subarray(HEADER_SIZE, prevPixelEnd);
|
|
2708
|
+
const prevDxPx = (prevLeft - unionLeft) / grid;
|
|
2709
|
+
const prevDyPx = (prevTop - unionTop) / grid;
|
|
2710
|
+
for (let y = 0; y < prevHeader.height; y++) {
|
|
2711
|
+
const srcOff = y * prevHeader.width;
|
|
2712
|
+
const dstOff = (prevDyPx + y) * unionWidth + prevDxPx;
|
|
2713
|
+
prevPixels.copy(newPixels, dstOff, srcOff, srcOff + prevHeader.width);
|
|
2714
|
+
}
|
|
2715
|
+
if (hasPixelDelta) {
|
|
2716
|
+
const pPixelEnd = HEADER_SIZE + pHeader.width * pHeader.height;
|
|
2717
|
+
if (pFrameInflated.length < pPixelEnd) {
|
|
2718
|
+
throw new MapDecodeError(
|
|
2719
|
+
`mergePFrame: P buffer truncated (need ${pPixelEnd} bytes for header+pixels, got ${pFrameInflated.length})`
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
const pPixels = pFrameInflated.subarray(HEADER_SIZE, pPixelEnd);
|
|
2723
|
+
const pDxPx = (pLeft - unionLeft) / grid;
|
|
2724
|
+
const pDyPx = (pTop - unionTop) / grid;
|
|
2725
|
+
for (let y = 0; y < pHeader.height; y++) {
|
|
2726
|
+
const dstRow = (pDyPx + y) * unionWidth + pDxPx;
|
|
2727
|
+
const srcRow = y * pHeader.width;
|
|
2728
|
+
for (let x = 0; x < pHeader.width; x++) {
|
|
2729
|
+
newPixels[dstRow + x] = newPixels[dstRow + x] + pPixels[srcRow + x] & 255;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
const newHeader = Buffer.alloc(HEADER_SIZE);
|
|
2734
|
+
newHeader.writeInt16LE(prevHeader.mapId, 0);
|
|
2735
|
+
newHeader.writeInt16LE(pHeader.frameId, 2);
|
|
2736
|
+
newHeader[4] = FRAME_TYPE.I;
|
|
2737
|
+
newHeader.writeInt16LE(pHeader.robotX, 5);
|
|
2738
|
+
newHeader.writeInt16LE(pHeader.robotY, 7);
|
|
2739
|
+
newHeader.writeInt16LE(pHeader.robotA, 9);
|
|
2740
|
+
newHeader.writeInt16LE(pHeader.chargerX, 11);
|
|
2741
|
+
newHeader.writeInt16LE(pHeader.chargerY, 13);
|
|
2742
|
+
newHeader.writeInt16LE(pHeader.chargerA, 15);
|
|
2743
|
+
newHeader.writeInt16LE(grid, 17);
|
|
2744
|
+
newHeader.writeInt16LE(unionWidth, 19);
|
|
2745
|
+
newHeader.writeInt16LE(unionHeight, 21);
|
|
2746
|
+
newHeader.writeInt16LE(unionLeft, 23);
|
|
2747
|
+
newHeader.writeInt16LE(unionTop, 25);
|
|
2748
|
+
const mergedTail = mergeTails(prevTail, pTail, unionLeft, unionTop);
|
|
2749
|
+
const tailBytes = Buffer.from(JSON.stringify(mergedTail), "utf8");
|
|
2750
|
+
return Buffer.concat([newHeader, newPixels, tailBytes]);
|
|
2751
|
+
}
|
|
2752
|
+
function mergePFrameEnvelope(prev, pframe, prevOpts, pframeOpts) {
|
|
2753
|
+
const prevBuf = typeof prev === "string" ? unwrapEnvelope(prev, prevOpts) : prev;
|
|
2754
|
+
const pBuf = typeof pframe === "string" ? unwrapEnvelope(pframe, pframeOpts) : pframe;
|
|
2755
|
+
return mergePFrame(prevBuf, pBuf);
|
|
2756
|
+
}
|
|
2757
|
+
function mergeTails(prev, p, unionLeft, unionTop) {
|
|
2758
|
+
const merged = { ...p };
|
|
2759
|
+
merged.origin = [unionLeft, unionTop];
|
|
2760
|
+
const prevTr = typeof prev.tr === "string" ? prev.tr : "";
|
|
2761
|
+
const pTr = typeof p.tr === "string" ? p.tr : "";
|
|
2762
|
+
if (prevTr || pTr) {
|
|
2763
|
+
merged.tr = prevTr + pTr;
|
|
2764
|
+
} else {
|
|
2765
|
+
delete merged.tr;
|
|
2766
|
+
}
|
|
2767
|
+
if (!("seg_inf" in p) && "seg_inf" in prev) {
|
|
2768
|
+
merged.seg_inf = prev.seg_inf;
|
|
2769
|
+
}
|
|
2770
|
+
if (!("sa" in p) && "sa" in prev) {
|
|
2771
|
+
merged.sa = prev.sa;
|
|
2772
|
+
}
|
|
2773
|
+
for (const key2 of PERSISTENT_TAIL_KEYS) {
|
|
2774
|
+
if (!(key2 in p) && key2 in prev) {
|
|
2775
|
+
merged[key2] = prev[key2];
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
return merged;
|
|
2779
|
+
}
|
|
2780
|
+
var PERSISTENT_TAIL_KEYS = [
|
|
2781
|
+
"vw",
|
|
2782
|
+
"vws",
|
|
2783
|
+
"sneak_areas",
|
|
2784
|
+
"sneak_areas_end",
|
|
2785
|
+
"walls_info",
|
|
2786
|
+
"rism",
|
|
2787
|
+
"decmap"
|
|
2788
|
+
];
|
|
2789
|
+
|
|
2636
2790
|
// src/models/vacuum/map/decode.ts
|
|
2637
2791
|
function decodeVacuumMap(input, opts = {}) {
|
|
2638
2792
|
const inflated = typeof input === "string" ? unwrapEnvelope(input, opts) : looksLikeBase64Zlib(input) ? unwrapEnvelope(input.toString("latin1"), opts) : input;
|
|
@@ -2677,6 +2831,10 @@ function decodeVacuumMap(input, opts = {}) {
|
|
|
2677
2831
|
cleanedArea
|
|
2678
2832
|
};
|
|
2679
2833
|
}
|
|
2834
|
+
function applyVacuumPFrame(prev, pframe, opts = {}) {
|
|
2835
|
+
const merged = typeof prev === "string" || typeof pframe === "string" ? mergePFrameEnvelope(prev, pframe, opts.prev, opts.pframe) : mergePFrame(prev, pframe);
|
|
2836
|
+
return { buffer: merged, data: decodeVacuumMap(merged) };
|
|
2837
|
+
}
|
|
2680
2838
|
function mergeDimensions(header, tail) {
|
|
2681
2839
|
const left = tail.origin?.[0] ?? header.left;
|
|
2682
2840
|
const top = tail.origin?.[1] ?? header.top;
|
|
@@ -3614,6 +3772,14 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
|
|
|
3614
3772
|
*/
|
|
3615
3773
|
async getMap(input) {
|
|
3616
3774
|
this.#requireCap(this.#caps.canMap, "getMap", "map decoding");
|
|
3775
|
+
const blob = await this.#fetchMapBlob(input);
|
|
3776
|
+
const map = decodeVacuumMap(blob, this.#decodeOpts(input));
|
|
3777
|
+
this.#lastMap = map;
|
|
3778
|
+
this.emit("map", map);
|
|
3779
|
+
return map;
|
|
3780
|
+
}
|
|
3781
|
+
/** Fetch the raw OSS blob (still the base64+zlib envelope) for a map frame. */
|
|
3782
|
+
async #fetchMapBlob(input) {
|
|
3617
3783
|
const session = this.currentSession();
|
|
3618
3784
|
const region = this.region;
|
|
3619
3785
|
const fetcher = input.fetcher ?? new OssFetcher();
|
|
@@ -3627,14 +3793,77 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
|
|
|
3627
3793
|
...input.timeoutMs !== void 0 ? { timeoutMs: input.timeoutMs } : {},
|
|
3628
3794
|
...input.signal !== void 0 ? { signal: input.signal } : {}
|
|
3629
3795
|
};
|
|
3630
|
-
|
|
3631
|
-
|
|
3796
|
+
return fetcher.fetchBlob(fetchInput);
|
|
3797
|
+
}
|
|
3798
|
+
/** The AES key/iv decode options, threaded from a {@link VacuumGetMapInput}. */
|
|
3799
|
+
#decodeOpts(input) {
|
|
3800
|
+
return {
|
|
3632
3801
|
...input.key !== void 0 ? { key: input.key } : {},
|
|
3633
3802
|
...input.iv !== void 0 ? { iv: input.iv } : {}
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3803
|
+
};
|
|
3804
|
+
}
|
|
3805
|
+
/**
|
|
3806
|
+
* The merge base for live P-frame streaming — always an INFLATED frame buffer.
|
|
3807
|
+
* An I-frame (re)seeds it; each P-frame merge replaces it with the merged
|
|
3808
|
+
* inflated buffer (so further P-frames stack). `null` until the first I-frame
|
|
3809
|
+
* (or after an out-of-order / map-id reset).
|
|
3810
|
+
*/
|
|
3811
|
+
#mapStreamBase = null;
|
|
3812
|
+
/** Drop the P-frame merge base so the next I-frame re-seeds the stream. */
|
|
3813
|
+
resetMapStream() {
|
|
3814
|
+
this.#mapStreamBase = null;
|
|
3815
|
+
}
|
|
3816
|
+
/** Inflate an OSS blob to a raw frame buffer (live envelope → zlib; verbatim if already inflated). */
|
|
3817
|
+
#inflateFrame(blob, opts) {
|
|
3818
|
+
return looksLikeBase64Zlib(blob) ? unwrapEnvelope(blob.toString("latin1"), opts) : blob;
|
|
3819
|
+
}
|
|
3820
|
+
/**
|
|
3821
|
+
* Fetch the latest advertised map frame and fold it into a continuously
|
|
3822
|
+
* updating map. An I-frame (re)seeds the merge base and renders standalone; a
|
|
3823
|
+
* P-frame is merged onto the base via {@link applyVacuumPFrame} so the live
|
|
3824
|
+
* grid stays COMPLETE (a P-frame decoded standalone is only byte-deltas). On an
|
|
3825
|
+
* out-of-order P-frame or a map-id change the base is dropped and `null`
|
|
3826
|
+
* returned — the next I-frame re-seeds. Returns `null` when no frame is
|
|
3827
|
+
* advertised yet, or a P-frame arrives before any I-frame.
|
|
3828
|
+
*
|
|
3829
|
+
* Caches {@link lastMap} and emits `'map'` exactly like {@link getMap}, so a
|
|
3830
|
+
* map-watching consumer (e.g. the camstack map child) gets a fresh complete
|
|
3831
|
+
* frame on every push during a live clean.
|
|
3832
|
+
*/
|
|
3833
|
+
async fetchLatestMapStreaming(opts = {}) {
|
|
3834
|
+
this.#requireCap(this.#caps.canMap, "fetchLatestMapStreaming", "map decoding");
|
|
3835
|
+
const filename = this.mapFilename;
|
|
3836
|
+
if (filename === null) return null;
|
|
3837
|
+
const input = { filename, ...opts };
|
|
3838
|
+
const decodeOpts = this.#decodeOpts(input);
|
|
3839
|
+
const blob = await this.#fetchMapBlob(input);
|
|
3840
|
+
const inflated = this.#inflateFrame(blob, decodeOpts);
|
|
3841
|
+
const frame = decodeVacuumMap(inflated, decodeOpts);
|
|
3842
|
+
if (frame.frameType === "I") {
|
|
3843
|
+
this.#mapStreamBase = inflated;
|
|
3844
|
+
this.#lastMap = frame;
|
|
3845
|
+
this.emit("map", frame);
|
|
3846
|
+
return frame;
|
|
3847
|
+
}
|
|
3848
|
+
if (frame.frameType === "P") {
|
|
3849
|
+
if (this.#mapStreamBase === null) return null;
|
|
3850
|
+
try {
|
|
3851
|
+
const { buffer, data } = applyVacuumPFrame(this.#mapStreamBase, inflated);
|
|
3852
|
+
this.#mapStreamBase = buffer;
|
|
3853
|
+
this.#lastMap = data;
|
|
3854
|
+
this.emit("map", data);
|
|
3855
|
+
return data;
|
|
3856
|
+
} catch (err) {
|
|
3857
|
+
if (err instanceof OutOfOrderFrameError || err instanceof MapDecodeError) {
|
|
3858
|
+
this.#mapStreamBase = null;
|
|
3859
|
+
return null;
|
|
3860
|
+
}
|
|
3861
|
+
throw err;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
this.#lastMap = frame;
|
|
3865
|
+
this.emit("map", frame);
|
|
3866
|
+
return frame;
|
|
3638
3867
|
}
|
|
3639
3868
|
/** Props worth seeding on start() / polling — exported for the facade. */
|
|
3640
3869
|
static DEFAULT_PROPS = [
|
|
@@ -3705,6 +3934,19 @@ function buildEdgePayload(contourIds) {
|
|
|
3705
3934
|
function buildSpotPayload(spotAreaIds) {
|
|
3706
3935
|
return { m: "a", p: 0, o: TASK_OPCODE.SPOT, d: { area: [...spotAreaIds] } };
|
|
3707
3936
|
}
|
|
3937
|
+
function buildGetConsumablePayload() {
|
|
3938
|
+
return { m: "g", t: "CMS" };
|
|
3939
|
+
}
|
|
3940
|
+
function buildSetConsumablePayload(values) {
|
|
3941
|
+
const normalized = values.map((v) => Math.trunc(v));
|
|
3942
|
+
if (normalized.length !== 3) {
|
|
3943
|
+
throw new RangeError(`CMS values must contain exactly 3 counters; got ${normalized.length}`);
|
|
3944
|
+
}
|
|
3945
|
+
if (normalized.some((v) => v < 0)) {
|
|
3946
|
+
throw new RangeError(`CMS values cannot be negative; got ${JSON.stringify(normalized)}`);
|
|
3947
|
+
}
|
|
3948
|
+
return { m: "s", t: "CMS", d: { value: normalized } };
|
|
3949
|
+
}
|
|
3708
3950
|
|
|
3709
3951
|
// src/models/mower/enums.ts
|
|
3710
3952
|
var MowerStatus = /* @__PURE__ */ ((MowerStatus2) => {
|
|
@@ -3896,6 +4138,104 @@ function parseControlStatus(value) {
|
|
|
3896
4138
|
zones
|
|
3897
4139
|
};
|
|
3898
4140
|
}
|
|
4141
|
+
var MOWER_CONSUMABLES = [
|
|
4142
|
+
{ key: "blade", index: 0, totalMinutes: 6e3 },
|
|
4143
|
+
{ key: "brush", index: 1, totalMinutes: 3e4 },
|
|
4144
|
+
{ key: "maintenance", index: 2, totalMinutes: 3600 }
|
|
4145
|
+
];
|
|
4146
|
+
function mowerConsumableIndex(item) {
|
|
4147
|
+
switch (item.trim().toLowerCase()) {
|
|
4148
|
+
case "blade":
|
|
4149
|
+
case "blades":
|
|
4150
|
+
return 0;
|
|
4151
|
+
case "brush":
|
|
4152
|
+
case "cleaning_brush":
|
|
4153
|
+
return 1;
|
|
4154
|
+
case "robot":
|
|
4155
|
+
case "maintenance":
|
|
4156
|
+
case "robot_maintenance":
|
|
4157
|
+
return 2;
|
|
4158
|
+
default:
|
|
4159
|
+
return null;
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
function toInt(v) {
|
|
4163
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
4164
|
+
return Math.trunc(v);
|
|
4165
|
+
}
|
|
4166
|
+
if (typeof v === "string") {
|
|
4167
|
+
const n = Number(v);
|
|
4168
|
+
return Number.isFinite(n) ? Math.trunc(n) : null;
|
|
4169
|
+
}
|
|
4170
|
+
return null;
|
|
4171
|
+
}
|
|
4172
|
+
function extractCustomActionData(result) {
|
|
4173
|
+
if (!isRecord2(result)) {
|
|
4174
|
+
return null;
|
|
4175
|
+
}
|
|
4176
|
+
if (Array.isArray(result["value"])) {
|
|
4177
|
+
return result;
|
|
4178
|
+
}
|
|
4179
|
+
if (isRecord2(result["d"])) {
|
|
4180
|
+
return result["d"];
|
|
4181
|
+
}
|
|
4182
|
+
const out = result["out"];
|
|
4183
|
+
if (!Array.isArray(out)) {
|
|
4184
|
+
return null;
|
|
4185
|
+
}
|
|
4186
|
+
for (const entry of out) {
|
|
4187
|
+
if (!isRecord2(entry)) {
|
|
4188
|
+
continue;
|
|
4189
|
+
}
|
|
4190
|
+
const r = entry["r"];
|
|
4191
|
+
const code = entry["code"];
|
|
4192
|
+
const rError = r !== void 0 && r !== null && r !== 0;
|
|
4193
|
+
const codeError = code !== void 0 && code !== null && code !== 0;
|
|
4194
|
+
if (rError && codeError) {
|
|
4195
|
+
continue;
|
|
4196
|
+
}
|
|
4197
|
+
if (isRecord2(entry["d"])) {
|
|
4198
|
+
return entry["d"];
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
return null;
|
|
4202
|
+
}
|
|
4203
|
+
function extractMowerConsumableValues(result) {
|
|
4204
|
+
const data = extractCustomActionData(result);
|
|
4205
|
+
if (data === null) {
|
|
4206
|
+
return null;
|
|
4207
|
+
}
|
|
4208
|
+
const values = data["value"];
|
|
4209
|
+
if (!Array.isArray(values) || values.length < 3) {
|
|
4210
|
+
return null;
|
|
4211
|
+
}
|
|
4212
|
+
const out = [];
|
|
4213
|
+
for (const v of values.slice(0, 3)) {
|
|
4214
|
+
const n = toInt(v);
|
|
4215
|
+
if (n === null) {
|
|
4216
|
+
return null;
|
|
4217
|
+
}
|
|
4218
|
+
out.push(n);
|
|
4219
|
+
}
|
|
4220
|
+
return out;
|
|
4221
|
+
}
|
|
4222
|
+
function parseMowerConsumables(result) {
|
|
4223
|
+
const values = extractMowerConsumableValues(result);
|
|
4224
|
+
if (values === null) {
|
|
4225
|
+
return null;
|
|
4226
|
+
}
|
|
4227
|
+
return MOWER_CONSUMABLES.map((c) => {
|
|
4228
|
+
const used = values[c.index] ?? 0;
|
|
4229
|
+
const remaining = c.totalMinutes - used;
|
|
4230
|
+
const pct = Math.max(0, Math.min(100, Math.round(remaining / c.totalMinutes * 1e3) / 10));
|
|
4231
|
+
return {
|
|
4232
|
+
key: c.key,
|
|
4233
|
+
usedMinutes: used,
|
|
4234
|
+
totalMinutes: c.totalMinutes,
|
|
4235
|
+
remainingPercent: pct
|
|
4236
|
+
};
|
|
4237
|
+
});
|
|
4238
|
+
}
|
|
3899
4239
|
|
|
3900
4240
|
// src/models/mower/capabilities.ts
|
|
3901
4241
|
var FALLBACK2 = {
|
|
@@ -4661,6 +5001,56 @@ var MowerDevice = class _MowerDevice extends BaseDevice {
|
|
|
4661
5001
|
}
|
|
4662
5002
|
return this.#sendTask(buildSpotPayload(spotAreaIds.map((s) => Math.trunc(s))));
|
|
4663
5003
|
}
|
|
5004
|
+
// -- CMS consumables ----------------------------------------------------
|
|
5005
|
+
/**
|
|
5006
|
+
* Read the raw CMS consumable counters `[blade, brush, robot]` (minutes used),
|
|
5007
|
+
* via the SCHEDULING_TASK (2:50) custom-action getter `{m:'g',t:'CMS'}`. This
|
|
5008
|
+
* is a LIVE device action (it wakes the mower); rejects with
|
|
5009
|
+
* {@link DreameDeviceOfflineError} when the mower is unreachable. Returns null
|
|
5010
|
+
* if the response is malformed.
|
|
5011
|
+
*/
|
|
5012
|
+
async getConsumableValues() {
|
|
5013
|
+
const result = await this.callAction(
|
|
5014
|
+
MOWER_PROP.SCHEDULING_TASK.siid,
|
|
5015
|
+
MOWER_PROP.SCHEDULING_TASK.piid,
|
|
5016
|
+
[buildGetConsumablePayload()]
|
|
5017
|
+
);
|
|
5018
|
+
return extractMowerConsumableValues(result);
|
|
5019
|
+
}
|
|
5020
|
+
/**
|
|
5021
|
+
* Read the CMS consumables as typed readings (blade/brush/maintenance) with
|
|
5022
|
+
* remaining %. LIVE action — see {@link getConsumableValues}. Returns null on
|
|
5023
|
+
* a malformed response.
|
|
5024
|
+
*/
|
|
5025
|
+
async getConsumables() {
|
|
5026
|
+
const result = await this.callAction(
|
|
5027
|
+
MOWER_PROP.SCHEDULING_TASK.siid,
|
|
5028
|
+
MOWER_PROP.SCHEDULING_TASK.piid,
|
|
5029
|
+
[buildGetConsumablePayload()]
|
|
5030
|
+
);
|
|
5031
|
+
return parseMowerConsumables(result);
|
|
5032
|
+
}
|
|
5033
|
+
/**
|
|
5034
|
+
* Reset one CMS consumable counter to zero. Reads the current counters, zeroes
|
|
5035
|
+
* the selected one (leaving the others), and writes them back via the
|
|
5036
|
+
* `{m:'s',t:'CMS',d:{value:[…]}}` setter. LIVE action. Throws on an unknown
|
|
5037
|
+
* item or when the current counters cannot be read.
|
|
5038
|
+
*/
|
|
5039
|
+
async resetConsumable(item) {
|
|
5040
|
+
const index = mowerConsumableIndex(item);
|
|
5041
|
+
if (index === null) {
|
|
5042
|
+
throw new DreameError(`resetConsumable: unknown consumable item "${item}"`);
|
|
5043
|
+
}
|
|
5044
|
+
const current = await this.getConsumableValues();
|
|
5045
|
+
if (current === null) {
|
|
5046
|
+
throw new DreameError("resetConsumable: failed to read current CMS counters");
|
|
5047
|
+
}
|
|
5048
|
+
const next = [...current];
|
|
5049
|
+
next[index] = 0;
|
|
5050
|
+
await this.callAction(MOWER_PROP.SCHEDULING_TASK.siid, MOWER_PROP.SCHEDULING_TASK.piid, [
|
|
5051
|
+
buildSetConsumablePayload(next)
|
|
5052
|
+
]);
|
|
5053
|
+
}
|
|
4664
5054
|
/**
|
|
4665
5055
|
* Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
|
|
4666
5056
|
* mower — reads {@link MowerDevice.DEFAULT_PROPS} from the cloud-cached
|
|
@@ -5363,9 +5753,12 @@ function createClientDumper(client, options) {
|
|
|
5363
5753
|
decodeAutoSwitchAll,
|
|
5364
5754
|
encodeAiFeatureWrite,
|
|
5365
5755
|
encodeAutoSwitchWrite,
|
|
5756
|
+
extractMowerConsumableValues,
|
|
5366
5757
|
getMowerCapabilities,
|
|
5367
5758
|
getVacuumCapabilities,
|
|
5368
5759
|
isDreameConsumableKey,
|
|
5760
|
+
mowerConsumableIndex,
|
|
5761
|
+
parseMowerConsumables,
|
|
5369
5762
|
renderMowerSvg,
|
|
5370
5763
|
renderVacuumPng,
|
|
5371
5764
|
resolveCapabilities,
|