@apocaliss92/nodedreame 1.11.10 → 1.12.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/README.md +151 -5
- package/dist/index.cjs +2491 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1124 -6
- package/dist/index.d.ts +1124 -6
- package/dist/index.js +2446 -72
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fetch } from 'undici';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
3
4
|
|
|
4
5
|
/** Public name of this library. */
|
|
5
6
|
declare const LIBRARY_NAME = "nodedreame";
|
|
@@ -36,6 +37,47 @@ declare class DreameTransportError extends DreameError {
|
|
|
36
37
|
/** Supported Dreame cloud regions. */
|
|
37
38
|
type DreameRegion = 'eu' | 'us' | 'cn' | 'ru' | 'sg' | 'in' | 'de' | 'tw';
|
|
38
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Connectivity / diagnostic info distilled from a device-list record.
|
|
42
|
+
*
|
|
43
|
+
* Scope note: the Dreame cloud device record exposes reachability + identity +
|
|
44
|
+
* the assigned MQTT broker, but NOT Wi-Fi RSSI / SSID / local IP — those are
|
|
45
|
+
* native-firmware (miIO-style) details the cloud API does not return for these
|
|
46
|
+
* vacuums. So `signalStrength`/`ssid`/`ip` are intentionally absent here; this
|
|
47
|
+
* surfaces everything the cloud actually provides.
|
|
48
|
+
*/
|
|
49
|
+
/** The device's assigned MQTT broker endpoint (from `bindDomain`). */
|
|
50
|
+
interface BrokerEndpoint {
|
|
51
|
+
readonly host: string;
|
|
52
|
+
readonly port: number | null;
|
|
53
|
+
}
|
|
54
|
+
interface DeviceConnectivity {
|
|
55
|
+
/** Whether the cloud considers the device online (LWT/online flag). */
|
|
56
|
+
readonly online: boolean;
|
|
57
|
+
/** Physical link type reported by the cloud (e.g. `"WIFI"`), or null. */
|
|
58
|
+
readonly connectionType: string | null;
|
|
59
|
+
/** Device MAC address, or null. */
|
|
60
|
+
readonly mac: string | null;
|
|
61
|
+
/** Assigned MQTT broker (`bindDomain` split into host/port), or null. */
|
|
62
|
+
readonly broker: BrokerEndpoint | null;
|
|
63
|
+
/** Cloud region the device is bound to (e.g. `"eu"`), or null. */
|
|
64
|
+
readonly region: string | null;
|
|
65
|
+
/** Cloud media/IoT vendor (e.g. `"ali"`), or null. */
|
|
66
|
+
readonly cloudVendor: string | null;
|
|
67
|
+
/** Firmware version (`ver`), or null. */
|
|
68
|
+
readonly firmwareVersion: string | null;
|
|
69
|
+
/** Serial number (`sn`), or null. */
|
|
70
|
+
readonly serialNumber: string | null;
|
|
71
|
+
/** Sub-model code, or null. */
|
|
72
|
+
readonly subModel: string | null;
|
|
73
|
+
/** Battery percentage 0–100 from the cloud snapshot, or null. */
|
|
74
|
+
readonly battery: number | null;
|
|
75
|
+
/** Latest MIoT status int from the cloud snapshot, or null. */
|
|
76
|
+
readonly statusCode: number | null;
|
|
77
|
+
}
|
|
78
|
+
/** Parse the connectivity view from a raw device-list record. */
|
|
79
|
+
declare function parseConnectivity(raw: Record<string, unknown>): DeviceConnectivity;
|
|
80
|
+
|
|
39
81
|
interface DreameSession {
|
|
40
82
|
accessToken: string;
|
|
41
83
|
refreshToken?: string | undefined;
|
|
@@ -55,6 +97,8 @@ interface DreameDevice {
|
|
|
55
97
|
firmwareVersion?: string;
|
|
56
98
|
serialNumber?: string;
|
|
57
99
|
cloudState?: DreameCloudState;
|
|
100
|
+
/** Connectivity/diagnostic view distilled from the cloud record. */
|
|
101
|
+
connectivity?: DeviceConnectivity;
|
|
58
102
|
}
|
|
59
103
|
/** Cloud-cached subset of device state distilled from the device-list response. */
|
|
60
104
|
interface DreameCloudState {
|
|
@@ -169,6 +213,8 @@ declare class TypedEmitter<Events extends Record<string, unknown[]>> {
|
|
|
169
213
|
once<K extends keyof Events & string>(event: K, listener: (...args: Events[K]) => void): this;
|
|
170
214
|
off<K extends keyof Events & string>(event: K, listener: (...args: Events[K]) => void): this;
|
|
171
215
|
emit<K extends keyof Events & string>(event: K, ...args: Events[K]): boolean;
|
|
216
|
+
/** Number of listeners registered for `event`. */
|
|
217
|
+
listenerCount<K extends keyof Events & string>(event: K): number;
|
|
172
218
|
removeAllListeners(): this;
|
|
173
219
|
}
|
|
174
220
|
|
|
@@ -179,6 +225,14 @@ interface PropertyChange {
|
|
|
179
225
|
piid: number;
|
|
180
226
|
value: unknown;
|
|
181
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* True when `err` (or an error in its `cause` chain) is an MQTT CONNACK
|
|
230
|
+
* auth-refusal. mqtt.js raises an `ErrorWithReasonCode` carrying a numeric
|
|
231
|
+
* `code`: MQTT 3.1.1 uses 4 (bad user/pass) / 5 (not authorized); MQTT 5 uses
|
|
232
|
+
* 134 / 135. We also match the human-readable message as a belt-and-braces
|
|
233
|
+
* fallback in case the code is not surfaced through a wrapper.
|
|
234
|
+
*/
|
|
235
|
+
declare function isAuthRefusedError(err: unknown): boolean;
|
|
182
236
|
|
|
183
237
|
/**
|
|
184
238
|
* A read-only set of capability tokens a device model supports. Phase 2 ships
|
|
@@ -294,7 +348,7 @@ interface PushLike {
|
|
|
294
348
|
}
|
|
295
349
|
/** Injectable collaborators — defaults wire the real transport + command layer. */
|
|
296
350
|
interface BaseDeviceDeps {
|
|
297
|
-
createPush(device: DreameDevice, session: DreameSession, region: DreameRegion): PushLike;
|
|
351
|
+
createPush(device: DreameDevice, session: DreameSession, region: DreameRegion, onAuthFailure?: () => Promise<DreameSession>): PushLike;
|
|
298
352
|
getProperties(base: CommonInput, props: MiotProp[]): Promise<PropertyResult[]>;
|
|
299
353
|
/** Read the cloud-cached (shadow) values WITHOUT waking the device. */
|
|
300
354
|
getCachedProperties(base: CommonInput, props: MiotProp[]): Promise<PropertyResult[]>;
|
|
@@ -316,6 +370,12 @@ interface BaseDeviceInput {
|
|
|
316
370
|
region: DreameRegion;
|
|
317
371
|
/** Always reads the LATEST session — the facade owns the variable. */
|
|
318
372
|
sessionRef: () => DreameSession;
|
|
373
|
+
/**
|
|
374
|
+
* Force-refresh callback handed to the underlying push so it can self-heal a
|
|
375
|
+
* broker CONNACK auth-refusal (stale token) by minting a fresh session.
|
|
376
|
+
* Wired by the facade to `Nodreame.reauthenticate()`.
|
|
377
|
+
*/
|
|
378
|
+
onAuthFailure?: () => Promise<DreameSession>;
|
|
319
379
|
deps?: BaseDeviceDeps;
|
|
320
380
|
/** Eager-seed the cache on `start()`. Default true. */
|
|
321
381
|
fetchInitialValues?: boolean;
|
|
@@ -376,6 +436,12 @@ interface CreateDeviceArgs {
|
|
|
376
436
|
device: DreameDevice;
|
|
377
437
|
region: DreameRegion;
|
|
378
438
|
sessionRef: () => DreameSession;
|
|
439
|
+
/**
|
|
440
|
+
* Force-refresh callback handed down to the device's MQTT push so it can
|
|
441
|
+
* self-heal a broker CONNACK auth-refusal (stale token). Bound by the facade
|
|
442
|
+
* to {@link Nodreame.reauthenticate}.
|
|
443
|
+
*/
|
|
444
|
+
onAuthFailure?: () => Promise<DreameSession>;
|
|
379
445
|
}
|
|
380
446
|
/** Injectable collaborators — defaults wire the real P1 modules. */
|
|
381
447
|
interface NodreameDeps {
|
|
@@ -419,6 +485,15 @@ declare class Nodreame extends TypedEmitter<NodreameEvents> {
|
|
|
419
485
|
login(): Promise<DreameSession>;
|
|
420
486
|
/** Return a valid session, refreshing proactively within the leeway window. */
|
|
421
487
|
ensureSession(): Promise<DreameSession>;
|
|
488
|
+
/**
|
|
489
|
+
* Force a session refresh REGARDLESS of the current expiry, then propagate the
|
|
490
|
+
* new token to every live device push. Wired as each push's `onAuthFailure`:
|
|
491
|
+
* when a broker refuses a CONNECT because it considers the token stale (even
|
|
492
|
+
* though {@link ensureSession} still thinks it valid — e.g. server-side early
|
|
493
|
+
* revocation or clock skew), this mints a genuinely fresh token instead of
|
|
494
|
+
* replaying the rejected one.
|
|
495
|
+
*/
|
|
496
|
+
reauthenticate(): Promise<DreameSession>;
|
|
422
497
|
/** Discover devices and build a live handle per device. */
|
|
423
498
|
discoverDevices(): Promise<readonly BaseDevice[]>;
|
|
424
499
|
/** Tear everything down: close every device push and clear timers. */
|
|
@@ -901,6 +976,268 @@ declare class VacuumCapabilityResolver implements CapabilityResolver {
|
|
|
901
976
|
resolve(model: string): DeviceCapabilities;
|
|
902
977
|
}
|
|
903
978
|
|
|
979
|
+
/**
|
|
980
|
+
* MIoT surface of the Dreame camera service (SIID 10001), reversed byte-exact
|
|
981
|
+
* from the r2538z (X50) React-Native plugin `Monitor` model. Every camera
|
|
982
|
+
* control the app issues is a MIoT `action` on this service; the values below
|
|
983
|
+
* are the verbatim aiid/piid maps from the bundle.
|
|
984
|
+
*/
|
|
985
|
+
/** The camera/monitor service id. Same across the X40/X50 vacuum class. */
|
|
986
|
+
declare const MONITOR_SIID = 10001;
|
|
987
|
+
/** Action instance ids on {@link MONITOR_SIID}. */
|
|
988
|
+
declare const MONITOR_AIID: {
|
|
989
|
+
/** Stream lifecycle + keep-alive (monitor start/end, keep_alive). */
|
|
990
|
+
readonly CAMERA_OPERATE: 1;
|
|
991
|
+
/** Two-way intercom start/end. */
|
|
992
|
+
readonly VOICE_OPERATE: 2;
|
|
993
|
+
/** Generic property-scoped operations. */
|
|
994
|
+
readonly PROPERTY_OPERATE: 3;
|
|
995
|
+
/** Privacy access-code gate (launch/verify/set/reset). */
|
|
996
|
+
readonly ACCESS_CODE_OPERATE: 4;
|
|
997
|
+
/** Video-vendor SDK init / switch. */
|
|
998
|
+
readonly VIDEO_VENDOR: 7;
|
|
999
|
+
};
|
|
1000
|
+
/** Property instance ids on {@link MONITOR_SIID}. */
|
|
1001
|
+
declare const MONITOR_PIID: {
|
|
1002
|
+
readonly MONITOR_STATUS: 1;
|
|
1003
|
+
readonly MONITOR_AUDIO_STATUS: 2;
|
|
1004
|
+
readonly MONITOR_RECORD_STATUS: 4;
|
|
1005
|
+
readonly TAKE_PHOTO: 5;
|
|
1006
|
+
readonly KEEP_ALIVE: 6;
|
|
1007
|
+
readonly MONITOR_FAULTS: 7;
|
|
1008
|
+
readonly FILL_LIGHT_SET: 9;
|
|
1009
|
+
readonly CAMERA_LIGHT_SWITCH: 10;
|
|
1010
|
+
readonly VIDEO_VENDOR_STATUS: 11;
|
|
1011
|
+
readonly GET_PROPERTY: 99;
|
|
1012
|
+
readonly PERSON_DATA: 110;
|
|
1013
|
+
readonly VIDEO_OBSTACLE_SWITCH: 111;
|
|
1014
|
+
readonly VIDEO_OBSTACLE_DATA: 112;
|
|
1015
|
+
readonly MONITOR_TASK_STATUS: 103;
|
|
1016
|
+
readonly UPLOAD_STATUS: 1003;
|
|
1017
|
+
readonly GET_ACCESS_CODE: 1100;
|
|
1018
|
+
readonly SET_ACCESS_CODE: 1101;
|
|
1019
|
+
readonly VERIFY_ACCESS_CODE: 1102;
|
|
1020
|
+
readonly MONITOR_REST_MODE: 1103;
|
|
1021
|
+
readonly SET_ACCESS_HOT_STATUS: 1104;
|
|
1022
|
+
readonly MONITOR_SPACE: 2003;
|
|
1023
|
+
};
|
|
1024
|
+
/**
|
|
1025
|
+
* The `token` field the `startMonitor` action expects, keyed by vendor. Aliyun
|
|
1026
|
+
* LinkVisual uses the literal `"alify"`; Tencent uses `"tx"`. (Agora passes an
|
|
1027
|
+
* RTC access token instead and is not modelled here.)
|
|
1028
|
+
*/
|
|
1029
|
+
declare const MONITOR_VENDOR_TOKEN: Record<'ali' | 'tx', string>;
|
|
1030
|
+
/**
|
|
1031
|
+
* The vacuum work service (SIID 4). The camera has no gimbal — "PTZ" and the
|
|
1032
|
+
* camera-adjacent controls (remote drive, go-to-point, person-follow) drive the
|
|
1033
|
+
* robot itself through this service.
|
|
1034
|
+
*/
|
|
1035
|
+
declare const VACUUM_SIID = 4;
|
|
1036
|
+
declare const VACUUM_MOVE: {
|
|
1037
|
+
/** Remote-drive state property (set_properties), sent ~1 Hz while held. */
|
|
1038
|
+
readonly REMOTE_STATE_PIID: 15;
|
|
1039
|
+
/** Work-mode action instance (go-to-point, cruise, person-follow start). */
|
|
1040
|
+
readonly WORK_AIID: 1;
|
|
1041
|
+
/** Work-mode stop action instance (person-follow stop). */
|
|
1042
|
+
readonly STOP_AIID: 2;
|
|
1043
|
+
/** piid carrying the work-mode selector value in the WORK action. */
|
|
1044
|
+
readonly MODE_PIID: 1;
|
|
1045
|
+
/** piid carrying the point-info JSON in the WORK action. */
|
|
1046
|
+
readonly POINT_PIID: 10;
|
|
1047
|
+
/** Work-mode selector values. */
|
|
1048
|
+
readonly MODE_SPOT: 23;
|
|
1049
|
+
readonly MODE_CRUISE: 22;
|
|
1050
|
+
readonly MODE_PERSON_FOLLOW: 26;
|
|
1051
|
+
readonly MODE_SPOT_CLEAN: 20;
|
|
1052
|
+
/** Auto-switch setter property (fill-light auto/full toggle lives here). */
|
|
1053
|
+
readonly AUTO_SWITCH_PIID: 50;
|
|
1054
|
+
};
|
|
1055
|
+
/** Pet-teasing sound clips playable via {@link VOICE_PLAY_SOUND_AIID}. */
|
|
1056
|
+
declare const PET_SOUNDS: {
|
|
1057
|
+
readonly meow: 684;
|
|
1058
|
+
readonly bark: 685;
|
|
1059
|
+
readonly footsteps: 686;
|
|
1060
|
+
readonly purring: 687;
|
|
1061
|
+
readonly tickTock: 688;
|
|
1062
|
+
};
|
|
1063
|
+
type PetSound = keyof typeof PET_SOUNDS;
|
|
1064
|
+
/** Common whole-robot actions (usable as camera presets). */
|
|
1065
|
+
declare const VACUUM_ACTIONS: {
|
|
1066
|
+
readonly startClean: {
|
|
1067
|
+
readonly siid: 2;
|
|
1068
|
+
readonly aiid: 1;
|
|
1069
|
+
};
|
|
1070
|
+
readonly pauseClean: {
|
|
1071
|
+
readonly siid: 2;
|
|
1072
|
+
readonly aiid: 2;
|
|
1073
|
+
};
|
|
1074
|
+
readonly stopClean: {
|
|
1075
|
+
readonly siid: 4;
|
|
1076
|
+
readonly aiid: 2;
|
|
1077
|
+
};
|
|
1078
|
+
readonly dockWash: {
|
|
1079
|
+
readonly siid: 4;
|
|
1080
|
+
readonly aiid: 4;
|
|
1081
|
+
};
|
|
1082
|
+
readonly autoEmpty: {
|
|
1083
|
+
readonly siid: 15;
|
|
1084
|
+
readonly aiid: 1;
|
|
1085
|
+
};
|
|
1086
|
+
};
|
|
1087
|
+
type VacuumActionKey = keyof typeof VACUUM_ACTIONS;
|
|
1088
|
+
|
|
1089
|
+
/**
|
|
1090
|
+
* Per-session correlation token, exactly as the app's `Monitor.updateSession()`:
|
|
1091
|
+
* `MD5(accountId + "_" + epochMillis)`. Pass an explicit `now` for deterministic
|
|
1092
|
+
* tests. `accountId` is the Dreame account uid.
|
|
1093
|
+
*/
|
|
1094
|
+
declare function makeMonitorSession(accountId: string, now?: number): string;
|
|
1095
|
+
/** SHA-256 hex of a privacy access code, as `Monitor.verifyAccessCode` sends it. */
|
|
1096
|
+
declare function hashAccessCode(code: string): string;
|
|
1097
|
+
/**
|
|
1098
|
+
* Remote-drive value (siid 4 / piid 15), sent ~1 Hz while a direction is held.
|
|
1099
|
+
* `spdv` = forward speed (200 fwd, 0 stop), `spdw` = turn (45 left, -45 right,
|
|
1100
|
+
* 180 turn-around, 0 straight). Pass an explicit `now` for deterministic tests.
|
|
1101
|
+
*/
|
|
1102
|
+
declare function remoteDriveValue(spdv: number, spdw: number, now?: number): string;
|
|
1103
|
+
/** Named remote-drive directions, mapping to (spdv, spdw). */
|
|
1104
|
+
declare const DRIVE_DIRECTIONS: {
|
|
1105
|
+
readonly forward: readonly [200, 0];
|
|
1106
|
+
readonly left: readonly [0, 45];
|
|
1107
|
+
readonly right: readonly [0, -45];
|
|
1108
|
+
readonly turnAround: readonly [0, 180];
|
|
1109
|
+
readonly stop: readonly [0, 0];
|
|
1110
|
+
};
|
|
1111
|
+
type DriveDirection = keyof typeof DRIVE_DIRECTIONS;
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* MIoT device transport the controller drives — satisfied by a nodedreame device
|
|
1115
|
+
* handle (VacuumDevice). `setProperty` is optional so callAction-only fakes still
|
|
1116
|
+
* satisfy the interface; the robot-drive helpers require it.
|
|
1117
|
+
*/
|
|
1118
|
+
interface MonitorActionCaller {
|
|
1119
|
+
callAction(siid: number, aiid: number, input: unknown[]): Promise<unknown>;
|
|
1120
|
+
setProperty?(write: {
|
|
1121
|
+
siid: number;
|
|
1122
|
+
piid: number;
|
|
1123
|
+
value: unknown;
|
|
1124
|
+
}): Promise<unknown>;
|
|
1125
|
+
}
|
|
1126
|
+
/** Minimal relay minter — satisfied by {@link DreameVideoSession}. */
|
|
1127
|
+
interface RelayMinter {
|
|
1128
|
+
getStreamInfo(iotId: string, opts?: {
|
|
1129
|
+
encrypted?: boolean;
|
|
1130
|
+
wakeTimeoutMs?: number;
|
|
1131
|
+
pollMs?: number;
|
|
1132
|
+
}): Promise<{
|
|
1133
|
+
relayUrl: string | null;
|
|
1134
|
+
}>;
|
|
1135
|
+
}
|
|
1136
|
+
interface DreameCameraControllerInput {
|
|
1137
|
+
/** MIoT action transport (the device handle). */
|
|
1138
|
+
readonly device: MonitorActionCaller;
|
|
1139
|
+
/** Aliyun relay minter (a {@link DreameVideoSession}). */
|
|
1140
|
+
readonly relay: RelayMinter;
|
|
1141
|
+
/** LinkVisual channel id for this device (`iotId`). */
|
|
1142
|
+
readonly iotId: string;
|
|
1143
|
+
/** Dreame account uid — seeds the per-session correlation token. */
|
|
1144
|
+
readonly accountId: string;
|
|
1145
|
+
/** Video vendor. Only `ali` mints an RTMP relay; defaults to `ali`. */
|
|
1146
|
+
readonly vendor?: 'ali' | 'tx';
|
|
1147
|
+
/** Privacy access code (PIN) set at pairing, if any. Required to wake a coded camera. */
|
|
1148
|
+
readonly accessCode?: string;
|
|
1149
|
+
/** Aliyun region code the device reports as `area`; defaults to `'4'`. */
|
|
1150
|
+
readonly area?: string;
|
|
1151
|
+
/** Keep-alive cadence in ms (app default 10s). */
|
|
1152
|
+
readonly keepAliveIntervalMs?: number;
|
|
1153
|
+
/** Max time to wait for the relay to come up while the device wakes. */
|
|
1154
|
+
readonly wakeTimeoutMs?: number;
|
|
1155
|
+
/** Reported when a keep-alive tick fails (the stream is likely dying). */
|
|
1156
|
+
readonly onKeepAliveError?: (err: unknown) => void;
|
|
1157
|
+
/** Optional line logger (nodelink-style). Defaults to no-op. */
|
|
1158
|
+
readonly log?: (line: string) => void;
|
|
1159
|
+
}
|
|
1160
|
+
/** A live camera stream: the pullable relay URL plus the device's per-stream key. */
|
|
1161
|
+
interface CameraStreamHandle {
|
|
1162
|
+
/** Plain RTMP relay URL (H.264 + AAC). Single stream; re-open for another consumer. */
|
|
1163
|
+
readonly rtmpUrl: string;
|
|
1164
|
+
/** Device-generated per-session encryption key echoed by `startMonitor`. */
|
|
1165
|
+
readonly encryptionKey: string | null;
|
|
1166
|
+
}
|
|
1167
|
+
declare class DreameCameraController {
|
|
1168
|
+
#private;
|
|
1169
|
+
constructor(input: DreameCameraControllerInput);
|
|
1170
|
+
/** True while a monitor session is active (between {@link open} and {@link close}). */
|
|
1171
|
+
get isOpen(): boolean;
|
|
1172
|
+
/**
|
|
1173
|
+
* Run the full cold-start sequence and return a live relay URL. Starts the
|
|
1174
|
+
* keep-alive loop; the caller MUST {@link close} when done to release the
|
|
1175
|
+
* device. Throws if the monitor cannot be started or no relay comes up.
|
|
1176
|
+
*/
|
|
1177
|
+
open(): Promise<CameraStreamHandle>;
|
|
1178
|
+
/**
|
|
1179
|
+
* Mint a FRESH single-use RTMP relay URL for the already-open monitor. Each
|
|
1180
|
+
* consumer needs its own relay URL, so call this once per ffmpeg/consumer.
|
|
1181
|
+
* Throws if the stream is not open.
|
|
1182
|
+
*/
|
|
1183
|
+
mintRelayUrl(): Promise<string>;
|
|
1184
|
+
/** Start the two-way intercom session (control plane only; see module docs on mic uplink). */
|
|
1185
|
+
startIntercom(opts?: {
|
|
1186
|
+
needRecordSound?: boolean;
|
|
1187
|
+
videoCall?: boolean;
|
|
1188
|
+
}): Promise<unknown>;
|
|
1189
|
+
/** Stop the two-way intercom session. */
|
|
1190
|
+
stopIntercom(): Promise<unknown>;
|
|
1191
|
+
/** Set the fill-light brightness (40–100 = manual %, below 40 / 101 = auto). */
|
|
1192
|
+
setFillLight(value: number): Promise<unknown>;
|
|
1193
|
+
/**
|
|
1194
|
+
* Device-side snapshot: the robot captures a still and uploads it to cloud
|
|
1195
|
+
* storage (read the result back from the `uploadStatus` property). Prefer a
|
|
1196
|
+
* frame-grab from the live stream for an instant image.
|
|
1197
|
+
*/
|
|
1198
|
+
takePhoto(): Promise<unknown>;
|
|
1199
|
+
/**
|
|
1200
|
+
* Remote-drive the robot ("PTZ" substitute — the camera is fixed, the robot
|
|
1201
|
+
* moves). `spdv` = forward speed, `spdw` = turn; send repeatedly (~1 Hz) while
|
|
1202
|
+
* a direction is held, then `stop`. Only valid while the stream is open.
|
|
1203
|
+
*/
|
|
1204
|
+
drive(spdv: number, spdw: number): Promise<unknown>;
|
|
1205
|
+
/** Remote-drive by named direction (forward/left/right/turnAround/stop). */
|
|
1206
|
+
driveDirection(direction: DriveDirection): Promise<unknown>;
|
|
1207
|
+
/** Start person-follow mode (robot tracks a detected person). */
|
|
1208
|
+
startPersonFollow(): Promise<unknown>;
|
|
1209
|
+
/**
|
|
1210
|
+
* Universal stop for the current work mode — stops person-follow, spot-clean,
|
|
1211
|
+
* go-to-point, cruise, etc. (siid 4 / aiid 2).
|
|
1212
|
+
*/
|
|
1213
|
+
stopWork(): Promise<unknown>;
|
|
1214
|
+
/** Stop person-follow mode (alias for {@link stopWork}). */
|
|
1215
|
+
stopPersonFollow(): Promise<unknown>;
|
|
1216
|
+
/** Spot-clean the robot's current position (WorkMode SpotClean). */
|
|
1217
|
+
spotClean(): Promise<unknown>;
|
|
1218
|
+
/** Play one of the robot's sound clips (e.g. pet sounds) by id. */
|
|
1219
|
+
playSound(soundId: number): Promise<unknown>;
|
|
1220
|
+
/** Play a named pet-teasing sound (meow/bark/footsteps/purring/tickTock). */
|
|
1221
|
+
playPetSound(sound: PetSound): Promise<unknown>;
|
|
1222
|
+
/** Run a common whole-robot action (startClean/pauseClean/stopClean/dockWash/autoEmpty). */
|
|
1223
|
+
runVacuumAction(action: VacuumActionKey): Promise<unknown>;
|
|
1224
|
+
/** Find-pet: cruise the home looking for the pet. */
|
|
1225
|
+
findPet(): Promise<unknown>;
|
|
1226
|
+
/** Send the robot to a map point (`spoint` current, `tpoint` targets). */
|
|
1227
|
+
goToPoint(spoint: number[][], tpoint: number[][]): Promise<unknown>;
|
|
1228
|
+
/** Toggle the fill light between auto (`full=false`) and full-on (`full=true`). */
|
|
1229
|
+
setFillLightAuto(full: boolean): Promise<unknown>;
|
|
1230
|
+
/**
|
|
1231
|
+
* Send the robot back to its dock to charge ("go home"). Works regardless of
|
|
1232
|
+
* stream state — usable as a standalone command or a PTZ "home" preset.
|
|
1233
|
+
*/
|
|
1234
|
+
returnToDock(): Promise<unknown>;
|
|
1235
|
+
/** Locate the robot — it beeps. Works regardless of stream state. */
|
|
1236
|
+
locate(): Promise<unknown>;
|
|
1237
|
+
/** Stop the keep-alive loop and release the monitor on the device. Idempotent. */
|
|
1238
|
+
close(): Promise<void>;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
904
1241
|
/**
|
|
905
1242
|
* Public types for the live-map decoder.
|
|
906
1243
|
*
|
|
@@ -1215,6 +1552,19 @@ interface VacuumMap {
|
|
|
1215
1552
|
*/
|
|
1216
1553
|
cleanedArea: MapCleanedAreaOverlay | null;
|
|
1217
1554
|
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Optional decode hints. The AES key is per-blob (it arrives appended to
|
|
1557
|
+
* the OSS object name with a comma, or via the OLD_MAP_DATA multiplexed
|
|
1558
|
+
* format); the IV is per-model (lives in Tasshack's `device_info` table)
|
|
1559
|
+
* and must be supplied here since node-dreame doesn't ship a per-model
|
|
1560
|
+
* IV lookup.
|
|
1561
|
+
*/
|
|
1562
|
+
interface VacuumMapDecodeOptions {
|
|
1563
|
+
/** AES-256-CBC key — 32 ASCII hex chars from `sha256(<rawKey>)[0:32]`. */
|
|
1564
|
+
key?: string;
|
|
1565
|
+
/** AES-256-CBC IV — 16 ASCII bytes, model-specific. */
|
|
1566
|
+
iv?: string;
|
|
1567
|
+
}
|
|
1218
1568
|
|
|
1219
1569
|
/**
|
|
1220
1570
|
* Raster PNG renderer for a decoded `VacuumMap`.
|
|
@@ -1276,6 +1626,34 @@ interface RenderVacuumPngOptions {
|
|
|
1276
1626
|
}
|
|
1277
1627
|
declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
|
|
1278
1628
|
|
|
1629
|
+
interface WifiSignalMap {
|
|
1630
|
+
readonly dimensions: MapDimensions;
|
|
1631
|
+
/** Robot pose from the frame header, or null when absent. */
|
|
1632
|
+
readonly robot: MapPose | null;
|
|
1633
|
+
/** Charging-dock pose from the frame header, or null when absent. */
|
|
1634
|
+
readonly dock: MapPose | null;
|
|
1635
|
+
/** Raw per-cell nibble (`pixel & 15`), row-major (width*height). */
|
|
1636
|
+
readonly cells: Uint8Array;
|
|
1637
|
+
/** Signal bars (1–4) at a world point, 0 = unreached, or null = no Wi-Fi data / off-map. */
|
|
1638
|
+
signalAt(worldX: number, worldY: number): number | null;
|
|
1639
|
+
/** Signal bars at the robot's own position (or null when unknown). */
|
|
1640
|
+
readonly currentSignal: number | null;
|
|
1641
|
+
}
|
|
1642
|
+
/** Decode a Wi-Fi map blob (raw string/Buffer or already-inflated) into signal cells + geometry. */
|
|
1643
|
+
declare function decodeWifiSignalMap(input: string | Buffer, opts?: VacuumMapDecodeOptions): WifiSignalMap;
|
|
1644
|
+
interface RenderWifiSignalPngOptions {
|
|
1645
|
+
/** Upscale factor (each map cell → scale×scale px). Default 4. */
|
|
1646
|
+
scale?: number;
|
|
1647
|
+
/** Draw a marker at the robot position. Default true. */
|
|
1648
|
+
markRobot?: boolean;
|
|
1649
|
+
}
|
|
1650
|
+
/**
|
|
1651
|
+
* Render a simple Wi-Fi coverage heatmap PNG (weakest→strongest blue, unreached
|
|
1652
|
+
* grey, no-data transparent), with an optional robot marker — a ready-to-show
|
|
1653
|
+
* image for consumers.
|
|
1654
|
+
*/
|
|
1655
|
+
declare function renderWifiSignalPng(map: WifiSignalMap, opts?: RenderWifiSignalPngOptions): Buffer;
|
|
1656
|
+
|
|
1279
1657
|
/**
|
|
1280
1658
|
* Resolve a Dreame OSS object name (advertised via the PATH push,
|
|
1281
1659
|
* `siid 6 piid 3`) to a signed download URL and fetch the blob.
|
|
@@ -1368,6 +1746,26 @@ interface VacuumGetMapInput {
|
|
|
1368
1746
|
/** Caller-supplied AbortSignal. */
|
|
1369
1747
|
signal?: AbortSignal;
|
|
1370
1748
|
}
|
|
1749
|
+
/** Input to {@link VacuumDevice.getWifiSignalMap} / {@link VacuumDevice.getCurrentSignal}. */
|
|
1750
|
+
interface WifiSignalMapInput {
|
|
1751
|
+
/** Request the last stored map first (default true). `false` = read what's advertised. */
|
|
1752
|
+
requestFresh?: boolean;
|
|
1753
|
+
/** Poll interval (ms) while waiting for the OSS object to be advertised. Default 2500. */
|
|
1754
|
+
pollMs?: number;
|
|
1755
|
+
/** Max wait (ms) for the map to become available. Default 20000. */
|
|
1756
|
+
maxWaitMs?: number;
|
|
1757
|
+
/** Inject the signed-blob fetcher (tests pass a fake). */
|
|
1758
|
+
fetcher?: OssFetcherLike;
|
|
1759
|
+
/** Optional AES key/iv for an encrypted blob (hex). */
|
|
1760
|
+
key?: string;
|
|
1761
|
+
iv?: string;
|
|
1762
|
+
/** Override the API host (defaults from the device region). */
|
|
1763
|
+
host?: string;
|
|
1764
|
+
/** Per-request timeout override in ms. */
|
|
1765
|
+
timeoutMs?: number;
|
|
1766
|
+
/** Caller-supplied AbortSignal. */
|
|
1767
|
+
signal?: AbortSignal;
|
|
1768
|
+
}
|
|
1371
1769
|
/** A typed Dreame-vacuum handle (state + capability-gated commands). */
|
|
1372
1770
|
declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
|
|
1373
1771
|
#private;
|
|
@@ -1608,6 +2006,29 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
|
|
|
1608
2006
|
* `applyVacuumPFrame` merge primitive ships separately for that work.
|
|
1609
2007
|
*/
|
|
1610
2008
|
getMap(input: VacuumGetMapInput): Promise<VacuumMap>;
|
|
2009
|
+
/**
|
|
2010
|
+
* Fetch the robot's Wi-Fi signal map — the last stored coverage heatmap, with
|
|
2011
|
+
* NO robot movement. Requests it (map service `wifiMap` action, siid 6 aiid 4),
|
|
2012
|
+
* polls `PropWifiMap` (6/15) for the OSS object it advertises, fetches + decodes
|
|
2013
|
+
* it into a per-cell signal grid + geometry + robot/dock poses. The signal at
|
|
2014
|
+
* any world point (incl. the robot's own position) is a lookup — see
|
|
2015
|
+
* {@link WifiSignalMap.signalAt} / {@link WifiSignalMap.currentSignal}.
|
|
2016
|
+
*
|
|
2017
|
+
* Pass `requestFresh: false` to skip the request and read whatever is already
|
|
2018
|
+
* advertised. Throws if no Wi-Fi map exists yet (never run on this device).
|
|
2019
|
+
*/
|
|
2020
|
+
getWifiSignalMap(input?: WifiSignalMapInput): Promise<WifiSignalMap>;
|
|
2021
|
+
/**
|
|
2022
|
+
* Render the Wi-Fi signal map as a simple PNG heatmap image (weakest→strongest,
|
|
2023
|
+
* unreached grey, robot marker) — ready to display. Convenience over
|
|
2024
|
+
* {@link getWifiSignalMap} + {@link renderWifiSignalPng}.
|
|
2025
|
+
*/
|
|
2026
|
+
getWifiSignalImage(input?: WifiSignalMapInput & RenderWifiSignalPngOptions): Promise<Buffer>;
|
|
2027
|
+
/**
|
|
2028
|
+
* The one-call "current Wi-Fi signal": fetches the signal map and returns the
|
|
2029
|
+
* bars (1–4, 0 = unreached) at the robot's own position, or null when unknown.
|
|
2030
|
+
*/
|
|
2031
|
+
getCurrentSignal(input?: WifiSignalMapInput): Promise<number | null>;
|
|
1611
2032
|
/** Drop the P-frame merge base so the next I-frame re-seeds the stream. */
|
|
1612
2033
|
resetMapStream(): void;
|
|
1613
2034
|
/**
|
|
@@ -1624,6 +2045,37 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
|
|
|
1624
2045
|
* frame on every push during a live clean.
|
|
1625
2046
|
*/
|
|
1626
2047
|
fetchLatestMapStreaming(opts?: Omit<VacuumGetMapInput, 'filename'>): Promise<VacuumMap | null>;
|
|
2048
|
+
/**
|
|
2049
|
+
* Build a {@link DreameCameraController} for this device's camera, wired to
|
|
2050
|
+
* both planes it needs: the MIoT action transport (this device handle) and a
|
|
2051
|
+
* fresh Aliyun {@link DreameVideoSession} for the relay. Call
|
|
2052
|
+
* `controller.open()` to autonomously cold-start the stream and get a pullable
|
|
2053
|
+
* RTMP URL, then `controller.close()` to release it.
|
|
2054
|
+
*
|
|
2055
|
+
* The camera's per-session privacy gate means a coded camera needs its PIN:
|
|
2056
|
+
* pass `accessCode` (the code set at pairing). `iotId` is resolved from the
|
|
2057
|
+
* device's cloud video profile when not supplied.
|
|
2058
|
+
*/
|
|
2059
|
+
createCameraController(opts?: {
|
|
2060
|
+
/** LinkVisual channel id; resolved from the cloud profile when omitted. */
|
|
2061
|
+
iotId?: string;
|
|
2062
|
+
/** Privacy PIN set at pairing (required to wake a coded camera). */
|
|
2063
|
+
accessCode?: string;
|
|
2064
|
+
/** Video vendor; defaults to `ali` (the only vendor that mints an RTMP relay). */
|
|
2065
|
+
vendor?: 'ali' | 'tx';
|
|
2066
|
+
/** Aliyun region code reported as `area`; defaults to `'4'`. */
|
|
2067
|
+
area?: string;
|
|
2068
|
+
/** Keep-alive cadence in ms (default 10s). */
|
|
2069
|
+
keepAliveIntervalMs?: number;
|
|
2070
|
+
/** Max time to wait for the relay while the device wakes. */
|
|
2071
|
+
wakeTimeoutMs?: number;
|
|
2072
|
+
/** Reported when a keep-alive tick fails. */
|
|
2073
|
+
onKeepAliveError?: (err: unknown) => void;
|
|
2074
|
+
/** Optional nodelink-style line logger. */
|
|
2075
|
+
log?: (line: string) => void;
|
|
2076
|
+
/** Reuse an existing relay minter (e.g. a shared {@link DreameVideoSession}). */
|
|
2077
|
+
relay?: RelayMinter;
|
|
2078
|
+
}): Promise<DreameCameraController>;
|
|
1627
2079
|
/** Props worth seeding on start() / polling — exported for the facade. */
|
|
1628
2080
|
static readonly DEFAULT_PROPS: readonly [{
|
|
1629
2081
|
readonly siid: 2;
|
|
@@ -2274,13 +2726,13 @@ declare const DeviceDumpSchema: z.ZodObject<{
|
|
|
2274
2726
|
type: z.ZodOptional<z.ZodString>;
|
|
2275
2727
|
}, "strip", z.ZodTypeAny, {
|
|
2276
2728
|
model: string;
|
|
2277
|
-
type?: string | undefined;
|
|
2278
2729
|
region?: string | undefined;
|
|
2730
|
+
type?: string | undefined;
|
|
2279
2731
|
firmware?: string | undefined;
|
|
2280
2732
|
}, {
|
|
2281
2733
|
model: string;
|
|
2282
|
-
type?: string | undefined;
|
|
2283
2734
|
region?: string | undefined;
|
|
2735
|
+
type?: string | undefined;
|
|
2284
2736
|
firmware?: string | undefined;
|
|
2285
2737
|
}>;
|
|
2286
2738
|
observations: z.ZodObject<{
|
|
@@ -2438,8 +2890,8 @@ declare const DeviceDumpSchema: z.ZodObject<{
|
|
|
2438
2890
|
libraryVersion: string;
|
|
2439
2891
|
device: {
|
|
2440
2892
|
model: string;
|
|
2441
|
-
type?: string | undefined;
|
|
2442
2893
|
region?: string | undefined;
|
|
2894
|
+
type?: string | undefined;
|
|
2443
2895
|
firmware?: string | undefined;
|
|
2444
2896
|
};
|
|
2445
2897
|
observations: {
|
|
@@ -2485,8 +2937,8 @@ declare const DeviceDumpSchema: z.ZodObject<{
|
|
|
2485
2937
|
libraryVersion: string;
|
|
2486
2938
|
device: {
|
|
2487
2939
|
model: string;
|
|
2488
|
-
type?: string | undefined;
|
|
2489
2940
|
region?: string | undefined;
|
|
2941
|
+
type?: string | undefined;
|
|
2490
2942
|
firmware?: string | undefined;
|
|
2491
2943
|
};
|
|
2492
2944
|
observations: {
|
|
@@ -2598,4 +3050,670 @@ declare function createDumper(target: DumperDevice, options?: DumperOptions): Du
|
|
|
2598
3050
|
*/
|
|
2599
3051
|
declare function createClientDumper(client: Nodreame, options?: DumperOptions): Dumper[];
|
|
2600
3052
|
|
|
2601
|
-
|
|
3053
|
+
/**
|
|
3054
|
+
* Public types for the video control-plane. The Dreamehome cloud fronts several
|
|
3055
|
+
* camera backends ("vendors"); a device advertises which ones it supports and
|
|
3056
|
+
* which one it is currently provisioned on. This module models only the
|
|
3057
|
+
* control-plane (token + session negotiation); the media transport per vendor
|
|
3058
|
+
* is built on top of it.
|
|
3059
|
+
*/
|
|
3060
|
+
/** Camera backend a device streams through. `tx` = Tencent, `ali` = Aliyun LinkVisual. */
|
|
3061
|
+
type VideoVendor = 'tx' | 'ali';
|
|
3062
|
+
/** Short-lived token minted by `tx/user/accesstoken`, used by the video endpoints. */
|
|
3063
|
+
interface VideoAccessToken {
|
|
3064
|
+
/** Opaque bearer for the third-video service (distinct from the account token). */
|
|
3065
|
+
readonly token: string;
|
|
3066
|
+
/** Numeric video-service user id, as a string. */
|
|
3067
|
+
readonly userId: string | null;
|
|
3068
|
+
/** Epoch-ms at which {@link token} expires (converted from the cloud's epoch-seconds). */
|
|
3069
|
+
readonly expiresAt: number;
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* A device's video profile, derived from its cloud record. Tells you whether the
|
|
3073
|
+
* device has a camera, which vendors it supports, which one it is on now, and —
|
|
3074
|
+
* when on Aliyun — its LinkVisual `iotId` (the id every LinkVisual call needs).
|
|
3075
|
+
*/
|
|
3076
|
+
interface DeviceVideoProfile {
|
|
3077
|
+
/** Dreame device id (the `did`). */
|
|
3078
|
+
readonly did: string;
|
|
3079
|
+
/** Model code, e.g. `dreame.vacuum.r2538z`. */
|
|
3080
|
+
readonly model: string;
|
|
3081
|
+
/** Human product name, e.g. `X50 Ultra Complete`. */
|
|
3082
|
+
readonly displayName: string | null;
|
|
3083
|
+
/** True when the device exposes a camera stream (`permit === "video"`). */
|
|
3084
|
+
readonly videoCapable: boolean;
|
|
3085
|
+
/** The vendor the device is currently provisioned on (`''` in the record → null). */
|
|
3086
|
+
readonly currentVendor: VideoVendor | null;
|
|
3087
|
+
/** Vendors the device can use, in cloud-declared order. */
|
|
3088
|
+
readonly supportedVendors: readonly VideoVendor[];
|
|
3089
|
+
/** True when the cloud picks the vendor dynamically at session time. */
|
|
3090
|
+
readonly dynamicVendor: boolean;
|
|
3091
|
+
/** Aliyun LinkVisual device id, when the device is on (or has been on) Aliyun. */
|
|
3092
|
+
readonly iotId: string | null;
|
|
3093
|
+
/** Whether the device is online. */
|
|
3094
|
+
readonly online: boolean;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
/** Shared inputs for every video control-plane call. Mirrors `listDevices`. */
|
|
3098
|
+
interface VideoRequestInput {
|
|
3099
|
+
session: DreameSession;
|
|
3100
|
+
region: DreameRegion;
|
|
3101
|
+
ctx?: RequestContext;
|
|
3102
|
+
country?: string;
|
|
3103
|
+
lang?: string;
|
|
3104
|
+
apiHost?: string;
|
|
3105
|
+
fetchImpl?: FetchImpl;
|
|
3106
|
+
signal?: AbortSignal;
|
|
3107
|
+
timeoutMs?: number;
|
|
3108
|
+
}
|
|
3109
|
+
/**
|
|
3110
|
+
* Mint the short-lived third-video access token. Required as a body field by the
|
|
3111
|
+
* device-scoped video calls (getIdentity, getP2PInfo, …).
|
|
3112
|
+
*/
|
|
3113
|
+
declare function getVideoAccessToken(input: VideoRequestInput): Promise<VideoAccessToken>;
|
|
3114
|
+
/**
|
|
3115
|
+
* Fetch the Aliyun authCode blob (`getAuthCodeV3`). This opaque hex string is the
|
|
3116
|
+
* material exchanged with Aliyun IoT to obtain a LinkVisual identity — the first
|
|
3117
|
+
* step of the Aliyun media path.
|
|
3118
|
+
*/
|
|
3119
|
+
declare function getAliyunAuthCode(input: VideoRequestInput): Promise<string>;
|
|
3120
|
+
/**
|
|
3121
|
+
* Fetch the account's video `familyId` (used to scope some vendor calls). This
|
|
3122
|
+
* endpoint requires the third-video access token in the body — pass one via
|
|
3123
|
+
* `videoToken`, otherwise a fresh one is minted with {@link getVideoAccessToken}.
|
|
3124
|
+
*/
|
|
3125
|
+
declare function getVideoFamilyId(input: VideoRequestInput & {
|
|
3126
|
+
videoToken?: string;
|
|
3127
|
+
}): Promise<string>;
|
|
3128
|
+
/**
|
|
3129
|
+
* Resolve a device's video profile (vendor, supported vendors, Aliyun iotId,
|
|
3130
|
+
* capability) from its cloud record. `did` is the Dreame device id.
|
|
3131
|
+
*/
|
|
3132
|
+
declare function getDeviceVideoProfile(input: VideoRequestInput & {
|
|
3133
|
+
did: string;
|
|
3134
|
+
}): Promise<DeviceVideoProfile>;
|
|
3135
|
+
|
|
3136
|
+
/** A device record as returned by `device/info` (`data` = single record). */
|
|
3137
|
+
declare const DeviceInfoRecordSchema: z.ZodObject<{
|
|
3138
|
+
did: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3139
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3140
|
+
customName: z.ZodOptional<z.ZodString>;
|
|
3141
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
3142
|
+
online: z.ZodOptional<z.ZodBoolean>;
|
|
3143
|
+
lwt: z.ZodOptional<z.ZodNumber>;
|
|
3144
|
+
property: z.ZodOptional<z.ZodString>;
|
|
3145
|
+
bindDomain: z.ZodOptional<z.ZodString>;
|
|
3146
|
+
deviceInfo: z.ZodOptional<z.ZodObject<{
|
|
3147
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3148
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3149
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3150
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3151
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3152
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3153
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3154
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3155
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3156
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3157
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3158
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3159
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3160
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3161
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3162
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3163
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3164
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3165
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3166
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3167
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
3168
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3169
|
+
did: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3170
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3171
|
+
customName: z.ZodOptional<z.ZodString>;
|
|
3172
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
3173
|
+
online: z.ZodOptional<z.ZodBoolean>;
|
|
3174
|
+
lwt: z.ZodOptional<z.ZodNumber>;
|
|
3175
|
+
property: z.ZodOptional<z.ZodString>;
|
|
3176
|
+
bindDomain: z.ZodOptional<z.ZodString>;
|
|
3177
|
+
deviceInfo: z.ZodOptional<z.ZodObject<{
|
|
3178
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3179
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3180
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3181
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3182
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3183
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3184
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3185
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3186
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3187
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3188
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3189
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3190
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3191
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3192
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3193
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3194
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3195
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3196
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3197
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3198
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
3199
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3200
|
+
did: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3201
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3202
|
+
customName: z.ZodOptional<z.ZodString>;
|
|
3203
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
3204
|
+
online: z.ZodOptional<z.ZodBoolean>;
|
|
3205
|
+
lwt: z.ZodOptional<z.ZodNumber>;
|
|
3206
|
+
property: z.ZodOptional<z.ZodString>;
|
|
3207
|
+
bindDomain: z.ZodOptional<z.ZodString>;
|
|
3208
|
+
deviceInfo: z.ZodOptional<z.ZodObject<{
|
|
3209
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3210
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3211
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3212
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3213
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3214
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3215
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3216
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3217
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3218
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3219
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3220
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3221
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3222
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3223
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3224
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3225
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3226
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3227
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3228
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3229
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
3230
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
3231
|
+
type DeviceInfoRecord = z.infer<typeof DeviceInfoRecordSchema>;
|
|
3232
|
+
|
|
3233
|
+
/**
|
|
3234
|
+
* Derive a {@link DeviceVideoProfile} from a raw device record. Pure — no I/O —
|
|
3235
|
+
* so it is unit-testable against captured records. `videoCapable` is true when
|
|
3236
|
+
* the cloud marks the device `permit: "video"` or lists any supported vendor.
|
|
3237
|
+
*/
|
|
3238
|
+
declare function toVideoProfile(record: DeviceInfoRecord): DeviceVideoProfile;
|
|
3239
|
+
|
|
3240
|
+
/**
|
|
3241
|
+
* Aliyun API Gateway ("x-ca") request signing, HmacSHA1. Used by the LinkVisual
|
|
3242
|
+
* video endpoints the Dreamehome app talks to (living-account + api-iot hosts).
|
|
3243
|
+
*
|
|
3244
|
+
* The string to sign is:
|
|
3245
|
+
* METHOD\n Accept\n Content-MD5\n Content-Type\n Date\n <signed-headers-block> <resource>
|
|
3246
|
+
* where the signed-headers block is `Name:Value\n` for each header named in
|
|
3247
|
+
* `X-Ca-Signature-Headers`, sorted case-insensitively, and `resource` is the
|
|
3248
|
+
* path plus, for form bodies, the `?`-joined sorted form params.
|
|
3249
|
+
*
|
|
3250
|
+
* Verified byte-for-byte against a captured connect.json request (see tests).
|
|
3251
|
+
*/
|
|
3252
|
+
interface SignedRequest {
|
|
3253
|
+
headers: Record<string, string>;
|
|
3254
|
+
body: string;
|
|
3255
|
+
}
|
|
3256
|
+
interface SignInput {
|
|
3257
|
+
method: 'POST' | 'GET';
|
|
3258
|
+
host: string;
|
|
3259
|
+
path: string;
|
|
3260
|
+
appKey: string;
|
|
3261
|
+
appSecret: string;
|
|
3262
|
+
/** JSON payload → sent as the raw body with a Content-MD5. */
|
|
3263
|
+
json?: unknown;
|
|
3264
|
+
/** Form fields → sent as application/x-www-form-urlencoded and folded into the resource. */
|
|
3265
|
+
form?: Record<string, string>;
|
|
3266
|
+
/** Extra x-ca headers to sign (e.g. `{ 'X-Ca-Stage': 'RELEASE' }`). */
|
|
3267
|
+
extraCaHeaders?: Record<string, string>;
|
|
3268
|
+
/** Overrides for deterministic tests. */
|
|
3269
|
+
nonce?: string;
|
|
3270
|
+
timestamp?: number;
|
|
3271
|
+
date?: string;
|
|
3272
|
+
}
|
|
3273
|
+
/** Build the x-ca headers + body for a request. */
|
|
3274
|
+
declare function signApiGatewayRequest(input: SignInput): SignedRequest;
|
|
3275
|
+
|
|
3276
|
+
/** LinkVisual live-stream descriptor returned by /vision/customer/stream/query. */
|
|
3277
|
+
interface StreamInfo {
|
|
3278
|
+
/** Connection strategies the cloud offers, e.g. ["Direct","Relay","NAT"]. */
|
|
3279
|
+
typeList: string[];
|
|
3280
|
+
/** RTMP relay URL. Plain (playable directly) when requested with relayEncrypted:false. */
|
|
3281
|
+
relayUrl: string | null;
|
|
3282
|
+
/** AES key/iv for the relay payload — present (non-empty) only when relayEncrypted:true. */
|
|
3283
|
+
relayDecryptKey: {
|
|
3284
|
+
iv: string;
|
|
3285
|
+
key: string;
|
|
3286
|
+
} | null;
|
|
3287
|
+
/** P2P (Direct/NAT) parameters: STUN/TURN + the WebSocket signaling URL. */
|
|
3288
|
+
p2pInfo: {
|
|
3289
|
+
stunUrl?: string;
|
|
3290
|
+
alterStunUrl?: string;
|
|
3291
|
+
signalUrl?: string;
|
|
3292
|
+
portDetectStunUrlList?: string[];
|
|
3293
|
+
} | null;
|
|
3294
|
+
supportVisionPtz?: boolean;
|
|
3295
|
+
}
|
|
3296
|
+
interface StreamQueryInput {
|
|
3297
|
+
/** Aliyun IoT region id, e.g. `eu-central-1`. */
|
|
3298
|
+
regionId: string;
|
|
3299
|
+
/** Short-lived IoT identity token (from createSessionByAuthCode). */
|
|
3300
|
+
iotToken: string;
|
|
3301
|
+
/** LinkVisual device id. */
|
|
3302
|
+
iotId: string;
|
|
3303
|
+
/**
|
|
3304
|
+
* When false (default) the relay RTMP carries a PLAIN (unencrypted) FLV, so it
|
|
3305
|
+
* can be handed straight to ffmpeg. When true the payload is AES-encrypted and
|
|
3306
|
+
* `relayDecryptKey` is returned.
|
|
3307
|
+
*/
|
|
3308
|
+
relayEncrypted?: boolean;
|
|
3309
|
+
/** 0 = HD main stream (default). */
|
|
3310
|
+
streamType?: number;
|
|
3311
|
+
appKey?: string;
|
|
3312
|
+
appSecret?: string;
|
|
3313
|
+
fetchImpl?: FetchImpl;
|
|
3314
|
+
signal?: AbortSignal;
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
3317
|
+
* Query the LinkVisual live stream for a device. Returns the relay RTMP URL and
|
|
3318
|
+
* P2P parameters. Note: each returned relay token is single-use — request a
|
|
3319
|
+
* fresh one per consumer.
|
|
3320
|
+
*/
|
|
3321
|
+
declare function streamQuery(input: StreamQueryInput): Promise<StreamInfo>;
|
|
3322
|
+
|
|
3323
|
+
/** Aliyun OpenAccount session from loginbyoauth. */
|
|
3324
|
+
interface OaSession {
|
|
3325
|
+
sid: string;
|
|
3326
|
+
refreshToken: string;
|
|
3327
|
+
openId: string;
|
|
3328
|
+
}
|
|
3329
|
+
/** Aliyun IoT session (the iotToken used by every LinkVisual call). */
|
|
3330
|
+
interface IotSession {
|
|
3331
|
+
iotToken: string;
|
|
3332
|
+
refreshToken: string;
|
|
3333
|
+
identityId: string;
|
|
3334
|
+
/** Epoch-ms at which iotToken expires. */
|
|
3335
|
+
expiresAt: number;
|
|
3336
|
+
}
|
|
3337
|
+
interface LoginByOauthInput {
|
|
3338
|
+
authCode: string;
|
|
3339
|
+
regionId: string;
|
|
3340
|
+
deviceId?: string;
|
|
3341
|
+
utdid?: string;
|
|
3342
|
+
fetchImpl?: FetchImpl;
|
|
3343
|
+
}
|
|
3344
|
+
/** Exchange the Dreame authCode for an Aliyun OpenAccount session (step 2). */
|
|
3345
|
+
declare function loginByOauth(input: LoginByOauthInput): Promise<OaSession>;
|
|
3346
|
+
interface CreateIotSessionInput {
|
|
3347
|
+
/** The OpenAccount `sid` from {@link loginByOauth}. */
|
|
3348
|
+
sid: string;
|
|
3349
|
+
regionId: string;
|
|
3350
|
+
fetchImpl?: FetchImpl;
|
|
3351
|
+
}
|
|
3352
|
+
/** Exchange the OA `sid` for an Aliyun IoT session / iotToken (step 3). */
|
|
3353
|
+
declare function createIotSession(input: CreateIotSessionInput): Promise<IotSession>;
|
|
3354
|
+
|
|
3355
|
+
/** Aliyun IoT region id per Dreame region. Only `eu-central-1` is verified live. */
|
|
3356
|
+
declare const ALIYUN_REGION_ID: Record<DreameRegion, string>;
|
|
3357
|
+
|
|
3358
|
+
interface DreameVideoSessionInput {
|
|
3359
|
+
/** An authenticated Dreame account session (from `Nodreame`). */
|
|
3360
|
+
session: DreameSession;
|
|
3361
|
+
region: DreameRegion;
|
|
3362
|
+
fetchImpl?: FetchImpl;
|
|
3363
|
+
}
|
|
3364
|
+
/**
|
|
3365
|
+
* High-level video session for a Dreame account. Establishes and caches the
|
|
3366
|
+
* Aliyun IoT identity (iotToken), and mints fresh LinkVisual RTMP stream URLs
|
|
3367
|
+
* on demand. Each returned relay URL is single-use — call {@link getStreamInfo}
|
|
3368
|
+
* (or {@link getRtmpUrl}) once per consumer.
|
|
3369
|
+
*
|
|
3370
|
+
* The plain (unencrypted) relay URL is a standard RTMP stream: hand it straight
|
|
3371
|
+
* to ffmpeg / scrypted / camstack. No local muxing or RTSP server required here.
|
|
3372
|
+
*/
|
|
3373
|
+
declare class DreameVideoSession {
|
|
3374
|
+
#private;
|
|
3375
|
+
constructor(input: DreameVideoSessionInput);
|
|
3376
|
+
/** The resolved Aliyun IoT region id (e.g. `eu-central-1`). */
|
|
3377
|
+
get regionId(): string;
|
|
3378
|
+
/** Mint (or reuse) a valid Aliyun IoT session, refreshing before expiry. */
|
|
3379
|
+
ensureIotSession(): Promise<IotSession>;
|
|
3380
|
+
/**
|
|
3381
|
+
* Mint a fresh LinkVisual stream descriptor for a device. `encrypted:false`
|
|
3382
|
+
* (default) yields a plain RTMP relay URL directly playable by ffmpeg.
|
|
3383
|
+
*
|
|
3384
|
+
* A camera that has been idle reports "device offline" until the query wakes
|
|
3385
|
+
* its video agent via the LinkVisual cloud. This transparently retries on that
|
|
3386
|
+
* (up to `wakeTimeoutMs`, default 30s) so a single call returns a live stream;
|
|
3387
|
+
* pass `wakeTimeoutMs: 0` to fail fast instead.
|
|
3388
|
+
*/
|
|
3389
|
+
getStreamInfo(iotId: string, opts?: {
|
|
3390
|
+
encrypted?: boolean;
|
|
3391
|
+
wakeTimeoutMs?: number;
|
|
3392
|
+
pollMs?: number;
|
|
3393
|
+
}): Promise<StreamInfo>;
|
|
3394
|
+
/**
|
|
3395
|
+
* Convenience: mint a fresh plain RTMP relay URL for a device, or `null` when
|
|
3396
|
+
* the device is not currently streaming (no relay available).
|
|
3397
|
+
*/
|
|
3398
|
+
getRtmpUrl(iotId: string): Promise<string | null>;
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
/**
|
|
3402
|
+
* Parsers for the robot's on-device AI detections, pushed as camera-service
|
|
3403
|
+
* properties (subscribe to `propertyChanged` for SIID 10001):
|
|
3404
|
+
* - piid 110 (`personFollow` / PERSON_DATA): a tracked-person bounding box
|
|
3405
|
+
* - piid 112 (`videoObstacleData`): a list of obstacle boxes
|
|
3406
|
+
*
|
|
3407
|
+
* The overlay for obstacle boxes is gated on `set_properties siid 28 piid 13 = 1`.
|
|
3408
|
+
* Payloads are JSON strings; these parsers are lenient (return `null` on garbage)
|
|
3409
|
+
* and normalize timestamps (the device reports nanoseconds).
|
|
3410
|
+
*/
|
|
3411
|
+
/** A normalized detection box. Coordinates are passed through as-reported. */
|
|
3412
|
+
interface DetectionBox {
|
|
3413
|
+
readonly x: number;
|
|
3414
|
+
readonly y: number;
|
|
3415
|
+
readonly w: number;
|
|
3416
|
+
readonly h: number;
|
|
3417
|
+
/** Optional label/class if the payload carries one. */
|
|
3418
|
+
readonly label?: string;
|
|
3419
|
+
/** Optional confidence if the payload carries one. */
|
|
3420
|
+
readonly score?: number;
|
|
3421
|
+
}
|
|
3422
|
+
interface PersonFollowDetection {
|
|
3423
|
+
/** Detection time in epoch milliseconds (converted from the device's ns). */
|
|
3424
|
+
readonly timestampMs: number;
|
|
3425
|
+
readonly box: DetectionBox | null;
|
|
3426
|
+
}
|
|
3427
|
+
interface ObstacleDetection {
|
|
3428
|
+
readonly timestampMs: number;
|
|
3429
|
+
readonly boxes: readonly DetectionBox[];
|
|
3430
|
+
}
|
|
3431
|
+
/** Parse a `personFollow` (piid 110) payload into a normalized detection. */
|
|
3432
|
+
declare function parsePersonFollow(value: unknown): PersonFollowDetection | null;
|
|
3433
|
+
/** Parse a `videoObstacleData` (piid 112) payload into normalized boxes. */
|
|
3434
|
+
declare function parseObstacleData(value: unknown): ObstacleDetection | null;
|
|
3435
|
+
|
|
3436
|
+
type AmfValue = number | boolean | string | null | undefined | AmfValue[] | {
|
|
3437
|
+
[key: string]: AmfValue;
|
|
3438
|
+
};
|
|
3439
|
+
|
|
3440
|
+
declare const LV_STATUS: {
|
|
3441
|
+
readonly REQUEST_AUDIO_TYPE: "NetStream.RequestAudioType";
|
|
3442
|
+
readonly FORCE_IFRAME_START: "NetStream.ForceIFrame.Start";
|
|
3443
|
+
readonly PING_START: "NetStream.Ping.Start";
|
|
3444
|
+
readonly PING_RESPONSE: "NetStream.Ping.Response";
|
|
3445
|
+
readonly PRE_CONNECT: "NetStream.PreConnect";
|
|
3446
|
+
readonly KEEPALIVE: "NetStream.Keepalive";
|
|
3447
|
+
readonly SERVER_INFO: "NetStream.ServerInfo";
|
|
3448
|
+
readonly TRANSFER_DATA: "NetStream.TransferData";
|
|
3449
|
+
readonly PLAY_START: "NetStream.Play.Start";
|
|
3450
|
+
/** Server signals the talk (intercom uplink) channel is ready. */
|
|
3451
|
+
readonly TALK_READY: "NetStream.TalkReady";
|
|
3452
|
+
};
|
|
3453
|
+
/**
|
|
3454
|
+
* FLV audio tag header byte for the intercom uplink: G.711 A-law, 8 kHz, 16-bit,
|
|
3455
|
+
* mono — `(7<<4)|(0<<2)|(1<<1)|0` = 0x72. Verified against liblvmedia SetTalkInfo.
|
|
3456
|
+
*/
|
|
3457
|
+
declare const TALK_AUDIO_HEADER_G711A = 114;
|
|
3458
|
+
interface StatusEvent {
|
|
3459
|
+
code: string;
|
|
3460
|
+
level: string;
|
|
3461
|
+
description: string;
|
|
3462
|
+
fields: {
|
|
3463
|
+
[key: string]: AmfValue;
|
|
3464
|
+
};
|
|
3465
|
+
transactionId: number;
|
|
3466
|
+
}
|
|
3467
|
+
interface VideoAccessUnitEvent {
|
|
3468
|
+
data: Buffer;
|
|
3469
|
+
isKeyframe: boolean;
|
|
3470
|
+
videoType: 'H264';
|
|
3471
|
+
microseconds: number;
|
|
3472
|
+
time?: number;
|
|
3473
|
+
}
|
|
3474
|
+
interface AudioInfoEvent {
|
|
3475
|
+
codec: 'aac' | 'g711a' | 'g711u' | 'unknown';
|
|
3476
|
+
sampleRate: number;
|
|
3477
|
+
channels: number;
|
|
3478
|
+
}
|
|
3479
|
+
interface LvRtmpClientOptions {
|
|
3480
|
+
/** relayUrl from stream/query, e.g. rtmp://host:8000/live?token=..&session=../<key>_0 */
|
|
3481
|
+
url: string;
|
|
3482
|
+
/** Appended to app/tcUrl as "&cacheDuration=N" when not already present (the app always sends 3000). */
|
|
3483
|
+
cacheDurationMs?: number;
|
|
3484
|
+
/** Send the onStatus NetStream.RequestAudioType {audio_type:"1"} right after play (app does). */
|
|
3485
|
+
requestAudioType?: boolean;
|
|
3486
|
+
/** Send NetStream.Ping.Start after the first server message and then periodically (app does). */
|
|
3487
|
+
ping?: boolean;
|
|
3488
|
+
pingIntervalMs?: number;
|
|
3489
|
+
/** Also send NetStream.ForceIFrame.Start once playing (the app sends it when it starts an MP4 record). */
|
|
3490
|
+
forceIFrameOnStart?: boolean;
|
|
3491
|
+
connectTimeoutMs?: number;
|
|
3492
|
+
log?: (line: string) => void;
|
|
3493
|
+
}
|
|
3494
|
+
interface Endpoint {
|
|
3495
|
+
host: string;
|
|
3496
|
+
port: number;
|
|
3497
|
+
app: string;
|
|
3498
|
+
tcUrl: string;
|
|
3499
|
+
playpath: string;
|
|
3500
|
+
}
|
|
3501
|
+
/**
|
|
3502
|
+
* Split like librtmp/ffmpeg do: playpath = last path segment, app = everything between host and it
|
|
3503
|
+
* (query string included). Verified against the app's connect/play in both pcaps.
|
|
3504
|
+
*/
|
|
3505
|
+
declare function parseRelayUrl(url: string, cacheDurationMs: number): Endpoint;
|
|
3506
|
+
type Events = {
|
|
3507
|
+
connected: [];
|
|
3508
|
+
status: [StatusEvent];
|
|
3509
|
+
command: [string, AmfValue[]];
|
|
3510
|
+
metadata: [{
|
|
3511
|
+
[key: string]: AmfValue;
|
|
3512
|
+
}];
|
|
3513
|
+
audio: [{
|
|
3514
|
+
timestamp: number;
|
|
3515
|
+
body: Buffer;
|
|
3516
|
+
}];
|
|
3517
|
+
video: [{
|
|
3518
|
+
timestamp: number;
|
|
3519
|
+
body: Buffer;
|
|
3520
|
+
}];
|
|
3521
|
+
videoAccessUnit: [VideoAccessUnitEvent];
|
|
3522
|
+
audioFrame: [Buffer];
|
|
3523
|
+
audioInfo: [AudioInfoEvent];
|
|
3524
|
+
error: [Error];
|
|
3525
|
+
close: [];
|
|
3526
|
+
};
|
|
3527
|
+
declare class LvRtmpClient extends EventEmitter<Events> {
|
|
3528
|
+
private readonly opts;
|
|
3529
|
+
private readonly endpoint;
|
|
3530
|
+
private socket;
|
|
3531
|
+
private handshakeBuf;
|
|
3532
|
+
private handshakeDone;
|
|
3533
|
+
private c1Time;
|
|
3534
|
+
private reader;
|
|
3535
|
+
private transactionId;
|
|
3536
|
+
private bytesIn;
|
|
3537
|
+
private bytesAcked;
|
|
3538
|
+
private ackWindow;
|
|
3539
|
+
private pingTimer;
|
|
3540
|
+
private firstMessageSeen;
|
|
3541
|
+
private avcConfig;
|
|
3542
|
+
private aacConfig;
|
|
3543
|
+
private closed;
|
|
3544
|
+
constructor(options: LvRtmpClientOptions);
|
|
3545
|
+
/** Open TCP, write C0+C1 and the pipelined connect/play/RequestAudioType. Resolves once written. */
|
|
3546
|
+
connect(): Promise<void>;
|
|
3547
|
+
close(): void;
|
|
3548
|
+
/** onStatus encoder — byte-exact replica of liblvmedia IOT_RTMP_SendMessage (csid 3, fmt 1, ts 0). */
|
|
3549
|
+
buildStatus(code: string, extra?: readonly [string, string]): Buffer;
|
|
3550
|
+
sendStatus(code: string, extra?: readonly [string, string]): void;
|
|
3551
|
+
forceIFrame(): void;
|
|
3552
|
+
private buildC0C1;
|
|
3553
|
+
private buildConnect;
|
|
3554
|
+
private buildPlay;
|
|
3555
|
+
/** Exposed for the byte-exact self-test against the pcap. */
|
|
3556
|
+
buildOpeningSequence(): Buffer[];
|
|
3557
|
+
/**
|
|
3558
|
+
* Push one intercom (talk-back) audio frame UPSTREAM to the device — an RTMP
|
|
3559
|
+
* type-8 audio message on the talk chunk stream (csid 4, msid 1), exactly as
|
|
3560
|
+
* liblvmedia's `IOT_RTMP_SendAudio` does. `payload` is the raw codec audio
|
|
3561
|
+
* (default framing = G.711 A-law); `tsMs` is a running millisecond timestamp.
|
|
3562
|
+
* Only meaningful after the server sends {@link LV_STATUS.TALK_READY}.
|
|
3563
|
+
*/
|
|
3564
|
+
sendAudioFrame(payload: Buffer, tsMs: number, header?: number): void;
|
|
3565
|
+
private write;
|
|
3566
|
+
private sendAck;
|
|
3567
|
+
private sendPingResponse;
|
|
3568
|
+
private startPinging;
|
|
3569
|
+
private onData;
|
|
3570
|
+
private onHandshake;
|
|
3571
|
+
private dispatch;
|
|
3572
|
+
private onUserControl;
|
|
3573
|
+
private onCommand;
|
|
3574
|
+
private onDataMessage;
|
|
3575
|
+
private onAudio;
|
|
3576
|
+
private onVideo;
|
|
3577
|
+
private fail;
|
|
3578
|
+
private onClose;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
/**
|
|
3582
|
+
* End-to-end camera media pipeline: autonomously cold-start the LinkVisual
|
|
3583
|
+
* monitor, connect to the resulting RTMP relay, and re-emit demuxed elementary
|
|
3584
|
+
* frames — H.264 access units (Annex-B, SPS/PPS prepended on keyframes) and AAC
|
|
3585
|
+
* (ADTS) — plus lifecycle events.
|
|
3586
|
+
*
|
|
3587
|
+
* This is the reusable frame feed for any external consumer (scrypted's RFC4571
|
|
3588
|
+
* muxer, camstack, a file writer). It owns the {@link DreameCameraController}
|
|
3589
|
+
* lifecycle: {@link start} opens the monitor and connects; {@link stop} closes
|
|
3590
|
+
* the relay socket and releases the monitor on the device.
|
|
3591
|
+
*/
|
|
3592
|
+
|
|
3593
|
+
/** The subset of {@link LvRtmpClient} the pipeline drives — injectable for tests. */
|
|
3594
|
+
interface FrameSource {
|
|
3595
|
+
connect(): Promise<void>;
|
|
3596
|
+
close(): void;
|
|
3597
|
+
on(event: 'videoAccessUnit', cb: (e: VideoAccessUnitEvent) => void): void;
|
|
3598
|
+
on(event: 'audioFrame', cb: (buf: Buffer) => void): void;
|
|
3599
|
+
on(event: 'audioInfo', cb: (info: AudioInfoEvent) => void): void;
|
|
3600
|
+
on(event: 'status', cb: (e: StatusEvent) => void): void;
|
|
3601
|
+
on(event: 'error', cb: (err: Error) => void): void;
|
|
3602
|
+
on(event: 'close', cb: () => void): void;
|
|
3603
|
+
/** Push an intercom (talk-back) audio frame upstream. */
|
|
3604
|
+
sendAudioFrame(payload: Buffer, tsMs: number, header?: number): void;
|
|
3605
|
+
}
|
|
3606
|
+
/** Factory for the RTMP frame source; defaults to a real {@link LvRtmpClient}. */
|
|
3607
|
+
type FrameSourceFactory = (url: string) => FrameSource;
|
|
3608
|
+
interface DreameCameraStreamEvents {
|
|
3609
|
+
/** Emitted once the relay is connected; carries the (single-use) RTMP URL. */
|
|
3610
|
+
started: [{
|
|
3611
|
+
rtmpUrl: string;
|
|
3612
|
+
}];
|
|
3613
|
+
/** One H.264 access unit (Annex-B). */
|
|
3614
|
+
videoAccessUnit: [VideoAccessUnitEvent];
|
|
3615
|
+
/** One audio frame (AAC in ADTS, or raw G.711). */
|
|
3616
|
+
audioFrame: [Buffer];
|
|
3617
|
+
/** Audio format, emitted once known. */
|
|
3618
|
+
audioInfo: [AudioInfoEvent];
|
|
3619
|
+
/** Emitted after the pipeline is fully torn down. */
|
|
3620
|
+
stopped: [];
|
|
3621
|
+
error: [Error];
|
|
3622
|
+
[event: string]: unknown[];
|
|
3623
|
+
}
|
|
3624
|
+
interface DreameCameraStreamInput {
|
|
3625
|
+
/** A controller for the target camera (not yet opened). */
|
|
3626
|
+
controller: DreameCameraController;
|
|
3627
|
+
/** Override the RTMP client (tests inject a fake). */
|
|
3628
|
+
clientFactory?: FrameSourceFactory;
|
|
3629
|
+
/** Forwarded to {@link LvRtmpClient}: send NetStream.RequestAudioType after play. */
|
|
3630
|
+
requestAudioType?: boolean;
|
|
3631
|
+
/** Forwarded to {@link LvRtmpClient}: send periodic NetStream.Ping. */
|
|
3632
|
+
ping?: boolean;
|
|
3633
|
+
/** Optional line logger for the RTMP client. */
|
|
3634
|
+
log?: (line: string) => void;
|
|
3635
|
+
}
|
|
3636
|
+
declare class DreameCameraStream extends TypedEmitter<DreameCameraStreamEvents> {
|
|
3637
|
+
#private;
|
|
3638
|
+
constructor(input: DreameCameraStreamInput);
|
|
3639
|
+
/** True while frames are flowing. */
|
|
3640
|
+
get isRunning(): boolean;
|
|
3641
|
+
/**
|
|
3642
|
+
* Cold-start the monitor, connect to the relay, and begin emitting frames.
|
|
3643
|
+
* Resolves once connected. Throws (and cleans up) if the monitor or relay
|
|
3644
|
+
* connection fails.
|
|
3645
|
+
*/
|
|
3646
|
+
start(): Promise<{
|
|
3647
|
+
rtmpUrl: string;
|
|
3648
|
+
}>;
|
|
3649
|
+
/** Close the relay connection and release the monitor. Idempotent. */
|
|
3650
|
+
stop(): Promise<void>;
|
|
3651
|
+
/**
|
|
3652
|
+
* Open the two-way intercom: send the MIoT start, then resolve once the relay
|
|
3653
|
+
* signals the talk channel is ready (so it's safe to {@link sendTalkPcm}).
|
|
3654
|
+
* Rejects if not ready within `timeoutMs`.
|
|
3655
|
+
*/
|
|
3656
|
+
startTalk(opts?: {
|
|
3657
|
+
needRecordSound?: boolean;
|
|
3658
|
+
videoCall?: boolean;
|
|
3659
|
+
timeoutMs?: number;
|
|
3660
|
+
}): Promise<void>;
|
|
3661
|
+
/** Resolve when the talk channel is ready (or reject on timeout). */
|
|
3662
|
+
whenTalkReady(timeoutMs?: number): Promise<void>;
|
|
3663
|
+
/**
|
|
3664
|
+
* Encode 16-bit LE mono PCM (8 kHz) to G.711 A-law and push it upstream.
|
|
3665
|
+
* `tsMs` defaults to a running clock advanced by the frame duration.
|
|
3666
|
+
*/
|
|
3667
|
+
sendTalkPcm(pcm16le: Buffer, tsMs?: number): void;
|
|
3668
|
+
/** Push raw A-law audio upstream (advancing the running talk clock). */
|
|
3669
|
+
sendTalkAlaw(alaw: Buffer, tsMs?: number): void;
|
|
3670
|
+
/** Close the two-way intercom (MIoT stop). */
|
|
3671
|
+
stopTalk(): Promise<void>;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
/**
|
|
3675
|
+
* G.711 A-law codec (ITU-T G.711) — the intercom uplink format for Dreame
|
|
3676
|
+
* cameras (8 kHz, mono, 16-bit PCM in). Pure functions, no state.
|
|
3677
|
+
*/
|
|
3678
|
+
/** Encode one 16-bit signed PCM sample to an A-law byte. */
|
|
3679
|
+
declare function pcm16ToALaw(sample: number): number;
|
|
3680
|
+
/**
|
|
3681
|
+
* Encode a buffer of little-endian 16-bit PCM samples to A-law bytes
|
|
3682
|
+
* (one output byte per input sample).
|
|
3683
|
+
*/
|
|
3684
|
+
declare function pcm16leToALaw(pcm: Buffer): Buffer;
|
|
3685
|
+
|
|
3686
|
+
interface AvcConfig {
|
|
3687
|
+
sps: Buffer[];
|
|
3688
|
+
pps: Buffer[];
|
|
3689
|
+
nalLengthSize: number;
|
|
3690
|
+
}
|
|
3691
|
+
interface AacConfig {
|
|
3692
|
+
objectType: number;
|
|
3693
|
+
samplingFrequencyIndex: number;
|
|
3694
|
+
channelConfiguration: number;
|
|
3695
|
+
}
|
|
3696
|
+
/** Parse an AVCDecoderConfigurationRecord (FLV AVC sequence header payload). */
|
|
3697
|
+
declare function parseAvcConfig(rec: Buffer): AvcConfig;
|
|
3698
|
+
/** Length-prefixed NALUs -> Annex-B. Prepends SPS/PPS when `prependParams` is set (for keyframes). */
|
|
3699
|
+
declare function avccToAnnexB(data: Buffer, cfg: AvcConfig, prependParams: boolean): Buffer;
|
|
3700
|
+
declare function parseAacConfig(asc: Buffer): AacConfig;
|
|
3701
|
+
declare function aacSampleRate(cfg: AacConfig): number;
|
|
3702
|
+
/** Wrap one raw AAC frame in a 7-byte ADTS header. */
|
|
3703
|
+
declare function aacToAdts(raw: Buffer, cfg: AacConfig): Buffer;
|
|
3704
|
+
|
|
3705
|
+
interface ListDevicesInput {
|
|
3706
|
+
session: DreameSession;
|
|
3707
|
+
region: DreameRegion;
|
|
3708
|
+
ctx?: RequestContext;
|
|
3709
|
+
country?: string;
|
|
3710
|
+
lang?: string;
|
|
3711
|
+
apiHost?: string;
|
|
3712
|
+
fetchImpl?: FetchImpl;
|
|
3713
|
+
signal?: AbortSignal;
|
|
3714
|
+
timeoutMs?: number;
|
|
3715
|
+
}
|
|
3716
|
+
/** Enumerate the devices visible to the authenticated account (incl. shared). */
|
|
3717
|
+
declare function listDevices(input: ListDevicesInput): Promise<DreameDevice[]>;
|
|
3718
|
+
|
|
3719
|
+
export { AI_FEATURE_BIT, AI_FEATURE_JSON_KEY, ALIYUN_REGION_ID, AUTO_SWITCH_JSON_KEY, type AacConfig, type AiDetectionRaw, type AudioInfoEvent, type AutoSwitchKey, type AutoSwitchRaw, type AvcConfig, BaseDevice, type BaseDeviceEvents, type BatchDeviceDataFetcher, type BrokerEndpoint, type CameraStreamHandle, type CapabilityResolver, ChargingStatus, type CleanOpts, CleaningMode, type ConsumableReading, type ConsumableSpec, type CreateDeviceArgs, DRIVE_DIRECTIONS, DefaultCapabilityResolver, type DetectionBox, type DeviceCapabilities, type DeviceConnectivity, type DeviceDump, type DeviceEvent, type DeviceVideoProfile, type DreameAiFeature, DreameApiError, DreameAuthError, DreameCameraController, type DreameCameraControllerInput, DreameCameraStream, type DreameCameraStreamEvents, type DreameCameraStreamInput, type DreameCloudState, type DreameConsumableKey, type DreameDevice, DreameDeviceOfflineError, DreameError, type DreameRegion, type DreameSession, DreameTransportError, DreameVideoSession, type DreameVideoSessionInput, type DriveDirection, type DumperOptions, type FrameSource, type FrameSourceFactory, type IotSession, LIBRARY_NAME, LV_STATUS, type ListDevicesInput, LvRtmpClient, type LvRtmpClientOptions, MONITOR_AIID, MONITOR_PIID, MONITOR_SIID, MONITOR_VENDOR_TOKEN, MODEL_CAPABILITIES as MOWER_MODEL_CAPABILITIES, MOWER_TASK_SUBSTATES, type MapBoundingBox, type MapCleanedAreaOverlay, type MapColorScheme, type MapDimensions, type MapFrameType, type MapLayer, type MapLayerType, type MapLowLyingArea, type MapObstacle, type MapPath, type MapPathType, type MapPoint, type MapPose, type MapRestrictedArea, type MapRoom, type MapRoomWall, type MapRun, type MapSegment, type MapStorey, type MapVirtualWall, type MapWallsInfo, type MiotAction, MiotError, type MiotProp, MiotState, type MonitorActionCaller, type MowerAvailableMap, type MowerCapabilities, MowerCapabilityResolver, MowerChargingStatus, type MowerConsumableKey, type MowerConsumableReading, type MowerContour, MowerControlAction, type MowerControlState, MowerDevice, type MowerDeviceInput, MowerFault, type MowerFaultSeverity, type MowerHeartbeat, type MowerMap, type MowerMapBoundary, type MowerMowPath, type MowerMowingProgress, type MowerPathEntry, type MowerPoint, type MowerSpotArea, MowerStatus, type MowerTaskDescriptor, MowerTaskStatus, type MowerTaskSubState, type MowerZone, Nodreame, type NodreameDeps, type NodreameEvents, type NodreameOptions, type OaSession, type ObstacleDetection, type OssFetchInput, type OssFetcherLike, PET_SOUNDS, type PersonFollowDetection, type PetSound, type PropertyChangedEvent, type PropertyResult, type PropertyState, type PropertyWrite, type RelayMinter, type RenderMowerSvgOptions, type RenderVacuumPngOptions, type RenderWifiSignalPngOptions, type StateChangedEvent, type StatusEvent, type StreamInfo, type StreamQueryInput, SuctionLevel, TALK_AUDIO_HEADER_G711A, TaskStatus, VACUUM_ACTIONS, VACUUM_CONSUMABLES, MODEL_CAPABILITIES$1 as VACUUM_MODEL_CAPABILITIES, VACUUM_MOVE, VACUUM_SIID, type VacuumActionKey, type VacuumCapabilities, VacuumCapabilityResolver, VacuumDevice, type VacuumGetMapInput, type VacuumMap, type VideoAccessToken, type VideoAccessUnitEvent, type VideoRequestInput, type VideoVendor, WaterVolume, type WifiSignalMap, type WifiSignalMapInput, aacSampleRate, aacToAdts, avccToAnnexB, consumableSpec, createClientDumper, createDumper, createIotSession, decodeAiFeature, decodeAutoSwitch, decodeAutoSwitchAll, decodeWifiSignalMap, encodeAiFeatureWrite, encodeAutoSwitchWrite, extractMowerConsumableValues, getAliyunAuthCode, getDeviceVideoProfile, getMowerCapabilities, getVacuumCapabilities, getVideoAccessToken, getVideoFamilyId, hashAccessCode, isAuthRefusedError, isDreameConsumableKey, listDevices, loginByOauth, makeMonitorSession, mowerConsumableIndex, mowerFaultSeverity, parseAacConfig, parseAvcConfig, parseConnectivity, parseMowerConsumables, parseMowerHeartbeat, parseMowingProgress, parseObstacleData, parsePersonFollow, parseRelayUrl, pcm16ToALaw, pcm16leToALaw, remoteDriveValue, renderMowerSvg, renderVacuumPng, renderWifiSignalPng, resolveCapabilities, signApiGatewayRequest, streamQuery, supportedAutoSwitchKeys, toVideoProfile };
|