@mega-yfue/eufy-sdk 0.2.0-beta.21 → 0.2.0-beta.23
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/index.js +147 -16
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/index.d.ts +1 -1
- package/dist/model/capabilities/types.d.ts +48 -7
- package/dist/model/capabilities/vacuum-clean.d.ts +59 -0
- package/dist/transport/mqtt/index.d.ts +1 -0
- package/dist/transport/mqtt/solix-mqtt.d.ts +43 -4
- package/package.json +1 -1
|
@@ -481,7 +481,7 @@ export declare function buildActions(caps: readonly Capability[], deps: MemberDe
|
|
|
481
481
|
* @internal
|
|
482
482
|
*/
|
|
483
483
|
export declare function describeCapabilities(bound: Partial<DeviceActionMap>, ctx?: AvailabilityContext): CapabilityDescriptor[];
|
|
484
|
-
export type { CommandContext, CapabilityActions, CapabilityStateReader, CapabilityModule, DetectionSpec, CapabilityFrame, CapabilityEvent, InboundSignal, EventMapping, ProductLine, ActionArgSpec, DecodedState, } from "./types.js";
|
|
484
|
+
export type { CommandContext, CapabilityActions, CapabilityStateReader, CapabilityModule, DetectionSpec, CapabilityFrame, CapabilityEvent, InboundSignal, EventClaim, EventMapping, ProductLine, ActionArgSpec, DecodedState, } from "./types.js";
|
|
485
485
|
/**
|
|
486
486
|
* The value-kind vocabulary a read is annotated with, re-exported from the barrel that publishes the
|
|
487
487
|
* read itself, so the union a member's `kind` is drawn from is reachable from the same import.
|
|
@@ -136,6 +136,45 @@ export interface EventMapping {
|
|
|
136
136
|
* capture. Return `{}` when this signal doesn't carry the field, so nothing is invented.
|
|
137
137
|
*/
|
|
138
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;
|
|
139
178
|
}
|
|
140
179
|
/**
|
|
141
180
|
* A decoded inbound event a capability wants surfaced on the SDK. `event` is the EufyMega event
|
|
@@ -197,6 +236,15 @@ export interface AvailabilityContext {
|
|
|
197
236
|
* `undefined` as an empty set — `ctx.paramIds?.has(dp) ?? false`.
|
|
198
237
|
*/
|
|
199
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;
|
|
200
248
|
}
|
|
201
249
|
export interface CommandContext extends AvailabilityContext {
|
|
202
250
|
/** Device channel (0 for standalone, `device_channel` on a HomeBase). */
|
|
@@ -259,13 +307,6 @@ export interface CommandContext extends AvailabilityContext {
|
|
|
259
307
|
* capability uses this to route lock/unlock to P2P vs. reject with a clear MQTT-not-wired error.
|
|
260
308
|
*/
|
|
261
309
|
hasP2p?: boolean;
|
|
262
|
-
/**
|
|
263
|
-
* Whether the device hangs off a HomeBase (a `parent_sn` other than its own) rather than standing
|
|
264
|
-
* alone. A DEVICE fact, not a transport one — the same class of routing evidence as {@link hasP2p}.
|
|
265
|
-
* The `rtsp` capability gates on it because a station serves an attached camera's stream itself and
|
|
266
|
-
* ignores that camera's authentication setting, so the write cannot do what its name promises there.
|
|
267
|
-
*/
|
|
268
|
-
homeBaseAttached?: boolean;
|
|
269
310
|
/**
|
|
270
311
|
* Parsed `get_product_data_point` catalog for this device's SKU — present for vacuum/mower devices,
|
|
271
312
|
* absent for all other codecs. Capabilities use it for per-model feature-availability and value-range
|
|
@@ -379,6 +379,23 @@ export type CarpetStrategy = (typeof CARPET_STRATEGIES)[number];
|
|
|
379
379
|
*/
|
|
380
380
|
export declare const CLEAN_EXTENTS: readonly ["normal", "narrow", "quick"];
|
|
381
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;
|
|
382
399
|
/**
|
|
383
400
|
* Read one setting out of the CONFIGURED `CleanParam` (DP 154), by its field number.
|
|
384
401
|
*
|
|
@@ -1963,6 +1980,48 @@ export declare const VACUUM_CLEAN_MEMBERS: {
|
|
|
1963
1980
|
readonly startScene: import("./members.js").MethodMember<(sceneId: number) => Promise<void>> & {
|
|
1964
1981
|
available: (ctx: import("./types.js").CommandContext) => boolean;
|
|
1965
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
|
+
};
|
|
1966
2025
|
/**
|
|
1967
2026
|
* Clean the named rooms of a named map (ModeCtrlRequest method 1 over DP 152).
|
|
1968
2027
|
*
|
|
@@ -5,3 +5,4 @@ export * from "./broker-discovery.js";
|
|
|
5
5
|
export * from "./biz-stream.js";
|
|
6
6
|
export { SolixMqtt } from "./solix-mqtt.js";
|
|
7
7
|
export type { SolixMqttOptions, SolixMqttDevice, SolixReading, SolixParamFrame, SolixChannel } from "./solix-mqtt.js";
|
|
8
|
+
export { SOLIX_MODBUS_EMS_MODES } from "./solix-mqtt.js";
|
|
@@ -46,10 +46,20 @@ export interface SolixParamFrame {
|
|
|
46
46
|
* slots read 0 on a single-CT install.
|
|
47
47
|
*
|
|
48
48
|
* The frame carries sixteen float slots (`0xa8`..`0xb7`). The four that name no field — `0xb2`, `0xb5`,
|
|
49
|
-
* `0xb6`, `0xb7` — stay raw `channel_<hex tag>` (see {@link solixReadings}). `0xb2`
|
|
50
|
-
*
|
|
51
|
-
* `0xb2`
|
|
52
|
-
*
|
|
49
|
+
* `0xb6`, `0xb7` — stay raw `channel_<hex tag>` (see {@link solixReadings}). Both `0xb2` and `0xb7` read
|
|
50
|
+
* zero at idle and non-zero under load, so they carry *something* load-related; what, is not established.
|
|
51
|
+
* `0xb2` is dimensionally consistent with **power factor** and rules **reactive power** out: on the same
|
|
52
|
+
* frame the line reads ~240 V at 1.371 A (apparent power S = V·I ≈ 328 VA), so a reactive-power slot would
|
|
53
|
+
* read in the hundreds of VAR, not `0xb2`'s 0.009 — whereas a power factor P/S is a sub-unity ratio of the
|
|
54
|
+
* right magnitude (~0.008). It is left raw regardless, since a single frame doesn't pin it. `0xb7` (~0.1
|
|
55
|
+
* under load) has no such magnitude tell and stays fully open.
|
|
56
|
+
*
|
|
57
|
+
* Each name is annotated with the equivalent register from Anker's OWN vendor integration for the
|
|
58
|
+
* newer Modbus-TCP meter generation (Smart Meter Gen 2), which independently corroborates the meaning
|
|
59
|
+
* of each tag: our `meterPowerL1` is their `primary_phase_1_active_power`, and so on. Same physical
|
|
60
|
+
* quantities, different hardware/transport (their meter reports two CT channels — `primary` and
|
|
61
|
+
* `secondary` — and also exposes `reactive_power`, `power_factor` and per-phase energy, none of which
|
|
62
|
+
* this single-channel ff09 frame carries).
|
|
53
63
|
*
|
|
54
64
|
* This table is **meter-family-specific**: the same tag carries a different quantity on another Solix
|
|
55
65
|
* device (a Solarbank's `0xac` reads a power value, not a voltage), so {@link solixReadings} applies
|
|
@@ -92,6 +102,13 @@ export declare const SOLIX_SOLARBANK_PRODUCT_PREFIX = "AE10";
|
|
|
92
102
|
* (a uint8) and temperature from the `0xa4` BMS status blob. The 4 PV-string channels (`0xc6`–`0xc9`),
|
|
93
103
|
* the AC currents (`0xb2`/`0xb3`) and export energy (`0xb4`) are not yet confirmed, so they stay raw
|
|
94
104
|
* `channel_<hex>` until a capture pins them.
|
|
105
|
+
*
|
|
106
|
+
* Names are annotated with the equivalent register from Anker's OWN vendor integration for the newer
|
|
107
|
+
* Modbus-TCP Solarbank generation (which includes a "Solarbank 4 E5000 Pro" config — the same product as
|
|
108
|
+
* `AE103`, a newer hardware rev), cross-checking each meaning. Their integration splits our signed
|
|
109
|
+
* `batteryPower` into `battery_charging_power` / `battery_discharging_power` off one register, and exposes
|
|
110
|
+
* a single `pv_power` total rather than our four per-string channels; `socketPower` (the on-board AC
|
|
111
|
+
* outlet) has no register there. Same quantities, different transport.
|
|
95
112
|
*/
|
|
96
113
|
export declare const SOLIX_SOLARBANK_FIELD_NAMES: Readonly<Record<number, string>>;
|
|
97
114
|
/**
|
|
@@ -103,6 +120,28 @@ export declare const SOLIX_SOLARBANK_FIELD_NAMES: Readonly<Record<number, string
|
|
|
103
120
|
* stays raw `state_<hex>` until confirmed the same way.
|
|
104
121
|
*/
|
|
105
122
|
export declare const SOLIX_STATE_FIELD_NAMES: Readonly<Record<number, string>>;
|
|
123
|
+
/**
|
|
124
|
+
* The Solarbank EMS `operating_mode` enumeration from Anker's OWN vendor integration for the newer
|
|
125
|
+
* **Modbus-TCP** hardware rev (Solarbank 4 E5000 Pro, register `operating_mode` gated by the `0x8006`
|
|
126
|
+
* capability mask). Value → English label:
|
|
127
|
+
* - `0` selfConsumption — "Self-Consumption Mode"
|
|
128
|
+
* - `1` timeOfUse — "Time Of Use Mode"
|
|
129
|
+
* - `3` thirdPartyControl — "Third-Party Controlled"
|
|
130
|
+
* - `4` custom — "Custom Mode"
|
|
131
|
+
* - `5` socketOverlay — "Socket Overlay Mode"
|
|
132
|
+
* - `6` smart — "Smart Mode"
|
|
133
|
+
* - `7` dynamicTariff — "Dynamic Tariff Mode"
|
|
134
|
+
*
|
|
135
|
+
* Value `2` is unassigned there — seven modes across `{0,1,3,4,5,6,7}`, not eight.
|
|
136
|
+
*
|
|
137
|
+
* NOT a decoder for this SDK's ff09 `mode` (`state_info` tag `0xa9`): that OLDER cloud/MQTT `AE103`
|
|
138
|
+
* numbering is DIFFERENT on every value — `1`=custom, `2`=self-consumption, `4`=rapid charge, `7`=smart,
|
|
139
|
+
* `8`=dynamic tariff (recorded on the `0xa9` field above, correlated against the app). Labelling an ff09
|
|
140
|
+
* `mode` value with this Modbus map would be confidently wrong. It is exported as the vendor's own
|
|
141
|
+
* reference enumeration and the thing an AE103 `0xa9` correlation capture would be checked against —
|
|
142
|
+
* fold the two only if such a capture proves the numbers match.
|
|
143
|
+
*/
|
|
144
|
+
export declare const SOLIX_MODBUS_EMS_MODES: Readonly<Record<number, string>>;
|
|
106
145
|
/**
|
|
107
146
|
* Decode a `state_info` ff09 frame to named + raw settings values. Skips the header tags (`< 0xa5`:
|
|
108
147
|
* request marker, serial, timestamps). Each settings tag is emitted under `state_<hex>` (a plain number
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mega-yfue/eufy-sdk",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.23",
|
|
4
4
|
"description": "One typed TypeScript client for the Anker eufy v6 cloud — capability-driven devices, realtime events over P2P/MQTT/push, and live media",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "mega-yfue",
|