@mega-yfue/eufy-sdk 0.0.4 → 0.1.0-beta.0
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/README.md +31 -12
- package/dist/client/device-registry.d.ts +343 -0
- package/dist/client/eufy-mega.d.ts +872 -0
- package/dist/client/index.d.ts +1 -6
- package/dist/client/map-channels.d.ts +22 -0
- package/dist/client/types.d.ts +383 -0
- package/dist/core/contracts.d.ts +898 -0
- package/dist/core/crypto.d.ts +98 -0
- package/dist/core/index.d.ts +9 -7
- package/dist/core/logger.d.ts +53 -0
- package/dist/core/lz4-block.d.ts +35 -0
- package/dist/core/raw-dp-hex.d.ts +32 -0
- package/dist/core/raw-dp-writer.d.ts +83 -0
- package/dist/core/store.d.ts +43 -0
- package/dist/core/types.d.ts +169 -0
- package/dist/core/util.d.ts +78 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.js +25526 -14
- package/dist/index.js.map +7 -1
- package/dist/model/capabilities/access.d.ts +127 -0
- package/dist/model/capabilities/arming.d.ts +201 -0
- package/dist/model/capabilities/audio.d.ts +154 -0
- package/dist/model/capabilities/battery.d.ts +363 -0
- package/dist/model/capabilities/camera.d.ts +564 -0
- package/dist/model/capabilities/co.d.ts +40 -0
- package/dist/model/capabilities/contact.d.ts +133 -0
- package/dist/model/capabilities/doorbell.d.ts +345 -0
- package/dist/model/capabilities/dp-catalog.d.ts +38 -0
- package/dist/model/capabilities/index.d.ts +561 -0
- package/dist/model/capabilities/info.d.ts +28 -0
- package/dist/model/capabilities/keypad.d.ts +61 -0
- package/dist/model/capabilities/leak.d.ts +43 -0
- package/dist/model/capabilities/light.d.ts +174 -0
- package/dist/model/capabilities/locate.d.ts +63 -0
- package/dist/model/capabilities/lock.d.ts +242 -0
- package/dist/model/capabilities/manifest.d.ts +107 -0
- package/dist/model/capabilities/members.d.ts +647 -0
- package/dist/model/capabilities/motion.d.ts +377 -0
- package/dist/model/capabilities/person-detection.d.ts +8 -0
- package/dist/model/capabilities/ptz.d.ts +289 -0
- package/dist/model/capabilities/rtsp.d.ts +221 -0
- package/dist/model/capabilities/siren.d.ts +218 -0
- package/dist/model/capabilities/smart-light.d.ts +172 -0
- package/dist/model/capabilities/smoke.d.ts +40 -0
- package/dist/model/capabilities/snapshot.d.ts +6 -0
- package/dist/model/capabilities/storage.d.ts +11 -0
- package/dist/model/capabilities/suction.d.ts +104 -0
- package/dist/model/capabilities/types.d.ts +484 -0
- package/dist/model/capabilities/vacuum-clean.d.ts +1946 -0
- package/dist/model/capabilities/vacuum-dock.d.ts +208 -0
- package/dist/model/capabilities/video.d.ts +6 -0
- package/dist/model/classify.d.ts +78 -0
- package/dist/model/clean-record-detail.d.ts +65 -0
- package/dist/model/clean-records.d.ts +69 -0
- package/dist/model/device-family.d.ts +73 -0
- package/dist/model/device-types.d.ts +123 -0
- package/dist/model/device.d.ts +265 -0
- package/dist/model/index.d.ts +29 -4
- package/dist/model/infer.d.ts +23 -0
- package/dist/model/inspect.d.ts +61 -0
- package/dist/model/life-params.d.ts +21 -0
- package/dist/model/map-pixels.d.ts +70 -0
- package/dist/model/param-dictionary.d.ts +28 -0
- package/dist/model/param-namespace.d.ts +21 -0
- package/dist/model/proto-read.d.ts +53 -0
- package/dist/model/push-events.d.ts +147 -0
- package/dist/model/registry.d.ts +54 -0
- package/dist/model/types.d.ts +301 -0
- package/dist/model/vacuum-map-store.d.ts +92 -0
- package/dist/model/vacuum-map.d.ts +286 -0
- package/dist/model/vacuum-scenes.d.ts +76 -0
- package/dist/model/vacuum-schedules.d.ts +85 -0
- package/dist/transport/dp-preset.d.ts +102 -0
- package/dist/transport/ff09.d.ts +444 -0
- package/dist/transport/ffmpeg.d.ts +86 -0
- package/dist/transport/http/decodeImageV1.d.ts +20 -0
- package/dist/transport/http/decodeImageV2.d.ts +19 -0
- package/dist/transport/http/index.d.ts +5 -0
- package/dist/transport/http/light-catalog.d.ts +62 -0
- package/dist/transport/http/media-download.d.ts +14 -0
- package/dist/transport/http/mega-client.d.ts +514 -0
- package/dist/transport/http/phone-model.d.ts +21 -0
- package/dist/transport/index.d.ts +10 -7
- package/dist/transport/mqtt/app-client-id.d.ts +16 -0
- package/dist/transport/mqtt/availability.d.ts +14 -0
- package/dist/transport/mqtt/bare-ip-tls.d.ts +46 -0
- package/dist/transport/mqtt/biz-stream.d.ts +98 -0
- package/dist/transport/mqtt/broker-discovery.d.ts +55 -0
- package/dist/transport/mqtt/clean-codec.d.ts +14 -0
- package/dist/transport/mqtt/command-router.d.ts +285 -0
- package/dist/transport/mqtt/dp-codec.d.ts +58 -0
- package/dist/transport/mqtt/dp-color.d.ts +14 -0
- package/dist/transport/mqtt/engine.d.ts +16 -0
- package/dist/transport/mqtt/index.d.ts +5 -0
- package/dist/transport/mqtt/secure-mqtt.d.ts +107 -0
- package/dist/transport/mqtt/topics.d.ts +80 -0
- package/dist/transport/p2p/adts.d.ts +91 -0
- package/dist/transport/p2p/annexb.d.ts +124 -0
- package/dist/transport/p2p/codec.d.ts +166 -0
- package/dist/transport/p2p/command-router.d.ts +655 -0
- package/dist/transport/p2p/commands.d.ts +550 -0
- package/dist/transport/p2p/envelope.d.ts +46 -0
- package/dist/transport/p2p/fmp4.d.ts +89 -0
- package/dist/transport/p2p/fragment-recording.d.ts +33 -0
- package/dist/transport/p2p/index.d.ts +13 -0
- package/dist/transport/p2p/lan-ip.d.ts +25 -0
- package/dist/transport/p2p/live-stream.d.ts +223 -0
- package/dist/transport/p2p/live-trace.d.ts +123 -0
- package/dist/transport/p2p/media.d.ts +105 -0
- package/dist/transport/p2p/p2p-session.d.ts +620 -0
- package/dist/transport/p2p/readable-egress.d.ts +27 -0
- package/dist/transport/p2p/session-manager.d.ts +154 -0
- package/dist/transport/p2p/shared-live-source.d.ts +431 -0
- package/dist/transport/p2p/talkback.d.ts +187 -0
- package/dist/transport/p2p/video.d.ts +150 -0
- package/dist/transport/p2p/write-commands.d.ts +21 -0
- package/dist/transport/protobuf.d.ts +5 -0
- package/dist/transport/push/fcm.d.ts +23 -0
- package/dist/transport/push/index.d.ts +6 -0
- package/dist/transport/push/message-tags.d.ts +26 -0
- package/dist/transport/push/parser.d.ts +27 -0
- package/dist/transport/push/proto.d.ts +11 -0
- package/dist/transport/push/push-client.d.ts +64 -0
- package/dist/transport/push/store.d.ts +23 -0
- package/dist/transport/push/types.d.ts +180 -0
- package/dist/transport/raw-dp.d.ts +6 -0
- package/dist/transport/stored-image-cache.d.ts +23 -0
- package/dist/transport/tuya/account.d.ts +44 -0
- package/dist/transport/tuya/client.d.ts +89 -0
- package/dist/transport/tuya/command-router.d.ts +79 -0
- package/dist/transport/tuya/dp-codec.d.ts +56 -0
- package/dist/transport/tuya/index.d.ts +28 -0
- package/dist/transport/tuya/request.d.ts +157 -0
- package/dist/transport/tuya/sign.d.ts +64 -0
- package/package.json +12 -13
- package/dist/client/index.js +0 -2
- package/dist/client/index.js.map +0 -1
- package/dist/core/index.js +0 -2
- package/dist/core/index.js.map +0 -1
- package/dist/model/index.js +0 -2
- package/dist/model/index.js.map +0 -1
- package/dist/transport/index.js +0 -2
- package/dist/transport/index.js.map +0 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import type { DpCatalog } from "./dp-catalog.js";
|
|
2
|
+
/**
|
|
3
|
+
* Capability-module contract — the extended shape each `capabilities/<cap>.ts` file implements.
|
|
4
|
+
*
|
|
5
|
+
* A capability module is **self-contained**: it owns everything about one feature —
|
|
6
|
+
* - its **property schema** (the properties it contributes),
|
|
7
|
+
* - how the device is **detected** as having the capability ({@link DetectionSpec}),
|
|
8
|
+
* - how to **decode inbound frames** it cares about ({@link CapabilityModule.decodeFrame}),
|
|
9
|
+
* - how to **build outbound commands** ({@link CapabilityModule.buildCommand}).
|
|
10
|
+
*
|
|
11
|
+
* The barrel (`capabilities/index.ts`) collects the modules and exposes fleet-wide helpers
|
|
12
|
+
* (`detectCapabilities`, `decodeFrame`, `buildCommand`). Deleting a module file = removing one
|
|
13
|
+
* import line from the barrel; nothing else needs editing.
|
|
14
|
+
*
|
|
15
|
+
* @module model/capabilities/types
|
|
16
|
+
*/
|
|
17
|
+
import type { Capability, Codec, CloudRecord, PropertySpec, PropertyValue, ValueKind } from "../types.js";
|
|
18
|
+
import type { Members, MemberDeps } from "./members.js";
|
|
19
|
+
import type { Command, DpInboundFrame } from "../../core/contracts.js";
|
|
20
|
+
/**
|
|
21
|
+
* How a capability is discovered on a device. All fields are additive OR-ed evidence — a device
|
|
22
|
+
* has the capability if ANY field matches. Mechanisms, most-to-least dynamic:
|
|
23
|
+
* - `evidenceParams` — a reported `param_type` whose PRESENCE proves the capability (the device
|
|
24
|
+
* self-reports it when its info is pulled over P2P). Namespace-agnostic: security param ids and
|
|
25
|
+
* vacuum Tuya-DP ids are declared the same way.
|
|
26
|
+
* - `deviceTypes` — vendor `DeviceType` numbers that guarantee the capability (static vendor
|
|
27
|
+
* table, e.g. Indoor-PT = 31/35/111). Used when there is no honest self-reported signal.
|
|
28
|
+
* - `modelHints` — regexes over the model / category / name strings.
|
|
29
|
+
* - `codecs` — the codec baseline already implies it (e.g. any `camera` has video).
|
|
30
|
+
* - `detect` — escape hatch for logic that doesn't fit the declarative fields. Must never throw.
|
|
31
|
+
*/
|
|
32
|
+
export interface DetectionSpec {
|
|
33
|
+
evidenceParams?: number[];
|
|
34
|
+
deviceTypes?: number[];
|
|
35
|
+
modelHints?: RegExp[];
|
|
36
|
+
codecs?: Codec[];
|
|
37
|
+
detect?(rec: CloudRecord, codec: Codec): boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Which product line a capability or a codec belongs to.
|
|
41
|
+
*
|
|
42
|
+
* eufy ships several ecosystems that share a cloud account and nothing else: `security` (cameras,
|
|
43
|
+
* stations, locks, sensors — P2P plus the security-scoped broker), `life` (the T8L0x smart-lighting
|
|
44
|
+
* line — its own credential and its own DP wire), and `clean` (robot vacuums — Tuya data points).
|
|
45
|
+
* They overlap in retail vocabulary but share no wire, no param space and no semantics.
|
|
46
|
+
*
|
|
47
|
+
* `any` is for the handful of capabilities that are genuinely line-independent (device identity).
|
|
48
|
+
*/
|
|
49
|
+
export type ProductLine = "security" | "life" | "clean" | "print" | "any";
|
|
50
|
+
/**
|
|
51
|
+
* A structural subset of a P2P frame. Deliberately NOT `import`ed from `p2p/*` — keeping it
|
|
52
|
+
* structural avoids a model→p2p cycle, and the real `P2PFrame` is assignable to it. It is the
|
|
53
|
+
* `p2p-frame` shape of {@link InboundSignal}.
|
|
54
|
+
*/
|
|
55
|
+
export interface CapabilityFrame {
|
|
56
|
+
stationSn: string;
|
|
57
|
+
commandId: number;
|
|
58
|
+
channel: number;
|
|
59
|
+
data?: Buffer;
|
|
60
|
+
json?: {
|
|
61
|
+
cmd?: number;
|
|
62
|
+
payload?: unknown;
|
|
63
|
+
} & Record<string, unknown>;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A transport-neutral **inbound signal** — the dual of {@link Command}. Device events reach the
|
|
67
|
+
* SDK from three sources; a capability's {@link CapabilityModule.decodeEvent} normalizes ANY of
|
|
68
|
+
* them into one semantic {@link CapabilityEvent} (`motion`, `doorbellPress`, `lockState`,
|
|
69
|
+
* `ptzNotify`, …), so the consumer never has to know which transport delivered it:
|
|
70
|
+
* - `push` — an FCM notification, already normalized: a numeric `eventType` + optional thumbnail.
|
|
71
|
+
* - `p2p-frame` — a live P2P frame ({@link CapabilityFrame}).
|
|
72
|
+
* - `poll` — a cloud param that changed between polls (`paramType` from→to).
|
|
73
|
+
*/
|
|
74
|
+
export type InboundSignal = {
|
|
75
|
+
source: "push";
|
|
76
|
+
eventType?: number;
|
|
77
|
+
eventName?: string;
|
|
78
|
+
deviceSn?: string;
|
|
79
|
+
stationSn?: string;
|
|
80
|
+
thumbnailUrl?: string;
|
|
81
|
+
payload: Record<string, unknown>;
|
|
82
|
+
} | ({
|
|
83
|
+
source: "p2p-frame";
|
|
84
|
+
} & CapabilityFrame) | {
|
|
85
|
+
source: "poll";
|
|
86
|
+
deviceSn: string;
|
|
87
|
+
paramType: number;
|
|
88
|
+
from?: string;
|
|
89
|
+
to?: string;
|
|
90
|
+
params: Record<number, string>;
|
|
91
|
+
} | {
|
|
92
|
+
source: "mqtt";
|
|
93
|
+
deviceSn?: string;
|
|
94
|
+
topic?: string;
|
|
95
|
+
raw: unknown;
|
|
96
|
+
frame?: DpInboundFrame;
|
|
97
|
+
dpParams?: Record<number, string>;
|
|
98
|
+
};
|
|
99
|
+
interface EventRefresh {
|
|
100
|
+
member: string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A **declarative** inbound-event mapping — the dual of {@link DetectionSpec} for events. A
|
|
104
|
+
* capability lists which push `eventType`s / poll `paramType`s belong to it and the semantic event
|
|
105
|
+
* name each emits. The barrel folds all modules' mappings into one lookup index (built once), so
|
|
106
|
+
* dispatch is a direct id→event lookup — no per-module decode code for the common case.
|
|
107
|
+
*/
|
|
108
|
+
export interface EventMapping {
|
|
109
|
+
/** Which source this id comes from. (p2p-frame decoding uses {@link CapabilityModule.decodeEvent}.) */
|
|
110
|
+
source: "push" | "poll";
|
|
111
|
+
/** An exact id, or an inclusive `[lo, hi]` range (e.g. lock push events 257..771). */
|
|
112
|
+
match: number | [number, number];
|
|
113
|
+
/** The semantic SDK event name to emit (e.g. `"motion"`, `"doorbellPress"`, `"lockState"`). */
|
|
114
|
+
emit: string;
|
|
115
|
+
/** @internal Refresh one reflected member before emitting a valueless transition. */
|
|
116
|
+
refresh?: EventRefresh;
|
|
117
|
+
/**
|
|
118
|
+
* Static fields folded into the emitted event payload — lets several ids emit the same event
|
|
119
|
+
* name with a discriminator (e.g. battery pushes 6/7/11 all → `batteryAlert` with
|
|
120
|
+
* `{state:"low"|"hot"|"full"}`). Merged OVER the signal's own fields, so a raw wire key can't
|
|
121
|
+
* overwrite a discriminator.
|
|
122
|
+
*/
|
|
123
|
+
payload?: Record<string, unknown>;
|
|
124
|
+
/**
|
|
125
|
+
* Fields DERIVED from the signal — for state a push carries under an opaque single-letter wire key.
|
|
126
|
+
* Merged last, over both the raw body and {@link payload}.
|
|
127
|
+
*
|
|
128
|
+
* Same evidence bar as everything else: only map a key whose meaning is confirmed in the V6 app or a
|
|
129
|
+
* capture. Return `{}` when this signal doesn't carry the field, so nothing is invented.
|
|
130
|
+
*/
|
|
131
|
+
derive?(signal: InboundSignal): Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A decoded inbound event a capability wants surfaced on the SDK. `event` is the EufyMega event
|
|
135
|
+
* name (e.g. `"ptzNotify"`); `payload` is spread into the emitted object after `stationSn`.
|
|
136
|
+
*/
|
|
137
|
+
export interface CapabilityEvent {
|
|
138
|
+
event: string;
|
|
139
|
+
payload: Record<string, unknown>;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Device state a capability recovered from an inbound realtime signal, expressed as `paramType →
|
|
143
|
+
* value` in the device's own param namespace — the same shape the cloud record reports, so it flows
|
|
144
|
+
* through `Device.applyParams` and reaches the typed {@link CapabilityModule.members} getters unchanged.
|
|
145
|
+
*
|
|
146
|
+
* Attribution stays with the signal: the transport already resolves which device a message came from,
|
|
147
|
+
* and a capability re-deriving it from the payload would be a second, disagreeable source of truth.
|
|
148
|
+
*/
|
|
149
|
+
export interface DecodedState {
|
|
150
|
+
params: Record<number, string>;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Device context handed to {@link CapabilityModule.buildCommand} / {@link CapabilityModule.actions}
|
|
154
|
+
* so a capability can resolve the right command **variant** for THIS device — the same evidence
|
|
155
|
+
* `detection` keys on. Namespace-agnostic: `paramIds` holds security param ids OR vacuum Tuya DPs.
|
|
156
|
+
*/
|
|
157
|
+
/**
|
|
158
|
+
* The device facts an availability / per-model gate reads. A truthful subset a {@link CloudRecord}
|
|
159
|
+
* can populate at resolve time — before a live session exists — without fabricating the transport
|
|
160
|
+
* fields ({@link CommandContext.channel}, {@link CommandContext.paramIds}) a real command carries.
|
|
161
|
+
* Every {@link CommandContext} is one structurally, so a gate written against this runs unchanged on
|
|
162
|
+
* the manifest path and the command path.
|
|
163
|
+
*/
|
|
164
|
+
export interface AvailabilityContext {
|
|
165
|
+
/** Resolved codec/family. */
|
|
166
|
+
codec: Codec;
|
|
167
|
+
/** eufy DeviceType, when known. */
|
|
168
|
+
deviceType?: number;
|
|
169
|
+
/** Model / T-code, when known. */
|
|
170
|
+
model?: string;
|
|
171
|
+
/** API category string, when known. */
|
|
172
|
+
category?: string;
|
|
173
|
+
/** The device's resolved capability set, when known. */
|
|
174
|
+
capabilities?: ReadonlySet<Capability>;
|
|
175
|
+
/**
|
|
176
|
+
* Whether the device is reachable over P2P — a live-transport fact, so it is absent on the pure
|
|
177
|
+
* resolve-time (manifest) path and present only when a command context is built. Availability gates
|
|
178
|
+
* that read it (a lock's P2P-only writes) are all `writeOnly`, which the manifest never lists, so
|
|
179
|
+
* its absence there changes nothing.
|
|
180
|
+
*/
|
|
181
|
+
hasP2p?: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* The param_type / DP ids this device has actually reported. Present at bind time (a real
|
|
184
|
+
* `CommandContext`); absent on the manifest path. DP-based availability gates should treat
|
|
185
|
+
* `undefined` as an empty set — `ctx.paramIds?.has(dp) ?? false`.
|
|
186
|
+
*/
|
|
187
|
+
paramIds?: ReadonlySet<number>;
|
|
188
|
+
}
|
|
189
|
+
export interface CommandContext extends AvailabilityContext {
|
|
190
|
+
/** Device channel (0 for standalone, `device_channel` on a HomeBase). */
|
|
191
|
+
channel: number;
|
|
192
|
+
/** eufy DeviceType, when known. */
|
|
193
|
+
deviceType?: number;
|
|
194
|
+
/** Model / T-code, when known. */
|
|
195
|
+
model?: string;
|
|
196
|
+
/**
|
|
197
|
+
* API category string — e.g. `"eufy_home"`, `"eufy_home_tuya"`, `"eufy_security"`. Primary
|
|
198
|
+
* transport discriminator for the clean line: `"eufy_home_tuya"` = ThingClips/Tuya Cloud, not
|
|
199
|
+
* Anker AIoT MQTT. Absent in unit-test contexts that build a minimal context without a real API.
|
|
200
|
+
*/
|
|
201
|
+
category?: string;
|
|
202
|
+
/** Full device serial number, when known. */
|
|
203
|
+
serial?: string;
|
|
204
|
+
/** Display name, when known — sourced from the device record by the facade (for `info`). */
|
|
205
|
+
name?: string;
|
|
206
|
+
/**
|
|
207
|
+
* Firmware (main software) version — the device record's `main_sw_version`, the same field the v6
|
|
208
|
+
* app maps to its `firmware_main_version` label. `undefined` when the record doesn't carry it.
|
|
209
|
+
*/
|
|
210
|
+
firmwareVersion?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Hardware version — the device record's `main_hw_version` (app label `hardware_version`).
|
|
213
|
+
* `undefined` when the record doesn't carry it.
|
|
214
|
+
*/
|
|
215
|
+
hardwareVersion?: string;
|
|
216
|
+
/** Secondary/sub firmware version — the record's `sec_sw_version` (app label `firmware_sub_version`). */
|
|
217
|
+
firmwareSubVersion?: string;
|
|
218
|
+
/** Wi-Fi MAC address — the record's `wifi_mac` (app label `mac_address`). */
|
|
219
|
+
macAddress?: string;
|
|
220
|
+
/** Firmware-update-available flag — the record's `needUpdate`. */
|
|
221
|
+
updateAvailable?: boolean;
|
|
222
|
+
/** The param_type / DP ids this device has actually reported (evidence for variant selection). */
|
|
223
|
+
paramIds: ReadonlySet<number>;
|
|
224
|
+
/**
|
|
225
|
+
* The capability set the device was RESOLVED to have (from `resolveDevice`: curated row +
|
|
226
|
+
* codec baseline + per-module detection over the full, fresh record). `buildCommand` gates on
|
|
227
|
+
* this so the authorization matches exactly what `device.has(cap)` / `buildActions` saw — never a
|
|
228
|
+
* weaker re-detection from a partial context. Omit only in unit tests that pass evidence directly.
|
|
229
|
+
*/
|
|
230
|
+
capabilities?: ReadonlySet<Capability>;
|
|
231
|
+
/**
|
|
232
|
+
* The lock owner's account id — the identity a lock command is authenticated against. Present on
|
|
233
|
+
* lock-family devices; absent elsewhere.
|
|
234
|
+
*/
|
|
235
|
+
adminUserId?: string;
|
|
236
|
+
/** The acting member's short id (`member.short_user_id`, hex, e.g. `"0003"`) — the lock cmd `A5` field. */
|
|
237
|
+
shortUserId?: string;
|
|
238
|
+
/** The logged-in account's display name (email local-part) — the lock cmd acting-username `A4` field. */
|
|
239
|
+
accountName?: string;
|
|
240
|
+
/**
|
|
241
|
+
* Whether the device has a usable P2P endpoint (a non-empty `p2p_did`). A HomeBase-attached lock
|
|
242
|
+
* (T8531) is P2P-reachable; a standalone garage/lock (T85D0, `p2p_did:""`) is MQTT-only. The lock
|
|
243
|
+
* capability uses this to route lock/unlock to P2P vs. reject with a clear MQTT-not-wired error.
|
|
244
|
+
*/
|
|
245
|
+
hasP2p?: boolean;
|
|
246
|
+
/**
|
|
247
|
+
* Whether the device hangs off a HomeBase (a `parent_sn` other than its own) rather than standing
|
|
248
|
+
* alone. A DEVICE fact, not a transport one — the same class of routing evidence as {@link hasP2p}.
|
|
249
|
+
* The `rtsp` capability gates on it because a station serves an attached camera's stream itself and
|
|
250
|
+
* ignores that camera's authentication setting, so the write cannot do what its name promises there.
|
|
251
|
+
*/
|
|
252
|
+
homeBaseAttached?: boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Parsed `get_product_data_point` catalog for this device's SKU — present for vacuum/mower devices,
|
|
255
|
+
* absent for all other codecs. Capabilities use it for per-model feature-availability and value-range
|
|
256
|
+
* data (e.g. which suction levels DP 158 admits). Absent means "catalog not fetched" — fall back to
|
|
257
|
+
* static defaults rather than treating the device as incapable.
|
|
258
|
+
*/
|
|
259
|
+
dpCatalog?: DpCatalog;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The **loose base** for a capability's bound action object — a bag of async methods. Each module
|
|
263
|
+
* declares its own precise, JSDoc'd alias (e.g. `PtzActions`, `CameraActions`) and returns THAT
|
|
264
|
+
* from `actions()`; those aliases are what the fluent `dev.<cap>()` accessors expose (assembled into
|
|
265
|
+
* `DeviceActionMap` in the barrel). This base only exists so `CapabilityModule.actions` has a common
|
|
266
|
+
* return type that every concrete alias is assignable to. Control actions resolve to `void` (they
|
|
267
|
+
* dispatch a {@link Command}); media actions resolve to data — a `Promise` of a still/stream/buffer,
|
|
268
|
+
* or a synchronous `AsyncIterable` (continuous fragment recording), so the return type is `any`.
|
|
269
|
+
*
|
|
270
|
+
* This models the common case: a **methods-bag** (control capabilities). A READ-ONLY *data*
|
|
271
|
+
* capability (`info`, returning a plain `DeviceInfo`) is the deliberate exception — it asserts its
|
|
272
|
+
* result to this type locally (see `info.ts`) rather than weaken this base for every module. The
|
|
273
|
+
* consumer-facing types stay precise via `DeviceActionMap` (`ptz: PtzActions`,
|
|
274
|
+
* `info: DeviceInfo`, …), from which the fluent `dev.<cap>()` accessors are derived.
|
|
275
|
+
*/
|
|
276
|
+
export type CapabilityActions = Record<string, (...args: any[]) => any>;
|
|
277
|
+
/**
|
|
278
|
+
* Live-state accessor handed to {@link CapabilityModule.actions} so a capability can expose **typed
|
|
279
|
+
* read getters** on its fluent object (`dev.battery()?.level: number`) alongside its write methods —
|
|
280
|
+
* closing the read/write asymmetry (writes are typed+fluent; a raw `getProperty("battery")?.value` is
|
|
281
|
+
* the loose `ParamValue`). Returns the current property value for a property NAME, or
|
|
282
|
+
* `undefined` if never observed. The facade wires it to the device's live state, so a getter built
|
|
283
|
+
* once stays current as realtime/poll updates land. The `readNum`/`readBool`/`readStr` extractors in
|
|
284
|
+
* `./access.ts` guard the runtime type rather than lie-cast.
|
|
285
|
+
*/
|
|
286
|
+
export type CapabilityStateReader = (name: string) => PropertyValue | undefined;
|
|
287
|
+
/**
|
|
288
|
+
* One argument a described action accepts, in the same value vocabulary the reads use ({@link ValueKind}).
|
|
289
|
+
*
|
|
290
|
+
* A numeric range belongs here as `min`/`max`/`step`, and must be the SAME constant the action clamps
|
|
291
|
+
* with: a retyped range is a drift bug no test can catch, since both copies stay individually valid.
|
|
292
|
+
*
|
|
293
|
+
* `values`/`labels` publish a fixed option set for an argument whose domain the schema cannot otherwise
|
|
294
|
+
* reach. A `kind: "enum"` argument normally omits both: it belongs to a control whose property already
|
|
295
|
+
* publishes its `enumValues`, and declaring the same set twice can only drift from it.
|
|
296
|
+
*/
|
|
297
|
+
export interface ActionArgSpec {
|
|
298
|
+
name: string;
|
|
299
|
+
kind: ValueKind;
|
|
300
|
+
optional?: boolean;
|
|
301
|
+
min?: number;
|
|
302
|
+
max?: number;
|
|
303
|
+
step?: number;
|
|
304
|
+
values?: readonly (string | number)[];
|
|
305
|
+
labels?: Readonly<Record<string, string>>;
|
|
306
|
+
description?: string;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* What one action on `dev.<cap>()` accepts and what it changes — the write-side counterpart to a
|
|
310
|
+
* member's read getter.
|
|
311
|
+
*
|
|
312
|
+
* Carries no name: it is attached to the method itself (`describedAction` in `./access.ts`), so the
|
|
313
|
+
* action's own key is its name and a rename cannot leave a description behind pointing at nothing.
|
|
314
|
+
*
|
|
315
|
+
* **A spec never gates anything.** {@link CapabilityModule.actions} alone decides which methods exist;
|
|
316
|
+
* an undescribed action stays fully callable, it just cannot be offered automatically.
|
|
317
|
+
*
|
|
318
|
+
* **Describe the method that TAKES the value, not its aliases.** `on()`/`off()` drive the same wire as
|
|
319
|
+
* `set(v)`, and describing all three would render three controls for one state.
|
|
320
|
+
*
|
|
321
|
+
* **A stateful action's {@link reflects} read is its evidence gate.** Each described write works exactly
|
|
322
|
+
* on the devices whose reflected read is installed — the read's backing param and the write's own
|
|
323
|
+
* precondition are the same evidence (`motion.setDetection` needs the PIR switch a standalone sensor
|
|
324
|
+
* never reports; `setHumanOnlyAtNight` and `setLoiteringDetection` each need the id their read gates on).
|
|
325
|
+
* So a description is a promise the method works wherever its read answers, and an action whose read is
|
|
326
|
+
* absent on a device is not offerable there.
|
|
327
|
+
*
|
|
328
|
+
* Describe only a wire confirmed on real hardware, for the same reason an unverified write rejects
|
|
329
|
+
* rather than guesses: a described control that always fails turns "a present method means a verified
|
|
330
|
+
* wire" into "a present description means nothing".
|
|
331
|
+
*/
|
|
332
|
+
export interface ActionSpec {
|
|
333
|
+
form: "momentary" | "stateful";
|
|
334
|
+
/** For a stateful action, the read accessor on the same capability whose value it changes. */
|
|
335
|
+
reflects?: string;
|
|
336
|
+
args?: readonly ActionArgSpec[];
|
|
337
|
+
description?: string;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* A capability module: property schema + detection + inbound decode + outbound commands. Written
|
|
341
|
+
* once, reused by every device that lists the capability.
|
|
342
|
+
*/
|
|
343
|
+
export interface CapabilityModule {
|
|
344
|
+
capability: Capability;
|
|
345
|
+
/**
|
|
346
|
+
* Properties this capability contributes.
|
|
347
|
+
*
|
|
348
|
+
* A module with a {@link members} table sets this to `propertiesOf(ITS_MEMBERS)` rather than writing
|
|
349
|
+
* it out: the schema is derived from the same declaration as the getters and setters, and is
|
|
350
|
+
* materialised here so every existing consumer keeps reading it the same way.
|
|
351
|
+
*/
|
|
352
|
+
properties: PropertySpec[];
|
|
353
|
+
/** Human description for docs. */
|
|
354
|
+
description?: string;
|
|
355
|
+
/** How devices are detected as having this capability. Absent = only via codec baseline / registry. */
|
|
356
|
+
detection?: DetectionSpec;
|
|
357
|
+
/**
|
|
358
|
+
* The product line this capability belongs to; defaults to `security` (the bulk of the catalogue).
|
|
359
|
+
*
|
|
360
|
+
* Checked BEFORE any detection evidence, so a capability can never land on a device from another
|
|
361
|
+
* line. This matters because the detection fields are OR-ed and several capabilities are found by
|
|
362
|
+
* NAME alone — eufy's retail vocabulary collides across ecosystems, so a light called "Outdoor
|
|
363
|
+
* Spotlights" or a strip named for a water effect otherwise picks up a camera or leak capability
|
|
364
|
+
* whose wire it does not speak.
|
|
365
|
+
*/
|
|
366
|
+
line?: ProductLine;
|
|
367
|
+
/**
|
|
368
|
+
* The capability belongs to whichever device OWNS the group, so a device that hangs off a parent
|
|
369
|
+
* station must not claim it — even when a curated row grants it or the device mirrors the param.
|
|
370
|
+
*
|
|
371
|
+
* Guard mode is the case that grounds this: a standalone SoloCam/Indoor cam owns its own mode (read
|
|
372
|
+
* live: `armingMode` answers on a standalone T8170/T8171/T8410), but behind a HomeBase the hub owns
|
|
373
|
+
* it and the app shows it there, not per camera — an attached T8170 reports no mode at all. Applied
|
|
374
|
+
* AFTER the three tiers are unioned (see `resolveDevice`), because the point is to withhold a
|
|
375
|
+
* control the device cannot answer for however it was granted.
|
|
376
|
+
*/
|
|
377
|
+
ownedByStation?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Declarative inbound-event mappings (push eventType / poll param → semantic event name). The
|
|
380
|
+
* common case: pure data, no code. The barrel indexes these for direct lookup.
|
|
381
|
+
*/
|
|
382
|
+
events?: EventMapping[];
|
|
383
|
+
/**
|
|
384
|
+
* Semantic event names this capability emits from {@link decodeEvent}, which the flat {@link events}
|
|
385
|
+
* table cannot express — a frame the module parses itself has no id to list there.
|
|
386
|
+
*
|
|
387
|
+
* Declared so the published manifest can state what a capability emits without a caller subscribing
|
|
388
|
+
* blind: the manifest's event list is this union'd with the table's own `emit` names. A capability
|
|
389
|
+
* whose events all come from the table omits it; `decode-event.spec.ts` locks the union against a
|
|
390
|
+
* hardcoded list, since the typed event map erases at build and cannot keep this honest.
|
|
391
|
+
*/
|
|
392
|
+
emits?: readonly string[];
|
|
393
|
+
/**
|
|
394
|
+
* Semantic events that describe a **state**, and the payload field holding it — so a state that
|
|
395
|
+
* several transports report is announced once per real change instead of once per transport.
|
|
396
|
+
*
|
|
397
|
+
* One physical change can reach the SDK on more than one path: an entry sensor's contact arrives
|
|
398
|
+
* as a station notify ~2 s before the same value arrives as an FCM push. Both are real, but they
|
|
399
|
+
* describe one change. Listing the event here makes the emitter edge-triggered on that field: a
|
|
400
|
+
* value equal to the last one announced for that device is suppressed.
|
|
401
|
+
*
|
|
402
|
+
* Declare this ONLY for events carrying a settled state. An event that is a **pulse** — motion,
|
|
403
|
+
* a doorbell press — must be omitted: consecutive pulses are identical by nature and deduping them
|
|
404
|
+
* would drop real detections.
|
|
405
|
+
*
|
|
406
|
+
* Edge-triggering is applied to realtime sources only. A poll still re-announces an unchanged
|
|
407
|
+
* state, so a missed realtime frame is re-synchronised rather than left waiting for the state to
|
|
408
|
+
* change again.
|
|
409
|
+
*/
|
|
410
|
+
stateEvents?: {
|
|
411
|
+
event: string;
|
|
412
|
+
field: string;
|
|
413
|
+
}[];
|
|
414
|
+
/**
|
|
415
|
+
* Escape hatch for inbound signals a flat {@link EventMapping} table can't express — e.g. parsing
|
|
416
|
+
* a binary P2P frame (the pan-tilt position stream). Return the semantic {@link CapabilityEvent}
|
|
417
|
+
* or `null`. Simple capabilities use only `events` and omit this.
|
|
418
|
+
*/
|
|
419
|
+
decodeEvent?(signal: InboundSignal): CapabilityEvent | null;
|
|
420
|
+
/**
|
|
421
|
+
* Recover device STATE from an inbound signal, as wire ids in this device's own param namespace —
|
|
422
|
+
* the input to `Device.applyParams`, so a realtime-only line's values reach the same
|
|
423
|
+
* {@link CapabilityModule.members} getters a pollable cloud param would.
|
|
424
|
+
*
|
|
425
|
+
* Separate from {@link CapabilityModule.decodeEvent} because the two answer different questions: a
|
|
426
|
+
* report that repeats the current state carries no event worth emitting but must still refresh the
|
|
427
|
+
* readable state. A module that decodes both shares one private parser between the hooks.
|
|
428
|
+
*/
|
|
429
|
+
decodeState?(signal: InboundSignal): DecodedState | null;
|
|
430
|
+
/**
|
|
431
|
+
* Commands to send once this device's realtime channel is up — e.g. a state-snapshot request, for a
|
|
432
|
+
* line whose state is pushed on change with no periodic heartbeat, so the typed reads are populated
|
|
433
|
+
* at connect instead of only after the first write. Best-effort: a failure is reported, never fatal.
|
|
434
|
+
*/
|
|
435
|
+
realtimeInit?(ctx: CommandContext): Command[];
|
|
436
|
+
/**
|
|
437
|
+
* Resolve a semantic action into a transport-neutral {@link Command} for THIS device, or
|
|
438
|
+
* `undefined` if this module doesn't handle `(action)`. `action` is a capability-local verb
|
|
439
|
+
* (e.g. `"on"`, `"off"`, `"setBrightness"`, `"rotate"`), NOT a param id. Uses `ctx` to
|
|
440
|
+
* pick the right variant. This is where per-device variance lives — once, in the module.
|
|
441
|
+
*/
|
|
442
|
+
buildCommand?(action: string, value: boolean | number | string, ctx: CommandContext): Command | undefined;
|
|
443
|
+
/**
|
|
444
|
+
* The object of bound action methods exposed on a device that HAS this capability
|
|
445
|
+
* (e.g. `device.light()` → `{on, off, setBrightness}`). Control actions resolve a
|
|
446
|
+
* {@link Command} and emit it through `sink`; media actions (snapshot/live/record) delegate to
|
|
447
|
+
* the optional `media` provider (absent on a model object not bound to a live client).
|
|
448
|
+
*
|
|
449
|
+
* Every injected provider here names a **technical job**, never the capability that happens to be
|
|
450
|
+
* its first caller: `media` is shared by every media-capable device, and `ff09Settings` is named for
|
|
451
|
+
* the frame family it reads, so any device driven by that frame can use it. That is the rule, not a
|
|
452
|
+
* convention — a provider that cannot be named without saying "lock" or "vacuum" is a provider split
|
|
453
|
+
* in the wrong place.
|
|
454
|
+
*
|
|
455
|
+
* `ff09Settings` exists as its own boundary because `GET_SETTINGS` is a request/reply query
|
|
456
|
+
* (decrypt + parse + pick P2P-vs-MQTT), and neither `CommandSink` (write-only, `Promise<void>`) nor
|
|
457
|
+
* `MediaProvider.p2pQuery` (P2P-only, raw passthrough, no decrypt) fits it. A further `ff09` setting
|
|
458
|
+
* needing a live read is one more method on {@link Ff09SettingsReader}; a genuinely different wire
|
|
459
|
+
* family is its own provider, not another parameter bolted on next to this one.
|
|
460
|
+
*/
|
|
461
|
+
actions?(deps: MemberDeps): CapabilityActions;
|
|
462
|
+
/**
|
|
463
|
+
* The capability's surface, one entry per feature — the schema, the getters, the setters, the intent
|
|
464
|
+
* routes and the descriptions all derived from it. See `./members.ts`.
|
|
465
|
+
*
|
|
466
|
+
* A feature is spelled ONCE: {@link properties} is `propertiesOf(X_MEMBERS)`, never a second list.
|
|
467
|
+
*
|
|
468
|
+
* Absent only on a capability with no surface to bind: pure detection (`video`, `snapshot`), or one
|
|
469
|
+
* whose object is a projection rather than device params (`info`).
|
|
470
|
+
*/
|
|
471
|
+
members?: Members;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Thrown when a control is requested on a device that doesn't support it — instead of silently
|
|
475
|
+
* sending a command into the void (the eufy P2P write path is fire-and-forget, so an unsupported
|
|
476
|
+
* command otherwise looks like success). Raised by `setProperty` / action paths when no capability
|
|
477
|
+
* module produces a command for the (device, action).
|
|
478
|
+
*/
|
|
479
|
+
export declare class CapabilityNotSupportedError extends Error {
|
|
480
|
+
readonly sn: string;
|
|
481
|
+
readonly action: string;
|
|
482
|
+
constructor(sn: string, action: string);
|
|
483
|
+
}
|
|
484
|
+
export {};
|