@mega-yfue/eufy-sdk 0.5.0-beta.57 → 0.5.0-beta.59

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.
@@ -185,7 +185,8 @@ export declare class EufyMega extends EventEmitter {
185
185
  *
186
186
  * Skipped entirely for a device with a pollable cloud record, for an already-reporting device, and
187
187
  * when realtime is off — so the common path adds nothing. Resolving on timeout leaves a silent device
188
- * read-less rather than failing the lookup.
188
+ * read-less rather than failing the lookup. A Tuya clean-line device waits on the Tuya cloud read the
189
+ * bring-up started, so no broker is brought up for it.
189
190
  */
190
191
  private awaitFirstRealtimeState;
191
192
  /**
@@ -245,8 +246,17 @@ export declare class EufyMega extends EventEmitter {
245
246
  * to land it under the property's name.
246
247
  */
247
248
  private rebindReads;
248
- /** The distinct credential scopes the current MQTT roster needs — no devices on a scope, no connection. */
249
+ /**
250
+ * The distinct credential scopes the current MQTT roster needs — no devices on a scope, no connection.
251
+ * A Tuya clean-line device (`eufy_home_tuya`) needs none: the broker refuses its topics, and its state
252
+ * is read from the Tuya cloud.
253
+ */
249
254
  private mqttScopesInUse;
255
+ /**
256
+ * Read every Tuya clean-line device's DPs from the Tuya cloud and deliver them as realtime state. A
257
+ * failed read is reported, not fatal, and leaves that device's state as it was.
258
+ */
259
+ private readTuyaDevices;
250
260
  /**
251
261
  * Emit a semantic capability event whose name is only known at runtime (`decodeCapabilityEvent`
252
262
  * returns a plain-string `event` + `payload`). The typed `emit` overload requires a literal event
@@ -793,8 +803,7 @@ export declare class EufyMega extends EventEmitter {
793
803
  * unreachable device must not stop the rest of the roster from coming up).
794
804
  *
795
805
  * 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.
806
+ * and the broker refuses its `eufy_home` topics on every credential.
798
807
  */
799
808
  private subscribeMqttDevices;
800
809
  /**
@@ -544,6 +544,12 @@ export interface LiveVideoConfig {
544
544
  width: number;
545
545
  height: number;
546
546
  }
547
+ /** Video frame paired with its original SDK source-arrival time. */
548
+ export interface ReceivedVideoFrame {
549
+ readonly frame: LiveVideoFrame;
550
+ /** Wall-clock milliseconds when the shared source received the frame; preserved on replay. */
551
+ readonly timestampMs: number;
552
+ }
547
553
  /**
548
554
  * Elementary-stream audio codec of a {@link LiveAudioFrame}. The station declares it per frame as a
549
555
  * byte in the `CMD_AUDIO_FRAME` header — unlike video, nothing is sniffed. These are the three values
@@ -823,12 +829,15 @@ export interface MediaProvider {
823
829
  }): Promise<Buffer>;
824
830
  /**
825
831
  * Open a video-only `node:stream` Readable over a shared source consumer — raw Annex-B bytes
826
- * (default) or `objectMode` {@link LiveVideoFrame}s. Audio is available separately through
827
- * {@link live} or muxed through {@link recordFragments}; it is never interleaved into raw video.
828
- * The caller owns the Readable's lifetime, and destroying it releases the shared pull.
832
+ * (default) or `objectMode` {@link LiveVideoFrame}s. With objectMode and arrivalTime, it yields
833
+ * {@link ReceivedVideoFrame} envelopes whose original source-arrival time survives retained replay.
834
+ * Raw mode ignores arrivalTime. Audio is available separately through {@link live} or muxed through
835
+ * {@link recordFragments}; it is never interleaved into raw video. The caller owns the Readable's
836
+ * lifetime, and destroying it releases the shared pull.
829
837
  */
830
838
  openReadable?(opts?: {
831
839
  objectMode?: boolean;
840
+ arrivalTime?: boolean;
832
841
  } & SharedSourceHints & AbortableCall): Promise<import("node:stream").Readable>;
833
842
  /**
834
843
  * Continuously record the live feed as fragmented-MP4 (CMAF). The caller-owned
package/dist/index.js CHANGED
@@ -20396,12 +20396,15 @@ function openReadableFromConsumer(consumer, opts = {}) {
20396
20396
  cb(err);
20397
20397
  }
20398
20398
  });
20399
- const onVideo = (frame) => {
20400
- const ok = readable.push(objectMode ? frame : frame.data);
20401
- if (!ok)
20399
+ consumer.onMedia((item) => {
20400
+ if (item.kind !== "video")
20401
+ return;
20402
+ const value = objectMode ? opts.arrivalTime ? { frame: item.frame, timestampMs: item.timestampMs } : item.frame : item.frame.data;
20403
+ if (!readable.push(value))
20402
20404
  consumer.pause();
20403
- };
20404
- consumer.on("video", onVideo);
20405
+ });
20406
+ consumer.on("video", () => {
20407
+ });
20405
20408
  consumer.on("stop", () => readable.push(null));
20406
20409
  consumer.on("error", (err) => readable.destroy(err));
20407
20410
  return readable;
@@ -28301,7 +28304,8 @@ var EufyMega = class extends EventEmitter13 {
28301
28304
  *
28302
28305
  * Skipped entirely for a device with a pollable cloud record, for an already-reporting device, and
28303
28306
  * when realtime is off — so the common path adds nothing. Resolving on timeout leaves a silent device
28304
- * read-less rather than failing the lookup.
28307
+ * read-less rather than failing the lookup. A Tuya clean-line device waits on the Tuya cloud read the
28308
+ * bring-up started, so no broker is brought up for it.
28305
28309
  */
28306
28310
  async awaitFirstRealtimeState(sn) {
28307
28311
  if (this.opts.autoRealtime === false || this.realtimeGeneration?.epoch !== this.realtimeEpoch)
@@ -28319,10 +28323,12 @@ var EufyMega = class extends EventEmitter13 {
28319
28323
  const caps = this.registry.capabilitiesForDevice(sn);
28320
28324
  if (!caps || !hasRealtimeReads(caps))
28321
28325
  return;
28322
- try {
28323
- await this.ensureMqttStarted(mqttScopeFor(dev));
28324
- } catch {
28325
- return;
28326
+ if (!isTuyaVacuum(dev)) {
28327
+ try {
28328
+ await this.ensureMqttStarted(mqttScopeFor(dev));
28329
+ } catch {
28330
+ return;
28331
+ }
28326
28332
  }
28327
28333
  await this.registry.awaitRealtimeState(sn, timeoutMs);
28328
28334
  }
@@ -28429,9 +28435,27 @@ var EufyMega = class extends EventEmitter13 {
28429
28435
  this.reportError(e);
28430
28436
  }
28431
28437
  }
28432
- /** The distinct credential scopes the current MQTT roster needs — no devices on a scope, no connection. */
28438
+ /**
28439
+ * The distinct credential scopes the current MQTT roster needs — no devices on a scope, no connection.
28440
+ * A Tuya clean-line device (`eufy_home_tuya`) needs none: the broker refuses its topics, and its state
28441
+ * is read from the Tuya cloud.
28442
+ */
28433
28443
  mqttScopesInUse() {
28434
- return [...new Set(this.getMqttDevices().map(mqttScopeFor))];
28444
+ return [
28445
+ ...new Set(this.getMqttDevices().filter((d) => !isTuyaVacuum(d)).map(mqttScopeFor))
28446
+ ];
28447
+ }
28448
+ /**
28449
+ * Read every Tuya clean-line device's DPs from the Tuya cloud and deliver them as realtime state. A
28450
+ * failed read is reported, not fatal, and leaves that device's state as it was.
28451
+ */
28452
+ readTuyaDevices() {
28453
+ for (const d of this.getMqttDevices().filter(isTuyaVacuum)) {
28454
+ this.tuya.fetchDps(d.sn).then((dps) => {
28455
+ if (dps)
28456
+ this.tuyaDpRouter.deliver(d.sn, dps);
28457
+ }).catch((e) => this.reportError(e));
28458
+ }
28435
28459
  }
28436
28460
  /**
28437
28461
  * Emit a semantic capability event whose name is only known at runtime (`decodeCapabilityEvent`
@@ -28942,7 +28966,7 @@ var EufyMega = class extends EventEmitter13 {
28942
28966
  }
28943
28967
  if (cmd.kind === "aiot-dp") {
28944
28968
  const dev = this.registry.require(sn);
28945
- if (dev.category === "eufy_home_tuya")
28969
+ if (isTuyaVacuum(dev))
28946
28970
  return this.tuya.dispatchCommand(sn, cmd);
28947
28971
  return this.mqtt.dispatchCommand(sn, cmd);
28948
28972
  }
@@ -29000,7 +29024,7 @@ var EufyMega = class extends EventEmitter13 {
29000
29024
  async getDevices() {
29001
29025
  const devices = await this.registry.getDevices();
29002
29026
  for (const dev of devices) {
29003
- if (dev.category !== "eufy_home_tuya")
29027
+ if (!isTuyaVacuum(dev))
29004
29028
  continue;
29005
29029
  const raw = dev.raw ?? {};
29006
29030
  this.tuya.registerDevice(dev.sn, tuyaDevIdFrom(raw) ?? dev.sn);
@@ -29174,6 +29198,7 @@ var EufyMega = class extends EventEmitter13 {
29174
29198
  await this.getDevices();
29175
29199
  if (epoch !== this.realtimeEpoch)
29176
29200
  return this.settleRealtimeGeneration(generation, "superseded");
29201
+ this.readTuyaDevices();
29177
29202
  const scopes = this.mqttScopesInUse();
29178
29203
  readiness.mqtt = createPlaneReadiness(scopes.length);
29179
29204
  let push;
@@ -29663,18 +29688,10 @@ var EufyMega = class extends EventEmitter13 {
29663
29688
  * unreachable device must not stop the rest of the roster from coming up).
29664
29689
  *
29665
29690
  * A Tuya clean-line device (`eufy_home_tuya`) is not subscribed: its state lives on the Tuya cloud,
29666
- * and the broker refuses its `eufy_home` topics on every credential. Its DPs are read from the Tuya
29667
- * cloud instead.
29691
+ * and the broker refuses its `eufy_home` topics on every credential.
29668
29692
  */
29669
29693
  async subscribeMqttDevices(scope, transport) {
29670
- for (const d of this.getMqttDevices().filter((dev) => mqttScopeFor(dev) === scope)) {
29671
- if (d.category === "eufy_home_tuya") {
29672
- this.tuya.fetchDps(d.sn).then((dps) => {
29673
- if (dps)
29674
- this.tuyaDpRouter.deliver(d.sn, dps);
29675
- }).catch((e) => this.reportError(e));
29676
- continue;
29677
- }
29694
+ for (const d of this.getMqttDevices().filter((dev) => !isTuyaVacuum(dev) && mqttScopeFor(dev) === scope)) {
29678
29695
  try {
29679
29696
  await transport.subscribeDevice(d);
29680
29697
  await this.sendRealtimeInit(d.sn);