homebridge-roborock-matter 2.9.4 → 2.9.6
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/CHANGELOG.md +9 -0
- package/README.md +26 -23
- package/package.json +2 -2
- package/roborockLib/roborockAPI.js +76 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.9.6
|
|
4
|
+
|
|
5
|
+
- **Friendlier plugin description and README.** The npm description shown in the Homebridge UI now leads with what the plugin does for you ("sign in with your Roborock account — start cleans, pick rooms, set suction power, and see live which room the robot is cleaning") instead of protocol terminology. The README's intro, feature matrix and live-room section were rewritten in plain language, with the technical depth preserved in collapsible under-the-hood sections. No functional changes.
|
|
6
|
+
|
|
7
|
+
## 2.9.5
|
|
8
|
+
|
|
9
|
+
- **One synchronous disk write per received robot message eliminated.** The per-device diagnostics states (last cloud/local message, transport history) were flushed to disk with a blocking `fs.writeFileSync` on EVERY message a robot pushed — every few seconds per robot while cleaning. They are served from memory (the settings UI never reads the file); the on-disk copy only needs to survive restarts. Disk flushes for these two states are now debounced to at most once per minute, with a guaranteed flush on shutdown. Result: event-loop stalls removed from the message hot path, and meaningfully less SD-card wear on Raspberry Pi installs. Critical states (credentials, HomeData, room caches) still persist immediately.
|
|
10
|
+
- Full suite: 263 passing (3 new persistence-debounce tests).
|
|
11
|
+
|
|
3
12
|
## 2.9.4
|
|
4
13
|
|
|
5
14
|
Startup-cost cleanup release (also refreshes the npm README with the Donate button and the prominent Verified badge).
|
package/README.md
CHANGED
|
@@ -27,29 +27,29 @@
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
Sign in with the same account you already use in the Roborock app — that's the whole setup. Every robot then appears in Apple Home as a real vacuum: start and stop cleans, send it to specific rooms, pick the suction power, check the battery — and watch the Home app tell you **which room it's cleaning right now**. No token extraction, no network tricks, no command line.
|
|
31
31
|
|
|
32
32
|
## Why this plugin
|
|
33
33
|
|
|
34
|
-
-
|
|
35
|
-
- 📍 **
|
|
36
|
-
- 🧭 **
|
|
37
|
-
-
|
|
38
|
-
- 🛡️ **
|
|
34
|
+
- 🥇 **The only plugin that works with the newest Roborocks.** The 2025 Q7 series (Q7 M5 / M5+) speaks a brand-new protocol that no other Homebridge plugin understands. Fully supported here — alongside the classic S- and Q-series models.
|
|
35
|
+
- 📍 **See where it's cleaning — live.** Apple Home shows _"Cleaning — Kitchen"_ with the room the robot is actually inside, updating as it moves from room to room. Works even for cleans started from the robot's button or the Roborock app. No other Homebridge plugin does this.
|
|
36
|
+
- 🧭 **One robot, one tile.** Your vacuum shows up as a single, native accessory in Apple Home — no clutter of fake fans and helper switches. Rooms appear with the names you gave them in the Roborock app.
|
|
37
|
+
- ⚡ **Fast and reliable.** Commands go directly to the robot over your own network whenever possible, with the Roborock cloud as automatic backup — and built-in diagnostics in the settings if you ever want to look under the hood.
|
|
38
|
+
- 🛡️ **Verified by Homebridge.** Reviewed and endorsed by the Homebridge team. 263 automated tests, zero known vulnerabilities, no analytics, and a startup designed to never crash your Homebridge — even when your Wi-Fi or the Roborock cloud has a bad day.
|
|
39
39
|
|
|
40
40
|
## Features
|
|
41
41
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
| 🤖 **
|
|
45
|
-
| 🚪 **
|
|
46
|
-
| 📍 **Live room tracking**
|
|
47
|
-
| 📊 **Honest cleaning progress**
|
|
48
|
-
| 🌀 **Cleaning modes**
|
|
49
|
-
| 🔋 **Battery & charging**
|
|
50
|
-
| 🧠 **
|
|
51
|
-
| 🩺 **Built-in diagnostics**
|
|
52
|
-
| 🔐 **
|
|
42
|
+
| | |
|
|
43
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
44
|
+
| 🤖 **Full control from Apple Home** | Start, stop, pause and send the robot home to its dock — from the Home app, Siri, or automations |
|
|
45
|
+
| 🚪 **Clean specific rooms** | Pick rooms right in Apple Home, with the names you gave them in the Roborock app — multi-floor homes included |
|
|
46
|
+
| 📍 **Live room tracking** | See which room the robot is cleaning right now, updated as it moves ([details](#live-room-tracking)) |
|
|
47
|
+
| 📊 **Honest cleaning progress** | Each room goes pending → cleaning → done — and a room only counts as done when the robot was actually there |
|
|
48
|
+
| 🌀 **Cleaning & suction modes** | Vacuum / Mop / Vacuum + Mop on models that support it — plus optional Quiet / Balanced / Turbo / Max suction levels (Max+ on Q7) |
|
|
49
|
+
| 🔋 **Battery & charging** | Battery level and charging state on the accessory ([one Apple-side caveat](#battery-percentage-in-apple-home)) |
|
|
50
|
+
| 🧠 **New models just work** | Brand-new Roborock models get sensible defaults automatically, and the plugin adapts to what each robot actually supports |
|
|
51
|
+
| 🩺 **Built-in diagnostics** | Connection status, a one-click connection test, and a ready-to-share report if you ever need help |
|
|
52
|
+
| 🔐 **Easy, safe login** | Sign in with your Roborock account right in the settings — two-factor supported, session stored encrypted |
|
|
53
53
|
|
|
54
54
|
## Quick start
|
|
55
55
|
|
|
@@ -66,13 +66,16 @@ For B01/Q7 robots, room selection appears once the map has been fetched (watch f
|
|
|
66
66
|
|
|
67
67
|
## Live room tracking
|
|
68
68
|
|
|
69
|
-
While
|
|
69
|
+
While your robot cleans, the plugin follows its position on the map and tells Apple Home which room it's in — _"Cleaning — Kitchen"_, just like the Roborock app shows it. It updates as the robot moves, works for whole-home cleans, and even for cleans you start from the robot's button.
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
- Works for full-home cleans and for runs started from the robot's button or the Roborock app, which previously had no room to show at all.
|
|
73
|
-
- Progress stays honest: a room is only marked _completed_ once the robot was actually detected inside it and has moved on. The plugin never invents data the robot didn't report.
|
|
71
|
+
Progress stays honest: a room is only shown as _completed_ once the robot was actually seen inside it. The plugin never invents data the robot didn't report. Enabled by default; turn it off with `enableLiveRoomTracking: false`.
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
<details>
|
|
74
|
+
<summary>How it works under the hood</summary>
|
|
75
|
+
|
|
76
|
+
While a robot is actively cleaning, the plugin fetches its live position from the map channel (throttled to ~20 s, active runs only, nothing while docked or paused) and publishes the room it is inside as the Matter Service Area `currentArea`. Both robot generations are covered: **B01/Q7** robots via the encrypted SCMap protobuf (position ray-cast against per-room boundary outlines), **classic S/Q-series** robots via the RRMap segment grid (position resolved against per-pixel room segments — a single-byte lookup on the raw map buffer, ~1 µs per check).
|
|
77
|
+
|
|
78
|
+
</details>
|
|
76
79
|
|
|
77
80
|
## Suction modes (optional)
|
|
78
81
|
|
|
@@ -122,7 +125,7 @@ The complete path — robot → plugin → Homebridge → matter.js store — wa
|
|
|
122
125
|
|
|
123
126
|
## Contributing
|
|
124
127
|
|
|
125
|
-
Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with
|
|
128
|
+
Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 263 tests (protocol fixtures verified against the [python-roborock](https://github.com/Python-roborock/python-roborock) reference), strict TypeScript checking, and CI across Node 22/24 × Homebridge 1.11/2.x — `npm test` before you push and you're set.
|
|
126
129
|
|
|
127
130
|
## Support the project
|
|
128
131
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-matter",
|
|
3
|
-
"version": "2.9.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.9.6",
|
|
4
|
+
"description": "Control your Roborock robot vacuums from Apple Home — just sign in with your Roborock account. Start cleans, pick rooms, set suction power, and see live which room the robot is cleaning. The only plugin supporting the new 2025 Q7 series, alongside classic S- and Q-series models. Verified by Homebridge.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Mathias Hornbek"
|
|
@@ -42,6 +42,15 @@ const B01_LIVE_ROOM_CLEAR_V1_STATES = new Set([3, 8]);
|
|
|
42
42
|
// slower cadence than the ~12s active status polls.
|
|
43
43
|
const B01_LIVE_ROOM_MIN_FETCH_GAP_MS = 20000;
|
|
44
44
|
|
|
45
|
+
// Persisted states whose disk flush is debounced (see setStateAsync): they
|
|
46
|
+
// change on every received robot message, are served from memory, and only
|
|
47
|
+
// need the on-disk copy for restart survival.
|
|
48
|
+
const DEBOUNCED_PERSIST_IDS = new Set([
|
|
49
|
+
"TransportDiagnostics",
|
|
50
|
+
"RoborockDiagnostics",
|
|
51
|
+
]);
|
|
52
|
+
const PERSIST_FLUSH_DEBOUNCE_MS = 60000;
|
|
53
|
+
|
|
45
54
|
const PERSISTED_STATE_IDS = new Set([
|
|
46
55
|
"UserData",
|
|
47
56
|
"clientID",
|
|
@@ -279,6 +288,19 @@ class Roborock {
|
|
|
279
288
|
async setStateAsync(id, state) {
|
|
280
289
|
try {
|
|
281
290
|
if (PERSISTED_STATE_IDS.has(id)) {
|
|
291
|
+
// Chatty diagnostic states update on every received robot message
|
|
292
|
+
// (every few seconds while cleaning). They are read from memory by
|
|
293
|
+
// the settings UI; the on-disk copy only needs to survive restarts.
|
|
294
|
+
// Debouncing their disk flush to once per minute turns one
|
|
295
|
+
// SYNCHRONOUS write per robot message into at most one per minute
|
|
296
|
+
// — a real win for event-loop latency and SD-card wear on
|
|
297
|
+
// Raspberry Pi installs. Critical states (credentials, HomeData,
|
|
298
|
+
// room caches) still persist immediately.
|
|
299
|
+
if (DEBOUNCED_PERSIST_IDS.has(id)) {
|
|
300
|
+
this.states[id] = state;
|
|
301
|
+
this.schedulePersistFlush(id);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
282
304
|
const persistPath = this.getPersistPath(id);
|
|
283
305
|
fs.mkdirSync(path.dirname(persistPath), { recursive: true });
|
|
284
306
|
fs.writeFileSync(persistPath, JSON.stringify(state, null, 2, "utf8"));
|
|
@@ -1528,6 +1550,7 @@ class Roborock {
|
|
|
1528
1550
|
|
|
1529
1551
|
async stopService() {
|
|
1530
1552
|
try {
|
|
1553
|
+
this.flushPendingPersistedStates();
|
|
1531
1554
|
await this.clearTimersAndIntervals();
|
|
1532
1555
|
this.bInited = false;
|
|
1533
1556
|
} catch (e) {
|
|
@@ -1535,6 +1558,59 @@ class Roborock {
|
|
|
1535
1558
|
}
|
|
1536
1559
|
}
|
|
1537
1560
|
|
|
1561
|
+
/**
|
|
1562
|
+
* Schedule a debounced disk flush for a chatty persisted state. The
|
|
1563
|
+
* in-memory copy is already current; the trailing flush (unref'd so it
|
|
1564
|
+
* never keeps the process alive) writes the LATEST value at most once
|
|
1565
|
+
* per PERSIST_FLUSH_DEBOUNCE_MS.
|
|
1566
|
+
* @param {string} id
|
|
1567
|
+
*/
|
|
1568
|
+
schedulePersistFlush(id) {
|
|
1569
|
+
if (!this._pendingPersistFlushes) {
|
|
1570
|
+
this._pendingPersistFlushes = new Map();
|
|
1571
|
+
}
|
|
1572
|
+
if (this._pendingPersistFlushes.has(id)) {
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
const timer = setTimeout(() => {
|
|
1576
|
+
this._pendingPersistFlushes.delete(id);
|
|
1577
|
+
this.persistStateToDisk(id);
|
|
1578
|
+
}, PERSIST_FLUSH_DEBOUNCE_MS);
|
|
1579
|
+
if (typeof timer?.unref === "function") {
|
|
1580
|
+
timer.unref();
|
|
1581
|
+
}
|
|
1582
|
+
this._pendingPersistFlushes.set(id, timer);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/** Write the current in-memory value of a persisted state to disk now. */
|
|
1586
|
+
persistStateToDisk(id) {
|
|
1587
|
+
try {
|
|
1588
|
+
const state = this.states[id];
|
|
1589
|
+
if (state === undefined) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
const persistPath = this.getPersistPath(id);
|
|
1593
|
+
fs.mkdirSync(path.dirname(persistPath), { recursive: true });
|
|
1594
|
+
fs.writeFileSync(persistPath, JSON.stringify(state, null, 2));
|
|
1595
|
+
} catch (error) {
|
|
1596
|
+
this.log.debug(
|
|
1597
|
+
`Debounced persist of '${id}' failed: ${error?.message || error}`
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/** Flush all pending debounced persists immediately (shutdown path). */
|
|
1603
|
+
flushPendingPersistedStates() {
|
|
1604
|
+
if (!this._pendingPersistFlushes) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
for (const [id, timer] of this._pendingPersistFlushes) {
|
|
1608
|
+
clearTimeout(timer);
|
|
1609
|
+
this.persistStateToDisk(id);
|
|
1610
|
+
}
|
|
1611
|
+
this._pendingPersistFlushes.clear();
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1538
1614
|
async getUserData(loginApi) {
|
|
1539
1615
|
try {
|
|
1540
1616
|
if (this.isValidUserData(this.userData)) {
|