matterbridge-roborock-vacuum-plugin 1.1.0-rc05 → 1.1.0-rc07

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/README.md CHANGED
@@ -90,10 +90,23 @@ For a detailed table of how Apple Home clean modes map to Roborock settings, see
90
90
 
91
91
  ---
92
92
 
93
+ ### ⚙️ Matterbridge setting
94
+
95
+ <div align="center">
96
+ <img src="./screenshot/IMG_6.PNG" alt="Matterbridge Configuration Screenshot" style="border-radius: 8px; max-width: 100%; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" />
97
+ </div>
98
+
99
+ ---
100
+
93
101
  ### 💬 Need Help?
94
102
 
103
+ 🛠️ **Reporting an Issue**
104
+ Before opening an issue, please make sure to read the instructions here:
105
+ [📄 How to Report an Issue](./README_REPORT_ISSUE.md)
106
+
107
+ 💬 **Community Support**
95
108
  Join our Discord for support, updates, and community discussions:
96
- 👉 [Join the Matterbridge Roborock Discord](https://discord.gg/NHMDKdzm)
109
+ 👉 [Join the Matterbridge Roborock Discord](https://discord.gg/favqExHGn4)
97
110
 
98
111
  ---
99
112
 
@@ -0,0 +1,34 @@
1
+ ### ⚙️ Matterbridge Setup
2
+
3
+ <div align="center">
4
+ <img src="./screenshot/IMG_6.PNG" alt="Matterbridge Configuration Screenshot" style="border-radius: 8px; max-width: 100%; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" />
5
+ </div>
6
+
7
+ > 🛠️ Follow the configuration shown above to set up Matterbridge correctly for your Roborock vacuum.
8
+
9
+ ---
10
+
11
+ ### 🔄 Steps to Reproduce
12
+
13
+ 1. Apply the configuration shown above.
14
+ 2. Restart **Matterbridge**.
15
+ 3. Reproduce the issue you're encountering.
16
+
17
+ ---
18
+
19
+ ### 🪵 Collect Logs
20
+
21
+ After reproducing the issue, download the **MATTERBRIDGE LOG** file:
22
+
23
+ <div align="center">
24
+ <img src="./screenshot/IMG_7.PNG" alt="Download Matterbridge Log Screenshot" style="border-radius: 8px; max-width: 100%; box-shadow: 0 4px 12px rgba(0,0,0,0.1);" />
25
+ </div>
26
+
27
+ ---
28
+
29
+ ### 📬 Submit an Issue
30
+
31
+ Please upload the log file and describe the problem at:
32
+ [https://github.com/RinDevJunior/matterbridge-roborock-vacuum-plugin/issues](https://github.com/RinDevJunior/matterbridge-roborock-vacuum-plugin/issues)
33
+
34
+ Your logs help me troubleshoot and improve compatibility. Thank you! 🙏
@@ -12,6 +12,19 @@ These devices have been fully tested and are confirmed to work as expected.
12
12
 
13
13
  ---
14
14
 
15
+ ## ⚠️ Not Supported Devices
16
+
17
+ This plugin does NOT support these models:
18
+
19
+ | Device Name | Model String |
20
+ |----------------------------|-------------------------------|
21
+ | Roborock Q10 Series | `roborock.vacuum.ss07` |
22
+
23
+ **Reason:**
24
+ Roborock recently released a new series of models, Q10. Roborock has changed the protocol for how these devices interact.
25
+
26
+ ---
27
+
15
28
  ## ⚠️ Other Supported Devices
16
29
 
17
30
  All other models listed in the code are supported, but **may have some limitations**.
@@ -51,4 +64,4 @@ If you have one of these models, please try it out and let me know your results!
51
64
  ---
52
65
 
53
66
  > **Note:**
54
- > If you have a device not listed above, feel free to try it and report your experience!
67
+ > If you have a device not listed above, feel free to try it and report your experience!
@@ -1 +1,28 @@
1
- export {};
1
+ export function createDefaultExperimentalFeatureSetting() {
2
+ return {
3
+ enableExperimentalFeature: false,
4
+ advancedFeature: {
5
+ showRoutinesAsRoom: false,
6
+ includeDockStationStatus: false,
7
+ forceRunAtDefault: false,
8
+ useVacationModeToSendVacuumToDock: false,
9
+ enableServerMode: false,
10
+ },
11
+ cleanModeSettings: {
12
+ enableCleanModeMapping: false,
13
+ vacuuming: {
14
+ fanMode: 'Balanced',
15
+ mopRouteMode: 'Standard',
16
+ },
17
+ mopping: {
18
+ waterFlowMode: 'Medium',
19
+ mopRouteMode: 'Standard',
20
+ },
21
+ vacmop: {
22
+ fanMode: 'Balanced',
23
+ waterFlowMode: 'Medium',
24
+ mopRouteMode: 'Standard',
25
+ },
26
+ },
27
+ };
28
+ }
package/dist/platform.js CHANGED
@@ -10,6 +10,7 @@ import { RoborockVacuumCleaner } from './rvc.js';
10
10
  import { configurateBehavior } from './behaviorFactory.js';
11
11
  import { RoborockAuthenticateApi, RoborockIoTApi } from './roborockCommunication/index.js';
12
12
  import { getSupportedAreas, getSupportedScenes } from './initialData/index.js';
13
+ import { createDefaultExperimentalFeatureSetting } from './model/ExperimentalFeatureSetting.js';
13
14
  import NodePersist from 'node-persist';
14
15
  import Path from 'node:path';
15
16
  export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
@@ -33,8 +34,8 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
33
34
  config.whiteList = [];
34
35
  if (config.blackList === undefined)
35
36
  config.blackList = [];
36
- if (config.enableExperimentalFeature === undefined)
37
- config.enableExperimentalFeature = false;
37
+ if (config.enableExperimental === undefined)
38
+ config.enableExperimental = createDefaultExperimentalFeatureSetting();
38
39
  const persistDir = Path.join(this.matterbridge.matterbridgePluginDirectory, PLUGIN_NAME, 'persist');
39
40
  this.persist = NodePersist.create({ dir: persistDir });
40
41
  this.clientManager = new ClientManager(this.log);
@@ -111,14 +112,21 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
111
112
  return;
112
113
  }
113
114
  const self = this;
115
+ const configurateSuccess = new Map();
114
116
  for (const vacuum of this.devices.values()) {
115
- await this.configurateDevice(vacuum);
116
- this.rrHomeId = vacuum.rrHomeId;
117
+ const success = await this.configurateDevice(vacuum);
118
+ configurateSuccess.set(vacuum.duid, success);
119
+ if (success) {
120
+ this.rrHomeId = vacuum.rrHomeId;
121
+ }
117
122
  }
118
123
  this.roborockService.setDeviceNotify(async function (messageSource, homeData) {
119
124
  await self.platformRunner?.updateRobot(messageSource, homeData);
120
125
  });
121
- for (const robot of this.robots.values()) {
126
+ for (const [duid, robot] of this.robots.entries()) {
127
+ if (!configurateSuccess.get(duid)) {
128
+ continue;
129
+ }
122
130
  await this.roborockService.activateDeviceNotify(robot.device);
123
131
  }
124
132
  await this.platformRunner?.requestHomeData();
@@ -128,9 +136,13 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
128
136
  const username = this.config.username;
129
137
  if (this.platformRunner === undefined || this.roborockService === undefined) {
130
138
  this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
131
- return;
139
+ return false;
140
+ }
141
+ const connectedToLocalNetwork = await this.roborockService.initializeMessageClientForLocal(vacuum);
142
+ if (!connectedToLocalNetwork) {
143
+ this.log.error(`Failed to connect to local network for device: ${vacuum.name} (${vacuum.duid})`);
144
+ return false;
132
145
  }
133
- await this.roborockService.initializeMessageClientForLocal(vacuum);
134
146
  const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
135
147
  this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
136
148
  const behaviorHandler = configurateBehavior(vacuum.data.model, vacuum.duid, this.roborockService, this.cleanModeSettings, this.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false, this.log);
@@ -149,6 +161,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
149
161
  await this.registerDevice(robot);
150
162
  }
151
163
  this.robots.set(robot.serialNumber ?? '', robot);
164
+ return true;
152
165
  }
153
166
  async onShutdown(reason) {
154
167
  await super.onShutdown(reason);
@@ -314,7 +314,7 @@ export default class RoborockService {
314
314
  this.logger.debug('Begin get local ip');
315
315
  if (this.messageClient === undefined) {
316
316
  this.logger.error('messageClient not initialized');
317
- return;
317
+ return false;
318
318
  }
319
319
  const self = this;
320
320
  const messageProcessor = new MessageProcessor(this.messageClient);
@@ -340,6 +340,10 @@ export default class RoborockService {
340
340
  if (!localIp) {
341
341
  this.logger.debug('Requesting network info for device', device.duid);
342
342
  const networkInfo = await messageProcessor.getNetworkInfo(device.duid);
343
+ if (!networkInfo || !networkInfo.ip) {
344
+ this.logger.error('Failed to retrieve network info for device', device.duid);
345
+ return false;
346
+ }
343
347
  localIp = networkInfo.ip;
344
348
  }
345
349
  if (localIp) {
@@ -362,7 +366,9 @@ export default class RoborockService {
362
366
  }
363
367
  catch (error) {
364
368
  this.logger.error('Error requesting network info', error);
369
+ return false;
365
370
  }
371
+ return true;
366
372
  }
367
373
  sleep(ms) {
368
374
  return new Promise((resolve) => setTimeout(resolve, ms));
package/dist/rvc.js CHANGED
@@ -15,7 +15,7 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
15
15
  log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
16
16
  log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
17
17
  log.debug(`Supported Areas: ${JSON.stringify(supportedAreas)}`);
18
- const bridgeMode = enableExperimentalFeature?.advancedFeature.enableServerMode ? 'server' : undefined;
18
+ const bridgeMode = enableExperimentalFeature?.enableExperimentalFeature && enableExperimentalFeature?.advancedFeature?.enableServerMode ? 'server' : undefined;
19
19
  super(deviceName, device.duid, bridgeMode, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(), supportedAreas, undefined, supportedAreas[0].areaId);
20
20
  this.username = username;
21
21
  this.device = device;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.1.0-rc05",
4
+ "version": "1.1.0-rc07",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc05 by https://github.com/RinDevJunior",
3
+ "description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc07 by https://github.com/RinDevJunior",
4
4
  "type": "object",
5
5
  "required": [
6
6
  "username",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
- "version": "1.1.0-rc05",
3
+ "version": "1.1.0-rc07",
4
4
  "description": "Matterbridge Roborock Vacuum Plugin",
5
5
  "author": "https://github.com/RinDevJunior",
6
6
  "license": "MIT",
Binary file
Binary file
@@ -28,3 +28,32 @@ export interface CleanModeSettings {
28
28
  distanceOff?: number;
29
29
  };
30
30
  }
31
+
32
+ export function createDefaultExperimentalFeatureSetting(): ExperimentalFeatureSetting {
33
+ return {
34
+ enableExperimentalFeature: false,
35
+ advancedFeature: {
36
+ showRoutinesAsRoom: false,
37
+ includeDockStationStatus: false,
38
+ forceRunAtDefault: false,
39
+ useVacationModeToSendVacuumToDock: false,
40
+ enableServerMode: false,
41
+ },
42
+ cleanModeSettings: {
43
+ enableCleanModeMapping: false,
44
+ vacuuming: {
45
+ fanMode: 'Balanced',
46
+ mopRouteMode: 'Standard',
47
+ },
48
+ mopping: {
49
+ waterFlowMode: 'Medium',
50
+ mopRouteMode: 'Standard',
51
+ },
52
+ vacmop: {
53
+ fanMode: 'Balanced',
54
+ waterFlowMode: 'Medium',
55
+ mopRouteMode: 'Standard',
56
+ },
57
+ },
58
+ };
59
+ }
package/src/platform.ts CHANGED
@@ -11,7 +11,7 @@ import { configurateBehavior } from './behaviorFactory.js';
11
11
  import { NotifyMessageTypes } from './notifyMessageTypes.js';
12
12
  import { Device, RoborockAuthenticateApi, RoborockIoTApi } from './roborockCommunication/index.js';
13
13
  import { getSupportedAreas, getSupportedScenes } from './initialData/index.js';
14
- import { CleanModeSettings, ExperimentalFeatureSetting } from './model/ExperimentalFeatureSetting.js';
14
+ import { CleanModeSettings, createDefaultExperimentalFeatureSetting, ExperimentalFeatureSetting } from './model/ExperimentalFeatureSetting.js';
15
15
  import { ServiceArea } from 'matterbridge/matter/clusters';
16
16
  import NodePersist from 'node-persist';
17
17
  import Path from 'node:path';
@@ -40,7 +40,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
40
40
  this.log.info('Initializing platform:', this.config.name);
41
41
  if (config.whiteList === undefined) config.whiteList = [];
42
42
  if (config.blackList === undefined) config.blackList = [];
43
- if (config.enableExperimentalFeature === undefined) config.enableExperimentalFeature = false;
43
+ if (config.enableExperimental === undefined) config.enableExperimental = createDefaultExperimentalFeatureSetting() as ExperimentalFeatureSetting;
44
44
 
45
45
  // Create storage for this plugin (initialised in onStart)
46
46
  const persistDir = Path.join(this.matterbridge.matterbridgePluginDirectory, PLUGIN_NAME, 'persist');
@@ -161,16 +161,24 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
161
161
  // eslint-disable-next-line @typescript-eslint/no-this-alias
162
162
  const self = this;
163
163
 
164
+ const configurateSuccess = new Map<string, boolean>();
165
+
164
166
  for (const vacuum of this.devices.values()) {
165
- await this.configurateDevice(vacuum);
166
- this.rrHomeId = vacuum.rrHomeId;
167
+ const success = await this.configurateDevice(vacuum);
168
+ configurateSuccess.set(vacuum.duid, success);
169
+ if (success) {
170
+ this.rrHomeId = vacuum.rrHomeId;
171
+ }
167
172
  }
168
173
 
169
174
  this.roborockService.setDeviceNotify(async function (messageSource: NotifyMessageTypes, homeData: unknown) {
170
175
  await self.platformRunner?.updateRobot(messageSource, homeData);
171
176
  });
172
177
 
173
- for (const robot of this.robots.values()) {
178
+ for (const [duid, robot] of this.robots.entries()) {
179
+ if (!configurateSuccess.get(duid)) {
180
+ continue;
181
+ }
174
182
  await this.roborockService.activateDeviceNotify(robot.device);
175
183
  }
176
184
 
@@ -180,15 +188,21 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
180
188
  }
181
189
 
182
190
  // Running in loop to configurate devices
183
- private async configurateDevice(vacuum: Device) {
191
+ private async configurateDevice(vacuum: Device): Promise<boolean> {
184
192
  const username = this.config.username as string;
185
193
 
186
194
  if (this.platformRunner === undefined || this.roborockService === undefined) {
187
195
  this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
188
- return;
196
+ return false;
197
+ }
198
+
199
+ const connectedToLocalNetwork = await this.roborockService.initializeMessageClientForLocal(vacuum);
200
+
201
+ if (!connectedToLocalNetwork) {
202
+ this.log.error(`Failed to connect to local network for device: ${vacuum.name} (${vacuum.duid})`);
203
+ return false;
189
204
  }
190
205
 
191
- await this.roborockService.initializeMessageClientForLocal(vacuum);
192
206
  const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
193
207
 
194
208
  this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
@@ -222,6 +236,8 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
222
236
  }
223
237
 
224
238
  this.robots.set(robot.serialNumber ?? '', robot);
239
+
240
+ return true;
225
241
  }
226
242
 
227
243
  override async onShutdown(reason?: string) {
@@ -408,11 +408,11 @@ export default class RoborockService {
408
408
  this.logger.debug('MessageClient connected');
409
409
  }
410
410
 
411
- public async initializeMessageClientForLocal(device: Device): Promise<void> {
411
+ public async initializeMessageClientForLocal(device: Device): Promise<boolean> {
412
412
  this.logger.debug('Begin get local ip');
413
413
  if (this.messageClient === undefined) {
414
414
  this.logger.error('messageClient not initialized');
415
- return;
415
+ return false;
416
416
  }
417
417
  // eslint-disable-next-line @typescript-eslint/no-this-alias
418
418
  const self = this;
@@ -448,6 +448,11 @@ export default class RoborockService {
448
448
  if (!localIp) {
449
449
  this.logger.debug('Requesting network info for device', device.duid);
450
450
  const networkInfo = await messageProcessor.getNetworkInfo(device.duid);
451
+ if (!networkInfo || !networkInfo.ip) {
452
+ this.logger.error('Failed to retrieve network info for device', device.duid);
453
+ return false;
454
+ }
455
+
451
456
  localIp = networkInfo.ip;
452
457
  }
453
458
 
@@ -473,7 +478,10 @@ export default class RoborockService {
473
478
  }
474
479
  } catch (error) {
475
480
  this.logger.error('Error requesting network info', error);
481
+ return false;
476
482
  }
483
+
484
+ return true;
477
485
  }
478
486
 
479
487
  private sleep(ms: number): Promise<void> {
package/src/rvc.ts CHANGED
@@ -34,7 +34,7 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
34
34
  log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
35
35
  log.debug(`Supported Areas: ${JSON.stringify(supportedAreas)}`);
36
36
 
37
- const bridgeMode = enableExperimentalFeature?.advancedFeature.enableServerMode ? 'server' : undefined;
37
+ const bridgeMode = enableExperimentalFeature?.enableExperimentalFeature && enableExperimentalFeature?.advancedFeature?.enableServerMode ? 'server' : undefined;
38
38
  super(
39
39
  deviceName, // name
40
40
  device.duid, // serial