@apocaliss92/nodedreame 1.11.11 → 1.12.1
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 +2303 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1122 -5
- package/dist/index.d.ts +1122 -5
- package/dist/index.js +2258 -64
- 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 {
|
|
@@ -932,6 +976,268 @@ declare class VacuumCapabilityResolver implements CapabilityResolver {
|
|
|
932
976
|
resolve(model: string): DeviceCapabilities;
|
|
933
977
|
}
|
|
934
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
|
+
|
|
935
1241
|
/**
|
|
936
1242
|
* Public types for the live-map decoder.
|
|
937
1243
|
*
|
|
@@ -1246,6 +1552,19 @@ interface VacuumMap {
|
|
|
1246
1552
|
*/
|
|
1247
1553
|
cleanedArea: MapCleanedAreaOverlay | null;
|
|
1248
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
|
+
}
|
|
1249
1568
|
|
|
1250
1569
|
/**
|
|
1251
1570
|
* Raster PNG renderer for a decoded `VacuumMap`.
|
|
@@ -1307,6 +1626,34 @@ interface RenderVacuumPngOptions {
|
|
|
1307
1626
|
}
|
|
1308
1627
|
declare function renderVacuumPng(map: VacuumMap, opts?: RenderVacuumPngOptions): Buffer;
|
|
1309
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
|
+
|
|
1310
1657
|
/**
|
|
1311
1658
|
* Resolve a Dreame OSS object name (advertised via the PATH push,
|
|
1312
1659
|
* `siid 6 piid 3`) to a signed download URL and fetch the blob.
|
|
@@ -1399,6 +1746,26 @@ interface VacuumGetMapInput {
|
|
|
1399
1746
|
/** Caller-supplied AbortSignal. */
|
|
1400
1747
|
signal?: AbortSignal;
|
|
1401
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
|
+
}
|
|
1402
1769
|
/** A typed Dreame-vacuum handle (state + capability-gated commands). */
|
|
1403
1770
|
declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
|
|
1404
1771
|
#private;
|
|
@@ -1639,6 +2006,29 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
|
|
|
1639
2006
|
* `applyVacuumPFrame` merge primitive ships separately for that work.
|
|
1640
2007
|
*/
|
|
1641
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>;
|
|
1642
2032
|
/** Drop the P-frame merge base so the next I-frame re-seeds the stream. */
|
|
1643
2033
|
resetMapStream(): void;
|
|
1644
2034
|
/**
|
|
@@ -1655,6 +2045,37 @@ declare class VacuumDevice extends BaseDevice<VacuumDeviceEvents> {
|
|
|
1655
2045
|
* frame on every push during a live clean.
|
|
1656
2046
|
*/
|
|
1657
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>;
|
|
1658
2079
|
/** Props worth seeding on start() / polling — exported for the facade. */
|
|
1659
2080
|
static readonly DEFAULT_PROPS: readonly [{
|
|
1660
2081
|
readonly siid: 2;
|
|
@@ -2305,13 +2726,13 @@ declare const DeviceDumpSchema: z.ZodObject<{
|
|
|
2305
2726
|
type: z.ZodOptional<z.ZodString>;
|
|
2306
2727
|
}, "strip", z.ZodTypeAny, {
|
|
2307
2728
|
model: string;
|
|
2308
|
-
type?: string | undefined;
|
|
2309
2729
|
region?: string | undefined;
|
|
2730
|
+
type?: string | undefined;
|
|
2310
2731
|
firmware?: string | undefined;
|
|
2311
2732
|
}, {
|
|
2312
2733
|
model: string;
|
|
2313
|
-
type?: string | undefined;
|
|
2314
2734
|
region?: string | undefined;
|
|
2735
|
+
type?: string | undefined;
|
|
2315
2736
|
firmware?: string | undefined;
|
|
2316
2737
|
}>;
|
|
2317
2738
|
observations: z.ZodObject<{
|
|
@@ -2469,8 +2890,8 @@ declare const DeviceDumpSchema: z.ZodObject<{
|
|
|
2469
2890
|
libraryVersion: string;
|
|
2470
2891
|
device: {
|
|
2471
2892
|
model: string;
|
|
2472
|
-
type?: string | undefined;
|
|
2473
2893
|
region?: string | undefined;
|
|
2894
|
+
type?: string | undefined;
|
|
2474
2895
|
firmware?: string | undefined;
|
|
2475
2896
|
};
|
|
2476
2897
|
observations: {
|
|
@@ -2516,8 +2937,8 @@ declare const DeviceDumpSchema: z.ZodObject<{
|
|
|
2516
2937
|
libraryVersion: string;
|
|
2517
2938
|
device: {
|
|
2518
2939
|
model: string;
|
|
2519
|
-
type?: string | undefined;
|
|
2520
2940
|
region?: string | undefined;
|
|
2941
|
+
type?: string | undefined;
|
|
2521
2942
|
firmware?: string | undefined;
|
|
2522
2943
|
};
|
|
2523
2944
|
observations: {
|
|
@@ -2629,4 +3050,700 @@ declare function createDumper(target: DumperDevice, options?: DumperOptions): Du
|
|
|
2629
3050
|
*/
|
|
2630
3051
|
declare function createClientDumper(client: Nodreame, options?: DumperOptions): Dumper[];
|
|
2631
3052
|
|
|
2632
|
-
|
|
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
|
+
/**
|
|
3137
|
+
* Why this device cannot open a camera channel — or `null` when it can.
|
|
3138
|
+
*
|
|
3139
|
+
* The camera path needs one thing from the cloud: the `iotId`, which is the
|
|
3140
|
+
* CHANNEL id `startMonitor` addresses (for both vendors — see
|
|
3141
|
+
* `startMonitorParams`). When it is absent the call cannot proceed, and for a
|
|
3142
|
+
* long time that was reported as a single sentence covering four different
|
|
3143
|
+
* situations:
|
|
3144
|
+
*
|
|
3145
|
+
* "device has no LinkVisual iotId; not a camera device or not provisioned"
|
|
3146
|
+
*
|
|
3147
|
+
* A CamStack operator met it on a robot whose camera they had just enabled in
|
|
3148
|
+
* the Dreame app (2026-09-16). Working out which of the four they were in
|
|
3149
|
+
* meant reading this library's source — the sentence names a MISSING FIELD,
|
|
3150
|
+
* not a cause, and the profile sitting right next to it already knows whether
|
|
3151
|
+
* the device is a camera at all, whether it is online, and which vendors it is
|
|
3152
|
+
* on and supports.
|
|
3153
|
+
*
|
|
3154
|
+
* The four states want four different actions:
|
|
3155
|
+
*
|
|
3156
|
+
* - not a camera → wrong device; nothing done here will help
|
|
3157
|
+
* - offline → wait; the binding is published BY the device
|
|
3158
|
+
* - camera, online, no channel → open it once in the vendor app, which is
|
|
3159
|
+
* what creates the binding
|
|
3160
|
+
* - channel present → not this function's business
|
|
3161
|
+
*
|
|
3162
|
+
* Pure, so it is testable against captured profiles without a cloud.
|
|
3163
|
+
*/
|
|
3164
|
+
declare function explainNoCameraChannel(profile: DeviceVideoProfile): string | null;
|
|
3165
|
+
|
|
3166
|
+
/** A device record as returned by `device/info` (`data` = single record). */
|
|
3167
|
+
declare const DeviceInfoRecordSchema: z.ZodObject<{
|
|
3168
|
+
did: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3169
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3170
|
+
customName: z.ZodOptional<z.ZodString>;
|
|
3171
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
3172
|
+
online: z.ZodOptional<z.ZodBoolean>;
|
|
3173
|
+
lwt: z.ZodOptional<z.ZodNumber>;
|
|
3174
|
+
property: z.ZodOptional<z.ZodString>;
|
|
3175
|
+
bindDomain: z.ZodOptional<z.ZodString>;
|
|
3176
|
+
deviceInfo: z.ZodOptional<z.ZodObject<{
|
|
3177
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3178
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3179
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3180
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3181
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3182
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3183
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3184
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3185
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3186
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3187
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3188
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3189
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3190
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3191
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3192
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3193
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3194
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3195
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3196
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3197
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
3198
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3199
|
+
did: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3200
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3201
|
+
customName: z.ZodOptional<z.ZodString>;
|
|
3202
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
3203
|
+
online: z.ZodOptional<z.ZodBoolean>;
|
|
3204
|
+
lwt: z.ZodOptional<z.ZodNumber>;
|
|
3205
|
+
property: z.ZodOptional<z.ZodString>;
|
|
3206
|
+
bindDomain: z.ZodOptional<z.ZodString>;
|
|
3207
|
+
deviceInfo: z.ZodOptional<z.ZodObject<{
|
|
3208
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3209
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3210
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3211
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3212
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3213
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3214
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3215
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3216
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3217
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3218
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3219
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3220
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3221
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3222
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3223
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3224
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3225
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3226
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3227
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3228
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
3229
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3230
|
+
did: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3231
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3232
|
+
customName: z.ZodOptional<z.ZodString>;
|
|
3233
|
+
vendor: z.ZodOptional<z.ZodString>;
|
|
3234
|
+
online: z.ZodOptional<z.ZodBoolean>;
|
|
3235
|
+
lwt: z.ZodOptional<z.ZodNumber>;
|
|
3236
|
+
property: z.ZodOptional<z.ZodString>;
|
|
3237
|
+
bindDomain: z.ZodOptional<z.ZodString>;
|
|
3238
|
+
deviceInfo: z.ZodOptional<z.ZodObject<{
|
|
3239
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3240
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3241
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3242
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3243
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3244
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3245
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
3246
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3247
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3248
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3249
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3250
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3251
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3252
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
3253
|
+
model: z.ZodOptional<z.ZodString>;
|
|
3254
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
3255
|
+
permit: z.ZodOptional<z.ZodString>;
|
|
3256
|
+
videoDynamicVendor: z.ZodOptional<z.ZodBoolean>;
|
|
3257
|
+
defaultVendors: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
3258
|
+
productId: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
|
|
3259
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
3260
|
+
}, z.ZodTypeAny, "passthrough">>;
|
|
3261
|
+
type DeviceInfoRecord = z.infer<typeof DeviceInfoRecordSchema>;
|
|
3262
|
+
|
|
3263
|
+
/**
|
|
3264
|
+
* Derive a {@link DeviceVideoProfile} from a raw device record. Pure — no I/O —
|
|
3265
|
+
* so it is unit-testable against captured records. `videoCapable` is true when
|
|
3266
|
+
* the cloud marks the device `permit: "video"` or lists any supported vendor.
|
|
3267
|
+
*/
|
|
3268
|
+
declare function toVideoProfile(record: DeviceInfoRecord): DeviceVideoProfile;
|
|
3269
|
+
|
|
3270
|
+
/**
|
|
3271
|
+
* Aliyun API Gateway ("x-ca") request signing, HmacSHA1. Used by the LinkVisual
|
|
3272
|
+
* video endpoints the Dreamehome app talks to (living-account + api-iot hosts).
|
|
3273
|
+
*
|
|
3274
|
+
* The string to sign is:
|
|
3275
|
+
* METHOD\n Accept\n Content-MD5\n Content-Type\n Date\n <signed-headers-block> <resource>
|
|
3276
|
+
* where the signed-headers block is `Name:Value\n` for each header named in
|
|
3277
|
+
* `X-Ca-Signature-Headers`, sorted case-insensitively, and `resource` is the
|
|
3278
|
+
* path plus, for form bodies, the `?`-joined sorted form params.
|
|
3279
|
+
*
|
|
3280
|
+
* Verified byte-for-byte against a captured connect.json request (see tests).
|
|
3281
|
+
*/
|
|
3282
|
+
interface SignedRequest {
|
|
3283
|
+
headers: Record<string, string>;
|
|
3284
|
+
body: string;
|
|
3285
|
+
}
|
|
3286
|
+
interface SignInput {
|
|
3287
|
+
method: 'POST' | 'GET';
|
|
3288
|
+
host: string;
|
|
3289
|
+
path: string;
|
|
3290
|
+
appKey: string;
|
|
3291
|
+
appSecret: string;
|
|
3292
|
+
/** JSON payload → sent as the raw body with a Content-MD5. */
|
|
3293
|
+
json?: unknown;
|
|
3294
|
+
/** Form fields → sent as application/x-www-form-urlencoded and folded into the resource. */
|
|
3295
|
+
form?: Record<string, string>;
|
|
3296
|
+
/** Extra x-ca headers to sign (e.g. `{ 'X-Ca-Stage': 'RELEASE' }`). */
|
|
3297
|
+
extraCaHeaders?: Record<string, string>;
|
|
3298
|
+
/** Overrides for deterministic tests. */
|
|
3299
|
+
nonce?: string;
|
|
3300
|
+
timestamp?: number;
|
|
3301
|
+
date?: string;
|
|
3302
|
+
}
|
|
3303
|
+
/** Build the x-ca headers + body for a request. */
|
|
3304
|
+
declare function signApiGatewayRequest(input: SignInput): SignedRequest;
|
|
3305
|
+
|
|
3306
|
+
/** LinkVisual live-stream descriptor returned by /vision/customer/stream/query. */
|
|
3307
|
+
interface StreamInfo {
|
|
3308
|
+
/** Connection strategies the cloud offers, e.g. ["Direct","Relay","NAT"]. */
|
|
3309
|
+
typeList: string[];
|
|
3310
|
+
/** RTMP relay URL. Plain (playable directly) when requested with relayEncrypted:false. */
|
|
3311
|
+
relayUrl: string | null;
|
|
3312
|
+
/** AES key/iv for the relay payload — present (non-empty) only when relayEncrypted:true. */
|
|
3313
|
+
relayDecryptKey: {
|
|
3314
|
+
iv: string;
|
|
3315
|
+
key: string;
|
|
3316
|
+
} | null;
|
|
3317
|
+
/** P2P (Direct/NAT) parameters: STUN/TURN + the WebSocket signaling URL. */
|
|
3318
|
+
p2pInfo: {
|
|
3319
|
+
stunUrl?: string;
|
|
3320
|
+
alterStunUrl?: string;
|
|
3321
|
+
signalUrl?: string;
|
|
3322
|
+
portDetectStunUrlList?: string[];
|
|
3323
|
+
} | null;
|
|
3324
|
+
supportVisionPtz?: boolean;
|
|
3325
|
+
}
|
|
3326
|
+
interface StreamQueryInput {
|
|
3327
|
+
/** Aliyun IoT region id, e.g. `eu-central-1`. */
|
|
3328
|
+
regionId: string;
|
|
3329
|
+
/** Short-lived IoT identity token (from createSessionByAuthCode). */
|
|
3330
|
+
iotToken: string;
|
|
3331
|
+
/** LinkVisual device id. */
|
|
3332
|
+
iotId: string;
|
|
3333
|
+
/**
|
|
3334
|
+
* When false (default) the relay RTMP carries a PLAIN (unencrypted) FLV, so it
|
|
3335
|
+
* can be handed straight to ffmpeg. When true the payload is AES-encrypted and
|
|
3336
|
+
* `relayDecryptKey` is returned.
|
|
3337
|
+
*/
|
|
3338
|
+
relayEncrypted?: boolean;
|
|
3339
|
+
/** 0 = HD main stream (default). */
|
|
3340
|
+
streamType?: number;
|
|
3341
|
+
appKey?: string;
|
|
3342
|
+
appSecret?: string;
|
|
3343
|
+
fetchImpl?: FetchImpl;
|
|
3344
|
+
signal?: AbortSignal;
|
|
3345
|
+
}
|
|
3346
|
+
/**
|
|
3347
|
+
* Query the LinkVisual live stream for a device. Returns the relay RTMP URL and
|
|
3348
|
+
* P2P parameters. Note: each returned relay token is single-use — request a
|
|
3349
|
+
* fresh one per consumer.
|
|
3350
|
+
*/
|
|
3351
|
+
declare function streamQuery(input: StreamQueryInput): Promise<StreamInfo>;
|
|
3352
|
+
|
|
3353
|
+
/** Aliyun OpenAccount session from loginbyoauth. */
|
|
3354
|
+
interface OaSession {
|
|
3355
|
+
sid: string;
|
|
3356
|
+
refreshToken: string;
|
|
3357
|
+
openId: string;
|
|
3358
|
+
}
|
|
3359
|
+
/** Aliyun IoT session (the iotToken used by every LinkVisual call). */
|
|
3360
|
+
interface IotSession {
|
|
3361
|
+
iotToken: string;
|
|
3362
|
+
refreshToken: string;
|
|
3363
|
+
identityId: string;
|
|
3364
|
+
/** Epoch-ms at which iotToken expires. */
|
|
3365
|
+
expiresAt: number;
|
|
3366
|
+
}
|
|
3367
|
+
interface LoginByOauthInput {
|
|
3368
|
+
authCode: string;
|
|
3369
|
+
regionId: string;
|
|
3370
|
+
deviceId?: string;
|
|
3371
|
+
utdid?: string;
|
|
3372
|
+
fetchImpl?: FetchImpl;
|
|
3373
|
+
}
|
|
3374
|
+
/** Exchange the Dreame authCode for an Aliyun OpenAccount session (step 2). */
|
|
3375
|
+
declare function loginByOauth(input: LoginByOauthInput): Promise<OaSession>;
|
|
3376
|
+
interface CreateIotSessionInput {
|
|
3377
|
+
/** The OpenAccount `sid` from {@link loginByOauth}. */
|
|
3378
|
+
sid: string;
|
|
3379
|
+
regionId: string;
|
|
3380
|
+
fetchImpl?: FetchImpl;
|
|
3381
|
+
}
|
|
3382
|
+
/** Exchange the OA `sid` for an Aliyun IoT session / iotToken (step 3). */
|
|
3383
|
+
declare function createIotSession(input: CreateIotSessionInput): Promise<IotSession>;
|
|
3384
|
+
|
|
3385
|
+
/** Aliyun IoT region id per Dreame region. Only `eu-central-1` is verified live. */
|
|
3386
|
+
declare const ALIYUN_REGION_ID: Record<DreameRegion, string>;
|
|
3387
|
+
|
|
3388
|
+
interface DreameVideoSessionInput {
|
|
3389
|
+
/** An authenticated Dreame account session (from `Nodreame`). */
|
|
3390
|
+
session: DreameSession;
|
|
3391
|
+
region: DreameRegion;
|
|
3392
|
+
fetchImpl?: FetchImpl;
|
|
3393
|
+
}
|
|
3394
|
+
/**
|
|
3395
|
+
* High-level video session for a Dreame account. Establishes and caches the
|
|
3396
|
+
* Aliyun IoT identity (iotToken), and mints fresh LinkVisual RTMP stream URLs
|
|
3397
|
+
* on demand. Each returned relay URL is single-use — call {@link getStreamInfo}
|
|
3398
|
+
* (or {@link getRtmpUrl}) once per consumer.
|
|
3399
|
+
*
|
|
3400
|
+
* The plain (unencrypted) relay URL is a standard RTMP stream: hand it straight
|
|
3401
|
+
* to ffmpeg / scrypted / camstack. No local muxing or RTSP server required here.
|
|
3402
|
+
*/
|
|
3403
|
+
declare class DreameVideoSession {
|
|
3404
|
+
#private;
|
|
3405
|
+
constructor(input: DreameVideoSessionInput);
|
|
3406
|
+
/** The resolved Aliyun IoT region id (e.g. `eu-central-1`). */
|
|
3407
|
+
get regionId(): string;
|
|
3408
|
+
/** Mint (or reuse) a valid Aliyun IoT session, refreshing before expiry. */
|
|
3409
|
+
ensureIotSession(): Promise<IotSession>;
|
|
3410
|
+
/**
|
|
3411
|
+
* Mint a fresh LinkVisual stream descriptor for a device. `encrypted:false`
|
|
3412
|
+
* (default) yields a plain RTMP relay URL directly playable by ffmpeg.
|
|
3413
|
+
*
|
|
3414
|
+
* A camera that has been idle reports "device offline" until the query wakes
|
|
3415
|
+
* its video agent via the LinkVisual cloud. This transparently retries on that
|
|
3416
|
+
* (up to `wakeTimeoutMs`, default 30s) so a single call returns a live stream;
|
|
3417
|
+
* pass `wakeTimeoutMs: 0` to fail fast instead.
|
|
3418
|
+
*/
|
|
3419
|
+
getStreamInfo(iotId: string, opts?: {
|
|
3420
|
+
encrypted?: boolean;
|
|
3421
|
+
wakeTimeoutMs?: number;
|
|
3422
|
+
pollMs?: number;
|
|
3423
|
+
}): Promise<StreamInfo>;
|
|
3424
|
+
/**
|
|
3425
|
+
* Convenience: mint a fresh plain RTMP relay URL for a device, or `null` when
|
|
3426
|
+
* the device is not currently streaming (no relay available).
|
|
3427
|
+
*/
|
|
3428
|
+
getRtmpUrl(iotId: string): Promise<string | null>;
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
/**
|
|
3432
|
+
* Parsers for the robot's on-device AI detections, pushed as camera-service
|
|
3433
|
+
* properties (subscribe to `propertyChanged` for SIID 10001):
|
|
3434
|
+
* - piid 110 (`personFollow` / PERSON_DATA): a tracked-person bounding box
|
|
3435
|
+
* - piid 112 (`videoObstacleData`): a list of obstacle boxes
|
|
3436
|
+
*
|
|
3437
|
+
* The overlay for obstacle boxes is gated on `set_properties siid 28 piid 13 = 1`.
|
|
3438
|
+
* Payloads are JSON strings; these parsers are lenient (return `null` on garbage)
|
|
3439
|
+
* and normalize timestamps (the device reports nanoseconds).
|
|
3440
|
+
*/
|
|
3441
|
+
/** A normalized detection box. Coordinates are passed through as-reported. */
|
|
3442
|
+
interface DetectionBox {
|
|
3443
|
+
readonly x: number;
|
|
3444
|
+
readonly y: number;
|
|
3445
|
+
readonly w: number;
|
|
3446
|
+
readonly h: number;
|
|
3447
|
+
/** Optional label/class if the payload carries one. */
|
|
3448
|
+
readonly label?: string;
|
|
3449
|
+
/** Optional confidence if the payload carries one. */
|
|
3450
|
+
readonly score?: number;
|
|
3451
|
+
}
|
|
3452
|
+
interface PersonFollowDetection {
|
|
3453
|
+
/** Detection time in epoch milliseconds (converted from the device's ns). */
|
|
3454
|
+
readonly timestampMs: number;
|
|
3455
|
+
readonly box: DetectionBox | null;
|
|
3456
|
+
}
|
|
3457
|
+
interface ObstacleDetection {
|
|
3458
|
+
readonly timestampMs: number;
|
|
3459
|
+
readonly boxes: readonly DetectionBox[];
|
|
3460
|
+
}
|
|
3461
|
+
/** Parse a `personFollow` (piid 110) payload into a normalized detection. */
|
|
3462
|
+
declare function parsePersonFollow(value: unknown): PersonFollowDetection | null;
|
|
3463
|
+
/** Parse a `videoObstacleData` (piid 112) payload into normalized boxes. */
|
|
3464
|
+
declare function parseObstacleData(value: unknown): ObstacleDetection | null;
|
|
3465
|
+
|
|
3466
|
+
type AmfValue = number | boolean | string | null | undefined | AmfValue[] | {
|
|
3467
|
+
[key: string]: AmfValue;
|
|
3468
|
+
};
|
|
3469
|
+
|
|
3470
|
+
declare const LV_STATUS: {
|
|
3471
|
+
readonly REQUEST_AUDIO_TYPE: "NetStream.RequestAudioType";
|
|
3472
|
+
readonly FORCE_IFRAME_START: "NetStream.ForceIFrame.Start";
|
|
3473
|
+
readonly PING_START: "NetStream.Ping.Start";
|
|
3474
|
+
readonly PING_RESPONSE: "NetStream.Ping.Response";
|
|
3475
|
+
readonly PRE_CONNECT: "NetStream.PreConnect";
|
|
3476
|
+
readonly KEEPALIVE: "NetStream.Keepalive";
|
|
3477
|
+
readonly SERVER_INFO: "NetStream.ServerInfo";
|
|
3478
|
+
readonly TRANSFER_DATA: "NetStream.TransferData";
|
|
3479
|
+
readonly PLAY_START: "NetStream.Play.Start";
|
|
3480
|
+
/** Server signals the talk (intercom uplink) channel is ready. */
|
|
3481
|
+
readonly TALK_READY: "NetStream.TalkReady";
|
|
3482
|
+
};
|
|
3483
|
+
/**
|
|
3484
|
+
* FLV audio tag header byte for the intercom uplink: G.711 A-law, 8 kHz, 16-bit,
|
|
3485
|
+
* mono — `(7<<4)|(0<<2)|(1<<1)|0` = 0x72. Verified against liblvmedia SetTalkInfo.
|
|
3486
|
+
*/
|
|
3487
|
+
declare const TALK_AUDIO_HEADER_G711A = 114;
|
|
3488
|
+
interface StatusEvent {
|
|
3489
|
+
code: string;
|
|
3490
|
+
level: string;
|
|
3491
|
+
description: string;
|
|
3492
|
+
fields: {
|
|
3493
|
+
[key: string]: AmfValue;
|
|
3494
|
+
};
|
|
3495
|
+
transactionId: number;
|
|
3496
|
+
}
|
|
3497
|
+
interface VideoAccessUnitEvent {
|
|
3498
|
+
data: Buffer;
|
|
3499
|
+
isKeyframe: boolean;
|
|
3500
|
+
videoType: 'H264';
|
|
3501
|
+
microseconds: number;
|
|
3502
|
+
time?: number;
|
|
3503
|
+
}
|
|
3504
|
+
interface AudioInfoEvent {
|
|
3505
|
+
codec: 'aac' | 'g711a' | 'g711u' | 'unknown';
|
|
3506
|
+
sampleRate: number;
|
|
3507
|
+
channels: number;
|
|
3508
|
+
}
|
|
3509
|
+
interface LvRtmpClientOptions {
|
|
3510
|
+
/** relayUrl from stream/query, e.g. rtmp://host:8000/live?token=..&session=../<key>_0 */
|
|
3511
|
+
url: string;
|
|
3512
|
+
/** Appended to app/tcUrl as "&cacheDuration=N" when not already present (the app always sends 3000). */
|
|
3513
|
+
cacheDurationMs?: number;
|
|
3514
|
+
/** Send the onStatus NetStream.RequestAudioType {audio_type:"1"} right after play (app does). */
|
|
3515
|
+
requestAudioType?: boolean;
|
|
3516
|
+
/** Send NetStream.Ping.Start after the first server message and then periodically (app does). */
|
|
3517
|
+
ping?: boolean;
|
|
3518
|
+
pingIntervalMs?: number;
|
|
3519
|
+
/** Also send NetStream.ForceIFrame.Start once playing (the app sends it when it starts an MP4 record). */
|
|
3520
|
+
forceIFrameOnStart?: boolean;
|
|
3521
|
+
connectTimeoutMs?: number;
|
|
3522
|
+
log?: (line: string) => void;
|
|
3523
|
+
}
|
|
3524
|
+
interface Endpoint {
|
|
3525
|
+
host: string;
|
|
3526
|
+
port: number;
|
|
3527
|
+
app: string;
|
|
3528
|
+
tcUrl: string;
|
|
3529
|
+
playpath: string;
|
|
3530
|
+
}
|
|
3531
|
+
/**
|
|
3532
|
+
* Split like librtmp/ffmpeg do: playpath = last path segment, app = everything between host and it
|
|
3533
|
+
* (query string included). Verified against the app's connect/play in both pcaps.
|
|
3534
|
+
*/
|
|
3535
|
+
declare function parseRelayUrl(url: string, cacheDurationMs: number): Endpoint;
|
|
3536
|
+
type Events = {
|
|
3537
|
+
connected: [];
|
|
3538
|
+
status: [StatusEvent];
|
|
3539
|
+
command: [string, AmfValue[]];
|
|
3540
|
+
metadata: [{
|
|
3541
|
+
[key: string]: AmfValue;
|
|
3542
|
+
}];
|
|
3543
|
+
audio: [{
|
|
3544
|
+
timestamp: number;
|
|
3545
|
+
body: Buffer;
|
|
3546
|
+
}];
|
|
3547
|
+
video: [{
|
|
3548
|
+
timestamp: number;
|
|
3549
|
+
body: Buffer;
|
|
3550
|
+
}];
|
|
3551
|
+
videoAccessUnit: [VideoAccessUnitEvent];
|
|
3552
|
+
audioFrame: [Buffer];
|
|
3553
|
+
audioInfo: [AudioInfoEvent];
|
|
3554
|
+
error: [Error];
|
|
3555
|
+
close: [];
|
|
3556
|
+
};
|
|
3557
|
+
declare class LvRtmpClient extends EventEmitter<Events> {
|
|
3558
|
+
private readonly opts;
|
|
3559
|
+
private readonly endpoint;
|
|
3560
|
+
private socket;
|
|
3561
|
+
private handshakeBuf;
|
|
3562
|
+
private handshakeDone;
|
|
3563
|
+
private c1Time;
|
|
3564
|
+
private reader;
|
|
3565
|
+
private transactionId;
|
|
3566
|
+
private bytesIn;
|
|
3567
|
+
private bytesAcked;
|
|
3568
|
+
private ackWindow;
|
|
3569
|
+
private pingTimer;
|
|
3570
|
+
private firstMessageSeen;
|
|
3571
|
+
private avcConfig;
|
|
3572
|
+
private aacConfig;
|
|
3573
|
+
private closed;
|
|
3574
|
+
constructor(options: LvRtmpClientOptions);
|
|
3575
|
+
/** Open TCP, write C0+C1 and the pipelined connect/play/RequestAudioType. Resolves once written. */
|
|
3576
|
+
connect(): Promise<void>;
|
|
3577
|
+
close(): void;
|
|
3578
|
+
/** onStatus encoder — byte-exact replica of liblvmedia IOT_RTMP_SendMessage (csid 3, fmt 1, ts 0). */
|
|
3579
|
+
buildStatus(code: string, extra?: readonly [string, string]): Buffer;
|
|
3580
|
+
sendStatus(code: string, extra?: readonly [string, string]): void;
|
|
3581
|
+
forceIFrame(): void;
|
|
3582
|
+
private buildC0C1;
|
|
3583
|
+
private buildConnect;
|
|
3584
|
+
private buildPlay;
|
|
3585
|
+
/** Exposed for the byte-exact self-test against the pcap. */
|
|
3586
|
+
buildOpeningSequence(): Buffer[];
|
|
3587
|
+
/**
|
|
3588
|
+
* Push one intercom (talk-back) audio frame UPSTREAM to the device — an RTMP
|
|
3589
|
+
* type-8 audio message on the talk chunk stream (csid 4, msid 1), exactly as
|
|
3590
|
+
* liblvmedia's `IOT_RTMP_SendAudio` does. `payload` is the raw codec audio
|
|
3591
|
+
* (default framing = G.711 A-law); `tsMs` is a running millisecond timestamp.
|
|
3592
|
+
* Only meaningful after the server sends {@link LV_STATUS.TALK_READY}.
|
|
3593
|
+
*/
|
|
3594
|
+
sendAudioFrame(payload: Buffer, tsMs: number, header?: number): void;
|
|
3595
|
+
private write;
|
|
3596
|
+
private sendAck;
|
|
3597
|
+
private sendPingResponse;
|
|
3598
|
+
private startPinging;
|
|
3599
|
+
private onData;
|
|
3600
|
+
private onHandshake;
|
|
3601
|
+
private dispatch;
|
|
3602
|
+
private onUserControl;
|
|
3603
|
+
private onCommand;
|
|
3604
|
+
private onDataMessage;
|
|
3605
|
+
private onAudio;
|
|
3606
|
+
private onVideo;
|
|
3607
|
+
private fail;
|
|
3608
|
+
private onClose;
|
|
3609
|
+
}
|
|
3610
|
+
|
|
3611
|
+
/**
|
|
3612
|
+
* End-to-end camera media pipeline: autonomously cold-start the LinkVisual
|
|
3613
|
+
* monitor, connect to the resulting RTMP relay, and re-emit demuxed elementary
|
|
3614
|
+
* frames — H.264 access units (Annex-B, SPS/PPS prepended on keyframes) and AAC
|
|
3615
|
+
* (ADTS) — plus lifecycle events.
|
|
3616
|
+
*
|
|
3617
|
+
* This is the reusable frame feed for any external consumer (scrypted's RFC4571
|
|
3618
|
+
* muxer, camstack, a file writer). It owns the {@link DreameCameraController}
|
|
3619
|
+
* lifecycle: {@link start} opens the monitor and connects; {@link stop} closes
|
|
3620
|
+
* the relay socket and releases the monitor on the device.
|
|
3621
|
+
*/
|
|
3622
|
+
|
|
3623
|
+
/** The subset of {@link LvRtmpClient} the pipeline drives — injectable for tests. */
|
|
3624
|
+
interface FrameSource {
|
|
3625
|
+
connect(): Promise<void>;
|
|
3626
|
+
close(): void;
|
|
3627
|
+
on(event: 'videoAccessUnit', cb: (e: VideoAccessUnitEvent) => void): void;
|
|
3628
|
+
on(event: 'audioFrame', cb: (buf: Buffer) => void): void;
|
|
3629
|
+
on(event: 'audioInfo', cb: (info: AudioInfoEvent) => void): void;
|
|
3630
|
+
on(event: 'status', cb: (e: StatusEvent) => void): void;
|
|
3631
|
+
on(event: 'error', cb: (err: Error) => void): void;
|
|
3632
|
+
on(event: 'close', cb: () => void): void;
|
|
3633
|
+
/** Push an intercom (talk-back) audio frame upstream. */
|
|
3634
|
+
sendAudioFrame(payload: Buffer, tsMs: number, header?: number): void;
|
|
3635
|
+
}
|
|
3636
|
+
/** Factory for the RTMP frame source; defaults to a real {@link LvRtmpClient}. */
|
|
3637
|
+
type FrameSourceFactory = (url: string) => FrameSource;
|
|
3638
|
+
interface DreameCameraStreamEvents {
|
|
3639
|
+
/** Emitted once the relay is connected; carries the (single-use) RTMP URL. */
|
|
3640
|
+
started: [{
|
|
3641
|
+
rtmpUrl: string;
|
|
3642
|
+
}];
|
|
3643
|
+
/** One H.264 access unit (Annex-B). */
|
|
3644
|
+
videoAccessUnit: [VideoAccessUnitEvent];
|
|
3645
|
+
/** One audio frame (AAC in ADTS, or raw G.711). */
|
|
3646
|
+
audioFrame: [Buffer];
|
|
3647
|
+
/** Audio format, emitted once known. */
|
|
3648
|
+
audioInfo: [AudioInfoEvent];
|
|
3649
|
+
/** Emitted after the pipeline is fully torn down. */
|
|
3650
|
+
stopped: [];
|
|
3651
|
+
error: [Error];
|
|
3652
|
+
[event: string]: unknown[];
|
|
3653
|
+
}
|
|
3654
|
+
interface DreameCameraStreamInput {
|
|
3655
|
+
/** A controller for the target camera (not yet opened). */
|
|
3656
|
+
controller: DreameCameraController;
|
|
3657
|
+
/** Override the RTMP client (tests inject a fake). */
|
|
3658
|
+
clientFactory?: FrameSourceFactory;
|
|
3659
|
+
/** Forwarded to {@link LvRtmpClient}: send NetStream.RequestAudioType after play. */
|
|
3660
|
+
requestAudioType?: boolean;
|
|
3661
|
+
/** Forwarded to {@link LvRtmpClient}: send periodic NetStream.Ping. */
|
|
3662
|
+
ping?: boolean;
|
|
3663
|
+
/** Optional line logger for the RTMP client. */
|
|
3664
|
+
log?: (line: string) => void;
|
|
3665
|
+
}
|
|
3666
|
+
declare class DreameCameraStream extends TypedEmitter<DreameCameraStreamEvents> {
|
|
3667
|
+
#private;
|
|
3668
|
+
constructor(input: DreameCameraStreamInput);
|
|
3669
|
+
/** True while frames are flowing. */
|
|
3670
|
+
get isRunning(): boolean;
|
|
3671
|
+
/**
|
|
3672
|
+
* Cold-start the monitor, connect to the relay, and begin emitting frames.
|
|
3673
|
+
* Resolves once connected. Throws (and cleans up) if the monitor or relay
|
|
3674
|
+
* connection fails.
|
|
3675
|
+
*/
|
|
3676
|
+
start(): Promise<{
|
|
3677
|
+
rtmpUrl: string;
|
|
3678
|
+
}>;
|
|
3679
|
+
/** Close the relay connection and release the monitor. Idempotent. */
|
|
3680
|
+
stop(): Promise<void>;
|
|
3681
|
+
/**
|
|
3682
|
+
* Open the two-way intercom: send the MIoT start, then resolve once the relay
|
|
3683
|
+
* signals the talk channel is ready (so it's safe to {@link sendTalkPcm}).
|
|
3684
|
+
* Rejects if not ready within `timeoutMs`.
|
|
3685
|
+
*/
|
|
3686
|
+
startTalk(opts?: {
|
|
3687
|
+
needRecordSound?: boolean;
|
|
3688
|
+
videoCall?: boolean;
|
|
3689
|
+
timeoutMs?: number;
|
|
3690
|
+
}): Promise<void>;
|
|
3691
|
+
/** Resolve when the talk channel is ready (or reject on timeout). */
|
|
3692
|
+
whenTalkReady(timeoutMs?: number): Promise<void>;
|
|
3693
|
+
/**
|
|
3694
|
+
* Encode 16-bit LE mono PCM (8 kHz) to G.711 A-law and push it upstream.
|
|
3695
|
+
* `tsMs` defaults to a running clock advanced by the frame duration.
|
|
3696
|
+
*/
|
|
3697
|
+
sendTalkPcm(pcm16le: Buffer, tsMs?: number): void;
|
|
3698
|
+
/** Push raw A-law audio upstream (advancing the running talk clock). */
|
|
3699
|
+
sendTalkAlaw(alaw: Buffer, tsMs?: number): void;
|
|
3700
|
+
/** Close the two-way intercom (MIoT stop). */
|
|
3701
|
+
stopTalk(): Promise<void>;
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
/**
|
|
3705
|
+
* G.711 A-law codec (ITU-T G.711) — the intercom uplink format for Dreame
|
|
3706
|
+
* cameras (8 kHz, mono, 16-bit PCM in). Pure functions, no state.
|
|
3707
|
+
*/
|
|
3708
|
+
/** Encode one 16-bit signed PCM sample to an A-law byte. */
|
|
3709
|
+
declare function pcm16ToALaw(sample: number): number;
|
|
3710
|
+
/**
|
|
3711
|
+
* Encode a buffer of little-endian 16-bit PCM samples to A-law bytes
|
|
3712
|
+
* (one output byte per input sample).
|
|
3713
|
+
*/
|
|
3714
|
+
declare function pcm16leToALaw(pcm: Buffer): Buffer;
|
|
3715
|
+
|
|
3716
|
+
interface AvcConfig {
|
|
3717
|
+
sps: Buffer[];
|
|
3718
|
+
pps: Buffer[];
|
|
3719
|
+
nalLengthSize: number;
|
|
3720
|
+
}
|
|
3721
|
+
interface AacConfig {
|
|
3722
|
+
objectType: number;
|
|
3723
|
+
samplingFrequencyIndex: number;
|
|
3724
|
+
channelConfiguration: number;
|
|
3725
|
+
}
|
|
3726
|
+
/** Parse an AVCDecoderConfigurationRecord (FLV AVC sequence header payload). */
|
|
3727
|
+
declare function parseAvcConfig(rec: Buffer): AvcConfig;
|
|
3728
|
+
/** Length-prefixed NALUs -> Annex-B. Prepends SPS/PPS when `prependParams` is set (for keyframes). */
|
|
3729
|
+
declare function avccToAnnexB(data: Buffer, cfg: AvcConfig, prependParams: boolean): Buffer;
|
|
3730
|
+
declare function parseAacConfig(asc: Buffer): AacConfig;
|
|
3731
|
+
declare function aacSampleRate(cfg: AacConfig): number;
|
|
3732
|
+
/** Wrap one raw AAC frame in a 7-byte ADTS header. */
|
|
3733
|
+
declare function aacToAdts(raw: Buffer, cfg: AacConfig): Buffer;
|
|
3734
|
+
|
|
3735
|
+
interface ListDevicesInput {
|
|
3736
|
+
session: DreameSession;
|
|
3737
|
+
region: DreameRegion;
|
|
3738
|
+
ctx?: RequestContext;
|
|
3739
|
+
country?: string;
|
|
3740
|
+
lang?: string;
|
|
3741
|
+
apiHost?: string;
|
|
3742
|
+
fetchImpl?: FetchImpl;
|
|
3743
|
+
signal?: AbortSignal;
|
|
3744
|
+
timeoutMs?: number;
|
|
3745
|
+
}
|
|
3746
|
+
/** Enumerate the devices visible to the authenticated account (incl. shared). */
|
|
3747
|
+
declare function listDevices(input: ListDevicesInput): Promise<DreameDevice[]>;
|
|
3748
|
+
|
|
3749
|
+
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, explainNoCameraChannel, 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 };
|