@mega-yfue/eufy-sdk 0.5.0-beta.14 → 0.5.0-beta.16
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.
|
@@ -16,7 +16,10 @@ export interface DeviceRecord {
|
|
|
16
16
|
deviceType?: number;
|
|
17
17
|
model?: string;
|
|
18
18
|
category?: string;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The app-shown device name: `alias_name` first on a vacuum, `device_name` first otherwise;
|
|
21
|
+
* see {@link CloudRecord.name}.
|
|
22
|
+
*/
|
|
20
23
|
name?: string;
|
|
21
24
|
/** Parent HomeBase serial when attached (topology signal; see {@link CloudRecord.parentSn}). */
|
|
22
25
|
parentSn?: string;
|
|
@@ -92,6 +95,8 @@ export declare class DeviceRegistry {
|
|
|
92
95
|
private overlayRefusalReported;
|
|
93
96
|
/** Per-serial capability cache for {@link capabilitiesForDevice}; `null` = negative hit. */
|
|
94
97
|
private readonly deviceCapsCache;
|
|
98
|
+
/** Per-serial cloud data points of an AIoT robot, joined beneath the params in {@link record} — see {@link loadCloudDps}. */
|
|
99
|
+
private readonly cloudDps;
|
|
95
100
|
/** Per-serial realtime state, keyed by param id in the device's own namespace (see {@link DeviceRecord.dpParams}). */
|
|
96
101
|
private readonly dpParams;
|
|
97
102
|
/** Callers blocked in {@link awaitRealtimeState}, released by the device's first report. */
|
|
@@ -181,9 +186,26 @@ export declare class DeviceRegistry {
|
|
|
181
186
|
* transport stacks own the claim predicate over the record.
|
|
182
187
|
*/
|
|
183
188
|
require(sn: string): EufyDevice;
|
|
189
|
+
/**
|
|
190
|
+
* Hold an AIoT robot's cloud data points: the cloud's last-known value of each one it has reported,
|
|
191
|
+
* from `getDeviceRelationList`'s per-device `dps`.
|
|
192
|
+
*
|
|
193
|
+
* The device list carries only some of a robot's data points, so a point the robot publishes on change
|
|
194
|
+
* alone (its scene list, its schedules, its consumables) has no value until it next reports. The
|
|
195
|
+
* relation list holds every one, for an account the robot is shared with as well as its owner.
|
|
196
|
+
*
|
|
197
|
+
* Kept apart from the device list's params rather than merged into them: those are what a poll diffs,
|
|
198
|
+
* and a diff retires the realtime report for that id, so a cloud value lagging the robot's last report
|
|
199
|
+
* would replace it. {@link record} joins this beneath both halves. A failure keeps what was held.
|
|
200
|
+
*
|
|
201
|
+
* Limited to {@link CLOUD_DPS_MODELS}, the models whose relation-list `dps` are checked to carry the
|
|
202
|
+
* DP numbers their realtime reports use.
|
|
203
|
+
*/
|
|
204
|
+
private loadCloudDps;
|
|
184
205
|
/**
|
|
185
206
|
* Resolve a serial to a {@link DeviceRecord} with current params: starts from the device-list params, then
|
|
186
|
-
* overlays a fresh `get_device_param_list` when that call is available to this account.
|
|
207
|
+
* overlays a fresh `get_device_param_list` when that call is available to this account. An AIoT robot's
|
|
208
|
+
* cloud data points ({@link loadCloudDps}) sit beneath both, filling only ids neither carries.
|
|
187
209
|
*
|
|
188
210
|
* The overlay is **owner-gated** — a shared or member account is refused it for every device, permanently,
|
|
189
211
|
* which {@link OWNER_ONLY_CODE} identifies — so when it is unavailable the device list is the source
|
package/dist/index.js
CHANGED
|
@@ -1996,6 +1996,17 @@ ${this.auth_?.authToken ?? ""}`;
|
|
|
1996
1996
|
device_sns: [deviceSn]
|
|
1997
1997
|
});
|
|
1998
1998
|
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Fetch the account's devices as the `devicerelation` service lists them, `{attribute: 7, house_id: ""}`.
|
|
2001
|
+
*
|
|
2002
|
+
* Each entry is `{device: {device_sn, dps, …}}`, and `dps` is the cloud's last-known value of every
|
|
2003
|
+
* data point that device has reported, keyed by DP number. Answered for an account a device is shared
|
|
2004
|
+
* with as well as for its owner. Sent with `category: eufy_home`, without which the service answers
|
|
2005
|
+
* `10000`.
|
|
2006
|
+
*/
|
|
2007
|
+
getDeviceRelationList() {
|
|
2008
|
+
return this.post("devicerelation", "/app/devicerelation/get_device_list", { attribute: 7, house_id: "" }, { category: "eufy_home" });
|
|
2009
|
+
}
|
|
1999
2010
|
/**
|
|
2000
2011
|
* Fetch one page of a device's **cleaning history** from the mega `clean` service.
|
|
2001
2012
|
*
|
|
@@ -11046,6 +11057,8 @@ var MODEL_REGISTRY = {
|
|
|
11046
11057
|
T8210: { codec: "camera", caps: ["doorbell", "battery"], name: "Video Doorbell" },
|
|
11047
11058
|
// Confirmed against a real owned unit (named "Doorbell"): Video Doorbell Dual.
|
|
11048
11059
|
T8214: { codec: "camera", caps: ["doorbell", "battery"], name: "Video Doorbell Dual" },
|
|
11060
|
+
// Battery Video Doorbell Dual, vendor deviceType 91 (BATTERY_DOORBELL_PLUS), reported by two users.
|
|
11061
|
+
T8213: { codec: "camera", caps: ["doorbell", "battery"], name: "Video Doorbell Dual" },
|
|
11049
11062
|
// Confirmed against a real owned unit: the mains-powered Wired Doorbell 2K. No `battery` row
|
|
11050
11063
|
// member on purpose — this model is wired, so the codec baseline plus inference is the whole
|
|
11051
11064
|
// truth for power. Without this row it classified as a plain camera, so the doorbell capability
|
|
@@ -26330,7 +26343,14 @@ function deviceClassOf(codec, realtime) {
|
|
|
26330
26343
|
const cls = CLASS_BY_CODEC[codec];
|
|
26331
26344
|
return cls === "camera" && realtime !== "p2p" ? "other" : cls;
|
|
26332
26345
|
}
|
|
26346
|
+
function recordName(raw, deviceClass) {
|
|
26347
|
+
if (deviceClass === "vacuum") {
|
|
26348
|
+
return raw.alias_name || raw.device_alias_name || raw.device_name || void 0;
|
|
26349
|
+
}
|
|
26350
|
+
return raw.device_name ?? raw.device_alias_name ?? raw.alias_name;
|
|
26351
|
+
}
|
|
26333
26352
|
var LIST_REUSE_MS = 5e3;
|
|
26353
|
+
var CLOUD_DPS_MODELS = /* @__PURE__ */ new Set(["T2351"]);
|
|
26334
26354
|
var DeviceRegistry = class {
|
|
26335
26355
|
mega;
|
|
26336
26356
|
onError;
|
|
@@ -26355,6 +26375,8 @@ var DeviceRegistry = class {
|
|
|
26355
26375
|
overlayRefusalReported = false;
|
|
26356
26376
|
/** Per-serial capability cache for {@link capabilitiesForDevice}; `null` = negative hit. */
|
|
26357
26377
|
deviceCapsCache = /* @__PURE__ */ new Map();
|
|
26378
|
+
/** Per-serial cloud data points of an AIoT robot, joined beneath the params in {@link record} — see {@link loadCloudDps}. */
|
|
26379
|
+
cloudDps = /* @__PURE__ */ new Map();
|
|
26358
26380
|
/** Per-serial realtime state, keyed by param id in the device's own namespace (see {@link DeviceRecord.dpParams}). */
|
|
26359
26381
|
dpParams = /* @__PURE__ */ new Map();
|
|
26360
26382
|
/** Callers blocked in {@link awaitRealtimeState}, released by the device's first report. */
|
|
@@ -26449,9 +26471,10 @@ var DeviceRegistry = class {
|
|
|
26449
26471
|
category: raw.category,
|
|
26450
26472
|
params
|
|
26451
26473
|
}).codec;
|
|
26474
|
+
const deviceClass = deviceClassOf(codec, c.realtime);
|
|
26452
26475
|
seen.set(raw.device_sn, {
|
|
26453
26476
|
sn: raw.device_sn,
|
|
26454
|
-
name: raw
|
|
26477
|
+
name: recordName(raw, deviceClass),
|
|
26455
26478
|
model: raw.device_model,
|
|
26456
26479
|
stationSn: resolvedStationSn(raw, raw.device_sn),
|
|
26457
26480
|
p2pDid: raw.p2p_did,
|
|
@@ -26459,11 +26482,12 @@ var DeviceRegistry = class {
|
|
|
26459
26482
|
paramUpdatedAt,
|
|
26460
26483
|
lastSeenMs: lastSeenMsOf(paramUpdatedAt),
|
|
26461
26484
|
raw,
|
|
26462
|
-
deviceClass
|
|
26485
|
+
deviceClass,
|
|
26463
26486
|
...c
|
|
26464
26487
|
});
|
|
26465
26488
|
}
|
|
26466
26489
|
}
|
|
26490
|
+
await this.loadCloudDps(seen);
|
|
26467
26491
|
this.lastRefreshPartial = partial;
|
|
26468
26492
|
if (partial) {
|
|
26469
26493
|
for (const prev of this.devices)
|
|
@@ -26554,9 +26578,46 @@ var DeviceRegistry = class {
|
|
|
26554
26578
|
}
|
|
26555
26579
|
return dev;
|
|
26556
26580
|
}
|
|
26581
|
+
/**
|
|
26582
|
+
* Hold an AIoT robot's cloud data points: the cloud's last-known value of each one it has reported,
|
|
26583
|
+
* from `getDeviceRelationList`'s per-device `dps`.
|
|
26584
|
+
*
|
|
26585
|
+
* The device list carries only some of a robot's data points, so a point the robot publishes on change
|
|
26586
|
+
* alone (its scene list, its schedules, its consumables) has no value until it next reports. The
|
|
26587
|
+
* relation list holds every one, for an account the robot is shared with as well as its owner.
|
|
26588
|
+
*
|
|
26589
|
+
* Kept apart from the device list's params rather than merged into them: those are what a poll diffs,
|
|
26590
|
+
* and a diff retires the realtime report for that id, so a cloud value lagging the robot's last report
|
|
26591
|
+
* would replace it. {@link record} joins this beneath both halves. A failure keeps what was held.
|
|
26592
|
+
*
|
|
26593
|
+
* Limited to {@link CLOUD_DPS_MODELS}, the models whose relation-list `dps` are checked to carry the
|
|
26594
|
+
* DP numbers their realtime reports use.
|
|
26595
|
+
*/
|
|
26596
|
+
async loadCloudDps(seen) {
|
|
26597
|
+
const robots = [...seen.values()].filter((d) => CLOUD_DPS_MODELS.has(d.model ?? ""));
|
|
26598
|
+
if (!robots.length)
|
|
26599
|
+
return;
|
|
26600
|
+
let res;
|
|
26601
|
+
try {
|
|
26602
|
+
res = await this.mega.getDeviceRelationList();
|
|
26603
|
+
} catch (e) {
|
|
26604
|
+
if (e instanceof SessionExpiredError)
|
|
26605
|
+
throw e;
|
|
26606
|
+
this.logger.debug(`[registry] device relation list unavailable: ${e?.message ?? e}`);
|
|
26607
|
+
return;
|
|
26608
|
+
}
|
|
26609
|
+
const wanted = new Set(robots.map((d) => d.sn));
|
|
26610
|
+
for (const entry of res.devices ?? []) {
|
|
26611
|
+
const sn = entry?.device?.device_sn;
|
|
26612
|
+
const dps = parseAiotDpReport({ payload: entry?.device?.dps });
|
|
26613
|
+
if (sn && wanted.has(sn) && dps)
|
|
26614
|
+
this.cloudDps.set(sn, dps);
|
|
26615
|
+
}
|
|
26616
|
+
}
|
|
26557
26617
|
/**
|
|
26558
26618
|
* Resolve a serial to a {@link DeviceRecord} with current params: starts from the device-list params, then
|
|
26559
|
-
* overlays a fresh `get_device_param_list` when that call is available to this account.
|
|
26619
|
+
* overlays a fresh `get_device_param_list` when that call is available to this account. An AIoT robot's
|
|
26620
|
+
* cloud data points ({@link loadCloudDps}) sit beneath both, filling only ids neither carries.
|
|
26560
26621
|
*
|
|
26561
26622
|
* The overlay is **owner-gated** — a shared or member account is refused it for every device, permanently,
|
|
26562
26623
|
* which {@link OWNER_ONLY_CODE} identifies — so when it is unavailable the device list is the source
|
|
@@ -26585,7 +26646,7 @@ var DeviceRegistry = class {
|
|
|
26585
26646
|
let dev = this.devices.find((d) => d.sn === sn);
|
|
26586
26647
|
if (!dev)
|
|
26587
26648
|
throw new Error(`device ${sn} not found (have: ${this.devices.map((d) => d.sn).join(", ")})`);
|
|
26588
|
-
const params = { ...dev.params ?? {} };
|
|
26649
|
+
const params = { ...this.cloudDps.get(sn), ...dev.params ?? {} };
|
|
26589
26650
|
const paramUpdatedAt = { ...dev.paramUpdatedAt ?? {} };
|
|
26590
26651
|
if (!this.overlayRefused.has(sn)) {
|
|
26591
26652
|
try {
|