homebridge-roborock-matter 3.26.0 → 3.28.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 +57 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/roborockLib/roborockAPI.js +221 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.28.0
|
|
4
|
+
|
|
5
|
+
**Startup and network traffic, measured rather than felt.**
|
|
6
|
+
|
|
7
|
+
### The tiles no longer wait for readings no tile displays
|
|
8
|
+
|
|
9
|
+
`initializeDeviceUpdates` awaited the whole first parameter cycle for every robot — 8 serial round-trips per classic robot — and only then did startup set `bInited`, fire the Homebridge callback and register the Matter accessories. Those 8 requests read consumable hours, timers, carpet mode and water-box mode. Not one of them reaches the tile; the tile displays `get_status`, which was not among them.
|
|
10
|
+
|
|
11
|
+
Counted on the critical path between "Homebridge started the plugin" and "the vacuum exists in Apple Home": **up to 12 serial network waits before, 3 after.** The home snapshot was also fetched twice back to back — `v2/user/homes` to build the device list, then `user/homes` again inside `updateHomeData` — and the second one is now handed the first one's payload. The network probe and the first status run concurrently instead of one after the other.
|
|
12
|
+
|
|
13
|
+
On my own network this was a few seconds. On a robot that does not answer locally — the case in [#22](https://github.com/mathiashornbek/homebridge-roborock-matter/issues/22), where every local request runs its full 10-second timeout before falling back to the cloud — it kept the vacuum out of Apple Home for over a minute after every restart.
|
|
14
|
+
|
|
15
|
+
### The first status is immediate, not 15 seconds away
|
|
16
|
+
|
|
17
|
+
The classic poller is a plain interval, so its first tick landed 15 seconds after start and the tile showed the cloud snapshot until then. The B01 loop already fired its first read immediately for exactly this reason; the classic path now does too. Each robot's first status is awaited before registration, in parallel across robots and capped at 4 seconds, so the tile registers with fresh state but never waits on a robot that will not answer.
|
|
18
|
+
|
|
19
|
+
### 160 requests an hour became 52
|
|
20
|
+
|
|
21
|
+
Measured per classic robot: the periodic cycle issued 8 requests every 180 seconds — `get_multi_maps_list`, `get_room_mapping`, `get_consumable`, `get_server_timer`, `get_timer`, `get_carpet_mode`, `get_carpet_clean_mode`, `get_water_box_custom_mode` — for values that change roughly once a week. 3 robots in one house: about 280 requests an hour, 6,700 a day, at the Roborock cloud and the robots' local ports.
|
|
22
|
+
|
|
23
|
+
The 6 slow-changing readings now move to a **30-minute lane**. Room mappings deliberately stay on every cycle: a room renamed in the Roborock app should reach Apple Home within minutes, and that refresh now serves from the persisted cache first — the same shape the B01 branch already had — so its 2 round-trips run behind the tile rather than in front of it.
|
|
24
|
+
|
|
25
|
+
| | before | after |
|
|
26
|
+
| ------------- | ---------- | ---------- |
|
|
27
|
+
| classic robot | 160 / hour | ~52 / hour |
|
|
28
|
+
| Q7 (B01) | 60 / hour | ~6 / hour |
|
|
29
|
+
|
|
30
|
+
`get_status` is untouched — still every 15 seconds for a classic robot, still the dedicated B01 loop — because that is the one reading the tile lives on. A schedule written from Apple Home or a diagnostics export that wants fresh numbers can bring the slow lane forward with `requestSlowParameterPoll`.
|
|
31
|
+
|
|
32
|
+
8 new tests, 6 of which fail against 3.27.0.
|
|
33
|
+
|
|
34
|
+
## 3.27.0
|
|
35
|
+
|
|
36
|
+
**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.**
|
|
37
|
+
|
|
38
|
+
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:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
OPTIONS user/scene/<id> → "" — the resource allows: DELETE,OPTIONS
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
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.
|
|
45
|
+
|
|
46
|
+
**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:
|
|
47
|
+
|
|
48
|
+
- `user/scene` — the collection, where a REST API most often keeps an update
|
|
49
|
+
- `user/scene/<id>/enable` — the literal candidate for the nested `TIMER` flag the reporter's app flips
|
|
50
|
+
|
|
51
|
+
**And of two controls, which is the part that makes the answers mean anything.** An `Allow` header on its own is not evidence:
|
|
52
|
+
|
|
53
|
+
- `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.
|
|
54
|
+
- `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.
|
|
55
|
+
|
|
56
|
+
Together they bound the search instead of extending it: this round either names a route or shows that no later round would.
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
3
60
|
## 3.26.0
|
|
4
61
|
|
|
5
62
|
**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.
|
|
40
|
+
- 🛡️ **Verified by Homebridge.** Reviewed and endorsed by the Homebridge team. 1870 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
|
|
261
|
+
Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 1870 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.
|
|
3
|
+
"version": "3.28.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
|
|
|
@@ -331,6 +340,32 @@ const DIAGNOSTIC_PRIORITY_KEYS = new Set([
|
|
|
331
340
|
// ~86k wake-ups per robot per day to serve at most 1440 polls.
|
|
332
341
|
const CLASSIC_STATUS_TICK_MS = 15000;
|
|
333
342
|
|
|
343
|
+
// HOW OFTEN THE SLOW-CHANGING PARAMETERS ARE RE-READ.
|
|
344
|
+
//
|
|
345
|
+
// Measured before this existed: a classic robot's periodic cycle issued 8
|
|
346
|
+
// requests every 180 seconds - `get_multi_maps_list`, `get_room_mapping`,
|
|
347
|
+
// `get_consumable`, `get_server_timer`, `get_timer`, `get_carpet_mode`,
|
|
348
|
+
// `get_carpet_clean_mode`, `get_water_box_custom_mode` - 160 an hour, all
|
|
349
|
+
// serial, for values that change roughly once a week. Consumable hours tick
|
|
350
|
+
// over during a clean and nowhere else; timers change when the user edits a
|
|
351
|
+
// schedule; carpet and water-box modes change when the user changes a setting.
|
|
352
|
+
// None of them drives the Apple Home tile - that is `get_status`, on its own
|
|
353
|
+
// 15-second tick and untouched by this.
|
|
354
|
+
//
|
|
355
|
+
// Room mappings are deliberately NOT in the slow lane: a room the user renames
|
|
356
|
+
// or splits in the Roborock app should show up in Apple Home within minutes,
|
|
357
|
+
// and the refresh already serves from cache first. Everything else waits.
|
|
358
|
+
//
|
|
359
|
+
// 30 minutes cuts a classic robot from 160 to about 52 requests an hour and a
|
|
360
|
+
// Q7 from 60 to about 46. A diagnostics export still sees the last reading;
|
|
361
|
+
// anything that needs a fresh one can ask for it.
|
|
362
|
+
const SLOW_PARAMETER_POLL_INTERVAL_MS = 30 * 60 * 1000;
|
|
363
|
+
|
|
364
|
+
// The longest startup will wait for a classic robot's first status before the
|
|
365
|
+
// Matter accessories register. A robot that answers does so in well under a
|
|
366
|
+
// second; one that does not must not hold the others' tiles hostage.
|
|
367
|
+
const INITIAL_STATUS_WAIT_CAP_MS = 4000;
|
|
368
|
+
|
|
334
369
|
// Persisted states whose disk flush is debounced (see setStateAsync): they
|
|
335
370
|
// change on every received robot message, are served from memory, and only
|
|
336
371
|
// need the on-disk copy for restart survival.
|
|
@@ -465,6 +500,8 @@ class Roborock {
|
|
|
465
500
|
this.unsupportedPollCommands = new Set();
|
|
466
501
|
this.loggedPollProfiles = new Set();
|
|
467
502
|
this.skippedDialectPolls = new Set();
|
|
503
|
+
/** @type {Map<string, number>} duid -> when its slow lane last ran */
|
|
504
|
+
this.lastSlowParameterPollAt = new Map();
|
|
468
505
|
this.baseURL = options.baseURL || "usiot.roborock.com";
|
|
469
506
|
|
|
470
507
|
this.userData = options.userData || null;
|
|
@@ -2042,7 +2079,7 @@ class Roborock {
|
|
|
2042
2079
|
return {};
|
|
2043
2080
|
});
|
|
2044
2081
|
|
|
2045
|
-
await this.updateHomeData(homeId);
|
|
2082
|
+
await this.updateHomeData(homeId, homedataResult);
|
|
2046
2083
|
|
|
2047
2084
|
await this.createDevices();
|
|
2048
2085
|
|
|
@@ -2061,8 +2098,14 @@ class Roborock {
|
|
|
2061
2098
|
// createDevices() so the boot poll cannot precede the wait above.
|
|
2062
2099
|
this.startB01StatusLoop();
|
|
2063
2100
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2101
|
+
// Independent, so concurrent: the network probe feeds the LAN
|
|
2102
|
+
// attach that runs later in the background, and the first status
|
|
2103
|
+
// feeds the tile. Neither needs the other, and serially they were
|
|
2104
|
+
// two full round-trip waits where one is enough.
|
|
2105
|
+
await Promise.allSettled([
|
|
2106
|
+
this.getNetworkInfo(),
|
|
2107
|
+
this.initializeDeviceUpdates(),
|
|
2108
|
+
]);
|
|
2066
2109
|
|
|
2067
2110
|
this.bInited = true;
|
|
2068
2111
|
// A success ends the retry chain and forgets the backoff, so a
|
|
@@ -2604,9 +2647,51 @@ class Roborock {
|
|
|
2604
2647
|
this.vacuums[duid].getStatusIntervall(); // actually start getStatusIntervall()
|
|
2605
2648
|
}
|
|
2606
2649
|
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2650
|
+
// THE TILE WAITS FOR NONE OF THIS, SO IT MUST NOT WAIT FOR ANY OF IT.
|
|
2651
|
+
//
|
|
2652
|
+
// `initializeDeviceUpdates` used to await the whole first parameter
|
|
2653
|
+
// cycle for every robot - 8 serial round-trips per classic robot, each
|
|
2654
|
+
// able to spend its full 10-second timeout on a robot that does not
|
|
2655
|
+
// answer locally - and only then did the caller set `bInited`, fire the
|
|
2656
|
+
// Homebridge callback, and register the Matter accessories. On a home
|
|
2657
|
+
// network that added a few seconds to startup. On a robot with a dead
|
|
2658
|
+
// local link it kept the vacuum out of Apple Home for over a minute
|
|
2659
|
+
// after every restart, for readings of consumable hours and carpet
|
|
2660
|
+
// mode that no tile displays.
|
|
2661
|
+
//
|
|
2662
|
+
// The cycle still runs once at start; it just runs behind the tiles.
|
|
2663
|
+
void this.updateDataMinimumData(
|
|
2664
|
+
duid,
|
|
2665
|
+
this.vacuums[duid],
|
|
2666
|
+
robotModel
|
|
2667
|
+
).catch((error) => {
|
|
2668
|
+
this.log.debug(
|
|
2669
|
+
`Initial parameter cycle failed for ${this.describeDevice(duid)}: ${
|
|
2670
|
+
/** @type {any} */ (error)?.message || error
|
|
2671
|
+
}`
|
|
2672
|
+
);
|
|
2673
|
+
});
|
|
2674
|
+
|
|
2675
|
+
// What the tile IS waiting for is one real status. The classic poller
|
|
2676
|
+
// is a plain interval, so its first tick used to land 15 seconds after
|
|
2677
|
+
// start and the tile showed the cloud snapshot until then; the B01 loop
|
|
2678
|
+
// already fires its first read immediately for exactly this reason.
|
|
2679
|
+
// Each robot's first status is awaited, in parallel across robots and
|
|
2680
|
+
// capped, so registration waits for fresh state but never for a robot
|
|
2681
|
+
// that will not answer.
|
|
2682
|
+
if (!this.isB01Device(duid)) {
|
|
2683
|
+
initialPolls.push(
|
|
2684
|
+
Promise.race([
|
|
2685
|
+
this.getStatus(duid),
|
|
2686
|
+
new Promise((resolve) => {
|
|
2687
|
+
const timer = setTimeout(resolve, INITIAL_STATUS_WAIT_CAP_MS);
|
|
2688
|
+
if (typeof timer?.unref === "function") {
|
|
2689
|
+
timer.unref();
|
|
2690
|
+
}
|
|
2691
|
+
}),
|
|
2692
|
+
])
|
|
2693
|
+
);
|
|
2694
|
+
}
|
|
2610
2695
|
}
|
|
2611
2696
|
|
|
2612
2697
|
await Promise.allSettled(initialPolls);
|
|
@@ -3229,6 +3314,37 @@ class Roborock {
|
|
|
3229
3314
|
* @param {boolean} isB01
|
|
3230
3315
|
* @returns {Promise<any>}
|
|
3231
3316
|
*/
|
|
3317
|
+
/**
|
|
3318
|
+
* Is it time to re-read a robot's slow-changing parameters?
|
|
3319
|
+
*
|
|
3320
|
+
* The first cycle after start always qualifies (nothing recorded yet), so a
|
|
3321
|
+
* fresh process still reads everything once - in the background, off the
|
|
3322
|
+
* startup critical path.
|
|
3323
|
+
*
|
|
3324
|
+
* @param {string} duid
|
|
3325
|
+
*/
|
|
3326
|
+
isSlowParameterPollDue(duid) {
|
|
3327
|
+
const last = this.lastSlowParameterPollAt.get(duid) ?? 0;
|
|
3328
|
+
return Date.now() - last >= SLOW_PARAMETER_POLL_INTERVAL_MS;
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
/** @param {string} duid */
|
|
3332
|
+
markSlowParameterPoll(duid) {
|
|
3333
|
+
this.lastSlowParameterPollAt.set(duid, Date.now());
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
/**
|
|
3337
|
+
* Forget a robot's slow-lane stamp so the next cycle re-reads everything.
|
|
3338
|
+
* Used when something the slow lane owns is known to have changed - a
|
|
3339
|
+
* schedule written from Apple Home, a diagnostics export asking for fresh
|
|
3340
|
+
* numbers.
|
|
3341
|
+
*
|
|
3342
|
+
* @param {string} duid
|
|
3343
|
+
*/
|
|
3344
|
+
requestSlowParameterPoll(duid) {
|
|
3345
|
+
this.lastSlowParameterPollAt.delete(duid);
|
|
3346
|
+
}
|
|
3347
|
+
|
|
3232
3348
|
async pollParameter(duid, vacuum, method, isB01) {
|
|
3233
3349
|
if (isB01 && !b01Q7Adapter.canAnswerV1Method(method)) {
|
|
3234
3350
|
const key = `${duid}:${method}`;
|
|
@@ -3491,6 +3607,13 @@ class Roborock {
|
|
|
3491
3607
|
await this.pollParameter(duid, vacuum, "get_room_mapping", isB01);
|
|
3492
3608
|
}
|
|
3493
3609
|
|
|
3610
|
+
// Everything below this line is the slow lane. See
|
|
3611
|
+
// SLOW_PARAMETER_POLL_INTERVAL_MS for the measurement behind it.
|
|
3612
|
+
if (!this.isSlowParameterPollDue(duid)) {
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
this.markSlowParameterPoll(duid);
|
|
3616
|
+
|
|
3494
3617
|
await this.pollParameter(duid, vacuum, "get_consumable", isB01);
|
|
3495
3618
|
|
|
3496
3619
|
await this.pollParameter(duid, vacuum, "get_server_timer", isB01);
|
|
@@ -3622,10 +3745,28 @@ class Roborock {
|
|
|
3622
3745
|
}
|
|
3623
3746
|
}
|
|
3624
3747
|
|
|
3625
|
-
|
|
3748
|
+
// Same shape as the B01 branch above, for the same reason: with a room
|
|
3749
|
+
// list already persisted from an earlier run, Service Area can expose it
|
|
3750
|
+
// the moment the accessory registers, and the 2 round-trips that refresh
|
|
3751
|
+
// it run behind the tile instead of in front of it. Only a robot the
|
|
3752
|
+
// plugin has never seen rooms for is worth waiting on.
|
|
3753
|
+
const refresh = async () => {
|
|
3626
3754
|
await vacuum.getParameter(duid, "get_multi_maps_list");
|
|
3627
3755
|
await vacuum.getParameter(duid, "get_room_mapping");
|
|
3628
3756
|
await this.cacheMissingMatterServiceAreaRoomMappings(duid, vacuum);
|
|
3757
|
+
};
|
|
3758
|
+
|
|
3759
|
+
if (this.getRoomMappingsForDevice(duid).length > 0) {
|
|
3760
|
+
void refresh().catch((error) => {
|
|
3761
|
+
this.log.debug(
|
|
3762
|
+
`Background room refresh failed for ${duid}: ${error.message || error}`
|
|
3763
|
+
);
|
|
3764
|
+
});
|
|
3765
|
+
return true;
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
try {
|
|
3769
|
+
await refresh();
|
|
3629
3770
|
return true;
|
|
3630
3771
|
} catch (error) {
|
|
3631
3772
|
this.log.debug(
|
|
@@ -3882,12 +4023,26 @@ class Roborock {
|
|
|
3882
4023
|
this.log.debug(`Length of message queue: ${this.messageQueue.size}`);
|
|
3883
4024
|
}
|
|
3884
4025
|
|
|
3885
|
-
|
|
4026
|
+
/**
|
|
4027
|
+
* Refresh the home snapshot - devices, products, local keys - and let every
|
|
4028
|
+
* robot's polling intervals re-check themselves against it.
|
|
4029
|
+
*
|
|
4030
|
+
* @param {string | number} homeId
|
|
4031
|
+
* @param {Record<string, any>} [prefetched] a home payload already fetched
|
|
4032
|
+
* by the caller. Startup fetched `v2/user/homes/{id}` seconds earlier,
|
|
4033
|
+
* built the device list from it, and then called this, which fetched
|
|
4034
|
+
* `user/homes/{id}` again before the Matter accessories could register:
|
|
4035
|
+
* 2 cloud round-trips for one snapshot, back to back, on the critical
|
|
4036
|
+
* path. With the payload handed in, this consumes it. The periodic
|
|
4037
|
+
* caller passes nothing and fetches fresh, as before.
|
|
4038
|
+
*/
|
|
4039
|
+
async updateHomeData(homeId, prefetched) {
|
|
3886
4040
|
this.log.debug(`Updating HomeData with homeId: ${homeId}`);
|
|
3887
4041
|
if (this.api) {
|
|
3888
4042
|
try {
|
|
3889
|
-
const
|
|
3890
|
-
|
|
4043
|
+
const homedata = prefetched
|
|
4044
|
+
? prefetched
|
|
4045
|
+
: (await this.api.get(`user/homes/${homeId}`)).data.result;
|
|
3891
4046
|
|
|
3892
4047
|
if (homedata) {
|
|
3893
4048
|
this.superviseDeviceIntervals();
|
|
@@ -4845,6 +5000,62 @@ class Roborock {
|
|
|
4845
5000
|
{ label: "sceneMethods", path: scenePath, method: "options" },
|
|
4846
5001
|
results
|
|
4847
5002
|
);
|
|
5003
|
+
|
|
5004
|
+
// And that question was answered: the resource allows DELETE and
|
|
5005
|
+
// OPTIONS. It is an answer, and not one of the two the thread's own
|
|
5006
|
+
// decision rule anticipated. The singular scene resource takes exactly
|
|
5007
|
+
// one method that changes anything, and it destroys a schedule rather
|
|
5008
|
+
// than toggling one — so there is no write here to put behind a HomeKit
|
|
5009
|
+
// switch, and a destructive verb aimed at a stranger's live account to
|
|
5010
|
+
// see what happens is not a measurement this project will take.
|
|
5011
|
+
//
|
|
5012
|
+
// That rules out the resource, not the feature. Our own `executeScene`
|
|
5013
|
+
// reaches `user/scene/{id}/execute`, so sub-resources under a scene id
|
|
5014
|
+
// demonstrably exist and carry verbs the scene itself does not. Whether
|
|
5015
|
+
// one of them is the toggle is the last question this instrument can
|
|
5016
|
+
// ask, and it is asked WITH CONTROLS, because on its own an `Allow`
|
|
5017
|
+
// header proves nothing:
|
|
5018
|
+
//
|
|
5019
|
+
// - `user/scene` — the collection, where a REST API most often keeps
|
|
5020
|
+
// an update;
|
|
5021
|
+
// - `user/scene/{id}/enable` — the literal candidate for the nested
|
|
5022
|
+
// flag the reporter's app flips;
|
|
5023
|
+
// - `user/scene/device/{duid}` — the POSITIVE control. This run has
|
|
5024
|
+
// ALREADY read it successfully, so it is mapped beyond doubt. If
|
|
5025
|
+
// OPTIONS cannot describe even that, the instrument does not see
|
|
5026
|
+
// routes and every other answer in this set is noise. It is also
|
|
5027
|
+
// why the control is this route and not `/execute`: a control must
|
|
5028
|
+
// not be a path whose real verb runs a robot.
|
|
5029
|
+
// - a path that cannot exist — the NEGATIVE control. If THAT comes
|
|
5030
|
+
// back with an Allow header, the server answers everything and no
|
|
5031
|
+
// positive here means anything.
|
|
5032
|
+
//
|
|
5033
|
+
// Together they bound the search rather than extend it: this round
|
|
5034
|
+
// either names a route or shows that no later round would.
|
|
5035
|
+
//
|
|
5036
|
+
// Asking costs nothing that could alter a schedule, and that is
|
|
5037
|
+
// measured rather than assumed: the OPTIONS of the scene resource came
|
|
5038
|
+
// back with an EMPTY body and an Allow header, which is a servlet
|
|
5039
|
+
// container answering the request itself instead of handing it to the
|
|
5040
|
+
// handler behind the path.
|
|
5041
|
+
for (const candidate of [
|
|
5042
|
+
{ label: "sceneCollectionMethods", path: "user/scene" },
|
|
5043
|
+
{ label: "sceneEnableMethods", path: `${scenePath}/enable` },
|
|
5044
|
+
{
|
|
5045
|
+
label: "mappedRouteControl",
|
|
5046
|
+
path: `user/scene/device/${duid}`,
|
|
5047
|
+
},
|
|
5048
|
+
{
|
|
5049
|
+
label: "absentRouteControl",
|
|
5050
|
+
path: `${scenePath}/${ABSENT_ROUTE_PROBE_SEGMENT}`,
|
|
5051
|
+
},
|
|
5052
|
+
]) {
|
|
5053
|
+
await this.probeOneCloudScheduleRoute(
|
|
5054
|
+
duid,
|
|
5055
|
+
{ ...candidate, method: "options" },
|
|
5056
|
+
results
|
|
5057
|
+
);
|
|
5058
|
+
}
|
|
4848
5059
|
}
|
|
4849
5060
|
|
|
4850
5061
|
await this.updateRoborockDiagnostics(
|