@mega-yfue/eufy-sdk 0.0.4 → 0.1.0-beta.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.
Files changed (143) hide show
  1. package/README.md +31 -12
  2. package/dist/client/device-registry.d.ts +343 -0
  3. package/dist/client/eufy-mega.d.ts +872 -0
  4. package/dist/client/index.d.ts +1 -6
  5. package/dist/client/map-channels.d.ts +22 -0
  6. package/dist/client/types.d.ts +383 -0
  7. package/dist/core/contracts.d.ts +898 -0
  8. package/dist/core/crypto.d.ts +98 -0
  9. package/dist/core/index.d.ts +9 -7
  10. package/dist/core/logger.d.ts +53 -0
  11. package/dist/core/lz4-block.d.ts +35 -0
  12. package/dist/core/raw-dp-hex.d.ts +32 -0
  13. package/dist/core/raw-dp-writer.d.ts +83 -0
  14. package/dist/core/store.d.ts +43 -0
  15. package/dist/core/types.d.ts +169 -0
  16. package/dist/core/util.d.ts +78 -0
  17. package/dist/index.d.ts +4 -7
  18. package/dist/index.js +25526 -14
  19. package/dist/index.js.map +7 -1
  20. package/dist/model/capabilities/access.d.ts +127 -0
  21. package/dist/model/capabilities/arming.d.ts +201 -0
  22. package/dist/model/capabilities/audio.d.ts +154 -0
  23. package/dist/model/capabilities/battery.d.ts +363 -0
  24. package/dist/model/capabilities/camera.d.ts +564 -0
  25. package/dist/model/capabilities/co.d.ts +40 -0
  26. package/dist/model/capabilities/contact.d.ts +133 -0
  27. package/dist/model/capabilities/doorbell.d.ts +345 -0
  28. package/dist/model/capabilities/dp-catalog.d.ts +38 -0
  29. package/dist/model/capabilities/index.d.ts +561 -0
  30. package/dist/model/capabilities/info.d.ts +28 -0
  31. package/dist/model/capabilities/keypad.d.ts +61 -0
  32. package/dist/model/capabilities/leak.d.ts +43 -0
  33. package/dist/model/capabilities/light.d.ts +174 -0
  34. package/dist/model/capabilities/locate.d.ts +63 -0
  35. package/dist/model/capabilities/lock.d.ts +242 -0
  36. package/dist/model/capabilities/manifest.d.ts +107 -0
  37. package/dist/model/capabilities/members.d.ts +647 -0
  38. package/dist/model/capabilities/motion.d.ts +377 -0
  39. package/dist/model/capabilities/person-detection.d.ts +8 -0
  40. package/dist/model/capabilities/ptz.d.ts +289 -0
  41. package/dist/model/capabilities/rtsp.d.ts +221 -0
  42. package/dist/model/capabilities/siren.d.ts +218 -0
  43. package/dist/model/capabilities/smart-light.d.ts +172 -0
  44. package/dist/model/capabilities/smoke.d.ts +40 -0
  45. package/dist/model/capabilities/snapshot.d.ts +6 -0
  46. package/dist/model/capabilities/storage.d.ts +11 -0
  47. package/dist/model/capabilities/suction.d.ts +104 -0
  48. package/dist/model/capabilities/types.d.ts +484 -0
  49. package/dist/model/capabilities/vacuum-clean.d.ts +1946 -0
  50. package/dist/model/capabilities/vacuum-dock.d.ts +208 -0
  51. package/dist/model/capabilities/video.d.ts +6 -0
  52. package/dist/model/classify.d.ts +78 -0
  53. package/dist/model/clean-record-detail.d.ts +65 -0
  54. package/dist/model/clean-records.d.ts +69 -0
  55. package/dist/model/device-family.d.ts +73 -0
  56. package/dist/model/device-types.d.ts +123 -0
  57. package/dist/model/device.d.ts +265 -0
  58. package/dist/model/index.d.ts +29 -4
  59. package/dist/model/infer.d.ts +23 -0
  60. package/dist/model/inspect.d.ts +61 -0
  61. package/dist/model/life-params.d.ts +21 -0
  62. package/dist/model/map-pixels.d.ts +70 -0
  63. package/dist/model/param-dictionary.d.ts +28 -0
  64. package/dist/model/param-namespace.d.ts +21 -0
  65. package/dist/model/proto-read.d.ts +53 -0
  66. package/dist/model/push-events.d.ts +147 -0
  67. package/dist/model/registry.d.ts +54 -0
  68. package/dist/model/types.d.ts +301 -0
  69. package/dist/model/vacuum-map-store.d.ts +92 -0
  70. package/dist/model/vacuum-map.d.ts +286 -0
  71. package/dist/model/vacuum-scenes.d.ts +76 -0
  72. package/dist/model/vacuum-schedules.d.ts +85 -0
  73. package/dist/transport/dp-preset.d.ts +102 -0
  74. package/dist/transport/ff09.d.ts +444 -0
  75. package/dist/transport/ffmpeg.d.ts +86 -0
  76. package/dist/transport/http/decodeImageV1.d.ts +20 -0
  77. package/dist/transport/http/decodeImageV2.d.ts +19 -0
  78. package/dist/transport/http/index.d.ts +5 -0
  79. package/dist/transport/http/light-catalog.d.ts +62 -0
  80. package/dist/transport/http/media-download.d.ts +14 -0
  81. package/dist/transport/http/mega-client.d.ts +514 -0
  82. package/dist/transport/http/phone-model.d.ts +21 -0
  83. package/dist/transport/index.d.ts +10 -7
  84. package/dist/transport/mqtt/app-client-id.d.ts +16 -0
  85. package/dist/transport/mqtt/availability.d.ts +14 -0
  86. package/dist/transport/mqtt/bare-ip-tls.d.ts +46 -0
  87. package/dist/transport/mqtt/biz-stream.d.ts +98 -0
  88. package/dist/transport/mqtt/broker-discovery.d.ts +55 -0
  89. package/dist/transport/mqtt/clean-codec.d.ts +14 -0
  90. package/dist/transport/mqtt/command-router.d.ts +285 -0
  91. package/dist/transport/mqtt/dp-codec.d.ts +58 -0
  92. package/dist/transport/mqtt/dp-color.d.ts +14 -0
  93. package/dist/transport/mqtt/engine.d.ts +16 -0
  94. package/dist/transport/mqtt/index.d.ts +5 -0
  95. package/dist/transport/mqtt/secure-mqtt.d.ts +107 -0
  96. package/dist/transport/mqtt/topics.d.ts +80 -0
  97. package/dist/transport/p2p/adts.d.ts +91 -0
  98. package/dist/transport/p2p/annexb.d.ts +124 -0
  99. package/dist/transport/p2p/codec.d.ts +166 -0
  100. package/dist/transport/p2p/command-router.d.ts +655 -0
  101. package/dist/transport/p2p/commands.d.ts +550 -0
  102. package/dist/transport/p2p/envelope.d.ts +46 -0
  103. package/dist/transport/p2p/fmp4.d.ts +89 -0
  104. package/dist/transport/p2p/fragment-recording.d.ts +33 -0
  105. package/dist/transport/p2p/index.d.ts +13 -0
  106. package/dist/transport/p2p/lan-ip.d.ts +25 -0
  107. package/dist/transport/p2p/live-stream.d.ts +223 -0
  108. package/dist/transport/p2p/live-trace.d.ts +123 -0
  109. package/dist/transport/p2p/media.d.ts +105 -0
  110. package/dist/transport/p2p/p2p-session.d.ts +620 -0
  111. package/dist/transport/p2p/readable-egress.d.ts +27 -0
  112. package/dist/transport/p2p/session-manager.d.ts +154 -0
  113. package/dist/transport/p2p/shared-live-source.d.ts +431 -0
  114. package/dist/transport/p2p/talkback.d.ts +187 -0
  115. package/dist/transport/p2p/video.d.ts +150 -0
  116. package/dist/transport/p2p/write-commands.d.ts +21 -0
  117. package/dist/transport/protobuf.d.ts +5 -0
  118. package/dist/transport/push/fcm.d.ts +23 -0
  119. package/dist/transport/push/index.d.ts +6 -0
  120. package/dist/transport/push/message-tags.d.ts +26 -0
  121. package/dist/transport/push/parser.d.ts +27 -0
  122. package/dist/transport/push/proto.d.ts +11 -0
  123. package/dist/transport/push/push-client.d.ts +64 -0
  124. package/dist/transport/push/store.d.ts +23 -0
  125. package/dist/transport/push/types.d.ts +180 -0
  126. package/dist/transport/raw-dp.d.ts +6 -0
  127. package/dist/transport/stored-image-cache.d.ts +23 -0
  128. package/dist/transport/tuya/account.d.ts +44 -0
  129. package/dist/transport/tuya/client.d.ts +89 -0
  130. package/dist/transport/tuya/command-router.d.ts +79 -0
  131. package/dist/transport/tuya/dp-codec.d.ts +56 -0
  132. package/dist/transport/tuya/index.d.ts +28 -0
  133. package/dist/transport/tuya/request.d.ts +157 -0
  134. package/dist/transport/tuya/sign.d.ts +64 -0
  135. package/package.json +12 -13
  136. package/dist/client/index.js +0 -2
  137. package/dist/client/index.js.map +0 -1
  138. package/dist/core/index.js +0 -2
  139. package/dist/core/index.js.map +0 -1
  140. package/dist/model/index.js +0 -2
  141. package/dist/model/index.js.map +0 -1
  142. package/dist/transport/index.js +0 -2
  143. package/dist/transport/index.js.map +0 -1
@@ -0,0 +1,289 @@
1
+ import { type Surface } from "./members.js";
2
+ import type { CapabilityModule, CommandContext } from "./types.js";
3
+ import type { Command } from "../../core/contracts.js";
4
+ /**
5
+ * The P2P **feature-command ids** this pan-tilt capability drives. Capability-owned wire vocabulary
6
+ * (transport forwards `cmd.param` opaquely; full id→name catalog in the generated
7
+ * `transport/p2p/commands.ts`). Distinct from the {@link PTZ_ROTATE} direction map below (which is
8
+ * `rotate_type` 1..4, not a command id).
9
+ *
10
+ * Ids + envelopes are taken from the V6 app's own command builders (the `Set*Parser`/`*PositionsParser`
11
+ * classes in the decompiled JS), which is this project's wire ground truth — NOT a third-party
12
+ * catalogue, whose PTZ labels are known-wrong here (one labels 6038 a rotate; the app sends it as a
13
+ * span-cruise preview).
14
+ */
15
+ export declare const PTZ_CMD: {
16
+ /** Rotate. App `SetPtz…`/`INDOOR_ROTATE`: 1700 wrapper, `{cmd_type,rotate_type,zoom}`. */
17
+ readonly PTZ_ROTATE: 6030;
18
+ /** Digital zoom. App `SetPictureZoomParser` (`COMMAND_DUAL_CAMERA_ZOOM`): 1350 SET_PAYLOAD sub-command,
19
+ * payload `{x,y,w,h,offset,orgZoom,dstZoom}`. Also the inbound `1351` zoom-notify `cmd`. */
20
+ readonly PTZ_ZOOM: 6203;
21
+ /** Go to a stored preset — AND the save-preset frame (identical bytes; the camera moves vs creates
22
+ * by whether the slot is occupied). App `SetPresetPositionsParser` (`COMMAND_INDOOR_SPAN_CRUISE_POINT`):
23
+ * 1700 wrapper, `{settingstate:0, value:<presetId>}`. */
24
+ readonly PTZ_PRESET_GOTO: 6032;
25
+ /** Capture the camera's current frame as a preset's thumbnail. App `COMMAND_APP_SPAN_PTZ_PIC`: 1700
26
+ * wrapper, `{value:<presetId>}`. The app sends this right before the 6032 save when creating a preset. */
27
+ readonly PTZ_PRESET_PIC: 6097;
28
+ /** Delete a stored preset. App `DeletePresetPositionsParser` (`COMMAND_INDOOR_SPAN_CRUISE_DELETE`):
29
+ * 1700 wrapper, `{value:<presetId>}`. */
30
+ readonly PTZ_PRESET_DELETE: 6033;
31
+ /** List stored presets. App `QueryPresetPositionsParser` (`COMMAND_INDOOR_SPAN_CRUISE_QUERY`): 1700
32
+ * request whose reply is a `1351` notify carrying `{points:[…]}`. */
33
+ readonly PTZ_PRESET_QUERY: 6034;
34
+ /** Preview a stored preset — move to it transiently (the app uses this to show a preset before
35
+ * committing a default). App `COMMAND_INDOOR_SPAN_CRUISE_PREVIEW`: 1700 wrapper, `{value:<presetId>}`. */
36
+ readonly PTZ_PRESET_PREVIEW: 6035;
37
+ /** Mark a stored preset as the default/home position. App `DefaultPresetPositionsParser`
38
+ * (`COMMAND_APP_SET_DEFAULT_POSITION`): 1350 SET_PAYLOAD, payload `{index:<presetId>, settingstate}`.
39
+ * A compound builder — the app follows it with PREVIEW (6035) + PTZ_PIC (6097) to refresh the
40
+ * thumbnail; the default-set itself is this frame. */
41
+ readonly PTZ_SET_DEFAULT_POSITION: 6242;
42
+ /** How fast a rotate STEP travels. 1700 wrapper, `{value:1|3|5}`. Observed live: the app's
43
+ * Slow/Mid/Fast control on an indoor pan-tilt writes 1 / 3 / 5 and nothing else moves. The id sits
44
+ * inside the smart lock's `601x` block, so the shared param dictionary labels it `lockParam` — an id
45
+ * reused across product lines, and the camera's meaning is the observed one. */
46
+ readonly PTZ_ROTATE_SPEED: 6015;
47
+ };
48
+ /**
49
+ * Optional digital-zoom crop window for `zoom`. The app's `SetPictureZoomParser`
50
+ * carries a region alongside the target factor; when `offset` is false the region is ignored and the
51
+ * zoom is centred (x/y/w/h sent as 0). Coordinates are the app's own normalised values — pass them
52
+ * through only when mirroring a captured region.
53
+ */
54
+ export type ZoomRegion = {
55
+ x?: number;
56
+ y?: number;
57
+ w?: number;
58
+ h?: number;
59
+ /** When true, the x/y/w/h crop is applied; when false (default) the zoom is centred. */
60
+ offset?: boolean;
61
+ /** The zoom factor the region is relative to (app default 0). */
62
+ orgZoom?: number;
63
+ };
64
+ /** One stored PTZ preset, as returned by `list`. Shape follows the app's
65
+ * `query_preset_positions` reply `points[]`; fields beyond `id` vary by model, so the raw entry is
66
+ * preserved. */
67
+ export type PtzPreset = {
68
+ /** The preset id used by `goto` / `delete`. */
69
+ id: number;
70
+ /** The raw reply entry, for fields (name/thumbnail/coordinates) that vary by model. */
71
+ raw: Record<string, unknown>;
72
+ };
73
+ /** A preset's thumbnail, as returned by `image`. Follows the app's
74
+ * `get_preset_position_pic` reply `{data, index}`. */
75
+ export type PtzPresetImage = {
76
+ /** The preset id the image belongs to (`-1` if the reply omitted it). */
77
+ index: number;
78
+ /** The thumbnail image payload as the camera delivers it (a base64-encoded JPEG). */
79
+ data: string;
80
+ };
81
+ /**
82
+ * The preset sub-API — everything that acts on a stored PTZ preset, grouped under `dev.ptz().preset()`.
83
+ * The write verbs are always present; the read verbs (`list`/`image`) are request/reply over P2P and so
84
+ * present only when the device is bound to a live client — call them with `?.`.
85
+ *
86
+ * The `id`-taking write verbs are **fire-and-forget** (P2P sends no ack): referencing an empty slot is
87
+ * a **silent no-op** — the camera ignores it and no error surfaces here. Only `save(id)` populates a
88
+ * slot; `goto`/`preview`/`setDefault`/`delete` on an unpopulated `id` do nothing. Check `list?()` first
89
+ * if the id might not exist.
90
+ */
91
+ export type PtzPresetActions = {
92
+ /** Move to the stored preset `id`. No-op if `id` isn't a saved preset (see the type note). */
93
+ goto(id: number): Promise<void>;
94
+ /** Preview the stored preset `id` — move to it transiently (the app's pre-default preview). No-op if
95
+ * `id` isn't a saved preset. */
96
+ preview(id: number): Promise<void>;
97
+ /** Save the camera's current position into preset `id` (thumbnail + save). Creates the preset when
98
+ * the slot is empty, overwrites it when occupied. */
99
+ save(id: number): Promise<void>;
100
+ /** Promote a preset to the default/home position. Sets the default to the preset the camera is
101
+ * **currently parked on**, so the sequence is `preview(id)` → wait for the pan to finish →
102
+ * `setDefault(id)`; sent with the camera elsewhere it has no effect. */
103
+ setDefault(id: number): Promise<void>;
104
+ /** Delete the stored preset `id`. No-op if `id` isn't a saved preset. */
105
+ delete(id: number): Promise<void>;
106
+ /** List the stored presets. Present only when the device is bound to a live client, so call with `?.`. */
107
+ list?(opts?: {
108
+ timeoutMs?: number;
109
+ }): Promise<PtzPreset[]>;
110
+ /** Fetch preset `id`'s thumbnail. Present only when the device is bound to a live client, so call with
111
+ * `?.`. Resolves `undefined` when the reply carried no image data — typically nothing stored for that
112
+ * preset. A reply that never arrives rejects instead. */
113
+ image?(id: number, opts?: {
114
+ timeoutMs?: number;
115
+ }): Promise<PtzPresetImage | undefined>;
116
+ };
117
+ /**
118
+ * Bound pan/tilt/zoom (PTZ) controls — the object returned by `dev.ptz()`. Movement is
119
+ * command-driven (the camera has no "go to angle X" write; it steps in a direction). Preset operations
120
+ * are grouped under {@link PtzPresetActions} via `preset()` (e.g. `dev.ptz().preset().goto(3)`).
121
+ */
122
+ export type PtzActions = Surface<typeof PTZ_MEMBERS>;
123
+ /**
124
+ * Rotate direction. `PtzDirection` is both the const value-object (`PtzDirection.left`) and the
125
+ * union type of its values, so callers use the named constant: `rotate(PtzDirection.left)`.
126
+ */
127
+ export declare const PtzDirection: {
128
+ /** Pan left (one step). */
129
+ readonly left: "left";
130
+ /** Pan right (one step). */
131
+ readonly right: "right";
132
+ /** Tilt up (one step). */
133
+ readonly up: "up";
134
+ /** Tilt down (one step). */
135
+ readonly down: "down";
136
+ };
137
+ export type PtzDirection = (typeof PtzDirection)[keyof typeof PtzDirection];
138
+ /** Pan-tilt rotate direction → `rotate_type` (the on-wire code). Not re-exported by the barrel —
139
+ * stays internal to the capability. Confirmed on hardware (T8425): 1=left, 2=right, 3=up, 4=down. */
140
+ export declare const PTZ_ROTATE: {
141
+ readonly left: 1;
142
+ readonly right: 2;
143
+ readonly up: 3;
144
+ readonly down: 4;
145
+ };
146
+ /**
147
+ * Pan-tilt rotate as a transport-neutral {@link Command}. CONFIRMED wire command (captured on
148
+ * T8425 ch3 during live-view): `{"commandType":6030,"data":{"cmd_type":1,"rotate_type":1..4,
149
+ * "zoom":1.0}}` under the generic control wrapper `1700` on the camera channel. `cmd_type:1` =
150
+ * move; `zoom` defaults to 1.0.
151
+ *
152
+ * Emitted as a `set-json` intent — the capability names only the param + payload; the transport
153
+ * resolver picks the encryption level by topology (standalone → L1, HomeBase → L2). A PT cam can be
154
+ * either, so the level is NOT fixed here.
155
+ */
156
+ export declare function rotateCommand(direction: PtzDirection, ctx: CommandContext, zoom?: number): Command;
157
+ /**
158
+ * Digital-zoom as a transport-neutral {@link Command}. The V6 app's `SetPictureZoomParser` builds a
159
+ * `SET_PAYLOAD` (1350) sub-command `COMMAND_DUAL_CAMERA_ZOOM` with the crop-window payload
160
+ * `{x,y,w,h,offset,orgZoom,dstZoom}`; the region collapses to zeros unless `offset` is set. `dstZoom`
161
+ * is the target factor. `mValue3:0` — verified byte-exact against a live capture of the real app's own
162
+ * pinch-zoom gesture; NOT `mValue3:cmd` like the general `SET_PAYLOAD` default (see `setPayload`'s doc).
163
+ */
164
+ export declare function zoomCommand(dstZoom: number, ctx: CommandContext, region?: ZoomRegion): Command;
165
+ /**
166
+ * Go-to-preset as a transport-neutral {@link Command}. The V6 app's `SetPresetPositionsParser` builds
167
+ * a 1700-wrapper sub-command `COMMAND_INDOOR_SPAN_CRUISE_POINT` with `{settingstate:0, value:<id>}`
168
+ * (the app follows it with a query + thumbnail fetch; the move itself is this frame).
169
+ */
170
+ export declare function gotoPresetCommand(id: number, ctx: CommandContext): Command;
171
+ /**
172
+ * Delete-preset as a transport-neutral {@link Command}. The V6 app's `DeletePresetPositionsParser`
173
+ * builds a 1700-wrapper sub-command `COMMAND_INDOOR_SPAN_CRUISE_DELETE` with `{value:<id>}`.
174
+ */
175
+ export declare function deletePresetCommand(id: number, ctx: CommandContext): Command;
176
+ /**
177
+ * Preview-preset as a transport-neutral {@link Command}. The V6 app's `COMMAND_INDOOR_SPAN_CRUISE_PREVIEW`
178
+ * sub-command carries `{value:<id>}`; the app moves the camera to preset `id` to preview it (decoded on
179
+ * a T8170 SoloCam: `{"commandType":6035,"data":{"value":3}}`).
180
+ */
181
+ export declare function previewPresetCommand(id: number, ctx: CommandContext): Command;
182
+ /**
183
+ * Capture-current-frame-as-thumbnail as a transport-neutral {@link Command}. The V6 app's
184
+ * `COMMAND_APP_SPAN_PTZ_PIC` sub-command carries `{value:<id>}`; the app emits it immediately before
185
+ * the 6032 save when creating a preset (see {@link savePresetCommand}).
186
+ */
187
+ export declare function presetPicCommand(id: number, ctx: CommandContext): Command;
188
+ /**
189
+ * Save the camera's CURRENT position into preset `id`, as the two frames the V6 app emits to create a
190
+ * preset (decoded on a T8170 SoloCam): `COMMAND_APP_SPAN_PTZ_PIC {value:id}` (thumbnail) then
191
+ * `COMMAND_INDOOR_SPAN_CRUISE_POINT {settingstate:0, value:id}` (save). The save frame is byte-identical
192
+ * to {@link gotoPresetCommand}; the camera creates vs moves by whether slot `id` is occupied.
193
+ */
194
+ export declare function savePresetCommand(id: number, ctx: CommandContext): [Command, Command];
195
+ /**
196
+ * Promote a stored preset to the default/home position, as a transport-neutral {@link Command}. The V6
197
+ * app's `DefaultPresetPositionsParser` builds a 1350 SET_PAYLOAD `COMMAND_APP_SET_DEFAULT_POSITION`
198
+ * `{index:<presetId>, settingstate:0}`.
199
+ *
200
+ * 6242 sets the default to the preset the camera is **currently parked on**. To move the default, park
201
+ * the camera on `presetId` first — `preview(presetId)`, let the pan finish, then `setDefault(presetId)`.
202
+ * Sent while the camera is elsewhere, it has no effect. The parser is topology-agnostic; the router
203
+ * picks the encryption level (L1/L2) by topology.
204
+ */
205
+ export declare function setDefaultPositionCommand(presetId: number, ctx: CommandContext): Command;
206
+ /**
207
+ * Parse a `query_preset_positions` reply (`{points:[…]}`) into {@link PtzPreset}s. Mirrors the app's
208
+ * `query_preset_positions_parse_payload`, which reads the reply's `points` array. The id field is
209
+ * `index` — confirmed live on a T8171 SoloCam, whose reply entries are
210
+ * `{index, enable, zoom, isdefault}` (8 slots, `enable:0` = empty). `id`/`value` are accepted as
211
+ * fallbacks for other models; the full entry is preserved as `raw`.
212
+ */
213
+ export declare function parsePresetPoints(reply: Record<string, unknown> | undefined): PtzPreset[];
214
+ /**
215
+ * Parse a `get_preset_position_pic` reply into a {@link PtzPresetImage}. Mirrors the app's
216
+ * `get_preset_position_pic_parse_payload`, which reads `{data, index}` off the notify (`data` = the
217
+ * thumbnail, `index` defaults to `-1`). Returns `undefined` when the reply carries no image.
218
+ */
219
+ export declare function parsePresetImage(reply: Record<string, unknown> | undefined): PtzPresetImage | undefined;
220
+ /**
221
+ * Every `ptz` feature, declared once.
222
+ *
223
+ * Movement is command-driven, so almost every entry is a `method`: `rotate` takes a direction plus an
224
+ * optional speed, the four compass verbs take nothing, `preset()` returns a whole sub-API, and `zoom`
225
+ * is gated on the device having a second lens. `rotationSpeed` is the one value member — the camera's
226
+ * own step speed, which it stores. There is no position member: no device reports where it is pointed
227
+ * as a parameter. Position arrives only while the camera moves, as the `ptzNotify` event {@link PTZ}
228
+ * decodes from a live frame.
229
+ *
230
+ * Exported but NOT published: each entry states its wire id and the evidence it was confirmed on,
231
+ * which the reference site does not carry.
232
+ * @internal
233
+ */
234
+ export declare const PTZ_MEMBERS: {
235
+ /**
236
+ * How fast a rotate STEP travels, on the app's own three-position control: `1` slow, `3` mid, `5`
237
+ * fast. A `scalar` rather than an enum — the endpoints and the midpoint are observed, so 2 and 4 are
238
+ * a prediction of the scale's shape and the wire is not known to reject them.
239
+ *
240
+ * Not the same thing as `rotate`'s `zoom` argument, which scales one step's SIZE rather than its
241
+ * speed.
242
+ *
243
+ * The parameter is **absent until first written**: a camera whose speed has never been set reports
244
+ * no `6015` at all, so the getter is installed only once the value exists — an absent reading is "not
245
+ * configured", never "unsupported".
246
+ */
247
+ readonly rotationSpeed: {
248
+ readonly param: 6015;
249
+ readonly type: "number";
250
+ readonly kind: "scalar";
251
+ readonly provenance: "verified";
252
+ readonly description: string;
253
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command | undefined;
254
+ };
255
+ /** Rotate a step in a direction; `zoom` scales the step size (1.0 = the default step). */
256
+ readonly rotate: import("./members.js").MethodMember<(direction: PtzDirection, zoom?: number) => Promise<void>>;
257
+ /** Rotate one step left. */
258
+ readonly left: import("./members.js").MethodMember<() => Promise<void>>;
259
+ /** Rotate one step right. */
260
+ readonly right: import("./members.js").MethodMember<() => Promise<void>>;
261
+ /** Rotate one step up. */
262
+ readonly up: import("./members.js").MethodMember<() => Promise<void>>;
263
+ /** Rotate one step down. */
264
+ readonly down: import("./members.js").MethodMember<() => Promise<void>>;
265
+ /**
266
+ * Zoom rides a SECOND (telephoto) lens, so it is offered only where the device shows zoom evidence —
267
+ * otherwise `dev.ptz()?.zoom` would be a silent no-op on a single-lens pan-tilt camera.
268
+ */
269
+ readonly zoom: import("./members.js").MethodMember<(dstZoom: number, region?: ZoomRegion) => Promise<void>> & {
270
+ available: (ctx: CommandContext) => boolean;
271
+ };
272
+ /**
273
+ * The preset sub-API. Write verbs dispatch through the sink; the READ verbs (`list`/`image`) are P2P
274
+ * request/reply and exist only when bound to a media provider — the transport stays
275
+ * capability-agnostic (it runs a generic control-payload query and resolves the correlated notify)
276
+ * while this module owns the sub-command id and the reply parsing.
277
+ *
278
+ * `answers` because calling it performs nothing: it hands back the namespace the verbs live on. Offering
279
+ * it as a control would produce one that returns an object and does nothing.
280
+ */
281
+ readonly preset: {
282
+ readonly method: (deps: import("./members.js").MemberDeps) => () => PtzPresetActions;
283
+ readonly description: string;
284
+ readonly available?: (ctx: CommandContext) => boolean;
285
+ readonly args?: readonly import("./types.js").ActionArgSpec[];
286
+ readonly answers: true;
287
+ };
288
+ };
289
+ export declare const PTZ: CapabilityModule;
@@ -0,0 +1,221 @@
1
+ import type { Command } from "../../core/contracts.js";
2
+ import { type Surface } from "./members.js";
3
+ import type { CapabilityModule, CommandContext } from "./types.js";
4
+ /** The state params this capability owns — its OWN vocabulary, used by no other capability. */
5
+ export declare const RTSP_PARAM: {
6
+ /**
7
+ * RTSP publish switch (app `NAS_STREAM_SWITHC`, the vendor's own typo for SWITCH). `1` publishes the
8
+ * camera's stream, `0` withdraws it. ✅ Verified live 2026-08-01 on a HomeBase-attached doorbell and
9
+ * two indoor cameras: written through this param's scalar wire, both directions confirmed by the
10
+ * stream appearing and disappearing on the serving device's RTSP port.
11
+ */
12
+ readonly STREAM_SWITCH: 1145;
13
+ /**
14
+ * The camera's authoritative RTSP URL — `rtsp://user:pass@host/path`, with the credentials it is
15
+ * enforcing right now. The station pushes this back on the 1145 wire as a string data frame once the
16
+ * stream is asked to start (see the transport's device-URL read); {@link RTSP.decodeState} lifts it
17
+ * into state, read as `dev.rtsp()?.url`.
18
+ *
19
+ * A SYNTHETIC id: the wire supplies no second id — the URL rides the same 1145 as the publish bool,
20
+ * and {@link STREAM_SWITCH} already owns that — so the string gets its own id here, keeping the two as
21
+ * distinct properties rather than one id read two ways. Never in the cloud record (P2P-notify only), so
22
+ * it is quarantined in `property-id-integrity`'s `KNOWN_UNLISTED` — which otherwise holds real-but-unlisted
23
+ * ids (1612), so its entry for this one says it is not a wire id at all.
24
+ *
25
+ * `1145 * 10` is a readable, currently-free number, NOT a reserved allocation — height buys nothing,
26
+ * the dictionary already holds real ids past 100000. It is the only synthetic id today; if a second is
27
+ * ever needed, reserve a stated band for them rather than copying this per-site choice.
28
+ */
29
+ readonly STREAM_URL: 11450;
30
+ /**
31
+ * RTSP credentials + the authentication switch (app `NAS_SEND_SECURITY_PASSWD`). A `SET_PAYLOAD`
32
+ * (1350) envelope: `{cmd:1287, mChannel:<deviceCh>, mValue3:0, payload:{mode, passwd, username}}`.
33
+ *
34
+ * ✅ Byte-exact against the app's own frame (confirmed 2026-08-03 on a T8425 behind a T8030): the app
35
+ * auto-generates BOTH a username and a password (16 chars each — its "13 char" UI rule is
36
+ * frontend-only; the firmware accepts any length). The app maps `mode` as `0` = open, `1` = Basic,
37
+ * `2` = Digest and offers Basic/Digest but no "open" option. A standalone T8442 was observed serving
38
+ * a Digest challenge. A HomeBase-attached T8210 echoed freshly supplied Basic credentials in its URL,
39
+ * confirming storage, while unauthenticated `DESCRIBE` remained 200 with no challenge.
40
+ */
41
+ readonly SEND_SECURITY_PASSWD: 1287;
42
+ /**
43
+ * What the NAS records (app `NAS_VIDEO_TYPE_EVENT`): events only or continuously. The readable half
44
+ * of the app's two-frame recording-mode pair (6050 + 6010). ✅ Write verified live on a T8030 by
45
+ * param readback (0 → 1 → 0).
46
+ */
47
+ readonly VIDEO_TYPE_EVENT: 6050;
48
+ /** Second frame of the recording-mode pair — see {@link VIDEO_TYPE_EVENT}. Sent with it, never alone. */
49
+ readonly VIDEO_TYPE_CONTINUE: 6010;
50
+ };
51
+ /**
52
+ * The RTSP authentication scheme a caller can require. Maps to the credential write's `mode` field.
53
+ *
54
+ * - `"digest"` — request a hashed nonce challenge, observed on a standalone camera. The reader does
55
+ * not send the plaintext password during RTSP authentication, though configuration still sends it
56
+ * to the device. The safer choice, and the app's default.
57
+ * - `"basic"` — request Basic authentication, where the reader sends the password base64-encoded on
58
+ * every request (encoding, not encryption). Offered for players that only speak Basic.
59
+ */
60
+ export type RtspAuthScheme = "digest" | "basic";
61
+ /**
62
+ * What the NAS records. `Events` stores clips around a detection; `Continuous` records without
63
+ * stopping.
64
+ */
65
+ export declare const RtspRecordingMode: {
66
+ /** Record only around detections. */
67
+ readonly Events: 0;
68
+ /** Record continuously. */
69
+ readonly Continuous: 1;
70
+ };
71
+ /** A NAS recording mode — the value side of {@link RtspRecordingMode}. */
72
+ export type RtspRecordingModeValue = (typeof RtspRecordingMode)[keyof typeof RtspRecordingMode];
73
+ /**
74
+ * Bound RTSP controls — the object returned by `dev.rtsp()`.
75
+ *
76
+ * This is the vendor's NAS/RTSP feature: publish a camera's stream so a NAS/NVR (or the HomeBase
77
+ * itself) can record it. Two things a caller must know, because neither is expressible in the wire:
78
+ *
79
+ * - **A station publishes for ONE attached camera at a time.** Enabling a second withdraws the first,
80
+ * silently — the station tracks a single camera, not a set. The SDK cannot detect or prevent this;
81
+ * a caller driving several cameras owns the arbitration.
82
+ * - **Publication is a persistent device setting, not an SDK-owned media session.** An RTSP consumer
83
+ * connects directly to the serving device; the SDK does not observe consumer disconnects and does
84
+ * not withdraw in response. The caller that publishes owns calling `withdraw()` when its recorder is
85
+ * done. The SDK deliberately applies no live-media power budget: on a device with the `battery`
86
+ * capability, leaving RTSP published will drain the cell, so the feature suits mains-powered cameras
87
+ * feeding a recorder.
88
+ *
89
+ * The stream itself is served over plain RTSP on the local network, by the station for a
90
+ * HomeBase-attached camera or by the camera itself when standalone. A standalone camera has been
91
+ * observed serving a Digest challenge. A tested HomeBase-attached camera echoed freshly supplied Basic
92
+ * credentials in its URL but stayed open without them. Verify storage from the device-reported URL
93
+ * and the served effect on each endpoint with an RTSP `DESCRIBE`: 401 = challenged, 200 = open.
94
+ */
95
+ export type RtspActions = Surface<typeof RTSP_MEMBERS>;
96
+ /**
97
+ * Every `rtsp` feature, declared once.
98
+ *
99
+ * `setRecordingMode` is a `method`, not a derived setter, because ONE UI change is TWO frames on
100
+ * the wire — the app sends 6050 then 6010, and sending only the first leaves the continuous recorder out
101
+ * of step with the advertised type. A member's `write` returns a single command, so the pair cannot be
102
+ * expressed as one.
103
+ *
104
+ * Exported but NOT published: each entry states its wire id and the evidence it was confirmed on,
105
+ * which the reference site does not carry.
106
+ * @internal
107
+ */
108
+ export declare const RTSP_MEMBERS: {
109
+ /**
110
+ * This is DEVICE STATE — "whether the stream is published over RTSP" — not a record of who turned it
111
+ * on. So it reads true from more than a caller's own `publish()`: {@link RTSP.decodeState} sets it from
112
+ * the station's URL push (which proves the stream is up) and the cloud poll reports it too. A caller
113
+ * that needs "did *I* turn it on" tracks its own `publish()` call rather than reading this back.
114
+ *
115
+ * `provenance` is name-trust: the name is the app's own typo'd constant (`NAS_STREAM_SWITHC`), so it
116
+ * is "apk". The verified live WRITE — both directions, HomeBase and standalone — is in the description.
117
+ */
118
+ readonly published: {
119
+ readonly param: 1145;
120
+ readonly property: "rtspStream";
121
+ readonly type: "bool";
122
+ readonly kind: "boolean";
123
+ readonly provenance: "apk";
124
+ readonly description: string;
125
+ readonly write: (v: string | number | boolean, ctx: CommandContext) => Command;
126
+ readonly aliases: {
127
+ readonly publish: true;
128
+ readonly withdraw: false;
129
+ };
130
+ };
131
+ /**
132
+ * The device-reported RTSP URL — the full `rtsp://user:pass@host/path`, carrying the credentials the
133
+ * device enforces RIGHT NOW. This is the only source of the freshly-generated pair: the credentials
134
+ * regenerate on every publish toggle and the cloud record lags a cycle, so an assembled URL or an
135
+ * imposed one would not match what the device is enforcing.
136
+ *
137
+ * The flat property name is `rtspUrl`, not `url`: property names are a FLAT namespace shared across
138
+ * every capability (`getProperty`/`setProperty`/`propertyChanged` key on the bare string), so the
139
+ * generic `url` would claim that word SDK-wide. The fluent read stays `dev.rtsp()?.url` — the member
140
+ * key qualifies it there.
141
+ *
142
+ * The FRAME SHAPE — 1145 returning as a NUL-terminated `rtsp://user:pass@host/path` string — was
143
+ * OBSERVED live, so `decodeState` is grounded. The station pushes it only once the stream is asked to
144
+ * start, not from the publish switch alone (field-confirmed).
145
+ *
146
+ * To FETCH the URL, the canonical call is `EufyMega.reportedRtspUrl(sn)` — it provokes and returns it.
147
+ * This member is NOT a second fetch path: it surfaces whatever the station last pushed as inbound
148
+ * state, for code that already holds a `dev.rtsp()` and reacts to `propertyChanged` (a getter that is
149
+ * simply absent until a push has landed), rather than driving the read itself.
150
+ *
151
+ * `provenance` below is `verified` for the VALUE and its frame — not for the id: {@link STREAM_URL}
152
+ * is synthetic and the wire never reports it, so no capture could have "verified" the id itself.
153
+ *
154
+ * Arrives ONLY over the P2P notify wire and never in the cloud record, so it is absent until a push
155
+ * lands and then reads back like any other state. Read-only: the device reports it, a caller does not
156
+ * set it. Lifted from the frame by {@link RTSP.decodeState}.
157
+ */
158
+ readonly url: {
159
+ readonly param: 11450;
160
+ readonly property: "rtspUrl";
161
+ readonly type: "string";
162
+ readonly kind: "text";
163
+ readonly provenance: "verified";
164
+ readonly description: string;
165
+ };
166
+ /**
167
+ * The READ half of a control written by `setRecordingMode` below, hence `writtenElsewhere` — one UI
168
+ * change is TWO frames on the wire (6050 then 6010) and a member's `write` returns a single command,
169
+ * so the pair cannot be a derived setter. 6050 is the half the device reports back, which is why the
170
+ * member hangs off that id and not its silent partner.
171
+ */
172
+ readonly recordingMode: {
173
+ readonly param: 6050;
174
+ readonly type: "enum";
175
+ readonly kind: "enum";
176
+ readonly enumValues: Record<number, string>;
177
+ readonly provenance: "apk";
178
+ readonly writtenElsewhere: true;
179
+ readonly description: string;
180
+ };
181
+ /** Persistently publish this camera's stream. The enabling caller owns withdrawing it when done. */
182
+ readonly publish: import("./members.js").MethodMember<() => Promise<void>>;
183
+ /** Withdraw this camera's persistent publication; consumer retries do not ask the SDK to republish it. */
184
+ readonly withdraw: import("./members.js").MethodMember<() => Promise<void>>;
185
+ /**
186
+ * Store credentials and request authentication on this camera's stream. Served enforcement is
187
+ * topology-dependent and must be verified with `DESCRIBE` (401 = challenged, 200 = open).
188
+ *
189
+ * `scheme` defaults to `"digest"` (a hashed nonce challenge and the app's own default). The reader
190
+ * does not send the plaintext password during Digest authentication, though this configuration write
191
+ * still sends it to the device. Pass `"basic"` only for a player that can't do Digest: Basic sends
192
+ * the password base64-encoded on every request, readable by anyone on the LAN.
193
+ *
194
+ * The write stores credentials on both topologies, but a tested HomeBase-attached endpoint did not
195
+ * enforce the requested mode. Verify storage from the device-reported URL, which embeds the
196
+ * credentials, and enforcement with a `DESCRIBE` (401 = challenged, 200 = open).
197
+ */
198
+ readonly requireAuth: import("./members.js").MethodMember<(username: string, password: string, scheme?: RtspAuthScheme) => Promise<void>>;
199
+ /** Store an anonymous-mode request while keeping the supplied credentials. */
200
+ readonly allowAnonymous: import("./members.js").MethodMember<(username: string, password: string) => Promise<void>>;
201
+ /**
202
+ * Set what the NAS records — events only, or continuously. Sends the app's two-frame pair in one call.
203
+ * Present only when this camera reports the recording-mode state, mirroring the publish switch's own
204
+ * evidence gate.
205
+ *
206
+ * On a battery-powered camera, `Continuous` keeps the stream up and will flatten the battery far
207
+ * faster than event recording — the app only offers it for wired/HomeBase-attached cameras.
208
+ */
209
+ readonly setRecordingMode: import("./members.js").MethodMember<(mode: RtspRecordingModeValue | number) => Promise<void>> & {
210
+ available: (ctx: CommandContext) => boolean;
211
+ };
212
+ };
213
+ /**
214
+ * `rtsp` — publish a camera's stream over RTSP for a NAS/NVR to record.
215
+ *
216
+ * Detection is evidence-only: a device advertises param 1145 or it does not get the accessor. The
217
+ * vendor app gates the setting to a subset of models, but that gate is CLIENT-side — a model whose app
218
+ * never shows the toggle still accepts the write, which is why detection keys off the reported param
219
+ * rather than a model table.
220
+ */
221
+ export declare const RTSP: CapabilityModule;