@mega-yfue/eufy-sdk 0.5.0-beta.70 → 0.5.0-beta.72

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.
@@ -883,6 +883,7 @@ export interface MediaProvider {
883
883
  */
884
884
  p2pQuery?(subCmd: number, opts?: {
885
885
  timeoutMs?: number;
886
+ payload?: Record<string, unknown>;
886
887
  }): Promise<Record<string, unknown>>;
887
888
  /**
888
889
  * Generic control-payload request/reply query: send a `CONTROL_PAYLOAD` (1700) `{commandType,data}`
package/dist/index.js CHANGED
@@ -3658,6 +3658,7 @@ var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
3658
3658
  var isDoorbellT8223 = (ctx) => ctx.model === "T8223";
3659
3659
  var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
3660
3660
  var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
3661
+ var isVideoSmartLock = (ctx) => ctx.deviceType === DeviceType.LOCK_8530;
3661
3662
 
3662
3663
  // dist/model/capabilities/rtsp.js
3663
3664
  var RTSP_PARAM = {
@@ -7434,9 +7435,24 @@ var LOCK_SETTING_ID = {
7434
7435
  /** One-touch rear-lock toggle. Structurally confirmed via the app's own JS (2026-07-18) — see the module doc. */
7435
7436
  ONE_TOUCH_REAR_LOCK: 11
7436
7437
  };
7438
+ var VIDEO_LOCK_CMD = { ACTUATE: 1961, STATUS: 1955, NOTIFY: 1351 };
7437
7439
  function actuate(engage, ctx, sink) {
7438
- if (!ctx.adminUserId || !ctx.shortUserId) {
7439
- return Promise.reject(new Error(`lock: missing member identity (admin_user_id/short_user_id) [${describeDevice(ctx)}]`));
7440
+ if (!ctx.adminUserId || !ctx.shortUserId || isVideoSmartLock(ctx) && !ctx.accountName) {
7441
+ return Promise.reject(new Error(`lock: missing member identity (admin_user_id/short_user_id, and user name on video locks) [${describeDevice(ctx)}]`));
7442
+ }
7443
+ if (isVideoSmartLock(ctx)) {
7444
+ return sink.dispatch({
7445
+ kind: "set-payload",
7446
+ cmd: VIDEO_LOCK_CMD.ACTUATE,
7447
+ channel: ctx.channel,
7448
+ mValue3: 0,
7449
+ payload: {
7450
+ shortUserId: ctx.shortUserId,
7451
+ slOperation: engage ? 1 : 0,
7452
+ userId: ctx.adminUserId,
7453
+ userName: ctx.accountName
7454
+ }
7455
+ });
7440
7456
  }
7441
7457
  return sink.dispatch({
7442
7458
  kind: "ff09-actuate",
@@ -7459,7 +7475,31 @@ function settingToggle(settingId, name, enabled, ctx, sink) {
7459
7475
  value: enabled
7460
7476
  });
7461
7477
  }
7462
- var overP2p = (ctx) => ctx.hasP2p === true;
7478
+ var overP2p = (ctx) => ctx.hasP2p === true && !isVideoSmartLock(ctx);
7479
+ function actuationAvailable(ctx) {
7480
+ return !isVideoSmartLock(ctx) || ctx.stationSerial?.startsWith("T8030") === true;
7481
+ }
7482
+ async function refreshStatus(query, timezone) {
7483
+ const payload = await query(VIDEO_LOCK_CMD.STATUS, {
7484
+ payload: { timezone }
7485
+ });
7486
+ if (payload.code !== 0)
7487
+ throw new Error(`lock.refreshStatus: device result ${String(payload.code)}`);
7488
+ return nativeState(payload.slState);
7489
+ }
7490
+ function nativeState(raw) {
7491
+ if (raw === "3")
7492
+ return false;
7493
+ if (raw === "4")
7494
+ return true;
7495
+ return void 0;
7496
+ }
7497
+ function nativeReport(signal) {
7498
+ if (signal.source !== "p2p-frame" || !isVideoSmartLock(signal) || signal.commandId !== VIDEO_LOCK_CMD.NOTIFY || signal.json?.cmd !== VIDEO_LOCK_CMD.STATUS)
7499
+ return void 0;
7500
+ const report = signal.json.payload;
7501
+ return report?.code === 0 ? report : void 0;
7502
+ }
7463
7503
  var LOCK_MEMBERS = {
7464
7504
  /**
7465
7505
  * The lock's own state, verified on param 6000: "4" = locked, "3" = unlocked.
@@ -7501,10 +7541,10 @@ var LOCK_MEMBERS = {
7501
7541
  provenance: "verified",
7502
7542
  description: "Lock signal strength (verified: param 1141 = RSSI)."
7503
7543
  },
7504
- /** Lock the deadbolt/door. Fire-and-forget — rejects only on missing member identity, never on a device timeout. */
7505
- lock: method(({ ctx, sink }) => () => actuate(true, ctx, sink), "Lock the deadbolt/door."),
7506
- /** Unlock the deadbolt/door. Fire-and-forget — rejects only on missing member identity. */
7507
- unlock: method(({ ctx, sink }) => () => actuate(false, ctx, sink), "Unlock the deadbolt/door."),
7544
+ /** Lock the deadbolt/door. Resolves after sending; does not confirm physical state. */
7545
+ lock: method(({ ctx, sink }) => () => actuate(true, ctx, sink), "Lock the deadbolt/door.", actuationAvailable),
7546
+ /** Unlock the deadbolt/door. Resolves after sending; does not confirm physical state. */
7547
+ unlock: method(({ ctx, sink }) => () => actuate(false, ctx, sink), "Unlock the deadbolt/door.", actuationAvailable),
7508
7548
  /**
7509
7549
  * Read-modify-write the auto-lock setting: the transport GETs the device's current settings, changes
7510
7550
  * only `enabled` (+ `delaySeconds` if given — otherwise the current delay is preserved), and writes the
@@ -7525,7 +7565,7 @@ var LOCK_MEMBERS = {
7525
7565
  enabled,
7526
7566
  delaySeconds
7527
7567
  });
7528
- }, "Read-modify-write the auto-lock setting, preserving the fields it does not change."),
7568
+ }, "Read-modify-write the auto-lock setting, preserving the fields it does not change.", (ctx) => !isVideoSmartLock(ctx)),
7529
7569
  /**
7530
7570
  * Toggle Rain Mode — a pure blind write, unlike `setAutoLock`: the compact frame carries only this one
7531
7571
  * field, so there is no GET pass and it is fire-and-forget. Confirmed on-device end-to-end in both
@@ -7623,6 +7663,15 @@ var LOCK_MEMBERS = {
7623
7663
  description: "One-touch rear locking (setting-id 11). Wire shape confirmed in the app's own JS, NOT captured.",
7624
7664
  write: (v, ctx) => settingToggleCommand(LOCK_SETTING_ID.ONE_TOUCH_REAR_LOCK, v, ctx)
7625
7665
  },
7666
+ /** Read the current device-reported state; undefined means no settled locked/unlocked state. */
7667
+ refreshStatus: {
7668
+ ...provided("media", (media, { ctx }) => {
7669
+ const query = media.p2pQuery?.bind(media);
7670
+ const timezone = ctx.timezone;
7671
+ return isVideoSmartLock(ctx) && timezone && query ? () => refreshStatus(query, timezone) : void 0;
7672
+ }, "Read the current lock state from the device."),
7673
+ answers: true
7674
+ },
7626
7675
  /**
7627
7676
  * Read the device's current auto-lock settings via a live `GET_SETTINGS` round-trip — the SAME read
7628
7677
  * `setAutoLock` does internally, exposed standalone with no write attached. Works over BOTH transports.
@@ -7632,7 +7681,7 @@ var LOCK_MEMBERS = {
7632
7681
  * though it takes no arguments — offering it as one would run a round-trip and discard the answer.
7633
7682
  */
7634
7683
  getAutoLockState: {
7635
- ...provided("ff09Settings", (f) => () => f.getAutoLockState(), "Read the current auto-lock settings via a live round-trip."),
7684
+ ...provided("ff09Settings", (f, { ctx }) => isVideoSmartLock(ctx) ? void 0 : () => f.getAutoLockState(), "Read the current auto-lock settings via a live round-trip."),
7636
7685
  answers: true
7637
7686
  }
7638
7687
  };
@@ -7659,6 +7708,17 @@ var LOCK = {
7659
7708
  description: "Smart-lock locked/unlocked state and battery.",
7660
7709
  members: LOCK_MEMBERS,
7661
7710
  properties: propertiesOf(LOCK_MEMBERS),
7711
+ decodeState(signal) {
7712
+ const report = nativeReport(signal);
7713
+ if (!report)
7714
+ return null;
7715
+ const params = {};
7716
+ if (nativeState(report.slState) !== void 0)
7717
+ params[6e3] = String(report.slState);
7718
+ if (report.slBattery !== void 0)
7719
+ params[6001] = String(report.slBattery);
7720
+ return { params };
7721
+ },
7662
7722
  // Lock state is reported via param 6000 (verified: 4=locked, 3=unlocked); the model name (lock/safe) is a
7663
7723
  // signal, and every lock-codec device has the lock capability as its baseline.
7664
7724
  detection: { evidenceParams: [6e3], modelHints: [/lock/i, /safe/i], codecs: ["lock"] },
@@ -22616,7 +22676,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
22616
22676
  }, timeoutMs);
22617
22677
  const onData = (f) => {
22618
22678
  const j = f.json;
22619
- if (j?.cmd === subCmd && j.payload && typeof j.payload === "object") {
22679
+ if (f.channel === channel && f.commandId === CommandType.CMD_NOTIFY_PAYLOAD && j?.cmd === subCmd && j.payload && typeof j.payload === "object") {
22620
22680
  cleanup();
22621
22681
  resolve(j.payload);
22622
22682
  }
@@ -22627,7 +22687,11 @@ var P2PCommandRouter = class _P2PCommandRouter {
22627
22687
  };
22628
22688
  session.on("data", onData);
22629
22689
  try {
22630
- session.sendSetPayload(subCmd, {}, { accountId, channel });
22690
+ if (opts.payload) {
22691
+ if (!session.sendControlLevel2(subCmd, channel, accountId, opts.payload, 0))
22692
+ throw new Error("p2p query send failed");
22693
+ } else
22694
+ session.sendSetPayload(subCmd, {}, { accountId, channel });
22631
22695
  } catch (e) {
22632
22696
  cleanup();
22633
22697
  reject(e instanceof Error ? e : new Error(String(e)));
@@ -24489,6 +24553,10 @@ var RtcSession = class extends EventEmitter11 {
24489
24553
  * `connect()` still waiting for the signalling auth is rejected and its timer stopped.
24490
24554
  */
24491
24555
  close() {
24556
+ this.shutdown();
24557
+ }
24558
+ /** {@link RtcSession.close}, with the failure that ended the session handed to `close` when one did. */
24559
+ shutdown(err) {
24492
24560
  if (this.closed)
24493
24561
  return;
24494
24562
  this.closed = true;
@@ -24501,7 +24569,7 @@ var RtcSession = class extends EventEmitter11 {
24501
24569
  this.signaling.close();
24502
24570
  this.peer.close();
24503
24571
  this.settleAuth(new Error("RTC session closed while waiting for signalling auth"));
24504
- this.announceClose();
24572
+ this.announceClose(err);
24505
24573
  }
24506
24574
  /**
24507
24575
  * Trickle one local candidate, or the empty end-of-candidates. The native peer can report one after the
@@ -24513,11 +24581,11 @@ var RtcSession = class extends EventEmitter11 {
24513
24581
  return;
24514
24582
  this.signaling.sendInfoCandidate(candidate);
24515
24583
  }
24516
- announceClose() {
24584
+ announceClose(err) {
24517
24585
  if (this.closeAnnounced)
24518
24586
  return;
24519
24587
  this.closeAnnounced = true;
24520
- this.emit("close");
24588
+ this.emit("close", err);
24521
24589
  }
24522
24590
  waitForAuth() {
24523
24591
  if (this.authOk)
@@ -24577,7 +24645,14 @@ var RtcSession = class extends EventEmitter11 {
24577
24645
  const status = payload.status;
24578
24646
  if (status === 100 && payload.turn) {
24579
24647
  this.turn = payload.turn;
24580
- await this.peer.init(payload.turn);
24648
+ try {
24649
+ await this.peer.init(payload.turn);
24650
+ } catch (e) {
24651
+ const err = e instanceof Error ? e : new Error(String(e));
24652
+ this.emit("error", err);
24653
+ this.shutdown(err);
24654
+ return;
24655
+ }
24581
24656
  return;
24582
24657
  }
24583
24658
  if (status === 200) {
@@ -24913,9 +24988,9 @@ var RtcCommandRouter = class {
24913
24988
  }
24914
24989
  /**
24915
24990
  * One bounded bring-up: the session is up when its command channel opens, and it fails, with the
24916
- * session closed and every listener gone, when `connect()` throws, the session closes first, or
24917
- * {@link CONNECT_TIMEOUT_MS} passes. A single promise, so an early failure cannot leave a second one
24918
- * rejecting unheard.
24991
+ * session closed and every listener gone, when `connect()` throws, the session closes first (rejecting
24992
+ * with the failure that closed it, when it carries one), or {@link CONNECT_TIMEOUT_MS} passes. A single
24993
+ * promise, so an early failure cannot leave a second one rejecting unheard.
24919
24994
  */
24920
24995
  bringUp(sn, session) {
24921
24996
  return new Promise((resolve, reject) => {
@@ -24939,7 +25014,7 @@ var RtcCommandRouter = class {
24939
25014
  }
24940
25015
  };
24941
25016
  const onConnected = () => finish();
24942
- const onClose = () => finish(new Error(`rtc: ${sn} session closed before the command channel opened`));
25017
+ const onClose = (err) => finish(err ?? new Error(`rtc: ${sn} session closed before the command channel opened`));
24943
25018
  const timer = setTimeout(() => finish(new Error(`rtc: ${sn} did not come up within ${CONNECT_TIMEOUT_MS2}ms`)), CONNECT_TIMEOUT_MS2);
24944
25019
  session.once("connected", onConnected);
24945
25020
  session.once("close", onClose);
@@ -29021,7 +29096,11 @@ var EufyMega = class extends EventEmitter14 {
29021
29096
  this.emit("p2p", f);
29022
29097
  const caps = this.registry.capabilitiesForFrame(stationSn, f.channel);
29023
29098
  const deviceSn = this.registry.serialForFrame(stationSn, f.channel);
29024
- const signal = { source: "p2p-frame", ...f };
29099
+ const signal = {
29100
+ source: "p2p-frame",
29101
+ ...f,
29102
+ deviceType: deviceSn ? this.registry.require(deviceSn).raw?.device_type : void 0
29103
+ };
29025
29104
  if (f.params)
29026
29105
  this.applyRealtimeState(deviceSn, f.params);
29027
29106
  this.applyRealtimeReport(deviceSn, decodeState(signal, caps));
@@ -30289,10 +30368,13 @@ var EufyMega = class extends EventEmitter14 {
30289
30368
  const rawChannel = raw.device_channel;
30290
30369
  const channel = typeof rawChannel === "number" ? rawChannel : 0;
30291
30370
  const member = raw.member ?? {};
30371
+ const parent = this.registry.list().find((d) => d.sn === rec.parentSn);
30372
+ const parentRaw = parent?.raw;
30292
30373
  const resolved = resolveDevice(rec);
30293
30374
  const dpCatalog = (resolved.codec === "vacuum" || resolved.codec === "mower") && rec.model ? await this.fetchDpCatalog(rec.model) : void 0;
30294
30375
  return {
30295
30376
  channel,
30377
+ timezone: recordString(parentRaw ?? {}, "time_zone"),
30296
30378
  codec: resolved.codec,
30297
30379
  deviceType: rec.deviceType,
30298
30380
  model: rec.model,