@mega-yfue/eufy-sdk 0.2.0-beta.3 → 0.2.0-beta.30
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/client/eufy-mega.d.ts +16 -11
- package/dist/client/types.d.ts +6 -2
- package/dist/core/contracts.d.ts +26 -30
- 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 +121 -0
- package/dist/core/store.d.ts +38 -10
- package/dist/index.js +2929 -505
- 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/doorbell.d.ts +24 -14
- package/dist/model/capabilities/index.d.ts +19 -4
- package/dist/model/capabilities/lock.d.ts +15 -12
- 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 +69 -12
- package/dist/model/capabilities/vacuum-clean.d.ts +133 -25
- package/dist/model/classify.d.ts +3 -1
- package/dist/model/device-family.d.ts +2 -1
- package/dist/model/device-types.d.ts +1 -0
- 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 +137 -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 +270 -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 +3 -0
- package/dist/transport/mqtt/secure-mqtt.d.ts +14 -1
- package/dist/transport/mqtt/solix-mqtt.d.ts +360 -0
- package/dist/transport/mqtt/topics.d.ts +30 -0
- package/dist/transport/p2p/command-router.d.ts +131 -19
- package/dist/transport/p2p/live-stream.d.ts +5 -4
- package/dist/transport/p2p/live-trace.d.ts +48 -5
- package/dist/transport/p2p/media.d.ts +11 -0
- package/dist/transport/p2p/p2p-session.d.ts +9 -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/p2p/station-channels.d.ts +54 -0
- package/dist/transport/stored-image-cache.d.ts +7 -1
- package/package.json +7 -6
|
@@ -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
|
|
@@ -129,6 +136,45 @@ export interface EventMapping {
|
|
|
129
136
|
* capture. Return `{}` when this signal doesn't carry the field, so nothing is invented.
|
|
130
137
|
*/
|
|
131
138
|
derive?(signal: InboundSignal): Record<string, unknown>;
|
|
139
|
+
/**
|
|
140
|
+
* Evidence that this device deals in the classification the id names, for an id whose presence in
|
|
141
|
+
* the wire vocabulary does not prove it.
|
|
142
|
+
*
|
|
143
|
+
* The AI-detection ids are shared verbatim across the camera families, so the id space says what an
|
|
144
|
+
* integer MEANS and never which units classify that way. A claim is how a mapping states the
|
|
145
|
+
* evidence that separates them, and it narrows the DESCRIPTION only: {@link EventMapping} stays in
|
|
146
|
+
* the dispatch index unclaimed, so a device that sends the push still gets the event. Under-reporting
|
|
147
|
+
* what a device is expected to emit is recoverable; dropping an event it did emit is not.
|
|
148
|
+
*
|
|
149
|
+
* Every field must hold — the AND to {@link DetectionSpec}'s OR, because a claim rules a family OUT
|
|
150
|
+
* rather than finding one more reason to say yes. A fact the context does not carry rules nothing
|
|
151
|
+
* out: only evidence that positively contradicts the claim withdraws the event.
|
|
152
|
+
*/
|
|
153
|
+
claim?: EventClaim;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Evidence separating the families that share one inbound id.
|
|
157
|
+
*
|
|
158
|
+
* Both fields are optional and independent; an empty claim asserts nothing and is the same as none.
|
|
159
|
+
*/
|
|
160
|
+
export interface EventClaim {
|
|
161
|
+
/**
|
|
162
|
+
* The codecs whose devices issue the id. For an id drawn from a vocabulary one device family owns:
|
|
163
|
+
* the AI-detection ids belong to the camera families, and a standalone sensor announces its own
|
|
164
|
+
* motion under a different id entirely.
|
|
165
|
+
*/
|
|
166
|
+
codecs?: readonly Codec[];
|
|
167
|
+
/**
|
|
168
|
+
* Member names whose INSTALLED getter is the evidence — the device reported the parameter behind
|
|
169
|
+
* the classification, which is the same bar every typed read is held to.
|
|
170
|
+
*/
|
|
171
|
+
reads?: readonly string[];
|
|
172
|
+
/**
|
|
173
|
+
* The topology the id belongs to: `true` for an id only a station's attached device sends, `false`
|
|
174
|
+
* for one only a standalone unit sends. Compared against {@link AvailabilityContext.homeBaseAttached},
|
|
175
|
+
* and ignored where that is absent.
|
|
176
|
+
*/
|
|
177
|
+
homeBaseAttached?: boolean;
|
|
132
178
|
}
|
|
133
179
|
/**
|
|
134
180
|
* A decoded inbound event a capability wants surfaced on the SDK. `event` is the EufyMega event
|
|
@@ -162,8 +208,13 @@ export interface DecodedState {
|
|
|
162
208
|
* the manifest path and the command path.
|
|
163
209
|
*/
|
|
164
210
|
export interface AvailabilityContext {
|
|
165
|
-
/**
|
|
166
|
-
|
|
211
|
+
/**
|
|
212
|
+
* Resolved codec/family. Absent for a device outside the eufy device model entirely — the codecs are
|
|
213
|
+
* the eufy transport families, so an ecosystem with its own backend has no truthful value here and
|
|
214
|
+
* says so by omission rather than borrowing another family's. Every gate that reads it compares
|
|
215
|
+
* against a specific codec, so an absent one matches none.
|
|
216
|
+
*/
|
|
217
|
+
codec?: Codec;
|
|
167
218
|
/** eufy DeviceType, when known. */
|
|
168
219
|
deviceType?: number;
|
|
169
220
|
/** Model / T-code, when known. */
|
|
@@ -185,6 +236,15 @@ export interface AvailabilityContext {
|
|
|
185
236
|
* `undefined` as an empty set — `ctx.paramIds?.has(dp) ?? false`.
|
|
186
237
|
*/
|
|
187
238
|
paramIds?: ReadonlySet<number>;
|
|
239
|
+
/**
|
|
240
|
+
* Whether the device hangs off a HomeBase (a `parent_sn` other than its own) rather than standing
|
|
241
|
+
* alone. A DEVICE fact, not a transport one — the same class of routing evidence as {@link hasP2p} —
|
|
242
|
+
* which is why it sits here rather than on {@link CommandContext}: it is as true of a described
|
|
243
|
+
* device as of a commanded one. The `rtsp` capability gates on it because a station serves an
|
|
244
|
+
* attached camera's stream itself and ignores that camera's authentication setting, so the write
|
|
245
|
+
* cannot do what its name promises there.
|
|
246
|
+
*/
|
|
247
|
+
homeBaseAttached?: boolean;
|
|
188
248
|
}
|
|
189
249
|
export interface CommandContext extends AvailabilityContext {
|
|
190
250
|
/** Device channel (0 for standalone, `device_channel` on a HomeBase). */
|
|
@@ -235,7 +295,11 @@ export interface CommandContext extends AvailabilityContext {
|
|
|
235
295
|
adminUserId?: string;
|
|
236
296
|
/** The acting member's short id (`member.short_user_id`, hex, e.g. `"0003"`) — the lock cmd `A5` field. */
|
|
237
297
|
shortUserId?: string;
|
|
238
|
-
/**
|
|
298
|
+
/**
|
|
299
|
+
* The acting name a command attributes itself to — the lock cmd acting-username `A4` field, and the
|
|
300
|
+
* `user_name` of the guard-mode and HomeBase-alarm writes. The logged-in account's display name
|
|
301
|
+
* (email local-part) unless the client pins a different label for it.
|
|
302
|
+
*/
|
|
239
303
|
accountName?: string;
|
|
240
304
|
/**
|
|
241
305
|
* Whether the device has a usable P2P endpoint (a non-empty `p2p_did`). A HomeBase-attached lock
|
|
@@ -243,13 +307,6 @@ export interface CommandContext extends AvailabilityContext {
|
|
|
243
307
|
* capability uses this to route lock/unlock to P2P vs. reject with a clear MQTT-not-wired error.
|
|
244
308
|
*/
|
|
245
309
|
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
310
|
/**
|
|
254
311
|
* Parsed `get_product_data_point` catalog for this device's SKU — present for vacuum/mower devices,
|
|
255
312
|
* absent for all other codecs. Capabilities use it for per-model feature-availability and value-range
|
|
@@ -192,35 +192,27 @@ export declare const ModeCtrlMethod: {
|
|
|
192
192
|
readonly STOP_SMART_FOLLOW: 18;
|
|
193
193
|
readonly START_GLOBAL_CRUISE: 20;
|
|
194
194
|
};
|
|
195
|
-
/**
|
|
196
|
-
* The methods that carry a `Param` oneof — room, zone, goto, schedule, cruise and scene cleans.
|
|
197
|
-
*
|
|
198
|
-
* Deliberately absent from {@link ModeCtrlMethod}. Each needs an argument the caller has to supply and
|
|
199
|
-
* this SDK cannot yet answer: a room or zone id comes from map data, which is not decodable here, and a
|
|
200
|
-
* coordinate is signed centimetres in a frame no capture has pinned. Listing their numbers beside the
|
|
201
|
-
* parameterless ones would invite a caller to send one with an empty payload, which is a valid frame
|
|
202
|
-
* meaning something nobody intended.
|
|
203
|
-
*/
|
|
204
|
-
/**
|
|
205
|
-
* Encode a `ModeCtrlRequest` protobuf (DP 152) as a DP value: `varint(bodyLen) ++ {method:1, seq:2}`.
|
|
206
|
-
*
|
|
207
|
-
* Built on {@link RawDpWriter} rather than hand-rolled bytes. The frame is unchanged and the existing
|
|
208
|
-
* byte-level test is what proves it — that test was written against a live T2351 capture, so it holds
|
|
209
|
-
* the writer to the wire rather than to this function's own idea of the wire.
|
|
210
|
-
*
|
|
211
|
-
* Method 0 (START_AUTO_CLEAN) is omitted rather than written as an explicit zero, per the proto3
|
|
212
|
-
* default-field rule and confirmed on that same capture. The writer deliberately does not apply that
|
|
213
|
-
* rule itself: whether an explicit zero and an absent field mean the same thing is the
|
|
214
|
-
* message's business, not the encoder's.
|
|
215
|
-
* @internal
|
|
216
|
-
*/
|
|
217
195
|
/**
|
|
218
196
|
* The area-selecting `ModeCtrlRequest` methods, and the `Param` field each one's payload rides in.
|
|
219
197
|
*
|
|
220
198
|
* Kept apart from {@link ModeCtrlMethod} because these are a different kind of thing: a parameterless
|
|
221
199
|
* verb is complete on its own, whereas each of these is meaningless without an argument the caller has
|
|
222
200
|
* to supply. Sending one with an empty payload is a well-formed frame that means something nobody
|
|
223
|
-
* intended, which is exactly why the numbers do not sit beside the others.
|
|
201
|
+
* intended, which is exactly why the numbers do not sit beside the others. Each verb built on one takes
|
|
202
|
+
* its argument in the signature: {@link VACUUM_CLEAN_MEMBERS.startScene},
|
|
203
|
+
* {@link VACUUM_CLEAN_MEMBERS.cleanRooms} and {@link VACUUM_CLEAN_MEMBERS.cleanZones}.
|
|
204
|
+
*
|
|
205
|
+
* The outer frame these ride in is byte-verified on a live T2351, and `SCENE`, `SELECT_ROOMS` and
|
|
206
|
+
* `SELECT_ZONES` have each since been RUN on a T2351 and did what they name — so their numbers rest on
|
|
207
|
+
* observed behaviour rather than on the vendor's definition alone.
|
|
208
|
+
*
|
|
209
|
+
* That distinction is the whole point of checking, and this is the one place it is argued: an AIoT
|
|
210
|
+
* data-point write is fire-and-forget, so a wrong number would be a different command arriving and
|
|
211
|
+
* looking exactly like success, which no frame check could catch. Watching the number is the only thing
|
|
212
|
+
* that rules it out.
|
|
213
|
+
*
|
|
214
|
+
* `GOTO` carries no encoder because a goto point is a coordinate no read on this SDK supplies, where a
|
|
215
|
+
* scene id and a map id both arrive on DP 180.
|
|
224
216
|
*/
|
|
225
217
|
export declare const ModeCtrlParamMethod: {
|
|
226
218
|
/** `START_SELECT_ROOMS_CLEAN` — clean the named rooms of a named map. */
|
|
@@ -267,7 +259,7 @@ export interface VacuumZoneTarget {
|
|
|
267
259
|
* `mapId` is required and has no default, deliberately. The obvious shortcut is to assume the map a
|
|
268
260
|
* single-floor home would have; on a two-floor home that silently sends the robot's ids against the
|
|
269
261
|
* wrong floor's map. A caller that cannot name the map cannot safely make this call, and saying so is
|
|
270
|
-
* better than picking for them.
|
|
262
|
+
* better than picking for them. {@link VACUUM_CLEAN_MEMBERS.cleanRooms} dispatches this.
|
|
271
263
|
* @internal
|
|
272
264
|
*/
|
|
273
265
|
export declare function encodeSelectRoomsClean(mapId: number, rooms: readonly VacuumRoomTarget[], cleanTimes?: number): string;
|
|
@@ -276,8 +268,25 @@ export declare function encodeSelectRoomsClean(mapId: number, rooms: readonly Va
|
|
|
276
268
|
* @internal
|
|
277
269
|
*/
|
|
278
270
|
export declare function encodeSelectZonesClean(mapId: number, zones: readonly VacuumZoneTarget[]): string;
|
|
279
|
-
/**
|
|
271
|
+
/**
|
|
272
|
+
* Build a scene clean, which needs only the scene's own id — `VacuumScene.id`, as DP 180 reports it.
|
|
273
|
+
* {@link VACUUM_CLEAN_MEMBERS.startScene} dispatches this.
|
|
274
|
+
* @internal
|
|
275
|
+
*/
|
|
280
276
|
export declare function encodeSceneClean(sceneId: number): string;
|
|
277
|
+
/**
|
|
278
|
+
* Encode a `ModeCtrlRequest` protobuf (DP 152) as a DP value: `varint(bodyLen) ++ {method:1, seq:2}`.
|
|
279
|
+
*
|
|
280
|
+
* Built on {@link RawDpWriter} rather than hand-rolled bytes. The frame is unchanged and the existing
|
|
281
|
+
* byte-level test is what proves it — that test was written against a live T2351 capture, so it holds
|
|
282
|
+
* the writer to the wire rather than to this function's own idea of the wire.
|
|
283
|
+
*
|
|
284
|
+
* Method 0 (START_AUTO_CLEAN) is omitted rather than written as an explicit zero, per the proto3
|
|
285
|
+
* default-field rule and confirmed on that same capture. The writer deliberately does not apply that
|
|
286
|
+
* rule itself: whether an explicit zero and an absent field mean the same thing is the
|
|
287
|
+
* message's business, not the encoder's.
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
281
290
|
export declare function encodeModeCtrl(method: number, seq: number): string;
|
|
282
291
|
/**
|
|
283
292
|
* Every value {@link VacuumActivity} can take, as data — the read's declared domain, so the schema a
|
|
@@ -370,6 +379,23 @@ export type CarpetStrategy = (typeof CARPET_STRATEGIES)[number];
|
|
|
370
379
|
*/
|
|
371
380
|
export declare const CLEAN_EXTENTS: readonly ["normal", "narrow", "quick"];
|
|
372
381
|
export type CleanExtent = (typeof CLEAN_EXTENTS)[number];
|
|
382
|
+
/**
|
|
383
|
+
* Build a `CleanParamRequest` (DP 154) stating the three cleaning settings a run uses.
|
|
384
|
+
*
|
|
385
|
+
* The message is `{ clean_param: CleanParam }` — field 1 of the request, carrying the same `CleanParam`
|
|
386
|
+
* this module decodes out of field 1 of the reports it receives, so the shape a write sends is the
|
|
387
|
+
* shape a read has already been proven against on live hardware.
|
|
388
|
+
*
|
|
389
|
+
* All three settings are stated together because one message carries all three: a write naming fewer
|
|
390
|
+
* would be a `CleanParam` with the rest silent, and what a robot does with a half-stated one is not
|
|
391
|
+
* something this SDK has observed. `clean_times`(7) is never written — the vendor's own field comment
|
|
392
|
+
* makes zero mean "not stated", so omitting it leaves the robot's configured pass count alone — and
|
|
393
|
+
* neither is `fan`(6), which belongs to the suction capability's own data point.
|
|
394
|
+
*
|
|
395
|
+
* {@link VACUUM_CLEAN_MEMBERS.setCleanParam} dispatches this.
|
|
396
|
+
* @internal
|
|
397
|
+
*/
|
|
398
|
+
export declare function encodeCleanParam(cleanType: VacuumCleanType, cleanExtent: CleanExtent, mopLevel: MopLevel): string;
|
|
373
399
|
/**
|
|
374
400
|
* Read one setting out of the CONFIGURED `CleanParam` (DP 154), by its field number.
|
|
375
401
|
*
|
|
@@ -1941,6 +1967,88 @@ export declare const VACUUM_CLEAN_MEMBERS: {
|
|
|
1941
1967
|
readonly pauseCleaning: import("./members.js").MethodMember<() => Promise<void>> & {
|
|
1942
1968
|
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
1943
1969
|
};
|
|
1970
|
+
/**
|
|
1971
|
+
* Run a saved cleaning scene by its id (ModeCtrlRequest method 24 over DP 152).
|
|
1972
|
+
*
|
|
1973
|
+
* The id is the device's own, as {@link VACUUM_CLEAN_MEMBERS.scenes} reports it — `VacuumScene.id`
|
|
1974
|
+
* off the `SceneResponse` on DP 180. A scene the device reports invalid stays reportable and running
|
|
1975
|
+
* it is still a well-formed request; `VacuumScene.invalidReason` says why the device will refuse.
|
|
1976
|
+
*
|
|
1977
|
+
* Frame shape is byte-proven against the shared outer `ModeCtrlRequest`, and method 24 has been
|
|
1978
|
+
* WATCHED: run on a T2351, it started the named scene.
|
|
1979
|
+
*/
|
|
1980
|
+
readonly startScene: import("./members.js").MethodMember<(sceneId: number) => Promise<void>> & {
|
|
1981
|
+
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
1982
|
+
};
|
|
1983
|
+
/**
|
|
1984
|
+
* State the cleaning settings a run uses — `CleanParamRequest.clean_param` over DP 154.
|
|
1985
|
+
*
|
|
1986
|
+
* The write counterpart of {@link VACUUM_CLEAN_MEMBERS.cleanType},
|
|
1987
|
+
* {@link VACUUM_CLEAN_MEMBERS.cleanExtent} and {@link VACUUM_CLEAN_MEMBERS.mopLevel}: one message
|
|
1988
|
+
* carries all three, so they are set together rather than through three setters that would each send
|
|
1989
|
+
* the same message with the other two silent.
|
|
1990
|
+
*
|
|
1991
|
+
* **The evidence, and its limit.** The frame is field 1 of `CleanParamRequest`, which carries the
|
|
1992
|
+
* very `CleanParam` this module decodes out of field 1 of the reports a live T2351 sends — the
|
|
1993
|
+
* field numbers, the single-field wrappers and the `mop_mode.level` scale are all read off that
|
|
1994
|
+
* capture, and `encodeCleanParam` writes what `decodeCleanParamValue` reads. What is NOT captured is
|
|
1995
|
+
* the write direction itself. It ships as a method rather than an unverified write because the
|
|
1996
|
+
* hazard that rule answers does not arise here: DP 154 is the robot's own settings report, so a frame
|
|
1997
|
+
* it does not accept leaves those three reads unchanged, where a wrong fire-and-forget command would
|
|
1998
|
+
* look exactly like success.
|
|
1999
|
+
*
|
|
2000
|
+
* Suction is not here. It has its own data point and its own capability — a `fan` field exists in
|
|
2001
|
+
* this message and is deliberately not written, for the same reason it is not read.
|
|
2002
|
+
*/
|
|
2003
|
+
readonly setCleanParam: {
|
|
2004
|
+
readonly method: (deps: import("./members.js").MemberDeps) => (cleanType: VacuumCleanType, cleanExtent: CleanExtent, mopLevel: MopLevel) => Promise<void>;
|
|
2005
|
+
readonly description: string;
|
|
2006
|
+
readonly available: ((ctx: import("./types.js").CommandContext) => boolean) & ((ctx: import("./types.js").CommandContext) => boolean);
|
|
2007
|
+
readonly answers?: true;
|
|
2008
|
+
readonly args: readonly [{
|
|
2009
|
+
readonly name: "cleanType";
|
|
2010
|
+
readonly kind: "enum";
|
|
2011
|
+
readonly values: readonly ["sweep", "mop", "sweepAndMop", "sweepThenMop"];
|
|
2012
|
+
readonly description: "What the robot does with a surface.";
|
|
2013
|
+
}, {
|
|
2014
|
+
readonly name: "cleanExtent";
|
|
2015
|
+
readonly kind: "enum";
|
|
2016
|
+
readonly values: readonly ["normal", "narrow", "quick"];
|
|
2017
|
+
readonly description: "How far past the mapped edge a job reaches. Wire order, not app order.";
|
|
2018
|
+
}, {
|
|
2019
|
+
readonly name: "mopLevel";
|
|
2020
|
+
readonly kind: "enum";
|
|
2021
|
+
readonly values: readonly ["low", "middle", "high"];
|
|
2022
|
+
readonly description: "How much water the mop lays down. Only meaningful for a clean type that mops.";
|
|
2023
|
+
}];
|
|
2024
|
+
};
|
|
2025
|
+
/**
|
|
2026
|
+
* Clean the named rooms of a named map (ModeCtrlRequest method 1 over DP 152).
|
|
2027
|
+
*
|
|
2028
|
+
* `mapId` has no default and that is deliberate: room ids are per map, so assuming the map a
|
|
2029
|
+
* single-floor home would have sends a two-floor home's ids against the wrong floor. `SceneInfo.mapid`
|
|
2030
|
+
* on DP 180 and a scheduled rooms-clean's `map_id` are the two real map ids the device reports.
|
|
2031
|
+
*
|
|
2032
|
+
* `cleanTimes` is how many passes to make over the set; rooms with no `order` are visited in the
|
|
2033
|
+
* order given.
|
|
2034
|
+
*
|
|
2035
|
+
* Frame shape is byte-proven against the shared outer `ModeCtrlRequest`, and method 1 has been
|
|
2036
|
+
* WATCHED: run on a T2351, it cleaned the rooms named.
|
|
2037
|
+
*/
|
|
2038
|
+
readonly cleanRooms: import("./members.js").MethodMember<(mapId: number, rooms: readonly VacuumRoomTarget[], cleanTimes?: number) => Promise<void>> & {
|
|
2039
|
+
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
2040
|
+
};
|
|
2041
|
+
/**
|
|
2042
|
+
* Clean the given rectangles of a named map (ModeCtrlRequest method 2 over DP 152).
|
|
2043
|
+
*
|
|
2044
|
+
* Corners are SIGNED centimetres in the map's own frame, whose origin sits wherever the robot first
|
|
2045
|
+
* mapped from — negative coordinates are ordinary and are ZigZag-encoded, not written as plain
|
|
2046
|
+
* varints. Same `mapId` reasoning as {@link VACUUM_CLEAN_MEMBERS.cleanRooms}, and the same evidence:
|
|
2047
|
+
* method 2 was run on a T2351 and cleaned the rectangles given.
|
|
2048
|
+
*/
|
|
2049
|
+
readonly cleanZones: import("./members.js").MethodMember<(mapId: number, zones: readonly VacuumZoneTarget[]) => Promise<void>> & {
|
|
2050
|
+
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
2051
|
+
};
|
|
1944
2052
|
};
|
|
1945
2053
|
/** `vacuum_clean` — core RoboVac scalar state + decoded activity: power, activity, volume, battery. */
|
|
1946
2054
|
export declare const VACUUM_CLEAN: CapabilityModule;
|
package/dist/model/classify.d.ts
CHANGED
|
@@ -55,7 +55,9 @@ export declare function codecForType(deviceType: number): Codec | undefined;
|
|
|
55
55
|
* EXCEPT `T8520`-prefixed which can be lock variants — still lock.
|
|
56
56
|
* - `T74xx` → **lock** (SmartSafe 7400-series).
|
|
57
57
|
* - `T80xx` / `T8001` / `T8002` / `T8010` / `T8030` / `T8023` / `T8025` → **station**
|
|
58
|
-
* (HomeBase / HomeBase 2 / 3 / Mini),
|
|
58
|
+
* (HomeBase / HomeBase 2 / 3 / Mini), `T9000` (the app's own model registry files it as family
|
|
59
|
+
* `STATION_9000`, the one station family outside the T8 band), and `T8N00` (NVR) / `T8E00` (PoE
|
|
60
|
+
* NVR) → station.
|
|
59
61
|
* - `T89xx` (entry/motion/water/siren sensors, e.g. T8900/T8910/T8920) → **sensor**.
|
|
60
62
|
* - `T87xx` keypad (`T8960`) → **keypad**.
|
|
61
63
|
* - any other `T8…` security T-code → **camera** (the default security family).
|
|
@@ -27,7 +27,8 @@ export declare const WALL_LIGHT_TYPES: ReadonlySet<number>;
|
|
|
27
27
|
* they own the hub-audio surface (alarm / voice-prompt volume). A deliberate SUBSET of the `station`
|
|
28
28
|
* codec that EXCLUDES the NVRs (S4 Max, PoE NVR): those resolve to `station` for arming/storage but
|
|
29
29
|
* have no speaker, so they must NOT expose the hub-audio controls (they'd fire at nothing). The
|
|
30
|
-
* alarm/prompt wire is verified on HomeBase 3 (HB3); the other hubs
|
|
30
|
+
* alarm/prompt wire is verified on HomeBase 3 (HB3); the other hubs, T9000 (`STATION_9000`) included,
|
|
31
|
+
* share the hub hardware.
|
|
31
32
|
*/
|
|
32
33
|
export declare const HOMEBASE_TYPES: ReadonlySet<number>;
|
|
33
34
|
/** Indoor camera (any indoor variant). */
|
package/dist/model/device.d.ts
CHANGED
|
@@ -62,6 +62,11 @@ export declare class Device {
|
|
|
62
62
|
* different wire ids across device families still resolves to one named value.
|
|
63
63
|
*/
|
|
64
64
|
private specByParam;
|
|
65
|
+
/**
|
|
66
|
+
* Params a resolved capability declares that this device's schema does not carry — withheld by a
|
|
67
|
+
* member's gate, so not named from the param dictionary either. See {@link Device.applyParams}.
|
|
68
|
+
*/
|
|
69
|
+
private withheld;
|
|
65
70
|
/** Which param namespace this device's ids live in (clean DPs vs security P2P). */
|
|
66
71
|
private namespace;
|
|
67
72
|
/** The record's `device_name` as stated, before the {@link modelName} fallback is applied. */
|
|
@@ -201,6 +206,16 @@ export declare class Device {
|
|
|
201
206
|
* Apply a raw param map (cloud record or P2P notification). Known params update their named
|
|
202
207
|
* property; unrecognised params are retained as `unknown_<paramType>` so nothing is lost.
|
|
203
208
|
*
|
|
209
|
+
* Naming precedence: this device's own `PropertySpec` (curated), then the param dictionary for its
|
|
210
|
+
* namespace, then the `unknown_<paramType>` passthrough. The dictionary def is consulted even where a
|
|
211
|
+
* spec exists, because `encoding` lives there.
|
|
212
|
+
*
|
|
213
|
+
* A param a resolved capability's gate WITHHELD takes the passthrough instead of its dictionary name.
|
|
214
|
+
* The gate decided the read does not describe this device, the dictionary names it what the member
|
|
215
|
+
* would have, and republishing it there hands a caller a reading indistinguishable from one the device
|
|
216
|
+
* really answered. A capability that never resolved withholds nothing: a param arriving before its
|
|
217
|
+
* capability is still the device's own, and keeps its dictionary name.
|
|
218
|
+
*
|
|
204
219
|
* @param params param_type → raw value.
|
|
205
220
|
* @param ts observation time (epoch ms); defaults to `Date.now()`.
|
|
206
221
|
* @returns the list of property names whose value changed.
|
package/dist/model/index.d.ts
CHANGED
|
@@ -30,3 +30,8 @@ export * from "./capabilities/index.js";
|
|
|
30
30
|
*/
|
|
31
31
|
export type { FamilyContext } from "./device-family.js";
|
|
32
32
|
export { CusPushEvent, CusPushAlarmType, CusPushMode, DoorbellPushEvent, IndoorPushEvent, HB3PairedDevicePushEvent, LockPushEvent, SmartDropPushEvent, NotificationStyle, detectionName, } from "./push-events.js";
|
|
33
|
+
export { SolixDevice, discoverSolixDevices, solarbankSceneReadings, type SolixDeviceReader, type SolixDeviceRecord, type SolixIdentity, type SolixConnectivity, type SolixEnergyMeter, type SolixDeviceOptions, } from "./solix-device.js";
|
|
34
|
+
export { SOLIX_ENERGY_METER_MEMBERS, type SolixCapability, type SolixEnergyMeterReads } from "./capabilities/solix.js";
|
|
35
|
+
export { buildModelIndex, type SolixProduct, type SolixProductCategory } from "./solix-catalog.js";
|
|
36
|
+
export { solixProductFamily, isSolixPowerStation, isSolixSolarbank, isSolixSmartMeter, type SolixProductFamily, type SolixFamilyInput, } from "./solix-family.js";
|
|
37
|
+
export { SolixSite, discoverSolixSites, type SolixSiteReader, type SolixSiteOptions } from "./solix-site.js";
|
|
@@ -26,3 +26,27 @@ export interface ParamDef {
|
|
|
26
26
|
export declare const SECURITY_PARAMS: Record<number, ParamDef>;
|
|
27
27
|
/** RoboVac Tuya DP space (ids ~150-180), from get_product_data_point. */
|
|
28
28
|
export declare const CLEAN_PARAMS: Record<number, ParamDef>;
|
|
29
|
+
/**
|
|
30
|
+
* eufy Smart Display (T87Ax) param space — ids 8001-8006.
|
|
31
|
+
*
|
|
32
|
+
* Its own table rather than a corner of {@link SECURITY_PARAMS}: nothing in the 8000s carries a security
|
|
33
|
+
* meaning, so reading these ids there would decode a future security param assigned in this range as
|
|
34
|
+
* whatever it means on a camera.
|
|
35
|
+
*
|
|
36
|
+
* Every id here was reported by a live T87A0 (captured 2026-09-04). That the device SENT an id is what
|
|
37
|
+
* earns it a place in this table; the provenance label beside each one rates something narrower — how far
|
|
38
|
+
* its NAME is trusted. `modelName` and `modelCode` are `mega`, their values matching what the cloud
|
|
39
|
+
* record already carried. `battery` is `verified`: the id is real and the reading consistent, but the
|
|
40
|
+
* name came from the maintainer's own knowledge of the hardware rather than from the cloud data-point
|
|
41
|
+
* list, and `"100"` fits brightness, volume or charge equally.
|
|
42
|
+
*
|
|
43
|
+
* A dictionary entry is what makes a param readable by name off `getProperties()`. `capabilities/display.ts`
|
|
44
|
+
* decides separately which of them reach the typed surface, and only `battery` does.
|
|
45
|
+
*
|
|
46
|
+
* **8002 (`"1"`) and 8004 (a serial-shaped string) are absent, deliberately.** Neither meaning is
|
|
47
|
+
* legible from one value: `1` fits any enum or flag, and a serial could be the display's own or the
|
|
48
|
+
* station's it is bound to. They arrive as `unknown_8002` / `unknown_8004`, which is the measure of this
|
|
49
|
+
* list: it holds what is unknown, not what is unknowable. What would settle them: the vendor app's own
|
|
50
|
+
* display settings screen, one control at a time, params diffed after each.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DISPLAY_PARAMS: Record<number, ParamDef>;
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import type { Codec } from "./types.js";
|
|
15
15
|
import { type ParamDef } from "./param-dictionary.js";
|
|
16
16
|
/** The param id spaces this SDK models. */
|
|
17
|
-
export type ParamNamespace = "security" | "clean" | "life" | "print";
|
|
17
|
+
export type ParamNamespace = "security" | "clean" | "life" | "print" | "display";
|
|
18
18
|
/** Look up a param def in the given namespace. */
|
|
19
19
|
export declare function paramDef(ns: ParamNamespace, paramType: number): ParamDef | undefined;
|
|
20
20
|
/** The param namespace a device's ids live in, from its codec, via the module-local `NAMESPACE_BY_CODEC` table. */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anker Solix product catalog — the vendor's pairable-product registry (categories → products),
|
|
3
|
+
* used to label a discovered device's model code with a marketing name + category.
|
|
4
|
+
*
|
|
5
|
+
* This is model-layer vocabulary: pure data shapes + a lookup builder, with no wire/transport
|
|
6
|
+
* dependency. The catalog itself is fetched from the cloud by the client layer (`SolixClient`), which
|
|
7
|
+
* feeds it here via {@link buildModelIndex}; keeping the shapes and the index in the model layer lets
|
|
8
|
+
* `SolixDevice` resolve a name/category without reaching across the capability↔transport boundary.
|
|
9
|
+
*/
|
|
10
|
+
export type { SolixProduct, SolixProductCategory } from "../core/solix-types.js";
|
|
11
|
+
import type { SolixProductCategory } from "../core/solix-types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Flatten a product catalog into a `product_code → { name, category }` lookup for labelling
|
|
14
|
+
* discovered devices. Every variant code in `p_codes` maps to its parent product too, so a device
|
|
15
|
+
* reporting a sub-model resolves to the same marketing name.
|
|
16
|
+
*
|
|
17
|
+
* The category name is trimmed here, at ingest — the live catalog returns some names with trailing
|
|
18
|
+
* whitespace (e.g. `"Plug-in Home Battery "`), and normalising once at the source means every consumer
|
|
19
|
+
* (a device's `identity().category`, `detectSolixCapabilities`, and any exact-match category test) sees
|
|
20
|
+
* the clean name, rather than each call site having to remember to trim.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildModelIndex(categories: SolixProductCategory[]): Map<string, {
|
|
23
|
+
name: string;
|
|
24
|
+
category: string;
|
|
25
|
+
}>;
|