@mega-yfue/eufy-sdk 0.2.0-beta.2 → 0.2.0-beta.21
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/eufy-mega.d.ts +16 -11
- package/dist/client/types.d.ts +6 -2
- package/dist/core/contracts.d.ts +57 -27
- package/dist/core/crypto.d.ts +10 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/logger.d.ts +5 -3
- package/dist/core/solix-types.d.ts +115 -0
- package/dist/core/store.d.ts +38 -10
- package/dist/index.js +2713 -452
- package/dist/index.js.map +4 -4
- package/dist/model/capabilities/access.d.ts +22 -3
- package/dist/model/capabilities/arming.d.ts +33 -27
- package/dist/model/capabilities/battery.d.ts +32 -4
- package/dist/model/capabilities/contact.d.ts +4 -0
- package/dist/model/capabilities/display.d.ts +85 -0
- package/dist/model/capabilities/index.d.ts +18 -3
- package/dist/model/capabilities/ptz.d.ts +6 -2
- package/dist/model/capabilities/solix.d.ts +173 -0
- package/dist/model/capabilities/types.d.ts +21 -5
- package/dist/model/capabilities/vacuum-clean.d.ts +74 -25
- package/dist/model/device.d.ts +15 -0
- package/dist/model/index.d.ts +5 -0
- package/dist/model/param-dictionary.d.ts +24 -0
- package/dist/model/param-namespace.d.ts +1 -1
- package/dist/model/solix-catalog.d.ts +25 -0
- package/dist/model/solix-device.d.ts +136 -0
- package/dist/model/solix-family.d.ts +31 -0
- package/dist/model/solix-site.d.ts +70 -0
- package/dist/model/types.d.ts +5 -5
- package/dist/transport/ff09.d.ts +7 -0
- package/dist/transport/http/decodeImageV2.d.ts +8 -14
- package/dist/transport/http/index.d.ts +1 -0
- package/dist/transport/http/jpeg-scan.d.ts +59 -0
- package/dist/transport/http/media-download.d.ts +3 -0
- package/dist/transport/http/mega-client.d.ts +89 -9
- package/dist/transport/http/solix-client.d.ts +269 -0
- package/dist/transport/http/solix-constants.d.ts +56 -0
- package/dist/transport/media-failure.d.ts +48 -0
- package/dist/transport/mqtt/index.d.ts +2 -0
- package/dist/transport/mqtt/secure-mqtt.d.ts +14 -1
- package/dist/transport/mqtt/solix-mqtt.d.ts +321 -0
- package/dist/transport/mqtt/topics.d.ts +30 -0
- package/dist/transport/p2p/command-router.d.ts +152 -15
- package/dist/transport/p2p/index.d.ts +1 -0
- package/dist/transport/p2p/live-stream.d.ts +5 -4
- package/dist/transport/p2p/live-trace.d.ts +118 -6
- package/dist/transport/p2p/media.d.ts +11 -0
- package/dist/transport/p2p/p2p-session.d.ts +13 -0
- package/dist/transport/p2p/session-manager.d.ts +57 -23
- package/dist/transport/p2p/shared-live-source.d.ts +10 -1
- package/dist/transport/stored-image-cache.d.ts +7 -1
- package/package.json +3 -2
|
@@ -50,9 +50,28 @@ export declare function setJsonRaw(cmd: number, data: Record<string, unknown>, c
|
|
|
50
50
|
* Set a param carried in the `SET_PAYLOAD` (1350) envelope — `{account_id,cmd,mChannel,mValue3:cmd,
|
|
51
51
|
* payload}`, GCM signCode 8 — NOT the bare `{commandType,data}` 1700 wrapper `setJson` uses. The wire
|
|
52
52
|
* eufy uses for a few doorbell controls (status-LED 1716 `{light_enable}`). Level-2 by default; the
|
|
53
|
-
* sink resolves the session/account_id and replays the frame.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
53
|
+
* sink resolves the session/account_id and replays the frame.
|
|
54
|
+
*
|
|
55
|
+
* ## When this envelope takes `form: "auto"`
|
|
56
|
+
*
|
|
57
|
+
* Left at the default the frame is level-2 ONLY, and on a station holding no level-2 key that does not
|
|
58
|
+
* fail — it WAITS: the transport spends the full level-2 grace, re-prompts, spends it again, and only
|
|
59
|
+
* then throws. Every caller with a shorter bound sees a hang rather than a refusal, so a control on a
|
|
60
|
+
* device that may be its own keyless station is effectively unreachable. `"auto"` hands the choice to
|
|
61
|
+
* the session (`sendBySessionLevel`), which seals level-2 wherever a key exists — unchanged for a
|
|
62
|
+
* HomeBase — and level-1 where none does.
|
|
63
|
+
*
|
|
64
|
+
* Two conditions, and BOTH have to hold:
|
|
65
|
+
*
|
|
66
|
+
* 1. **`mValue3` is passed explicitly as 0.** The level-1 form of this envelope writes `mValue3:0`
|
|
67
|
+
* itself, while the level-2 form defaults it to the sub-command — so a command that passes 0 sends
|
|
68
|
+
* byte-identical JSON either way and `"auto"` only changes the seal. A command that OMITS `mValue3`
|
|
69
|
+
* would send a DIFFERENT object at level 1 than the one captured at level 2; that is a new wire
|
|
70
|
+
* needing its own evidence, not a downgrade, and it stays pinned until something captures it.
|
|
71
|
+
* 2. **The device can be its own station.** A camera or doorbell may be standalone; a HomeBase, and an
|
|
72
|
+
* accessory whose session IS its HomeBase's, always holds a key. Where a key is structurally
|
|
73
|
+
* guaranteed, staying pinned is the honest behaviour: a keyless station there is an anomaly, and
|
|
74
|
+
* throwing says so where a silently-ignored level-1 frame would look like success.
|
|
56
75
|
*/
|
|
57
76
|
export declare function setPayload(cmd: number, payload: Record<string, unknown>, ctx: CommandContext, mValue3?: number, channel?: number, form?: ScalarForm): Command;
|
|
58
77
|
/**
|
|
@@ -2,27 +2,35 @@ import { type Surface } from "./members.js";
|
|
|
2
2
|
import type { CapabilityModule, CommandContext } from "./types.js";
|
|
3
3
|
import type { Command } from "../../core/contracts.js";
|
|
4
4
|
/**
|
|
5
|
-
* The guard modes `setMode` can SET —
|
|
6
|
-
*
|
|
7
|
-
* evidence. `ArmingMode` is both the const value-object (`ArmingMode.home`) and the union type of its
|
|
5
|
+
* The guard modes `setMode` can SET — every mode a station reports, all nine confirmed against real
|
|
6
|
+
* hardware. `ArmingMode` is both the const value-object (`ArmingMode.home`) and the union type of its
|
|
8
7
|
* values, so callers pass the named constant: `setMode(ArmingMode.home)`.
|
|
9
8
|
*
|
|
10
9
|
* The domain of `setMode` (cmd 1224) alone. The alarm-delay write (cmd 1255) carries its own mode integer
|
|
11
|
-
* on a separate wire and takes {@link AlarmDelayMode}
|
|
10
|
+
* on a separate wire and takes {@link AlarmDelayMode}, which stays NARROWER — evidence for one command is
|
|
11
|
+
* not evidence for the other, and 1255 still has no capture beyond its byte-captured three.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* compile-time half of the refusal; `mode`'s published argument and the generated rejection are the
|
|
17
|
-
* runtime half.
|
|
13
|
+
* A mode belongs here only once its write is confirmed against real hardware, because a fire-and-forget
|
|
14
|
+
* wire makes a wrong mode look exactly like success; `ARMING_MODE_WIRE` carries the per-value evidence.
|
|
15
|
+
* The union is also what `ARMING_MODE_WIRE` is keyed by, so the table cannot name a mode this does not.
|
|
18
16
|
*/
|
|
19
17
|
export declare const ArmingMode: {
|
|
20
18
|
/** Armed — full protection, nobody home (wire value 0). */
|
|
21
19
|
readonly away: "away";
|
|
22
20
|
/** Armed for occupancy — reduced/perimeter protection while home (wire value 1). */
|
|
23
21
|
readonly home: "home";
|
|
22
|
+
/** Scheduled — the station follows the timetable configured in the app (wire value 2). */
|
|
23
|
+
readonly schedule: "schedule";
|
|
24
24
|
/** Custom 1 — a user-defined posture configured in the app (wire value 3). */
|
|
25
25
|
readonly custom1: "custom1";
|
|
26
|
+
/** Custom 2 — a user-defined posture configured in the app (wire value 4). */
|
|
27
|
+
readonly custom2: "custom2";
|
|
28
|
+
/** Custom 3 — a user-defined posture configured in the app (wire value 5). */
|
|
29
|
+
readonly custom3: "custom3";
|
|
30
|
+
/** Off — the station's alarm system is switched off entirely (wire value 6). */
|
|
31
|
+
readonly off: "off";
|
|
32
|
+
/** Geofenced — the station follows the app's location-based rules (wire value 47). */
|
|
33
|
+
readonly geo: "geo";
|
|
26
34
|
/** Disarmed — no alarms; sensors still report state (wire value 63). */
|
|
27
35
|
readonly disarmed: "disarmed";
|
|
28
36
|
};
|
|
@@ -60,10 +68,14 @@ export declare const ARMING_CMD: {
|
|
|
60
68
|
*
|
|
61
69
|
* ⚠️ Only 3 of the 9 modes were exercised in that capture — `mode_type` 0 (away), 63 (disarmed), 1
|
|
62
70
|
* (home), all confirmed byte-exact. Re-confirmed live 2026-08-05: each reported its own MODE_SWITCH push
|
|
63
|
-
* within ~5s of the write.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* per-value
|
|
71
|
+
* within ~5s of the write. The remaining six are live confirmations rather than captures — `custom1` 3
|
|
72
|
+
* first, then `schedule` 2, `custom2` 4, `custom3` 5, `off` 6 and `geo` 47 — each sent as this exact
|
|
73
|
+
* frame and each observed to bring MODE_SWITCH back, so all nine are settable. `ARMING_MODE_WIRE` has
|
|
74
|
+
* the per-value evidence and the dates.
|
|
75
|
+
*
|
|
76
|
+
* The ENCRYPTION LEVEL is the session's to pick (`"auto"`), not this command's: the T8030 the envelope
|
|
77
|
+
* was captured on holds a level-2 key and seals it level-2, while an own-session camera that never
|
|
78
|
+
* negotiates one carries the same envelope level-1. See `armingCommand`.
|
|
67
79
|
*/
|
|
68
80
|
readonly SET_ARMING: 1224;
|
|
69
81
|
/**
|
|
@@ -164,11 +176,10 @@ export type ArmingActions = Surface<typeof ARMING_MEMBERS>;
|
|
|
164
176
|
*/
|
|
165
177
|
export declare const ARMING_MEMBERS: {
|
|
166
178
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* declaration.
|
|
179
|
+
* Read and write are the same nine modes, so `enumValues` is the whole domain: `writeDomain` falls back
|
|
180
|
+
* to it, and the derived setter, the refusal message and the offered argument all read from that one
|
|
181
|
+
* declaration. A member states an `args` entry only where the two sides DIFFER. See
|
|
182
|
+
* {@link ARMING_MODE_WIRE} for the per-value evidence.
|
|
172
183
|
*
|
|
173
184
|
* `armingCommand` may also throw synchronously (missing account identity) and `bindMembers` turns that
|
|
174
185
|
* into a rejection, so the builder stays plain.
|
|
@@ -188,11 +199,6 @@ export declare const ARMING_MEMBERS: {
|
|
|
188
199
|
readonly kind: "enum";
|
|
189
200
|
readonly enumValues: Record<number, string>;
|
|
190
201
|
readonly provenance: "verified";
|
|
191
|
-
readonly args: readonly [{
|
|
192
|
-
readonly name: "mode";
|
|
193
|
-
readonly kind: "enum";
|
|
194
|
-
readonly values: readonly number[];
|
|
195
|
-
}];
|
|
196
202
|
readonly description: string;
|
|
197
203
|
readonly observation: {
|
|
198
204
|
readonly event: "armingModeChanged";
|
|
@@ -221,9 +227,9 @@ export declare const ARMING_MEMBERS: {
|
|
|
221
227
|
readonly setAlarmDelayConfig: import("./members.js").MethodMember<(mode: AlarmDelayMode, config: AlarmDelayConfig) => Promise<void>>;
|
|
222
228
|
};
|
|
223
229
|
/**
|
|
224
|
-
* `arming` — guard/arming mode. `armingMode` (see {@link ARMING_CMD.SET_ARMING}) has a verified
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
*
|
|
230
|
+
* `arming` — guard/arming mode. `armingMode` (see {@link ARMING_CMD.SET_ARMING}) has a verified read/write
|
|
231
|
+
* MECHANISM, and all 9 modes it reports are now confirmed as writes — the {@link ArmingMode} union is the
|
|
232
|
+
* whole set. See `ARMING_MODE_WIRE` for which three are byte-captured and which six are live-confirmed.
|
|
233
|
+
* The alarm-delay write (cmd 1255) is unaffected and keeps its narrower {@link AlarmDelayMode}.
|
|
228
234
|
*/
|
|
229
235
|
export declare const ARMING: CapabilityModule;
|
|
@@ -90,8 +90,33 @@ export type BatteryActions = Surface<typeof BATTERY_MEMBERS>;
|
|
|
90
90
|
* The richer raw `APP_CMD_SET_POWER_SOURCE` blob is a separate param, surfaced as `powerSourceInfo`.
|
|
91
91
|
*/
|
|
92
92
|
declare function decodePowerSource(raw: string | number | boolean): number | string;
|
|
93
|
-
/**
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The params whose subject IS the physical cell — so every member reading one must carry
|
|
95
|
+
* {@link notMainsCamera}.
|
|
96
|
+
*
|
|
97
|
+
* The ONE place that fact is declared. {@link cellGated} applies {@link notMainsCamera} from this list
|
|
98
|
+
* when the table is built, so a member never states the gate itself: adding a cell read is adding its
|
|
99
|
+
* param here, and there is no second place for it to be missing from. A per-member `available` was the
|
|
100
|
+
* alternative and is what the first two passes of this guard got wrong, in both directions — first by
|
|
101
|
+
* covering two of the seven, then by reading `unexposed` as covering a third.
|
|
102
|
+
*
|
|
103
|
+
* What is NOT here matters as much.
|
|
104
|
+
*
|
|
105
|
+
* - `workingMode` and the three `record*` settings describe how hard the camera works, not what powers
|
|
106
|
+
* it, and a mains camera genuinely has them. They are the reason the capability stays attached.
|
|
107
|
+
* - `cameraInfo` (1103) is a number whose meaning is unevidenced. Gating it would assert it is a
|
|
108
|
+
* battery fact, which is the kind of claim this guard exists to stop making.
|
|
109
|
+
* - `powerSource` (1293) is the open one. Both values it names — `Battery` and `External Solar Panel` —
|
|
110
|
+
* describe how a CELL is fed, so by this list's own rule it arguably belongs here. It is out because
|
|
111
|
+
* every param above is one a live mains camera was observed to publish and 1293 was not among them,
|
|
112
|
+
* and because it is `requires`-gated on its own param, so it appears only where the device reports
|
|
113
|
+
* it. Gating it would also withhold a described WRITE rather than a read, which is a different class
|
|
114
|
+
* of change. Unresolved rather than decided: see the note in the pull request.
|
|
115
|
+
*
|
|
116
|
+
* The two solar params ARE here: a panel exists to charge a cell, so a device without one has no solar
|
|
117
|
+
* harvest to report either.
|
|
118
|
+
*/
|
|
119
|
+
export declare const CELL_PARAMS: readonly number[];
|
|
95
120
|
/**
|
|
96
121
|
* Every `battery` feature, declared once — the property schema, the evidence-gated getters, the derived
|
|
97
122
|
* setters, the intent routes and the descriptions all come out of this table. Order is schema order.
|
|
@@ -117,7 +142,6 @@ export declare const BATTERY_MEMBERS: {
|
|
|
117
142
|
readonly unit: "%";
|
|
118
143
|
readonly kind: "percent";
|
|
119
144
|
readonly provenance: "verified";
|
|
120
|
-
readonly available: typeof notMainsCamera;
|
|
121
145
|
readonly description: "Battery level 0-100 (verified: param 1101).";
|
|
122
146
|
};
|
|
123
147
|
/**
|
|
@@ -130,7 +154,6 @@ export declare const BATTERY_MEMBERS: {
|
|
|
130
154
|
readonly type: "bool";
|
|
131
155
|
readonly kind: "boolean";
|
|
132
156
|
readonly provenance: "apk";
|
|
133
|
-
readonly available: typeof notMainsCamera;
|
|
134
157
|
readonly coerce: (v: string | number | boolean) => boolean;
|
|
135
158
|
readonly description: string;
|
|
136
159
|
};
|
|
@@ -300,6 +323,11 @@ export declare const BATTERY_MEMBERS: {
|
|
|
300
323
|
* Reported, so it stays in the schema and answers through `getProperty` — but given no typed getter:
|
|
301
324
|
* the payload's fields have never been decoded, and a getter would hand back an opaque blob typed as
|
|
302
325
|
* though it meant something.
|
|
326
|
+
*
|
|
327
|
+
* `unexposed` is NOT a substitute for the cell gate. It suppresses the fluent GETTER; `propertiesOf`
|
|
328
|
+
* filters `writeOnly` and `available` and deliberately not `unexposed`, because a schema entry
|
|
329
|
+
* reachable through `getProperty` is the whole point of the mark. So a cell param needs
|
|
330
|
+
* {@link CELL_PARAMS} either way, or a mains camera publishes "battery power history" and answers it.
|
|
303
331
|
*/
|
|
304
332
|
readonly batteryPowerStats: {
|
|
305
333
|
readonly param: 3100;
|
|
@@ -106,6 +106,10 @@ export declare const CONTACT_MEMBERS: {
|
|
|
106
106
|
* `1350` SET_PAYLOAD, `mChannel` = the device channel, `mValue3` 0, payload carrying the channel and a
|
|
107
107
|
* transaction stamp — the byte-shape of the app's own captured frame. Out of range is refused rather
|
|
108
108
|
* than clamped: the app's slider has no values outside it, so one is a caller error, not a nudge.
|
|
109
|
+
*
|
|
110
|
+
* Level-2 only, though the `mValue3` 0 would allow `"auto"`: an entry sensor's session IS its
|
|
111
|
+
* HomeBase's, which always holds a key — the second of `setPayload`'s two conditions, not an oversight
|
|
112
|
+
* of the first.
|
|
109
113
|
*/
|
|
110
114
|
readonly alarmVolume: {
|
|
111
115
|
readonly param: 1508;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { CapabilityModule } from "./types.js";
|
|
2
|
+
import { type Surface } from "./members.js";
|
|
3
|
+
/**
|
|
4
|
+
* `display` — a eufy Smart Display's charge.
|
|
5
|
+
*
|
|
6
|
+
* One typed read, and the smallness is the finding rather than a gap. The captured unit (a T87A0,
|
|
7
|
+
* 2026-09-04) connects over secure MQTT with no `p2p_did`, so it speaks no P2P at all, and it reported
|
|
8
|
+
* six params in an id range no other line uses. Only one of them tells a caller something it could not
|
|
9
|
+
* get another way:
|
|
10
|
+
*
|
|
11
|
+
* - 8005 and 8006 restate the model's retail name and code, which `info` already answers from the
|
|
12
|
+
* curated registry and the cloud record. Their evidence IS that agreement, so a getter beside `info`
|
|
13
|
+
* would offer a caller a second spelling of what it just read. They are named in the display param
|
|
14
|
+
* dictionary, which is what makes them readable off `getProperties()`.
|
|
15
|
+
* - 8003 is version-shaped and nothing confirms what it means, so it is `unexposed`: in the schema with
|
|
16
|
+
* its type and its `guessed` label, reachable through `getProperty`, no typed getter.
|
|
17
|
+
*
|
|
18
|
+
* Nothing is writable. No capture pins a write for any display param, and an AIoT write is
|
|
19
|
+
* fire-and-forget, so a wrong frame to a device that acknowledges nothing looks exactly like success. A
|
|
20
|
+
* screen, a volume, an assistant: the device plainly has all three and reports none of them in anything
|
|
21
|
+
* captured, which means the reads have to arrive before a control can be honest about what it moves.
|
|
22
|
+
*
|
|
23
|
+
* @module model/capabilities/display
|
|
24
|
+
*/
|
|
25
|
+
/** Smart Display param ids this capability reads. The rest of the range is named in the dictionary. */
|
|
26
|
+
export declare const DISPLAY_PARAM: {
|
|
27
|
+
readonly BATTERY: 8001;
|
|
28
|
+
readonly SOFTWARE_VERSION: 8003;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* The `display` reads. Read-only; see the module note for why there is no write.
|
|
32
|
+
*
|
|
33
|
+
* Exported but NOT published: the entries state their wire ids and what each claim rests on, which the
|
|
34
|
+
* reference site does not carry.
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare const DISPLAY_MEMBERS: {
|
|
38
|
+
/**
|
|
39
|
+
* Battery level, 0-100.
|
|
40
|
+
*
|
|
41
|
+
* **On this capability rather than on `battery`, and that is the line partition doing its job.** The
|
|
42
|
+
* security-line `battery` capability reads param 1101 and a Smart Display's charge is 8001 in its own
|
|
43
|
+
* id space — two wires that happen to mean the same thing. One capability reading both would be a claim
|
|
44
|
+
* that the two ecosystems share a param space, so a consumer reads a display's charge through
|
|
45
|
+
* `dev.display()` and a camera's through `dev.battery()`.
|
|
46
|
+
*
|
|
47
|
+
* `verified` rather than `mega`: the id is in the device's cloud record, but the NAME came from the
|
|
48
|
+
* maintainer's own knowledge of the hardware rather than from the cloud data-point list, and `"100"`
|
|
49
|
+
* fits brightness, volume or charge equally.
|
|
50
|
+
*
|
|
51
|
+
* The scale is `percent` on the reading itself, not on convention alone: a full charge reads `255` on a
|
|
52
|
+
* 0-255 scale and `1000` on a 0-1000 one, so `"100"` on a charged unit is positive evidence for 0-100
|
|
53
|
+
* rather than merely consistent with it. What nobody has done is watch it MOVE, which is why a value
|
|
54
|
+
* frozen at 100 would not yet be distinguishable from a healthy one.
|
|
55
|
+
*/
|
|
56
|
+
readonly battery: {
|
|
57
|
+
readonly param: 8001;
|
|
58
|
+
readonly type: "number";
|
|
59
|
+
readonly unit: "%";
|
|
60
|
+
readonly kind: "percent";
|
|
61
|
+
readonly provenance: "verified";
|
|
62
|
+
readonly description: "Battery level, 0-100 (param 8001).";
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Version-shaped, and that shape is the whole of the evidence — hence `guessed`, and hence no typed
|
|
66
|
+
* getter: a caller reading this off a bound object cannot see the label.
|
|
67
|
+
*
|
|
68
|
+
* `unexposed` rather than absent, so the schema still carries its type and that label and
|
|
69
|
+
* `getProperty("softwareVersion")` still answers. `dev.info()?.firmwareVersion` is the field to trust
|
|
70
|
+
* where the cloud record carries one; on this display it does not, which is the only reason 8003 is
|
|
71
|
+
* named at all.
|
|
72
|
+
*/
|
|
73
|
+
readonly softwareVersion: {
|
|
74
|
+
readonly param: 8003;
|
|
75
|
+
readonly type: "string";
|
|
76
|
+
readonly kind: "text";
|
|
77
|
+
readonly provenance: "guessed";
|
|
78
|
+
readonly unexposed: true;
|
|
79
|
+
readonly description: "Version-shaped string (param 8003), meaning unconfirmed — prefer `info.firmwareVersion`.";
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/** `display` — a eufy Smart Display (T87Ax). Read-only; no display write is captured. */
|
|
83
|
+
export declare const DISPLAY: CapabilityModule;
|
|
84
|
+
/** Bound Smart Display reads — the object returned by `dev.display()`. Read-only. */
|
|
85
|
+
export type DisplayActions = Surface<typeof DISPLAY_MEMBERS>;
|
|
@@ -40,6 +40,7 @@ import type { KeypadActions } from "./keypad.js";
|
|
|
40
40
|
import type { RtspActions } from "./rtsp.js";
|
|
41
41
|
import type { VacuumCleanActions } from "./vacuum-clean.js";
|
|
42
42
|
import type { VacuumDockActions } from "./vacuum-dock.js";
|
|
43
|
+
import type { DisplayActions } from "./display.js";
|
|
43
44
|
import type { SuctionActions } from "./suction.js";
|
|
44
45
|
import type { LocateActions } from "./locate.js";
|
|
45
46
|
import type { DeviceInfo } from "./info.js";
|
|
@@ -53,6 +54,15 @@ export declare function getCapabilityModule(cap: Capability): CapabilityModule |
|
|
|
53
54
|
* @internal
|
|
54
55
|
*/
|
|
55
56
|
export declare const CAPABILITY_MODULES: Record<Capability, CapabilityModule>;
|
|
57
|
+
/**
|
|
58
|
+
* Every param these capabilities declare before any gate — the set a device's schema is a subset of.
|
|
59
|
+
*
|
|
60
|
+
* A param in here that a device's schema does NOT carry is one a gate withheld: the capability resolved,
|
|
61
|
+
* and its member decided the read does not describe this device — a cell reading on a mains model, a mode
|
|
62
|
+
* a family does not carry. Read-aliases count, since a member reads them under its own name.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export declare function claimedParams(caps: Capability[]): Set<number>;
|
|
56
66
|
/**
|
|
57
67
|
* Merge the property schemas of several capabilities into one flat, de-duplicated list.
|
|
58
68
|
*
|
|
@@ -74,10 +84,12 @@ export declare function mergeProperties(caps: Capability[], ctx?: AvailabilityCo
|
|
|
74
84
|
* - a `modelHints` regex matches the model/category/name haystack,
|
|
75
85
|
* - `codecs` includes `codec`,
|
|
76
86
|
* - `detect(rec, codec)` returns true.
|
|
77
|
-
*
|
|
87
|
+
*
|
|
88
|
+
* An absent `codec` belongs to no line, so only the line-agnostic capabilities can match — the truthful
|
|
89
|
+
* answer for a device outside the eufy families entirely. Never throws. Returns a de-duplicated array.
|
|
78
90
|
* @internal
|
|
79
91
|
*/
|
|
80
|
-
export declare function detectCapabilities(rec: CloudRecord, codec
|
|
92
|
+
export declare function detectCapabilities(rec: CloudRecord, codec?: Codec): Capability[];
|
|
81
93
|
/**
|
|
82
94
|
* The baseline capabilities a codec grants every device of that family — derived from the modules
|
|
83
95
|
* that declare the codec in their {@link import("./types").DetectionSpec} `codecs`. Each capability
|
|
@@ -411,6 +423,8 @@ export interface DeviceActionMap {
|
|
|
411
423
|
suction: SuctionActions;
|
|
412
424
|
/** RoboVac locate (find-robot beep): `locating`; `locate(on?)`. */
|
|
413
425
|
locate: LocateActions;
|
|
426
|
+
/** Smart Display (read-only): `battery`. No display write is captured. */
|
|
427
|
+
display: DisplayActions;
|
|
414
428
|
/** Identity metadata (read-only): `{ manufacturer, model, serialNumber, name, deviceType?, firmwareVersion?, hardwareVersion? }`. */
|
|
415
429
|
info: DeviceInfo;
|
|
416
430
|
}
|
|
@@ -529,6 +543,7 @@ export { RTSP_MEMBERS } from "./rtsp.js";
|
|
|
529
543
|
export { SIREN_MEMBERS } from "./siren.js";
|
|
530
544
|
export { SMART_LIGHT_MEMBERS } from "./smart-light.js";
|
|
531
545
|
export { SMOKE_MEMBERS } from "./smoke.js";
|
|
546
|
+
export { DISPLAY_MEMBERS, type DisplayActions } from "./display.js";
|
|
532
547
|
export { SUCTION_MEMBERS } from "./suction.js";
|
|
533
548
|
export { VACUUM_CLEAN_MEMBERS } from "./vacuum-clean.js";
|
|
534
549
|
export type { DeviceInfo } from "./info.js";
|
|
@@ -550,7 +565,7 @@ export { HubAlarmTone, type HubAlarmToneValue } from "./siren.js";
|
|
|
550
565
|
* RoboVac activity and clean type are the declared returns of the public `dev.vacuumClean()` getters,
|
|
551
566
|
* so both unions are published.
|
|
552
567
|
*/
|
|
553
|
-
export type { VacuumActivity, VacuumCleanType, CarpetStrategy, CleanExtent } from "./vacuum-clean.js";
|
|
568
|
+
export type { VacuumActivity, VacuumCleanType, CarpetStrategy, CleanExtent, VacuumRoomTarget, VacuumZoneTarget, } from "./vacuum-clean.js";
|
|
554
569
|
/** The lists those unions are taken from — published because each union names its own. */
|
|
555
570
|
export { VACUUM_ACTIVITIES, VACUUM_CLEAN_TYPES, CARPET_STRATEGIES, CLEAN_EXTENTS, MOP_LEVELS } from "./vacuum-clean.js";
|
|
556
571
|
export { SuctionLevel, suctionLevelName, type SuctionLevelValue } from "./suction.js";
|
|
@@ -199,8 +199,12 @@ export declare function savePresetCommand(id: number, ctx: CommandContext): [Com
|
|
|
199
199
|
*
|
|
200
200
|
* 6242 sets the default to the preset the camera is **currently parked on**. To move the default, park
|
|
201
201
|
* the camera on `presetId` first — `preview(presetId)`, let the pan finish, then `setDefault(presetId)`.
|
|
202
|
-
* Sent while the camera is elsewhere, it has no effect.
|
|
203
|
-
*
|
|
202
|
+
* Sent while the camera is elsewhere, it has no effect.
|
|
203
|
+
*
|
|
204
|
+
* Level-2 only, unlike `zoom` beside it: this frame carries the envelope's DEFAULT `mValue3` (the
|
|
205
|
+
* sub-command), which the level-1 form cannot express — it writes 0. Downgrading it would send an
|
|
206
|
+
* object nothing has captured, so it stays pinned and is unreachable on a keyless standalone camera
|
|
207
|
+
* until one is. See `setPayload`'s two conditions.
|
|
204
208
|
*/
|
|
205
209
|
export declare function setDefaultPositionCommand(presetId: number, ctx: CommandContext): Command;
|
|
206
210
|
/**
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anker **Solix** capability surface. Solix is a separate ecosystem — its own Anker account, backend
|
|
3
|
+
* and product catalog — so it keeps its own capability id union rather than joining eufy's `Capability`
|
|
4
|
+
* / `Codec` unions, and detection is by Anker catalog CATEGORY + product-code prefix (see
|
|
5
|
+
* {@link detectSolixCapabilities}) rather than eufy param ids.
|
|
6
|
+
*
|
|
7
|
+
* What it shares is the `members` engine: the one feature with a readable wire declares ONE `members`
|
|
8
|
+
* table, and its property schema, evidence gate and typed surface all derive from it through
|
|
9
|
+
* `members.ts` (`bindMembers` / `Surface`), exactly as a eufy capability does.
|
|
10
|
+
*
|
|
11
|
+
* @module model/capabilities/solix
|
|
12
|
+
*/
|
|
13
|
+
import type { Surface } from "./members.js";
|
|
14
|
+
/** Every capability a Solix device may carry. Solix's OWN union (not eufy's `Capability`). */
|
|
15
|
+
export type SolixCapability = "identity" | "firmware" | "connectivity" | "energyMeter" | "battery" | "solarInput" | "acOutput" | "evCharger" | "charger" | "cooler";
|
|
16
|
+
/**
|
|
17
|
+
* The `energyMeter` surface — the electrical readings the vendor app names, as typed members. Each is
|
|
18
|
+
* read-only and evidence-gated: `bindMembers` installs a getter only once a frame that REPORTS that tag
|
|
19
|
+
* has landed, and answers `undefined` (not a fabricated `0`) before any has. The gate is "reported", not
|
|
20
|
+
* "non-zero": a single-phase / single-CT meter still reports its L2/L3 slots as `0.0`, so those members
|
|
21
|
+
* install and read `0` rather than staying absent — a caller sees `0` for an idle phase, not `undefined`.
|
|
22
|
+
*
|
|
23
|
+
* Provenance splits by what the evidence actually pins. The app's field VOCABULARY (these twelve names)
|
|
24
|
+
* is authoritative. For the tag→field binding, a live single-phase frame confirmed the L1 and total
|
|
25
|
+
* magnitudes (a nominal mains voltage, an equal line/total power pair, the line current), so those four
|
|
26
|
+
* positions are `verified`. The L2/L3 tags are never non-zero on a single-CT install and the app itself
|
|
27
|
+
* receives the meter as named JSON — there is no tag→phase decoder in the app binary — so their phase
|
|
28
|
+
* assignment is inferred from the block ordering and is marked `guessed`, not `apk`. The energy counters (`meterImportEnergy`/`meterExportEnergy`) are named
|
|
29
|
+
* on the wire but are NOT members here: their tag→name is confirmed, but their unit SCALE is not (a live
|
|
30
|
+
* reading is consistent with either Wh or kWh), so they stay raw named values via
|
|
31
|
+
* {@link SOLIX_METER_FIELD_NAMES} until a capture pins the scale, rather than ship a member with a guessed unit.
|
|
32
|
+
*
|
|
33
|
+
* @internal — the declaration `SolixEnergyMeterReads` derives from; exported (like the eufy `*_MEMBERS`
|
|
34
|
+
* tables) so it is a known symbol, but excluded from the rendered API reference.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SOLIX_ENERGY_METER_MEMBERS: {
|
|
37
|
+
/** Line-1 voltage (V), ff09 tag `0xAC` — confirmed live (a nominal mains voltage). */
|
|
38
|
+
readonly meterVoltageL1: {
|
|
39
|
+
readonly param: 172;
|
|
40
|
+
readonly type: "number";
|
|
41
|
+
readonly kind: "scalar";
|
|
42
|
+
readonly unit: "V";
|
|
43
|
+
readonly provenance: "verified";
|
|
44
|
+
readonly description: "Meter line-1 voltage (V) — ff09 tag 0xAC, confirmed against a live single-phase frame.";
|
|
45
|
+
};
|
|
46
|
+
/** Line-2 voltage (V), ff09 tag `0xAD` — the app's field; reads 0 until a multi-phase frame carries it. */
|
|
47
|
+
readonly meterVoltageL2: {
|
|
48
|
+
readonly param: 173;
|
|
49
|
+
readonly type: "number";
|
|
50
|
+
readonly kind: "scalar";
|
|
51
|
+
readonly unit: "V";
|
|
52
|
+
readonly provenance: "guessed";
|
|
53
|
+
readonly description: "Meter line-2 voltage (V) — ff09 tag 0xAD; phase assignment inferred from block ordering (never observed non-zero), 0 on a single-phase install.";
|
|
54
|
+
};
|
|
55
|
+
/** Line-3 voltage (V), ff09 tag `0xAE` — the app's field; reads 0 until a multi-phase frame carries it. */
|
|
56
|
+
readonly meterVoltageL3: {
|
|
57
|
+
readonly param: 174;
|
|
58
|
+
readonly type: "number";
|
|
59
|
+
readonly kind: "scalar";
|
|
60
|
+
readonly unit: "V";
|
|
61
|
+
readonly provenance: "guessed";
|
|
62
|
+
readonly description: "Meter line-3 voltage (V) — ff09 tag 0xAE; phase assignment inferred from block ordering (never observed non-zero), 0 on a single-phase install.";
|
|
63
|
+
};
|
|
64
|
+
/** Line-1 current (A), ff09 tag `0xAF` — confirmed live (the line's CT current). */
|
|
65
|
+
readonly meterCurrentL1: {
|
|
66
|
+
readonly param: 175;
|
|
67
|
+
readonly type: "number";
|
|
68
|
+
readonly kind: "scalar";
|
|
69
|
+
readonly unit: "A";
|
|
70
|
+
readonly provenance: "verified";
|
|
71
|
+
readonly description: "Meter line-1 current (A) — ff09 tag 0xAF, confirmed against a live single-phase frame.";
|
|
72
|
+
};
|
|
73
|
+
/** Line-2 current (A), ff09 tag `0xB0` — the app's field; reads 0 until a multi-phase frame carries it. */
|
|
74
|
+
readonly meterCurrentL2: {
|
|
75
|
+
readonly param: 176;
|
|
76
|
+
readonly type: "number";
|
|
77
|
+
readonly kind: "scalar";
|
|
78
|
+
readonly unit: "A";
|
|
79
|
+
readonly provenance: "guessed";
|
|
80
|
+
readonly description: "Meter line-2 current (A) — ff09 tag 0xB0; phase assignment inferred from block ordering (never observed non-zero), 0 on a single-phase install.";
|
|
81
|
+
};
|
|
82
|
+
/** Line-3 current (A), ff09 tag `0xB1` — the app's field; reads 0 until a multi-phase frame carries it. */
|
|
83
|
+
readonly meterCurrentL3: {
|
|
84
|
+
readonly param: 177;
|
|
85
|
+
readonly type: "number";
|
|
86
|
+
readonly kind: "scalar";
|
|
87
|
+
readonly unit: "A";
|
|
88
|
+
readonly provenance: "guessed";
|
|
89
|
+
readonly description: "Meter line-3 current (A) — ff09 tag 0xB1; phase assignment inferred from block ordering (never observed non-zero), 0 on a single-phase install.";
|
|
90
|
+
};
|
|
91
|
+
/** Line-1 active power (W), ff09 tag `0xA8` — confirmed live; negative on export. */
|
|
92
|
+
readonly meterPowerL1: {
|
|
93
|
+
readonly param: 168;
|
|
94
|
+
readonly type: "number";
|
|
95
|
+
readonly kind: "scalar";
|
|
96
|
+
readonly unit: "W";
|
|
97
|
+
readonly provenance: "verified";
|
|
98
|
+
readonly description: "Meter line-1 active power (W) — ff09 tag 0xA8, confirmed live; negative on export.";
|
|
99
|
+
};
|
|
100
|
+
/** Line-2 active power (W), ff09 tag `0xA9` — the app's field; reads 0 until a multi-phase frame carries it. */
|
|
101
|
+
readonly meterPowerL2: {
|
|
102
|
+
readonly param: 169;
|
|
103
|
+
readonly type: "number";
|
|
104
|
+
readonly kind: "scalar";
|
|
105
|
+
readonly unit: "W";
|
|
106
|
+
readonly provenance: "guessed";
|
|
107
|
+
readonly description: "Meter line-2 active power (W) — ff09 tag 0xA9; phase assignment inferred from block ordering (never observed non-zero), 0 on a single-phase install.";
|
|
108
|
+
};
|
|
109
|
+
/** Line-3 active power (W), ff09 tag `0xAA` — the app's field; reads 0 until a multi-phase frame carries it. */
|
|
110
|
+
readonly meterPowerL3: {
|
|
111
|
+
readonly param: 170;
|
|
112
|
+
readonly type: "number";
|
|
113
|
+
readonly kind: "scalar";
|
|
114
|
+
readonly unit: "W";
|
|
115
|
+
readonly provenance: "guessed";
|
|
116
|
+
readonly description: "Meter line-3 active power (W) — ff09 tag 0xAA; phase assignment inferred from block ordering (never observed non-zero), 0 on a single-phase install.";
|
|
117
|
+
};
|
|
118
|
+
/** Aggregate active power (W), ff09 tag `0xAB` — confirmed live; equals line-1 on a single phase. */
|
|
119
|
+
readonly meterPowerTotal: {
|
|
120
|
+
readonly param: 171;
|
|
121
|
+
readonly type: "number";
|
|
122
|
+
readonly kind: "scalar";
|
|
123
|
+
readonly unit: "W";
|
|
124
|
+
readonly provenance: "verified";
|
|
125
|
+
readonly description: "Meter total active power (W) — ff09 tag 0xAB, confirmed live; equals L1 on one phase.";
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/** Bound `energyMeter` reads (the members-derived half of `dev.energyMeter()`). Read-only. */
|
|
129
|
+
export type SolixEnergyMeterReads = Surface<typeof SOLIX_ENERGY_METER_MEMBERS>;
|
|
130
|
+
/**
|
|
131
|
+
* The capabilities each Anker catalog category implies. Category is a detection SIGNAL (like eufy's
|
|
132
|
+
* `deviceTypes`), not the model's identity — a device still resolves `energyMeter` from its product code
|
|
133
|
+
* even though its category is "Accessory", which is why that category maps to nothing on its own.
|
|
134
|
+
* Unlisted categories contribute nothing here.
|
|
135
|
+
*/
|
|
136
|
+
export declare const CATEGORY_CAPABILITIES: Readonly<Record<string, readonly SolixCapability[]>>;
|
|
137
|
+
/**
|
|
138
|
+
* Product-code prefixes known to be grid/energy meters (detects `energyMeter` regardless of category).
|
|
139
|
+
* Keep in lockstep with `SOLIX_METER_PRODUCT_PREFIXES` in `transport/mqtt/solix-mqtt.ts` (the same meter
|
|
140
|
+
* prefixes, transport-side, that gate the tag→name table): a prefix added here but not there grants
|
|
141
|
+
* `energyMeter` to a device whose frames the decoder then refuses to name. Add a meter prefix to both.
|
|
142
|
+
*/
|
|
143
|
+
export declare const SOLIX_METER_MODELS: readonly string[];
|
|
144
|
+
/**
|
|
145
|
+
* Product-code prefixes for the grid-tie Solarbank / home-battery family (detects `battery` +
|
|
146
|
+
* `solarInput` regardless of category, so a caller that builds a device without the catalog still gets
|
|
147
|
+
* them): `A1790` = Solarbank E1600 gen-1, `A17C*` = Solarbank 2 / 3, `AE10*` = Solarbank 4 E5000 Pro /
|
|
148
|
+
* SOLIX Power Dock.
|
|
149
|
+
*
|
|
150
|
+
* Grounded in the live `product_categories` catalog: `A17C0`–`A17C5` and `AE100` all list under
|
|
151
|
+
* category `"Plug-in Home Battery "`, and `AE103` by the device spec. `AE1X0`/`AE1R0` meters start
|
|
152
|
+
* `AE1X`/`AE1R`, so `AE10` does not catch them.
|
|
153
|
+
*
|
|
154
|
+
* The speculative `A17E` ("Solarbank Max AC") and `AE11` ("Solarbank Max") were dropped: neither is in
|
|
155
|
+
* the catalog, and the only `AE11x` product there — `AE113` "XE 6/8kW" — is a Residential Storage
|
|
156
|
+
* System, a different family whose telemetry is unverified, so granting it `battery`/`solarInput` would
|
|
157
|
+
* be an unevidenced false positive (exactly what this detection is otherwise careful to avoid).
|
|
158
|
+
*/
|
|
159
|
+
export declare const SOLARBANK_MODELS: readonly string[];
|
|
160
|
+
/** The minimum device shape {@link detectSolixCapabilities} reads. */
|
|
161
|
+
export interface SolixDetectionInput {
|
|
162
|
+
product_code: string;
|
|
163
|
+
device_sw_version?: string;
|
|
164
|
+
wifi_online?: boolean;
|
|
165
|
+
wifi_name?: string;
|
|
166
|
+
rssi?: string | number;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Resolve a Solix device's capability set from its record fields, catalog category, and product-code
|
|
170
|
+
* prefix — the Solix analogue of eufy's `detectCapabilities`, kept Solix-scoped so eufy detection is
|
|
171
|
+
* untouched. `identity` is universal; the rest are OR-ed evidence.
|
|
172
|
+
*/
|
|
173
|
+
export declare function detectSolixCapabilities(rec: SolixDetectionInput, category?: string): Set<SolixCapability>;
|
|
@@ -41,12 +41,19 @@ export interface DetectionSpec {
|
|
|
41
41
|
*
|
|
42
42
|
* eufy ships several ecosystems that share a cloud account and nothing else: `security` (cameras,
|
|
43
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),
|
|
44
|
+
* line — its own credential and its own DP wire), `clean` (robot vacuums — Tuya data points) and
|
|
45
|
+
* `display` (the T87Ax Smart Display — secure MQTT, never P2P, its own 8001-8006 param space).
|
|
45
46
|
* They overlap in retail vocabulary but share no wire, no param space and no semantics.
|
|
46
47
|
*
|
|
48
|
+
* `display` is a line of its own for the second of those reasons rather than the first: without it,
|
|
49
|
+
* every security capability detected by a NAME regex is attachable to a Smart Display — measured at six,
|
|
50
|
+
* on a device that can answer for none of them because it speaks no P2P at all. A line holding one
|
|
51
|
+
* capability still buys that, which is why the count is not the measure of whether a line is worth
|
|
52
|
+
* declaring.
|
|
53
|
+
*
|
|
47
54
|
* `any` is for the handful of capabilities that are genuinely line-independent (device identity).
|
|
48
55
|
*/
|
|
49
|
-
export type ProductLine = "security" | "life" | "clean" | "print" | "any";
|
|
56
|
+
export type ProductLine = "security" | "life" | "clean" | "print" | "display" | "any";
|
|
50
57
|
/**
|
|
51
58
|
* A structural subset of a P2P frame. Deliberately NOT `import`ed from `p2p/*` — keeping it
|
|
52
59
|
* structural avoids a model→p2p cycle, and the real `P2PFrame` is assignable to it. It is the
|
|
@@ -162,8 +169,13 @@ export interface DecodedState {
|
|
|
162
169
|
* the manifest path and the command path.
|
|
163
170
|
*/
|
|
164
171
|
export interface AvailabilityContext {
|
|
165
|
-
/**
|
|
166
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Resolved codec/family. Absent for a device outside the eufy device model entirely — the codecs are
|
|
174
|
+
* the eufy transport families, so an ecosystem with its own backend has no truthful value here and
|
|
175
|
+
* says so by omission rather than borrowing another family's. Every gate that reads it compares
|
|
176
|
+
* against a specific codec, so an absent one matches none.
|
|
177
|
+
*/
|
|
178
|
+
codec?: Codec;
|
|
167
179
|
/** eufy DeviceType, when known. */
|
|
168
180
|
deviceType?: number;
|
|
169
181
|
/** Model / T-code, when known. */
|
|
@@ -235,7 +247,11 @@ export interface CommandContext extends AvailabilityContext {
|
|
|
235
247
|
adminUserId?: string;
|
|
236
248
|
/** The acting member's short id (`member.short_user_id`, hex, e.g. `"0003"`) — the lock cmd `A5` field. */
|
|
237
249
|
shortUserId?: string;
|
|
238
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* The acting name a command attributes itself to — the lock cmd acting-username `A4` field, and the
|
|
252
|
+
* `user_name` of the guard-mode and HomeBase-alarm writes. The logged-in account's display name
|
|
253
|
+
* (email local-part) unless the client pins a different label for it.
|
|
254
|
+
*/
|
|
239
255
|
accountName?: string;
|
|
240
256
|
/**
|
|
241
257
|
* Whether the device has a usable P2P endpoint (a non-empty `p2p_did`). A HomeBase-attached lock
|