@mega-yfue/eufy-sdk 0.5.0-beta.52 → 0.5.0-beta.54

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.
@@ -792,9 +792,9 @@ export declare class EufyMega extends EventEmitter {
792
792
  * Subscribe the devices on one credential scope; a failing subscribe is reported, not fatal (one
793
793
  * unreachable device must not stop the rest of the roster from coming up).
794
794
  *
795
- * A Tuya clean-line device's DPs are also read from the Tuya cloud, whether or not its subscribe
796
- * succeeded: the cloud read is a separate plane, and the broker can refuse a subscribe for a device
797
- * whose DPs the Tuya cloud still serves.
795
+ * A Tuya clean-line device (`eufy_home_tuya`) is not subscribed: its state lives on the Tuya cloud,
796
+ * and the broker refuses its `eufy_home` topics on every credential. Its DPs are read from the Tuya
797
+ * cloud instead.
798
798
  */
799
799
  private subscribeMqttDevices;
800
800
  /**
@@ -458,7 +458,7 @@ export interface CommandSink {
458
458
  dispatch(cmd: Command): Promise<void>;
459
459
  }
460
460
  /**
461
- * Inbound Tuya DP event contract — the transport parses a ThingClips MQTT payload
461
+ * Inbound Tuya DP event contract — the transport parses the Tuya cloud DP read
462
462
  * (string-keyed object → numeric-keyed record) and delivers it through this interface. The
463
463
  * transport owns the parse; a capability owns the semantics (which DP id means what). Mirrors
464
464
  * {@link DpInboundFrame} for the AIoT MQTT path; the capability layer never names the Tuya framing.
package/dist/index.js CHANGED
@@ -7769,6 +7769,12 @@ var ARMING_CMD = {
7769
7769
  SET_ALL_ACTION: 1255
7770
7770
  };
7771
7771
  var DEVICE_NOTIFY_FLAG = 8;
7772
+ var STANDALONE_ACTION_MODELS = /* @__PURE__ */ new Set(["T8170", "T8171"]);
7773
+ var STANDALONE_READ_MODELS = /* @__PURE__ */ new Set([...STANDALONE_ACTION_MODELS, "T8400", "T8442", "T8410"]);
7774
+ function ownAction(read, mode) {
7775
+ const value = Number(read(`${mode}Action`)?.value);
7776
+ return Number.isInteger(value) && value >= 0 ? value : void 0;
7777
+ }
7772
7778
  function actionTable(read, mode) {
7773
7779
  const value = read(`${mode}ActionTable`)?.value;
7774
7780
  return value && typeof value === "object" && Array.isArray(value.devices) ? value : void 0;
@@ -7811,6 +7817,13 @@ function alarmDelayCommand(mode, config, ctx) {
7811
7817
  };
7812
7818
  return setJsonRaw(ARMING_CMD.SET_ALL_ACTION, data, ctx, STATION_CHANNEL2);
7813
7819
  }
7820
+ function ownTable(read, mode, channel) {
7821
+ const flags = ownAction(read, mode);
7822
+ if (flags === void 0)
7823
+ return void 0;
7824
+ const wire = ARMING_MODE_WIRE[mode];
7825
+ return { mode_id: wire, devices: [{ device_channel: channel, action: wire << 24 | flags }] };
7826
+ }
7814
7827
  var ACTION_TABLE_LAG_MS = 15e3;
7815
7828
  function withDeviceNotification(table, channel, on) {
7816
7829
  if (!table?.devices.some((d) => d.device_channel === channel))
@@ -7879,25 +7892,29 @@ var ARMING_MEMBERS = {
7879
7892
  /**
7880
7893
  * Whether the device on `channel` sends a push notification in `mode`: its {@link DEVICE_NOTIFY_FLAG}
7881
7894
  * in the action table the station reports for that mode. `undefined` where the station reports no table
7882
- * or the table does not list `channel`. A device's channel is `EufyDevice.channel`.
7895
+ * or the table does not list `channel`. A standalone camera answers from its own 1225/1239/1148 flags,
7896
+ * for its own channel only. A device's channel is `EufyDevice.channel`.
7883
7897
  */
7884
- deviceNotification: method(({ read }) => (mode, channel) => {
7885
- const action = actionTable(read, mode)?.devices.find((d) => d.device_channel === channel)?.action;
7898
+ deviceNotification: method(({ ctx, read }) => (mode, channel) => {
7899
+ const action = ctx.model === "T8010" ? actionTable(read, mode)?.devices.find((d) => d.device_channel === channel)?.action : channel === ctx.channel ? ownAction(read, mode) : void 0;
7886
7900
  return typeof action === "number" ? (action & DEVICE_NOTIFY_FLAG) !== 0 : void 0;
7887
- }, "Whether the device on a channel sends a push notification in a guard mode (home/away).", (ctx) => ctx.model === "T8010"),
7901
+ }, "Whether the device on a channel sends a push notification in a guard mode (home/away/custom1).", (ctx) => ctx.model === "T8010" || STANDALONE_READ_MODELS.has(ctx.model ?? "")),
7888
7902
  /**
7889
7903
  * Turn the push notification of the device on `channel` on or off in `mode` (cmd
7890
7904
  * {@link ARMING_CMD.SET_ALL_ACTION}): the station's reported table written back with that one flag
7891
- * changed. Rejects, sending nothing, where the table does not list `channel`. A write builds on the last
7892
- * one sent for that mode until the station reports a table observed 15 s after it, so a second write does
7893
- * not revert it; a write whose dispatch rejects is not built on.
7905
+ * changed. A standalone camera is sent a one-entry `{mode_id, devices}` table built from its own flags,
7906
+ * for its own channel only. Rejects, sending nothing, where the table does not list `channel`. A write
7907
+ * builds on the last one sent for that mode until the device reports a table (`${mode}Action` on a
7908
+ * standalone camera) observed 15 s after it, so a second write does not revert it; a write whose dispatch
7909
+ * rejects is not built on.
7894
7910
  */
7895
7911
  setDeviceNotification: method(({ ctx, sink, read }) => {
7896
7912
  const sent2 = /* @__PURE__ */ new Map();
7897
7913
  return (mode, channel, on) => {
7898
7914
  const last = sent2.get(mode);
7899
- const observed = read(`${mode}ActionTable`)?.ts ?? 0;
7900
- const base = last && observed < last.at + ACTION_TABLE_LAG_MS ? last.table : actionTable(read, mode);
7915
+ const reported = ctx.model === "T8010" ? `${mode}ActionTable` : `${mode}Action`;
7916
+ const observed = read(reported)?.ts ?? 0;
7917
+ const base = last && observed < last.at + ACTION_TABLE_LAG_MS ? last.table : ctx.model === "T8010" ? actionTable(read, mode) : ownTable(read, mode, ctx.channel);
7901
7918
  const table = withDeviceNotification(base, channel, on);
7902
7919
  if (!table) {
7903
7920
  return Promise.reject(new Error(`arming: no ${mode} action table lists channel ${channel} [${describeDevice(ctx)}]`));
@@ -7913,7 +7930,7 @@ var ARMING_MEMBERS = {
7913
7930
  throw err;
7914
7931
  });
7915
7932
  };
7916
- }, "Turn the push notification of the device on a channel on or off in a guard mode (home/away).", (ctx) => ctx.model === "T8010")
7933
+ }, "Turn the push notification of the device on a channel on or off in a guard mode (home/away/custom1).", (ctx) => ctx.model === "T8010" || STANDALONE_ACTION_MODELS.has(ctx.model ?? ""))
7917
7934
  };
7918
7935
  function decodeAlarmPhase(signal) {
7919
7936
  const type = signal.source === "push" ? signal.payload?.["type"] : void 0;
@@ -11544,7 +11561,7 @@ var SECURITY_PARAMS = {
11544
11561
  1148: {
11545
11562
  name: "custom1Action",
11546
11563
  type: "number",
11547
- provenance: "apk"
11564
+ provenance: "verified"
11548
11565
  },
11549
11566
  1149: {
11550
11567
  name: "custom2Action",
@@ -11759,7 +11776,7 @@ var SECURITY_PARAMS = {
11759
11776
  1225: {
11760
11777
  name: "homeAction",
11761
11778
  type: "number",
11762
- provenance: "apk"
11779
+ provenance: "verified"
11763
11780
  },
11764
11781
  1229: {
11765
11782
  name: "param1229",
@@ -25726,33 +25743,6 @@ function parseTuyaDpEvent(payload) {
25726
25743
  }
25727
25744
  return Object.keys(result).length > 0 ? result : null;
25728
25745
  }
25729
- function parseTuyaDpReport(raw) {
25730
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
25731
- return void 0;
25732
- const envelope = raw;
25733
- const payloadObj = jsonObject(envelope.payload);
25734
- if (payloadObj) {
25735
- const data2 = payloadObj.data;
25736
- if (data2 && typeof data2 === "object" && !Array.isArray(data2)) {
25737
- const dataObj = data2;
25738
- const dps = dataObj.dps;
25739
- if (dps && typeof dps === "object" && !Array.isArray(dps))
25740
- return dps;
25741
- const filtered = Object.fromEntries(Object.entries(dataObj).filter(([k]) => /^[1-9]\d*$/.test(k)));
25742
- return Object.keys(filtered).length > 0 ? filtered : void 0;
25743
- }
25744
- }
25745
- const topDps = envelope.dps;
25746
- if (topDps && typeof topDps === "object" && !Array.isArray(topDps))
25747
- return topDps;
25748
- const data = envelope.data;
25749
- if (data && typeof data === "object" && !Array.isArray(data)) {
25750
- const dataDps = data.dps;
25751
- if (dataDps && typeof dataDps === "object" && !Array.isArray(dataDps))
25752
- return dataDps;
25753
- }
25754
- return void 0;
25755
- }
25756
25746
  var TuyaDpRouter = class {
25757
25747
  listener;
25758
25748
  /** Register the inbound listener. Replaces any previously registered one. */
@@ -29312,16 +29302,6 @@ var EufyMega = class extends EventEmitter13 {
29312
29302
  transport.on("disconnect", (r) => this.emit("disconnect", r));
29313
29303
  transport.on("message", (m) => {
29314
29304
  this.emit("message", m);
29315
- if (m.deviceSn) {
29316
- const dev = this.registry.list().find((d) => d.sn === m.deviceSn);
29317
- if (dev?.category === "eufy_home_tuya") {
29318
- const dps = parseTuyaDpReport(m.raw);
29319
- if (dps) {
29320
- this.tuyaDpRouter.deliver(m.deviceSn, dps);
29321
- return;
29322
- }
29323
- }
29324
- }
29325
29305
  if (m.deviceSn) {
29326
29306
  const frame = parseBizMapFrame(m.raw);
29327
29307
  if (frame) {
@@ -29355,23 +29335,24 @@ var EufyMega = class extends EventEmitter13 {
29355
29335
  * Subscribe the devices on one credential scope; a failing subscribe is reported, not fatal (one
29356
29336
  * unreachable device must not stop the rest of the roster from coming up).
29357
29337
  *
29358
- * A Tuya clean-line device's DPs are also read from the Tuya cloud, whether or not its subscribe
29359
- * succeeded: the cloud read is a separate plane, and the broker can refuse a subscribe for a device
29360
- * whose DPs the Tuya cloud still serves.
29338
+ * A Tuya clean-line device (`eufy_home_tuya`) is not subscribed: its state lives on the Tuya cloud,
29339
+ * and the broker refuses its `eufy_home` topics on every credential. Its DPs are read from the Tuya
29340
+ * cloud instead.
29361
29341
  */
29362
29342
  async subscribeMqttDevices(scope, transport) {
29363
29343
  for (const d of this.getMqttDevices().filter((dev) => mqttScopeFor(dev) === scope)) {
29364
- try {
29365
- await transport.subscribeDevice(d);
29366
- await this.sendRealtimeInit(d.sn);
29367
- } catch (e) {
29368
- this.reportError(e);
29369
- }
29370
29344
  if (d.category === "eufy_home_tuya") {
29371
29345
  this.tuya.fetchDps(d.sn).then((dps) => {
29372
29346
  if (dps)
29373
29347
  this.tuyaDpRouter.deliver(d.sn, dps);
29374
29348
  }).catch((e) => this.reportError(e));
29349
+ continue;
29350
+ }
29351
+ try {
29352
+ await transport.subscribeDevice(d);
29353
+ await this.sendRealtimeInit(d.sn);
29354
+ } catch (e) {
29355
+ this.reportError(e);
29375
29356
  }
29376
29357
  }
29377
29358
  }