homebridge-roborock-matter 3.24.1 → 3.24.2
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 +12 -0
- package/README.md +3 -2
- package/package.json +1 -1
- package/roborockLib/lib/localConnector.js +37 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.24.2
|
|
4
|
+
|
|
5
|
+
**Shutting down while LAN discovery was listening left a UDP socket open and every caller waiting on it hung.**
|
|
6
|
+
|
|
7
|
+
Discovery listens for the robots' broadcasts for a fixed five-second window. One timer ends that window: it closes the socket and hands back whatever was heard. Shutdown's only hook into the local transport cleared that timer — which disarmed the one thing that ended the pass.
|
|
8
|
+
|
|
9
|
+
A pass caught in the air when Homebridge stopped therefore never closed its socket, leaving a bound handle holding the event loop open, and never settled its promise, so anything awaiting discovery waited until the process was killed. Shutdown already fixes exactly that hang for pending cloud requests; discovery was missing from the list. It also never released the single-flight claim added in 3.24.1, because that claim is released in the promise's own completion.
|
|
10
|
+
|
|
11
|
+
Shutdown now ends the pass properly: socket closed, promise resolved with the addresses already heard. Resolved rather than rejected, because a rejection would log an error line for an ordinary shutdown, and those addresses were measured facts already recorded — there is no reason to throw them away. Forgetting the pass without closing its socket would have been worse than the leak: the port is fixed, so the next pass would fail to bind with `EADDRINUSE` and reject a discovery that had nothing wrong with it.
|
|
12
|
+
|
|
13
|
+
Nobody would have noticed this as a fault. The process was being torn down anyway, so the leaked handle went with it. It is fixed because a shutdown that cannot finish cleanly is the thing that turns a restart into a kill.
|
|
14
|
+
|
|
3
15
|
## 3.24.1
|
|
4
16
|
|
|
5
17
|
**A robot whose DHCP lease moved it stayed on the cloud until Homebridge was restarted.**
|
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.
|
|
40
|
+
- 🛡️ **Verified by Homebridge.** Reviewed and endorsed by the Homebridge team. 1831 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
|
|
|
@@ -250,6 +250,7 @@ The complete path — robot → plugin → Homebridge → matter.js store — wa
|
|
|
250
250
|
- **"It only happens to my robot vacuum, never to my other Matter accessories" — that is expected, and it is not evidence the plugin is at fault.** Apple Home requires a robot vacuum to be its own Matter node, so Homebridge publishes each one on its own dedicated Matter server with its own port and its own pairing code, while every other accessory you own sits behind the single bridge node. In Homebridge 2.4.x the robot vacuum is the _only_ device type treated that way (`EXTERNAL_DEVICE_TYPES` in `dist/matter/MatterAPIImpl.js` contains `RoboticVacuumCleaner` and nothing else). A controller that loses one subscription therefore loses exactly one tile if that subscription was to a vacuum, whereas losing the bridge's subscription would blank out dozens of accessories at once and be unmistakable. **Your vacuum is not the accessory that breaks most often; it is the only one that can break alone.** So "only the vacuum is affected" is what the controller-side explanation above predicts, rather than something that contradicts it.
|
|
251
251
|
- **Robot shows "Updating…" on every Apple device at once:** _now_ remove the robot from Apple Home and pair it again — a pairing carrying state over from an earlier install is the usual cause (tracked upstream in homebridge/homebridge#3951). What finally worked for the reporter in [#5](https://github.com/mathiashornbek/homebridge-roborock-matter/issues/5) was the full teardown in this exact order: unpair, **uninstall** the plugin, install the current version, pair fresh. A re-pair on top of the existing install did not work for him, so the order is part of the remedy.
|
|
252
252
|
- **Rooms missing for a Q7/B01 robot:** wait for the `B01 rooms for ...` log line, then re-pair once so the Service Area cluster is announced with room data.
|
|
253
|
+
- **Pairing fails with "Accessory not found", and the robot works fine in the Homebridge UI:** that combination is a network path, not a plugin fault — the robot is running, the plugin is publishing, and only the phone cannot reach the node it is being asked to commission. **Try the pairing again with the phone on your 2.4 GHz SSID.** That, and nothing else, is what fixed it for the reporter in [#21](https://github.com/mathiashornbek/homebridge-roborock-matter/issues/21) after both codes had failed repeatedly: routers that keep the bands on separate segments, or that run client isolation on one of them, break commissioning while leaving every other symptom looking healthy. Worth checking in the same pass: the phone and Homebridge on the same subnet, IPv6 enabled on the router, and an Apple TV or HomePod present as a home hub. See also [the switches and sensors need their own pairing](#the-switches-and-sensors-need-their-own-pairing--a-different-qr-code) if you are unsure which of the QR codes you are scanning.
|
|
253
254
|
- **Apple Home shows Manufacturer "Homebridge", and the Model row repeats the robot's _name_:** that is a Homebridge version, not a plugin setting, and there is nothing to configure here. The plugin hands Homebridge `Roborock` plus the real model name for every robot, but for an _external_ Matter accessory — which every robot vacuum is — Homebridge `2.4.0` and earlier hardcode the vendor name and derive the product name from the display name instead (the `basicInformation` block in `dist/matter/server/ServerLifecycle.js`). Fixed upstream in [homebridge/homebridge#3996](https://github.com/homebridge/homebridge/pull/3996) and shipping since `homebridge@2.4.1-beta.3`, where that same block reads the plugin's values whenever the node is external. **Nothing needs re-pairing when you get there:** both attributes are Fixed quality in Matter and are therefore never persisted, so the correct values simply apply on the next restart. The **Serial Number** and **Firmware** rows are unaffected and are already correct on `2.4.0` — the serial is the one Roborock has on file for that robot, falling back to its internal device id if your account carries none.
|
|
254
255
|
- **The Roborock app does not appear on the accessory page in Apple Home, and no plugin change can put it there:** Apple keys that card to the Matter **Vendor ID** in the node's attestation certificate, not to the Manufacturer row above it. Homebridge commissions every node with the Matter _test_ vendor ID `0xFFF1` (`DEFAULT_VENDOR_ID` in `dist/matter/server/ServerConfig.js`; it appears as `vendorId=65521` in the pairing log line), because an uncertified node may not claim a certified manufacturer's ID. Getting the card would take Roborock certifying this bridge, not a setting. The Manufacturer row is free text and unrelated to the ID, which is exactly why that one is fixable and this one is not.
|
|
255
256
|
- **Debug logging needs two switches, not one:** the plugin's own **Debug Mode** only decides whether it _calls_ the debug logger — Homebridge decides whether anything is _printed_, and it suppresses plugin debug output unless Homebridge itself runs with `-D`. Turn on **Homebridge Settings → Homebridge Debug Mode** as well, or the log will look exactly the same as before.
|
|
@@ -257,7 +258,7 @@ The complete path — robot → plugin → Homebridge → matter.js store — wa
|
|
|
257
258
|
|
|
258
259
|
## Contributing
|
|
259
260
|
|
|
260
|
-
Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with
|
|
261
|
+
Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 1831 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.
|
|
261
262
|
|
|
262
263
|
## Support the project
|
|
263
264
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-matter",
|
|
3
|
-
"version": "3.24.
|
|
3
|
+
"version": "3.24.2",
|
|
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": {
|
|
@@ -123,6 +123,16 @@ class localConnector {
|
|
|
123
123
|
* @type {Promise<Record<string, string>>|null}
|
|
124
124
|
*/
|
|
125
125
|
this.discoveryInFlight = null;
|
|
126
|
+
/**
|
|
127
|
+
* Tear down the listening discovery pass: close its socket and settle its
|
|
128
|
+
* promise. Set while a pass is in the air, cleared the moment it ends by
|
|
129
|
+
* any route, so shutdown can never reach for a pass that is already gone.
|
|
130
|
+
*
|
|
131
|
+
* A single slot is enough only because `getLocalDevices` is single-flight;
|
|
132
|
+
* two overlapping passes would clobber it and leak the first socket.
|
|
133
|
+
* @type {(() => void)|null}
|
|
134
|
+
*/
|
|
135
|
+
this.closeDiscoveryPass = null;
|
|
126
136
|
}
|
|
127
137
|
|
|
128
138
|
/**
|
|
@@ -986,6 +996,7 @@ class localConnector {
|
|
|
986
996
|
});
|
|
987
997
|
|
|
988
998
|
server.on("error", (error) => {
|
|
999
|
+
this.closeDiscoveryPass = null;
|
|
989
1000
|
this.adapter.catchError(`Discover server error: ${error.stack}`);
|
|
990
1001
|
closeServer();
|
|
991
1002
|
reject(error);
|
|
@@ -993,7 +1004,23 @@ class localConnector {
|
|
|
993
1004
|
|
|
994
1005
|
server.bind(PORT);
|
|
995
1006
|
|
|
1007
|
+
// Shutdown's only hook into this module clears the timer below, which
|
|
1008
|
+
// used to be the sole route to `closeServer()` and `resolve()` — so a
|
|
1009
|
+
// pass caught in the air leaked a bound UDP socket AND hung every
|
|
1010
|
+
// caller awaiting it. Hand shutdown the pass's own teardown instead.
|
|
1011
|
+
//
|
|
1012
|
+
// It resolves rather than rejects: a rejection reaches `catchError` and
|
|
1013
|
+
// would log an error line for an ordinary shutdown. The addresses heard
|
|
1014
|
+
// so far are already-measured facts, written to diagnostics as they
|
|
1015
|
+
// arrived, so they are handed over rather than discarded.
|
|
1016
|
+
this.closeDiscoveryPass = () => {
|
|
1017
|
+
this.closeDiscoveryPass = null;
|
|
1018
|
+
closeServer();
|
|
1019
|
+
resolve(devices);
|
|
1020
|
+
};
|
|
1021
|
+
|
|
996
1022
|
this.localDevicesTimeout = this.adapter.setTimeout(() => {
|
|
1023
|
+
this.closeDiscoveryPass = null;
|
|
997
1024
|
closeServer();
|
|
998
1025
|
|
|
999
1026
|
resolve(devices);
|
|
@@ -1055,8 +1082,18 @@ class localConnector {
|
|
|
1055
1082
|
clearLocalDevicedTimeout() {
|
|
1056
1083
|
if (this.localDevicesTimeout) {
|
|
1057
1084
|
this.adapter.clearTimeout(this.localDevicesTimeout);
|
|
1085
|
+
this.localDevicesTimeout = null;
|
|
1058
1086
|
}
|
|
1059
1087
|
|
|
1088
|
+
// Clearing that timer disarms the pass's own ending, so the pass has to be
|
|
1089
|
+
// ended here instead: its socket closed and its promise settled. Dropping
|
|
1090
|
+
// the single-flight claim while leaving the socket bound would be worse
|
|
1091
|
+
// than the leak — the port is fixed (58866), so the next pass would fail
|
|
1092
|
+
// to bind with EADDRINUSE and reject a discovery that had nothing wrong
|
|
1093
|
+
// with it. No pass in flight (a cloud-only install never opens one) makes
|
|
1094
|
+
// this a no-op by construction.
|
|
1095
|
+
this.closeDiscoveryPass?.();
|
|
1096
|
+
|
|
1060
1097
|
// This is the only local-transport hook the adapter's
|
|
1061
1098
|
// clearTimersAndIntervals calls on shutdown. Reconnect timers are armed as
|
|
1062
1099
|
// far out as LOCAL_RECONNECT_MAX_DELAY_MS, so without this they would
|