@apocaliss92/nodedreame 1.5.0 → 1.6.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 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.3.1";
76
+ var LIBRARY_VERSION = "1.6.0";
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,39 @@ 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
+ * Toggle ONE AI-obstacle feature, preserving the others via a read-modify-write
2871
+ * of the packed `AI_DETECTION` property (mirrors Tasshack `set_ai_detection`).
2872
+ * Capability-gated on `hasAiObstacleDetection`; rejects when the current value
2873
+ * is unknown (a blind write would clobber the other toggles).
2874
+ */
2875
+ async setAiFeature(feature, value) {
2876
+ this.#requireCap(this.#caps.hasAiObstacleDetection, "setAiFeature", "AI obstacle detection");
2877
+ const next = encodeAiFeatureWrite(this.aiDetectionRaw, feature, value);
2878
+ return this.setProperty({ ...VACUUM_PROP.AI_DETECTION, value: next });
2879
+ }
2748
2880
  // -- command helpers ----------------------------------------------------
2749
2881
  #resolveCleanOpts(opts) {
2750
2882
  const repeats = Math.max(1, Math.trunc(opts.repeats ?? 1));
@@ -2980,6 +3112,7 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
2980
3112
  VACUUM_PROP.WATER_VOLUME,
2981
3113
  VACUUM_PROP.CLEAN_MODE_SETTING,
2982
3114
  VACUUM_PROP.TASK_PROGRESS_PCT,
3115
+ VACUUM_PROP.AI_DETECTION,
2983
3116
  BATTERY_PROP.LEVEL,
2984
3117
  BATTERY_PROP.CHARGING_STATUS,
2985
3118
  CONSUMABLE_PROP.MAIN_BRUSH_LEFT,
@@ -3152,7 +3285,7 @@ var MowerFault = /* @__PURE__ */ ((MowerFault2) => {
3152
3285
  })(MowerFault || {});
3153
3286
 
3154
3287
  // src/models/mower/decode.ts
3155
- function isRecord(v) {
3288
+ function isRecord2(v) {
3156
3289
  return typeof v === "object" && v !== null && !Array.isArray(v);
3157
3290
  }
3158
3291
  function numArrayOrNull(v) {
@@ -3169,12 +3302,12 @@ function numArrayOrNull(v) {
3169
3302
  return out;
3170
3303
  }
3171
3304
  function parseTaskDescriptor(value) {
3172
- if (!isRecord(value)) {
3305
+ if (!isRecord2(value)) {
3173
3306
  return null;
3174
3307
  }
3175
3308
  const t = value["t"];
3176
3309
  const d = value["d"];
3177
- if (typeof t !== "string" || !isRecord(d)) {
3310
+ if (typeof t !== "string" || !isRecord2(d)) {
3178
3311
  return null;
3179
3312
  }
3180
3313
  const exe = d["exe"];
@@ -3203,7 +3336,7 @@ function controlActionFor(code) {
3203
3336
  return controlLookup(code);
3204
3337
  }
3205
3338
  function parseControlStatus(value) {
3206
- if (!isRecord(value)) {
3339
+ if (!isRecord2(value)) {
3207
3340
  return null;
3208
3341
  }
3209
3342
  const status = value["status"];
@@ -3313,7 +3446,7 @@ var MowerCapabilityResolver = class {
3313
3446
  // src/models/mower/map/parser.ts
3314
3447
  var PATH_SENTINEL_X = 32767;
3315
3448
  var PATH_SENTINEL_Y = -32768;
3316
- function isRecord2(v) {
3449
+ function isRecord3(v) {
3317
3450
  return typeof v === "object" && v !== null && !Array.isArray(v);
3318
3451
  }
3319
3452
  function asNumber(v, fallback) {
@@ -3351,7 +3484,7 @@ function parseSplitPos(info) {
3351
3484
  return 0;
3352
3485
  }
3353
3486
  function parsePolygonList(dataMap) {
3354
- if (!isRecord2(dataMap) || dataMap["dataType"] !== "Map") {
3487
+ if (!isRecord3(dataMap) || dataMap["dataType"] !== "Map") {
3355
3488
  return [];
3356
3489
  }
3357
3490
  const value = dataMap["value"];
@@ -3363,7 +3496,7 @@ function extractPathCoords(pathList) {
3363
3496
  }
3364
3497
  const out = [];
3365
3498
  for (const p of pathList) {
3366
- if (isRecord2(p) && typeof p["x"] === "number" && typeof p["y"] === "number") {
3499
+ if (isRecord3(p) && typeof p["x"] === "number" && typeof p["y"] === "number") {
3367
3500
  out.push({ x: p["x"], y: p["y"] });
3368
3501
  }
3369
3502
  }
@@ -3398,7 +3531,7 @@ function asEntry(entry) {
3398
3531
  }
3399
3532
  const tuple = entry;
3400
3533
  const data = tuple[1];
3401
- if (!isRecord2(data)) {
3534
+ if (!isRecord3(data)) {
3402
3535
  return null;
3403
3536
  }
3404
3537
  return { id: tuple[0], data };
@@ -3476,7 +3609,7 @@ function parseContours(list) {
3476
3609
  return out;
3477
3610
  }
3478
3611
  function parseBoundary(raw) {
3479
- if (!isRecord2(raw)) {
3612
+ if (!isRecord3(raw)) {
3480
3613
  return null;
3481
3614
  }
3482
3615
  const { x1, y1, x2, y2 } = raw;
@@ -3487,7 +3620,7 @@ function parseBoundary(raw) {
3487
3620
  }
3488
3621
  function parseMowerMap(mapJsonStr) {
3489
3622
  const data = JSON.parse(mapJsonStr);
3490
- if (!isRecord2(data)) {
3623
+ if (!isRecord3(data)) {
3491
3624
  throw new Error("Map JSON is not an object");
3492
3625
  }
3493
3626
  const mapIndex = asNumber(data["mapIndex"], 0);
@@ -4657,6 +4790,8 @@ function createClientDumper(client, options) {
4657
4790
  }
4658
4791
  // Annotate the CommonJS export names for ESM import in node:
4659
4792
  0 && (module.exports = {
4793
+ AI_FEATURE_BIT,
4794
+ AI_FEATURE_JSON_KEY,
4660
4795
  BaseDevice,
4661
4796
  ChargingStatus,
4662
4797
  CleaningMode,
@@ -4686,6 +4821,8 @@ function createClientDumper(client, options) {
4686
4821
  WaterVolume,
4687
4822
  createClientDumper,
4688
4823
  createDumper,
4824
+ decodeAiFeature,
4825
+ encodeAiFeatureWrite,
4689
4826
  getMowerCapabilities,
4690
4827
  getVacuumCapabilities,
4691
4828
  renderMowerSvg,