homebridge-roborock-matter 2.9.9 → 3.0.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 +12 -0
- package/README.md +6 -3
- package/docs/matter-battery-issue-draft.md +37 -42
- package/package.json +6 -6
- package/roborockLib/lib/vacuum.js +37 -8
- package/roborockLib/roborockAPI.js +50 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
Startup and refresh pass: Homebridge restarts are noticeably faster, and a status refresh that had never actually run now does. Nothing here requires re-pairing — existing setups keep working exactly as they are.
|
|
6
|
+
|
|
7
|
+
- **Restarts are roughly 5 seconds faster.** LAN discovery listens for robot broadcasts for a fixed 5-second window, and startup used to sit and wait for it with nothing else happening. It now runs alongside the home-data refresh, device setup and network probes, which all fit inside that same window. Measured on a three-robot setup: ~8 s from "Starting adapter" to "Lets go!!!!!!!" before, ~3 s after.
|
|
8
|
+
- **Multi-robot startup no longer costs extra time.** Each robot's first status read and network probe used to run one after another; they now run at once, so three robots start as quickly as one. New tests pin the concurrency down so it cannot silently regress.
|
|
9
|
+
- **A dead status refresh has been repaired.** The periodic `get_status` refresh for classic (S/Q-series) robots was gated on a config key this plugin never sets, which made the condition permanently false — the refresh promised by the code has never run in any released version. It now polls each robot at most once a minute (forced refreshes are unaffected), so a dropped MQTT push self-corrects within a minute instead of waiting up to three for the slow full poll.
|
|
10
|
+
- **~86,000 needless timer wake-ups per robot per day removed.** With the refresh properly throttled, the 1-second scheduler tick that served it is now 15 seconds — same refresh rate, a fraction of the idle CPU on Raspberry Pi class hardware.
|
|
11
|
+
- One request removed from every startup: a scene list was fetched from the Roborock cloud and thrown away.
|
|
12
|
+
- **Security:** a newly published high-severity advisory in a transitive dependency (`ip-address`, reached through the MQTT client) is resolved, and the build toolchain was refreshed. `npm audit` reports zero vulnerabilities for both the shipped package and the development tree.
|
|
13
|
+
- Full suite: 279 passing (7 new startup/refresh tests).
|
|
14
|
+
|
|
3
15
|
## 2.9.9
|
|
4
16
|
|
|
5
17
|
- **Cleans started outside Apple Home now show the right clean mode.** Starting a vacuum+mop (or mop-only) clean from the Roborock app or the robot's buttons left Apple Home claiming plain "Vacuum". The Q7 series reports its active clean type in every status poll (the plugin sent it on start but never read it back); classic S/Q robots are derived from the mop-only suction signature and the active water-flow setting. Apple Home's mode picker now follows the robot live during a run — no re-pairing needed.
|
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. 279 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
|
|
|
@@ -92,7 +92,10 @@ The clean mode follows the robot as well: start a vacuum+mop or mop-only clean f
|
|
|
92
92
|
**The entire Roborock lineup.** If it runs in the Roborock app, this plugin can control it:
|
|
93
93
|
|
|
94
94
|
- **2025 Q7 series** (`roborock.vacuum.sc05`, Q7 M5 / M5+) — the only Homebridge plugin that supports these at all, including manual-tank mopping with vacuum/mop mode switching.
|
|
95
|
-
- **Classic S-, Q- and Saros-series** — S5 through S8 Pro Ultra, Q5/Q7/Q8/Q Revo families, Saros, and newer.
|
|
95
|
+
- **Classic S-, Q- and Saros-series** — S4 / S5 Max through S8 Pro Ultra, Q5/Q7/Q8/Q Revo families, Saros, and newer.
|
|
96
|
+
|
|
97
|
+
> **Heads-up for early models:** a few legacy robots — most notably the original S5 — only work with Xiaomi's Mi Home app and can never be added to a Roborock account, so no Roborock-account plugin can reach them. For those, [homebridge-xiaomi-roborock-vacuum](https://github.com/homebridge-xiaomi-roborock-vacuum/homebridge-xiaomi-roborock-vacuum) is the right tool.
|
|
98
|
+
|
|
96
99
|
- **Future models** are adopted automatically: the plugin reads what each robot says it can do and adapts, so brand-new releases get sensible defaults from day one. If something looks off, [open a model report](https://github.com/mathiashornbek/homebridge-roborock-matter/issues) with a diagnostics export — that's exactly what it's for.
|
|
97
100
|
|
|
98
101
|
## Configuration
|
|
@@ -113,7 +116,7 @@ Everything is configurable from the Homebridge UI. The essentials:
|
|
|
113
116
|
|
|
114
117
|
## Battery percentage in Apple Home
|
|
115
118
|
|
|
116
|
-
Apple Home renders the battery percentage from pairing time and refreshes it only on a fresh read (commissioning, hub restart) — while charging state on the very same cluster updates live. This is
|
|
119
|
+
Apple Home renders the battery percentage from pairing time and refreshes it only on a fresh read (commissioning, hub restart) — while charging state on the very same cluster updates live. This is not a plugin bug, and the root cause is now **confirmed in the source of matter.js** (the Matter stack Homebridge uses): the percentage attribute carries the spec's "changes omitted" quality, and matter.js currently never emits subscription reports for such attributes — while Apple Home never re-reads them on its own. The fix is tracked upstream in [matter-js/matter.js#4163](https://github.com/matter-js/matter.js/issues/4163) (an opt-in to report them anyway, which the spec permits); once it lands, Homebridge can enable it for bridged accessories and every plugin gets working battery percentages at once. Full investigation: [homebridge#3958](https://github.com/homebridge/homebridge/issues/3958).
|
|
117
120
|
|
|
118
121
|
<details>
|
|
119
122
|
<summary>The full evidence chain and workarounds</summary>
|
|
@@ -13,45 +13,40 @@ updates continuously; the matter.js store verifiably carries the live value;
|
|
|
13
13
|
but the rendered battery **percentage** stays at its commissioning-time value
|
|
14
14
|
until a fresh read (re-pair or Matter hub restart).
|
|
15
15
|
|
|
16
|
-
##
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
the live values.
|
|
54
|
-
|
|
55
|
-
If both confirm reports going out, the permanent fix belongs with Apple
|
|
56
|
-
(Apple Feedback report about the controller's handling of Q-quality
|
|
57
|
-
PowerSource attributes). The upstream issue stays open in the meantime.
|
|
16
|
+
## Root cause (confirmed in the matter.js source by the Homebridge maintainer, July 2026)
|
|
17
|
+
|
|
18
|
+
`ServerBehaviorBacking#configureEventSuppression()` collects every
|
|
19
|
+
changes-omitted property into a suppressed set; only properties that are ALSO
|
|
20
|
+
marked `quieter` get the observer that re-broadcasts them
|
|
21
|
+
(`broadcastChanges([name])`). `batPercentRemaining` is changes-omitted without
|
|
22
|
+
`quieter`, so it hits the `continue` and **no subscription report is ever
|
|
23
|
+
produced** — the store stays fresh and reads serve the live value, which is
|
|
24
|
+
exactly what this investigation's store dumps showed. `batChargeState`
|
|
25
|
+
carries no C quality, which is why it updates live on the same cluster.
|
|
26
|
+
|
|
27
|
+
Ruled out along the way:
|
|
28
|
+
|
|
29
|
+
- An intermediate theory (Matter 1.4 Q-quality, reports leaving the bridge)
|
|
30
|
+
did not survive the maintainer's source check.
|
|
31
|
+
- The freeze reproduces on Homebridge v2.2.2-beta.12 in a **restart-free**
|
|
32
|
+
window, ruling out the dead-subscription bug fixed by homebridge#3973.
|
|
33
|
+
- matter.js 0.17.7 does not change the behavior (`ServerBehaviorBacking.js`
|
|
34
|
+
is byte-identical to what Homebridge ships).
|
|
35
|
+
- No viable device-side nudge exists from the Homebridge layer:
|
|
36
|
+
`broadcastChanges` is `protected`, and private-internals access or patching
|
|
37
|
+
would break silently on a matter.js update.
|
|
38
|
+
|
|
39
|
+
## The fix
|
|
40
|
+
|
|
41
|
+
The Matter spec says a server _may_ omit changes for C attributes — reporting
|
|
42
|
+
them anyway is permitted. The clean solution is an opt-in on the matter.js
|
|
43
|
+
side, raised upstream by the Homebridge maintainer as
|
|
44
|
+
[matter-js/matter.js#4163](https://github.com/matter-js/matter.js/issues/4163)
|
|
45
|
+
with this investigation linked as evidence. Once it lands, Homebridge wires it
|
|
46
|
+
up for bridged accessories and every plugin gets working battery percentages
|
|
47
|
+
at once — no plugin-side change needed.
|
|
48
|
+
|
|
49
|
+
Until then: homebridge#3958 stays open to track; the plugin keeps its
|
|
50
|
+
boot-time battery resync (useful for controllers that re-prime their
|
|
51
|
+
subscriptions after a hub restart), and the known refresh paths remain
|
|
52
|
+
restarting the Matter hub or re-pairing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-roborock-matter",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": {
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"qrcode": "^1.5.4"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@babel/core": "7.
|
|
64
|
-
"@babel/preset-env": "7.
|
|
65
|
-
"@babel/preset-typescript": "7.
|
|
63
|
+
"@babel/core": "7.29.7",
|
|
64
|
+
"@babel/preset-env": "7.29.7",
|
|
65
|
+
"@babel/preset-typescript": "7.29.7",
|
|
66
66
|
"@types/jest": "30.0.0",
|
|
67
67
|
"@types/node": "20.14.10",
|
|
68
68
|
"@types/qrcode": "^1.5.6",
|
|
69
69
|
"@types/semver": "7.5.8",
|
|
70
|
-
"babel-jest": "30.
|
|
70
|
+
"babel-jest": "30.4.1",
|
|
71
71
|
"homebridge": "1.8.3",
|
|
72
|
-
"jest": "30.2
|
|
72
|
+
"jest": "30.4.2",
|
|
73
73
|
"prettier": "3.3.2",
|
|
74
74
|
"rimraf": "6.0.1",
|
|
75
75
|
"typescript": "5.5.3"
|
|
@@ -5,6 +5,11 @@ const RRMapParser = require("./RRMapParser");
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const zlib = require("zlib");
|
|
7
7
|
|
|
8
|
+
// Minimum spacing between periodic (non-forced) get_status polls per robot.
|
|
9
|
+
// MQTT push remains the primary live channel; this is the safety net that
|
|
10
|
+
// catches a dropped push long before the 3-minute full refresh would.
|
|
11
|
+
const STATUS_POLL_MIN_INTERVAL_MS = 60 * 1000;
|
|
12
|
+
|
|
8
13
|
const mappedCleanSummary = {
|
|
9
14
|
0: "clean_time",
|
|
10
15
|
1: "clean_area",
|
|
@@ -47,6 +52,25 @@ class vacuum {
|
|
|
47
52
|
get_carpet_clean_mode: "deviceStatus",
|
|
48
53
|
get_carpet_cleaning_mode: "deviceStatus",
|
|
49
54
|
};
|
|
55
|
+
|
|
56
|
+
/** @type {Map<string, number>} last periodic status poll, per duid */
|
|
57
|
+
this.lastStatusPollAt = new Map();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* True when this robot's periodic status poll is due again.
|
|
62
|
+
* @param {string} duid
|
|
63
|
+
*/
|
|
64
|
+
shouldPollStatusNow(duid) {
|
|
65
|
+
const last = this.lastStatusPollAt.get(duid);
|
|
66
|
+
return (
|
|
67
|
+
last === undefined || Date.now() - last >= STATUS_POLL_MIN_INTERVAL_MS
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** @param {string} duid */
|
|
72
|
+
markStatusPolled(duid) {
|
|
73
|
+
this.lastStatusPollAt.set(duid, Date.now());
|
|
50
74
|
}
|
|
51
75
|
|
|
52
76
|
async updateDiagnosticSnapshot(duid, key, payload) {
|
|
@@ -332,16 +356,21 @@ class vacuum {
|
|
|
332
356
|
}
|
|
333
357
|
}
|
|
334
358
|
} else if (parameter == "get_status") {
|
|
335
|
-
const now = new Date();
|
|
336
|
-
const seconds = now.getSeconds();
|
|
337
359
|
const force = attribute == "force";
|
|
338
360
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
361
|
+
// Periodic status refresh, throttled per robot.
|
|
362
|
+
//
|
|
363
|
+
// The inherited gate here read `config.updateInterval` (a key this
|
|
364
|
+
// plugin never sets) and `adapter.socket` (permanently null), so the
|
|
365
|
+
// expression was `NaN == 0` — always false. The refresh the comment
|
|
366
|
+
// promised has therefore never run: classic robots relied entirely on
|
|
367
|
+
// MQTT push plus the slow 3-minute full poll, and a silently dropped
|
|
368
|
+
// push left Apple Home stale for minutes. An explicit elapsed-time
|
|
369
|
+
// throttle restores the safety net, and being relative rather than
|
|
370
|
+
// aligned to wall-clock seconds also stops every robot in a fleet
|
|
371
|
+
// from polling in the same instant.
|
|
372
|
+
if (force || this.shouldPollStatusNow(duid)) {
|
|
373
|
+
this.markStatusPolled(duid);
|
|
345
374
|
|
|
346
375
|
// const deviceStatus = await this.adapter.messageQueueHandler.sendRequest(duid, "get_status", []);
|
|
347
376
|
const requestOptions = options.preferCloud
|
|
@@ -42,6 +42,12 @@ 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
|
+
// Scheduler granularity for the classic (v1-protocol) status refresh. The
|
|
46
|
+
// refresh itself is throttled per robot inside vacuum.getParameter, so this
|
|
47
|
+
// only decides how promptly that window is noticed — a 1-second tick meant
|
|
48
|
+
// ~86k wake-ups per robot per day to serve at most 1440 polls.
|
|
49
|
+
const CLASSIC_STATUS_TICK_MS = 15000;
|
|
50
|
+
|
|
45
51
|
// Persisted states whose disk flush is debounced (see setStateAsync): they
|
|
46
52
|
// change on every received robot message, are served from memory, and only
|
|
47
53
|
// need the on-disk copy for restart survival.
|
|
@@ -1404,8 +1410,6 @@ class Roborock {
|
|
|
1404
1410
|
const homedata = await this.api.get(`v2/user/homes/${homeId}`);
|
|
1405
1411
|
const homedataResult = homedata.data.result;
|
|
1406
1412
|
|
|
1407
|
-
const scene = await this.api.get(`user/scene/home/${homeId}`);
|
|
1408
|
-
|
|
1409
1413
|
await this.setStateAsync("HomeData", {
|
|
1410
1414
|
val: JSON.stringify(homedataResult),
|
|
1411
1415
|
ack: true,
|
|
@@ -1482,15 +1486,29 @@ class Roborock {
|
|
|
1482
1486
|
this.updateInterval * 1000,
|
|
1483
1487
|
homeId
|
|
1484
1488
|
);
|
|
1485
|
-
await this.updateHomeData(homeId);
|
|
1486
1489
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
+
// LAN discovery listens for UDP broadcasts for a fixed window, so
|
|
1491
|
+
// awaiting it here used to stall startup for the full timeout with
|
|
1492
|
+
// the CPU idle. Its results are only needed at the merge below, and
|
|
1493
|
+
// the local keys it matches against are already loaded, so start it
|
|
1494
|
+
// now and let the home-data refresh, device creation and network
|
|
1495
|
+
// probes run inside that window instead.
|
|
1496
|
+
const localDiscovery = this.isCloudOnlyModeEnabled()
|
|
1497
|
+
? Promise.resolve({})
|
|
1498
|
+
: this.localConnector.getLocalDevices().catch((error) => {
|
|
1499
|
+
this.log.debug(
|
|
1500
|
+
`LAN discovery failed; continuing with cloud transport: ${error?.message || error}`
|
|
1501
|
+
);
|
|
1502
|
+
return {};
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
await this.updateHomeData(homeId);
|
|
1490
1506
|
|
|
1491
1507
|
await this.createDevices();
|
|
1492
1508
|
await this.getNetworkInfo();
|
|
1493
1509
|
|
|
1510
|
+
const discoveredDevices = await localDiscovery;
|
|
1511
|
+
|
|
1494
1512
|
if (!this.isCloudOnlyModeEnabled()) {
|
|
1495
1513
|
// merge udp discovered devices with local devices found via mqtt
|
|
1496
1514
|
Object.entries(discoveredDevices).forEach(([duid, ip]) => {
|
|
@@ -1763,14 +1781,20 @@ class Roborock {
|
|
|
1763
1781
|
}
|
|
1764
1782
|
|
|
1765
1783
|
async getNetworkInfo() {
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1784
|
+
// One round-trip per robot, all independent: probing them in parallel
|
|
1785
|
+
// turns N sequential cloud/LAN waits into one at startup. Failures are
|
|
1786
|
+
// already handled inside getParameter, but allSettled keeps a rejection
|
|
1787
|
+
// from one robot from skipping the others.
|
|
1788
|
+
await Promise.allSettled(
|
|
1789
|
+
this.devices
|
|
1790
|
+
.filter((device) => this.hasInitializedVacuum(device.duid))
|
|
1791
|
+
.map((device) =>
|
|
1792
|
+
this.vacuums[device.duid].getParameter(
|
|
1793
|
+
device.duid,
|
|
1794
|
+
"get_network_info"
|
|
1795
|
+
)
|
|
1796
|
+
)
|
|
1797
|
+
);
|
|
1774
1798
|
}
|
|
1775
1799
|
|
|
1776
1800
|
async createDevices() {
|
|
@@ -1830,6 +1854,11 @@ class Roborock {
|
|
|
1830
1854
|
this.log.debug(`initializeDeviceUpdates`);
|
|
1831
1855
|
|
|
1832
1856
|
const devices = this.devices;
|
|
1857
|
+
// Each robot's first poll is a chain of round-trips to that robot alone.
|
|
1858
|
+
// Running the chains for different robots concurrently keeps a
|
|
1859
|
+
// multi-robot startup as fast as a single-robot one; the timers below are
|
|
1860
|
+
// still wired up in order, only the initial reads overlap.
|
|
1861
|
+
const initialPolls = [];
|
|
1833
1862
|
|
|
1834
1863
|
for (const device of devices) {
|
|
1835
1864
|
const duid = device.duid;
|
|
@@ -1863,7 +1892,7 @@ class Roborock {
|
|
|
1863
1892
|
|
|
1864
1893
|
this.vacuums[duid].getStatusIntervall = () => {
|
|
1865
1894
|
// B01/Q7 status is owned by the dedicated 15s loop; the per-device
|
|
1866
|
-
//
|
|
1895
|
+
// tick would only burn cycles hitting the attempt throttle.
|
|
1867
1896
|
if (
|
|
1868
1897
|
this.getVacuumDeviceInfo(duid, "pv") ===
|
|
1869
1898
|
b01Q7Adapter.B01_PROTOCOL_VERSION
|
|
@@ -1873,7 +1902,7 @@ class Roborock {
|
|
|
1873
1902
|
this.clearInterval(this.vacuums[duid].getStatusIntervalHandle);
|
|
1874
1903
|
this.vacuums[duid].getStatusIntervalHandle = this.setInterval(
|
|
1875
1904
|
this.getStatus.bind(this),
|
|
1876
|
-
|
|
1905
|
+
CLASSIC_STATUS_TICK_MS,
|
|
1877
1906
|
duid,
|
|
1878
1907
|
this.vacuums[duid],
|
|
1879
1908
|
robotModel
|
|
@@ -1886,8 +1915,12 @@ class Roborock {
|
|
|
1886
1915
|
this.vacuums[duid].getStatusIntervall(); // actually start getStatusIntervall()
|
|
1887
1916
|
}
|
|
1888
1917
|
|
|
1889
|
-
|
|
1918
|
+
initialPolls.push(
|
|
1919
|
+
this.updateDataMinimumData(duid, this.vacuums[duid], robotModel)
|
|
1920
|
+
);
|
|
1890
1921
|
}
|
|
1922
|
+
|
|
1923
|
+
await Promise.allSettled(initialPolls);
|
|
1891
1924
|
}
|
|
1892
1925
|
|
|
1893
1926
|
async executeScene(sceneID) {
|
|
@@ -1921,17 +1954,6 @@ class Roborock {
|
|
|
1921
1954
|
}
|
|
1922
1955
|
}
|
|
1923
1956
|
|
|
1924
|
-
/**
|
|
1925
|
-
* Get scenes from the Roborock API
|
|
1926
|
-
* @returns {Promise<Object>} The scenes data
|
|
1927
|
-
*/
|
|
1928
|
-
|
|
1929
|
-
/**
|
|
1930
|
-
* Get scenes for a specific device by duid
|
|
1931
|
-
* @param {string} duid - The device unique identifier
|
|
1932
|
-
* @returns {Array} Array of scenes for the specified device
|
|
1933
|
-
*/
|
|
1934
|
-
|
|
1935
1957
|
getProductAttribute(duid, attribute) {
|
|
1936
1958
|
const device = this.getVacuumDeviceData(duid);
|
|
1937
1959
|
const deviceValue = this.getDeviceAttribute(device, attribute);
|