@apocaliss92/nodedreame 1.11.11 → 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 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
- Phase 4 complete: on top of the Phase 2 generic handle, `discoverDevices()` now
10
- returns a typed `VacuumDevice` for `dreame.vacuum.*` models and a typed
11
- `MowerDevice` for `dreame.mower.*` models, each with decoded state getters and
12
- capability-gated commands. Live map / pose-coverage track decoding (Phase 5) is
13
- not implemented yet.
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