@mega-yfue/eufy-sdk 0.2.0-beta.16 → 0.2.0-beta.17
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 +101 -10
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/solix.d.ts +102 -15
- package/dist/transport/mqtt/solix-mqtt.d.ts +37 -13
- package/package.json +1 -1
|
@@ -14,26 +14,27 @@ import type { Surface } from "./members.js";
|
|
|
14
14
|
/** Every capability a Solix device may carry. Solix's OWN union (not eufy's `Capability`). */
|
|
15
15
|
export type SolixCapability = "identity" | "firmware" | "connectivity" | "energyMeter" | "battery" | "solarInput" | "acOutput" | "evCharger" | "charger" | "cooler";
|
|
16
16
|
/**
|
|
17
|
-
* The `energyMeter` surface
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
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`.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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.
|
|
27
32
|
*
|
|
28
33
|
* @internal — the declaration `SolixEnergyMeterReads` derives from; exported (like the eufy `*_MEMBERS`
|
|
29
34
|
* tables) so it is a known symbol, but excluded from the rendered API reference.
|
|
30
35
|
*/
|
|
31
36
|
export declare const SOLIX_ENERGY_METER_MEMBERS: {
|
|
32
|
-
/**
|
|
33
|
-
* Line-1 voltage (V), ff09 tag `0xAC` — the ONE confirmed meter binding, matched against a live
|
|
34
|
-
* single-phase frame (a nominal mains voltage). Read-only; the evidence gate installs its getter only
|
|
35
|
-
* once a frame carrying `0xAC` has landed, so it is absent (not a fabricated `0`) until then.
|
|
36
|
-
*/
|
|
37
|
+
/** Line-1 voltage (V), ff09 tag `0xAC` — confirmed live (a nominal mains voltage). */
|
|
37
38
|
readonly meterVoltageL1: {
|
|
38
39
|
readonly param: 172;
|
|
39
40
|
readonly type: "number";
|
|
@@ -42,6 +43,87 @@ export declare const SOLIX_ENERGY_METER_MEMBERS: {
|
|
|
42
43
|
readonly provenance: "verified";
|
|
43
44
|
readonly description: "Meter line-1 voltage (V) — ff09 tag 0xAC, confirmed against a live single-phase frame.";
|
|
44
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
|
+
};
|
|
45
127
|
};
|
|
46
128
|
/** Bound `energyMeter` reads (the members-derived half of `dev.energyMeter()`). Read-only. */
|
|
47
129
|
export type SolixEnergyMeterReads = Surface<typeof SOLIX_ENERGY_METER_MEMBERS>;
|
|
@@ -52,7 +134,12 @@ export type SolixEnergyMeterReads = Surface<typeof SOLIX_ENERGY_METER_MEMBERS>;
|
|
|
52
134
|
* Unlisted categories contribute nothing here.
|
|
53
135
|
*/
|
|
54
136
|
export declare const CATEGORY_CAPABILITIES: Readonly<Record<string, readonly SolixCapability[]>>;
|
|
55
|
-
/**
|
|
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
|
+
*/
|
|
56
143
|
export declare const SOLIX_METER_MODELS: readonly string[];
|
|
57
144
|
/**
|
|
58
145
|
* Product-code prefixes for the grid-tie Solarbank / home-battery family (detects `battery` +
|
|
@@ -34,22 +34,43 @@ export interface SolixParamFrame {
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Telemetry field tags for the Smart Meter (AE1X0) that we emit under a stable NAME, keyed by ff09 tag
|
|
37
|
-
* byte.
|
|
37
|
+
* byte. These twelve are the meter fields the vendor app itself names, and their tag→name bindings are
|
|
38
|
+
* confirmed:
|
|
38
39
|
*
|
|
39
|
-
* -
|
|
40
|
+
* - The app's field vocabulary is exactly these twelve — voltage, current and power per line
|
|
41
|
+
* (L1/L2/L3), a power total, and cumulative import/export energy — with no current total, no
|
|
42
|
+
* frequency and no power-factor field.
|
|
43
|
+
* - A live single-phase frame confirms the tag→field magnitudes: `0xac` a nominal mains voltage,
|
|
44
|
+
* `0xa8` == `0xab` an equal power pair (line power equals total on one phase, one of them going
|
|
45
|
+
* negative on export), `0xaf` the line current, `0xb3` a slowly-cumulative import counter; the L2/L3
|
|
46
|
+
* slots read 0 on a single-CT install.
|
|
40
47
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* comment to the map above) as a known-load capture confirms each binding:
|
|
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` in particular is NOT
|
|
50
|
+
* a current total: under a 1.371 A line current it reads 0.009, three orders of magnitude off. Both
|
|
51
|
+
* `0xb2` and `0xb7` read zero at idle and non-zero under load, so they carry *something* load-related;
|
|
52
|
+
* what, is not established.
|
|
47
53
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
54
|
+
* This table is **meter-family-specific**: the same tag carries a different quantity on another Solix
|
|
55
|
+
* device (a Solarbank's `0xac` reads a power value, not a voltage), so {@link solixReadings} applies
|
|
56
|
+
* these names ONLY to a frame from the meter family — see {@link SOLIX_METER_PRODUCT_PREFIXES}. Every
|
|
57
|
+
* measurement tag still surfaces as `channel_<hex tag>` regardless of device, so nothing on the wire is
|
|
58
|
+
* lost; the model layer names non-meter tags per capability.
|
|
51
59
|
*/
|
|
52
60
|
export declare const SOLIX_METER_FIELD_NAMES: Readonly<Record<number, string>>;
|
|
61
|
+
/**
|
|
62
|
+
* Product-code prefixes of the Smart Meter family that {@link SOLIX_METER_FIELD_NAMES} decodes. The table
|
|
63
|
+
* is meter-specific, so {@link solixReadings} applies its named fields ONLY to a frame whose product code
|
|
64
|
+
* starts with one of these; a Solarbank (`AE103`) reporting the same `0xac` tag would otherwise be
|
|
65
|
+
* mislabelled `meterVoltageL1` with a nonsensical (negative-power) value. These are product-code prefixes
|
|
66
|
+
* used to select a decode table — not a model import — so the `transport ⊥ model` rule is untouched.
|
|
67
|
+
*
|
|
68
|
+
* Keep this in lockstep with `SOLIX_METER_MODELS` in `model/capabilities/solix.ts` (the same meter
|
|
69
|
+
* prefixes, model-side): a prefix added there but not here grants `energyMeter` to a device whose frames
|
|
70
|
+
* this decoder then refuses to name, and no guard can catch the split (the model layer can't import
|
|
71
|
+
* transport). Add a meter prefix to both.
|
|
72
|
+
*/
|
|
73
|
+
export declare const SOLIX_METER_PRODUCT_PREFIXES: readonly string[];
|
|
53
74
|
/** Interpret one TLV value as a telemetry channel (leading type byte + payload). */
|
|
54
75
|
export declare function readSolixChannel(value: Buffer | undefined): SolixChannel | undefined;
|
|
55
76
|
/**
|
|
@@ -65,9 +86,12 @@ export declare function decodeSolixParamFrame(buf: Buffer): SolixParamFrame | nu
|
|
|
65
86
|
* carry the field count, the serial and the status, not measurements. A measurement channel is one whose
|
|
66
87
|
* leading type byte is `0x05` (float32 LE over a 4-byte payload); any other type is a non-measurement
|
|
67
88
|
* param and contributes nothing. Each measurement is emitted under `channel_<hex tag>`, and additionally
|
|
68
|
-
* under its name when the tag has a confirmed one in {@link SOLIX_METER_FIELD_NAMES}
|
|
89
|
+
* under its name when the tag has a confirmed one in {@link SOLIX_METER_FIELD_NAMES} AND `productCode` is
|
|
90
|
+
* from the meter family (see {@link SOLIX_METER_PRODUCT_PREFIXES}) — so a non-meter device's tags stay
|
|
91
|
+
* raw `channel_<hex>` rather than borrowing the meter's tag→name table. `productCode` is required (it
|
|
92
|
+
* comes straight from the telemetry topic); pass `""` for a frame of unknown origin and no names apply.
|
|
69
93
|
*/
|
|
70
|
-
export declare function solixReadings(frame: SolixParamFrame): Record<string, number>;
|
|
94
|
+
export declare function solixReadings(frame: SolixParamFrame, productCode: string): Record<string, number>;
|
|
71
95
|
/** A live telemetry sample emitted by {@link SolixMqtt} as a `reading` event. */
|
|
72
96
|
export interface SolixReading {
|
|
73
97
|
deviceSn: string;
|
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.17",
|
|
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",
|