@mega-yfue/eufy-sdk 0.6.0-beta.24 → 0.6.0-beta.26

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.
@@ -344,6 +344,8 @@ export type Command = {
344
344
  cmd: number;
345
345
  value: number;
346
346
  channel: number;
347
+ /** `"auto"`: the seal follows the session, level-2 with a key and level-1 without. Absent, level-2 only. */
348
+ form?: Extract<ScalarForm, "auto">;
347
349
  }
348
350
  /** P2P int-plus-string frame; the transport injects the authenticated account id string. */
349
351
  | {
package/dist/index.js CHANGED
@@ -3304,8 +3304,8 @@ function setJsonRaw(cmd, data, ctx, channel) {
3304
3304
  function setPayload(cmd, payload, ctx, mValue3, channel, form) {
3305
3305
  return { kind: "set-payload", cmd, payload, channel: channel ?? ctx.channel, mValue3, form };
3306
3306
  }
3307
- function setStationScalar(cmd, value, channel) {
3308
- return { kind: "p2p-station-scalar", cmd, value, channel };
3307
+ function setStationScalar(cmd, value, channel, form) {
3308
+ return { kind: "p2p-station-scalar", cmd, value, channel, ...form ? { form } : {} };
3309
3309
  }
3310
3310
  function describeDevice(ctx) {
3311
3311
  return `deviceType=${ctx.deviceType ?? "?"} model=${ctx.model ?? "?"} serial=${ctx.serial ?? "?"} channel=${ctx.channel} codec=${ctx.codec}`;
@@ -3474,12 +3474,13 @@ function describe(description, fn, m) {
3474
3474
  function reports(requires, ctx) {
3475
3475
  return !requires || requires.some((p) => ctx.paramIds.has(p));
3476
3476
  }
3477
- function narrow(type, read, prop) {
3477
+ function narrow(type, read, prop, values) {
3478
3478
  if (type === "bool")
3479
3479
  return readBool(read, prop);
3480
3480
  if (type === "string")
3481
3481
  return readStr(read, prop);
3482
- return readNum(read, prop);
3482
+ const n = readNum(read, prop);
3483
+ return values && n !== void 0 && !(n in values) ? void 0 : n;
3483
3484
  }
3484
3485
  function reads(m, ctx) {
3485
3486
  return m.realtime === true || sent(m, ctx);
@@ -3532,7 +3533,9 @@ function bindMembers(members, deps) {
3532
3533
  if (reported && !m.writeOnly && !m.unexposed) {
3533
3534
  const decode = m.decode;
3534
3535
  const raw = () => read(prop)?.value ?? (borrowed === void 0 ? void 0 : read(borrowed)?.value);
3535
- const get = decode ? () => decode(raw(), rawDp2, ctx) : () => narrow(m.type, read, prop);
3536
+ const { values, dynamic } = resolvedEnum(m, ctx);
3537
+ const domain = (dynamic ? "enum" : m.kind) === "enum" ? values : void 0;
3538
+ const get = decode ? () => decode(raw(), rawDp2, ctx) : () => narrow(m.type, read, prop, domain);
3536
3539
  Object.defineProperty(out, name, { get, enumerable: true, configurable: true });
3537
3540
  }
3538
3541
  if (!m.write || m.unverified || !installs(m, ctx))
@@ -4802,7 +4805,7 @@ var BATTERY_MEMBERS = cellGated({
4802
4805
  /**
4803
4806
  * 1293 is the READABLE indicator (6445 is write-only/cloud-hidden). Verified live on a T8124R via an
4804
4807
  * app-toggle before/after diff: 0 = Battery, 1 = External Solar Panel. Values beyond 0/1 (some models
4805
- * report 5) are shown raw — `enumValues` only labels the two that are confirmed.
4808
+ * report 5) read as `undefined` — `enumValues` holds only the two that are confirmed.
4806
4809
  *
4807
4810
  * The write is a `1350` SET_PAYLOAD, inner cmd 1293, `{charge_mode:N}`, `mValue3` 0 (the app uses 0,
4808
4811
  * not the cmd id), and the setter accepts a NAME as well as the index the getter answers.
@@ -4817,7 +4820,7 @@ var BATTERY_MEMBERS = cellGated({
4817
4820
  requires: [BATTERY_PARAM.POWER_CHARGE],
4818
4821
  args: [{ name: "source", kind: "enum", description: "The raw value, or the name `battery`/`external`." }],
4819
4822
  ...accepts(),
4820
- description: "Configured power source (1293): 0 = Battery, 1 = External Solar Panel. Verified live on T8124R. The richer raw APP_CMD_SET_POWER_SOURCE blob is surfaced separately as `powerSourceInfo` (6445). Format varies by model (plain int or JSON {power_source:N}); values beyond 0/1 shown raw.",
4823
+ description: "Configured power source (1293): 0 = Battery, 1 = External Solar Panel. Verified live on T8124R. The richer raw APP_CMD_SET_POWER_SOURCE blob is surfaced separately as `powerSourceInfo` (6445). Format varies by model (plain int or JSON {power_source:N}); a value beyond 0/1 reads undefined.",
4821
4824
  write: (v, ctx) => setPayload(BATTERY_PARAM.POWER_CHARGE, { charge_mode: v === "external" ? 1 : v === "battery" ? 0 : asBool(v) ? 1 : 0 }, ctx, 0, void 0, "auto")
4822
4825
  },
4823
4826
  /**
@@ -6199,6 +6202,10 @@ var CAMERA_MEMBERS = {
6199
6202
  * sensor omits it, and the wire accepts the value regardless — the device's own reported set is the
6200
6203
  * only statement of which of the three it has. `coerceEnumValue` rejects a value outside
6201
6204
  * {@link NightVision} rather than coercing it. The payload key on the wire is `night_sion`.
6205
+ *
6206
+ * 1277 carries more than these three on some models: the V6 app's per-model mappings put modes on
6207
+ * 3, 5, 6 and 8 as well, and which model takes which mapping is not in the app bundle. A reported value
6208
+ * outside the published options reads as `undefined` rather than as an unnamed number.
6202
6209
  */
6203
6210
  nightVision: {
6204
6211
  param: CAMERA_CMD.NIGHT_VISION_TYPE,
@@ -7970,7 +7977,8 @@ var ARMING_CMD = {
7970
7977
  * was captured on holds a level-2 key and seals it level-2, while an own-session camera that never
7971
7978
  * negotiates one carries the same envelope level-1. See `armingCommand`.
7972
7979
  *
7973
- * A T8410C takes the mode as a direct frame instead of this envelope; see {@link DIRECT_ARMING_MODELS}.
7980
+ * A T8410C and a T8410 take the mode as a direct frame instead of this envelope; see
7981
+ * {@link DIRECT_ARMING_MODELS} and {@link BROADCAST_ARMING_MODELS}.
7974
7982
  */
7975
7983
  SET_ARMING: 1224,
7976
7984
  /**
@@ -8010,7 +8018,7 @@ var ARMING_CMD = {
8010
8018
  var DEVICE_NOTIFY_FLAG = 8;
8011
8019
  var STANDALONE_ACTION_MODELS = /* @__PURE__ */ new Set(["T8170", "T8171"]);
8012
8020
  var DIRECT_ARMING_MODELS = /* @__PURE__ */ new Set(["T8410C"]);
8013
- var DIRECT_ARMING_MODES = /* @__PURE__ */ new Set(["away", "home", "disarmed"]);
8021
+ var BROADCAST_ARMING_MODELS = /* @__PURE__ */ new Set(["T8410"]);
8014
8022
  var STANDALONE_READ_MODELS = /* @__PURE__ */ new Set([...STANDALONE_ACTION_MODELS, "T8400", "T8442", "T8410"]);
8015
8023
  function ownAction(read, mode) {
8016
8024
  const value = Number(read(`${mode}Action`)?.value);
@@ -8043,10 +8051,11 @@ function armingCommand(mode, ctx) {
8043
8051
  if (!ctx.accountName) {
8044
8052
  throw new Error(`arming: missing account identity (user_name) [${describeDevice(ctx)}]`);
8045
8053
  }
8046
- if (DIRECT_ARMING_MODELS.has(ctx.model ?? "")) {
8047
- if (!DIRECT_ARMING_MODES.has(mode)) {
8048
- throw new Error(`arming: mode ${mode} is unconfirmed on the direct 1224 frame [${describeDevice(ctx)}]`);
8049
- }
8054
+ const model = ctx.model ?? "";
8055
+ if (BROADCAST_ARMING_MODELS.has(model)) {
8056
+ return setStationScalar(ARMING_CMD.SET_ARMING, ARMING_MODE_WIRE[mode], STATION_CHANNEL2, "auto");
8057
+ }
8058
+ if (DIRECT_ARMING_MODELS.has(model)) {
8050
8059
  return setStationScalar(ARMING_CMD.SET_ARMING, ARMING_MODE_WIRE[mode], ctx.channel);
8051
8060
  }
8052
8061
  return setPayload(ARMING_CMD.SET_ARMING, { mode_type: ARMING_MODE_WIRE[mode], user_name: ctx.accountName }, ctx, 0, void 0, "auto");
@@ -8104,7 +8113,7 @@ var ARMING_MEMBERS = {
8104
8113
  kind: "enum",
8105
8114
  enumValues: ARMING_MODE_LABELS,
8106
8115
  provenance: "verified",
8107
- description: "Guard mode (verified: param 1224 = GUARD_MODE, read/write mechanism confirmed). Reads and SETS all 9 modes the app defines \u2014 away/home/schedule/custom1/custom2/custom3/off/geo/disarmed. Three are byte-captured writes and six are live-confirmed (each sent and observed to report its own MODE_SWITCH); see ARMING_MODE_WIRE in arming.ts for the per-value evidence. On a T8410C only away, home and disarmed are writable.",
8116
+ description: "Guard mode (verified: param 1224 = GUARD_MODE, read/write mechanism confirmed). Reads and SETS all 9 modes the app defines \u2014 away/home/schedule/custom1/custom2/custom3/off/geo/disarmed. Three are byte-captured writes and six are live-confirmed (each sent and observed to report its own MODE_SWITCH); see ARMING_MODE_WIRE in arming.ts for the per-value evidence.",
8108
8117
  observation: {
8109
8118
  event: "armingModeChanged",
8110
8119
  reflects: (value) => ({ param: ARMING_CMD.SET_ARMING, expected: ARMING_MODE_WIRE[armingModeOf(value)] }),
@@ -14185,7 +14194,7 @@ var Device = class _Device {
14185
14194
  const spec = this.specByName.get(name);
14186
14195
  if (!spec)
14187
14196
  continue;
14188
- const value = spec.raw ? void 0 : narrow(spec.type, (n) => this.state.get(n), name);
14197
+ const value = spec.raw ? void 0 : narrow(spec.type, (n) => this.state.get(n), name, spec.kind === "enum" ? spec.enumValues : void 0);
14189
14198
  out.push(value === void 0 ? { property: name } : { property: name, value });
14190
14199
  }
14191
14200
  return out;
@@ -22795,7 +22804,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
22795
22804
  await this.sendSetPayloadEnvelope(sn, cmd.cmd, cmd.payload, cmd.channel, cmd.mValue3, void 0, cmd.form, cmd.level2Sender);
22796
22805
  return;
22797
22806
  case "p2p-station-scalar":
22798
- await this.sendStationScalar(sn, cmd.cmd, cmd.value, cmd.channel);
22807
+ await this.sendStationScalar(sn, cmd.cmd, cmd.value, cmd.channel, cmd.form);
22799
22808
  return;
22800
22809
  case "p2p-int-string":
22801
22810
  await this.sendIntString(sn, cmd.cmd, cmd.value, cmd.valueSub, cmd.channel);
@@ -22837,10 +22846,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
22837
22846
  * control here is.
22838
22847
  */
22839
22848
  async rebootStation(sn) {
22840
- await this.sendBySessionLevel(sn, {
22841
- l2: (resolved) => this.replayLevel2Send(sn, `reboot ${sn}`, ({ session, accountId }) => session.sendRawLevel2Bytes(buildDirectBinaryBody(0, accountId), 255, P2P_ENVELOPE.RESTART_HUB, 8), resolved),
22842
- l1: (resolved) => this.replayLevel2Send(sn, `reboot ${sn}`, ({ session, accountId }) => session.sendRawLevel1Bytes(buildDirectBinaryBody(0, accountId), 255, P2P_ENVELOPE.RESTART_HUB), resolved)
22843
- });
22849
+ return this.sendStationScalar(sn, P2P_ENVELOPE.RESTART_HUB, 0, 255, "auto");
22844
22850
  }
22845
22851
  /**
22846
22852
  * A {@link MediaProvider} bound to one serial — resolves the session then calls `p2p/media`.
@@ -23736,9 +23742,19 @@ var P2PCommandRouter = class _P2PCommandRouter {
23736
23742
  * **Station-scoped scalar** (`p2p-station-scalar` intent): the 132-byte channel-less body
23737
23743
  * ({@link buildDirectBinaryBody} with no `channel`) on an EXPLICIT channel, signCode 8. The
23738
23744
  * HomeBase's own controls ride the station broadcast channel 255 (alarm/speaker volume 1235).
23745
+ *
23746
+ * `form` `"auto"` seals it at the session's level, as {@link sendBySessionLevel} defines it: level-2 on a
23747
+ * keyed session, the same body sealed level-1 on a keyless one. Absent, the send waits for the level-2 key.
23739
23748
  */
23740
- async sendStationScalar(sn, outerCmd, value, channel) {
23741
- await this.replayLevel2Send(sn, `station scalar cmd ${outerCmd}`, ({ session, accountId }) => session.sendRawLevel2Bytes(buildDirectBinaryBody(value, accountId), channel, outerCmd, 8));
23749
+ async sendStationScalar(sn, outerCmd, value, channel, form) {
23750
+ const describe2 = `station scalar cmd ${outerCmd}`;
23751
+ const l2 = (resolved) => this.replayLevel2Send(sn, describe2, ({ session, accountId }) => session.sendRawLevel2Bytes(buildDirectBinaryBody(value, accountId), channel, outerCmd, 8), resolved);
23752
+ if (form !== "auto")
23753
+ return l2();
23754
+ await this.sendBySessionLevel(sn, {
23755
+ l2,
23756
+ l1: (resolved) => this.replayLevel2Send(sn, describe2, ({ session, accountId }) => session.sendRawLevel1Bytes(buildDirectBinaryBody(value, accountId), channel, outerCmd), resolved)
23757
+ });
23742
23758
  }
23743
23759
  /** Send a level-1 int-plus-string frame with authenticated account identity injected by the transport. */
23744
23760
  async sendIntString(sn, commandType, value, valueSub, channel) {