@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.cjs CHANGED
@@ -351,6 +351,16 @@ var PropertyResultSchema = import_zod.z.object({
351
351
  value: import_zod.z.unknown().optional(),
352
352
  code: import_zod.z.number().optional()
353
353
  }).passthrough();
354
+ var CachedPropEntrySchema = import_zod.z.object({
355
+ key: import_zod.z.string(),
356
+ value: import_zod.z.unknown().optional(),
357
+ updateDate: import_zod.z.number().optional()
358
+ }).passthrough();
359
+ var CachedPropsResponseSchema = import_zod.z.object({
360
+ code: import_zod.z.number().optional(),
361
+ msg: import_zod.z.string().optional(),
362
+ data: import_zod.z.array(CachedPropEntrySchema).optional()
363
+ }).passthrough();
354
364
  var SendCommandResponseSchema = import_zod.z.object({
355
365
  code: import_zod.z.number().optional(),
356
366
  msg: import_zod.z.string().optional(),
@@ -540,6 +550,57 @@ async function getProperties(base, props, opts = {}) {
540
550
  const res = await sendCommand({ ...base, ...opts, method: "get_properties", params });
541
551
  return extractResultArray(res, "get_properties");
542
552
  }
553
+ async function getCachedProperties(base, props, opts = {}) {
554
+ const ctx = base.ctx ?? RequestContext.from({ ...base, host: base.apiHost });
555
+ const keys = props.map((p) => `${p.siid}.${p.piid}`).join(",");
556
+ const signal = opts.signal ?? base.signal;
557
+ const timeoutMs = opts.timeoutMs ?? base.timeoutMs;
558
+ const raw = await httpPostJsonBody({
559
+ ctx,
560
+ path: "/dreame-user-iot/iotstatus/props",
561
+ accessToken: base.session.accessToken,
562
+ body: { did: base.did, keys },
563
+ context: "cached properties",
564
+ ...signal !== void 0 ? { signal } : {},
565
+ ...timeoutMs !== void 0 ? { timeoutMs } : {}
566
+ });
567
+ const parsed = CachedPropsResponseSchema.parse(raw);
568
+ if (parsed.code !== void 0 && parsed.code !== 0) {
569
+ throw new DreameApiError(
570
+ `cached properties rejected: code=${parsed.code} msg=${parsed.msg ?? "?"}`,
571
+ 200,
572
+ parsed
573
+ );
574
+ }
575
+ return (parsed.data ?? []).flatMap((entry) => {
576
+ const [siidStr, piidStr] = entry.key.split(".");
577
+ const siid = Number(siidStr);
578
+ const piid = Number(piidStr);
579
+ if (!Number.isFinite(siid) || !Number.isFinite(piid)) {
580
+ return [];
581
+ }
582
+ const result = { siid, piid, value: coerceShadowValue(entry.value) };
583
+ if (entry.updateDate !== void 0) {
584
+ result.updateDate = entry.updateDate;
585
+ }
586
+ return [result];
587
+ });
588
+ }
589
+ function coerceShadowValue(value) {
590
+ if (typeof value !== "string") {
591
+ return value;
592
+ }
593
+ if (value === "true") {
594
+ return true;
595
+ }
596
+ if (value === "false") {
597
+ return false;
598
+ }
599
+ if (value.trim() !== "" && Number.isFinite(Number(value))) {
600
+ return Number(value);
601
+ }
602
+ return value;
603
+ }
543
604
  async function setProperties(base, writes, opts = {}) {
544
605
  const params = writes.map((p) => ({
545
606
  did: base.did,
@@ -917,6 +978,7 @@ function defaultBaseDeviceDeps() {
917
978
  return {
918
979
  createPush: (device, session, region) => new DreamePush({ device, session, region }),
919
980
  getProperties: (base, props) => getProperties(base, props),
981
+ getCachedProperties: (base, props) => getCachedProperties(base, props),
920
982
  setProperties: (base, writes) => setProperties(base, writes),
921
983
  callAction: (base, action) => callAction(base, action)
922
984
  };
@@ -1005,6 +1067,23 @@ var BaseDevice = class extends TypedEmitter {
1005
1067
  async refreshProperties(props) {
1006
1068
  this.#assertOpen();
1007
1069
  const results = await this.#deps.getProperties(this.#base(), props);
1070
+ this.#seedFromResults(results);
1071
+ return results;
1072
+ }
1073
+ /**
1074
+ * Read the CLOUD-CACHED (shadow) values of `props` WITHOUT waking the device,
1075
+ * update the cache, and emit `propertyChanged`/`stateChanged` just like
1076
+ * {@link refreshProperties} — but sourced from the cloud shadow endpoint, so
1077
+ * it works for standby/offline robots (and never surfaces a false 80001).
1078
+ */
1079
+ async refreshCachedProperties(props) {
1080
+ this.#assertOpen();
1081
+ const results = await this.#deps.getCachedProperties(this.#base(), props);
1082
+ this.#seedFromResults(results);
1083
+ return results;
1084
+ }
1085
+ /** Mirror a PropertyResult[] into the cache + emit the change events. */
1086
+ #seedFromResults(results) {
1008
1087
  const changes = [];
1009
1088
  for (const r of results) {
1010
1089
  if (typeof r.siid === "number" && typeof r.piid === "number") {
@@ -1014,7 +1093,6 @@ var BaseDevice = class extends TypedEmitter {
1014
1093
  if (changes.length > 0) {
1015
1094
  this.#onProperties(changes);
1016
1095
  }
1017
- return results;
1018
1096
  }
1019
1097
  /** Write a property to the device. */
1020
1098
  async setProperty(write) {
@@ -2408,7 +2486,7 @@ var TASK = enumLookup([
2408
2486
  12 /* TransientPauseEdge */,
2409
2487
  14 /* NeedsIntervention */
2410
2488
  ]);
2411
- var VacuumDevice = class extends BaseDevice {
2489
+ var VacuumDevice = class _VacuumDevice extends BaseDevice {
2412
2490
  #caps;
2413
2491
  #lastMap = null;
2414
2492
  constructor(input) {
@@ -2623,6 +2701,16 @@ var VacuumDevice = class extends BaseDevice {
2623
2701
  const points = [[Math.round(point.x), Math.round(point.y), repeats, fan, water]];
2624
2702
  return this.#startCustom(CUSTOM_CLEAN_MODE.SPOT, { points });
2625
2703
  }
2704
+ /**
2705
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
2706
+ * robot — reads {@link VacuumDevice.DEFAULT_PROPS} from the cloud-cached
2707
+ * endpoint. After it resolves, every typed getter (status/battery/suction/
2708
+ * water/cleaningMode/error/charging…) reflects the cached values, so a
2709
+ * standby/docked vacuum reports its state exactly as the Dreamehome app does.
2710
+ */
2711
+ async refreshFromCache() {
2712
+ await this.refreshCachedProperties([..._VacuumDevice.DEFAULT_PROPS]);
2713
+ }
2626
2714
  // -- maps ---------------------------------------------------------------
2627
2715
  /** The most-recently-decoded map, or `null` until {@link getMap} succeeds. */
2628
2716
  get lastMap() {
@@ -3455,7 +3543,7 @@ var STATUS = enumLookup(
3455
3543
  var CHARGING2 = enumLookup(
3456
3544
  Object.values(MowerChargingStatus).filter((v) => typeof v === "number")
3457
3545
  );
3458
- var MowerDevice = class extends BaseDevice {
3546
+ var MowerDevice = class _MowerDevice extends BaseDevice {
3459
3547
  #caps;
3460
3548
  #fetchBatch;
3461
3549
  #lastMap = null;
@@ -3595,6 +3683,16 @@ var MowerDevice = class extends BaseDevice {
3595
3683
  }
3596
3684
  return this.#sendTask(buildSpotPayload(spotAreaIds.map((s) => Math.trunc(s))));
3597
3685
  }
3686
+ /**
3687
+ * Seed the cache from the CLOUD SHADOW (last-known values) WITHOUT waking the
3688
+ * mower — reads {@link MowerDevice.DEFAULT_PROPS} from the cloud-cached
3689
+ * endpoint. After it resolves, every typed getter (status/battery/charging/
3690
+ * coverage/task/controlAction…) reflects the cached values, so a docked/
3691
+ * standby mower reports its state exactly as the Dreamehome app does.
3692
+ */
3693
+ async refreshFromCache() {
3694
+ await this.refreshCachedProperties([..._MowerDevice.DEFAULT_PROPS]);
3695
+ }
3598
3696
  // -- maps ---------------------------------------------------------------
3599
3697
  /** The most-recently-parsed map, or `null` until {@link getMap} succeeds. */
3600
3698
  get lastMap() {