@apocaliss92/nodedreame 1.5.0 → 1.6.1
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 +164 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -1
- package/dist/index.d.ts +86 -1
- package/dist/index.js +160 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
AI_FEATURE_BIT: () => AI_FEATURE_BIT,
|
|
34
|
+
AI_FEATURE_JSON_KEY: () => AI_FEATURE_JSON_KEY,
|
|
33
35
|
BaseDevice: () => BaseDevice,
|
|
34
36
|
ChargingStatus: () => ChargingStatus,
|
|
35
37
|
CleaningMode: () => CleaningMode,
|
|
@@ -59,6 +61,8 @@ __export(index_exports, {
|
|
|
59
61
|
WaterVolume: () => WaterVolume,
|
|
60
62
|
createClientDumper: () => createClientDumper,
|
|
61
63
|
createDumper: () => createDumper,
|
|
64
|
+
decodeAiFeature: () => decodeAiFeature,
|
|
65
|
+
encodeAiFeatureWrite: () => encodeAiFeatureWrite,
|
|
62
66
|
getMowerCapabilities: () => getMowerCapabilities,
|
|
63
67
|
getVacuumCapabilities: () => getVacuumCapabilities,
|
|
64
68
|
renderMowerSvg: () => renderMowerSvg,
|
|
@@ -69,7 +73,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
69
73
|
|
|
70
74
|
// src/support/version.ts
|
|
71
75
|
var LIBRARY_NAME = "nodedreame";
|
|
72
|
-
var LIBRARY_VERSION = "1.
|
|
76
|
+
var LIBRARY_VERSION = "1.6.1";
|
|
73
77
|
|
|
74
78
|
// src/transport/errors.ts
|
|
75
79
|
var DreameError = class extends Error {
|
|
@@ -1218,6 +1222,14 @@ var VACUUM_PROP = {
|
|
|
1218
1222
|
CLEANING_MODE: { siid: 4, piid: 23 },
|
|
1219
1223
|
/** VERIFIED r2532a — Child Lock boolean. */
|
|
1220
1224
|
CHILD_LOCK: { siid: 4, piid: 27 },
|
|
1225
|
+
/**
|
|
1226
|
+
* AI obstacle-detection toggle bundle (Tasshack types.py:1609 — `AI_DETECTION`,
|
|
1227
|
+
* siid 4 piid 22). The value packs ALL AI-obstacle switches into ONE property,
|
|
1228
|
+
* encoded as EITHER an int bitmask (`DreameVacuumAIProperty`) OR a JSON string
|
|
1229
|
+
* (`DreameVacuumStrAIProperty`) depending on firmware. Decode/encode per-feature
|
|
1230
|
+
* via `ai-detection.ts` ({@link decodeAiFeature} / {@link encodeAiFeatureWrite}).
|
|
1231
|
+
*/
|
|
1232
|
+
AI_DETECTION: { siid: 4, piid: 22 },
|
|
1221
1233
|
/** VERIFIED r2532a 2026-05-03 — task progress percentage 0..100. */
|
|
1222
1234
|
TASK_PROGRESS_PCT: { siid: 4, piid: 63 },
|
|
1223
1235
|
/** VERIFIED r2532a — mop-drying progress (minutes ticking during MopDrying). */
|
|
@@ -1556,7 +1568,91 @@ function parseFaultList(value) {
|
|
|
1556
1568
|
return codes;
|
|
1557
1569
|
}
|
|
1558
1570
|
|
|
1571
|
+
// src/models/vacuum/ai-detection.ts
|
|
1572
|
+
var AI_FEATURE_BIT = {
|
|
1573
|
+
furnitureDetection: 1,
|
|
1574
|
+
obstacleDetection: 2,
|
|
1575
|
+
obstaclePicture: 4,
|
|
1576
|
+
fluidDetection: 8,
|
|
1577
|
+
petDetection: 16,
|
|
1578
|
+
obstacleImageUpload: 32,
|
|
1579
|
+
// AI_IMAGE = 64 is an internal flag, not a user toggle — intentionally omitted.
|
|
1580
|
+
petAvoidance: 128,
|
|
1581
|
+
fuzzyObstacleDetection: 256,
|
|
1582
|
+
petPicture: 512,
|
|
1583
|
+
petFocusedDetection: 1024,
|
|
1584
|
+
largeParticlesBoost: 2048
|
|
1585
|
+
};
|
|
1586
|
+
var AI_FEATURE_JSON_KEY = {
|
|
1587
|
+
obstacleDetection: "obstacle_detect_switch",
|
|
1588
|
+
obstacleImageUpload: "obstacle_app_display_switch",
|
|
1589
|
+
petDetection: "whether_have_pet",
|
|
1590
|
+
humanDetection: "human_detect_switch",
|
|
1591
|
+
furnitureDetection: "furniture_detect_switch",
|
|
1592
|
+
fluidDetection: "fluid_detect_switch"
|
|
1593
|
+
};
|
|
1594
|
+
function isRecord(v) {
|
|
1595
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
1596
|
+
}
|
|
1597
|
+
function parseJsonPayload(raw) {
|
|
1598
|
+
try {
|
|
1599
|
+
const parsed = JSON.parse(raw);
|
|
1600
|
+
return isRecord(parsed) ? parsed : null;
|
|
1601
|
+
} catch {
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
function coerceBool(v) {
|
|
1606
|
+
if (typeof v === "boolean") return v;
|
|
1607
|
+
if (v === 1 || v === "1") return true;
|
|
1608
|
+
if (v === 0 || v === "0") return false;
|
|
1609
|
+
return null;
|
|
1610
|
+
}
|
|
1611
|
+
function decodeAiFeature(raw, feature) {
|
|
1612
|
+
if (typeof raw === "number") {
|
|
1613
|
+
const bit = AI_FEATURE_BIT[feature];
|
|
1614
|
+
if (bit === void 0) return null;
|
|
1615
|
+
return (raw & bit) === bit;
|
|
1616
|
+
}
|
|
1617
|
+
if (typeof raw === "string") {
|
|
1618
|
+
const key2 = AI_FEATURE_JSON_KEY[feature];
|
|
1619
|
+
if (key2 === void 0) return null;
|
|
1620
|
+
const payload = parseJsonPayload(raw);
|
|
1621
|
+
if (payload === null) return null;
|
|
1622
|
+
return coerceBool(payload[key2]);
|
|
1623
|
+
}
|
|
1624
|
+
return null;
|
|
1625
|
+
}
|
|
1626
|
+
function encodeAiFeatureWrite(raw, feature, value) {
|
|
1627
|
+
if (typeof raw === "number") {
|
|
1628
|
+
const bit = AI_FEATURE_BIT[feature];
|
|
1629
|
+
if (bit === void 0) {
|
|
1630
|
+
throw new Error(`encodeAiFeatureWrite: feature "${feature}" has no int-bitmask representation`);
|
|
1631
|
+
}
|
|
1632
|
+
return value ? raw | bit : raw & ~bit;
|
|
1633
|
+
}
|
|
1634
|
+
if (typeof raw === "string") {
|
|
1635
|
+
const key2 = AI_FEATURE_JSON_KEY[feature];
|
|
1636
|
+
if (key2 === void 0) {
|
|
1637
|
+
throw new Error(`encodeAiFeatureWrite: feature "${feature}" has no JSON-key representation`);
|
|
1638
|
+
}
|
|
1639
|
+
return JSON.stringify({ [key2]: value });
|
|
1640
|
+
}
|
|
1641
|
+
throw new Error(
|
|
1642
|
+
`encodeAiFeatureWrite: AI_DETECTION value not yet known \u2014 cannot safely toggle "${feature}"`
|
|
1643
|
+
);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1559
1646
|
// src/models/vacuum/capabilities.ts
|
|
1647
|
+
var X50_AI_FEATURES = [
|
|
1648
|
+
"obstacleDetection",
|
|
1649
|
+
"petDetection",
|
|
1650
|
+
"furnitureDetection",
|
|
1651
|
+
"fluidDetection",
|
|
1652
|
+
"obstaclePicture",
|
|
1653
|
+
"obstacleImageUpload",
|
|
1654
|
+
"fuzzyObstacleDetection"
|
|
1655
|
+
];
|
|
1560
1656
|
var FALLBACK = {
|
|
1561
1657
|
verified: false,
|
|
1562
1658
|
canMop: false,
|
|
@@ -1585,7 +1681,9 @@ var FALLBACK = {
|
|
|
1585
1681
|
2 /* Intense */,
|
|
1586
1682
|
3 /* Max */
|
|
1587
1683
|
],
|
|
1588
|
-
supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */]
|
|
1684
|
+
supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */],
|
|
1685
|
+
// An unknown model advertises no AI toggles until its feature set is verified.
|
|
1686
|
+
supportedAiFeatures: []
|
|
1589
1687
|
};
|
|
1590
1688
|
var X50_FAMILY = {
|
|
1591
1689
|
canMop: true,
|
|
@@ -1611,7 +1709,8 @@ var X50_FAMILY = {
|
|
|
1611
1709
|
2 /* Intense */,
|
|
1612
1710
|
3 /* Max */
|
|
1613
1711
|
],
|
|
1614
|
-
supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */]
|
|
1712
|
+
supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */],
|
|
1713
|
+
supportedAiFeatures: X50_AI_FEATURES
|
|
1615
1714
|
};
|
|
1616
1715
|
function deepFreeze(obj) {
|
|
1617
1716
|
if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
|
|
@@ -2745,6 +2844,53 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
|
|
|
2745
2844
|
get volume() {
|
|
2746
2845
|
return this.#num(SETTINGS_PROP.VOLUME.siid, SETTINGS_PROP.VOLUME.piid);
|
|
2747
2846
|
}
|
|
2847
|
+
// -- AI obstacle-detection ----------------------------------------------
|
|
2848
|
+
/**
|
|
2849
|
+
* The raw `AI_DETECTION` value (siid 4 piid 22) — an int bitmask or a JSON
|
|
2850
|
+
* string, depending on firmware — or `null` when not yet observed. Prefer the
|
|
2851
|
+
* decoded {@link aiFeature} / {@link supportedAiFeatures} surface.
|
|
2852
|
+
*/
|
|
2853
|
+
get aiDetectionRaw() {
|
|
2854
|
+
const v = this.getProperty(VACUUM_PROP.AI_DETECTION.siid, VACUUM_PROP.AI_DETECTION.piid)?.value;
|
|
2855
|
+
return typeof v === "number" || typeof v === "string" ? v : null;
|
|
2856
|
+
}
|
|
2857
|
+
/** The AI-obstacle toggles this model exposes (from its capability record). */
|
|
2858
|
+
get supportedAiFeatures() {
|
|
2859
|
+
return this.#caps.supportedAiFeatures;
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Read ONE AI-obstacle toggle's on/off state, decoded from the packed
|
|
2863
|
+
* `AI_DETECTION` value (transparent across the int / JSON encodings). `null`
|
|
2864
|
+
* when the value is unobserved or the feature is not representable.
|
|
2865
|
+
*/
|
|
2866
|
+
aiFeature(feature) {
|
|
2867
|
+
return decodeAiFeature(this.aiDetectionRaw, feature);
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Seed {@link aiDetectionRaw} from the CLOUD SHADOW (the last value the robot
|
|
2871
|
+
* pushed for `AI_DETECTION`, siid 4 piid 22) WITHOUT waking it. The AI-obstacle
|
|
2872
|
+
* toggles are STATIC settings the robot rarely re-pushes over MQTT, so a fresh
|
|
2873
|
+
* connect to an idle robot has no value cached — call this on activate to
|
|
2874
|
+
* surface the current toggles. Emits the usual `propertyChanged`/`stateChanged`
|
|
2875
|
+
* (so a watching consumer re-decodes) and returns the resolved raw value
|
|
2876
|
+
* (`null` when the model has no AI detection or the shadow carries none yet).
|
|
2877
|
+
*/
|
|
2878
|
+
async refreshAiDetection() {
|
|
2879
|
+
if (!this.#caps.hasAiObstacleDetection) return null;
|
|
2880
|
+
await this.refreshCachedProperties([VACUUM_PROP.AI_DETECTION]);
|
|
2881
|
+
return this.aiDetectionRaw;
|
|
2882
|
+
}
|
|
2883
|
+
/**
|
|
2884
|
+
* Toggle ONE AI-obstacle feature, preserving the others via a read-modify-write
|
|
2885
|
+
* of the packed `AI_DETECTION` property (mirrors Tasshack `set_ai_detection`).
|
|
2886
|
+
* Capability-gated on `hasAiObstacleDetection`; rejects when the current value
|
|
2887
|
+
* is unknown (a blind write would clobber the other toggles).
|
|
2888
|
+
*/
|
|
2889
|
+
async setAiFeature(feature, value) {
|
|
2890
|
+
this.#requireCap(this.#caps.hasAiObstacleDetection, "setAiFeature", "AI obstacle detection");
|
|
2891
|
+
const next = encodeAiFeatureWrite(this.aiDetectionRaw, feature, value);
|
|
2892
|
+
return this.setProperty({ ...VACUUM_PROP.AI_DETECTION, value: next });
|
|
2893
|
+
}
|
|
2748
2894
|
// -- command helpers ----------------------------------------------------
|
|
2749
2895
|
#resolveCleanOpts(opts) {
|
|
2750
2896
|
const repeats = Math.max(1, Math.trunc(opts.repeats ?? 1));
|
|
@@ -2980,6 +3126,7 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
|
|
|
2980
3126
|
VACUUM_PROP.WATER_VOLUME,
|
|
2981
3127
|
VACUUM_PROP.CLEAN_MODE_SETTING,
|
|
2982
3128
|
VACUUM_PROP.TASK_PROGRESS_PCT,
|
|
3129
|
+
VACUUM_PROP.AI_DETECTION,
|
|
2983
3130
|
BATTERY_PROP.LEVEL,
|
|
2984
3131
|
BATTERY_PROP.CHARGING_STATUS,
|
|
2985
3132
|
CONSUMABLE_PROP.MAIN_BRUSH_LEFT,
|
|
@@ -3152,7 +3299,7 @@ var MowerFault = /* @__PURE__ */ ((MowerFault2) => {
|
|
|
3152
3299
|
})(MowerFault || {});
|
|
3153
3300
|
|
|
3154
3301
|
// src/models/mower/decode.ts
|
|
3155
|
-
function
|
|
3302
|
+
function isRecord2(v) {
|
|
3156
3303
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3157
3304
|
}
|
|
3158
3305
|
function numArrayOrNull(v) {
|
|
@@ -3169,12 +3316,12 @@ function numArrayOrNull(v) {
|
|
|
3169
3316
|
return out;
|
|
3170
3317
|
}
|
|
3171
3318
|
function parseTaskDescriptor(value) {
|
|
3172
|
-
if (!
|
|
3319
|
+
if (!isRecord2(value)) {
|
|
3173
3320
|
return null;
|
|
3174
3321
|
}
|
|
3175
3322
|
const t = value["t"];
|
|
3176
3323
|
const d = value["d"];
|
|
3177
|
-
if (typeof t !== "string" || !
|
|
3324
|
+
if (typeof t !== "string" || !isRecord2(d)) {
|
|
3178
3325
|
return null;
|
|
3179
3326
|
}
|
|
3180
3327
|
const exe = d["exe"];
|
|
@@ -3203,7 +3350,7 @@ function controlActionFor(code) {
|
|
|
3203
3350
|
return controlLookup(code);
|
|
3204
3351
|
}
|
|
3205
3352
|
function parseControlStatus(value) {
|
|
3206
|
-
if (!
|
|
3353
|
+
if (!isRecord2(value)) {
|
|
3207
3354
|
return null;
|
|
3208
3355
|
}
|
|
3209
3356
|
const status = value["status"];
|
|
@@ -3313,7 +3460,7 @@ var MowerCapabilityResolver = class {
|
|
|
3313
3460
|
// src/models/mower/map/parser.ts
|
|
3314
3461
|
var PATH_SENTINEL_X = 32767;
|
|
3315
3462
|
var PATH_SENTINEL_Y = -32768;
|
|
3316
|
-
function
|
|
3463
|
+
function isRecord3(v) {
|
|
3317
3464
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3318
3465
|
}
|
|
3319
3466
|
function asNumber(v, fallback) {
|
|
@@ -3351,7 +3498,7 @@ function parseSplitPos(info) {
|
|
|
3351
3498
|
return 0;
|
|
3352
3499
|
}
|
|
3353
3500
|
function parsePolygonList(dataMap) {
|
|
3354
|
-
if (!
|
|
3501
|
+
if (!isRecord3(dataMap) || dataMap["dataType"] !== "Map") {
|
|
3355
3502
|
return [];
|
|
3356
3503
|
}
|
|
3357
3504
|
const value = dataMap["value"];
|
|
@@ -3363,7 +3510,7 @@ function extractPathCoords(pathList) {
|
|
|
3363
3510
|
}
|
|
3364
3511
|
const out = [];
|
|
3365
3512
|
for (const p of pathList) {
|
|
3366
|
-
if (
|
|
3513
|
+
if (isRecord3(p) && typeof p["x"] === "number" && typeof p["y"] === "number") {
|
|
3367
3514
|
out.push({ x: p["x"], y: p["y"] });
|
|
3368
3515
|
}
|
|
3369
3516
|
}
|
|
@@ -3398,7 +3545,7 @@ function asEntry(entry) {
|
|
|
3398
3545
|
}
|
|
3399
3546
|
const tuple = entry;
|
|
3400
3547
|
const data = tuple[1];
|
|
3401
|
-
if (!
|
|
3548
|
+
if (!isRecord3(data)) {
|
|
3402
3549
|
return null;
|
|
3403
3550
|
}
|
|
3404
3551
|
return { id: tuple[0], data };
|
|
@@ -3476,7 +3623,7 @@ function parseContours(list) {
|
|
|
3476
3623
|
return out;
|
|
3477
3624
|
}
|
|
3478
3625
|
function parseBoundary(raw) {
|
|
3479
|
-
if (!
|
|
3626
|
+
if (!isRecord3(raw)) {
|
|
3480
3627
|
return null;
|
|
3481
3628
|
}
|
|
3482
3629
|
const { x1, y1, x2, y2 } = raw;
|
|
@@ -3487,7 +3634,7 @@ function parseBoundary(raw) {
|
|
|
3487
3634
|
}
|
|
3488
3635
|
function parseMowerMap(mapJsonStr) {
|
|
3489
3636
|
const data = JSON.parse(mapJsonStr);
|
|
3490
|
-
if (!
|
|
3637
|
+
if (!isRecord3(data)) {
|
|
3491
3638
|
throw new Error("Map JSON is not an object");
|
|
3492
3639
|
}
|
|
3493
3640
|
const mapIndex = asNumber(data["mapIndex"], 0);
|
|
@@ -4657,6 +4804,8 @@ function createClientDumper(client, options) {
|
|
|
4657
4804
|
}
|
|
4658
4805
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4659
4806
|
0 && (module.exports = {
|
|
4807
|
+
AI_FEATURE_BIT,
|
|
4808
|
+
AI_FEATURE_JSON_KEY,
|
|
4660
4809
|
BaseDevice,
|
|
4661
4810
|
ChargingStatus,
|
|
4662
4811
|
CleaningMode,
|
|
@@ -4686,6 +4835,8 @@ function createClientDumper(client, options) {
|
|
|
4686
4835
|
WaterVolume,
|
|
4687
4836
|
createClientDumper,
|
|
4688
4837
|
createDumper,
|
|
4838
|
+
decodeAiFeature,
|
|
4839
|
+
encodeAiFeatureWrite,
|
|
4689
4840
|
getMowerCapabilities,
|
|
4690
4841
|
getVacuumCapabilities,
|
|
4691
4842
|
renderMowerSvg,
|