homebridge-roborock-matter 2.4.2 → 2.6.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,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.6.0
4
+
5
+ - **New: opt-in suction-level cleaning modes.** With `enableFanPowerCleanModes` (default off), the Matter cleaning mode list gains **Quiet / Balanced / Turbo / Max Vacuum** variants with proper Matter mode tags (Vacuum + Quiet/Max), so suction can be chosen directly from Apple Home's mode picker. Selecting a variant pins the robot's fan power (v1 codes 101-104; the B01/Q7 adapter translates to wind levels 1-4) while behaving as a vacuum-family mode everywhere else (water box handling, mop rules). Off by default because Matter fixes an accessory's mode list at commissioning: toggling the option requires removing and re-pairing the robot once — this ships as a deliberate opt-in rather than a forced re-pair for everyone.
6
+ - **README rebuilt from scratch** around what makes the plugin unique (2025 B01/Q7 support, live room tracking, Matter-only design), with a feature matrix, configuration reference, honest limitation notes, and the plugin icon.
7
+ - Full suite: 247 passing (6 new clean-mode tests). No changes to default behavior anywhere.
8
+
9
+ ## 2.5.0
10
+
11
+ Supply-chain, robustness and capability-detection release. Every Socket.dev alert with a code-level source is eliminated at the source, and the plugin now adapts itself to unknown robot models instead of guessing silently.
12
+
13
+ - **Custom UI server moved to native ESM loading — no more dynamic code evaluation.** The `homebridge-ui` directory is now marked `"type": "module"`, so `server.js` imports the pure-ESM `@homebridge/plugin-ui-utils` natively and instantiates the exported (side-effect-free) server class from the compiled output. The `new Function("return import(...)")` interop shim is gone, and with it the Socket.dev "uses eval" alert.
14
+ - **Removed the dead ioBroker-era package/image downloader** (`roborockPackageHelper`) and its `jszip` dependency (12 packages out of the tree). The helper was never called by this fork, wrote to relative paths, and was the source of Socket.dev's AI-detected ZIP-slip/path-traversal alert. Deleting it removes the entire alert surface rather than patching around it.
15
+ - **Self-healing capability detection.** Any periodic poll request a robot definitively answers with an unsupported-method error is now remembered per device and skipped until the next restart (firmware updates get a fresh probe) — exotic and brand-new models stop generating repeated warnings for requests they will never answer. Timeouts and transport errors never count as unsupported.
16
+ - **Capability-derived poll profiles for unknown models.** Models without a dedicated poll profile (e.g. newly released Saros 10 / Q5 Max+ / QX Revo Plus-class devices) now derive their polls from the robot's own capability bitmask where available (carpet support), announce the chosen profile once in the log, and point to the model-report issue template. Known models keep their verified profiles unchanged.
17
+ - **Clearer model lookup mismatch logs:** a device whose HomeData model string does not look like a Roborock vacuum now logs exactly what was reported and how to file a useful report, instead of a generic "unsupported model" line.
18
+ - **Leaner npm package:** the mitmproxy sniffing script, the ioBroker map viewer, test files, and editor metadata no longer ship in the tarball.
19
+ - ROADMAP refreshed against live upstream status: applemanj#12 (pause/dock) confirmed fixed and closed upstream; applemanj#4 (S8 local timeouts) still awaiting reporter retest; homebridge#3951 stable with no recurrence since June. The legacy "HomeKit scene/room controls" item is superseded by the Matter-only design.
20
+ - Full suite: 241 passing (6 new capability-detection tests). Verified end to end under Homebridge 1.8.3 and 2.1.2-beta.3, including the plugin-verification harness's crash scenarios (invalid credentials, unreachable cloud).
21
+
3
22
  ## 2.4.2
4
23
 
5
24
  Robustness and supply-chain release (Homebridge verification runtime checks + Socket.dev scan).
package/README.md CHANGED
@@ -1,68 +1,121 @@
1
- # homebridge-roborock-matter
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/mathiashornbek/homebridge-roborock-matter/main/assets/icon.png" width="140" alt="homebridge-roborock-matter icon">
3
+ </p>
2
4
 
3
- A **Matter-only** Homebridge plugin that publishes your Roborock robot vacuums — including the 2025 **B01/Q7-series** (`roborock.vacuum.sc05`, Q7 M5 / M5+) — as native **Matter** accessories for Apple Home.
5
+ <h1 align="center">homebridge-roborock-matter</h1>
4
6
 
5
- > This is a fork of [`homebridge-roborock-vacuum2`](https://github.com/applemanj/homebridge-roborock-vacuum2) by Joshua Appleman, itself adapted from [ioBroker.roborock](https://github.com/copystring/ioBroker.roborock) by copystring. See [Attribution](#attribution) and [LICENSE](./LICENSE). All original copyright is preserved.
7
+ <p align="center">
8
+ <b>Your Roborock vacuum as a native Matter robot in Apple Home — with live "cleaning in the kitchen" room tracking.</b>
9
+ </p>
6
10
 
7
- ## What makes this fork different
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/homebridge-roborock-matter"><img src="https://img.shields.io/npm/v/homebridge-roborock-matter?label=npm&color=cb3837" alt="npm version"></a>
13
+ <a href="https://www.npmjs.com/package/homebridge-roborock-matter"><img src="https://img.shields.io/npm/dt/homebridge-roborock-matter?label=downloads&color=8a5cf5" alt="npm downloads"></a>
14
+ <a href="https://github.com/mathiashornbek/homebridge-roborock-matter/actions"><img src="https://img.shields.io/github/actions/workflow/status/mathiashornbek/homebridge-roborock-matter/nodejs.yml?label=CI" alt="CI status"></a>
15
+ <img src="https://img.shields.io/badge/node-22%20%7C%2024-brightgreen" alt="Node 22/24">
16
+ <img src="https://img.shields.io/badge/homebridge-1.11%20%7C%202.x-purple" alt="Homebridge 1.11/2.x">
17
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
18
+ </p>
8
19
 
9
- - **Matter-only.** All HomeKit (HAP) accessories — the fan tile, helper switches, scene and schedule switches — have been removed. Each robot appears exactly once in Apple Home as a native Matter robot vacuum. Legacy HomeKit accessories are unregistered automatically on first start.
10
- - **B01/Q7-series protocol support.** The 2025 Q-series robots speak a different RPC dialect that upstream does not implement. This fork adds a full B01 adapter — commands (start/stop/pause/dock/locate/segment cleaning), status, battery, charging state, mop/vacuum mode switching, and room selection via the encrypted B01 map channel — implemented against the [python-roborock](https://github.com/Python-roborock/python-roborock) reference and verified with fixture-driven tests.
11
- - **Live room tracking (B01/Q7).** While the robot is actively cleaning, the plugin periodically reads the robot's position from the encrypted map channel, resolves which room outline contains it, and publishes that room as the current Matter Service Area — so Apple Home can show "cleaning in \<room\>" with the room the robot is physically inside, the way the vendor app does. Room progress stays honest: only rooms the robot was actually detected in are marked completed when it moves on. Opt out with the **Enable Live Room Tracking** setting.
12
- - **Robustness hardening.** Startup guards, self-healing status polling with a dedicated B01 loop, per-cluster Matter publish isolation, interval-lifecycle fixes, and a light, accessible settings UI with per-device enable/disable.
20
+ ---
13
21
 
14
- ## Battery percentage in Apple Home known controller-side limitation
22
+ Log in with your **Roborock app account** — no token extraction, no rooted apps, no packet sniffing — and every robot appears in Apple Home as a first-class **Matter Robotic Vacuum Cleaner**: start, pause, dock, pick rooms, choose cleaning modes, and watch the status pill name the room the robot is _actually inside_, live.
15
23
 
16
- The Matter PowerSource attribute `batPercentRemaining` carries the spec
17
- reporting quality **"changes omitted"**: value changes are not pushed to
18
- subscribed controllers, by design. matter.js implements this faithfully on
19
- the device side, and its own controller documents the consequence ("Always
20
- read attributes that do not report changes via subscriptions"). Apple Home
21
- performs no such re-reads, so the vacuum tile's battery percentage freezes
22
- at whatever it was when the accessory was paired — while the charging state
23
- on the very same cluster updates live.
24
+ ## Why this plugin
24
25
 
25
- This was verified end-to-end in the field: the plugin's publishes, the
26
- Homebridge API, and the persisted matter.js store all carry the live value
27
- in real time while Apple keeps rendering the pairing-day percentage. The
28
- plugin performs a one-time battery resync per boot so controllers that
29
- re-prime their subscriptions pick up a fresh value; beyond that, no
30
- device-side write can force a changes-omitted attribute to report. Known
31
- refresh paths today: re-establishing the controller subscription (Matter
32
- hub restart) and re-pairing. The permanent fix belongs in the controller
33
- ecosystem — `docs/matter-battery-issue-draft.md` contains a ready-to-file
34
- upstream report with the complete evidence chain.
26
+ - 🗣️ **The only plugin that speaks 2025 Roborock.** The B01/Q7-series (`roborock.vacuum.sc05`, Q7 M5 / M5+) exists solely in the Roborock app ecosystem — a new RPC dialect with an encrypted protobuf map channel that miio-based plugins cannot talk to at all. Fully implemented here: commands, status, battery, suction levels, room cleaning, and the map channel.
27
+ - 📍 **Live room tracking.** While the robot works, its position is read from the encrypted map, matched against your room outlines, and published as the current Matter Service Area. Apple Home shows _"Cleaning — Kitchen"_ — including runs started from the robot's button or the Roborock app. No other Homebridge plugin does this.
28
+ - 🧭 **Matter-only, by design.** No legacy fan tiles, no helper-switch clutter. One robot, one native accessory, on Homebridge 2's built-in Matter bridge — including room/map selection sourced from your Roborock account's named rooms.
29
+ - 🔌 **Cloud + local, automatically.** Commands prefer a direct local TCP connection to the robot and fall back to the Roborock cloud transparently, with per-device connection diagnostics in the settings UI when you want to see exactly what happened.
30
+ - 🛡️ **Hardened and boring where it counts.** 247 automated tests, CI on Node 22/24 against Homebridge 1.11 and 2.x, zero known vulnerabilities, no analytics, no post-install scripts, and a startup that retries with backoff instead of ever crash-looping Homebridge — verified against the Homebridge plugin-verification harness.
35
31
 
36
- ## Requirements
32
+ ## Features
37
33
 
38
- - Homebridge 2 with Matter enabled on the Roborock child/daughter bridge.
39
- - A Matter controller (a HomePod or Apple TV acting as a home hub) to add the accessories to Apple Home.
34
+ | | |
35
+ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
36
+ | 🤖 **Native Matter RVC** | Start / stop / pause / return-to-dock, run modes, operational state, error reporting |
37
+ | 🚪 **Room cleaning from Apple Home** | Matter Service Area selection with your real room names, multi-map homes included |
38
+ | 📍 **Live room tracking** | The room the robot is physically inside, updated every ~20 s while cleaning ([details](#live-room-tracking)) |
39
+ | 📊 **Honest cleaning progress** | Per-room pending → operating → completed, only claiming rooms the robot was actually detected in |
40
+ | 🌀 **Cleaning modes** | Vacuum / Mop / Vacuum + Mop, capability-gated per robot — plus optional Quiet / Balanced / Turbo / Max suction modes |
41
+ | 🔋 **Battery & charging** | Live percentage and charge state via Matter PowerSource ([one Apple-side caveat](#battery-percentage-in-apple-home)) |
42
+ | 🧠 **Self-adapting model support** | Unknown models get capability-derived polling; requests a robot reports as unsupported are disabled automatically |
43
+ | 🩺 **Built-in diagnostics** | Connection state, transport history, live LAN probe, and a redacted report generator for bug reports |
44
+ | 🔐 **2FA-friendly login** | Roborock account two-factor authentication handled entirely in the settings UI |
45
+
46
+ ## Quick start
47
+
48
+ 1. Install through the Homebridge UI (search for **`homebridge-roborock-matter`**) or:
49
+
50
+ ```bash
51
+ npm install -g homebridge-roborock-matter
52
+ ```
53
+
54
+ 2. Open the plugin settings, sign in with your **Roborock app account** (2FA supported), and pick which robots to manage.
55
+ 3. Enable **Matter** for the plugin's child bridge, restart Homebridge, and add each robot to Apple Home with the pairing code from the **Matter Pairing** section of the settings.
56
+
57
+ For B01/Q7 robots, room selection appears once the map has been fetched (watch for a `B01 rooms for ...` log line). Robots paired _before_ rooms were available need one remove/re-pair in Apple Home — Matter fixes an accessory's capabilities at commissioning time.
58
+
59
+ ## Live room tracking
60
+
61
+ While a robot is actively cleaning, the plugin fetches its live position from Roborock's encrypted SCMap channel (throttled to ~20 s, active runs only, nothing while docked), ray-casts it against the per-room boundary outlines, and publishes the result as the Matter Service Area `currentArea`:
62
+
63
+ - Apple Home's status pill names the room the robot is **physically inside** — the way the vendor app does it.
64
+ - Works for full-home cleans and for runs started from the robot's button or the Roborock app, which previously had no room to show at all.
65
+ - Progress stays honest: a room is only marked _completed_ once the robot was actually detected inside it and has moved on. The plugin never invents data the robot didn't report.
66
+
67
+ Enabled by default; opt out with `enableLiveRoomTracking: false`.
40
68
 
41
69
  ## Supported robots
42
70
 
43
- - Classic protocol Roborock vacuums supported by the upstream plugin (published as Matter).
44
- - **B01/Q7-series** (`roborock.vacuum.sc05` and compatible), including manual-tank mopping (vacuum/mop mode switch; no water-level status, by design).
71
+ - **B01/Q7-series (2025):** `roborock.vacuum.sc05` and compatible (Q7 M5 / M5+), including manual-tank mopping with vacuum/mop mode switching.
72
+ - **Classic app-account Roborock vacuums** supported by the upstream lineage (S-series, Q-series, Saros S5 through S8 Pro Ultra, Q5/Q7/Q8/Q Revo families and newer), published as Matter accessories.
73
+ - Brand-new models get capability-derived defaults automatically. 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.
45
74
 
46
- ## Setup
75
+ ## Configuration
47
76
 
48
- 1. Install the plugin in Homebridge.
49
- 2. Enter your Roborock app account credentials in the plugin settings.
50
- 3. Enable Matter for the Roborock child bridge, restart, and add each robot to Apple Home using the codes shown in the **Matter Pairing** section of the plugin settings.
51
- 4. Use the **Devices** section to choose which robots the plugin manages.
77
+ Everything is configurable from the Homebridge UI. The essentials:
52
78
 
53
- For B01/Q7 robots, room selection appears after the plugin has fetched the map (watch for a `B01 rooms for ...` log line); robots paired before rooms were available must be removed from Apple Home and re-paired once, as Matter fixes the cluster set at commissioning.
79
+ | Option | Default | What it does |
80
+ | ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
81
+ | `email` / password | — | Your Roborock app account (2FA handled in the UI; the session token is stored encrypted) |
82
+ | `skipDevices` | — | Comma-separated device IDs the plugin should ignore |
83
+ | `enableMatterServiceArea` | `true` | Room/map selection in Apple Home |
84
+ | `enableLiveRoomTracking` | `true` | Live current-room from the robot's map position while cleaning |
85
+ | `enableMatterCleanMode` | `true` | Vacuum / Mop / Vacuum + Mop mode selection |
86
+ | `enableFanPowerCleanModes` | `false` | Adds Quiet / Balanced / Turbo / Max suction modes to the Matter mode list. **Re-pair the robot once after toggling** — Matter locks the mode list at pairing |
87
+ | `enableMatterPowerSource` | `true` | Battery cluster |
88
+ | `cloudOnlyMode` | `false` | Skip local TCP entirely and use the cloud for everything |
89
+ | `transientWarningThrottleHours` | `6` | How often recurring transient-timeout warnings may repeat (0 = only in debug) |
54
90
 
55
- ## Attribution
91
+ ## Battery percentage in Apple Home
92
+
93
+ Apple Home renders the battery percentage from pairing time and never re-reads it — the Matter attribute carries the spec's "changes omitted" reporting quality, so value changes are not pushed to controllers _by design_ (charging state on the very same cluster updates live). This is a controller-side limitation verified end-to-end, not a plugin bug.
94
+
95
+ <details>
96
+ <summary>The full evidence chain and workarounds</summary>
97
+
98
+ The complete path — robot → plugin → Homebridge → matter.js store — was verified to carry the live value in real time while Apple kept rendering the pairing-day percentage. matter.js's own controller documents the consequence ("always read attributes that do not report changes via subscriptions"); Apple's controller performs no such re-reads. The plugin performs a one-time battery resync each boot so controllers that re-prime their subscriptions pick up a fresh value. Known refresh paths: restarting the Matter hub (HomePod/Apple TV) or re-pairing. A ready-to-file upstream report with the full evidence lives in [`docs/matter-battery-issue-draft.md`](./docs/matter-battery-issue-draft.md).
99
+
100
+ </details>
56
101
 
57
- This project builds directly on the work of others, preserved under the MIT license:
102
+ ## Troubleshooting
58
103
 
59
- - **Nico Hartung** original author of the upstream lineage.
60
- - **Joshua Appleman**author of [`homebridge-roborock-vacuum2`](https://github.com/applemanj/homebridge-roborock-vacuum2), the base for this fork.
61
- - **copystring** [ioBroker.roborock](https://github.com/copystring/ioBroker.roborock), the source of much of the Roborock protocol implementation.
62
- - **The python-roborock project** the reference implementation used to build B01/Q7 support.
104
+ - **Diagnostics first:** the plugin settings include per-device connection state, the last cloud/local transport used, a live **Test Local Connection** probe, and a **redacted diagnostics report** you can paste straight into a GitHub issue.
105
+ - **Robot shows "Updating…" in Apple Home:** remove the robot from Apple Home and pair it again a stale controller cache from an earlier pairing is the usual cause (tracked upstream in homebridge/homebridge#3951).
106
+ - **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.
107
+ - **Startup without network:** the plugin retries the Roborock cloud with increasing backoff (up to 10 attempts) and never crash-loops Homebridge; wrong credentials stop cleanly with a clear log message.
108
+
109
+ ## Contributing
110
+
111
+ Model reports, diagnostics exports, and pull requests are very welcome. The codebase ships with 247 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.
112
+
113
+ ## Attribution
63
114
 
64
- This fork is maintained by **Mathias Hornbek**. It is an independent, community-maintained fork and is not affiliated with or endorsed by Roborock or Apple.
115
+ A Matter-only fork of [`homebridge-roborock-vacuum2`](https://github.com/applemanj/homebridge-roborock-vacuum2) by **Joshua Appleman**, itself adapted from [ioBroker.roborock](https://github.com/copystring/ioBroker.roborock) by **copystring**, with original work by **Nico Hartung**. B01/Q7 protocol work is implemented against the [python-roborock](https://github.com/Python-roborock/python-roborock) reference. All original copyright is preserved under the [MIT license](./LICENSE).
65
116
 
66
- ## License
117
+ ---
67
118
 
68
- MIT — see [LICENSE](./LICENSE).
119
+ <p align="center">
120
+ <sub>Not affiliated with or endorsed by Roborock, Apple, or the Connectivity Standards Alliance. Roborock is a trademark of Beijing Roborock Technology Co., Ltd.</sub>
121
+ </p>
@@ -92,6 +92,12 @@
92
92
  "type": "boolean",
93
93
  "default": true
94
94
  },
95
+ "enableFanPowerCleanModes": {
96
+ "title": "Enable Suction-Level Cleaning Modes",
97
+ "description": "Adds Quiet / Balanced / Turbo / Max vacuum modes to the Matter cleaning mode list, so suction can be chosen directly from Apple Home. IMPORTANT: Matter fixes the mode list when the robot is paired, so after enabling or disabling this you must remove the robot from Apple Home and re-pair it once. Requires Matter Cleaning Mode Selection.",
98
+ "type": "boolean",
99
+ "default": false
100
+ },
95
101
  "enableMatterExtendedOperationalStates": {
96
102
  "title": "Enable Matter Returning Status",
97
103
  "description": "Opt in to the extended Matter Returning status while the vacuum is heading back to the dock. Unless Charging/Docked status is also enabled below, docked and charging still report as Ready for Apple Home compatibility.",
@@ -28,6 +28,45 @@ const LIVE_STATUS_STALENESS_MS = 15 * 60 * 1000;
28
28
  const CLEAN_MODE_VACUUM = 0;
29
29
  const CLEAN_MODE_MOP = 1;
30
30
  const CLEAN_MODE_VACUUM_AND_MOP = 2;
31
+ // Opt-in fan-power clean modes (enableFanPowerCleanModes, default off since
32
+ // Matter locks the announced mode set at commissioning — enabling requires
33
+ // one re-pair). Mode ids are stable and appended after the base modes.
34
+ // Fan power values are Roborock v1 codes (101-104); the B01/Q7 adapter
35
+ // translates them to its wind levels 1-4 transparently.
36
+ const CLEAN_MODE_VACUUM_QUIET = 3;
37
+ const CLEAN_MODE_VACUUM_BALANCED = 4;
38
+ const CLEAN_MODE_VACUUM_TURBO = 5;
39
+ const CLEAN_MODE_VACUUM_MAX = 6;
40
+ // Matter ModeBase common mode tags (Quiet/Max) — combined with the RVC
41
+ // Vacuum tag so controllers can render semantic labels where supported.
42
+ const RVC_CLEAN_MODE_TAG_QUIET = 2;
43
+ const RVC_CLEAN_MODE_TAG_MAX = 7;
44
+ const FAN_POWER_CLEAN_MODES = [
45
+ {
46
+ mode: CLEAN_MODE_VACUUM_QUIET,
47
+ label: "Quiet Vacuum",
48
+ fanPower: 101,
49
+ extraTags: [RVC_CLEAN_MODE_TAG_QUIET],
50
+ },
51
+ {
52
+ mode: CLEAN_MODE_VACUUM_BALANCED,
53
+ label: "Balanced Vacuum",
54
+ fanPower: 102,
55
+ extraTags: [],
56
+ },
57
+ {
58
+ mode: CLEAN_MODE_VACUUM_TURBO,
59
+ label: "Turbo Vacuum",
60
+ fanPower: 103,
61
+ extraTags: [],
62
+ },
63
+ {
64
+ mode: CLEAN_MODE_VACUUM_MAX,
65
+ label: "Max Vacuum",
66
+ fanPower: 104,
67
+ extraTags: [RVC_CLEAN_MODE_TAG_MAX],
68
+ },
69
+ ];
31
70
  const RVC_RUN_MODE_TAG_IDLE = 16384;
32
71
  const RVC_RUN_MODE_TAG_CLEANING = 16385;
33
72
  const RVC_CLEAN_MODE_TAG_VACUUM = 16385;
@@ -848,7 +887,8 @@ class RoborockMatterVacuumAccessory {
848
887
  modeTags: [{ value: RVC_CLEAN_MODE_TAG_VACUUM }],
849
888
  },
850
889
  ];
851
- if (this.getMatterCleanModeCapabilities().canMop) {
890
+ const capabilities = this.getMatterCleanModeCapabilities();
891
+ if (capabilities.canMop) {
852
892
  supportedModes.push({
853
893
  label: "Mop",
854
894
  mode: CLEAN_MODE_MOP,
@@ -864,8 +904,31 @@ class RoborockMatterVacuumAccessory {
864
904
  ],
865
905
  });
866
906
  }
907
+ // Opt-in suction-level variants, only when the robot actually exposes
908
+ // fan-power control. NOTE: Matter fixes the announced mode set at
909
+ // commissioning — toggling this option requires re-pairing the robot.
910
+ if (this.isFanPowerCleanModesEnabled() &&
911
+ capabilities.canControlFanPower === true) {
912
+ for (const powerMode of FAN_POWER_CLEAN_MODES) {
913
+ supportedModes.push({
914
+ label: powerMode.label,
915
+ mode: powerMode.mode,
916
+ modeTags: [
917
+ { value: RVC_CLEAN_MODE_TAG_VACUUM },
918
+ ...powerMode.extraTags.map((value) => ({ value })),
919
+ ],
920
+ });
921
+ }
922
+ }
867
923
  return supportedModes;
868
924
  }
925
+ isFanPowerCleanModesEnabled() {
926
+ return this.platform.platformConfig.enableFanPowerCleanModes === true;
927
+ }
928
+ getFanPowerCleanMode(cleanMode) {
929
+ var _a;
930
+ return ((_a = FAN_POWER_CLEAN_MODES.find((powerMode) => powerMode.mode === cleanMode)) !== null && _a !== void 0 ? _a : null);
931
+ }
869
932
  getCurrentCleanMode() {
870
933
  return this.isSupportedCleanMode(this.selectedCleanMode)
871
934
  ? this.selectedCleanMode
@@ -928,19 +991,25 @@ class RoborockMatterVacuumAccessory {
928
991
  }
929
992
  getRoborockCleanModeSettings(cleanMode) {
930
993
  const capabilities = this.getMatterCleanModeCapabilities();
994
+ // Fan-power variants are vacuum-family modes with a pinned suction
995
+ // level: protocol layers only understand the three base clean types, so
996
+ // translate before handing over.
997
+ const fanPowerMode = this.getFanPowerCleanMode(cleanMode);
998
+ const baseCleanMode = fanPowerMode ? CLEAN_MODE_VACUUM : cleanMode;
931
999
  // Always carry the selected Matter clean mode; protocol layers that have
932
1000
  // a native clean-type concept (B01/Q7) apply it directly and ignore the
933
1001
  // v1-style fan/water workarounds below.
934
- const settings = { cleanMode };
1002
+ const settings = { cleanMode: baseCleanMode };
935
1003
  if (capabilities.canControlFanPower) {
936
- settings.fanPower =
937
- cleanMode === CLEAN_MODE_MOP
1004
+ settings.fanPower = fanPowerMode
1005
+ ? fanPowerMode.fanPower
1006
+ : baseCleanMode === CLEAN_MODE_MOP
938
1007
  ? ROBOROCK_FAN_POWER_OFF
939
1008
  : this.getPreferredVacuumFanPower();
940
1009
  }
941
1010
  if (capabilities.canControlWater) {
942
1011
  settings.waterBoxMode =
943
- cleanMode === CLEAN_MODE_VACUUM
1012
+ baseCleanMode === CLEAN_MODE_VACUUM
944
1013
  ? ROBOROCK_WATER_BOX_OFF
945
1014
  : this.getPreferredWaterBoxMode();
946
1015
  }
@@ -981,6 +1050,10 @@ class RoborockMatterVacuumAccessory {
981
1050
  return (_a = this.lastWaterBoxMode) !== null && _a !== void 0 ? _a : ROBOROCK_WATER_BOX_MILD;
982
1051
  }
983
1052
  getCleanModeLabel(cleanMode) {
1053
+ const fanPowerMode = this.getFanPowerCleanMode(cleanMode);
1054
+ if (fanPowerMode) {
1055
+ return fanPowerMode.label;
1056
+ }
984
1057
  switch (cleanMode) {
985
1058
  case CLEAN_MODE_MOP:
986
1059
  return "Mop";