homebridge-roborock-matter 3.9.0 → 3.9.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.9.1
4
+
5
+ **The first status request of every startup was refused, on every Q7, on every restart.**
6
+
7
+ The log said so plainly and had for weeks: `B01 status for 1. Sal recovered after 1 failed attempt(s).` It looked like the Roborock cloud being briefly unreachable, so it was left alone.
8
+
9
+ It was not the cloud. Measured over 30,224 log lines covering 49 restarts: 92 recoveries, one per Q7 per restart, without a single exception. Always exactly one failed attempt, never two. Always between 2 and 32 seconds after startup, never later. The robot on the older protocol had none of them.
10
+
11
+ A flaky connection does not look like that. It gives a varying number of attempts at varying times. One attempt, every time, only at startup, only on the cloud-only protocol is a race — and it was an ordering mistake in the startup sequence. The dedicated Q7 status loop was started at the end of device creation, and it polls immediately; the sequence did not wait for the MQTT session until after device creation had returned. A Q7 request is cloud-only by construction, so that first poll was rejected before anything reached the wire. The wait was already there, with a comment explaining this exact hazard for the two calls after it. The loop start had simply slipped in front of it.
12
+
13
+ The same event explains the other half, which had been observed fourteen times and never connected to it: for about 27 seconds after every restart, a Q7's tile in Apple Home showed `battery=100%, operationalState=0, runMode=0` — the snapshot taken at registration rather than the robot. That window is not a separate phenomenon. A refused attempt still stamps the request throttle, so the 15-second tick that followed fell inside the 25-second idle gap and was dropped, and the robot's real status did not arrive until the tick at 30 seconds. Measured median: 31 seconds.
14
+
15
+ Two changes, because the ordering fix alone leaves the hazard reachable — the wait resolves on a 10-second timeout whether or not the broker came up:
16
+
17
+ - The loop is started by the login sequence, immediately after it has waited for the MQTT session, instead of at the end of `createDevices()`. A source-level test now asserts that order and that device creation does not start the loop, so it cannot drift back.
18
+ - The loop's own boot poll is skipped when the cloud session is known to be down. A request that is never sent cannot stamp the throttle either, so even in that case the first real status arrives at the next tick rather than the one after it. A connector that cannot report its state is treated as usable, so nothing changes for callers without a live session.
19
+
20
+ Verified red against 3.9.0: 7 of 12 assertions failed, and the symptom test failed by producing the field log line verbatim.
21
+
22
+ 934 tests, up from 922.
23
+
3
24
  ## 3.9.0
4
25
 
5
26
  **Automations can now be triggered by the robot.**
package/README.md CHANGED
@@ -37,7 +37,7 @@ This is the most feature-packed, most thoroughly engineered Roborock plugin for
37
37
  - 📍 **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.
38
38
  - 🧭 **One robot, one tile — and as many robots as you own.** Sign in once and your whole fleet comes along: every vacuum on your account appears as its own clean, native accessory in Apple Home. No clutter of fake fans and helper switches, and rooms appear with the names you gave them in the Roborock app.
39
39
  - ⚡ **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.
40
- - 🛡️ **Verified by Homebridge.** Reviewed and endorsed by the Homebridge team. 922 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.
40
+ - 🛡️ **Verified by Homebridge.** Reviewed and endorsed by the Homebridge team. 934 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.
41
41
 
42
42
  ## Features
43
43
 
@@ -197,7 +197,7 @@ The complete path — robot → plugin → Homebridge → matter.js store — wa
197
197
 
198
198
  ## Contributing
199
199
 
200
- Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 922 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.
200
+ Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 934 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.
201
201
 
202
202
  ## Support the project
203
203
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-matter",
3
- "version": "3.9.0",
3
+ "version": "3.9.1",
4
4
  "description": "The most complete Roborock plugin for Apple Home. Supports the entire Roborock lineup — from the classic S-series to the new 2025 Q7 series that no other plugin can control. Sign in with your Roborock account and get native start/stop, room cleaning, suction levels, battery, and live 'cleaning in the kitchen' room tracking. Verified by Homebridge.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -1767,6 +1767,13 @@ class Roborock {
1767
1767
  // up releases the wait and the requests fail as they would have.
1768
1768
  await this.rr_mqtt_connector.waitUntilConnected();
1769
1769
 
1770
+ // First on the wire once the session is up, because it is the one
1771
+ // request an Apple Home tile is waiting for: until a Q7's real
1772
+ // status lands, the tile shows the registration snapshot from the
1773
+ // Matter store. Started here rather than at the end of
1774
+ // createDevices() so the boot poll cannot precede the wait above.
1775
+ this.startB01StatusLoop();
1776
+
1770
1777
  await this.getNetworkInfo();
1771
1778
  await this.initializeDeviceUpdates();
1772
1779
 
@@ -2158,10 +2165,14 @@ class Roborock {
2158
2165
  this.subscribeStates("Devices." + duid + ".deviceInfo.online");
2159
2166
  }
2160
2167
 
2161
- // Start AFTER the loop: the loop's device gate reads
2162
- // initializedVacuumDuids, which is only fully populated at this point.
2163
- // (Calling it per-device made the start depend on device ordering.)
2164
- this.startB01StatusLoop();
2168
+ // The B01 status loop is deliberately NOT started here. Its device gate
2169
+ // reads initializedVacuumDuids, which is only fully populated at this
2170
+ // point, so this looked like the earliest safe place — but the loop polls
2171
+ // immediately, and a B01 request is cloud-only by construction, and the
2172
+ // caller does not wait for the MQTT session until after this method
2173
+ // returns. The first status of every single startup was therefore refused
2174
+ // with "cloud unavailable". It is started by the caller instead, straight
2175
+ // after that wait.
2165
2176
  }
2166
2177
 
2167
2178
  async initializeDeviceUpdates() {
@@ -4240,7 +4251,28 @@ class Roborock {
4240
4251
  // First poll immediately: after a restart the Matter store holds the
4241
4252
  // registration snapshot (HomeData fallback), and the sooner the real
4242
4253
  // values land, the sooner controllers receive a genuine change report.
4243
- pollAllB01({ force: true });
4254
+ //
4255
+ // But only into a cloud session that is actually up. A B01 request is
4256
+ // cloud-only, so one issued before the MQTT session is established is
4257
+ // refused before it reaches the wire — and a refused attempt still stamps
4258
+ // the attempt throttle, which pushes the retry past the 15s tick and into
4259
+ // the one at 30s. Skipping the attempt costs nothing and buys both: no
4260
+ // spurious "recovered after 1 failed attempt(s)" line, and a first real
4261
+ // status at the next tick instead of the one after it.
4262
+ //
4263
+ // A connector that cannot answer the question is treated as usable, so a
4264
+ // caller without a live connector keeps the behaviour it always had.
4265
+ const cloudSessionUp =
4266
+ typeof this.rr_mqtt_connector?.isConnected === "function"
4267
+ ? this.rr_mqtt_connector.isConnected()
4268
+ : true;
4269
+ if (cloudSessionUp) {
4270
+ pollAllB01({ force: true });
4271
+ } else {
4272
+ this.log.debug(
4273
+ "Holding the first B01/Q7 status poll until the Roborock cloud session is up; the loop tick will take it."
4274
+ );
4275
+ }
4244
4276
  this.b01StatusLoopHandle = this.setInterval(pollAllB01, B01_STATUS_TICK_MS);
4245
4277
  if (typeof this.b01StatusLoopHandle?.unref === "function") {
4246
4278
  this.b01StatusLoopHandle.unref();