matterbridge-roborock-vacuum-plugin 1.0.8-rc09 → 1.1.0-rc02

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
@@ -69,7 +69,7 @@ To get the **DUID** for your devices, you have two options:
69
69
  ### 🚧 Project Status
70
70
 
71
71
  - **Under active development**
72
- - Requires **`matterbridge@3.0.4`**
72
+ - Requires **`matterbridge@3.1.3`**
73
73
  - ⚠️ **Known Issue:** Vacuum may appear as **two devices** in Apple Home
74
74
 
75
75
  ---
package/dist/platform.js CHANGED
@@ -13,20 +13,20 @@ import { getSupportedAreas, getSupportedScenes } from './initialData/index.js';
13
13
  import NodePersist from 'node-persist';
14
14
  import Path from 'node:path';
15
15
  export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
16
- robot;
16
+ robots = new Map();
17
17
  rvcInterval;
18
18
  roborockService;
19
19
  clientManager;
20
20
  platformRunner;
21
- devices;
22
- serialNumber;
21
+ devices = new Map();
23
22
  cleanModeSettings;
24
23
  enableExperimentalFeature;
25
24
  persist;
25
+ rrHomeId;
26
26
  constructor(matterbridge, log, config) {
27
27
  super(matterbridge, log, config);
28
- if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.0.7')) {
29
- throw new Error(`This plugin requires Matterbridge version >= "3.0.7". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
28
+ if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.3')) {
29
+ throw new Error(`This plugin requires Matterbridge version >= "3.1.3". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
30
30
  }
31
31
  this.log.info('Initializing platform:', this.config.name);
32
32
  if (config.whiteList === undefined)
@@ -64,22 +64,31 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
64
64
  this.log.debug('Initializing - userData:', debugStringify(userData));
65
65
  const devices = await this.roborockService.listDevices(username);
66
66
  this.log.notice('Initializing - devices: ', debugStringify(devices));
67
- let vacuum = undefined;
67
+ let vacuums = [];
68
68
  if (this.config.whiteList.length > 0) {
69
- const firstDUID = this.config.whiteList[0];
70
- const duid = firstDUID.split('-')[1];
71
- vacuum = devices.find((d) => d.duid == duid);
69
+ const whiteList = (this.config.whiteList ?? []);
70
+ for (const item of whiteList) {
71
+ const duid = item.split('-')[1];
72
+ const vacuum = devices.find((d) => d.duid == duid);
73
+ if (vacuum) {
74
+ vacuums.push(vacuum);
75
+ }
76
+ }
72
77
  }
73
78
  else {
74
- vacuum = devices.find((d) => isSupportedDevice(d.data.model));
79
+ vacuums = devices.filter((d) => isSupportedDevice(d.data.model));
75
80
  }
76
- if (!vacuum) {
81
+ if (vacuums.length === 0) {
77
82
  this.log.error('Initializing: No device found');
78
83
  return;
79
84
  }
80
- await this.roborockService.initializeMessageClient(username, vacuum, userData);
81
- this.devices.set(vacuum.serialNumber, vacuum);
82
- this.serialNumber = vacuum.serialNumber;
85
+ if (!this.enableExperimentalFeature || !this.enableExperimentalFeature.advancedFeature.enableServerMode) {
86
+ vacuums = [vacuums[0]];
87
+ }
88
+ for (const vacuum of vacuums) {
89
+ await this.roborockService.initializeMessageClient(username, vacuum, userData);
90
+ this.devices.set(vacuum.serialNumber, vacuum);
91
+ }
83
92
  await this.onConfigurateDevice();
84
93
  this.log.notice('onStart finished');
85
94
  }
@@ -96,13 +105,31 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
96
105
  this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
97
106
  return;
98
107
  }
99
- const vacuum = this.devices.get(this.serialNumber);
100
108
  const username = this.config.username;
101
- if (!vacuum || !username) {
109
+ if (this.devices.size === 0 || !username) {
102
110
  this.log.error('Initializing: No supported devices found');
103
111
  return;
104
112
  }
105
113
  const self = this;
114
+ for (const vacuum of this.devices.values()) {
115
+ await this.configurateDevice(vacuum);
116
+ this.rrHomeId = vacuum.rrHomeId;
117
+ }
118
+ this.roborockService.setDeviceNotify(async function (messageSource, homeData) {
119
+ await self.platformRunner?.updateRobot(messageSource, homeData);
120
+ });
121
+ for (const robot of this.robots.values()) {
122
+ await this.roborockService.activateDeviceNotify(robot.device);
123
+ }
124
+ await this.platformRunner?.requestHomeData();
125
+ this.log.info('onConfigurateDevice finished');
126
+ }
127
+ async configurateDevice(vacuum) {
128
+ const username = this.config.username;
129
+ if (this.platformRunner === undefined || this.roborockService === undefined) {
130
+ this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
131
+ return;
132
+ }
106
133
  await this.roborockService.initializeMessageClientForLocal(vacuum);
107
134
  const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
108
135
  this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
@@ -113,19 +140,14 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
113
140
  routineAsRoom = getSupportedScenes(vacuum.scenes, this.log);
114
141
  this.roborockService.setSupportedScenes(vacuum.duid, routineAsRoom);
115
142
  }
116
- this.robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature, this.log);
117
- this.robot.configurateHandler(behaviorHandler);
143
+ const robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature, this.log);
144
+ robot.configurateHandler(behaviorHandler);
118
145
  this.log.info('vacuum:', debugStringify(vacuum));
119
- this.setSelectDevice(this.robot.serialNumber ?? '', this.robot.deviceName ?? '', undefined, 'hub');
120
- if (this.validateDevice(this.robot.deviceName ?? '')) {
121
- await this.registerDevice(this.robot);
146
+ this.setSelectDevice(robot.serialNumber ?? '', robot.deviceName ?? '', undefined, 'hub');
147
+ if (this.validateDevice(robot.deviceName ?? '')) {
148
+ await this.registerDevice(robot);
122
149
  }
123
- this.roborockService.setDeviceNotify(async function (messageSource, homeData) {
124
- await self.platformRunner?.updateRobot(messageSource, homeData);
125
- });
126
- await this.roborockService.activateDeviceNotify(this.robot.device);
127
- await self.platformRunner?.requestHomeData();
128
- this.log.info('onConfigurateDevice finished');
150
+ this.robots.set(robot.serialNumber ?? '', robot);
129
151
  }
130
152
  async onShutdown(reason) {
131
153
  await super.onShutdown(reason);
@@ -25,11 +25,11 @@ export class PlatformRunner {
25
25
  }
26
26
  async requestHomeData() {
27
27
  const platform = this.platform;
28
- if (!platform.robot || !platform.robot.rrHomeId)
28
+ if (platform.robots.size === 0 || !platform.rrHomeId)
29
29
  return;
30
30
  if (platform.roborockService === undefined)
31
31
  return;
32
- const homeData = await platform.roborockService.getHomeDataForUpdating(platform.robot.rrHomeId);
32
+ const homeData = await platform.roborockService.getHomeDataForUpdating(platform.rrHomeId);
33
33
  await this.updateRobot(NotifyMessageTypes.HomeData, homeData);
34
34
  }
35
35
  async getRoomMapFromDevice(device) {
@@ -41,21 +41,32 @@ export class PlatformRunner {
41
41
  }
42
42
  return new RoomMap([], rooms);
43
43
  }
44
- async getRoomMap() {
44
+ async getRoomMap(duid) {
45
45
  const platform = this.platform;
46
- const rooms = platform.robot?.device.rooms ?? [];
47
- if (platform.robot?.device === undefined || platform.roborockService === undefined)
46
+ const robot = platform.robots.get(duid);
47
+ if (robot === undefined) {
48
+ platform.log.error(`Error6: Robot with DUID ${duid} not found`);
48
49
  return undefined;
49
- if (platform.robot.roomInfo === undefined) {
50
- const roomData = await platform.roborockService.getRoomMappings(platform.robot.device.duid);
51
- platform.robot.roomInfo = new RoomMap(roomData ?? [], rooms);
52
- return platform.robot.roomInfo;
53
50
  }
54
- return platform.robot.roomInfo;
51
+ if (platform.roborockService === undefined)
52
+ return undefined;
53
+ const rooms = robot.device.rooms ?? [];
54
+ if (robot.roomInfo === undefined) {
55
+ const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
56
+ robot.roomInfo = new RoomMap(roomData ?? [], rooms);
57
+ return robot.roomInfo;
58
+ }
59
+ return robot.roomInfo;
55
60
  }
56
- async updateFromMQTTMessage(messageSource, messageData, tracked = false) {
61
+ async updateFromMQTTMessage(messageSource, messageData, duid = '', tracked = false) {
57
62
  const platform = this.platform;
58
- const deviceData = platform.robot?.device.data;
63
+ duid = duid || messageData?.duid || '';
64
+ const robot = platform.robots.get(duid);
65
+ if (robot === undefined) {
66
+ platform.log.error(`Error1: Robot with DUID ${duid} not found`);
67
+ return;
68
+ }
69
+ const deviceData = robot.device.data;
59
70
  if (deviceData === undefined) {
60
71
  platform.log.error('Device data is undefined');
61
72
  return;
@@ -63,24 +74,17 @@ export class PlatformRunner {
63
74
  if (!tracked) {
64
75
  platform.log.debug(`${messageSource} updateFromMQTTMessage: ${debugStringify(messageData)}`);
65
76
  }
66
- const duid = messageData.duid;
67
- if (platform.robot === undefined)
68
- return;
69
- if (!platform.robot.serialNumber) {
77
+ if (!robot.serialNumber) {
70
78
  platform.log.error('Robot serial number is undefined');
71
79
  return;
72
80
  }
73
- if (platform.robot.serialNumber !== duid) {
74
- platform.log.notice(`DUID mismatch: ${duid}, device serial number: ${platform.robot.serialNumber}`);
75
- return;
76
- }
77
81
  switch (messageSource) {
78
82
  case NotifyMessageTypes.ErrorOccurred: {
79
83
  const message = messageData;
80
84
  const operationalStateId = getOperationalErrorState(message.errorCode);
81
85
  if (operationalStateId) {
82
86
  platform.log.error(`Error occurred: ${message.errorCode}`);
83
- platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
87
+ robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
84
88
  }
85
89
  break;
86
90
  }
@@ -88,32 +92,37 @@ export class PlatformRunner {
88
92
  const message = messageData;
89
93
  const batteryLevel = message.percentage;
90
94
  if (batteryLevel) {
91
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
92
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
95
+ robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
96
+ robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
93
97
  }
94
98
  break;
95
99
  }
96
100
  case NotifyMessageTypes.LocalMessage: {
97
101
  const data = messageData;
102
+ const robot = platform.robots.get(duid);
103
+ if (!robot) {
104
+ platform.log.error(`Error2: Robot with DUID ${duid} not found`);
105
+ return;
106
+ }
98
107
  if (data) {
99
108
  const state = state_to_matter_state(data.state);
100
109
  if (state) {
101
- platform.robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
110
+ robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
102
111
  }
103
112
  const currentRoom = data.cleaning_info?.segment_id ?? -1;
104
113
  const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
105
114
  const isMappedArea = currentMappedAreas?.some((x) => x.areaId == currentRoom);
106
115
  if (currentRoom !== -1 && isMappedArea) {
107
- const roomMap = await this.getRoomMap();
116
+ const roomMap = await this.getRoomMap(duid);
108
117
  this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
109
118
  this.platform.log.debug('CurrentRoom:', currentRoom);
110
- platform.robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
119
+ robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
111
120
  }
112
121
  if (data.battery) {
113
122
  const batteryLevel = data.battery;
114
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
115
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(data.state, data.battery), platform.log);
116
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
123
+ robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
124
+ robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(data.state, data.battery), platform.log);
125
+ robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
117
126
  }
118
127
  const currentCleanModeSetting = {
119
128
  suctionPower: data.cleaning_info?.fan_power ?? data.fan_power,
@@ -127,10 +136,10 @@ export class PlatformRunner {
127
136
  const currentCleanMode = getCurrentCleanModeFunc(deviceData.model, this.platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)(currentCleanModeSetting);
128
137
  this.platform.log.debug(`Current clean mode: ${currentCleanMode}`);
129
138
  if (currentCleanMode) {
130
- platform.robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
139
+ robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
131
140
  }
132
141
  }
133
- this.processAdditionalProps(platform.robot, data);
142
+ this.processAdditionalProps(robot, data, duid);
134
143
  }
135
144
  break;
136
145
  }
@@ -149,19 +158,24 @@ export class PlatformRunner {
149
158
  const platform = this.platform;
150
159
  const messageTypes = Object.keys(data.dps).map(Number);
151
160
  const self = this;
161
+ const robot = platform.robots.get(duid);
162
+ if (robot === undefined) {
163
+ platform.log.error(`Error3: Robot with DUID ${duid} not found`);
164
+ return;
165
+ }
152
166
  messageTypes.forEach(async (messageType) => {
153
167
  switch (messageType) {
154
168
  case Protocol.status_update: {
155
169
  const status = Number(data.dps[messageType]);
156
170
  const matterState = state_to_matter_state(status);
157
171
  if (matterState) {
158
- platform.robot?.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
172
+ robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
159
173
  }
160
174
  const operationalStateId = state_to_matter_operational_status(status);
161
175
  if (operationalStateId) {
162
- const dssHasError = hasDockingStationError(platform.robot?.dockStationStatus);
163
- if (!(dssHasError && self.triggerDssError())) {
164
- platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
176
+ const dssHasError = hasDockingStationError(robot.dockStationStatus);
177
+ if (!(dssHasError && self.triggerDssError(robot))) {
178
+ robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
165
179
  }
166
180
  }
167
181
  break;
@@ -177,17 +191,17 @@ export class PlatformRunner {
177
191
  roboStatus = response.result[0];
178
192
  }
179
193
  if (roboStatus) {
180
- const message = { duid: duid, ...roboStatus };
194
+ const message = { ...roboStatus };
181
195
  platform.log.debug('rpc_response:', debugStringify(message));
182
- await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, true);
196
+ await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, duid, true);
183
197
  }
184
198
  break;
185
199
  }
186
200
  case Protocol.suction_power:
187
201
  case Protocol.water_box_mode: {
188
202
  await platform.roborockService?.getCleanModeData(duid).then((cleanModeData) => {
189
- if (cleanModeData && platform.robot) {
190
- const currentCleanMode = getCurrentCleanModeFunc(platform.robot.device.data.model, platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)({
203
+ if (cleanModeData) {
204
+ const currentCleanMode = getCurrentCleanModeFunc(robot.device.data.model, platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)({
191
205
  suctionPower: cleanModeData.suctionPower,
192
206
  waterFlow: cleanModeData.waterFlow,
193
207
  distance_off: cleanModeData.distance_off,
@@ -196,7 +210,7 @@ export class PlatformRunner {
196
210
  platform.log.debug(`Clean mode data: ${debugStringify(cleanModeData)}`);
197
211
  platform.log.debug(`Current clean mode: ${currentCleanMode}`);
198
212
  if (currentCleanMode) {
199
- platform.robot?.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
213
+ robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
200
214
  }
201
215
  }
202
216
  });
@@ -213,15 +227,19 @@ export class PlatformRunner {
213
227
  }
214
228
  });
215
229
  }
216
- async processAdditionalProps(robot, message) {
217
- const dssStatus = this.getDssStatus(message);
230
+ async processAdditionalProps(robot, message, duid) {
231
+ const dssStatus = this.getDssStatus(message, duid);
218
232
  if (dssStatus) {
219
- this.triggerDssError();
233
+ this.triggerDssError(robot);
220
234
  }
221
235
  }
222
- getDssStatus(message) {
236
+ getDssStatus(message, duid) {
223
237
  const platform = this.platform;
224
- const robot = platform.robot;
238
+ const robot = platform.robots.get(duid);
239
+ if (robot === undefined) {
240
+ platform.log.error(`Error4: Robot with DUID ${duid} not found`);
241
+ return undefined;
242
+ }
225
243
  if (platform.enableExperimentalFeature &&
226
244
  platform.enableExperimentalFeature.enableExperimentalFeature &&
227
245
  platform.enableExperimentalFeature.advancedFeature.includeDockStationStatus &&
@@ -247,52 +265,61 @@ export class PlatformRunner {
247
265
  }
248
266
  updateFromHomeData(homeData) {
249
267
  const platform = this.platform;
250
- if (platform.robot === undefined)
268
+ if (platform.robots.size === 0)
251
269
  return;
252
- const device = homeData.devices.find((d) => d.duid === platform.robot?.serialNumber);
253
- const deviceData = platform.robot?.device.data;
254
- if (!device || deviceData === undefined) {
255
- platform.log.error('Device not found in home data');
256
- return;
257
- }
258
- device.schema = homeData.products.find((prd) => prd.id == device.productId || prd.model == device.data.model)?.schema ?? [];
259
- this.platform.log.debug('updateFromHomeData-homeData:', debugStringify(homeData));
260
- this.platform.log.debug('updateFromHomeData-device:', debugStringify(device));
261
- const batteryLevel = getVacuumProperty(device, 'battery');
262
- if (batteryLevel) {
263
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel ? batteryLevel * 2 : 200, platform.log);
264
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
265
- }
266
- const state = getVacuumProperty(device, 'state');
267
- const matterState = state_to_matter_state(state);
268
- if (!state || !matterState) {
269
- return;
270
- }
271
- this.platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
272
- if (matterState) {
273
- platform.robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
274
- }
275
- const operationalStateId = state_to_matter_operational_status(state);
276
- if (operationalStateId) {
277
- const dssHasError = hasDockingStationError(platform.robot?.dockStationStatus);
278
- this.platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(platform.robot?.dockStationStatus ?? {})}`);
279
- if (!(dssHasError && this.triggerDssError())) {
280
- this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
281
- platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
270
+ const devices = homeData.devices.filter((d) => platform.robots.has(d.duid));
271
+ for (const device of devices) {
272
+ const robot = platform.robots.get(device.duid);
273
+ if (robot === undefined) {
274
+ platform.log.error(`Error5: Robot with DUID ${device.duid} not found`);
275
+ continue;
276
+ }
277
+ const deviceData = robot.device.data;
278
+ if (!device || deviceData === undefined) {
279
+ platform.log.error('Device not found in home data');
280
+ return;
281
+ }
282
+ device.schema = homeData.products.find((prd) => prd.id === device.productId || prd.model === device.data.model)?.schema ?? [];
283
+ this.platform.log.debug('updateFromHomeData-homeData:', debugStringify(homeData));
284
+ this.platform.log.debug('updateFromHomeData-device:', debugStringify(device));
285
+ const batteryLevel = getVacuumProperty(device, 'battery');
286
+ this.platform.log.debug('updateFromHomeData-schema:' + debugStringify(device.schema));
287
+ this.platform.log.debug('updateFromHomeData-battery:' + debugStringify(device.deviceStatus));
288
+ if (batteryLevel) {
289
+ robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel ? batteryLevel * 2 : 200, platform.log);
290
+ robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
291
+ }
292
+ const state = getVacuumProperty(device, 'state');
293
+ const matterState = state_to_matter_state(state);
294
+ if (!state || !matterState) {
295
+ return;
296
+ }
297
+ this.platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
298
+ if (matterState) {
299
+ robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
300
+ }
301
+ const operationalStateId = state_to_matter_operational_status(state);
302
+ if (operationalStateId) {
303
+ const dssHasError = hasDockingStationError(robot.dockStationStatus);
304
+ this.platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(robot.dockStationStatus ?? {})}`);
305
+ if (!(dssHasError && this.triggerDssError(robot))) {
306
+ this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
307
+ robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
308
+ }
309
+ }
310
+ if (batteryLevel) {
311
+ robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
282
312
  }
283
- }
284
- if (batteryLevel) {
285
- platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
286
313
  }
287
314
  }
288
- triggerDssError() {
315
+ triggerDssError(robot) {
289
316
  const platform = this.platform;
290
- const currentOperationState = platform?.robot?.getAttribute(RvcOperationalState.Cluster.id, 'operationalState');
317
+ const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState');
291
318
  if (currentOperationState === RvcOperationalState.OperationalState.Error) {
292
319
  return true;
293
320
  }
294
321
  if (currentOperationState === RvcOperationalState.OperationalState.Docked) {
295
- platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
322
+ robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
296
323
  return true;
297
324
  }
298
325
  return false;
package/dist/rvc.js CHANGED
@@ -1,10 +1,9 @@
1
- import { RoboticVacuumCleaner } from 'matterbridge';
1
+ import { RoboticVacuumCleaner } from 'matterbridge/devices';
2
2
  import { getOperationalStates, getSupportedAreas, getSupportedCleanModes, getSupportedRunModes } from './initialData/index.js';
3
3
  import { RvcOperationalState } from 'matterbridge/matter/clusters';
4
4
  export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
5
5
  username;
6
6
  device;
7
- rrHomeId;
8
7
  roomInfo;
9
8
  dockStationStatus;
10
9
  constructor(username, device, roomMap, routineAsRoom, enableExperimentalFeature, log) {
@@ -15,10 +14,10 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
15
14
  log.debug(`Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${enableExperimentalFeature?.advancedFeature?.forceRunAtDefault}`);
16
15
  log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
17
16
  log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
18
- super(deviceName, device.duid, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(), supportedAreas, undefined, supportedAreas[0].areaId);
17
+ const bridgeMode = enableExperimentalFeature?.advancedFeature.enableServerMode ? 'server' : undefined;
18
+ super(deviceName, device.duid, bridgeMode, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(), supportedAreas, undefined, supportedAreas[0].areaId);
19
19
  this.username = username;
20
20
  this.device = device;
21
- this.rrHomeId = device.rrHomeId;
22
21
  }
23
22
  configurateHandler(behaviorHandler) {
24
23
  this.addCommandHandler('identify', async ({ request, cluster, attributes, endpoint }) => {
package/jest.config.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export default {
2
+ preset: 'ts-jest/presets/default-esm',
2
3
  testEnvironment: 'node',
3
4
  extensionsToTreatAsEsm: ['.ts'],
4
5
  moduleNameMapper: {
@@ -15,7 +16,7 @@ export default {
15
16
  ],
16
17
  },
17
18
  transformIgnorePatterns: ['/node_modules/'],
18
- testPathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/'],
19
+ testPathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/', 'webui', '.shouldnotcommit'],
19
20
  coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/', '/src/mock/'],
20
21
  setupFiles: ['./jest.setup.js'],
21
22
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.0.8-rc09",
4
+ "version": "1.1.0-rc02",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -11,7 +11,8 @@
11
11
  "showRoutinesAsRooms": false,
12
12
  "includeDockStationStatus": false,
13
13
  "forceRunAtDefault": false,
14
- "useVacationModeToSendVacuumToDock": false
14
+ "useVacationModeToSendVacuumToDock": false,
15
+ "enableServerMode": false
15
16
  },
16
17
  "cleanModeSettings": {
17
18
  "enableCleanModeMapping": false,
@@ -35,4 +36,4 @@
35
36
  "debug": true,
36
37
  "unregisterOnShutdown": false,
37
38
  "enableExperimentalFeature": false
38
- }
39
+ }
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.0.8-rc09 by https://github.com/RinDevJunior",
3
+ "description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc02 by https://github.com/RinDevJunior",
4
4
  "type": "object",
5
- "required": ["username", "password"],
5
+ "required": [
6
+ "username",
7
+ "password"
8
+ ],
6
9
  "properties": {
7
10
  "name": {
8
11
  "description": "Plugin name",
@@ -57,7 +60,9 @@
57
60
  "const": true
58
61
  }
59
62
  },
60
- "required": ["enableExperimentalFeature"]
63
+ "required": [
64
+ "enableExperimentalFeature"
65
+ ]
61
66
  },
62
67
  "then": {
63
68
  "properties": {
@@ -81,7 +86,12 @@
81
86
  "default": false
82
87
  },
83
88
  "useVacationModeToSendVacuumToDock": {
84
- "title": "Use Vacation Mode To Send Vacuum To Dock",
89
+ "title": "Use \"Vacation\" Mode To Send Vacuum To Dock",
90
+ "type": "boolean",
91
+ "default": false
92
+ },
93
+ "enableServerMode": {
94
+ "description": "Enable the Robot Vacuum Cleaner in server mode (Each vacuum will have its own server).",
85
95
  "type": "boolean",
86
96
  "default": false
87
97
  }
@@ -104,7 +114,9 @@
104
114
  "const": true
105
115
  }
106
116
  },
107
- "required": ["enableCleanModeMapping"]
117
+ "required": [
118
+ "enableCleanModeMapping"
119
+ ]
108
120
  },
109
121
  "then": {
110
122
  "properties": {
@@ -149,7 +161,9 @@
149
161
  "default": 25
150
162
  }
151
163
  },
152
- "required": ["distanceOff"]
164
+ "required": [
165
+ "distanceOff"
166
+ ]
153
167
  }
154
168
  }
155
169
  ]
@@ -186,7 +200,9 @@
186
200
  "default": 25
187
201
  }
188
202
  },
189
- "required": ["distanceOff"]
203
+ "required": [
204
+ "distanceOff"
205
+ ]
190
206
  }
191
207
  }
192
208
  ]
@@ -216,20 +232,36 @@
216
232
  "fanMode": {
217
233
  "type": "string",
218
234
  "description": "Suction power mode to use (e.g., 'Quiet', 'Balanced', 'Turbo', 'Max', 'MaxPlus').",
219
- "enum": ["Quiet", "Balanced", "Turbo", "Max", "MaxPlus"],
235
+ "enum": [
236
+ "Quiet",
237
+ "Balanced",
238
+ "Turbo",
239
+ "Max",
240
+ "MaxPlus"
241
+ ],
220
242
  "default": "Balanced"
221
243
  },
222
244
  "waterFlowMode": {
223
245
  "type": "string",
224
246
  "description": "Water flow mode to use (e.g., 'Low', 'Medium', 'High', 'CustomizeWithDistanceOff').",
225
- "enum": ["Low", "Medium", "High", "CustomizeWithDistanceOff"],
247
+ "enum": [
248
+ "Low",
249
+ "Medium",
250
+ "High",
251
+ "CustomizeWithDistanceOff"
252
+ ],
226
253
  "default": "Medium"
227
254
  },
228
255
  "mopRouteMode": {
229
256
  "type": "string",
230
257
  "description": "Mop route intensity to use (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
231
- "enum": ["Standard", "Deep", "DeepPlus", "Fast"],
258
+ "enum": [
259
+ "Standard",
260
+ "Deep",
261
+ "DeepPlus",
262
+ "Fast"
263
+ ],
232
264
  "default": "Standard"
233
265
  }
234
266
  }
235
- }
267
+ }