@mega-yfue/eufy-sdk 0.2.0-beta.28 → 0.2.0-beta.29
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/client/device-registry.d.ts +14 -28
- package/dist/core/contracts.d.ts +21 -0
- package/dist/core/solix-types.d.ts +6 -0
- package/dist/index.js +104 -46
- package/dist/index.js.map +3 -3
- package/dist/model/solix-device.d.ts +6 -5
- package/dist/transport/p2p/command-router.d.ts +0 -2
- package/dist/transport/p2p/live-trace.d.ts +9 -0
- package/dist/transport/p2p/station-channels.d.ts +54 -0
- package/package.json +1 -1
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The station a device's traffic belongs to, from its cloud record and its own serial.
|
|
3
|
-
*
|
|
4
|
-
* `parent_sn` carries the parent on a HomeBase-attached device. `station_sn` is frequently absent there —
|
|
5
|
-
* empty on every attached sensor of a T8010 — and serves only as a fallback. An empty string states no
|
|
6
|
-
* station.
|
|
7
|
-
*
|
|
8
|
-
* A device naming no parent answers its own serial, so every device has a station.
|
|
9
|
-
*/
|
|
10
|
-
export declare function resolvedStationSn(raw: Record<string, unknown>, sn: string): string;
|
|
11
1
|
/**
|
|
12
2
|
* DeviceRegistry — the device list/record/capability-resolution collaborator behind {@link EufyMega}.
|
|
13
3
|
*
|
|
@@ -85,6 +75,9 @@ export declare class DeviceRegistry {
|
|
|
85
75
|
private devices;
|
|
86
76
|
/** Per-(station, channel) capability cache for {@link capabilitiesForFrame}; `null` = negative hit. */
|
|
87
77
|
private readonly frameCapsCache;
|
|
78
|
+
/** {@link stationChannels} of {@link channelMapFor}, the roster it was computed over. */
|
|
79
|
+
private channelMap;
|
|
80
|
+
private channelMapFor?;
|
|
88
81
|
/**
|
|
89
82
|
* Serials whose per-device param overlay has been refused. The call is owner-gated, so on a shared or
|
|
90
83
|
* member account it fails for the whole life of the client — retrying it every refresh spends a request
|
|
@@ -291,33 +284,26 @@ export declare class DeviceRegistry {
|
|
|
291
284
|
* hub would emit indistinguishable events. The facade enriches the payload with this.
|
|
292
285
|
*/
|
|
293
286
|
serialForFrame(stationSn: string, channel: number): string | undefined;
|
|
294
|
-
/**
|
|
295
|
-
* The parent station a device's frames arrive under.
|
|
296
|
-
*
|
|
297
|
-
* `parent_sn` on the cloud record is the field that is actually populated for a HomeBase-attached
|
|
298
|
-
* device — `stationSn` is frequently absent (observed empty on every attached sensor of a T8010),
|
|
299
|
-
* so keying on it alone silently resolves an attached device to ITSELF and no frame ever matches.
|
|
300
|
-
* Mirrors the router's own session-keying precedence, which is the source of truth for which
|
|
301
|
-
* station a device's traffic belongs to. Answering the device's OWN serial is what "stands alone"
|
|
302
|
-
* means, so this is also the topology signal `record()`/`capsOf` hand the resolver.
|
|
303
|
-
*/
|
|
304
|
-
private stationOf;
|
|
305
287
|
/**
|
|
306
288
|
* The device a `(station, channel)` pair refers to — a station fans out to attached devices by
|
|
307
289
|
* `device_channel`, while a standalone device is its own station at channel 0.
|
|
308
290
|
*
|
|
309
|
-
* A device claims a channel only when its record actually STATES one
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
* decides where realtime state is written, not just which decoders may run, so an ambiguous answer
|
|
291
|
+
* A device claims a channel only when its record actually STATES one that no other device attached to
|
|
292
|
+
* the same station also states ({@link stationChannels}). Treating a missing `device_channel` as 0, or
|
|
293
|
+
* letting two claimants of one channel both hold it, leaves the winner to cloud list order — which a
|
|
294
|
+
* partial refresh reorders — so the same frame resolves to different devices across refreshes. The resolved
|
|
295
|
+
* serial decides where realtime state is written, not just which decoders may run, so an ambiguous answer
|
|
314
296
|
* writes one device's params onto another.
|
|
315
297
|
*
|
|
316
298
|
* An attached device that names the channel wins over the station itself, which is what a station
|
|
317
|
-
* fanning traffic out by channel means
|
|
318
|
-
*
|
|
299
|
+
* fanning traffic out by channel means. A channel two attached devices both state belongs to one of them,
|
|
300
|
+
* which cannot be told apart, so it answers nothing rather than the station. The station answers for
|
|
301
|
+
* channel 0 only when nothing attached states it, which is also the standalone case (a device is its own
|
|
302
|
+
* station).
|
|
319
303
|
*/
|
|
320
304
|
private deviceForFrame;
|
|
305
|
+
/** {@link stationChannels} over the current roster, recomputed only when the roster itself is replaced. */
|
|
306
|
+
private stationChannelMap;
|
|
321
307
|
/**
|
|
322
308
|
* Resolve the capability set of the device a P2P frame belongs to — the `(station, channel)` pair
|
|
323
309
|
* (a station fans out to attached devices by `device_channel`; a standalone device is its own
|
package/dist/core/contracts.d.ts
CHANGED
|
@@ -129,6 +129,27 @@ export declare class StationUnreachableError extends Error {
|
|
|
129
129
|
cause?: unknown;
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Work on a device was refused: its channel within its station cannot be established from the device records.
|
|
134
|
+
*
|
|
135
|
+
* A device attached to a HomeBase is addressed by a channel within that station: a media start and every
|
|
136
|
+
* per-channel command name it. When its record states no channel, or another device attached to the same station
|
|
137
|
+
* states the same one, any channel chosen would address whichever device actually holds it (streaming another
|
|
138
|
+
* camera's video under this serial), so nothing is sent. The `station-channel-unresolved` trace says which.
|
|
139
|
+
*/
|
|
140
|
+
export declare class DeviceChannelUnresolvedError extends Error {
|
|
141
|
+
/** The device that could not be addressed. */
|
|
142
|
+
readonly sn: string;
|
|
143
|
+
/** The station it is attached to. */
|
|
144
|
+
readonly stationSn: string;
|
|
145
|
+
constructor(
|
|
146
|
+
/** The device that could not be addressed. */
|
|
147
|
+
sn: string,
|
|
148
|
+
/** The station it is attached to. */
|
|
149
|
+
stationSn: string, options?: {
|
|
150
|
+
cause?: unknown;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
132
153
|
/**
|
|
133
154
|
* How a live stream ended before its first video keyframe: the warm-up deadline elapsed, the source
|
|
134
155
|
* reported an error, or the source ended on its own.
|
|
@@ -98,6 +98,12 @@ export interface SolixSceneSolarbank {
|
|
|
98
98
|
bat_temperature?: string | number;
|
|
99
99
|
/** State of charge, % (string on the wire). Cross-checks the `ff09` `0xa3` SOC. */
|
|
100
100
|
bat_soc?: string | number;
|
|
101
|
+
/**
|
|
102
|
+
* Number of ATTACHED expansion battery packs — the built-in battery is the host, not a pack. Read as
|
|
103
|
+
* `expansionPacks`. Observed live as `0` on a standalone AE103 main unit; a populated value (and the
|
|
104
|
+
* matching per-pack `bms_list` detail) has not yet been captured with a pack attached.
|
|
105
|
+
*/
|
|
106
|
+
sub_package_num?: string | number;
|
|
101
107
|
[k: string]: unknown;
|
|
102
108
|
}
|
|
103
109
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2898,6 +2898,16 @@ var StationUnreachableError = class extends Error {
|
|
|
2898
2898
|
this.name = "StationUnreachableError";
|
|
2899
2899
|
}
|
|
2900
2900
|
};
|
|
2901
|
+
var DeviceChannelUnresolvedError = class extends Error {
|
|
2902
|
+
sn;
|
|
2903
|
+
stationSn;
|
|
2904
|
+
constructor(sn, stationSn, options) {
|
|
2905
|
+
super(`${sn} has no usable channel on station ${stationSn}, so nothing was sent to it`, options);
|
|
2906
|
+
this.sn = sn;
|
|
2907
|
+
this.stationSn = stationSn;
|
|
2908
|
+
this.name = "DeviceChannelUnresolvedError";
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2901
2911
|
var LiveStreamStartError = class extends Error {
|
|
2902
2912
|
reason;
|
|
2903
2913
|
stage;
|
|
@@ -14138,6 +14148,9 @@ function solarbankSceneReadings(scene) {
|
|
|
14138
14148
|
const soc = sceneNum(sb.bat_soc);
|
|
14139
14149
|
if (soc !== void 0)
|
|
14140
14150
|
values.batterySoc = soc;
|
|
14151
|
+
const packs = sceneNum(sb.sub_package_num);
|
|
14152
|
+
if (packs !== void 0)
|
|
14153
|
+
values.expansionPacks = packs;
|
|
14141
14154
|
if (Object.keys(values).length > 0)
|
|
14142
14155
|
out.push({ deviceSn, values });
|
|
14143
14156
|
}
|
|
@@ -20016,6 +20029,50 @@ var FragmentRecording = class extends EventEmitter6 {
|
|
|
20016
20029
|
}
|
|
20017
20030
|
};
|
|
20018
20031
|
|
|
20032
|
+
// dist/transport/p2p/station-channels.js
|
|
20033
|
+
function resolvedStationSn(raw, sn) {
|
|
20034
|
+
const parent = typeof raw.parent_sn === "string" && raw.parent_sn ? raw.parent_sn : void 0;
|
|
20035
|
+
if (parent && parent !== sn)
|
|
20036
|
+
return parent;
|
|
20037
|
+
const station = typeof raw.station_sn === "string" && raw.station_sn ? raw.station_sn : void 0;
|
|
20038
|
+
return station ?? sn;
|
|
20039
|
+
}
|
|
20040
|
+
function stationOf(dev) {
|
|
20041
|
+
const raw = dev.raw ?? {};
|
|
20042
|
+
const parent = typeof raw.parent_sn === "string" && raw.parent_sn && raw.parent_sn !== dev.sn ? raw.parent_sn : void 0;
|
|
20043
|
+
return parent ?? dev.stationSn ?? resolvedStationSn(raw, dev.sn);
|
|
20044
|
+
}
|
|
20045
|
+
function statedChannel(dev) {
|
|
20046
|
+
const v = dev.raw?.device_channel;
|
|
20047
|
+
return typeof v === "number" ? v : void 0;
|
|
20048
|
+
}
|
|
20049
|
+
function stationChannels(devices) {
|
|
20050
|
+
const out = /* @__PURE__ */ new Map();
|
|
20051
|
+
const claimants = /* @__PURE__ */ new Map();
|
|
20052
|
+
for (const d of devices) {
|
|
20053
|
+
const station = stationOf(d);
|
|
20054
|
+
const stated = statedChannel(d);
|
|
20055
|
+
if (d.sn === station || stated === void 0)
|
|
20056
|
+
continue;
|
|
20057
|
+
const counts = claimants.get(station) ?? /* @__PURE__ */ new Map();
|
|
20058
|
+
counts.set(stated, (counts.get(stated) ?? 0) + 1);
|
|
20059
|
+
claimants.set(station, counts);
|
|
20060
|
+
}
|
|
20061
|
+
for (const d of devices) {
|
|
20062
|
+
const station = stationOf(d);
|
|
20063
|
+
const stated = statedChannel(d);
|
|
20064
|
+
if (d.sn === station)
|
|
20065
|
+
out.set(d.sn, { channel: stated ?? 0 });
|
|
20066
|
+
else if (stated === void 0)
|
|
20067
|
+
out.set(d.sn, { issue: "missing" });
|
|
20068
|
+
else if ((claimants.get(station)?.get(stated) ?? 0) > 1)
|
|
20069
|
+
out.set(d.sn, { issue: "shared", claimed: stated });
|
|
20070
|
+
else
|
|
20071
|
+
out.set(d.sn, { channel: stated });
|
|
20072
|
+
}
|
|
20073
|
+
return out;
|
|
20074
|
+
}
|
|
20075
|
+
|
|
20019
20076
|
// dist/transport/p2p/command-router.js
|
|
20020
20077
|
var DIRECT_CMD_SENDS = 5;
|
|
20021
20078
|
function abortable(work, signal) {
|
|
@@ -20169,11 +20226,6 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
20169
20226
|
recordFor(sn) {
|
|
20170
20227
|
return this.deps.listDevices().find((d) => d.sn === sn);
|
|
20171
20228
|
}
|
|
20172
|
-
/** The parent-station key a device's session lives under (its HomeBase, or itself if standalone). */
|
|
20173
|
-
stationKeyFor(dev) {
|
|
20174
|
-
const raw = dev.raw ?? {};
|
|
20175
|
-
return raw.parent_sn && raw.parent_sn !== dev.sn ? raw.parent_sn : dev.stationSn ?? dev.sn;
|
|
20176
|
-
}
|
|
20177
20229
|
/**
|
|
20178
20230
|
* The parent-station serial a device serial's session lives under — the single source of truth for
|
|
20179
20231
|
* session keying, used by the facade (e.g. to pre-warm the right station for an event). Returns the
|
|
@@ -20181,14 +20233,14 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
20181
20233
|
*/
|
|
20182
20234
|
stationKeyOf(sn) {
|
|
20183
20235
|
const dev = this.recordFor(sn);
|
|
20184
|
-
return dev ?
|
|
20236
|
+
return dev ? stationOf(dev) : sn;
|
|
20185
20237
|
}
|
|
20186
20238
|
/** Reset only a standalone device's session; an attached device must not close its shared HomeBase. */
|
|
20187
20239
|
async resetStandaloneSession(sn) {
|
|
20188
20240
|
const device = this.recordFor(sn);
|
|
20189
20241
|
if (!device)
|
|
20190
20242
|
return;
|
|
20191
|
-
const station =
|
|
20243
|
+
const station = stationOf(device);
|
|
20192
20244
|
if (station === sn)
|
|
20193
20245
|
await this.manager.resetWhenUnused(station);
|
|
20194
20246
|
}
|
|
@@ -20400,7 +20452,7 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
20400
20452
|
const dev = this.recordFor(sn);
|
|
20401
20453
|
if (!dev)
|
|
20402
20454
|
throw new Error(`device ${sn} not found`);
|
|
20403
|
-
await this.openSession(
|
|
20455
|
+
await this.openSession(stationOf(dev), stationOf(dev));
|
|
20404
20456
|
return dev;
|
|
20405
20457
|
}
|
|
20406
20458
|
/**
|
|
@@ -21188,8 +21240,8 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21188
21240
|
async resolveSession(sn, opts = {}, rebuilt = false) {
|
|
21189
21241
|
const dev = await this.deviceFor(sn);
|
|
21190
21242
|
const raw = dev.raw ?? {};
|
|
21191
|
-
const
|
|
21192
|
-
const
|
|
21243
|
+
const parentSn = stationOf(dev);
|
|
21244
|
+
const homeBaseAttached = parentSn !== sn;
|
|
21193
21245
|
const session = this.manager.get(parentSn) ?? this.manager.get(sn) ?? (dev.stationSn ? this.manager.get(dev.stationSn) : void 0);
|
|
21194
21246
|
if (!session) {
|
|
21195
21247
|
const stations = this.manager.keys().filter((k) => !_P2PCommandRouter.isMediaSessionKey(k));
|
|
@@ -21200,8 +21252,13 @@ var P2PCommandRouter = class _P2PCommandRouter {
|
|
|
21200
21252
|
await this.manager.close(parentSn).catch((error) => this.reportError(error instanceof Error ? error : new Error(String(error))));
|
|
21201
21253
|
return await this.resolveSession(sn, opts, true);
|
|
21202
21254
|
}
|
|
21255
|
+
const address = stationChannels(this.deps.listDevices()).get(sn);
|
|
21256
|
+
if (!("channel" in address)) {
|
|
21257
|
+
this.traceOnStation(session, { phase: "station-channel-unresolved", issue: address.issue });
|
|
21258
|
+
throw new DeviceChannelUnresolvedError(sn, parentSn);
|
|
21259
|
+
}
|
|
21203
21260
|
this.manager.bumpCommand(parentSn, parentSn);
|
|
21204
|
-
const channel =
|
|
21261
|
+
const { channel } = address;
|
|
21205
21262
|
const stationAdminId = raw.member?.admin_user_id;
|
|
21206
21263
|
const stationModel = this.recordFor(parentSn)?.model;
|
|
21207
21264
|
const accountId = stationAdminId ?? this.deps.mega.auth?.userId ?? "";
|
|
@@ -24149,13 +24206,6 @@ function mediaFailureTag(error) {
|
|
|
24149
24206
|
}
|
|
24150
24207
|
|
|
24151
24208
|
// dist/client/device-registry.js
|
|
24152
|
-
function resolvedStationSn(raw, sn) {
|
|
24153
|
-
const parent = typeof raw.parent_sn === "string" && raw.parent_sn ? raw.parent_sn : void 0;
|
|
24154
|
-
if (parent && parent !== sn)
|
|
24155
|
-
return parent;
|
|
24156
|
-
const station = typeof raw.station_sn === "string" && raw.station_sn ? raw.station_sn : void 0;
|
|
24157
|
-
return station ?? sn;
|
|
24158
|
-
}
|
|
24159
24209
|
function mergeParams(raw, params, paramUpdatedAt) {
|
|
24160
24210
|
for (const p of raw ?? []) {
|
|
24161
24211
|
if (!p || p.param_type == null || p.param_value == null)
|
|
@@ -24197,6 +24247,9 @@ var DeviceRegistry = class {
|
|
|
24197
24247
|
devices = [];
|
|
24198
24248
|
/** Per-(station, channel) capability cache for {@link capabilitiesForFrame}; `null` = negative hit. */
|
|
24199
24249
|
frameCapsCache = /* @__PURE__ */ new Map();
|
|
24250
|
+
/** {@link stationChannels} of {@link channelMapFor}, the roster it was computed over. */
|
|
24251
|
+
channelMap = /* @__PURE__ */ new Map();
|
|
24252
|
+
channelMapFor;
|
|
24200
24253
|
/**
|
|
24201
24254
|
* Serials whose per-device param overlay has been refused. The call is owner-gated, so on a shared or
|
|
24202
24255
|
* member account it fails for the whole life of the client — retrying it every refresh spends a request
|
|
@@ -24464,7 +24517,7 @@ var DeviceRegistry = class {
|
|
|
24464
24517
|
}
|
|
24465
24518
|
const raw = dev.raw;
|
|
24466
24519
|
const deviceType = typeof raw?.["device_type"] === "number" ? raw["device_type"] : void 0;
|
|
24467
|
-
const station =
|
|
24520
|
+
const station = stationOf(dev);
|
|
24468
24521
|
return {
|
|
24469
24522
|
deviceType,
|
|
24470
24523
|
model: dev.model,
|
|
@@ -24612,43 +24665,47 @@ var DeviceRegistry = class {
|
|
|
24612
24665
|
serialForFrame(stationSn, channel) {
|
|
24613
24666
|
return this.deviceForFrame(stationSn, channel)?.sn;
|
|
24614
24667
|
}
|
|
24615
|
-
/**
|
|
24616
|
-
* The parent station a device's frames arrive under.
|
|
24617
|
-
*
|
|
24618
|
-
* `parent_sn` on the cloud record is the field that is actually populated for a HomeBase-attached
|
|
24619
|
-
* device — `stationSn` is frequently absent (observed empty on every attached sensor of a T8010),
|
|
24620
|
-
* so keying on it alone silently resolves an attached device to ITSELF and no frame ever matches.
|
|
24621
|
-
* Mirrors the router's own session-keying precedence, which is the source of truth for which
|
|
24622
|
-
* station a device's traffic belongs to. Answering the device's OWN serial is what "stands alone"
|
|
24623
|
-
* means, so this is also the topology signal `record()`/`capsOf` hand the resolver.
|
|
24624
|
-
*/
|
|
24625
|
-
stationOf(dev) {
|
|
24626
|
-
return dev.stationSn ?? resolvedStationSn(dev.raw ?? {}, dev.sn);
|
|
24627
|
-
}
|
|
24628
24668
|
/**
|
|
24629
24669
|
* The device a `(station, channel)` pair refers to — a station fans out to attached devices by
|
|
24630
24670
|
* `device_channel`, while a standalone device is its own station at channel 0.
|
|
24631
24671
|
*
|
|
24632
|
-
* A device claims a channel only when its record actually STATES one
|
|
24633
|
-
*
|
|
24634
|
-
*
|
|
24635
|
-
*
|
|
24636
|
-
* decides where realtime state is written, not just which decoders may run, so an ambiguous answer
|
|
24672
|
+
* A device claims a channel only when its record actually STATES one that no other device attached to
|
|
24673
|
+
* the same station also states ({@link stationChannels}). Treating a missing `device_channel` as 0, or
|
|
24674
|
+
* letting two claimants of one channel both hold it, leaves the winner to cloud list order — which a
|
|
24675
|
+
* partial refresh reorders — so the same frame resolves to different devices across refreshes. The resolved
|
|
24676
|
+
* serial decides where realtime state is written, not just which decoders may run, so an ambiguous answer
|
|
24637
24677
|
* writes one device's params onto another.
|
|
24638
24678
|
*
|
|
24639
24679
|
* An attached device that names the channel wins over the station itself, which is what a station
|
|
24640
|
-
* fanning traffic out by channel means
|
|
24641
|
-
*
|
|
24680
|
+
* fanning traffic out by channel means. A channel two attached devices both state belongs to one of them,
|
|
24681
|
+
* which cannot be told apart, so it answers nothing rather than the station. The station answers for
|
|
24682
|
+
* channel 0 only when nothing attached states it, which is also the standalone case (a device is its own
|
|
24683
|
+
* station).
|
|
24642
24684
|
*/
|
|
24643
24685
|
deviceForFrame(stationSn, channel) {
|
|
24644
|
-
const
|
|
24645
|
-
|
|
24646
|
-
|
|
24647
|
-
|
|
24648
|
-
|
|
24649
|
-
|
|
24686
|
+
const channels = this.stationChannelMap();
|
|
24687
|
+
let claimed = false;
|
|
24688
|
+
for (const d of this.devices) {
|
|
24689
|
+
if (d.sn === stationSn || stationOf(d) !== stationSn)
|
|
24690
|
+
continue;
|
|
24691
|
+
const c = channels.get(d.sn);
|
|
24692
|
+
if (c && "channel" in c && c.channel === channel)
|
|
24693
|
+
return d;
|
|
24694
|
+
if (c && "claimed" in c && c.claimed === channel)
|
|
24695
|
+
claimed = true;
|
|
24696
|
+
}
|
|
24697
|
+
if (claimed)
|
|
24698
|
+
return void 0;
|
|
24650
24699
|
return channel === 0 ? this.devices.find((d) => d.sn === stationSn) : void 0;
|
|
24651
24700
|
}
|
|
24701
|
+
/** {@link stationChannels} over the current roster, recomputed only when the roster itself is replaced. */
|
|
24702
|
+
stationChannelMap() {
|
|
24703
|
+
if (this.channelMapFor !== this.devices) {
|
|
24704
|
+
this.channelMap = stationChannels(this.devices);
|
|
24705
|
+
this.channelMapFor = this.devices;
|
|
24706
|
+
}
|
|
24707
|
+
return this.channelMap;
|
|
24708
|
+
}
|
|
24652
24709
|
/**
|
|
24653
24710
|
* Resolve the capability set of the device a P2P frame belongs to — the `(station, channel)` pair
|
|
24654
24711
|
* (a station fans out to attached devices by `device_channel`; a standalone device is its own
|
|
@@ -24697,7 +24754,7 @@ var DeviceRegistry = class {
|
|
|
24697
24754
|
/** Resolve a record's capabilities the way `getDevice` does, so gating matches `device.has()`. */
|
|
24698
24755
|
capsOf(dev) {
|
|
24699
24756
|
const raw = dev.raw ?? {};
|
|
24700
|
-
const station =
|
|
24757
|
+
const station = stationOf(dev);
|
|
24701
24758
|
return new Set(resolveDevice({
|
|
24702
24759
|
deviceType: raw.device_type,
|
|
24703
24760
|
model: dev.model,
|
|
@@ -27766,6 +27823,7 @@ export {
|
|
|
27766
27823
|
DOCK_KINDS,
|
|
27767
27824
|
DOORBELL_MEMBERS,
|
|
27768
27825
|
Device,
|
|
27826
|
+
DeviceChannelUnresolvedError,
|
|
27769
27827
|
DoorbellPushEvent,
|
|
27770
27828
|
DoorbellRingtone,
|
|
27771
27829
|
EMPTY_CLEAN_RECORD_PAGE,
|