@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/README.md
CHANGED
|
@@ -6,11 +6,19 @@ Node.js/TypeScript client for Dreame robot vacuums and mowers via the Dreamehome
|
|
|
6
6
|
|
|
7
7
|
## Status
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
`discoverDevices()` returns a typed `VacuumDevice` for `dreame.vacuum.*` models
|
|
10
|
+
and a typed `MowerDevice` for `dreame.mower.*` models, each with decoded state
|
|
11
|
+
getters and capability-gated commands. On top of that:
|
|
12
|
+
|
|
13
|
+
- **Maps** — live vacuum map decode (I/P-frame stream) + PNG render, mower map + SVG.
|
|
14
|
+
- **Connectivity** — per-device online/link-type/broker/firmware/serial view
|
|
15
|
+
([Connectivity](#connectivity)).
|
|
16
|
+
- **WiFi signal** — the robot's WiFi coverage heatmap, sampled at the robot's
|
|
17
|
+
pose for a current-signal reading ([WiFi signal](#wifi-signal)).
|
|
18
|
+
- **Camera & video** (X40/X50 class) — autonomous cold-start of the LinkVisual
|
|
19
|
+
RTMP stream with H.264+AAC frame output, camera actions (drive/presets/sounds/
|
|
20
|
+
light), object detections, and two-way intercom
|
|
21
|
+
([Camera & video streaming](#camera--video-streaming)).
|
|
14
22
|
|
|
15
23
|
Under the hood:
|
|
16
24
|
|
|
@@ -288,6 +296,144 @@ parser and SVG renderer from
|
|
|
288
296
|
[antondaubert/dreame-mower](https://github.com/antondaubert/dreame-mower). Both
|
|
289
297
|
are MIT — see `LICENSE`.
|
|
290
298
|
|
|
299
|
+
## Connectivity
|
|
300
|
+
|
|
301
|
+
Every `DreameDevice` from `listDevices()` (and the records behind
|
|
302
|
+
`discoverDevices()`) carries a distilled connectivity view — everything the
|
|
303
|
+
Dreame cloud actually reports:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { listDevices } from '@apocaliss92/nodedreame';
|
|
307
|
+
|
|
308
|
+
for (const d of await listDevices({ session, region: 'eu' })) {
|
|
309
|
+
console.log(d.name, d.connectivity);
|
|
310
|
+
// {
|
|
311
|
+
// online: true,
|
|
312
|
+
// connectionType: 'WIFI', // or 'BLE' (mowers)
|
|
313
|
+
// mac, broker: { host, port }, // assigned MQTT broker (bindDomain)
|
|
314
|
+
// region, cloudVendor, firmwareVersion, serialNumber, subModel,
|
|
315
|
+
// battery, statusCode
|
|
316
|
+
// }
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`parseConnectivity(rawRecord)` is exported for parsing a record you already hold.
|
|
321
|
+
Note: the cloud record does **not** expose Wi-Fi RSSI / SSID / local IP — those
|
|
322
|
+
are native-firmware only. For signal strength use the WiFi map below.
|
|
323
|
+
|
|
324
|
+
## WiFi signal
|
|
325
|
+
|
|
326
|
+
The robot has no live RSSI property; its only signal data is a **WiFi coverage
|
|
327
|
+
map** it builds while cleaning. nodedreame fetches the **last stored** map (no
|
|
328
|
+
robot movement — the same thing the app shows), decodes the per-cell signal
|
|
329
|
+
level, and — since the map header carries the robot pose — reports the signal at
|
|
330
|
+
the robot's current position.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
const bars = await vacuum.getCurrentSignal(); // 0–4 (0 = unreached), or null
|
|
334
|
+
const png = await vacuum.getWifiSignalImage(); // Buffer: heatmap PNG (ready to show)
|
|
335
|
+
const map = await vacuum.getWifiSignalMap(); // full data:
|
|
336
|
+
// map.dimensions {left,top,width,height,gridSize}
|
|
337
|
+
// map.robot / map.dock (poses, mm world frame)
|
|
338
|
+
// map.cells (Uint8Array, raw nibble per cell)
|
|
339
|
+
// map.signalAt(x, y) -> bars 1–4 / 0 unreached / null (no data / off-map)
|
|
340
|
+
// map.currentSignal -> bars at the robot pose
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Each call issues `requestWMap` (map service action `6/4`) to (re)fetch the last
|
|
344
|
+
stored map, polls `PropWifiMap` (`6/15`) for the OSS object, and decodes it via
|
|
345
|
+
the normal signed-OSS map pipeline. Signal levels: `10` unreached, `11`–`14` =
|
|
346
|
+
1–4 bars. Throws if the device has never built a WiFi map.
|
|
347
|
+
|
|
348
|
+
## Camera & video streaming
|
|
349
|
+
|
|
350
|
+
For camera-equipped vacuums (X40/X50 class, LinkVisual/Aliyun backend),
|
|
351
|
+
nodedreame **autonomously cold-starts the live stream** — no Dreamehome app — and
|
|
352
|
+
emits demuxed H.264 + AAC frames any consumer (ffmpeg, scrypted, camstack) can
|
|
353
|
+
use.
|
|
354
|
+
|
|
355
|
+
### How it works (high level)
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
DreameCameraController.open() DreameCameraStream consumer
|
|
359
|
+
───────────────────────────── ────────────────── ────────
|
|
360
|
+
1. getAccessCodeLaunch (prime) ┐
|
|
361
|
+
2. initCameraSdk (boot agent) │ MIoT actions on siid 10001
|
|
362
|
+
3. startMonitor ── code -1 ─────┤ over the device's MQTT channel
|
|
363
|
+
4. verifyAccessCode(sha256 PIN) │ (the camera has a per-session
|
|
364
|
+
5. startMonitor (retry) ── ok ───┘ privacy gate: PIN is mandatory)
|
|
365
|
+
6. keep-alive loop (~10s) │
|
|
366
|
+
7. stream/query (Aliyun) → RTMP relay URL ───┤
|
|
367
|
+
▼
|
|
368
|
+
LvRtmpClient connects to the relay
|
|
369
|
+
(private-mode RTMP), demuxes FLV →
|
|
370
|
+
emits: videoAccessUnit (H.264 Annex-B),
|
|
371
|
+
audioFrame (AAC/ADTS), audioInfo
|
|
372
|
+
│
|
|
373
|
+
▼ (scrypted wraps these as RFC4571;
|
|
374
|
+
camstack consumes frames directly)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
The **access-code gate** is the key device quirk: a bare `startMonitor` returns
|
|
378
|
+
`code:-1`; you must first `verifyAccessCode` with the SHA-256 of the pairing PIN,
|
|
379
|
+
then `startMonitor` is accepted and the Aliyun relay mints an RTMP URL. The whole
|
|
380
|
+
sequence, keep-alive, and teardown are handled by `DreameCameraController`.
|
|
381
|
+
|
|
382
|
+
### Quick start
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
const controller = await vacuum.createCameraController({ accessCode: '0000' });
|
|
386
|
+
const { rtmpUrl } = await controller.open(); // cold-start → live relay URL (H.264+AAC)
|
|
387
|
+
// … hand rtmpUrl to ffmpeg, or use DreameCameraStream for frames …
|
|
388
|
+
await controller.close(); // stops keep-alive + releases the monitor
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Frame-level pipeline (what camstack/scrypted build on):
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
import { DreameCameraStream } from '@apocaliss92/nodedreame';
|
|
395
|
+
|
|
396
|
+
const stream = new DreameCameraStream({ controller });
|
|
397
|
+
stream.on('videoAccessUnit', (au) => { /* au.data = H.264 Annex-B, au.isKeyframe */ });
|
|
398
|
+
stream.on('audioFrame', (buf) => { /* AAC ADTS */ });
|
|
399
|
+
await stream.start(); // cold-start + connect + emit frames
|
|
400
|
+
// … later …
|
|
401
|
+
await stream.stop();
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Camera actions (control plane)
|
|
405
|
+
|
|
406
|
+
All on `DreameCameraController`, most gated to an open stream:
|
|
407
|
+
|
|
408
|
+
| Method | Effect |
|
|
409
|
+
| --- | --- |
|
|
410
|
+
| `driveDirection('forward'\|'left'\|'right'\|'turnAround'\|'stop')` / `drive(spdv, spdw)` | Remote-drive the robot (send ~1 Hz while held; the camera is fixed, the robot moves) |
|
|
411
|
+
| `returnToDock()` · `locate()` | Send home / beep to locate |
|
|
412
|
+
| `spotClean()` · `startPersonFollow()` / `stopPersonFollow()` · `findPet()` · `goToPoint(sp, tp)` | Work-mode actions |
|
|
413
|
+
| `stopWork()` | Universal stop (follow / spot-clean / cruise) |
|
|
414
|
+
| `playPetSound('meow'\|'bark'\|'footsteps'\|'purring'\|'tickTock')` / `playSound(id)` | Play a sound clip |
|
|
415
|
+
| `setFillLight(level)` / `setFillLightAuto(full)` | Fill-light brightness / full-light on-off |
|
|
416
|
+
| `takePhoto()` | Device-side snapshot (uploads to cloud) |
|
|
417
|
+
| `startIntercom()` / `stopIntercom()` | Two-way audio session (control) |
|
|
418
|
+
| `runVacuumAction('startClean'\|'pauseClean'\|'stopClean'\|'dockWash'\|'autoEmpty')` | Common whole-robot actions |
|
|
419
|
+
|
|
420
|
+
### Two-way intercom (talk-back)
|
|
421
|
+
|
|
422
|
+
The mic uplink is RTMP type-8 audio pushed upstream on the same play session
|
|
423
|
+
(G.711 A-law, 8 kHz). On the stream:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
await stream.startTalk(); // MIoT intercom start + wait for TalkReady
|
|
427
|
+
stream.sendTalkPcm(pcm16le8kMono); // encodes to A-law + pushes upstream
|
|
428
|
+
await stream.stopTalk();
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Object detections
|
|
432
|
+
|
|
433
|
+
The robot's own person-follow (`10001/110`) and obstacle (`10001/112`) boxes are
|
|
434
|
+
pushed as camera-service properties; parse them with `parsePersonFollow` /
|
|
435
|
+
`parseObstacleData` (subscribe to the device's `propertyChanged`).
|
|
436
|
+
|
|
291
437
|
## Diagnostic dump (read-only)
|
|
292
438
|
|
|
293
439
|
`nodedreame` can record what a device exposes while it operates and export an
|