@mega-yfue/eufy-sdk 0.2.0-beta.1 → 0.2.0-beta.3
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/core/contracts.d.ts +55 -0
- package/dist/index.js +109 -24
- package/dist/index.js.map +2 -2
- package/dist/model/capabilities/arming.d.ts +44 -16
- package/dist/model/capabilities/index.d.ts +2 -2
- package/dist/transport/p2p/command-router.d.ts +23 -0
- package/dist/transport/p2p/index.d.ts +1 -0
- package/dist/transport/p2p/live-trace.d.ts +84 -6
- package/dist/transport/p2p/p2p-session.d.ts +4 -0
- package/package.json +1 -1
|
@@ -2,11 +2,15 @@ import { type Surface } from "./members.js";
|
|
|
2
2
|
import type { CapabilityModule, CommandContext } from "./types.js";
|
|
3
3
|
import type { Command } from "../../core/contracts.js";
|
|
4
4
|
/**
|
|
5
|
-
* The guard modes
|
|
6
|
-
*
|
|
5
|
+
* The guard modes `setMode` can SET — the four whose write is confirmed against a real station. Three are
|
|
6
|
+
* byte-exact captures and `custom1` is a live confirmation; `ARMING_MODE_WIRE` carries the per-value
|
|
7
|
+
* evidence. `ArmingMode` is both the const value-object (`ArmingMode.home`) and the union type of its
|
|
7
8
|
* values, so callers pass the named constant: `setMode(ArmingMode.home)`.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* The domain of `setMode` (cmd 1224) alone. The alarm-delay write (cmd 1255) carries its own mode integer
|
|
11
|
+
* on a separate wire and takes {@link AlarmDelayMode}.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately NARROWER than the set a device may report. The remaining five modes are ones the app itself
|
|
10
14
|
* defines and the `mode` read still names them, but no capture shows one being SENT — and
|
|
11
15
|
* on a fire-and-forget wire a wrong one looks exactly like success. Leaving them out of this union is the
|
|
12
16
|
* compile-time half of the refusal; `mode`'s published argument and the generated rejection are the
|
|
@@ -17,10 +21,31 @@ export declare const ArmingMode: {
|
|
|
17
21
|
readonly away: "away";
|
|
18
22
|
/** Armed for occupancy — reduced/perimeter protection while home (wire value 1). */
|
|
19
23
|
readonly home: "home";
|
|
24
|
+
/** Custom 1 — a user-defined posture configured in the app (wire value 3). */
|
|
25
|
+
readonly custom1: "custom1";
|
|
20
26
|
/** Disarmed — no alarms; sensors still report state (wire value 63). */
|
|
21
27
|
readonly disarmed: "disarmed";
|
|
22
28
|
};
|
|
23
29
|
export type ArmingMode = (typeof ArmingMode)[keyof typeof ArmingMode];
|
|
30
|
+
/**
|
|
31
|
+
* The modes the alarm-delay write (cmd 1255) accepts a `mode_id` for — the three guard modes whose wire
|
|
32
|
+
* integer is byte-captured.
|
|
33
|
+
*
|
|
34
|
+
* A domain of its own, because the two mode integers ride different commands: `setMode` writes `mode_type`
|
|
35
|
+
* on cmd 1224, and cmd 1255 carries `mode_id`. Neither wire validates the integer, so each union IS its
|
|
36
|
+
* command's gate, and evidence for one is not evidence for the other. `custom1` is confirmed on 1224 only;
|
|
37
|
+
* 1255 has no capture carrying mode 3, and no known GET to read one back — the app's own replies
|
|
38
|
+
* `{count:0,data:null}` — so it is absent here. Narrower than {@link ArmingMode} by exactly that value.
|
|
39
|
+
*/
|
|
40
|
+
export declare const AlarmDelayMode: {
|
|
41
|
+
/** Armed — full protection, nobody home (wire value 0). */
|
|
42
|
+
readonly away: "away";
|
|
43
|
+
/** Armed for occupancy — reduced/perimeter protection while home (wire value 1). */
|
|
44
|
+
readonly home: "home";
|
|
45
|
+
/** Disarmed — no alarms; sensors still report state (wire value 63). */
|
|
46
|
+
readonly disarmed: "disarmed";
|
|
47
|
+
};
|
|
48
|
+
export type AlarmDelayMode = (typeof AlarmDelayMode)[keyof typeof AlarmDelayMode];
|
|
24
49
|
/**
|
|
25
50
|
* The P2P **feature-command ids** this arming capability drives. Capability-owned wire vocabulary
|
|
26
51
|
* (transport forwards `cmd.param` opaquely; full id→name catalog in the generated
|
|
@@ -34,10 +59,11 @@ export declare const ARMING_CMD: {
|
|
|
34
59
|
* mValue3:0, `payload:{mode_type:<int>, user_name:<string>}`.
|
|
35
60
|
*
|
|
36
61
|
* ⚠️ Only 3 of the 9 modes were exercised in that capture — `mode_type` 0 (away), 63 (disarmed), 1
|
|
37
|
-
* (home), all confirmed byte-exact
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* them. See `ARMING_MODE_WIRE` for the
|
|
62
|
+
* (home), all confirmed byte-exact. Re-confirmed live 2026-08-05: each reported its own MODE_SWITCH push
|
|
63
|
+
* within ~5s of the write. `custom1` 3 joined {@link ArmingMode} on a live confirmation rather than a
|
|
64
|
+
* capture, making four settable in total. The remaining five are named by the app but never observed
|
|
65
|
+
* leaving it, so this capability reads them and refuses to send them. See `ARMING_MODE_WIRE` for the
|
|
66
|
+
* per-value breakdown.
|
|
41
67
|
*/
|
|
42
68
|
readonly SET_ARMING: 1224;
|
|
43
69
|
/**
|
|
@@ -139,9 +165,9 @@ export type ArmingActions = Surface<typeof ARMING_MEMBERS>;
|
|
|
139
165
|
export declare const ARMING_MEMBERS: {
|
|
140
166
|
/**
|
|
141
167
|
* The one member whose write domain is NARROWER than its read: `enumValues` names all nine modes a
|
|
142
|
-
* station can report, and the argument's `values` publishes only the
|
|
143
|
-
* argument IS the domain the derived setter enforces and the refusal names, so an
|
|
144
|
-
* refused by naming the
|
|
168
|
+
* station can report, and the argument's `values` publishes only the four whose write is confirmed. That
|
|
169
|
+
* argument IS the domain the derived setter enforces and the refusal names, so an unconfirmed mode is
|
|
170
|
+
* refused by naming the four that work — nine labels for the read and four for the write, off one
|
|
145
171
|
* declaration.
|
|
146
172
|
*
|
|
147
173
|
* `armingCommand` may also throw synchronously (missing account identity) and `bindMembers` turns that
|
|
@@ -187,15 +213,17 @@ export declare const ARMING_MEMBERS: {
|
|
|
187
213
|
* this mode — there is no known GET to fetch it automatically, and a wrong guess here can silently
|
|
188
214
|
* misconfigure which sensors arm/trigger for real.
|
|
189
215
|
*
|
|
190
|
-
* Takes {@link
|
|
191
|
-
* captured
|
|
192
|
-
* `
|
|
216
|
+
* Takes {@link AlarmDelayMode}, not {@link ArmingMode}: a delay is configurable only for a mode whose
|
|
217
|
+
* integer is captured on THIS command, and `custom1` is confirmed on cmd 1224 only. The frame carries
|
|
218
|
+
* that integer in `mode_id` with no runtime validation and no readback, so a mode outside this union
|
|
219
|
+
* would be the same unverified guess `setMode` refuses.
|
|
193
220
|
*/
|
|
194
|
-
readonly setAlarmDelayConfig: import("./members.js").MethodMember<(mode:
|
|
221
|
+
readonly setAlarmDelayConfig: import("./members.js").MethodMember<(mode: AlarmDelayMode, config: AlarmDelayConfig) => Promise<void>>;
|
|
195
222
|
};
|
|
196
223
|
/**
|
|
197
224
|
* `arming` — guard/arming mode. `armingMode` (see {@link ARMING_CMD.SET_ARMING}) has a verified
|
|
198
|
-
* read/write MECHANISM, but only
|
|
199
|
-
*
|
|
225
|
+
* read/write MECHANISM, but only 4 of the 9 modes it reports (away/home/custom1/disarmed, the
|
|
226
|
+
* {@link ArmingMode} union) are confirmed as writes — see `ARMING_MODE_WIRE` for which 5 are still
|
|
227
|
+
* unverified third-party integers, and which of the 4 is live-confirmed rather than byte-captured.
|
|
200
228
|
*/
|
|
201
229
|
export declare const ARMING: CapabilityModule;
|
|
@@ -385,7 +385,7 @@ export interface DeviceActionMap {
|
|
|
385
385
|
lock: LockActions;
|
|
386
386
|
/** Siren: reads `active`, `volume`, `alarmDuration`, `doNotDisturb`; writes `setVolume`, `setAlarmDuration`, `test`, `stop` (config setters present when the param is reported). No direct "sound the alarm" wire — a real alarm is driven by the `arming` system; `test` is the on-demand trigger. */
|
|
387
387
|
siren: SirenActions;
|
|
388
|
-
/** Guard mode: `setMode(ArmingMode)` + `setAlarmDelayConfig(mode, config)`. Of the
|
|
388
|
+
/** Guard mode: `setMode(ArmingMode)` + `setAlarmDelayConfig(mode, config)`. Of the 9 modes a station reports, only `away`/`home`/`custom1`/`disarmed` are confirmed as writes (`ArmingMode`); the alarm-delay write takes the narrower byte-captured `AlarmDelayMode`. */
|
|
389
389
|
arming: ArmingActions;
|
|
390
390
|
/** Doorbell: `playQuickResponse(voiceId)` (the canned voice replies). */
|
|
391
391
|
doorbell: DoorbellActions;
|
|
@@ -555,7 +555,7 @@ export type { VacuumActivity, VacuumCleanType, CarpetStrategy, CleanExtent } fro
|
|
|
555
555
|
export { VACUUM_ACTIVITIES, VACUUM_CLEAN_TYPES, CARPET_STRATEGIES, CLEAN_EXTENTS, MOP_LEVELS } from "./vacuum-clean.js";
|
|
556
556
|
export { SuctionLevel, suctionLevelName, type SuctionLevelValue } from "./suction.js";
|
|
557
557
|
export type { PtzPresetActions, ZoomRegion, PtzPreset, PtzPresetImage } from "./ptz.js";
|
|
558
|
-
export { ArmingMode } from "./arming.js";
|
|
558
|
+
export { AlarmDelayMode, ArmingMode } from "./arming.js";
|
|
559
559
|
export type { AlarmDelayConfig, AlarmDelayCountdown, AlarmDelayDeviceAction, AlarmDelaySeconds } from "./arming.js";
|
|
560
560
|
export { PtzDirection } from "./ptz.js";
|
|
561
561
|
export { AiDetectType, encodeAiDetectType, decodeAiDetectType, type AiDetectFlags } from "./motion.js";
|
|
@@ -18,6 +18,23 @@ import type { FfmpegLevel } from "../ffmpeg.js";
|
|
|
18
18
|
import { SharedLiveSource } from "./shared-live-source.js";
|
|
19
19
|
import { type PowerTier, type SessionManagerOpts } from "./session-manager.js";
|
|
20
20
|
import { FragmentRecording } from "./fragment-recording.js";
|
|
21
|
+
/**
|
|
22
|
+
* What a caller's own deadline on a station call has to clear, in milliseconds.
|
|
23
|
+
*
|
|
24
|
+
* A caller that bounds one of these calls itself races these waits, and a bound below them reports the
|
|
25
|
+
* caller's own expiry in place of the reason this SDK was about to give — the two are indistinguishable to
|
|
26
|
+
* whoever reads the outcome, and they call for different next steps. Published so that bound can be derived
|
|
27
|
+
* rather than copied: a literal in a caller's source is a second source of truth that goes stale silently
|
|
28
|
+
* when these change.
|
|
29
|
+
*
|
|
30
|
+
* `connect` applies to every call on a station, because nothing can be addressed to one before its session is
|
|
31
|
+
* up. `level2Grace` applies twice where the key is required: the negotiation is re-prompted once.
|
|
32
|
+
*/
|
|
33
|
+
export declare const P2P_STATION_WAITS: {
|
|
34
|
+
readonly connect: 20000;
|
|
35
|
+
readonly level2Grace: 25000;
|
|
36
|
+
readonly level2Settle: 8000;
|
|
37
|
+
};
|
|
21
38
|
/**
|
|
22
39
|
* Options accepted when warming a {@link SharedLiveSource} for a device (all optional).
|
|
23
40
|
*
|
|
@@ -90,6 +107,12 @@ export declare class P2PCommandRouter {
|
|
|
90
107
|
constructor(deps: P2PRouterDeps);
|
|
91
108
|
/** Forward one P2P failure once even when both the session listener and startup waiter observe it. */
|
|
92
109
|
private reportError;
|
|
110
|
+
/**
|
|
111
|
+
* Emit a live trace under a station session's handle, for work this router does ON that session before
|
|
112
|
+
* the session itself records anything — reaching the station, and resolving what a device is on it. Same
|
|
113
|
+
* handle as everything the session goes on to trace, which is what groups one attempt.
|
|
114
|
+
*/
|
|
115
|
+
private traceOnStation;
|
|
93
116
|
/**
|
|
94
117
|
* Whether this transport stack drives `dev`'s `ff09-*` commands — true when the device has its own
|
|
95
118
|
* usable P2P endpoint (a non-empty `p2p_did`). The command sink asks each stack this to route a
|
|
@@ -10,4 +10,5 @@ export * from "./envelope.js";
|
|
|
10
10
|
export * from "./write-commands.js";
|
|
11
11
|
export * from "./lan-ip.js";
|
|
12
12
|
export { LIVE_TRACE_MESSAGE, type LiveTrace } from "./live-trace.js";
|
|
13
|
+
export { P2P_STATION_WAITS } from "./command-router.js";
|
|
13
14
|
export * as p2pCodec from "./codec.js";
|
|
@@ -63,12 +63,37 @@ export type LiveTrace =
|
|
|
63
63
|
phase: "sequence-restart";
|
|
64
64
|
dataType: number;
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Work on a station is holding for its session to connect, with the milliseconds it will wait.
|
|
68
|
+
*
|
|
69
|
+
* The earliest phase there is: nothing else on a station can be attempted until its session is up, and a
|
|
70
|
+
* caller whose own deadline expires inside this wait has this record and no other. Emitted only where a wait
|
|
71
|
+
* actually happens, so its absence states that the session was already connected.
|
|
72
|
+
*/
|
|
73
|
+
| {
|
|
74
|
+
phase: "session-connect-wait";
|
|
75
|
+
waitMs: number;
|
|
76
|
+
}
|
|
77
|
+
/** The session connected, after this long. */
|
|
78
|
+
| {
|
|
79
|
+
phase: "session-connected";
|
|
80
|
+
waitedMs: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The session did not connect within its wait, so nothing on this station can be attempted.
|
|
84
|
+
*
|
|
85
|
+
* The one outcome that is otherwise indistinguishable from a station that answered and then refused: both
|
|
86
|
+
* leave a caller with no media and no phase naming a station.
|
|
87
|
+
*/
|
|
88
|
+
| {
|
|
89
|
+
phase: "session-unreachable";
|
|
90
|
+
waitedMs: number;
|
|
91
|
+
}
|
|
66
92
|
/**
|
|
67
93
|
* A live start is holding for the station's level-2 key, with the milliseconds it will wait.
|
|
68
94
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* re-issued — and only these separate them.
|
|
95
|
+
* A start that looks slow is either waiting here, waiting for its session to connect, waiting for the station
|
|
96
|
+
* to serve the channel it was asked for, or being re-issued — and only these phases separate them.
|
|
72
97
|
*/
|
|
73
98
|
| {
|
|
74
99
|
phase: "level2-wait";
|
|
@@ -79,10 +104,63 @@ export type LiveTrace =
|
|
|
79
104
|
phase: "level2-ready";
|
|
80
105
|
cipherId: number;
|
|
81
106
|
}
|
|
82
|
-
/**
|
|
107
|
+
/**
|
|
108
|
+
* The station's key is not coming, why, and the cipher where a station named one.
|
|
109
|
+
*
|
|
110
|
+
* Every ending of a level-2 wait carries one of these reasons, so a start refused for want of a key is
|
|
111
|
+
* accounted for however it ended. `grace-elapsed` is a wait that ran out and states how long was waited;
|
|
112
|
+
* the rest are answered without waiting, because the negotiation is one-shot per connection and a
|
|
113
|
+
* concluded one is final. `no-cipher-key` and `derivation-failed` are about this account's cipher
|
|
114
|
+
* material, `not-negotiating` and `session-closed` about the station or its connection — and only a
|
|
115
|
+
* reason reached under a negotiation has a cipher to name.
|
|
116
|
+
*
|
|
117
|
+
* A `grace-elapsed` start proceeds at level 1 where it has such a form, and not at all where it does not.
|
|
118
|
+
*/
|
|
119
|
+
| {
|
|
120
|
+
phase: "level2-unavailable";
|
|
121
|
+
reason: "no-cipher-key" | "derivation-failed" | "not-negotiating" | "session-closed" | "grace-elapsed";
|
|
122
|
+
cipherId?: number;
|
|
123
|
+
waitedMs?: number;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A station's cipher was answered with material for a DIFFERENT cipher, which was used in its place.
|
|
127
|
+
*
|
|
128
|
+
* The one lookup outcome no other phase accounts for: material for the cipher the station named is followed
|
|
129
|
+
* by `level2-ready` or by `level2-unavailable` with `derivation-failed`, an answer holding none by
|
|
130
|
+
* `no-cipher-key`, and a lookup that threw is reported as an error. Substituted material derives to
|
|
131
|
+
* nothing and otherwise reads as a station fault. `cipherId` is the cipher the station asked for,
|
|
132
|
+
* `answeredCipherId` the one whose material was used.
|
|
133
|
+
*/
|
|
134
|
+
| {
|
|
135
|
+
phase: "cipher-fallback";
|
|
136
|
+
cipherId: number;
|
|
137
|
+
answeredCipherId: number;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The station answered its gateway-info prompt, so a key derivation has begun under the cipher it named.
|
|
141
|
+
*
|
|
142
|
+
* What separates a station that never answered the prompt from one that answered and produced no usable key:
|
|
143
|
+
* without it, `level2-unavailable` with `not-negotiating` covers both, and they are a station or network
|
|
144
|
+
* problem and an account cipher-material problem respectively.
|
|
145
|
+
*/
|
|
83
146
|
| {
|
|
84
|
-
phase: "level2-
|
|
85
|
-
|
|
147
|
+
phase: "level2-negotiating";
|
|
148
|
+
cipherId: number;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* A station was resolved for a call, stating what the caller's device is on it and whose station it is.
|
|
152
|
+
*
|
|
153
|
+
* Emitted before anything is sent, so it is the only account of the intended topology on a call that fails
|
|
154
|
+
* during resolution: an attached camera's media start has no unencrypted form, so whether a device was taken
|
|
155
|
+
* as attached decides what its failure means. `stationAdmin` states whether the signed-in account is the
|
|
156
|
+
* station's administrator, which is what a key the account cannot resolve turns on; `unstated` is a device
|
|
157
|
+
* record that names no administrator, which is not the same as naming another.
|
|
158
|
+
*/
|
|
159
|
+
| {
|
|
160
|
+
phase: "station-resolved";
|
|
161
|
+
topology: "attached" | "own";
|
|
162
|
+
channel: number;
|
|
163
|
+
stationAdmin: "self" | "other" | "unstated";
|
|
86
164
|
}
|
|
87
165
|
/** A shared source began warming, with the interval it re-issues on and the deadline it fails at. */
|
|
88
166
|
| {
|
|
@@ -208,6 +208,10 @@ export declare class P2PSession extends EventEmitter {
|
|
|
208
208
|
* cameras from that second group streamed normally at level-1 — including one of the same firmware as an
|
|
209
209
|
* own-session camera that delivered no video at all for a reason of its own. An expired grace therefore
|
|
210
210
|
* separates nothing on this path, and a start failure on such a session is not evidence about it.
|
|
211
|
+
*
|
|
212
|
+
* Every `false` answer carries a `level2-unavailable` trace naming its reason, wherever the wait ended: a
|
|
213
|
+
* `terminal` outcome is the one already stated where the negotiation concluded, since that is where the
|
|
214
|
+
* cipher and the cause are known, and re-stating it here would double every settled negotiation.
|
|
211
215
|
*/
|
|
212
216
|
awaitLevel2Key(graceMs: number, graceFrom?: "call" | "session"): Promise<boolean>;
|
|
213
217
|
/**
|
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.3",
|
|
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",
|