@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.d.cts CHANGED
@@ -710,6 +710,51 @@ declare enum TaskStatus {
710
710
  CustomCleaningWashingPaused = 33
711
711
  }
712
712
 
713
+ /**
714
+ * AI obstacle-detection codec. The robot packs ALL AI-obstacle toggles into a
715
+ * SINGLE MIoT property — `AI_DETECTION` (siid 4 piid 22) — whose value is EITHER
716
+ * an `int` bitmask OR a `str` JSON object, depending on firmware generation.
717
+ * Ported from the Tasshack `dreame-vacuum` HA integration (v2.0.0b25):
718
+ * - INT ←→ `DreameVacuumAIProperty` (bitwise flags)
719
+ * - JSON ←→ `DreameVacuumStrAIProperty` ({ "<key>": bool })
720
+ *
721
+ * This module exposes per-feature decode + a read-modify-write encode that
722
+ * preserves the other features, so a consumer can present each AI toggle as an
723
+ * independent boolean without knowing which on-wire encoding the model uses.
724
+ */
725
+ /** Canonical AI-obstacle feature keys. A given model exposes a subset. */
726
+ type DreameAiFeature = 'furnitureDetection' | 'obstacleDetection' | 'obstaclePicture' | 'fluidDetection' | 'petDetection' | 'obstacleImageUpload' | 'petAvoidance' | 'fuzzyObstacleDetection' | 'petPicture' | 'petFocusedDetection' | 'largeParticlesBoost' | 'humanDetection';
727
+ /**
728
+ * Bit value in the INT-encoded `AI_DETECTION` (Tasshack `DreameVacuumAIProperty`).
729
+ * A feature absent here has no int representation (JSON-only).
730
+ */
731
+ declare const AI_FEATURE_BIT: Readonly<Partial<Record<DreameAiFeature, number>>>;
732
+ /**
733
+ * Key in the JSON-string-encoded `AI_DETECTION` (Tasshack
734
+ * `DreameVacuumStrAIProperty`). A feature absent here has no JSON representation.
735
+ */
736
+ declare const AI_FEATURE_JSON_KEY: Readonly<Partial<Record<DreameAiFeature, string>>>;
737
+ /** The raw on-wire `AI_DETECTION` value: an int bitmask, a JSON string, or absent. */
738
+ type AiDetectionRaw = number | string | null;
739
+ /**
740
+ * Decode ONE AI feature's on/off state from the raw `AI_DETECTION` value,
741
+ * transparently handling both the int-bitmask and JSON-string encodings.
742
+ * Returns `null` when the value is absent, the encoding cannot represent the
743
+ * feature, or the JSON payload omits the key.
744
+ */
745
+ declare function decodeAiFeature(raw: AiDetectionRaw, feature: DreameAiFeature): boolean | null;
746
+ /**
747
+ * Compute the value to WRITE back to `AI_DETECTION` after toggling ONE feature,
748
+ * preserving the on-wire encoding and the other features. Mirrors Tasshack
749
+ * `set_ai_detection`: an int payload is the full new bitmask; a JSON payload
750
+ * carries ONLY the changed key (the device merges it server-side).
751
+ *
752
+ * Throws when the current value/encoding is unknown (a blind read-modify-write
753
+ * could clobber the other features) or the feature has no representation in the
754
+ * active encoding.
755
+ */
756
+ declare function encodeAiFeatureWrite(raw: AiDetectionRaw, feature: DreameAiFeature, value: boolean): number | string;
757
+
713
758
  /**
714
759
  * Per-model vacuum capability records (ported from malard/node-dreame
715
760
  * src/capabilities.ts, MIT — attribution retained) plus a resolver that
@@ -743,6 +788,12 @@ interface VacuumCapabilities {
743
788
  canMap: boolean;
744
789
  supportedSuctionLevels: readonly SuctionLevel[];
745
790
  supportedWaterVolumes: readonly WaterVolume[];
791
+ /**
792
+ * The AI-obstacle toggles this model exposes (subset of {@link DreameAiFeature}),
793
+ * each surfaced as an independent boolean from the packed `AI_DETECTION` property.
794
+ * Empty when `hasAiObstacleDetection` is false.
795
+ */
796
+ supportedAiFeatures: readonly DreameAiFeature[];
746
797
  }
747
798
  declare const MODEL_CAPABILITIES$1: Readonly<Record<string, VacuumCapabilities>>;
748
799
  /** Resolve a model to its rich vacuum capability record (frozen / fallback). */
@@ -1215,6 +1266,37 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
1215
1266
  get sideBrushLeftPct(): number | null;
1216
1267
  get filterLeftPct(): number | null;
1217
1268
  get volume(): number | null;
1269
+ /**
1270
+ * The raw `AI_DETECTION` value (siid 4 piid 22) — an int bitmask or a JSON
1271
+ * string, depending on firmware — or `null` when not yet observed. Prefer the
1272
+ * decoded {@link aiFeature} / {@link supportedAiFeatures} surface.
1273
+ */
1274
+ get aiDetectionRaw(): AiDetectionRaw;
1275
+ /** The AI-obstacle toggles this model exposes (from its capability record). */
1276
+ get supportedAiFeatures(): readonly DreameAiFeature[];
1277
+ /**
1278
+ * Read ONE AI-obstacle toggle's on/off state, decoded from the packed
1279
+ * `AI_DETECTION` value (transparent across the int / JSON encodings). `null`
1280
+ * when the value is unobserved or the feature is not representable.
1281
+ */
1282
+ aiFeature(feature: DreameAiFeature): boolean | null;
1283
+ /**
1284
+ * Seed {@link aiDetectionRaw} from the CLOUD SHADOW (the last value the robot
1285
+ * pushed for `AI_DETECTION`, siid 4 piid 22) WITHOUT waking it. The AI-obstacle
1286
+ * toggles are STATIC settings the robot rarely re-pushes over MQTT, so a fresh
1287
+ * connect to an idle robot has no value cached — call this on activate to
1288
+ * surface the current toggles. Emits the usual `propertyChanged`/`stateChanged`
1289
+ * (so a watching consumer re-decodes) and returns the resolved raw value
1290
+ * (`null` when the model has no AI detection or the shadow carries none yet).
1291
+ */
1292
+ refreshAiDetection(): Promise<AiDetectionRaw>;
1293
+ /**
1294
+ * Toggle ONE AI-obstacle feature, preserving the others via a read-modify-write
1295
+ * of the packed `AI_DETECTION` property (mirrors Tasshack `set_ai_detection`).
1296
+ * Capability-gated on `hasAiObstacleDetection`; rejects when the current value
1297
+ * is unknown (a blind write would clobber the other toggles).
1298
+ */
1299
+ setAiFeature(feature: DreameAiFeature, value: boolean): Promise<unknown>;
1218
1300
  /**
1219
1301
  * Start cleaning (MIoT action siid 2 aiid 1). Named `startCleaning` — NOT
1220
1302
  * `start` — because `BaseDevice.start()` is the lifecycle method that opens
@@ -1347,6 +1429,9 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
1347
1429
  }, {
1348
1430
  readonly siid: 4;
1349
1431
  readonly piid: 63;
1432
+ }, {
1433
+ readonly siid: 4;
1434
+ readonly piid: 22;
1350
1435
  }, {
1351
1436
  readonly siid: 3;
1352
1437
  readonly piid: 1;
@@ -2160,4 +2245,4 @@ declare function createDumper(target: DumperDevice, options?: DumperOptions): Du
2160
2245
  */
2161
2246
  declare function createClientDumper(client: Nodreame, options?: DumperOptions): Dumper[];
2162
2247
 
2163
- export { BaseDevice, type BaseDeviceEvents, type BatchDeviceDataFetcher, type CapabilityResolver, ChargingStatus, type CleanOpts, CleaningMode, type CreateDeviceArgs, DefaultCapabilityResolver, type DeviceCapabilities, type DeviceDump, type DeviceEvent, DreameApiError, DreameAuthError, type DreameCloudState, type DreameDevice, DreameDeviceOfflineError, DreameError, type DreameRegion, type DreameSession, DreameTransportError, type DumperOptions, LIBRARY_NAME, MODEL_CAPABILITIES as MOWER_MODEL_CAPABILITIES, type MapBoundingBox, type MapCleanedAreaOverlay, type MapDimensions, type MapFrameType, type MapLayer, type MapLayerType, type MapLowLyingArea, type MapObstacle, type MapPath, type MapPathType, type MapPoint, type MapPose, type MapRestrictedArea, type MapRoom, type MapRoomWall, type MapRun, type MapSegment, type MapStorey, type MapVirtualWall, type MapWallsInfo, type MiotAction, MiotError, type MiotProp, MiotState, type MowerAvailableMap, type MowerCapabilities, MowerCapabilityResolver, MowerChargingStatus, type MowerContour, MowerControlAction, type MowerControlState, MowerDevice, type MowerDeviceInput, MowerFault, type MowerMap, type MowerMapBoundary, type MowerMowPath, type MowerPathEntry, type MowerPoint, type MowerSpotArea, MowerStatus, type MowerTaskDescriptor, MowerTaskStatus, type MowerZone, Nodreame, type NodreameDeps, type NodreameEvents, type NodreameOptions, type OssFetchInput, type OssFetcherLike, type PropertyChangedEvent, type PropertyResult, type PropertyState, type PropertyWrite, type RenderMowerSvgOptions, type RenderVacuumPngOptions, type StateChangedEvent, SuctionLevel, TaskStatus, MODEL_CAPABILITIES$1 as VACUUM_MODEL_CAPABILITIES, type VacuumCapabilities, VacuumCapabilityResolver, VacuumDevice, type VacuumGetMapInput, type VacuumMap, WaterVolume, createClientDumper, createDumper, getMowerCapabilities, getVacuumCapabilities, renderMowerSvg, renderVacuumPng, resolveCapabilities };
2248
+ export { AI_FEATURE_BIT, AI_FEATURE_JSON_KEY, type AiDetectionRaw, BaseDevice, type BaseDeviceEvents, type BatchDeviceDataFetcher, type CapabilityResolver, ChargingStatus, type CleanOpts, CleaningMode, type CreateDeviceArgs, DefaultCapabilityResolver, type DeviceCapabilities, type DeviceDump, type DeviceEvent, type DreameAiFeature, DreameApiError, DreameAuthError, type DreameCloudState, type DreameDevice, DreameDeviceOfflineError, DreameError, type DreameRegion, type DreameSession, DreameTransportError, type DumperOptions, LIBRARY_NAME, MODEL_CAPABILITIES as MOWER_MODEL_CAPABILITIES, type MapBoundingBox, type MapCleanedAreaOverlay, type MapDimensions, type MapFrameType, type MapLayer, type MapLayerType, type MapLowLyingArea, type MapObstacle, type MapPath, type MapPathType, type MapPoint, type MapPose, type MapRestrictedArea, type MapRoom, type MapRoomWall, type MapRun, type MapSegment, type MapStorey, type MapVirtualWall, type MapWallsInfo, type MiotAction, MiotError, type MiotProp, MiotState, type MowerAvailableMap, type MowerCapabilities, MowerCapabilityResolver, MowerChargingStatus, type MowerContour, MowerControlAction, type MowerControlState, MowerDevice, type MowerDeviceInput, MowerFault, type MowerMap, type MowerMapBoundary, type MowerMowPath, type MowerPathEntry, type MowerPoint, type MowerSpotArea, MowerStatus, type MowerTaskDescriptor, MowerTaskStatus, type MowerZone, Nodreame, type NodreameDeps, type NodreameEvents, type NodreameOptions, type OssFetchInput, type OssFetcherLike, type PropertyChangedEvent, type PropertyResult, type PropertyState, type PropertyWrite, type RenderMowerSvgOptions, type RenderVacuumPngOptions, type StateChangedEvent, SuctionLevel, TaskStatus, MODEL_CAPABILITIES$1 as VACUUM_MODEL_CAPABILITIES, type VacuumCapabilities, VacuumCapabilityResolver, VacuumDevice, type VacuumGetMapInput, type VacuumMap, WaterVolume, createClientDumper, createDumper, decodeAiFeature, encodeAiFeatureWrite, getMowerCapabilities, getVacuumCapabilities, renderMowerSvg, renderVacuumPng, resolveCapabilities };
package/dist/index.d.ts CHANGED
@@ -710,6 +710,51 @@ declare enum TaskStatus {
710
710
  CustomCleaningWashingPaused = 33
711
711
  }
712
712
 
713
+ /**
714
+ * AI obstacle-detection codec. The robot packs ALL AI-obstacle toggles into a
715
+ * SINGLE MIoT property — `AI_DETECTION` (siid 4 piid 22) — whose value is EITHER
716
+ * an `int` bitmask OR a `str` JSON object, depending on firmware generation.
717
+ * Ported from the Tasshack `dreame-vacuum` HA integration (v2.0.0b25):
718
+ * - INT ←→ `DreameVacuumAIProperty` (bitwise flags)
719
+ * - JSON ←→ `DreameVacuumStrAIProperty` ({ "<key>": bool })
720
+ *
721
+ * This module exposes per-feature decode + a read-modify-write encode that
722
+ * preserves the other features, so a consumer can present each AI toggle as an
723
+ * independent boolean without knowing which on-wire encoding the model uses.
724
+ */
725
+ /** Canonical AI-obstacle feature keys. A given model exposes a subset. */
726
+ type DreameAiFeature = 'furnitureDetection' | 'obstacleDetection' | 'obstaclePicture' | 'fluidDetection' | 'petDetection' | 'obstacleImageUpload' | 'petAvoidance' | 'fuzzyObstacleDetection' | 'petPicture' | 'petFocusedDetection' | 'largeParticlesBoost' | 'humanDetection';
727
+ /**
728
+ * Bit value in the INT-encoded `AI_DETECTION` (Tasshack `DreameVacuumAIProperty`).
729
+ * A feature absent here has no int representation (JSON-only).
730
+ */
731
+ declare const AI_FEATURE_BIT: Readonly<Partial<Record<DreameAiFeature, number>>>;
732
+ /**
733
+ * Key in the JSON-string-encoded `AI_DETECTION` (Tasshack
734
+ * `DreameVacuumStrAIProperty`). A feature absent here has no JSON representation.
735
+ */
736
+ declare const AI_FEATURE_JSON_KEY: Readonly<Partial<Record<DreameAiFeature, string>>>;
737
+ /** The raw on-wire `AI_DETECTION` value: an int bitmask, a JSON string, or absent. */
738
+ type AiDetectionRaw = number | string | null;
739
+ /**
740
+ * Decode ONE AI feature's on/off state from the raw `AI_DETECTION` value,
741
+ * transparently handling both the int-bitmask and JSON-string encodings.
742
+ * Returns `null` when the value is absent, the encoding cannot represent the
743
+ * feature, or the JSON payload omits the key.
744
+ */
745
+ declare function decodeAiFeature(raw: AiDetectionRaw, feature: DreameAiFeature): boolean | null;
746
+ /**
747
+ * Compute the value to WRITE back to `AI_DETECTION` after toggling ONE feature,
748
+ * preserving the on-wire encoding and the other features. Mirrors Tasshack
749
+ * `set_ai_detection`: an int payload is the full new bitmask; a JSON payload
750
+ * carries ONLY the changed key (the device merges it server-side).
751
+ *
752
+ * Throws when the current value/encoding is unknown (a blind read-modify-write
753
+ * could clobber the other features) or the feature has no representation in the
754
+ * active encoding.
755
+ */
756
+ declare function encodeAiFeatureWrite(raw: AiDetectionRaw, feature: DreameAiFeature, value: boolean): number | string;
757
+
713
758
  /**
714
759
  * Per-model vacuum capability records (ported from malard/node-dreame
715
760
  * src/capabilities.ts, MIT — attribution retained) plus a resolver that
@@ -743,6 +788,12 @@ interface VacuumCapabilities {
743
788
  canMap: boolean;
744
789
  supportedSuctionLevels: readonly SuctionLevel[];
745
790
  supportedWaterVolumes: readonly WaterVolume[];
791
+ /**
792
+ * The AI-obstacle toggles this model exposes (subset of {@link DreameAiFeature}),
793
+ * each surfaced as an independent boolean from the packed `AI_DETECTION` property.
794
+ * Empty when `hasAiObstacleDetection` is false.
795
+ */
796
+ supportedAiFeatures: readonly DreameAiFeature[];
746
797
  }
747
798
  declare const MODEL_CAPABILITIES$1: Readonly<Record<string, VacuumCapabilities>>;
748
799
  /** Resolve a model to its rich vacuum capability record (frozen / fallback). */
@@ -1215,6 +1266,37 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
1215
1266
  get sideBrushLeftPct(): number | null;
1216
1267
  get filterLeftPct(): number | null;
1217
1268
  get volume(): number | null;
1269
+ /**
1270
+ * The raw `AI_DETECTION` value (siid 4 piid 22) — an int bitmask or a JSON
1271
+ * string, depending on firmware — or `null` when not yet observed. Prefer the
1272
+ * decoded {@link aiFeature} / {@link supportedAiFeatures} surface.
1273
+ */
1274
+ get aiDetectionRaw(): AiDetectionRaw;
1275
+ /** The AI-obstacle toggles this model exposes (from its capability record). */
1276
+ get supportedAiFeatures(): readonly DreameAiFeature[];
1277
+ /**
1278
+ * Read ONE AI-obstacle toggle's on/off state, decoded from the packed
1279
+ * `AI_DETECTION` value (transparent across the int / JSON encodings). `null`
1280
+ * when the value is unobserved or the feature is not representable.
1281
+ */
1282
+ aiFeature(feature: DreameAiFeature): boolean | null;
1283
+ /**
1284
+ * Seed {@link aiDetectionRaw} from the CLOUD SHADOW (the last value the robot
1285
+ * pushed for `AI_DETECTION`, siid 4 piid 22) WITHOUT waking it. The AI-obstacle
1286
+ * toggles are STATIC settings the robot rarely re-pushes over MQTT, so a fresh
1287
+ * connect to an idle robot has no value cached — call this on activate to
1288
+ * surface the current toggles. Emits the usual `propertyChanged`/`stateChanged`
1289
+ * (so a watching consumer re-decodes) and returns the resolved raw value
1290
+ * (`null` when the model has no AI detection or the shadow carries none yet).
1291
+ */
1292
+ refreshAiDetection(): Promise<AiDetectionRaw>;
1293
+ /**
1294
+ * Toggle ONE AI-obstacle feature, preserving the others via a read-modify-write
1295
+ * of the packed `AI_DETECTION` property (mirrors Tasshack `set_ai_detection`).
1296
+ * Capability-gated on `hasAiObstacleDetection`; rejects when the current value
1297
+ * is unknown (a blind write would clobber the other toggles).
1298
+ */
1299
+ setAiFeature(feature: DreameAiFeature, value: boolean): Promise<unknown>;
1218
1300
  /**
1219
1301
  * Start cleaning (MIoT action siid 2 aiid 1). Named `startCleaning` — NOT
1220
1302
  * `start` — because `BaseDevice.start()` is the lifecycle method that opens
@@ -1347,6 +1429,9 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
1347
1429
  }, {
1348
1430
  readonly siid: 4;
1349
1431
  readonly piid: 63;
1432
+ }, {
1433
+ readonly siid: 4;
1434
+ readonly piid: 22;
1350
1435
  }, {
1351
1436
  readonly siid: 3;
1352
1437
  readonly piid: 1;
@@ -2160,4 +2245,4 @@ declare function createDumper(target: DumperDevice, options?: DumperOptions): Du
2160
2245
  */
2161
2246
  declare function createClientDumper(client: Nodreame, options?: DumperOptions): Dumper[];
2162
2247
 
2163
- export { BaseDevice, type BaseDeviceEvents, type BatchDeviceDataFetcher, type CapabilityResolver, ChargingStatus, type CleanOpts, CleaningMode, type CreateDeviceArgs, DefaultCapabilityResolver, type DeviceCapabilities, type DeviceDump, type DeviceEvent, DreameApiError, DreameAuthError, type DreameCloudState, type DreameDevice, DreameDeviceOfflineError, DreameError, type DreameRegion, type DreameSession, DreameTransportError, type DumperOptions, LIBRARY_NAME, MODEL_CAPABILITIES as MOWER_MODEL_CAPABILITIES, type MapBoundingBox, type MapCleanedAreaOverlay, type MapDimensions, type MapFrameType, type MapLayer, type MapLayerType, type MapLowLyingArea, type MapObstacle, type MapPath, type MapPathType, type MapPoint, type MapPose, type MapRestrictedArea, type MapRoom, type MapRoomWall, type MapRun, type MapSegment, type MapStorey, type MapVirtualWall, type MapWallsInfo, type MiotAction, MiotError, type MiotProp, MiotState, type MowerAvailableMap, type MowerCapabilities, MowerCapabilityResolver, MowerChargingStatus, type MowerContour, MowerControlAction, type MowerControlState, MowerDevice, type MowerDeviceInput, MowerFault, type MowerMap, type MowerMapBoundary, type MowerMowPath, type MowerPathEntry, type MowerPoint, type MowerSpotArea, MowerStatus, type MowerTaskDescriptor, MowerTaskStatus, type MowerZone, Nodreame, type NodreameDeps, type NodreameEvents, type NodreameOptions, type OssFetchInput, type OssFetcherLike, type PropertyChangedEvent, type PropertyResult, type PropertyState, type PropertyWrite, type RenderMowerSvgOptions, type RenderVacuumPngOptions, type StateChangedEvent, SuctionLevel, TaskStatus, MODEL_CAPABILITIES$1 as VACUUM_MODEL_CAPABILITIES, type VacuumCapabilities, VacuumCapabilityResolver, VacuumDevice, type VacuumGetMapInput, type VacuumMap, WaterVolume, createClientDumper, createDumper, getMowerCapabilities, getVacuumCapabilities, renderMowerSvg, renderVacuumPng, resolveCapabilities };
2248
+ export { AI_FEATURE_BIT, AI_FEATURE_JSON_KEY, type AiDetectionRaw, BaseDevice, type BaseDeviceEvents, type BatchDeviceDataFetcher, type CapabilityResolver, ChargingStatus, type CleanOpts, CleaningMode, type CreateDeviceArgs, DefaultCapabilityResolver, type DeviceCapabilities, type DeviceDump, type DeviceEvent, type DreameAiFeature, DreameApiError, DreameAuthError, type DreameCloudState, type DreameDevice, DreameDeviceOfflineError, DreameError, type DreameRegion, type DreameSession, DreameTransportError, type DumperOptions, LIBRARY_NAME, MODEL_CAPABILITIES as MOWER_MODEL_CAPABILITIES, type MapBoundingBox, type MapCleanedAreaOverlay, type MapDimensions, type MapFrameType, type MapLayer, type MapLayerType, type MapLowLyingArea, type MapObstacle, type MapPath, type MapPathType, type MapPoint, type MapPose, type MapRestrictedArea, type MapRoom, type MapRoomWall, type MapRun, type MapSegment, type MapStorey, type MapVirtualWall, type MapWallsInfo, type MiotAction, MiotError, type MiotProp, MiotState, type MowerAvailableMap, type MowerCapabilities, MowerCapabilityResolver, MowerChargingStatus, type MowerContour, MowerControlAction, type MowerControlState, MowerDevice, type MowerDeviceInput, MowerFault, type MowerMap, type MowerMapBoundary, type MowerMowPath, type MowerPathEntry, type MowerPoint, type MowerSpotArea, MowerStatus, type MowerTaskDescriptor, MowerTaskStatus, type MowerZone, Nodreame, type NodreameDeps, type NodreameEvents, type NodreameOptions, type OssFetchInput, type OssFetcherLike, type PropertyChangedEvent, type PropertyResult, type PropertyState, type PropertyWrite, type RenderMowerSvgOptions, type RenderVacuumPngOptions, type StateChangedEvent, SuctionLevel, TaskStatus, MODEL_CAPABILITIES$1 as VACUUM_MODEL_CAPABILITIES, type VacuumCapabilities, VacuumCapabilityResolver, VacuumDevice, type VacuumGetMapInput, type VacuumMap, WaterVolume, createClientDumper, createDumper, decodeAiFeature, encodeAiFeatureWrite, getMowerCapabilities, getVacuumCapabilities, renderMowerSvg, renderVacuumPng, resolveCapabilities };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/support/version.ts
2
2
  var LIBRARY_NAME = "nodedreame";
3
- var LIBRARY_VERSION = "1.3.1";
3
+ var LIBRARY_VERSION = "1.6.1";
4
4
 
5
5
  // src/transport/errors.ts
6
6
  var DreameError = class extends Error {
@@ -1149,6 +1149,14 @@ var VACUUM_PROP = {
1149
1149
  CLEANING_MODE: { siid: 4, piid: 23 },
1150
1150
  /** VERIFIED r2532a — Child Lock boolean. */
1151
1151
  CHILD_LOCK: { siid: 4, piid: 27 },
1152
+ /**
1153
+ * AI obstacle-detection toggle bundle (Tasshack types.py:1609 — `AI_DETECTION`,
1154
+ * siid 4 piid 22). The value packs ALL AI-obstacle switches into ONE property,
1155
+ * encoded as EITHER an int bitmask (`DreameVacuumAIProperty`) OR a JSON string
1156
+ * (`DreameVacuumStrAIProperty`) depending on firmware. Decode/encode per-feature
1157
+ * via `ai-detection.ts` ({@link decodeAiFeature} / {@link encodeAiFeatureWrite}).
1158
+ */
1159
+ AI_DETECTION: { siid: 4, piid: 22 },
1152
1160
  /** VERIFIED r2532a 2026-05-03 — task progress percentage 0..100. */
1153
1161
  TASK_PROGRESS_PCT: { siid: 4, piid: 63 },
1154
1162
  /** VERIFIED r2532a — mop-drying progress (minutes ticking during MopDrying). */
@@ -1487,7 +1495,91 @@ function parseFaultList(value) {
1487
1495
  return codes;
1488
1496
  }
1489
1497
 
1498
+ // src/models/vacuum/ai-detection.ts
1499
+ var AI_FEATURE_BIT = {
1500
+ furnitureDetection: 1,
1501
+ obstacleDetection: 2,
1502
+ obstaclePicture: 4,
1503
+ fluidDetection: 8,
1504
+ petDetection: 16,
1505
+ obstacleImageUpload: 32,
1506
+ // AI_IMAGE = 64 is an internal flag, not a user toggle — intentionally omitted.
1507
+ petAvoidance: 128,
1508
+ fuzzyObstacleDetection: 256,
1509
+ petPicture: 512,
1510
+ petFocusedDetection: 1024,
1511
+ largeParticlesBoost: 2048
1512
+ };
1513
+ var AI_FEATURE_JSON_KEY = {
1514
+ obstacleDetection: "obstacle_detect_switch",
1515
+ obstacleImageUpload: "obstacle_app_display_switch",
1516
+ petDetection: "whether_have_pet",
1517
+ humanDetection: "human_detect_switch",
1518
+ furnitureDetection: "furniture_detect_switch",
1519
+ fluidDetection: "fluid_detect_switch"
1520
+ };
1521
+ function isRecord(v) {
1522
+ return v !== null && typeof v === "object" && !Array.isArray(v);
1523
+ }
1524
+ function parseJsonPayload(raw) {
1525
+ try {
1526
+ const parsed = JSON.parse(raw);
1527
+ return isRecord(parsed) ? parsed : null;
1528
+ } catch {
1529
+ return null;
1530
+ }
1531
+ }
1532
+ function coerceBool(v) {
1533
+ if (typeof v === "boolean") return v;
1534
+ if (v === 1 || v === "1") return true;
1535
+ if (v === 0 || v === "0") return false;
1536
+ return null;
1537
+ }
1538
+ function decodeAiFeature(raw, feature) {
1539
+ if (typeof raw === "number") {
1540
+ const bit = AI_FEATURE_BIT[feature];
1541
+ if (bit === void 0) return null;
1542
+ return (raw & bit) === bit;
1543
+ }
1544
+ if (typeof raw === "string") {
1545
+ const key2 = AI_FEATURE_JSON_KEY[feature];
1546
+ if (key2 === void 0) return null;
1547
+ const payload = parseJsonPayload(raw);
1548
+ if (payload === null) return null;
1549
+ return coerceBool(payload[key2]);
1550
+ }
1551
+ return null;
1552
+ }
1553
+ function encodeAiFeatureWrite(raw, feature, value) {
1554
+ if (typeof raw === "number") {
1555
+ const bit = AI_FEATURE_BIT[feature];
1556
+ if (bit === void 0) {
1557
+ throw new Error(`encodeAiFeatureWrite: feature "${feature}" has no int-bitmask representation`);
1558
+ }
1559
+ return value ? raw | bit : raw & ~bit;
1560
+ }
1561
+ if (typeof raw === "string") {
1562
+ const key2 = AI_FEATURE_JSON_KEY[feature];
1563
+ if (key2 === void 0) {
1564
+ throw new Error(`encodeAiFeatureWrite: feature "${feature}" has no JSON-key representation`);
1565
+ }
1566
+ return JSON.stringify({ [key2]: value });
1567
+ }
1568
+ throw new Error(
1569
+ `encodeAiFeatureWrite: AI_DETECTION value not yet known \u2014 cannot safely toggle "${feature}"`
1570
+ );
1571
+ }
1572
+
1490
1573
  // src/models/vacuum/capabilities.ts
1574
+ var X50_AI_FEATURES = [
1575
+ "obstacleDetection",
1576
+ "petDetection",
1577
+ "furnitureDetection",
1578
+ "fluidDetection",
1579
+ "obstaclePicture",
1580
+ "obstacleImageUpload",
1581
+ "fuzzyObstacleDetection"
1582
+ ];
1491
1583
  var FALLBACK = {
1492
1584
  verified: false,
1493
1585
  canMop: false,
@@ -1516,7 +1608,9 @@ var FALLBACK = {
1516
1608
  2 /* Intense */,
1517
1609
  3 /* Max */
1518
1610
  ],
1519
- supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */]
1611
+ supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */],
1612
+ // An unknown model advertises no AI toggles until its feature set is verified.
1613
+ supportedAiFeatures: []
1520
1614
  };
1521
1615
  var X50_FAMILY = {
1522
1616
  canMop: true,
@@ -1542,7 +1636,8 @@ var X50_FAMILY = {
1542
1636
  2 /* Intense */,
1543
1637
  3 /* Max */
1544
1638
  ],
1545
- supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */]
1639
+ supportedWaterVolumes: [1 /* Low */, 2 /* Medium */, 3 /* High */],
1640
+ supportedAiFeatures: X50_AI_FEATURES
1546
1641
  };
1547
1642
  function deepFreeze(obj) {
1548
1643
  if (obj && typeof obj === "object" && !Object.isFrozen(obj)) {
@@ -2676,6 +2771,53 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
2676
2771
  get volume() {
2677
2772
  return this.#num(SETTINGS_PROP.VOLUME.siid, SETTINGS_PROP.VOLUME.piid);
2678
2773
  }
2774
+ // -- AI obstacle-detection ----------------------------------------------
2775
+ /**
2776
+ * The raw `AI_DETECTION` value (siid 4 piid 22) — an int bitmask or a JSON
2777
+ * string, depending on firmware — or `null` when not yet observed. Prefer the
2778
+ * decoded {@link aiFeature} / {@link supportedAiFeatures} surface.
2779
+ */
2780
+ get aiDetectionRaw() {
2781
+ const v = this.getProperty(VACUUM_PROP.AI_DETECTION.siid, VACUUM_PROP.AI_DETECTION.piid)?.value;
2782
+ return typeof v === "number" || typeof v === "string" ? v : null;
2783
+ }
2784
+ /** The AI-obstacle toggles this model exposes (from its capability record). */
2785
+ get supportedAiFeatures() {
2786
+ return this.#caps.supportedAiFeatures;
2787
+ }
2788
+ /**
2789
+ * Read ONE AI-obstacle toggle's on/off state, decoded from the packed
2790
+ * `AI_DETECTION` value (transparent across the int / JSON encodings). `null`
2791
+ * when the value is unobserved or the feature is not representable.
2792
+ */
2793
+ aiFeature(feature) {
2794
+ return decodeAiFeature(this.aiDetectionRaw, feature);
2795
+ }
2796
+ /**
2797
+ * Seed {@link aiDetectionRaw} from the CLOUD SHADOW (the last value the robot
2798
+ * pushed for `AI_DETECTION`, siid 4 piid 22) WITHOUT waking it. The AI-obstacle
2799
+ * toggles are STATIC settings the robot rarely re-pushes over MQTT, so a fresh
2800
+ * connect to an idle robot has no value cached — call this on activate to
2801
+ * surface the current toggles. Emits the usual `propertyChanged`/`stateChanged`
2802
+ * (so a watching consumer re-decodes) and returns the resolved raw value
2803
+ * (`null` when the model has no AI detection or the shadow carries none yet).
2804
+ */
2805
+ async refreshAiDetection() {
2806
+ if (!this.#caps.hasAiObstacleDetection) return null;
2807
+ await this.refreshCachedProperties([VACUUM_PROP.AI_DETECTION]);
2808
+ return this.aiDetectionRaw;
2809
+ }
2810
+ /**
2811
+ * Toggle ONE AI-obstacle feature, preserving the others via a read-modify-write
2812
+ * of the packed `AI_DETECTION` property (mirrors Tasshack `set_ai_detection`).
2813
+ * Capability-gated on `hasAiObstacleDetection`; rejects when the current value
2814
+ * is unknown (a blind write would clobber the other toggles).
2815
+ */
2816
+ async setAiFeature(feature, value) {
2817
+ this.#requireCap(this.#caps.hasAiObstacleDetection, "setAiFeature", "AI obstacle detection");
2818
+ const next = encodeAiFeatureWrite(this.aiDetectionRaw, feature, value);
2819
+ return this.setProperty({ ...VACUUM_PROP.AI_DETECTION, value: next });
2820
+ }
2679
2821
  // -- command helpers ----------------------------------------------------
2680
2822
  #resolveCleanOpts(opts) {
2681
2823
  const repeats = Math.max(1, Math.trunc(opts.repeats ?? 1));
@@ -2911,6 +3053,7 @@ var VacuumDevice = class _VacuumDevice extends BaseDevice {
2911
3053
  VACUUM_PROP.WATER_VOLUME,
2912
3054
  VACUUM_PROP.CLEAN_MODE_SETTING,
2913
3055
  VACUUM_PROP.TASK_PROGRESS_PCT,
3056
+ VACUUM_PROP.AI_DETECTION,
2914
3057
  BATTERY_PROP.LEVEL,
2915
3058
  BATTERY_PROP.CHARGING_STATUS,
2916
3059
  CONSUMABLE_PROP.MAIN_BRUSH_LEFT,
@@ -3083,7 +3226,7 @@ var MowerFault = /* @__PURE__ */ ((MowerFault2) => {
3083
3226
  })(MowerFault || {});
3084
3227
 
3085
3228
  // src/models/mower/decode.ts
3086
- function isRecord(v) {
3229
+ function isRecord2(v) {
3087
3230
  return typeof v === "object" && v !== null && !Array.isArray(v);
3088
3231
  }
3089
3232
  function numArrayOrNull(v) {
@@ -3100,12 +3243,12 @@ function numArrayOrNull(v) {
3100
3243
  return out;
3101
3244
  }
3102
3245
  function parseTaskDescriptor(value) {
3103
- if (!isRecord(value)) {
3246
+ if (!isRecord2(value)) {
3104
3247
  return null;
3105
3248
  }
3106
3249
  const t = value["t"];
3107
3250
  const d = value["d"];
3108
- if (typeof t !== "string" || !isRecord(d)) {
3251
+ if (typeof t !== "string" || !isRecord2(d)) {
3109
3252
  return null;
3110
3253
  }
3111
3254
  const exe = d["exe"];
@@ -3134,7 +3277,7 @@ function controlActionFor(code) {
3134
3277
  return controlLookup(code);
3135
3278
  }
3136
3279
  function parseControlStatus(value) {
3137
- if (!isRecord(value)) {
3280
+ if (!isRecord2(value)) {
3138
3281
  return null;
3139
3282
  }
3140
3283
  const status = value["status"];
@@ -3244,7 +3387,7 @@ var MowerCapabilityResolver = class {
3244
3387
  // src/models/mower/map/parser.ts
3245
3388
  var PATH_SENTINEL_X = 32767;
3246
3389
  var PATH_SENTINEL_Y = -32768;
3247
- function isRecord2(v) {
3390
+ function isRecord3(v) {
3248
3391
  return typeof v === "object" && v !== null && !Array.isArray(v);
3249
3392
  }
3250
3393
  function asNumber(v, fallback) {
@@ -3282,7 +3425,7 @@ function parseSplitPos(info) {
3282
3425
  return 0;
3283
3426
  }
3284
3427
  function parsePolygonList(dataMap) {
3285
- if (!isRecord2(dataMap) || dataMap["dataType"] !== "Map") {
3428
+ if (!isRecord3(dataMap) || dataMap["dataType"] !== "Map") {
3286
3429
  return [];
3287
3430
  }
3288
3431
  const value = dataMap["value"];
@@ -3294,7 +3437,7 @@ function extractPathCoords(pathList) {
3294
3437
  }
3295
3438
  const out = [];
3296
3439
  for (const p of pathList) {
3297
- if (isRecord2(p) && typeof p["x"] === "number" && typeof p["y"] === "number") {
3440
+ if (isRecord3(p) && typeof p["x"] === "number" && typeof p["y"] === "number") {
3298
3441
  out.push({ x: p["x"], y: p["y"] });
3299
3442
  }
3300
3443
  }
@@ -3329,7 +3472,7 @@ function asEntry(entry) {
3329
3472
  }
3330
3473
  const tuple = entry;
3331
3474
  const data = tuple[1];
3332
- if (!isRecord2(data)) {
3475
+ if (!isRecord3(data)) {
3333
3476
  return null;
3334
3477
  }
3335
3478
  return { id: tuple[0], data };
@@ -3407,7 +3550,7 @@ function parseContours(list) {
3407
3550
  return out;
3408
3551
  }
3409
3552
  function parseBoundary(raw) {
3410
- if (!isRecord2(raw)) {
3553
+ if (!isRecord3(raw)) {
3411
3554
  return null;
3412
3555
  }
3413
3556
  const { x1, y1, x2, y2 } = raw;
@@ -3418,7 +3561,7 @@ function parseBoundary(raw) {
3418
3561
  }
3419
3562
  function parseMowerMap(mapJsonStr) {
3420
3563
  const data = JSON.parse(mapJsonStr);
3421
- if (!isRecord2(data)) {
3564
+ if (!isRecord3(data)) {
3422
3565
  throw new Error("Map JSON is not an object");
3423
3566
  }
3424
3567
  const mapIndex = asNumber(data["mapIndex"], 0);
@@ -4587,6 +4730,8 @@ function createClientDumper(client, options) {
4587
4730
  return client.devices.map((d) => createDumper(d, options));
4588
4731
  }
4589
4732
  export {
4733
+ AI_FEATURE_BIT,
4734
+ AI_FEATURE_JSON_KEY,
4590
4735
  BaseDevice,
4591
4736
  ChargingStatus,
4592
4737
  CleaningMode,
@@ -4616,6 +4761,8 @@ export {
4616
4761
  WaterVolume,
4617
4762
  createClientDumper,
4618
4763
  createDumper,
4764
+ decodeAiFeature,
4765
+ encodeAiFeatureWrite,
4619
4766
  getMowerCapabilities,
4620
4767
  getVacuumCapabilities,
4621
4768
  renderMowerSvg,