点心
用于Dreame机器人真空和割草机的Node.js/TypeScript客户端通过Dreamehome云.
工作正在进行中。 统一,事件驱动库——不是UI.
状态
discoverDevices() 返回键入 VacuumDevice 页:1 dreame.vacuum.* 模型
键入 MowerDevice 页:1 dreame.mower.* 模型,每个模型都有解码状态
获取器和能力命令。 除此之外:
- 地图 ——活式真空图解码(I/P-帧流)+PNG渲染,割草机图+SVG.
- 连通性 ——每台设备在线/链接类型/经纪人/公司软件/系列视图 (单位:千美元)连通性) (中文(简体) ).
- WiFi 信号 ——机器人的WiFi覆盖热映射,在机器人那里取样 用于当前符号读取的姿势(WiFi 信号) (中文(简体) ).
- 相机视频( V) (X40/X50级)——自主冷起动"连通视觉" RTMP流与 H.264+AAC 帧输出,相机动作(驱动/预设/声调/) 和双向对讲机 (单位:千美元)相机和视频流) (中文(简体) ).
盖下:
- OAuth 密码赠出登录, 并主动更新符号( 共享会话) 过期前刷新~100s,每个设备的 MQTT 推为 以透明的方式重新认证新标志).
- 每台设备 MQTT 推取 持久重联 - 它与备份重接
在意外的下降中重建连接
刷新点 A 类
get_properties民意调查倒计时只在推倒时进行 一旦重新连接就停止. - 所有云和 MQTT 回复均经过验证
zod在边界,所以 错误的响应以明显的错误快速失败, 而不是传播undefined。 。 。 .
使用量
import { Nodreame } from '@apocaliss92/nodedreame';
const client = new Nodreame({
username: process.env.DREAME_USERNAME!,
password: process.env.DREAME_PASSWORD!,
region: 'eu',
});
await client.login();
const devices = await client.discoverDevices();
for (const device of devices) {
console.log(device.deviceId, device.model, device.name);
// Live-read a couple of MIoT properties (siid.piid are model-specific).
await device.refreshProperties([{ siid: 2, piid: 1 }]);
console.log('state:', device.getProperty(2, 1)?.value);
// React to pushed updates.
device.on('stateChanged', (e) => console.log('changed', e.changes));
}
// The shared session auto-refreshes ~100s before expiry; every device's MQTT
// push is re-authenticated with the new token transparently.
await client.close(); // closes all pushes, clears all timers
每个手柄上都有通用的MIOT原始相:
refreshProperties编号 :getProperty(缓存) /setProperty编号 :callAction。 。 。 . 摩尔startMowing/附表)处理和活地地图后期到达.
真空
对于 dreame.vacuum.* 型号, discoverDevices() 返回 a VacuumDevice 联合国
用于添加打入、解码状态获取器和
在原始的MIOT原始的上方有能力命令.
import { Nodreame, VacuumDevice, SuctionLevel } from '@apocaliss92/nodedreame';
const client = new Nodreame({
username: process.env.DREAME_USERNAME!,
password: process.env.DREAME_PASSWORD!,
region: 'eu',
});
await client.login();
const devices = await client.discoverDevices();
const vacuum = devices.find((d): d is VacuumDevice => d instanceof VacuumDevice);
if (vacuum) {
// Seed the cache with the vacuum's known properties (one live read).
await vacuum.refreshProperties([...VacuumDevice.DEFAULT_PROPS]);
// Typed, decoded state. Each getter returns null until the matching
// property has landed (via the seed read above or a pushed update).
console.log('status:', vacuum.status); // MiotState | null
console.log('battery:', vacuum.battery); // number | null (%)
console.log('suction:', vacuum.suction); // SuctionLevel | null
console.log('water:', vacuum.water); // WaterVolume | null
console.log('docked:', vacuum.isDocked);
console.log('faults:', vacuum.faults); // number[]
// Capability-gated commands (all async).
await vacuum.setSuction(SuctionLevel.Max);
await vacuum.startCleaning(); // begins a clean (NOT the lifecycle start())
// await vacuum.pause();
// await vacuum.stop();
// await vacuum.dock(); // return to dock / charge
// await vacuum.locate(); // make the robot beep
// await vacuum.cleanSegments([1, 2]); // per-room, gated by canCleanPerRoom
}
await client.close();
startCleaning()没有start()。 。 。 .start()是继承的生命周期 打开 MQTT 推力的方法; 清除命令是startCleaning()。 。 。 . 所有指挥方法都是async。 。 。 .
诚实的警告
- 返回状态获取器
null直到数据着陆. 打电话refreshProperties([...VacuumDevice.DEFAULT_PROPS])(或等被推倒) 在读取前更新; 未种子获取器是null,以及一个外程生 值还解码为null(原始整数仍可通过*Raw获得者,例如:.vacuum.suctionRaw) (中文(简体) ). r2538z能力是假定的,没有经过核实. 用户的dreame.vacuum.r2538z能力记录从其r2532a(X50) (中文(简体) ) 师兄,所以vacuum.vacuumCapabilities.verified === false。处理其特点 旗帜是最佳假说,直到确认在设备上.- 清末写作通过建筑安全.
setCleaningMode()写入 简单CLEAN_MODE_SETTING属性(siid 2 piid 6); 未加工0x1400- 装模作样 bitfield(siid 4 piid 23)在此仅读取,从未直接写入. - 一些动作映射是假设的.
pause编号 :stop编号 :locate编号 :clearWarning在r2532a兄弟身上有线核查;startCleaning编号 :dock编辑 目标清洁有效载荷cleanSegments编号 :cleanZones编号 :cleanSpot) 已移植 从Tashack开始 还没有对所有模型进行现场验证. - 还没有地图生成状态 . 每个房间/当前区域的数据和现场地图都来了 从映射层(第五阶段);它们不在第三阶段暴露.
汽车
对于 dreame.mower.* 型号, discoverDevices() 返回 a MowerDevice 联合国
普通手柄的子类),带有已打入、已解码的状态获取器和
能力命令,镜像真空表面.
import { Nodreame, MowerDevice } from '@apocaliss92/nodedreame';
const client = new Nodreame({
username: process.env.DREAME_USERNAME!,
password: process.env.DREAME_PASSWORD!,
region: 'eu',
});
await client.login();
const devices = await client.discoverDevices();
const mower = devices.find((d): d is MowerDevice => d instanceof MowerDevice);
if (mower) {
// Seed the cache with the mower's known properties (one live read).
await mower.refreshProperties([...MowerDevice.DEFAULT_PROPS]);
// Typed, decoded state. Each getter returns null until the matching
// property has landed (via the seed read above or a pushed update).
console.log('status:', mower.status); // MowerStatus | null
console.log('battery:', mower.battery); // number | null (%)
console.log('charging:', mower.charging); // MowerChargingStatus | null
console.log('docked:', mower.isDocked); // boolean
console.log('mowing:', mower.isMowing); // boolean
console.log('task:', mower.task); // MowerTaskDescriptor | null (2:50)
console.log('coverage target %:', mower.coverageTargetPct); // number | null
console.log('control action:', mower.controlAction); // MowerControlAction | null
// Capability-gated commands (all async).
await mower.startMowing(); // begins mowing (NOT the lifecycle start())
// await mower.pause();
// await mower.stop();
// await mower.dock(); // return to dock / charge
// await mower.resume(); // resume after a pause (continueControl opcode)
// Targeted starts (gated by the model's capability flags):
// await mower.startMowingAllArea(mapId); // whole map
// await mower.startMowingZones([1, 3]); // selected zones
// await mower.startMowingEdges([[1, 0]]); // edge / contour pairs
// await mower.startMowingSpots([5]); // spot areas
}
await client.close();
startMowing()没有start()。 。 。 .start()是继承的生命周期 打开 MQTT 推力的方法; 剪切命令是startMowing()全部数据 命令方法为async。 。 。 .
诚实的警告
- 返回状态获取器
null直到数据着陆. 打电话refreshProperties([...MowerDevice.DEFAULT_PROPS])(或等被推倒) 在读取前更新; 未种子获取器是null,以及一个外程生 值还解码为null(原始整数仍可通过*Raw获得者,例如:.mower.statusRaw, (中文(简体) ),mower.chargingRaw, (中文(简体) ),mower.taskStatusRaw) (中文(简体) ). dreame.mower.p2255(Dreame A1)能力是假设的,没有核实. 捐助方的一体化没有每个模型的割草机能力矩阵,因此 从指挥表面看,有目标地升起旗帜是一种保守的假设;mower.mowerCapabilities.verified === false直到确认在设备上. 不支持的目标开始抛出DreameError。 。 。 .- 进步是覆盖的尺度,而不是地图轨道.
coverageTargetPct显示列表任务描述符的覆盖目标( q)d.o, 2:50) 字节精确的姿势/覆盖轨道几何和活图来自地图 在此不解码.
错误类别和核心域类型也被导出,以便消费者能够捕捉 和类型云失败 :
import {
DreameError,
DreameAuthError,
DreameApiError,
DreameDeviceOfflineError,
DreameTransportError,
} from '@apocaliss92/nodedreame';
import type { DreameSession, DreameDevice, MiotProp } from '@apocaliss92/nodedreame';
地图
两个设备家庭都解码了他们的安装设备地图,并将其制作成图像。 这个 二进制/JSON解码器、开放源码软件签名接取器和每个中间步骤 保持私人状态——通过设备手柄和(可选的) 两个出炉器.
真空图-巴布亚新几内亚
VacuumDevice.getMap() 解决已保存/ 启用的地图块、 解密和充气
二进制信封,解析 27 字节标题和 fsm:1 像素网格,以及
返回结构 VacuumMap ——分会/会所,清洁道,AI
屏障、虚拟墙、禁行区/禁行区、潜行区、每室墙和
清理区域覆盖. renderVacuumPng(map) 把它给巴布亚新几内亚人
Buffer 通过 pngjs。 。 。 .
import { renderVacuumPng } from '@apocaliss92/nodedreame';
import type { VacuumMap } from '@apocaliss92/nodedreame';
import { writeFile } from 'node:fs/promises';
// `filename` is the OSS object name advertised on the map PATH push (siid 6,
// piid 3); resolve it from a `mapInfo` push before calling getMap().
const map: VacuumMap = await vacuum.getMap({ filename });
const png = renderVacuumPng(map); // Buffer (optional { scale } upscale)
await writeFile('map.png', png);
// The most-recently-decoded map is cached, and the active room id is derived:
vacuum.lastMap; // VacuumMap | null
vacuum.currentSegmentId; // number | null (id of the active segment)
活地地图数据需要一个醒地机器人. 沉睡的真空无所新鲜 鲍勃.
getMap()解码单一框架; 连续活框 P-框架 流线 (随着机器人的移动而合并三角框)是一个文件 后续行动——applyVacuumPFrame合并原始船只,并经过单位测试, 这样的工作是添加剂,而不是重写.
摩尔地图 → SVG
MowerDevice.getMap() 重新组合批次 MAP.* 编号 : M_PATH.* JSON块
把他们描述成 MowerMap 禁区、禁区、禁区,
导航路径、轮廓、割草路径和地图边界.
mower.mapSvg() (或免费的) renderMowerSvg(map))使SVG具有确定性
取出几何的字符串.
import { renderMowerSvg } from '@apocaliss92/nodedreame';
import type { MowerMap } from '@apocaliss92/nodedreame';
import { writeFile } from 'node:fs/promises';
const map: MowerMap = await mower.getMap();
const svg = await mower.mapSvg(); // or renderMowerSvg(map)
await writeFile('map.svg', svg);
同样的清醒机器人警告. 没有新鲜货. 此外,混凝土 有记录的活批量云端点 后续行动:其道路在捐助方一体化中模糊不清,尚未 已恢复,所以默认的拾取者将抛出
getMap()然后,他们否认真理。 地图 剖析器和 SVG 渲染器已完全运出并测试单元 对批次 固定装置,以及MowerDevice接受注入的批量取取取器接合,所以 已经知道路径的呼叫者可以驱动今天的活地图.
归属
真空映射解码器从
马拉德/节点-梦想; 割草机地图
解析器和 SVG 渲染器
antondaubert/冷冻机两者
麻省理工学院——见 LICENSE。 。 。 .
连通性
每个 DreameDevice 从 listDevices() (和后面的记录)
discoverDevices())搭载了蒸馏接通视图——所有
梦云实际报:
import { listDevices } from '@apocaliss92/nodedreame';
for (const d of await listDevices({ session, region: 'eu' })) {
console.log(d.name, d.connectivity);
// {
// online: true,
// connectionType: 'WIFI', // or 'BLE' (mowers)
// mac, broker: { host, port }, // assigned MQTT broker (bindDomain)
// region, cloudVendor, firmwareVersion, serialNumber, subModel,
// battery, statusCode
// }
}
parseConnectivity(rawRecord) 用于解析您已经持有的记录.
注:云录有 没有 暴露无线甚高频/ SSID/ 本地IP—— 这些
仅是本地公司的软件。 对于信号强度,使用下面的WiFi地图.
WiFi 信号
机器人没有RSSI的活性属性;其唯一的信号数据是a WiFi 覆盖 地图 它在清洁时会建起 点头取出 上次存储 映射( 无) 机器人运动——应用程序显示的同样内容),解码每个细胞的信号 级别,并且——由于地图头带有机器人的姿势——报告信号在 机器人现在的位置.
const bars = await vacuum.getCurrentSignal(); // 0–4 (0 = unreached), or null
const png = await vacuum.getWifiSignalImage(); // Buffer: heatmap PNG (ready to show)
const map = await vacuum.getWifiSignalMap(); // full data:
// map.dimensions {left,top,width,height,gridSize}
// map.robot / map.dock (poses, mm world frame)
// map.cells (Uint8Array, raw nibble per cell)
// map.signalAt(x, y) -> bars 1–4 / 0 unreached / null (no data / off-map)
// map.currentSignal -> bars at the robot pose
每个呼叫问题 requestWMap (地图服务行动) 6/4以( 重) 缩写上
储存的地图、民意测验 PropWifiMap (单位:千美元)6/15用于 OSS 对象,并通过
正常的OSS地图管道。 信号级别 : 10 未触及, 11– 说吧 –14 =:
1–4条栏. 如果设备从未建立 WiFi 映射, 则丢弃 .
相机和视频流
相机真空(X40/X50级,Link Visual/Aliyun后端), 点心 自动冷启动活流 没有Dreamehome应用软件,以及 任何消费者(ffmpeg、scripted、camstack)都可以使用H.264 + AAC框架 使用时.
如何运作(高级)
DreameCameraController.open() DreameCameraStream consumer
───────────────────────────── ────────────────── ────────
1. getAccessCodeLaunch (prime) ┐
2. initCameraSdk (boot agent) │ MIoT actions on siid 10001
3. startMonitor ── code -1 ─────┤ over the device's MQTT channel
4. verifyAccessCode(sha256 PIN) │ (the camera has a per-session
5. startMonitor (retry) ── ok ───┘ privacy gate: PIN is mandatory)
6. keep-alive loop (~10s) │
7. stream/query (Aliyun) → RTMP relay URL ───┤
▼
LvRtmpClient connects to the relay
(private-mode RTMP), demuxes FLV →
emits: videoAccessUnit (H.264 Annex-B),
audioFrame (AAC/ADTS), audioInfo
│
▼ (scrypted wraps these as RFC4571;
camstack consumes frames directly)
这个 访问码门 是关键设备突变:一空 startMonitor 返回时
code:-1; 您必须首先 verifyAccessCode 与配对PIN的SHA-256,
接下来 startMonitor 被接受,阿里云中继薄荷则使用RTMP URL。 整个
顺序,保持活性, 和拆解由 DreameCameraController。 。 。 .
快开始
const controller = await vacuum.createCameraController({ accessCode: '0000' });
const { rtmpUrl } = await controller.open(); // cold-start → live relay URL (H.264+AAC)
// … hand rtmpUrl to ffmpeg, or use DreameCameraStream for frames …
await controller.close(); // stops keep-alive + releases the monitor
框架级管线(凸起/被撕裂所依托):
import { DreameCameraStream } from '@apocaliss92/nodedreame';
const stream = new DreameCameraStream({ controller });
stream.on('videoAccessUnit', (au) => { /* au.data = H.264 Annex-B, au.isKeyframe */ });
stream.on('audioFrame', (buf) => { /* AAC ADTS */ });
await stream.start(); // cold-start + connect + emit frames
// … later …
await stream.stop();
相机动作(控制平面)
全部开始 DreameCameraController,大多数门开到开口的溪流:
| 方法 | 效果 |
|---|---|
driveDirection('forward'|'left'|'right'|'turnAround'|'stop') 编号 : drive(spdv, spdw) | 远程驱动机器人( 保持时发送~ 1 Hz; 相机固定, 机器人移动) |
returnToDock() • 妇女 locate() | 发送回家/ 哔声以定位 |
spotClean() • 妇女 startPersonFollow() 编号 : stopPersonFollow() • 妇女 findPet() • 妇女 goToPoint(sp, tp) | 工作模式行动 |
stopWork() | 通用停站(跟随/即地清扫/巡航) |
playPetSound('meow'|'bark'|'footsteps'|'purring'|'tickTock') 编号 : playSound(id) | 播放声音剪辑 |
setFillLight(level) 编号 : setFillLightAuto(full) | 充光亮度/ 全光亮亮亮度 |
takePhoto() | 设备侧快照( 上载到云) |
startIntercom() 编号 : stopIntercom() | 双向音频会话( 控制) |
runVacuumAction('startClean'|'pauseClean'|'stopClean'|'dockWash'|'autoEmpty') | 共同的全机器人行动 |
双向对讲机(回声)
麦克风上行链路是 RTMP 类型 8 音频 在同一播放会话上向上推 (G.711 A-law, 8 kHz). 在溪流上:
await stream.startTalk(); // MIoT intercom start + wait for TalkReady
stream.sendTalkPcm(pcm16le8kMono); // encodes to A-law + pushes upstream
await stream.stopTalk();
对象检测
机器人自己跟随的人10001/110和障碍10001/112框为
驱动作为相机服务属性;用 parsePersonFollow 编号 :
parseObstacleData (订阅设备的) propertyChanged) (中文(简体) ).
诊断堆(只读)
nodedreame 可以记录设备在运行和导出时暴露的内容
匿名 JSON 您可以附加在 GitHub 上, 帮助映射无文件
代码(如割草机) taskStatus 2/3/10/13号文件。 垃圾箱很严格 只读 —— 说
它从不发出命令 也从不唤醒机器人 它只订阅
设备的事件流( e)on编号 :off),读取缓存(getProperty),以及
取出云影来refreshFromCache) (中文(简体) ).
import { Nodreame, createDumper } from '@apocaliss92/nodedreame';
const client = new Nodreame({ username, password, region: 'eu' });
await client.login();
const [device] = await client.discoverDevices();
const dumper = createDumper(device);
await dumper.start(); // hooks the live stream + periodic cloud-shadow read
// ...operate the device normally for a few minutes...
await dumper.stop();
console.log(dumper.exportJson()); // pretty, anonymized — safe to share
await client.close();
它捕捉到的东西:每个财产 独特的价值集 unmapped 旗帜
(值与无已知的enum相匹配——最高值信号),MIoT事件,a
静态命令/能力目录。 设备/账号 代号,
MAC、序列、Wi-Fi/IP、GPS、房间名称和任何自定义设备名称(全部)
[redacted]) (中文(简体) ). firmware编号 :region 省略( 不被设备浮出水面)
手柄尚未打开).
立即将整个账户用 createClientDumper(client),返回一个
每个发现装置的倾卸器:
import { createClientDumper } from '@apocaliss92/nodedreame';
const dumpers = createClientDumper(client);
await Promise.all(dumpers.map((d) => d.start()));
// ...observe...
await Promise.all(dumpers.map((d) => d.stop()));
const dumps = dumpers.map((d) => d.exportJson());
将导出 JSON 共享到库中 表格来标记新代码并将其折叠到库中.
安装
npm install @apocaliss92/nodedreame
许可证
麻省理工学院. 前端口从 塔斯夏克/德雷姆真空, (中文(简体) ),
antondaubert/冷冻机,以及
马拉德/节点-梦想; 见 LICENSE。 。 。 .