@mega-yfue/eufy-sdk 0.5.0-beta.77 → 0.5.0-beta.79
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/core/contracts.d.ts +9 -3
- package/dist/index.js +71 -27
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/camera.d.ts +1 -0
- package/dist/model/device-family.d.ts +2 -0
- package/dist/transport/rtc/command-router.d.ts +1 -1
- package/dist/transport/rtc/live.d.ts +3 -1
- package/package.json +1 -1
package/dist/core/contracts.d.ts
CHANGED
|
@@ -734,6 +734,10 @@ export interface LiveStreamConsumer extends LiveStreamHandle {
|
|
|
734
734
|
export interface AbortableCall {
|
|
735
735
|
signal?: AbortSignal;
|
|
736
736
|
}
|
|
737
|
+
/** Select an optical view; absence selects view 0. */
|
|
738
|
+
export interface MediaSourceSelection {
|
|
739
|
+
view?: number;
|
|
740
|
+
}
|
|
737
741
|
export interface SharedSourceHints {
|
|
738
742
|
/**
|
|
739
743
|
* Power source, a runtime device fact (`"battery"` incl. solar, or `"wired"`) — never a device-family
|
|
@@ -759,6 +763,8 @@ export interface SharedSourceHints {
|
|
|
759
763
|
* because an unbound model has no live client (hence `?.` at the call site).
|
|
760
764
|
*/
|
|
761
765
|
export interface MediaProvider {
|
|
766
|
+
/** Optical view identifiers supported by this bound media route. */
|
|
767
|
+
views?: readonly number[];
|
|
762
768
|
/**
|
|
763
769
|
* Return the latest validated push thumbnail retained in memory. This passive operation performs no
|
|
764
770
|
* network, storage, P2P, live-media, or transcoding work at call time. It rejects with
|
|
@@ -785,7 +791,7 @@ export interface MediaProvider {
|
|
|
785
791
|
timeoutMs?: number;
|
|
786
792
|
collectMs?: number;
|
|
787
793
|
skipKeyframes?: number;
|
|
788
|
-
} & SharedSourceHints & AbortableCall): Promise<{
|
|
794
|
+
} & SharedSourceHints & AbortableCall & MediaSourceSelection): Promise<{
|
|
789
795
|
jpeg: Buffer;
|
|
790
796
|
width: number;
|
|
791
797
|
height: number;
|
|
@@ -817,7 +823,7 @@ export interface MediaProvider {
|
|
|
817
823
|
* stream?.stop(); // detach this consumer
|
|
818
824
|
* ```
|
|
819
825
|
*/
|
|
820
|
-
live(opts?: SharedSourceHints & AbortableCall & Record<string, unknown>): Promise<LiveStreamConsumer>;
|
|
826
|
+
live(opts?: SharedSourceHints & AbortableCall & MediaSourceSelection & Record<string, unknown>): Promise<LiveStreamConsumer>;
|
|
821
827
|
/**
|
|
822
828
|
* Record `seconds` of video → an mp4/h264 buffer.
|
|
823
829
|
*
|
|
@@ -843,7 +849,7 @@ export interface MediaProvider {
|
|
|
843
849
|
openReadable?(opts?: {
|
|
844
850
|
objectMode?: boolean;
|
|
845
851
|
arrivalTime?: boolean;
|
|
846
|
-
} & SharedSourceHints & AbortableCall): Promise<import("node:stream").Readable>;
|
|
852
|
+
} & SharedSourceHints & AbortableCall & MediaSourceSelection): Promise<import("node:stream").Readable>;
|
|
847
853
|
/**
|
|
848
854
|
* Continuously record the live feed as fragmented-MP4 (CMAF). The caller-owned
|
|
849
855
|
* {@link FragmentRecordingHandle} yields an init segment then keyframe-bounded media fragments,
|
package/dist/index.js
CHANGED
|
@@ -3656,6 +3656,7 @@ var isHomeBase = (ctx) => has(HOMEBASE_TYPES, ctx.deviceType);
|
|
|
3656
3656
|
var isStation9000 = (ctx) => ctx.deviceType === DeviceType.STATION_9000;
|
|
3657
3657
|
var isNvrT8N00 = (ctx) => ctx.model === "T8N00";
|
|
3658
3658
|
var isNvrMediaCamera = (ctx) => ctx.model === "T8E00" || ctx.model === "T8P00";
|
|
3659
|
+
var isNvrDualLensCamera = (ctx) => ctx.model === "T8E00";
|
|
3659
3660
|
var isDoorbellT8223 = (ctx) => ctx.model === "T8223";
|
|
3660
3661
|
var isAiotVacuum = (ctx) => ctx.category !== "eufy_home_tuya";
|
|
3661
3662
|
var isTuyaVacuum = (ctx) => ctx.category === "eufy_home_tuya";
|
|
@@ -5591,6 +5592,11 @@ function refuseWhenDisabled(ctx, read) {
|
|
|
5591
5592
|
if (CAMERA_MEMBERS.enabled.readReflectsWrite(ctx) && read("enabled")?.value === false)
|
|
5592
5593
|
throw new CameraDisabledError(ctx.name ?? ctx.serial);
|
|
5593
5594
|
}
|
|
5595
|
+
function refuseUnsupportedView(media, opts) {
|
|
5596
|
+
const view = opts?.view ?? 0;
|
|
5597
|
+
if (!(media.views ?? [0]).includes(view))
|
|
5598
|
+
throw new RangeError(`camera: unsupported optical view ${view}`);
|
|
5599
|
+
}
|
|
5594
5600
|
var CAMERA_MEMBERS = {
|
|
5595
5601
|
/** Reads the latest cached report and exposes no setter. */
|
|
5596
5602
|
wifiRssi: {
|
|
@@ -5861,6 +5867,7 @@ var CAMERA_MEMBERS = {
|
|
|
5861
5867
|
description: "Camera status LED. Video doorbells report the same state under their button-ring LED parameter.",
|
|
5862
5868
|
write: (v, ctx) => statusLedCommand(asBool(v), ctx)
|
|
5863
5869
|
},
|
|
5870
|
+
views: provided("media", (m) => m.views && (() => m.views), "Optical view identifiers supported by this camera's media route."),
|
|
5864
5871
|
/**
|
|
5865
5872
|
* Media is not a property: it returns DATA rather than moving state, its options are richer than a
|
|
5866
5873
|
* value, and it exists only on a device bound to a provider. Each is declared once, with its
|
|
@@ -5881,10 +5888,12 @@ var CAMERA_MEMBERS = {
|
|
|
5881
5888
|
snapshotStored: provided("media", (m, { ctx }) => isCameraCodec(ctx) && m.snapshotStored && (() => m.snapshotStored()), "Latest validated push thumbnail retained in memory.", ["snapshot"]),
|
|
5882
5889
|
snapshotLive: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && (async (opts) => {
|
|
5883
5890
|
refuseWhenDisabled(ctx, read);
|
|
5891
|
+
refuseUnsupportedView(m, opts);
|
|
5884
5892
|
return m.snapshotLive({ powered: poweredOf(ctx), ...opts });
|
|
5885
5893
|
}), "Fresh still decoded from a short live burst."),
|
|
5886
5894
|
live: provided("media", (m, { ctx, read }) => async (opts) => {
|
|
5887
5895
|
refuseWhenDisabled(ctx, read);
|
|
5896
|
+
refuseUnsupportedView(m, opts);
|
|
5888
5897
|
return m.live({ powered: poweredOf(ctx), ...opts });
|
|
5889
5898
|
}, "Open a managed live stream."),
|
|
5890
5899
|
record: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && (async (...args) => {
|
|
@@ -5893,6 +5902,7 @@ var CAMERA_MEMBERS = {
|
|
|
5893
5902
|
}), "Record N seconds \u2192 an mp4/h264 buffer."),
|
|
5894
5903
|
openReadable: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && m.openReadable && (async (opts) => {
|
|
5895
5904
|
refuseWhenDisabled(ctx, read);
|
|
5905
|
+
refuseUnsupportedView(m, opts);
|
|
5896
5906
|
return m.openReadable({ powered: poweredOf(ctx), ...opts });
|
|
5897
5907
|
}), "Open a node:stream Readable of the live feed."),
|
|
5898
5908
|
recordFragments: provided("media", (m, { ctx, read }) => isCameraCodec(ctx) && m.recordFragments && ((opts) => {
|
|
@@ -6977,6 +6987,28 @@ var PTZ = {
|
|
|
6977
6987
|
|
|
6978
6988
|
// dist/model/capabilities/doorbell.js
|
|
6979
6989
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
6990
|
+
var DOORBELL_MODELS = /* @__PURE__ */ new Set([
|
|
6991
|
+
"T820",
|
|
6992
|
+
"T8200",
|
|
6993
|
+
"T8201",
|
|
6994
|
+
"T8202",
|
|
6995
|
+
"T8203",
|
|
6996
|
+
"T8210",
|
|
6997
|
+
"T8212",
|
|
6998
|
+
"T8213",
|
|
6999
|
+
"T8214",
|
|
7000
|
+
"T8220",
|
|
7001
|
+
"T8221",
|
|
7002
|
+
"T8222"
|
|
7003
|
+
]);
|
|
7004
|
+
var DOORBELL_TYPES = [
|
|
7005
|
+
DeviceType.DOORBELL,
|
|
7006
|
+
DeviceType.BATTERY_DOORBELL,
|
|
7007
|
+
DeviceType.BATTERY_DOORBELL_2,
|
|
7008
|
+
DeviceType.BATTERY_DOORBELL_PLUS,
|
|
7009
|
+
DeviceType.DOORBELL_SOLO,
|
|
7010
|
+
DeviceType.BATTERY_DOORBELL_PLUS_E340
|
|
7011
|
+
];
|
|
6980
7012
|
var DOORBELL_CMD = {
|
|
6981
7013
|
/**
|
|
6982
7014
|
* Doorbell "quick response" — make the doorbell PLAY a stored voice reply. Same 136-byte
|
|
@@ -7313,8 +7345,16 @@ var DOORBELL = {
|
|
|
7313
7345
|
description: "Doorbell chime / ringtone configuration (ring press is an event, not a param).",
|
|
7314
7346
|
members: DOORBELL_MEMBERS,
|
|
7315
7347
|
properties: propertiesOf(DOORBELL_MEMBERS),
|
|
7316
|
-
/**
|
|
7317
|
-
|
|
7348
|
+
/**
|
|
7349
|
+
* Doorbells self-report no param of their own (the 1702-1719 settings also arrive from eufyCams), and
|
|
7350
|
+
* the cloud record names no product, so detection keys off the app's own tables:
|
|
7351
|
+
* {@link DOORBELL_MODELS} and {@link DOORBELL_TYPES}.
|
|
7352
|
+
*/
|
|
7353
|
+
detection: {
|
|
7354
|
+
detect: (rec) => DOORBELL_MODELS.has(rec.model ?? ""),
|
|
7355
|
+
deviceTypes: DOORBELL_TYPES,
|
|
7356
|
+
modelHints: [/doorbell/i]
|
|
7357
|
+
},
|
|
7318
7358
|
/**
|
|
7319
7359
|
* Inbound FCM doorbell events (`DoorbellPushEvent`): ring press and the package trio.
|
|
7320
7360
|
*
|
|
@@ -11584,18 +11624,16 @@ var MODEL_REGISTRY = {
|
|
|
11584
11624
|
T8002: { codec: "station", name: "HomeBase E" },
|
|
11585
11625
|
// Cameras with curated extras that aren't always provable from a fresh device's params.
|
|
11586
11626
|
T8423: { codec: "camera", caps: ["light", "battery"], name: "Floodlight Cam" },
|
|
11587
|
-
T8210: { codec: "camera", caps: ["
|
|
11627
|
+
T8210: { codec: "camera", caps: ["battery"], name: "Video Doorbell" },
|
|
11588
11628
|
// T8210 hardware variant, reported as its own code; shares the T8210 row.
|
|
11589
|
-
T8210C: { codec: "camera", caps: ["
|
|
11629
|
+
T8210C: { codec: "camera", caps: ["battery"], name: "Video Doorbell" },
|
|
11590
11630
|
// Confirmed against a real owned unit (named "Doorbell"): Video Doorbell Dual.
|
|
11591
|
-
T8214: { codec: "camera", caps: ["
|
|
11592
|
-
|
|
11593
|
-
T8213: { codec: "camera", caps: ["doorbell", "battery"], name: "Video Doorbell Dual" },
|
|
11631
|
+
T8214: { codec: "camera", caps: ["battery"], name: "Video Doorbell Dual" },
|
|
11632
|
+
T8213: { codec: "camera", caps: ["battery"], name: "Video Doorbell Dual" },
|
|
11594
11633
|
// Confirmed against a real owned unit: the mains-powered Wired Doorbell 2K. No `battery` row
|
|
11595
11634
|
// member on purpose — this model is wired, so the codec baseline plus inference is the whole
|
|
11596
|
-
// truth for power.
|
|
11597
|
-
|
|
11598
|
-
T8200: { codec: "camera", caps: ["doorbell"], name: "Wired Doorbell 2K" },
|
|
11635
|
+
// truth for power.
|
|
11636
|
+
T8200: { codec: "camera", name: "Wired Doorbell 2K" },
|
|
11599
11637
|
// Cameras observed on real owned hardware (inspect-device sweep). Names from the app's own
|
|
11600
11638
|
// model-family constants (scripts/data/app_model_registry.json); caps mirror what the device
|
|
11601
11639
|
// reports. T8171 has no family entry in that dump, so it keeps the raw T-code.
|
|
@@ -23501,7 +23539,7 @@ var RtcLiveStream = class extends EventEmitter7 {
|
|
|
23501
23539
|
audio_chn: nvr ? this.opts.channel : -1,
|
|
23502
23540
|
streamtype: 2,
|
|
23503
23541
|
stitch_mode: 1,
|
|
23504
|
-
chn_list: [{ index: 0, chn: this.opts.channel, sensor: 0, isUps: 0, isClicked: true }],
|
|
23542
|
+
chn_list: [{ index: 0, chn: this.opts.channel, sensor: this.opts.view ?? 0, isUps: 0, isClicked: true }],
|
|
23505
23543
|
pip_cord: { x1: 0, y1: 0, x2: 0, y2: 0 },
|
|
23506
23544
|
station_video_type: 6,
|
|
23507
23545
|
play_id: 1
|
|
@@ -24843,22 +24881,28 @@ var RtcCommandRouter = class {
|
|
|
24843
24881
|
return this.callMediaProviderFor(route, route.stationSn, (session) => new RtcCallLiveStream(session, this.deps.logger), `record is not available for ${route.stationSn} over a portal call`, 45e3);
|
|
24844
24882
|
}
|
|
24845
24883
|
/** NVR media uses a dedicated call per camera, independent of station commands. */
|
|
24846
|
-
nvrMediaProviderFor(route) {
|
|
24847
|
-
return
|
|
24848
|
-
session,
|
|
24849
|
-
|
|
24850
|
-
|
|
24851
|
-
|
|
24852
|
-
|
|
24853
|
-
|
|
24854
|
-
|
|
24855
|
-
|
|
24884
|
+
nvrMediaProviderFor(route, views) {
|
|
24885
|
+
return {
|
|
24886
|
+
...this.callMediaProviderFor(route, route.cameraSn, (session, accountId, view) => new RtcLiveStream({
|
|
24887
|
+
session,
|
|
24888
|
+
seg: new SegmentCounter(),
|
|
24889
|
+
stationSn: route.stationSn,
|
|
24890
|
+
channel: route.channel,
|
|
24891
|
+
accountId,
|
|
24892
|
+
logger: this.deps.logger,
|
|
24893
|
+
nvr: true,
|
|
24894
|
+
view
|
|
24895
|
+
}), "record is not available over an NVR portal call"),
|
|
24896
|
+
views
|
|
24897
|
+
};
|
|
24856
24898
|
}
|
|
24857
24899
|
/** Share a dedicated-call source and its consumer operations under one route key. */
|
|
24858
24900
|
callMediaProviderFor(route, key, makeStream, recordError, warmTimeoutMs) {
|
|
24859
24901
|
const sourceFor = (opts = {}) => {
|
|
24860
24902
|
opts.signal?.throwIfAborted();
|
|
24861
|
-
|
|
24903
|
+
const view = opts.view ?? 0;
|
|
24904
|
+
const sourceKey = view === 0 ? key : `${key}:view:${view}`;
|
|
24905
|
+
let source = this.callSources.get(sourceKey);
|
|
24862
24906
|
if (!source) {
|
|
24863
24907
|
source = new SharedLiveSource({
|
|
24864
24908
|
makeStream: () => {
|
|
@@ -24866,15 +24910,15 @@ var RtcCommandRouter = class {
|
|
|
24866
24910
|
if (!identity)
|
|
24867
24911
|
throw new Error(`rtc: not logged in, cannot call ${route.stationSn}`);
|
|
24868
24912
|
const accountId = route.adminUserId || identity.userId;
|
|
24869
|
-
return makeStream(this.newSession(route, accountId, identity), accountId);
|
|
24913
|
+
return makeStream(this.newSession(route, accountId, identity), accountId, view);
|
|
24870
24914
|
},
|
|
24871
|
-
label:
|
|
24915
|
+
label: sourceKey,
|
|
24872
24916
|
logger: this.deps.logger,
|
|
24873
24917
|
powered: opts.powered,
|
|
24874
24918
|
preBufferSeconds: opts.preBufferSeconds,
|
|
24875
24919
|
warmTimeoutMs
|
|
24876
24920
|
});
|
|
24877
|
-
this.callSources.set(
|
|
24921
|
+
this.callSources.set(sourceKey, source);
|
|
24878
24922
|
}
|
|
24879
24923
|
return source;
|
|
24880
24924
|
};
|
|
@@ -29447,7 +29491,7 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29447
29491
|
try {
|
|
29448
29492
|
return await media.snapshotLive(opts);
|
|
29449
29493
|
} catch (error) {
|
|
29450
|
-
if (!(error instanceof LiveSnapshotUnavailableError))
|
|
29494
|
+
if (!(error instanceof LiveSnapshotUnavailableError) || (opts?.view ?? 0) !== 0)
|
|
29451
29495
|
throw error;
|
|
29452
29496
|
const retained = await retainedStill().catch(() => void 0);
|
|
29453
29497
|
const geometry = retained && jpegGeometry(retained);
|
|
@@ -29536,7 +29580,7 @@ var EufyMega = class extends EventEmitter14 {
|
|
|
29536
29580
|
if (route.signalingMode === "call") {
|
|
29537
29581
|
if (!camera || !isNvrMediaCamera(camera))
|
|
29538
29582
|
return void 0;
|
|
29539
|
-
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29583
|
+
return this.rtc.nvrMediaProviderFor({ ...route, cameraSn: sn, channel }, isNvrDualLensCamera(camera) ? [0, 1] : [0]);
|
|
29540
29584
|
}
|
|
29541
29585
|
return this.rtc.mediaProviderFor({ ...route, cameraSn: sn, channel });
|
|
29542
29586
|
}
|