homebridge-roborock-matter 3.26.0 → 3.27.0

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.27.0
4
+
5
+ **The scene resource named its one write verb, and it is the one that deletes. So the search moves off that resource and onto its siblings — asked safely, and this time with controls, so the round either finds the route or proves there is nothing left to find.**
6
+
7
+ 3.26.0 asked `user/scene/<id>` which methods it accepts. Issue #22's reporter ran it and the answer came back on the first try:
8
+
9
+ ```
10
+ OPTIONS user/scene/<id> → "" — the resource allows: DELETE,OPTIONS
11
+ ```
12
+
13
+ That is an answer, and not one of the two the thread had planned for. The plan said an `Allow` naming a write verb means the next step is a promised no-op, and a missing `Allow` means we are near the end of what can be measured from outside. What arrived names exactly one method that changes anything, and it removes a schedule rather than toggling one. There is no write there to put behind a HomeKit switch, and a destructive verb is not something to try against someone's live account to see what happens.
14
+
15
+ **What that rules out is the resource, not the feature.** The plugin's own scene-run path reaches `user/scene/<id>/execute`, so sub-resources under a scene id exist and carry verbs the scene itself does not. This release asks the same safe question of the routes that are left:
16
+
17
+ - `user/scene` — the collection, where a REST API most often keeps an update
18
+ - `user/scene/<id>/enable` — the literal candidate for the nested `TIMER` flag the reporter's app flips
19
+
20
+ **And of two controls, which is the part that makes the answers mean anything.** An `Allow` header on its own is not evidence:
21
+
22
+ - `user/scene/device/<duid>` — a **positive** control. This same probe run already read it successfully, so it is mapped beyond doubt. If `OPTIONS` cannot describe even that, the instrument does not see routes and no other answer in the set is worth reading. It is also why the control is this route and not `/execute`: a control must not be a path whose real verb starts a robot.
23
+ - `user/scene/<id>/no-such-subresource-control` — a **negative** control. If a path with nothing behind it also answers with an `Allow` header, the server answers everything and every positive here is an artefact.
24
+
25
+ Together they bound the search instead of extending it: this round either names a route or shows that no later round would.
26
+
27
+ Everything that made the probe safe to ship is unchanged and still bound by tests — silent unless debug logging is on, safe methods only, once per robot per session, cannot throw into the poll it rides on, and only the `Allow` header is taken from a response rather than the header block, which carries session material. A source guard now also pins the two properties that would otherwise fail invisibly: every candidate is narrowed to a safe method at one point, and the negative control cannot be dropped.
28
+
3
29
  ## 3.26.0
4
30
 
5
31
  **The refused route answered, and what it said was that the route exists — just not for the verb we asked with. So this release asks it which verb it does take, without attempting one.**
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. 1854 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. 1862 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
 
@@ -258,7 +258,7 @@ The complete path — robot → plugin → Homebridge → matter.js store — wa
258
258
 
259
259
  ## Contributing
260
260
 
261
- Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 1854 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
+ Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 1862 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.
262
262
 
263
263
  ## Support the project
264
264
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-roborock-matter",
3
- "version": "3.26.0",
3
+ "version": "3.27.0",
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": {
@@ -299,6 +299,15 @@ const B01_STATUS_FORCED_GAP_MS = 1500;
299
299
  const B01_STATUS_ACTIVE_GAP_MS = 12000;
300
300
  const B01_STATUS_IDLE_GAP_MS = 25000;
301
301
 
302
+ // The last path segment of the cloud schedule probe's negative control.
303
+ //
304
+ // The probe asks candidate routes which methods they take. An `Allow` header
305
+ // coming back is only evidence if a route that CANNOT exist stays silent, so
306
+ // one deliberately unmappable path is asked the same question. It is a fixed
307
+ // literal rather than a random string so that the same reading is reproducible
308
+ // across restarts and legible in a log a user pastes into an issue.
309
+ const ABSENT_ROUTE_PROBE_SEGMENT = "no-such-subresource-control";
310
+
302
311
  // How many keys of an arbitrary diagnostic object survive compaction.
303
312
  const DIAGNOSTIC_KEY_LIMIT = 30;
304
313
 
@@ -4845,6 +4854,62 @@ class Roborock {
4845
4854
  { label: "sceneMethods", path: scenePath, method: "options" },
4846
4855
  results
4847
4856
  );
4857
+
4858
+ // And that question was answered: the resource allows DELETE and
4859
+ // OPTIONS. It is an answer, and not one of the two the thread's own
4860
+ // decision rule anticipated. The singular scene resource takes exactly
4861
+ // one method that changes anything, and it destroys a schedule rather
4862
+ // than toggling one — so there is no write here to put behind a HomeKit
4863
+ // switch, and a destructive verb aimed at a stranger's live account to
4864
+ // see what happens is not a measurement this project will take.
4865
+ //
4866
+ // That rules out the resource, not the feature. Our own `executeScene`
4867
+ // reaches `user/scene/{id}/execute`, so sub-resources under a scene id
4868
+ // demonstrably exist and carry verbs the scene itself does not. Whether
4869
+ // one of them is the toggle is the last question this instrument can
4870
+ // ask, and it is asked WITH CONTROLS, because on its own an `Allow`
4871
+ // header proves nothing:
4872
+ //
4873
+ // - `user/scene` — the collection, where a REST API most often keeps
4874
+ // an update;
4875
+ // - `user/scene/{id}/enable` — the literal candidate for the nested
4876
+ // flag the reporter's app flips;
4877
+ // - `user/scene/device/{duid}` — the POSITIVE control. This run has
4878
+ // ALREADY read it successfully, so it is mapped beyond doubt. If
4879
+ // OPTIONS cannot describe even that, the instrument does not see
4880
+ // routes and every other answer in this set is noise. It is also
4881
+ // why the control is this route and not `/execute`: a control must
4882
+ // not be a path whose real verb runs a robot.
4883
+ // - a path that cannot exist — the NEGATIVE control. If THAT comes
4884
+ // back with an Allow header, the server answers everything and no
4885
+ // positive here means anything.
4886
+ //
4887
+ // Together they bound the search rather than extend it: this round
4888
+ // either names a route or shows that no later round would.
4889
+ //
4890
+ // Asking costs nothing that could alter a schedule, and that is
4891
+ // measured rather than assumed: the OPTIONS of the scene resource came
4892
+ // back with an EMPTY body and an Allow header, which is a servlet
4893
+ // container answering the request itself instead of handing it to the
4894
+ // handler behind the path.
4895
+ for (const candidate of [
4896
+ { label: "sceneCollectionMethods", path: "user/scene" },
4897
+ { label: "sceneEnableMethods", path: `${scenePath}/enable` },
4898
+ {
4899
+ label: "mappedRouteControl",
4900
+ path: `user/scene/device/${duid}`,
4901
+ },
4902
+ {
4903
+ label: "absentRouteControl",
4904
+ path: `${scenePath}/${ABSENT_ROUTE_PROBE_SEGMENT}`,
4905
+ },
4906
+ ]) {
4907
+ await this.probeOneCloudScheduleRoute(
4908
+ duid,
4909
+ { ...candidate, method: "options" },
4910
+ results
4911
+ );
4912
+ }
4848
4913
  }
4849
4914
 
4850
4915
  await this.updateRoborockDiagnostics(