homebridge-roborock-matter 2.9.4 → 2.9.5
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 +5 -0
- package/package.json +1 -1
- package/roborockLib/roborockAPI.js +76 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.9.5
|
|
4
|
+
|
|
5
|
+
- **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.
|
|
6
|
+
- Full suite: 263 passing (3 new persistence-debounce tests).
|
|
7
|
+
|
|
3
8
|
## 2.9.4
|
|
4
9
|
|
|
5
10
|
Startup-cost cleanup release (also refreshes the npm README with the Donate button and the prominent Verified badge).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-matter",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.5",
|
|
4
4
|
"description": "Matter-only Homebridge plugin publishing Roborock robot vacuums (including 2025 B01/Q7-series) as native Matter accessories for Apple Home. Fork of homebridge-roborock-vacuum2.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -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)) {
|