@mega-yfue/eufy-sdk 0.2.0-beta.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/device-registry.d.ts +14 -28
- package/dist/client/eufy-mega.d.ts +11 -6
- package/dist/client/types.d.ts +6 -2
- package/dist/core/contracts.d.ts +23 -27
- package/dist/core/crypto.d.ts +10 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/solix-types.d.ts +121 -0
- package/dist/core/store.d.ts +38 -10
- package/dist/index.js +2709 -503
- 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/doorbell.d.ts +24 -14
- package/dist/model/capabilities/index.d.ts +14 -3
- 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 +60 -10
- package/dist/model/capabilities/vacuum-clean.d.ts +59 -0
- 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/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/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 +32 -5
- package/dist/transport/p2p/media.d.ts +2 -2
- 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 +5 -5
|
@@ -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
|
|
@@ -169,8 +208,13 @@ export interface DecodedState {
|
|
|
169
208
|
* the manifest path and the command path.
|
|
170
209
|
*/
|
|
171
210
|
export interface AvailabilityContext {
|
|
172
|
-
/**
|
|
173
|
-
|
|
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;
|
|
174
218
|
/** eufy DeviceType, when known. */
|
|
175
219
|
deviceType?: number;
|
|
176
220
|
/** Model / T-code, when known. */
|
|
@@ -192,6 +236,15 @@ export interface AvailabilityContext {
|
|
|
192
236
|
* `undefined` as an empty set — `ctx.paramIds?.has(dp) ?? false`.
|
|
193
237
|
*/
|
|
194
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;
|
|
195
248
|
}
|
|
196
249
|
export interface CommandContext extends AvailabilityContext {
|
|
197
250
|
/** Device channel (0 for standalone, `device_channel` on a HomeBase). */
|
|
@@ -242,7 +295,11 @@ export interface CommandContext extends AvailabilityContext {
|
|
|
242
295
|
adminUserId?: string;
|
|
243
296
|
/** The acting member's short id (`member.short_user_id`, hex, e.g. `"0003"`) — the lock cmd `A5` field. */
|
|
244
297
|
shortUserId?: string;
|
|
245
|
-
/**
|
|
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
|
+
*/
|
|
246
303
|
accountName?: string;
|
|
247
304
|
/**
|
|
248
305
|
* Whether the device has a usable P2P endpoint (a non-empty `p2p_did`). A HomeBase-attached lock
|
|
@@ -250,13 +307,6 @@ export interface CommandContext extends AvailabilityContext {
|
|
|
250
307
|
* capability uses this to route lock/unlock to P2P vs. reject with a clear MQTT-not-wired error.
|
|
251
308
|
*/
|
|
252
309
|
hasP2p?: boolean;
|
|
253
|
-
/**
|
|
254
|
-
* Whether the device hangs off a HomeBase (a `parent_sn` other than its own) rather than standing
|
|
255
|
-
* alone. A DEVICE fact, not a transport one — the same class of routing evidence as {@link hasP2p}.
|
|
256
|
-
* The `rtsp` capability gates on it because a station serves an attached camera's stream itself and
|
|
257
|
-
* ignores that camera's authentication setting, so the write cannot do what its name promises there.
|
|
258
|
-
*/
|
|
259
|
-
homeBaseAttached?: boolean;
|
|
260
310
|
/**
|
|
261
311
|
* Parsed `get_product_data_point` catalog for this device's SKU — present for vacuum/mower devices,
|
|
262
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
|
*
|
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";
|
|
@@ -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
|
+
}>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type SolixCapability, type SolixEnergyMeterReads } from "./capabilities/solix.js";
|
|
2
|
+
import { type SolixProductFamily } from "./solix-family.js";
|
|
3
|
+
import type { SolixDeviceRecord, SolixProductCategory, SolixSiteScene } from "../core/solix-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The device record shape, re-exported from the model surface. It lives in `core/solix-types` so the
|
|
6
|
+
* transport client can return it without crossing the transport↔model line.
|
|
7
|
+
*/
|
|
8
|
+
export type { SolixDeviceRecord } from "../core/solix-types.js";
|
|
9
|
+
export interface SolixIdentity {
|
|
10
|
+
serial: string;
|
|
11
|
+
productCode: string;
|
|
12
|
+
/** Friendly name — the catalog marketing name if resolvable, else the record's alias/name. */
|
|
13
|
+
name: string;
|
|
14
|
+
/** Anker catalog category (e.g. "Accessory", "Portable Power Station"), if resolvable. */
|
|
15
|
+
category?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Normalized product family — the "what kind of thing is this" answer (power station, Solarbank,
|
|
18
|
+
* smart meter, …), decorrelated from the marketing `category` string. See {@link SolixDevice.family}.
|
|
19
|
+
*/
|
|
20
|
+
family: SolixProductFamily;
|
|
21
|
+
}
|
|
22
|
+
export interface SolixConnectivity {
|
|
23
|
+
online: boolean;
|
|
24
|
+
rssi?: number;
|
|
25
|
+
ssid?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The `dev.energyMeter()` handle: the members-derived reads ({@link SolixEnergyMeterReads} —
|
|
29
|
+
* `meterVoltageL1` is present only once a frame carrying its tag has landed). Every not-yet-named meter
|
|
30
|
+
* quantity is read from {@link SolixDevice.telemetry} under its `channel_<hex tag>` key instead, which a
|
|
31
|
+
* static members table cannot enumerate.
|
|
32
|
+
*/
|
|
33
|
+
export type SolixEnergyMeter = SolixEnergyMeterReads;
|
|
34
|
+
/** Options for {@link SolixDevice}. */
|
|
35
|
+
export interface SolixDeviceOptions {
|
|
36
|
+
/** Catalog categories (from `SolixClient.getProductCatalog()`) — used to resolve name + category. */
|
|
37
|
+
catalog?: SolixProductCategory[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A discovered Solix device with resolved category + capabilities. Feed live telemetry with
|
|
41
|
+
* {@link applyReading} (from {@link SolixMqtt}'s `reading` events) to populate value accessors.
|
|
42
|
+
*/
|
|
43
|
+
export declare class SolixDevice {
|
|
44
|
+
readonly serial: string;
|
|
45
|
+
readonly productCode: string;
|
|
46
|
+
readonly record: SolixDeviceRecord;
|
|
47
|
+
private readonly caps;
|
|
48
|
+
private readonly identity_;
|
|
49
|
+
private values;
|
|
50
|
+
constructor(record: SolixDeviceRecord, opts?: SolixDeviceOptions);
|
|
51
|
+
/**
|
|
52
|
+
* The device's {@link SolixProductFamily} — the classification a caller branches on to SORT devices
|
|
53
|
+
* (a site's power stations vs its meters), the Solix analogue of eufy's `isHomeBase()`. Distinct from
|
|
54
|
+
* {@link has}, which answers what the device can DO; family answers what KIND of device it is.
|
|
55
|
+
*/
|
|
56
|
+
get family(): SolixProductFamily;
|
|
57
|
+
/** All capabilities this device carries. */
|
|
58
|
+
get capabilities(): SolixCapability[];
|
|
59
|
+
/** Whether the device carries a capability — the only correct way to branch on behaviour. */
|
|
60
|
+
has(capability: SolixCapability): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Merge a live telemetry reading (a `SolixMqtt` `reading` event) so accessors reflect it. Takes the
|
|
63
|
+
* WHOLE reading, not just its values, and drops one addressed to a different device: the documented
|
|
64
|
+
* wiring is `mqtt.on("reading", r => device.applyReading(r))`, and one MQTT stream carries every
|
|
65
|
+
* watched meter on the account — so without this filter two meters would cross-feed each other's floats.
|
|
66
|
+
* A reading with no `deviceSn` (a hand-built one) is accepted as-is.
|
|
67
|
+
*/
|
|
68
|
+
applyReading(reading: {
|
|
69
|
+
deviceSn?: string;
|
|
70
|
+
values: Record<string, number>;
|
|
71
|
+
}): void;
|
|
72
|
+
/** All decoded float telemetry channels from the latest applied reading (raw, `channel_<tag>` keys). */
|
|
73
|
+
telemetry(): Record<string, number>;
|
|
74
|
+
identity(): SolixIdentity;
|
|
75
|
+
firmware(): {
|
|
76
|
+
version: string;
|
|
77
|
+
} | undefined;
|
|
78
|
+
connectivity(): SolixConnectivity | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* The members-derived `energyMeter` reads, or `undefined` when the device has no meter. Each getter is
|
|
81
|
+
* installed only for a tag this device has actually reported, and reads `this.values` LIVE — a handle
|
|
82
|
+
* held across an {@link applyReading} reflects the newer values. Getter INSTALLATION is fixed at the
|
|
83
|
+
* time of this call, so re-call it to pick up a tag first seen since.
|
|
84
|
+
*/
|
|
85
|
+
energyMeter(): SolixEnergyMeter | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* The {@link MemberDeps} the members engine needs: a read closure over the live values store, and the
|
|
88
|
+
* evidence gate (`ctx.paramIds`) rebuilt from the ff09 tags this device has reported. No `codec` — the
|
|
89
|
+
* codecs are eufy transport families and a Solix device belongs to none of them. The sink is a no-op:
|
|
90
|
+
* Solix telemetry is read-only, no member here dispatches a command.
|
|
91
|
+
*/
|
|
92
|
+
private meterDeps;
|
|
93
|
+
/** The ff09 tags this device has reported, derived from the decoder's `channel_<hex>` keys. */
|
|
94
|
+
private seenTags;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The minimum a client must offer to be discovered against — the two wire reads {@link discoverSolixDevices}
|
|
98
|
+
* composes. Typed STRUCTURALLY (not as `SolixClient`) so the model layer never imports the transport
|
|
99
|
+
* client: the hard `transport ⊥ model` rule forbids it, and a structural shape needs no import.
|
|
100
|
+
*/
|
|
101
|
+
export interface SolixDeviceReader {
|
|
102
|
+
getDevices(): Promise<SolixDeviceRecord[]>;
|
|
103
|
+
getProductCatalog(): Promise<SolixProductCategory[]>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the product catalog for a discovery: a caller-supplied `opts.catalog` short-circuits the wire
|
|
107
|
+
* read, and a failed `getProductCatalog()` degrades to no categories (names/families just go unresolved,
|
|
108
|
+
* never a thrown discovery). Shared by {@link discoverSolixDevices} and `discoverSolixSites` so that
|
|
109
|
+
* "a failed catalog is non-fatal" decision lives in exactly one place.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolveSolixCatalog(client: SolixDeviceReader, opts: {
|
|
112
|
+
catalog?: SolixProductCategory[];
|
|
113
|
+
}): Promise<SolixProductCategory[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Discover an account's Solix devices as capability-driven {@link SolixDevice} objects — the wire+model
|
|
116
|
+
* composition (a transport read + the product catalog) that used to be `SolixClient.discoverDevices()`.
|
|
117
|
+
* It lives in the model layer because it builds `SolixDevice`; the wire client (now `transport/http`)
|
|
118
|
+
* cannot, and passing it structurally keeps the layers decorrelated. Feed live telemetry to each result
|
|
119
|
+
* via `SolixDevice.applyReading`.
|
|
120
|
+
*/
|
|
121
|
+
export declare function discoverSolixDevices(client: SolixDeviceReader, opts?: {
|
|
122
|
+
catalog?: SolixProductCategory[];
|
|
123
|
+
}): Promise<SolixDevice[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Reduce a site "scene" snapshot to per-device telemetry readings — the BACKSTOP counterpart to the
|
|
126
|
+
* realtime `ff09` decode. It emits the fields the scene reliably carries that the fast MQTT frame does
|
|
127
|
+
* NOT: `batteryTemperature` (the realtime frame's BMS blob is empty, so `solixReadings` withholds it),
|
|
128
|
+
* `batterySoc` (a cross-check/seed for the `0xa3` SOC), and `expansionPacks` — the count of ATTACHED
|
|
129
|
+
* add-on battery packs (0 on a standalone main unit). Each reading is shaped exactly like a `SolixMqtt`
|
|
130
|
+
* `reading` event — `{ deviceSn, values }` — so a caller can feed it straight into
|
|
131
|
+
* {@link SolixDevice.applyReading} and broadcast it on the same path as a live frame. Entries with no
|
|
132
|
+
* usable value are dropped.
|
|
133
|
+
*/
|
|
134
|
+
export declare function solarbankSceneReadings(scene: SolixSiteScene): {
|
|
135
|
+
deviceSn: string;
|
|
136
|
+
values: Record<string, number>;
|
|
137
|
+
}[];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Solix device's product family — the normalized "what kind of thing is this" answer, decorrelated
|
|
3
|
+
* from the Anker catalog's marketing category strings. `unknown` when neither the product code nor the
|
|
4
|
+
* category identifies a family (never a guess).
|
|
5
|
+
*/
|
|
6
|
+
export type SolixProductFamily = "powerStation" | "solarbank" | "smartMeter" | "powerBank" | "cooler" | "evCharger" | "charger" | "unknown";
|
|
7
|
+
/** The pure evidence a family decision reads: the product code, and the catalog category when resolved. */
|
|
8
|
+
export interface SolixFamilyInput {
|
|
9
|
+
/** SKU / model code, e.g. `A1782`, `AE103`, `AE1X0`. */
|
|
10
|
+
product_code: string;
|
|
11
|
+
/** Anker catalog category, when a catalog resolved one (e.g. `"Portable Power Station"`). */
|
|
12
|
+
category?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Grid-tie Solarbank / plug-in home battery family (by product code, catalog-independent). */
|
|
15
|
+
export declare const isSolixSolarbank: (input: SolixFamilyInput) => boolean;
|
|
16
|
+
/** Smart energy meter / grid-CT family (by product code, catalog-independent). */
|
|
17
|
+
export declare const isSolixSmartMeter: (input: SolixFamilyInput) => boolean;
|
|
18
|
+
/** Portable Power Station (SOLIX F-series) family — resolved from the catalog category. */
|
|
19
|
+
export declare const isSolixPowerStation: (input: SolixFamilyInput) => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a Solix device's {@link SolixProductFamily} by consulting the family predicates first (they are
|
|
22
|
+
* catalog-independent, so a device classifies even before a catalog is fetched), then the catalog
|
|
23
|
+
* category for the families that have no prefix set yet. Returns `"unknown"` when neither identifies one —
|
|
24
|
+
* never a guess, mirroring `device-family.ts` returning `false` on an unknown device type.
|
|
25
|
+
*
|
|
26
|
+
* The meter is checked before the Solarbank: both can present a battery-ish catalog category, but the
|
|
27
|
+
* meter's `AE1X0` prefix is unambiguous and a meter is never a Solarbank. The Solarbank case reuses
|
|
28
|
+
* {@link isSolixSolarbank} (prefix OR the home-battery category) rather than re-testing the prefix here,
|
|
29
|
+
* so the two never drift.
|
|
30
|
+
*/
|
|
31
|
+
export declare function solixProductFamily(input: SolixFamilyInput): SolixProductFamily;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A capability-driven model for an Anker Solix **site** — the account's home energy system, the "My
|
|
3
|
+
* Home" the app shows. A site is the Solix analogue of a eufy HomeBase in its GROUPING role: it is the
|
|
4
|
+
* system that member devices belong to, so it is what a caller reaches for to ask "what's in this
|
|
5
|
+
* system" and to sort the members by family ({@link SolixSite.powerStations} / {@link solarbanks} /
|
|
6
|
+
* {@link smartMeters}) or by capability ({@link SolixSite.withCapability}).
|
|
7
|
+
*
|
|
8
|
+
* A site is a *grouping*, not a device: it carries no telemetry of its own. Its members are full
|
|
9
|
+
* {@link SolixDevice} models (each with its own capabilities + live telemetry), resolved by
|
|
10
|
+
* {@link discoverSolixSites} from the site record's member list joined to the account's device records.
|
|
11
|
+
* The realtime SYSTEM aggregate the app draws (instantaneous battery SoC + solar + grid flow) is not
|
|
12
|
+
* modelled here: it has no confirmed, stable read surface for the current device generation, and this
|
|
13
|
+
* layer does not fabricate a getter for a value it cannot ground — a caller reads each member device's
|
|
14
|
+
* telemetry instead.
|
|
15
|
+
*/
|
|
16
|
+
import { SolixDevice, type SolixDeviceReader } from "./solix-device.js";
|
|
17
|
+
import type { SolixProductFamily } from "./solix-family.js";
|
|
18
|
+
import type { SolixCapability } from "./capabilities/solix.js";
|
|
19
|
+
import type { SolixProductCategory, SolixSiteRecord } from "../core/solix-types.js";
|
|
20
|
+
/**
|
|
21
|
+
* The minimum a client must offer to discover an account's SITES against — {@link SolixDeviceReader}
|
|
22
|
+
* (the device + catalog reads {@link discoverSolixDevices} already composes) plus the site read.
|
|
23
|
+
* Structural (not `SolixClient`) so the model layer never imports the transport client: the hard
|
|
24
|
+
* `transport ⊥ model` rule forbids it, and a structural shape needs no import.
|
|
25
|
+
*/
|
|
26
|
+
export interface SolixSiteReader extends SolixDeviceReader {
|
|
27
|
+
getSites(): Promise<SolixSiteRecord[]>;
|
|
28
|
+
}
|
|
29
|
+
/** Options for {@link SolixSite} / {@link discoverSolixSites} — the same catalog the device model takes. */
|
|
30
|
+
export interface SolixSiteOptions {
|
|
31
|
+
catalog?: SolixProductCategory[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A discovered Solix site with its member devices resolved to {@link SolixDevice} models. Build one
|
|
35
|
+
* directly from a record + members, or discover an account's sites with {@link discoverSolixSites}.
|
|
36
|
+
*/
|
|
37
|
+
export declare class SolixSite {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
/** Friendly site name (e.g. "My Home"), or the site id when the record carries none. */
|
|
40
|
+
readonly name: string;
|
|
41
|
+
/** Anker's site-type discriminator (e.g. 20 for a Solarbank-anchored home system), when present. */
|
|
42
|
+
readonly powerSiteType?: number;
|
|
43
|
+
readonly record: SolixSiteRecord;
|
|
44
|
+
readonly devices: SolixDevice[];
|
|
45
|
+
constructor(record: SolixSiteRecord, devices: SolixDevice[]);
|
|
46
|
+
/** The member device with this serial, if it belongs to the site. */
|
|
47
|
+
device(serial: string): SolixDevice | undefined;
|
|
48
|
+
/** Member devices of a given product {@link SolixProductFamily} — the grouping accessor. */
|
|
49
|
+
withFamily(family: SolixProductFamily): SolixDevice[];
|
|
50
|
+
/** Member devices that carry a given capability (e.g. every `battery` in the system). */
|
|
51
|
+
withCapability(capability: SolixCapability): SolixDevice[];
|
|
52
|
+
/** The Solarbank / plug-in home-battery members of the system. */
|
|
53
|
+
solarbanks(): SolixDevice[];
|
|
54
|
+
/** The smart-meter (grid-CT) members of the system. */
|
|
55
|
+
smartMeters(): SolixDevice[];
|
|
56
|
+
/** The portable power-station members of the system. */
|
|
57
|
+
powerStations(): SolixDevice[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Discover an account's Solix sites as {@link SolixSite} groupings of capability-driven
|
|
61
|
+
* {@link SolixDevice} members — the site analogue of {@link discoverSolixDevices}. Composes three wire
|
|
62
|
+
* reads (the sites, the account's device records, the product catalog) entirely model-side, so the
|
|
63
|
+
* transport client is passed structurally and the layers stay decorrelated.
|
|
64
|
+
*
|
|
65
|
+
* A site member is resolved to its FULL device record from `getDevices()` when the account lists one
|
|
66
|
+
* (so the member carries firmware/connectivity + telemetry identity); a member the flat device list
|
|
67
|
+
* omits is built from the site entry alone (serial + product code + name), so the system is never
|
|
68
|
+
* missing a device it declares.
|
|
69
|
+
*/
|
|
70
|
+
export declare function discoverSolixSites(client: SolixSiteReader, opts?: SolixSiteOptions): Promise<SolixSite[]>;
|
package/dist/transport/ff09.d.ts
CHANGED
|
@@ -397,6 +397,13 @@ export interface Ff09SettingsResponse {
|
|
|
397
397
|
* padding past the last real field.
|
|
398
398
|
*/
|
|
399
399
|
export declare function parseFf09SettingsResponse(plain: Buffer): Ff09SettingsResponse;
|
|
400
|
+
/**
|
|
401
|
+
* Walk a bounded `tag|len|value` TLV region into a `tag → bytes` map. Stops at a `0x00` tag (only ever
|
|
402
|
+
* trailing zero-padding, never a real field — real tags start at `0xa1`) and refuses a field whose
|
|
403
|
+
* declared length would overrun `end`, so a corrupt length can't read past the region (e.g. into a
|
|
404
|
+
* trailing checksum). Shared by {@link parseFf09SettingsResponse} and the Solix param decoder.
|
|
405
|
+
*/
|
|
406
|
+
export declare function walkFf09Tlv(buf: Buffer, start: number, end: number): Map<number, Buffer>;
|
|
400
407
|
/** Read a little-endian u16 out of a TLV field buffer (throws on a missing/short field — a caller-side bug, not a wire ambiguity). */
|
|
401
408
|
export declare function readFf09U16LE(field: Buffer | undefined, name: string): number;
|
|
402
409
|
/** Read a single byte out of a TLV field buffer (throws on a missing field — same convention as {@link readFf09U16LE}). */
|
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-channel auto-contrast, a byte-exact port of PIL `ImageOps.autocontrast` (cutoff 0.5%). The lost
|
|
3
|
-
* quant tables leave the reconstruction low-contrast ("foggy"); stretching each channel's clipped range
|
|
4
|
-
* to full scale restores a natural-looking image. Mutates `data` (RGBA) in place.
|
|
5
|
-
*
|
|
6
|
-
* Parity notes vs PIL: the cutoff count is `n * cutoff // 100` (integer floor); it is trimmed off each
|
|
7
|
-
* end by zeroing whole histogram bins until the count is spent; the range is then the first/last
|
|
8
|
-
* non-empty bins; and the LUT truncates toward zero (`int()`), NOT rounds — rounding would shift pixels.
|
|
9
|
-
*/
|
|
10
|
-
export declare function autoContrast(data: Uint8Array, width: number, height: number, cutoff?: number): void;
|
|
11
1
|
/** True if the blob is a v2 `v2_eufysecurity:` push thumbnail. */
|
|
12
2
|
export declare function isV2Image(data: Buffer): boolean;
|
|
13
3
|
/**
|
|
14
|
-
* Decode a v2 blob to a plain JPEG buffer by reconstructing its header, or null if it isn't v2
|
|
15
|
-
* plaintext scan can't be located
|
|
16
|
-
*
|
|
17
|
-
*
|
|
4
|
+
* Decode a v2 blob to a plain JPEG buffer by reconstructing its header, or null if it isn't v2, the
|
|
5
|
+
* plaintext scan can't be located, or no frame shape explains it.
|
|
6
|
+
*
|
|
7
|
+
* Three steps and no pixel rewrite: the frame shape is read out of the entropy scan,
|
|
8
|
+
* one probe decode both proves the spliced JPEG decodes and measures how far the substitute quant
|
|
9
|
+
* tables fall short of the camera's, and the answer is the same tail under a header carrying the
|
|
10
|
+
* corrected tables. See the module doc for the keyless-splice rationale and for why neither the search
|
|
11
|
+
* nor the correction decodes candidate frames or re-encodes the picture.
|
|
18
12
|
*/
|
|
19
13
|
export declare function decodeImageV2(data: Buffer): Buffer | null;
|
|
@@ -3,3 +3,4 @@ export { randomPhoneModel, randomUserAgent } from "./phone-model.js";
|
|
|
3
3
|
export * from "./decodeImageV1.js";
|
|
4
4
|
export * from "./decodeImageV2.js";
|
|
5
5
|
export { listLightEffects, listAiSceneRecommendations, type LightEffectSummary } from "./light-catalog.js";
|
|
6
|
+
export { SolixClient, type SolixClientOptions, type SolixLoginResult, type SolixSession, type SolixPersisted, type SolixSessionStore, } from "./solix-client.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A baseline-JPEG entropy scanner that decodes no pixels.
|
|
3
|
+
*
|
|
4
|
+
* The v2 thumbnail decoder next door has to discover a frame geometry its blob does not state, and the
|
|
5
|
+
* only evidence is the plaintext entropy-coded scan: how many MCUs it carries, and whether it carries
|
|
6
|
+
* them under a given chroma subsampling. A JPEG decoder answers that — it throws on a frame the scan
|
|
7
|
+
* does not fill — and charges a full set of component and output buffers for each question.
|
|
8
|
+
*
|
|
9
|
+
* That bill is fatal for a caller under a hard memory cap. Each `jpeg-js` decode churns roughly a
|
|
10
|
+
* megabyte of typed arrays, glibc keeps the arenas rather than handing them back, and a search asking
|
|
11
|
+
* the question of every candidate geometry costs tens of megabytes per thumbnail — permanently, and
|
|
12
|
+
* more than an embedded host gives an app in total.
|
|
13
|
+
*
|
|
14
|
+
* This module asks the same question by walking the Huffman-coded coefficients and throwing them away:
|
|
15
|
+
* no IDCT, no component planes, no output image. What it keeps is one number per MCU — the DC
|
|
16
|
+
* coefficient of its luma block(s), i.e. that block's average brightness — which is all the geometry
|
|
17
|
+
* search needs to tell a sheared row apart from a continuous one. Allocation is a single `Int32Array`
|
|
18
|
+
* of MCU count, and the walk is linear in the scan's length.
|
|
19
|
+
*
|
|
20
|
+
* Baseline sequential only (SOF0), which is what the v2 tail is: no progressive refinement, no
|
|
21
|
+
* arithmetic coding. Restart markers are tolerated — the scan resynchronises and resets its DC
|
|
22
|
+
* predictors, exactly as a decoder would.
|
|
23
|
+
*
|
|
24
|
+
* @module transport/http/jpeg-scan
|
|
25
|
+
*/
|
|
26
|
+
/** What the scan carried, under one subsampling hypothesis. */
|
|
27
|
+
export interface EntropyScan {
|
|
28
|
+
/** Complete MCUs decoded before the data ran out. */
|
|
29
|
+
mcus: number;
|
|
30
|
+
/**
|
|
31
|
+
* Mean luma DC per MCU, in scan order — a thumbnail of the picture at MCU resolution.
|
|
32
|
+
*
|
|
33
|
+
* Quantized units (the quant tables are lost with the v2 head), so the values are a scale of their
|
|
34
|
+
* own. Differences between neighbours are what the geometry search reads, and those survive.
|
|
35
|
+
*/
|
|
36
|
+
luma: Int32Array;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the scan ended where a whole MCU ended, with nothing but the EOI marker left.
|
|
39
|
+
*
|
|
40
|
+
* The discriminator between hypotheses: a wrong one reads a block with the wrong Huffman table,
|
|
41
|
+
* diverges, and either dies mid-MCU or stops with data still ahead of it. Only the subsampling the
|
|
42
|
+
* encoder used walks the scan to its last byte on an MCU boundary.
|
|
43
|
+
*/
|
|
44
|
+
complete: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Walk the plaintext tail's scan under one subsampling hypothesis.
|
|
48
|
+
*
|
|
49
|
+
* `extraTables` carries the DHT segments that are NOT in the tail — for a v2 thumbnail, the standard
|
|
50
|
+
* luma tables the reconstructed header supplies, since only the chroma ones survive in plaintext.
|
|
51
|
+
* Returns null when the tail is not a baseline scan at all (no SOS, missing tables).
|
|
52
|
+
*
|
|
53
|
+
* The walk stops at the first byte it cannot read as this hypothesis's next block, and the result is
|
|
54
|
+
* `complete` only when that happened on an MCU boundary with nothing but a marker left. The DC array
|
|
55
|
+
* grows as the scan turns out to be long rather than being sized from the scan's byte length: a wrong
|
|
56
|
+
* hypothesis dies after a handful of MCUs, and provisioning three whole-scan arrays to discover that is
|
|
57
|
+
* the kind of allocation this module exists to avoid.
|
|
58
|
+
*/
|
|
59
|
+
export declare function scanEntropy(tail: Uint8Array, subsampling: number, extraTables: readonly Uint8Array[]): EntropyScan | null;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { MediaFailureReason } from "../media-failure.js";
|
|
1
2
|
type ResolveHost = (hostname: string) => Promise<readonly {
|
|
2
3
|
address: string;
|
|
3
4
|
family: number;
|
|
4
5
|
}[]>;
|
|
6
|
+
/** Tag a failure OUTSIDE the transfer itself — the push-image decoder refusing bytes that did arrive. @internal */
|
|
7
|
+
export declare function mediaFailureError(message: string, reason: MediaFailureReason, cause?: unknown): Error;
|
|
5
8
|
/** Signals rejection of the active Eufy session without exposing response content. @internal */
|
|
6
9
|
export declare class MediaDownloadAuthenticationError extends Error {
|
|
7
10
|
}
|