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

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)));
@@ -29021,7 +29085,11 @@ var EufyMega = class extends EventEmitter14 {
29021
29085
  this.emit("p2p", f);
29022
29086
  const caps = this.registry.capabilitiesForFrame(stationSn, f.channel);
29023
29087
  const deviceSn = this.registry.serialForFrame(stationSn, f.channel);
29024
- const signal = { source: "p2p-frame", ...f };
29088
+ const signal = {
29089
+ source: "p2p-frame",
29090
+ ...f,
29091
+ deviceType: deviceSn ? this.registry.require(deviceSn).raw?.device_type : void 0
29092
+ };
29025
29093
  if (f.params)
29026
29094
  this.applyRealtimeState(deviceSn, f.params);
29027
29095
  this.applyRealtimeReport(deviceSn, decodeState(signal, caps));
@@ -30289,10 +30357,13 @@ var EufyMega = class extends EventEmitter14 {
30289
30357
  const rawChannel = raw.device_channel;
30290
30358
  const channel = typeof rawChannel === "number" ? rawChannel : 0;
30291
30359
  const member = raw.member ?? {};
30360
+ const parent = this.registry.list().find((d) => d.sn === rec.parentSn);
30361
+ const parentRaw = parent?.raw;
30292
30362
  const resolved = resolveDevice(rec);
30293
30363
  const dpCatalog = (resolved.codec === "vacuum" || resolved.codec === "mower") && rec.model ? await this.fetchDpCatalog(rec.model) : void 0;
30294
30364
  return {
30295
30365
  channel,
30366
+ timezone: recordString(parentRaw ?? {}, "time_zone"),
30296
30367
  codec: resolved.codec,
30297
30368
  deviceType: rec.deviceType,
30298
30369
  model: rec.model,