@mega-yfue/eufy-sdk 0.4.0-beta.31 → 0.4.0-beta.33

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.js CHANGED
@@ -3273,7 +3273,7 @@ function propertiesOf(members, ctx) {
3273
3273
  unit: m.unit,
3274
3274
  enumValues: enumValues2,
3275
3275
  provenance: m.provenance,
3276
- invert: promoted?.invert ?? m.invert,
3276
+ invert: promoted?.invert ?? (ctx ? m.invertFor?.(ctx) : void 0) ?? m.invert,
3277
3277
  decode: m.coerce,
3278
3278
  // A member with a getter-side `decode` reads a field out of a structured payload, so the
3279
3279
  // stored value is that payload — see `PropertySpec.raw`.
@@ -5205,8 +5205,10 @@ var AUDIO = {
5205
5205
 
5206
5206
  // dist/model/capabilities/camera.js
5207
5207
  var CAMERA_CMD = {
5208
- /** Camera on/off. switch is inverted: camera ON ⇒ 0, OFF ⇒ 1. */
5208
+ /** Camera on/off. Read and write polarity depend on the family and topology. */
5209
5209
  CAMERA_ENABLE: 1035,
5210
+ /** Wrapped T8410 power switch on HomeBase 3. */
5211
+ PRIVACY_MODE: 6250,
5210
5212
  /**
5211
5213
  * Camera status LED on/off — the "power/recording" indicator. The
5212
5214
  * app JS has a sibling param `1056` (`APP_CMD_LIVEVIEW_LED_SWITCH`) for the SAME UI "Status Light"
@@ -5426,7 +5428,27 @@ var ENABLE_BIT_FLOODLIGHT_TYPES = /* @__PURE__ */ new Set([
5426
5428
  function powerValue(on, ctx) {
5427
5429
  return isEnableBitPolarity(ctx) ? on ? 1 : 0 : on ? 0 : 1;
5428
5430
  }
5431
+ function isT8410HomeBase3(ctx) {
5432
+ return ctx.model === "T8410" && ctx.stationSerial?.startsWith("T8030") === true;
5433
+ }
5434
+ function usesHomeBasePowerPayload(ctx) {
5435
+ if (!isT8410HomeBase3(ctx) || !ctx.firmwareVersion || !/^\d+(?:\.\d+){2,3}$/.test(ctx.firmwareVersion))
5436
+ return false;
5437
+ const version = ctx.firmwareVersion.split(".").map(Number);
5438
+ const minimum = [2, 3, 1, 0];
5439
+ for (let i = 0; i < minimum.length; i++) {
5440
+ const difference = (version[i] ?? 0) - minimum[i];
5441
+ if (difference !== 0)
5442
+ return difference > 0;
5443
+ }
5444
+ return true;
5445
+ }
5446
+ function hasUnreflectedHomeBasePower(ctx) {
5447
+ return usesHomeBasePowerPayload(ctx) && !ctx.paramIds.has(2001);
5448
+ }
5429
5449
  function powerCommand(on, ctx) {
5450
+ if (usesHomeBasePowerPayload(ctx))
5451
+ return setPayload(CAMERA_CMD.PRIVACY_MODE, { switch: on ? 0 : 1 }, ctx, 0);
5430
5452
  return setScalar(CAMERA_CMD.CAMERA_ENABLE, powerValue(on, ctx), ctx, "auto");
5431
5453
  }
5432
5454
  function privacyCommand(enabled, channel) {
@@ -5444,28 +5466,34 @@ function enablementReflection(on, ctx) {
5444
5466
  const alias = CAMERA_MEMBERS.enabled.readAliases[0].paramType;
5445
5467
  if (ctx.paramIds.has(alias))
5446
5468
  return { param: alias, expected: on, observed: on };
5469
+ if (hasUnreflectedHomeBasePower(ctx))
5470
+ return void 0;
5447
5471
  if (ctx.paramIds.has(CAMERA_CMD.CAMERA_ENABLE)) {
5448
- return { param: CAMERA_CMD.CAMERA_ENABLE, expected: powerValue(on, ctx), observed: on };
5472
+ return {
5473
+ param: CAMERA_CMD.CAMERA_ENABLE,
5474
+ expected: powerValue(on, ctx),
5475
+ observed: on
5476
+ };
5449
5477
  }
5450
5478
  return void 0;
5451
5479
  }
5452
5480
  function refuseWhenDisabled(ctx, read) {
5453
- if (read("enabled")?.value === false)
5481
+ if (!hasUnreflectedHomeBasePower(ctx) && read("enabled")?.value === false)
5454
5482
  throw new CameraDisabledError(ctx.name ?? ctx.serial);
5455
5483
  }
5456
5484
  var CAMERA_MEMBERS = {
5457
5485
  /**
5458
- * The READ is the *disable*-bit convention (1035 "0" ⇒ ON, 2001 direct); the WRITE polarity is
5486
+ * The default READ is the *disable*-bit convention (1035 "0" ⇒ ON, 2001 direct); the WRITE polarity is
5459
5487
  * family-dependent — see `powerValue` / `isEnableBitPolarity`. Battery/solo cams report the state under
5460
5488
  * 1035, standalone indoor/outdoor cams under 2001 OPEN_DEVICE with direct polarity, so 2001 is a
5461
5489
  * read-alias. Both verified live, and the write polarity is confirmed against the app's own frames.
5462
5490
  *
5463
- * The read and the setter observe the SAME wire on every family — see `powerCommand` — which is what
5464
- * makes this value track what it is told, and what lets `enablementReflection` confirm a write.
5491
+ * T8410 on HomeBase 3 reports 1035 as an enable bit ("1" ⇒ ON). That reported state does not reliably
5492
+ * reflect the privacy command. `readReflectsWrite` states this distinction.
5465
5493
  *
5466
5494
  * The privacy param (6250) is reported by the outdoor-PT family and by no other camera measured, and both
5467
5495
  * of its polarities are observed. It is deliberately NOT aliased here: it moved in the same step as 1035, so
5468
- * the reading cannot say whether power and privacy are one state or two, and the app drives 1035 — so
5496
+ * the reading cannot say whether power and privacy are one state or two — so
5469
5497
  * aliasing a second param could only fold two possible states into one getter for no gain.
5470
5498
  */
5471
5499
  enabled: {
@@ -5474,8 +5502,10 @@ var CAMERA_MEMBERS = {
5474
5502
  kind: "boolean",
5475
5503
  provenance: "verified",
5476
5504
  invert: true,
5505
+ invertFor: (ctx) => isT8410HomeBase3(ctx) ? false : void 0,
5477
5506
  readAliases: [{ paramType: 2001, invert: false }],
5478
- description: "Camera enabled. Family-dependent wire param: 1035 CMD_DEVS_SWITCH (disable bit, battery/solo cams) or 2001 OPEN_DEVICE (standalone indoor/outdoor). Reliable on/off status source (a live-stream probe is not).",
5507
+ readReflectsWrite: (ctx) => !hasUnreflectedHomeBasePower(ctx),
5508
+ description: "Camera enabled. Family-dependent wire param: 1035 CMD_DEVS_SWITCH (enable bit on T8410/HomeBase 3, disable bit on battery/solo cams) or 2001 OPEN_DEVICE (standalone indoor/outdoor). Reported on/off state; unreflectedMembers identifies commands whose effect this report does not reliably reflect.",
5479
5509
  observation: {
5480
5510
  event: "cameraEnabledChanged",
5481
5511
  reflects: (value, ctx) => enablementReflection(asBool(value), ctx),
@@ -5702,7 +5732,7 @@ var CAMERA_MEMBERS = {
5702
5732
  * value, and it exists only on a device bound to a provider. Each is declared once, with its
5703
5733
  * signature taken FROM {@link MediaProvider} — so a change there is a compile error here, not a drift.
5704
5734
  *
5705
- * Every pull is refused where {@link CAMERA_MEMBERS.enabled} reads false, with {@link CameraDisabledError} —
5735
+ * Every pull is refused where a reliable {@link CAMERA_MEMBERS.enabled} reads false, with {@link CameraDisabledError} —
5706
5736
  * `live`, `snapshotLive`, `record`, `openReadable` and `recordFragments` alike, since each opens media on a
5707
5737
  * camera that serves none. That reading is the on/off source; a live probe is not one, since a disabled
5708
5738
  * camera answers a start with audio and never a video frame.
@@ -11160,7 +11190,8 @@ function resolveProperties(rec, codec, capabilities) {
11160
11190
  model: rec.model,
11161
11191
  category: rec.category,
11162
11192
  capabilities: new Set(capabilities),
11163
- paramIds
11193
+ paramIds,
11194
+ stationSerial: rec.parentSn
11164
11195
  });
11165
11196
  }
11166
11197
 
@@ -13350,7 +13381,8 @@ var Device = class _Device {
13350
13381
  codec: this.codec,
13351
13382
  model: this.model,
13352
13383
  capabilities: new Set(this.capabilities),
13353
- homeBaseAttached: this.stationSn !== this.sn
13384
+ homeBaseAttached: this.stationSn !== this.sn,
13385
+ stationSerial: this.stationSn
13354
13386
  })
13355
13387
  };
13356
13388
  }
@@ -16214,8 +16246,17 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16214
16246
  this.send(this.connectAddress, RequestMessageType.DATA, data);
16215
16247
  return true;
16216
16248
  }
16217
- /** Stop the realtime media stream (`CMD_STOP_REALTIME_MEDIA`, 1004) on a camera `channel`. */
16218
- stopLiveMedia(channel = STATION_CHANNEL3, accountId = "") {
16249
+ /**
16250
+ * Stop the realtime media stream (`CMD_STOP_REALTIME_MEDIA`, 1004) on a camera `channel`, in the shape
16251
+ * its runtime topology takes, as {@link startLiveMedia} selects its start:
16252
+ * - `homeBaseAttached`: `CMD_SET_PAYLOAD` (1350) wrapping `{cmd:1004, mChannel:channel}` at level-2.
16253
+ * - own-session at level-2: the direct 1004 frame whose entire plaintext is the camera channel as a
16254
+ * `uint32`, the same 4-byte body as the attached talkback frames. The app sends this frame on closing
16255
+ * an own-session camera's live view; the camera stops streaming on it within a second, and keeps
16256
+ * streaming through the 1350-wrapped form.
16257
+ * - without a level-2 key: the bare 1004 command.
16258
+ */
16259
+ stopLiveMedia(channel = STATION_CHANNEL3, accountId = "", homeBaseAttached = false) {
16219
16260
  this.liveStartedChannels.delete(channel);
16220
16261
  for (const [sequence, pending] of this.unackedLiveStarts) {
16221
16262
  if (pending.channel === channel)
@@ -16225,7 +16266,11 @@ var P2PSession = class _P2PSession extends EventEmitter2 {
16225
16266
  clearInterval(this.liveStartRetransmitTimer);
16226
16267
  this.liveStartRetransmitTimer = void 0;
16227
16268
  }
16228
- if (this.level2Key) {
16269
+ if (this.level2Key && !homeBaseAttached) {
16270
+ const body = Buffer.allocUnsafe(4);
16271
+ body.writeUInt32LE(channel >>> 0, 0);
16272
+ this.sendRawLevel2Bytes(body, channel, CMD_STOP_REALTIME_MEDIA, 8);
16273
+ } else if (this.level2Key) {
16229
16274
  this.sendMediaPayloadLevel2(CMD_STOP_REALTIME_MEDIA, channel, accountId, {});
16230
16275
  } else {
16231
16276
  this.sendCommand(CMD_STOP_REALTIME_MEDIA, channel);
@@ -17991,7 +18036,7 @@ var LiveStream = class extends EventEmitter3 {
17991
18036
  clearTimeout(this.stallTimer);
17992
18037
  this.stallTimer = void 0;
17993
18038
  try {
17994
- this.session.stopLiveMedia(this.opts.channel, this.opts.accountId);
18039
+ this.session.stopLiveMedia(this.opts.channel, this.opts.accountId, this.opts.homeBaseAttached);
17995
18040
  } catch (e) {
17996
18041
  this.logger.debug(`[live] stopLiveMedia ignored: ${e instanceof Error ? e.message : e}`);
17997
18042
  }
@@ -27297,6 +27342,7 @@ var EufyMega = class extends EventEmitter9 {
27297
27342
  model: rec.model,
27298
27343
  category: dev.category,
27299
27344
  serial: sn,
27345
+ stationSerial: rec.parentSn,
27300
27346
  // Identity metadata for `info`, for a host's device-registry surface. `name` = the app-shown
27301
27347
  // device_name (registry's `name`), not the resolved codec/inferred name. firmware/hardware come
27302
27348
  // straight off the device record: `main_sw_version` / `main_hw_version` — the same fields the v6