@apocaliss92/nodedreame 1.0.0 → 1.1.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.d.cts CHANGED
@@ -92,6 +92,12 @@ interface PropertyResult {
92
92
  piid?: number | undefined;
93
93
  value?: unknown;
94
94
  code?: number | undefined;
95
+ /**
96
+ * Epoch-ms the cloud last observed this value. Only populated by the
97
+ * cloud-shadow read (`getCachedProperties`); absent on live `get_properties`.
98
+ * Surfaced so callers can report the cache age of a standby device.
99
+ */
100
+ updateDate?: number | undefined;
95
101
  [key: string]: unknown;
96
102
  }
97
103
 
@@ -289,6 +295,8 @@ interface PushLike {
289
295
  interface BaseDeviceDeps {
290
296
  createPush(device: DreameDevice, session: DreameSession, region: DreameRegion): PushLike;
291
297
  getProperties(base: CommonInput, props: MiotProp[]): Promise<PropertyResult[]>;
298
+ /** Read the cloud-cached (shadow) values WITHOUT waking the device. */
299
+ getCachedProperties(base: CommonInput, props: MiotProp[]): Promise<PropertyResult[]>;
292
300
  setProperties(base: CommonInput, writes: PropertyWrite[]): Promise<PropertyResult[]>;
293
301
  callAction(base: CommonInput, action: {
294
302
  siid: number;
@@ -345,6 +353,13 @@ declare class BaseDevice<Events extends BaseDeviceEvents = BaseDeviceEvents> ext
345
353
  start(): Promise<void>;
346
354
  /** Live-read properties, update the cache, return the raw results. */
347
355
  refreshProperties(props: MiotProp[]): Promise<PropertyResult[]>;
356
+ /**
357
+ * Read the CLOUD-CACHED (shadow) values of `props` WITHOUT waking the device,
358
+ * update the cache, and emit `propertyChanged`/`stateChanged` just like
359
+ * {@link refreshProperties} — but sourced from the cloud shadow endpoint, so
360
+ * it works for standby/offline robots (and never surfaces a false 80001).
361
+ */
362
+ refreshCachedProperties(props: MiotProp[]): Promise<PropertyResult[]>;
348
363
  /** Write a property to the device. */
349
364
  setProperty(write: PropertyWrite): Promise<PropertyResult[]>;
350
365
  /** Invoke a MIoT action on the device. */
@@ -1080,6 +1095,14 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
1080
1095
  x: number;
1081
1096
  y: number;
1082
1097
  }, opts?: CleanOpts): Promise<unknown>;
1098
+ /**
1099
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
1100
+ * robot — reads {@link VacuumDevice.DEFAULT_PROPS} from the cloud-cached
1101
+ * endpoint. After it resolves, every typed getter (status/battery/suction/
1102
+ * water/cleaningMode/error/charging…) reflects the cached values, so a
1103
+ * standby/docked vacuum reports its state exactly as the Dreamehome app does.
1104
+ */
1105
+ refreshFromCache(): Promise<void>;
1083
1106
  /** The most-recently-decoded map, or `null` until {@link getMap} succeeds. */
1084
1107
  get lastMap(): VacuumMap | null;
1085
1108
  /**
@@ -1451,6 +1474,14 @@ declare class MowerDevice extends BaseDevice {
1451
1474
  startMowingEdges(contourIds: number[][]): Promise<unknown>;
1452
1475
  /** Spot mowing (2:50 o:103). */
1453
1476
  startMowingSpots(spotAreaIds: number[]): Promise<unknown>;
1477
+ /**
1478
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
1479
+ * mower — reads {@link MowerDevice.DEFAULT_PROPS} from the cloud-cached
1480
+ * endpoint. After it resolves, every typed getter (status/battery/charging/
1481
+ * coverage/task/controlAction…) reflects the cached values, so a docked/
1482
+ * standby mower reports its state exactly as the Dreamehome app does.
1483
+ */
1484
+ refreshFromCache(): Promise<void>;
1454
1485
  /** The most-recently-parsed map, or `null` until {@link getMap} succeeds. */
1455
1486
  get lastMap(): MowerMap | null;
1456
1487
  /**
package/dist/index.d.ts CHANGED
@@ -92,6 +92,12 @@ interface PropertyResult {
92
92
  piid?: number | undefined;
93
93
  value?: unknown;
94
94
  code?: number | undefined;
95
+ /**
96
+ * Epoch-ms the cloud last observed this value. Only populated by the
97
+ * cloud-shadow read (`getCachedProperties`); absent on live `get_properties`.
98
+ * Surfaced so callers can report the cache age of a standby device.
99
+ */
100
+ updateDate?: number | undefined;
95
101
  [key: string]: unknown;
96
102
  }
97
103
 
@@ -289,6 +295,8 @@ interface PushLike {
289
295
  interface BaseDeviceDeps {
290
296
  createPush(device: DreameDevice, session: DreameSession, region: DreameRegion): PushLike;
291
297
  getProperties(base: CommonInput, props: MiotProp[]): Promise<PropertyResult[]>;
298
+ /** Read the cloud-cached (shadow) values WITHOUT waking the device. */
299
+ getCachedProperties(base: CommonInput, props: MiotProp[]): Promise<PropertyResult[]>;
292
300
  setProperties(base: CommonInput, writes: PropertyWrite[]): Promise<PropertyResult[]>;
293
301
  callAction(base: CommonInput, action: {
294
302
  siid: number;
@@ -345,6 +353,13 @@ declare class BaseDevice<Events extends BaseDeviceEvents = BaseDeviceEvents> ext
345
353
  start(): Promise<void>;
346
354
  /** Live-read properties, update the cache, return the raw results. */
347
355
  refreshProperties(props: MiotProp[]): Promise<PropertyResult[]>;
356
+ /**
357
+ * Read the CLOUD-CACHED (shadow) values of `props` WITHOUT waking the device,
358
+ * update the cache, and emit `propertyChanged`/`stateChanged` just like
359
+ * {@link refreshProperties} — but sourced from the cloud shadow endpoint, so
360
+ * it works for standby/offline robots (and never surfaces a false 80001).
361
+ */
362
+ refreshCachedProperties(props: MiotProp[]): Promise<PropertyResult[]>;
348
363
  /** Write a property to the device. */
349
364
  setProperty(write: PropertyWrite): Promise<PropertyResult[]>;
350
365
  /** Invoke a MIoT action on the device. */
@@ -1080,6 +1095,14 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
1080
1095
  x: number;
1081
1096
  y: number;
1082
1097
  }, opts?: CleanOpts): Promise<unknown>;
1098
+ /**
1099
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
1100
+ * robot — reads {@link VacuumDevice.DEFAULT_PROPS} from the cloud-cached
1101
+ * endpoint. After it resolves, every typed getter (status/battery/suction/
1102
+ * water/cleaningMode/error/charging…) reflects the cached values, so a
1103
+ * standby/docked vacuum reports its state exactly as the Dreamehome app does.
1104
+ */
1105
+ refreshFromCache(): Promise<void>;
1083
1106
  /** The most-recently-decoded map, or `null` until {@link getMap} succeeds. */
1084
1107
  get lastMap(): VacuumMap | null;
1085
1108
  /**
@@ -1451,6 +1474,14 @@ declare class MowerDevice extends BaseDevice {
1451
1474
  startMowingEdges(contourIds: number[][]): Promise<unknown>;
1452
1475
  /** Spot mowing (2:50 o:103). */
1453
1476
  startMowingSpots(spotAreaIds: number[]): Promise<unknown>;
1477
+ /**
1478
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
1479
+ * mower — reads {@link MowerDevice.DEFAULT_PROPS} from the cloud-cached
1480
+ * endpoint. After it resolves, every typed getter (status/battery/charging/
1481
+ * coverage/task/controlAction…) reflects the cached values, so a docked/
1482
+ * standby mower reports its state exactly as the Dreamehome app does.
1483
+ */
1484
+ refreshFromCache(): Promise<void>;
1454
1485
  /** The most-recently-parsed map, or `null` until {@link getMap} succeeds. */
1455
1486
  get lastMap(): MowerMap | null;
1456
1487
  /**
package/dist/index.js CHANGED
@@ -285,6 +285,16 @@ var PropertyResultSchema = z.object({
285
285
  value: z.unknown().optional(),
286
286
  code: z.number().optional()
287
287
  }).passthrough();
288
+ var CachedPropEntrySchema = z.object({
289
+ key: z.string(),
290
+ value: z.unknown().optional(),
291
+ updateDate: z.number().optional()
292
+ }).passthrough();
293
+ var CachedPropsResponseSchema = z.object({
294
+ code: z.number().optional(),
295
+ msg: z.string().optional(),
296
+ data: z.array(CachedPropEntrySchema).optional()
297
+ }).passthrough();
288
298
  var SendCommandResponseSchema = z.object({
289
299
  code: z.number().optional(),
290
300
  msg: z.string().optional(),
@@ -474,6 +484,57 @@ async function getProperties(base, props, opts = {}) {
474
484
  const res = await sendCommand({ ...base, ...opts, method: "get_properties", params });
475
485
  return extractResultArray(res, "get_properties");
476
486
  }
487
+ async function getCachedProperties(base, props, opts = {}) {
488
+ const ctx = base.ctx ?? RequestContext.from({ ...base, host: base.apiHost });
489
+ const keys = props.map((p) => `${p.siid}.${p.piid}`).join(",");
490
+ const signal = opts.signal ?? base.signal;
491
+ const timeoutMs = opts.timeoutMs ?? base.timeoutMs;
492
+ const raw = await httpPostJsonBody({
493
+ ctx,
494
+ path: "/dreame-user-iot/iotstatus/props",
495
+ accessToken: base.session.accessToken,
496
+ body: { did: base.did, keys },
497
+ context: "cached properties",
498
+ ...signal !== void 0 ? { signal } : {},
499
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
500
+ });
501
+ const parsed = CachedPropsResponseSchema.parse(raw);
502
+ if (parsed.code !== void 0 && parsed.code !== 0) {
503
+ throw new DreameApiError(
504
+ `cached properties rejected: code=${parsed.code} msg=${parsed.msg ?? "?"}`,
505
+ 200,
506
+ parsed
507
+ );
508
+ }
509
+ return (parsed.data ?? []).flatMap((entry) => {
510
+ const [siidStr, piidStr] = entry.key.split(".");
511
+ const siid = Number(siidStr);
512
+ const piid = Number(piidStr);
513
+ if (!Number.isFinite(siid) || !Number.isFinite(piid)) {
514
+ return [];
515
+ }
516
+ const result = { siid, piid, value: coerceShadowValue(entry.value) };
517
+ if (entry.updateDate !== void 0) {
518
+ result.updateDate = entry.updateDate;
519
+ }
520
+ return [result];
521
+ });
522
+ }
523
+ function coerceShadowValue(value) {
524
+ if (typeof value !== "string") {
525
+ return value;
526
+ }
527
+ if (value === "true") {
528
+ return true;
529
+ }
530
+ if (value === "false") {
531
+ return false;
532
+ }
533
+ if (value.trim() !== "" && Number.isFinite(Number(value))) {
534
+ return Number(value);
535
+ }
536
+ return value;
537
+ }
477
538
  async function setProperties(base, writes, opts = {}) {
478
539
  const params = writes.map((p) => ({
479
540
  did: base.did,
@@ -851,6 +912,7 @@ function defaultBaseDeviceDeps() {
851
912
  return {
852
913
  createPush: (device, session, region) => new DreamePush({ device, session, region }),
853
914
  getProperties: (base, props) => getProperties(base, props),
915
+ getCachedProperties: (base, props) => getCachedProperties(base, props),
854
916
  setProperties: (base, writes) => setProperties(base, writes),
855
917
  callAction: (base, action) => callAction(base, action)
856
918
  };
@@ -939,6 +1001,23 @@ var BaseDevice = class extends TypedEmitter {
939
1001
  async refreshProperties(props) {
940
1002
  this.#assertOpen();
941
1003
  const results = await this.#deps.getProperties(this.#base(), props);
1004
+ this.#seedFromResults(results);
1005
+ return results;
1006
+ }
1007
+ /**
1008
+ * Read the CLOUD-CACHED (shadow) values of `props` WITHOUT waking the device,
1009
+ * update the cache, and emit `propertyChanged`/`stateChanged` just like
1010
+ * {@link refreshProperties} — but sourced from the cloud shadow endpoint, so
1011
+ * it works for standby/offline robots (and never surfaces a false 80001).
1012
+ */
1013
+ async refreshCachedProperties(props) {
1014
+ this.#assertOpen();
1015
+ const results = await this.#deps.getCachedProperties(this.#base(), props);
1016
+ this.#seedFromResults(results);
1017
+ return results;
1018
+ }
1019
+ /** Mirror a PropertyResult[] into the cache + emit the change events. */
1020
+ #seedFromResults(results) {
942
1021
  const changes = [];
943
1022
  for (const r of results) {
944
1023
  if (typeof r.siid === "number" && typeof r.piid === "number") {
@@ -948,7 +1027,6 @@ var BaseDevice = class extends TypedEmitter {
948
1027
  if (changes.length > 0) {
949
1028
  this.#onProperties(changes);
950
1029
  }
951
- return results;
952
1030
  }
953
1031
  /** Write a property to the device. */
954
1032
  async setProperty(write) {
@@ -2342,7 +2420,7 @@ var TASK = enumLookup([
2342
2420
  12 /* TransientPauseEdge */,
2343
2421
  14 /* NeedsIntervention */
2344
2422
  ]);
2345
- var VacuumDevice = class extends BaseDevice {
2423
+ var VacuumDevice = class _VacuumDevice extends BaseDevice {
2346
2424
  #caps;
2347
2425
  #lastMap = null;
2348
2426
  constructor(input) {
@@ -2557,6 +2635,16 @@ var VacuumDevice = class extends BaseDevice {
2557
2635
  const points = [[Math.round(point.x), Math.round(point.y), repeats, fan, water]];
2558
2636
  return this.#startCustom(CUSTOM_CLEAN_MODE.SPOT, { points });
2559
2637
  }
2638
+ /**
2639
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
2640
+ * robot — reads {@link VacuumDevice.DEFAULT_PROPS} from the cloud-cached
2641
+ * endpoint. After it resolves, every typed getter (status/battery/suction/
2642
+ * water/cleaningMode/error/charging…) reflects the cached values, so a
2643
+ * standby/docked vacuum reports its state exactly as the Dreamehome app does.
2644
+ */
2645
+ async refreshFromCache() {
2646
+ await this.refreshCachedProperties([..._VacuumDevice.DEFAULT_PROPS]);
2647
+ }
2560
2648
  // -- maps ---------------------------------------------------------------
2561
2649
  /** The most-recently-decoded map, or `null` until {@link getMap} succeeds. */
2562
2650
  get lastMap() {
@@ -3389,7 +3477,7 @@ var STATUS = enumLookup(
3389
3477
  var CHARGING2 = enumLookup(
3390
3478
  Object.values(MowerChargingStatus).filter((v) => typeof v === "number")
3391
3479
  );
3392
- var MowerDevice = class extends BaseDevice {
3480
+ var MowerDevice = class _MowerDevice extends BaseDevice {
3393
3481
  #caps;
3394
3482
  #fetchBatch;
3395
3483
  #lastMap = null;
@@ -3529,6 +3617,16 @@ var MowerDevice = class extends BaseDevice {
3529
3617
  }
3530
3618
  return this.#sendTask(buildSpotPayload(spotAreaIds.map((s) => Math.trunc(s))));
3531
3619
  }
3620
+ /**
3621
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
3622
+ * mower — reads {@link MowerDevice.DEFAULT_PROPS} from the cloud-cached
3623
+ * endpoint. After it resolves, every typed getter (status/battery/charging/
3624
+ * coverage/task/controlAction…) reflects the cached values, so a docked/
3625
+ * standby mower reports its state exactly as the Dreamehome app does.
3626
+ */
3627
+ async refreshFromCache() {
3628
+ await this.refreshCachedProperties([..._MowerDevice.DEFAULT_PROPS]);
3629
+ }
3532
3630
  // -- maps ---------------------------------------------------------------
3533
3631
  /** The most-recently-parsed map, or `null` until {@link getMap} succeeds. */
3534
3632
  get lastMap() {