homebridge-roborock-vacuum 2.2.7 → 2.2.9

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.2.9
4
+
5
+ - **Fix (Matter)**: Rooms are now published on a map (#60). They used to be published without one, and Apple Home showed no rooms. Room selection works as before
6
+ - **Fix (status)**: Vacuum status (state, battery, charging, error, fan and water settings) now comes from the vacuum itself. The plugin polls `get_status` on every refresh and keeps the real-time status broadcasts, instead of relying on the cloud's home-data snapshot, which can lag behind the device. This fixes Matter vacuums that kept an old state, such as "Return to dock" staying available while the vacuum was already docked (#60). HAP accessories read the same status
7
+ - **Fix (Matter)**: The charging state is reported correctly. "Not charging" was sent as Matter's "fully charged" value, and models that do not report `charge_status` now derive it from the charging and charged states
8
+ - **Fix (Matter)**: The start button no longer fails with `Cannot resume in current state` (#60). A Resume command sent while the vacuum is stopped or docked now starts a clean, and honors the room selection
9
+ - **Change (Matter)**: A vacuum that is returning to or docking at its station now reports "Seeking charger" instead of "Running"
10
+ - **New**: Clear device errors from the settings page. Some errors stay on the vacuum until they are marked as resolved in the Roborock app, even after the cause is fixed, such as the dock's "clean water tank empty" after a refill. When a vacuum reports an error, the settings page now shows it in the device list with a **Clear error** button that resolves it, like **Resolved** in the Roborock app. Homebridge must be running
11
+ - **Fix (login)**: Signing in no longer fails with code 3006 ("user agreement version is not up-to-date"). The login sent a fixed user agreement version, but the current version differs per Roborock server and country; it is now fetched before each login, with the old value as the fallback
12
+ - **Fix (connectivity)**: A cloud connection that stays connected but no longer delivers any messages is restarted. This only happens when the connection reports itself as connected, the Roborock cloud is reachable, and several requests in a row got no answer with no other message in between; restarts back off from 30 minutes up to 6 hours while they do not help. A network or Roborock outage does not trigger restarts
13
+
14
+ ## 2.2.8
15
+
16
+ - **New**: Project website in English, Traditional Chinese and Japanese: [tasict.github.io/homebridge-roborock-vacuum](https://tasict.github.io/homebridge-roborock-vacuum/). It is now the plugin's homepage in the Homebridge UI
17
+ - **New**: You can now support the plugin by buying me a boba (paid by card, no PayPal account needed) or with PayPal: from the bottom of the plugin settings, the donate link in the Homebridge UI, the README or GitHub's Sponsor button
18
+ - **Change (packaging)**: The npm package no longer contains a development test script, a leftover client ID file or the protocol sniffing helper; it now holds only what the plugin needs to run
19
+ - **Docs**: README rewritten around the current feature set — supported models (with corrected names such as Qrevo Curv), requirements matching the supported Homebridge and Node.js versions, the sign-in flow, and a reference of every config option. The header image is now the plugin's own icon
20
+ - **Change (config UI)**: The settings page describes what it is for, and has a small support footer below the settings
21
+
3
22
  ## 2.2.7
4
23
 
5
24
  - **Fix (connectivity)**: The plugin no longer fails to start when the Roborock cloud is unreachable. Startup used to abandon everything after the home-data request timed out (`AxiosError: timeout of 20000ms exceeded`, `ECONNRESET`), so no accessories were registered and every command was refused with "Adapter not inited" — even though the vacuum was reachable on the LAN the whole time. The home data (device list and local keys) is now cached on disk, and when the cloud fetch fails the plugin starts in **local-only mode** from that cache: it still runs LAN discovery, connects to each vacuum over the local protocol, publishes the accessories, and accepts start/stop/dock commands. Cloud-only features (scenes, push updates, fresh online flags) resume by themselves once the cloud answers again, without a Homebridge restart. One successful online start is needed to seed the cache; with no cache the plugin stays idle as before, now with a message that says why
@@ -55,7 +74,7 @@ Maintenance release — no functional changes to device control. Reworks the fou
55
74
 
56
75
  ## 2.2.0
57
76
 
58
- Connectivity and device-support release, porting recent fixes from the python-roborock and ioBroker.roborock upstream projects (see `UpstreamReview-2026-07.md`).
77
+ Connectivity and device-support release, porting recent fixes from the python-roborock and ioBroker.roborock upstream projects.
59
78
 
60
79
  - **New Feature**: Docks introduced since 2025 (Qrevo Curv, Saros 10, Qrevo S5V, Saros 20, and other new dock type codes 10–40) now get their dust-collection, mop-wash, and drying features — including the dock mop-wash switch. Unknown future dock types default to full-featured instead of featureless
61
80
  - **New Feature**: Room names now resolve for vacuums shared from another Roborock account (fetched via the device-share rooms API), so Matter room cleaning and the current-room publisher work on shared devices
package/README.md CHANGED
@@ -1,82 +1,89 @@
1
- # homebridge-roborock-vacuum
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/tasict/homebridge-roborock-vacuum/master/branding/icon.png" width="96" height="96" alt="">
3
+ </p>
2
4
 
3
- ![Roborock Vacuum in Home App](https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/40/21/71/40217177-c879-f670-bd01-c93acfabc31e/AppIcon-0-0-1x_U007emarketing-0-8-0-85-220.png/460x0w.webp)
5
+ # Homebridge Roborock Vacuum
4
6
 
5
- A Homebridge plugin to integrate your Roborock vacuum cleaner with Apple HomeKit, allowing you to control it via the Home app and Siri.
7
+ **Website: [tasict.github.io/homebridge-roborock-vacuum](https://tasict.github.io/homebridge-roborock-vacuum/)** (English, 繁體中文, 日本語)
6
8
 
7
- ## Introduction
9
+ [![npm version](https://img.shields.io/npm/v/homebridge-roborock-vacuum?color=%23cb3837&label=npm)](https://www.npmjs.com/package/homebridge-roborock-vacuum)
10
+ [![npm downloads](https://img.shields.io/npm/dm/homebridge-roborock-vacuum)](https://www.npmjs.com/package/homebridge-roborock-vacuum)
11
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
12
 
9
- `homebridge-roborock-vacuum` brings your Roborock vacuum cleaner into Apple HomeKit. Using your Roborock app account credentials, this plugin automatically detects your vacuum, enabling you to control it directly from the Home app on your iPhone, iPad, or Mac, or with Siri voice commands.
13
+ `homebridge-roborock-vacuum` is a [Homebridge](https://homebridge.io) plugin that brings Roborock robot vacuums into Apple Home, over HomeKit or Matter. Sign in with your Roborock app account and the plugin finds your vacuums by itself; you can then start and stop them from the Home app, with Siri or from automations. Commands go to the vacuum over your home network, so it stays controllable when the Roborock cloud is down.
14
+
15
+ Free and open source. If it saves you a few chores, you can [buy me a boba](https://tasict.bobaboba.me) (by card, no PayPal account needed) or [tip with PayPal](https://paypal.me/tasict).
10
16
 
11
17
  This plugin is inspired by and adapted from the [ioBroker.roborock](https://github.com/copystring/ioBroker.roborock) project by copystring.
12
18
 
13
19
  ## Features
14
20
 
15
- - **Automatic Device Detection**: No need to manually find or enter your vacuum's device ID.
16
- - **Start/Stop Cleaning**: Begin or end cleaning sessions.
17
- - **Device Exclusion**: Skip selected Roborock device IDs so shared or remote vacuums stay out of HomeKit.
18
-
19
- ## The supported robots are:
20
-
21
- Roborock S4
22
- Roborock S4 Max
23
- Roborock S5 Max
24
- Roborock S6
25
- Roborock S6 Pure
26
- Roborock S6 MaxV
27
- Roborock S7
28
- Roborock S7 MaxV (Ultra)
29
- Roborock Q5+
30
- Roborock Q7
31
- Roborock Q7 Max
32
- Roborock S7 Pro Ultra
33
- Roborock S7 Max Ultra
34
- Roborock S8
35
- Roborock S8 Pro Ultra
36
- Roborock Q Revo
37
- Roborock Q8 Max
38
- Roborock Q5 Pro
39
- Roborock Q Revo Pro
40
- Roborock Qrevo S
41
- Roborock Qrevo Curve
42
- Roborock Saros 10
43
- Roborock (roborock.vacuum.a95)
44
- Roborock (roborock.vacuum.a159)
45
- Roborock (roborock.vacuum.ss07)
21
+ - **Automatic device discovery** — no device IDs or IP addresses to look up. The Roborock region is detected from your account.
22
+ - **Start, stop and send home** — over HomeKit the vacuum is a fan-style accessory: turn it on to start cleaning, off to stop and return to the dock.
23
+ - **Battery, charging and status** — battery level, charging state, low-battery warning, and a fault/offline flag when the vacuum reports an error or loses its connection.
24
+ - **Roborock scenes as switches** — every enabled scene from the Roborock app that uses the vacuum can be run from Apple Home.
25
+ - **Dock mop-wash switch** on docks that wash the mop. See [Dock mop-wash switch](#dock-mop-wash-switch).
26
+ - **Consumables** (HomeKit) — remaining life of the filter, main brush and side brush, with a change reminder at 5% or less.
27
+ - **Clear errors from the settings page** — an error the vacuum or dock keeps reporting after you fixed the cause can be cleared without the Roborock app. See [Clearing device errors](#clearing-device-errors).
28
+ - **Matter (Beta)** — publish a vacuum as a native Matter robot vacuum, with room cleaning and clean modes. See [Matter support (Beta)](#matter-support-beta).
29
+ - **Works through cloud outages** — local control over the LAN, and a local-only start when the Roborock cloud is unreachable. See [Cloud outages and local-only mode](#cloud-outages-and-local-only-mode).
30
+ - **Current room → MQTT** (optional) — publish the room being cleaned to your MQTT broker. See [Current Room → MQTT](#current-room--mqtt-optional-telemetry).
31
+ - **Per-device choice** — publish each vacuum over HomeKit or Matter, or skip it (for example a vacuum shared from another home).
46
32
 
47
- ## Requirements
33
+ ## Supported vacuums
48
34
 
49
- Before installing, ensure you have:
35
+ The plugin works with vacuums that are on your **Roborock app** account. These models are known to work:
50
36
 
51
- - A Roborock vacuum cleaner compatible with the Roborock app.
52
- - A Roborock app account (email and password).
53
- - [Homebridge](https://github.com/homebridge/homebridge) installed on a server (e.g., Raspberry Pi).
54
- - Node.js and npm installed on your Homebridge server.
37
+ S4, S4 Max, S5 Max, S6, S6 Pure, S6 MaxV, S7, S7 MaxV (Ultra), S7 Pro Ultra, S7 Max Ultra, S8, S8 Pro Ultra, Q5+, Q5 Pro, Q7, Q7 Max, Q8 Max, Qrevo, Qrevo Pro, Qrevo S, Qrevo Curv, Saros 10, and the models `roborock.vacuum.a95`, `roborock.vacuum.a159` and `roborock.vacuum.ss07`.
55
38
 
56
- ## Installation
39
+ Most other vacuums in the Roborock app work as well; which extras (dock mop wash, consumables, clean modes) appear depends on what the model reports. A vacuum that is set up only in the Xiaomi Home app is not on your Roborock account, so the plugin cannot see it.
40
+
41
+ ## Requirements
57
42
 
58
- Follow these steps to install the plugin:
43
+ - A Roborock vacuum added to the Roborock app, and the email address of that Roborock account.
44
+ - [Homebridge](https://homebridge.io) 1.6 or later, on Node.js 18.20, 20.15, 22 or 24.
45
+ - For Matter: Homebridge 2.x with Matter enabled on the bridge the plugin runs on.
59
46
 
60
- 1. **Install Homebridge** (if not already installed):
47
+ ## Installation
48
+
49
+ In the Homebridge UI, open **Plugins**, search for **Roborock Vacuum** and install `homebridge-roborock-vacuum`.
61
50
 
62
- - Refer to the official [Homebridge installation guide](https://github.com/homebridge/homebridge#installation).
51
+ Without the UI, install it next to Homebridge:
63
52
 
64
- 2. **Install the Plugin**:
65
- - Open a terminal and run:
66
- ```bash
67
- npm install -g homebridge-roborock-vacuum
68
- ```
53
+ ```bash
54
+ npm install -g homebridge-roborock-vacuum
55
+ ```
69
56
 
70
57
  ## Configuration
71
58
 
72
- Use the Homebridge UI settings page to sign in and configure the plugin. Click **Load devices** to list the vacuums on your account, then choose a protocol for each one:
59
+ Configure the plugin from its settings page in the Homebridge UI:
73
60
 
74
- - **HomeKit (HAP)** — the default. The vacuum is bridged to Apple Home as a fan-style accessory, as in previous versions.
75
- - **Matter (Beta)** — the vacuum is published over Matter as a robotic vacuum cleaner, so it gets the native vacuum icon and controls in Apple Home (and works with Google Home, Alexa and SmartThings). This is an experimental feature — read the [Matter support (Beta)](#matter-support-beta) section below before enabling it.
76
- - **Skip** — the vacuum is excluded entirely. You can also enter device IDs manually in the list below; manual entries are always skipped.
61
+ 1. **Sign in.** Enter your Roborock app email. Either enter your password and press **Password Login**, or press **Send 2FA Email** and enter the code from the email with **Verify Code** (also the way to finish a login that asks for two-factor authentication). Leave **Region** on _Auto detect_ unless detection picks the wrong server. The settings page stores an encrypted session token; you do not need to sign in again after a restart.
62
+ 2. **Choose a protocol per vacuum.** Press **Load devices** to list the vacuums on your account and pick one for each:
63
+ - **HomeKit (HAP)** — the default. The vacuum is bridged to Apple Home as a fan-style accessory, as in previous versions.
64
+ - **Matter (Beta)** — the vacuum is published over Matter as a robotic vacuum cleaner, so it gets the native vacuum icon and controls in Apple Home (and works with Google Home, Alexa and SmartThings). This is an experimental feature — read the [Matter support (Beta)](#matter-support-beta) section below before enabling it.
65
+ - **Skip** — the vacuum is excluded entirely. You can also enter device IDs manually in the list below; manual entries are always skipped.
66
+ 3. **Restart Homebridge.**
77
67
 
78
68
  When Homebridge restarts, each device is (re)published according to its selection. A device that was cached under a different protocol (or is now skipped) is removed from the old bridge first. **Changing a device's protocol re-creates the accessory**, so it loses its room and automation assignments in your home app and must be re-organized there.
79
69
 
70
+ ### Options
71
+
72
+ The settings page writes these keys to the `RoborockVacuumPlatform` block in `config.json`:
73
+
74
+ | Key | Description |
75
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
76
+ | `email` | Roborock app account email. Required. |
77
+ | `password` | Roborock account password, saved by **Password Login**. Not needed when you sign in with an email code. |
78
+ | `encryptedToken` | Encrypted session data, managed by the settings page. |
79
+ | `baseURL` | Roborock API endpoint. Leave empty to auto-detect; otherwise `https://usiot.roborock.com` (US), `https://euiot.roborock.com` (EU), `https://cniot.roborock.com` (CN) or `https://api.roborock.com` (Asia). |
80
+ | `language` | Language of the names the plugin gives to switches and sensors it adds (`en`, `de`, `es`, `fr`, `it`, `nl`, `pl`, `pt`, `ru`, `uk`, `zh-cn`, `zh-tw`). Default `en`. |
81
+ | `skipDevices` | Device IDs to leave out, as a list or a comma- or newline-separated string. |
82
+ | `matterDevices` | Device IDs to publish over Matter instead of HomeKit. Ignored without Homebridge 2 and Matter. |
83
+ | `matterSceneDevices` | Matter device IDs whose Roborock scenes become Matter buttons. When omitted, every Matter device gets them. |
84
+ | `currentRoomMqtt` | Optional current-room telemetry, see [Current Room → MQTT](#current-room--mqtt-optional-telemetry). |
85
+ | `debugMode` | Write debug messages to the Homebridge log. Default `false`. |
86
+
80
87
  ### Matter support (Beta)
81
88
 
82
89
  > ⚠️ **This is an experimental, lab-grade beta feature.** Matter support for robotic vacuums is still very new on the controller side. In particular, **Apple Home's compatibility is currently poor and not very stable**: the bridge is uncertified (Apple shows an "Uncertified Accessory" warning), the accessory appears as a generic **"Matter Accessory"** during pairing and must be named manually, room selection needs iOS 18.4 or later, and accessories can occasionally show "Not responding" or lose features after iOS updates. If you want the most reliable day-to-day experience, stay on **HomeKit (HAP)**. Choose Matter only if you want the native vacuum UI and room cleaning, and can live with rough edges.
@@ -153,6 +160,14 @@ The switch is **stateful**: it turns on whenever the dock is washing the mop —
153
160
 
154
161
  The switch's default name comes from the plugin's translation catalog in the language selected by the **Language** option on the settings page (`"language"` in `config.json`; default `en`, 12 languages including `zh-cn` and `zh-tw`). Changing the language only renames accessories on the next restart — accessory identities never change, so **no re-pairing and no room re-selection is needed**. You can also rename the switch freely in your home app.
155
162
 
163
+ ## Clearing device errors
164
+
165
+ Some errors stay on the vacuum until they are marked as resolved in the Roborock app, even after the cause is fixed. A typical one is the dock's "clean water tank empty" after you have refilled the tank. While such an error is latched, Apple Home keeps showing the fault.
166
+
167
+ When a vacuum reports an error, the plugin's settings page shows it next to the vacuum in the device list, with the error code and a short description. Press **Clear error** to resolve it, like tapping **Resolved** in the Roborock app. A dock error is cleared first; press the button again if the vacuum then still reports an error of its own.
168
+
169
+ The settings page asks the running plugin to send the command, so Homebridge must be running. Fix the cause before clearing the error; an error whose cause is still there comes back.
170
+
156
171
  ## Current Room → MQTT (optional telemetry)
157
172
 
158
173
  The plugin can publish the room a vacuum is currently cleaning to a local MQTT broker, for use in external automations (e.g. lighting that follows the vacuum room to room). This is **telemetry only** — it is not exposed to HomeKit — and is **off by default**. It reuses the `mqtt` dependency the plugin already ships, so it adds nothing when disabled.
@@ -191,3 +206,14 @@ A retained JSON message is published whenever the room changes:
191
206
  - Room names are resolved from the robot's saved map; name your rooms in the Roborock app for them to appear.
192
207
 
193
208
  > Validated on a Roborock Qrevo (`roborock.vacuum.a185`). On models that don't populate `cleaning_info`, the payload degrades gracefully to `segment_id: -1` / `room: null`.
209
+
210
+ ## Support
211
+
212
+ If this plugin is useful to you, I'd love it if you bought me a boba:
213
+
214
+ - **[Buy me a boba](https://tasict.bobaboba.me)**: pay by card, no PayPal account needed
215
+ - **[Tip with PayPal](https://paypal.me/tasict)**
216
+
217
+ ## Trademarks
218
+
219
+ Roborock is a trademark of Beijing Roborock Technology Co., Ltd. Apple Home and HomeKit are trademarks of Apple Inc. Matter is a trademark of the Connectivity Standards Alliance. This plugin is unofficial and is not affiliated with, endorsed by, or sponsored by any of them.
@@ -17,13 +17,13 @@
17
17
  "title": "Email",
18
18
  "type": "string",
19
19
  "format": "email",
20
- "description": "Roborock account email"
20
+ "description": "Email of your Roborock app account"
21
21
  },
22
22
  "password": {
23
23
  "title": "Password",
24
24
  "type": "string",
25
25
  "placeholder": "Password",
26
- "description": "Roborock account password"
26
+ "description": "Roborock account password (not needed when signing in with an email code)"
27
27
  },
28
28
  "encryptedToken": {
29
29
  "title": "Encrypted Token",
@@ -34,7 +34,7 @@
34
34
  "skipDevices": {
35
35
  "title": "Skipped Device IDs",
36
36
  "type": "string",
37
- "description": "Roborock device IDs to exclude from HomeKit."
37
+ "description": "Roborock device IDs to leave out of Apple Home (neither HomeKit nor Matter)."
38
38
  },
39
39
  "matterDevices": {
40
40
  "title": "Matter Device IDs",
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ /**
3
+ * Device errors shared between the plugin and its settings page.
4
+ *
5
+ * The settings page runs in the Homebridge UI's own process and cannot reach
6
+ * the vacuums itself — a second cloud MQTT session under the account's
7
+ * client id would disconnect the plugin's. So the files in the Homebridge
8
+ * storage directory carry the exchange instead:
9
+ * - the plugin publishes every bridged vacuum's current errors to
10
+ * DEVICE_ERRORS_FILE;
11
+ * - the page asks for an error to be resolved by writing a request file
12
+ * into RESOLVE_REQUEST_DIR, which the plugin picks up, runs and answers
13
+ * with a result file next to it.
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.DeviceErrorBridge = exports.RESOLVE_REQUEST_TTL_MS = exports.RESOLVE_REQUEST_DIR = exports.DEVICE_ERRORS_FILE = void 0;
20
+ exports.isValidRequestId = isValidRequestId;
21
+ exports.requestFilePath = requestFilePath;
22
+ exports.resultFilePath = resultFilePath;
23
+ exports.writeJsonAtomic = writeJsonAtomic;
24
+ exports.readJson = readJson;
25
+ const fs_1 = __importDefault(require("fs"));
26
+ const path_1 = __importDefault(require("path"));
27
+ exports.DEVICE_ERRORS_FILE = "roborock.deviceErrors.json";
28
+ exports.RESOLVE_REQUEST_DIR = "roborock.resolveError";
29
+ // A request the plugin has not picked up within this window is dropped, and
30
+ // an unread result is removed after the same time.
31
+ exports.RESOLVE_REQUEST_TTL_MS = 60 * 1000;
32
+ const REQUEST_ID_PATTERN = /^[A-Za-z0-9-]{1,64}$/;
33
+ function isValidRequestId(id) {
34
+ return typeof id === "string" && REQUEST_ID_PATTERN.test(id);
35
+ }
36
+ function requestFilePath(storagePath, id) {
37
+ return path_1.default.join(storagePath, exports.RESOLVE_REQUEST_DIR, `${id}.request.json`);
38
+ }
39
+ function resultFilePath(storagePath, id) {
40
+ return path_1.default.join(storagePath, exports.RESOLVE_REQUEST_DIR, `${id}.result.json`);
41
+ }
42
+ /** Write through a temporary file so readers never see a partial file. */
43
+ function writeJsonAtomic(filePath, value) {
44
+ const tmpPath = `${filePath}.${process.pid}.tmp`;
45
+ fs_1.default.writeFileSync(tmpPath, JSON.stringify(value, null, 2), "utf8");
46
+ fs_1.default.renameSync(tmpPath, filePath);
47
+ }
48
+ function readJson(filePath) {
49
+ try {
50
+ return JSON.parse(fs_1.default.readFileSync(filePath, "utf8"));
51
+ }
52
+ catch (error) {
53
+ return null;
54
+ }
55
+ }
56
+ /** Plugin side: publishes device errors and serves resolve requests. */
57
+ class DeviceErrorBridge {
58
+ constructor(storagePath,
59
+ // Resolves the device's current error; true when a resolve was sent,
60
+ // false when there was nothing to resolve. Throws on failure.
61
+ resolve, log) {
62
+ this.storagePath = storagePath;
63
+ this.resolve = resolve;
64
+ this.log = log;
65
+ this.lastPublished = null;
66
+ this.processing = false;
67
+ }
68
+ /** Publish the current errors; a no-op when nothing changed. */
69
+ publish(devices) {
70
+ const content = JSON.stringify(devices);
71
+ if (content === this.lastPublished) {
72
+ return;
73
+ }
74
+ try {
75
+ const file = { updatedAt: Date.now(), devices };
76
+ writeJsonAtomic(path_1.default.join(this.storagePath, exports.DEVICE_ERRORS_FILE), file);
77
+ this.lastPublished = content;
78
+ }
79
+ catch (error) {
80
+ this.log.debug(`Failed to publish device errors: ${error}`);
81
+ }
82
+ }
83
+ /** Poll for resolve requests from the settings page. */
84
+ start(intervalMs = 2000) {
85
+ var _a, _b;
86
+ if (this.timer) {
87
+ return;
88
+ }
89
+ this.timer = setInterval(() => {
90
+ void this.processRequests();
91
+ }, intervalMs);
92
+ (_b = (_a = this.timer).unref) === null || _b === void 0 ? void 0 : _b.call(_a);
93
+ }
94
+ stop() {
95
+ if (this.timer) {
96
+ clearInterval(this.timer);
97
+ this.timer = undefined;
98
+ }
99
+ }
100
+ async processRequests() {
101
+ if (this.processing) {
102
+ return;
103
+ }
104
+ this.processing = true;
105
+ try {
106
+ const dir = path_1.default.join(this.storagePath, exports.RESOLVE_REQUEST_DIR);
107
+ let entries;
108
+ try {
109
+ entries = fs_1.default.readdirSync(dir);
110
+ }
111
+ catch (error) {
112
+ return; // No request has been made yet.
113
+ }
114
+ const now = Date.now();
115
+ for (const entry of entries) {
116
+ const filePath = path_1.default.join(dir, entry);
117
+ if (entry.endsWith(".result.json")) {
118
+ this.removeIfOlderThan(filePath, now - exports.RESOLVE_REQUEST_TTL_MS);
119
+ continue;
120
+ }
121
+ if (!entry.endsWith(".request.json")) {
122
+ continue;
123
+ }
124
+ const request = readJson(filePath);
125
+ // Claim the request before running it so it is never run twice.
126
+ this.remove(filePath);
127
+ if (!request ||
128
+ !isValidRequestId(request.id) ||
129
+ typeof request.duid !== "string" ||
130
+ typeof request.requestedAt !== "number" ||
131
+ now - request.requestedAt > exports.RESOLVE_REQUEST_TTL_MS) {
132
+ continue;
133
+ }
134
+ await this.handleRequest(request);
135
+ }
136
+ }
137
+ finally {
138
+ this.processing = false;
139
+ }
140
+ }
141
+ async handleRequest(request) {
142
+ let result;
143
+ try {
144
+ const sent = await this.resolve(request.duid);
145
+ result = {
146
+ id: request.id,
147
+ ok: true,
148
+ message: sent ? "Error resolved." : "There is no error to resolve.",
149
+ finishedAt: Date.now(),
150
+ };
151
+ }
152
+ catch (error) {
153
+ this.log.warn(`Failed to resolve the error of ${request.duid} requested from the settings page: ${error}`);
154
+ result = {
155
+ id: request.id,
156
+ ok: false,
157
+ message: `Failed to resolve the error: ${error instanceof Error ? error.message : String(error)}`,
158
+ finishedAt: Date.now(),
159
+ };
160
+ }
161
+ try {
162
+ writeJsonAtomic(resultFilePath(this.storagePath, request.id), result);
163
+ }
164
+ catch (error) {
165
+ this.log.debug(`Failed to write the resolve result: ${error}`);
166
+ }
167
+ }
168
+ remove(filePath) {
169
+ try {
170
+ fs_1.default.unlinkSync(filePath);
171
+ }
172
+ catch (error) {
173
+ // Already gone.
174
+ }
175
+ }
176
+ removeIfOlderThan(filePath, cutoff) {
177
+ try {
178
+ if (fs_1.default.statSync(filePath).mtimeMs < cutoff) {
179
+ fs_1.default.unlinkSync(filePath);
180
+ }
181
+ }
182
+ catch (error) {
183
+ // Already gone.
184
+ }
185
+ }
186
+ }
187
+ exports.DeviceErrorBridge = DeviceErrorBridge;
188
+ //# sourceMappingURL=device_error_bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device_error_bridge.js","sourceRoot":"","sources":["../src/device_error_bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ /**
3
+ * Labels for Roborock error codes, shared by the Matter operational-error
4
+ * reporting and the settings page's device error display.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ROBOROCK_DOCK_ERROR_LABELS = exports.ROBOROCK_ERROR_LABELS = void 0;
8
+ // Robot error_code values.
9
+ exports.ROBOROCK_ERROR_LABELS = {
10
+ 1: "Laser sensor fault",
11
+ 2: "Collision sensor fault",
12
+ 3: "Wheel floating",
13
+ 4: "Cliff sensor fault",
14
+ 5: "Main brush blocked",
15
+ 6: "Side brush blocked",
16
+ 7: "Wheel blocked",
17
+ 8: "Device stuck",
18
+ 9: "Dust bin missing",
19
+ 10: "Filter blocked",
20
+ 11: "Magnetic field detected",
21
+ 12: "Low battery",
22
+ 13: "Charging problem",
23
+ 14: "Battery failure",
24
+ 15: "Wall sensor fault",
25
+ 16: "Uneven surface",
26
+ 17: "Side brush failure",
27
+ 18: "Suction fan failure",
28
+ 19: "Unpowered charging station",
29
+ 21: "Laser pressure sensor problem",
30
+ 22: "Charge sensor problem",
31
+ 23: "Dock problem",
32
+ 24: "No-go zone or invisible wall detected",
33
+ 254: "Bin full",
34
+ 255: "Internal error",
35
+ };
36
+ // Dock dock_error_status values (python-roborock RoborockDockErrorCode).
37
+ exports.ROBOROCK_DOCK_ERROR_LABELS = {
38
+ 32: "No dock dustbin or filter",
39
+ 33: "Auto-empty dock fan error",
40
+ 34: "Auto-empty dock jammed",
41
+ 35: "Auto-empty dock voltage error",
42
+ 38: "Clean water tank empty",
43
+ 39: "Dirty water tank full or missing",
44
+ 42: "Maintenance brush jammed",
45
+ 44: "Water filter blocked",
46
+ 46: "Dustbin not installed",
47
+ 53: "Cleaning tank full or blocked",
48
+ };
49
+ //# sourceMappingURL=error_codes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error_codes.js","sourceRoot":"","sources":["../src/error_codes.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,2BAA2B;AACd,QAAA,qBAAqB,GAA2B;IAC3D,CAAC,EAAE,oBAAoB;IACvB,CAAC,EAAE,wBAAwB;IAC3B,CAAC,EAAE,gBAAgB;IACnB,CAAC,EAAE,oBAAoB;IACvB,CAAC,EAAE,oBAAoB;IACvB,CAAC,EAAE,oBAAoB;IACvB,CAAC,EAAE,eAAe;IAClB,CAAC,EAAE,cAAc;IACjB,CAAC,EAAE,kBAAkB;IACrB,EAAE,EAAE,gBAAgB;IACpB,EAAE,EAAE,yBAAyB;IAC7B,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,mBAAmB;IACvB,EAAE,EAAE,gBAAgB;IACpB,EAAE,EAAE,oBAAoB;IACxB,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,4BAA4B;IAChC,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,uCAAuC;IAC3C,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,gBAAgB;CACtB,CAAC;AAEF,yEAAyE;AAC5D,QAAA,0BAA0B,GAA2B;IAChE,EAAE,EAAE,2BAA2B;IAC/B,EAAE,EAAE,2BAA2B;IAC/B,EAAE,EAAE,wBAAwB;IAC5B,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,wBAAwB;IAC5B,EAAE,EAAE,kCAAkC;IACtC,EAAE,EAAE,0BAA0B;IAC9B,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,uBAAuB;IAC3B,EAAE,EAAE,+BAA+B;CACpC,CAAC"}
package/dist/platform.js CHANGED
@@ -10,6 +10,7 @@ const dock_wash_matter_accessory_1 = require("./dock_wash_matter_accessory");
10
10
  const logger_1 = __importDefault(require("./logger"));
11
11
  const settings_1 = require("./settings");
12
12
  const crypto_1 = require("./crypto");
13
+ const device_error_bridge_1 = require("./device_error_bridge");
13
14
  const protocol_1 = require("./protocol");
14
15
  const DEP0040_CODE = "DEP0040";
15
16
  let dep0040FilterInstalled = false;
@@ -45,6 +46,7 @@ const DPS_STATUS_FIELDS = {
45
46
  "122": "battery",
46
47
  "123": "fan_power",
47
48
  "124": "water_box_mode",
49
+ "133": "charge_status",
48
50
  };
49
51
  function translateDpsFields(payload) {
50
52
  const dps = payload.dps;
@@ -121,6 +123,11 @@ class RoborockPlatform {
121
123
  storagePath: storagePath,
122
124
  currentRoomMqtt: this.platformConfig.currentRoomMqtt,
123
125
  });
126
+ this.errorBridge = new device_error_bridge_1.DeviceErrorBridge(storagePath, async (duid) => {
127
+ const sent = await this.roborockAPI.resolveError(duid);
128
+ this.publishDeviceErrors();
129
+ return sent;
130
+ }, this.log);
124
131
  /**
125
132
  * When this event is fired it means Homebridge has restored all cached accessories from disk.
126
133
  * Dynamic Platform plugins should only register new accessories after this event was fired,
@@ -138,6 +145,7 @@ class RoborockPlatform {
138
145
  });
139
146
  this.api.on("shutdown" /* APIEvent.SHUTDOWN */, () => {
140
147
  this.log.debug("Shutting down...");
148
+ this.errorBridge.stop();
141
149
  if (this.roborockAPI) {
142
150
  this.roborockAPI.stopService();
143
151
  }
@@ -232,6 +240,7 @@ class RoborockPlatform {
232
240
  dockWash.updateFromRoborockData(data);
233
241
  }
234
242
  }
243
+ self.publishDeviceErrors();
235
244
  return;
236
245
  }
237
246
  for (const vacuum of self.vacuums) {
@@ -241,12 +250,10 @@ class RoborockPlatform {
241
250
  // Scenes load asynchronously; sync the Matter scene buttons once
242
251
  // they are available (or when they change).
243
252
  self.syncMatterScenes();
244
- // The HomeData refresh is the only periodic status source — the
245
- // roborockLib get_status interval runs but its results never reach
246
- // the cache this plugin reads (gated on an unset config field, and
247
- // written to a state path getVacuumDeviceStatus does not read).
248
- // Mirror it into the Matter vacuums the same way updateDeviceState
249
- // does for the HAP accessories.
253
+ // Periodic status sync. getVacuumDeviceStatus serves the device's
254
+ // live status (get_status polls and broadcasts) and falls back to
255
+ // the HomeData snapshot. Mirror it into the Matter vacuums the same
256
+ // way updateDeviceState does for the HAP accessories.
250
257
  for (const matterVacuum of self.matterVacuums.values()) {
251
258
  matterVacuum.refreshFromDevice();
252
259
  }
@@ -254,12 +261,15 @@ class RoborockPlatform {
254
261
  for (const dockWash of self.matterDockWash.values()) {
255
262
  dockWash.refreshFromDevice();
256
263
  }
264
+ self.publishDeviceErrors();
257
265
  }
258
266
  });
259
267
  Promise.resolve(self.roborockAPI.startService(function () {
260
268
  self.log.info("Service started");
261
269
  //call the discoverDevices function
262
270
  self.discoverDevices();
271
+ self.publishDeviceErrors();
272
+ self.errorBridge.start();
263
273
  })).catch((error) => {
264
274
  self.log.error(`Failed to start the Roborock service: ${error}. ` +
265
275
  "The plugin will stay idle; fix the configuration or connectivity " +
@@ -309,6 +319,26 @@ class RoborockPlatform {
309
319
  // model must start with "roborock.vacuum."
310
320
  return typeof model === "string" && model.startsWith("roborock.vacuum.");
311
321
  }
322
+ /**
323
+ * Publish every bridged vacuum's current robot and dock error for the
324
+ * settings page. Cheap when nothing changed (the bridge skips the write).
325
+ */
326
+ publishDeviceErrors() {
327
+ if (!this.roborockAPI.isInited()) {
328
+ return;
329
+ }
330
+ const errorCode = (duid, field) => Number(this.roborockAPI.getVacuumDeviceStatus(duid, field)) || 0;
331
+ const devices = this.roborockAPI
332
+ .getVacuumList()
333
+ .filter((device) => !this.isSkippedDeviceId(device.duid))
334
+ .map((device) => ({
335
+ duid: device.duid,
336
+ name: device.name,
337
+ errorCode: errorCode(device.duid, "error_code"),
338
+ dockErrorCode: errorCode(device.duid, "dock_error_status"),
339
+ }));
340
+ this.errorBridge.publish(devices);
341
+ }
312
342
  isSkippedDeviceId(deviceId) {
313
343
  return this.resolveProtocol(deviceId) === "skip";
314
344
  }