matterbridge-roborock-vacuum-plugin 1.1.0-rc10 → 1.1.0-rc12

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.
Files changed (42) hide show
  1. package/dist/initialData/getSupportedAreas.js +6 -1
  2. package/dist/platform.js +4 -6
  3. package/dist/platformRunner.js +33 -6
  4. package/dist/roborockCommunication/RESTAPI/roborockIoTApi.js +11 -0
  5. package/dist/roborockCommunication/Zmodel/mapInfo.js +8 -6
  6. package/dist/roborockCommunication/broadcast/abstractClient.js +8 -1
  7. package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +3 -1
  8. package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +3 -2
  9. package/dist/roborockCommunication/broadcast/messageProcessor.js +6 -3
  10. package/dist/roborockCommunication/helper/messageDeserializer.js +7 -1
  11. package/dist/roborockCommunication/helper/messageSerializer.js +6 -0
  12. package/dist/roborockService.js +53 -6
  13. package/eslint.config.js +1 -1
  14. package/matterbridge-roborock-vacuum-plugin.config.json +2 -2
  15. package/matterbridge-roborock-vacuum-plugin.schema.json +37 -10
  16. package/package.json +1 -1
  17. package/src/initialData/getSupportedAreas.ts +8 -1
  18. package/src/platform.ts +4 -7
  19. package/src/platformRunner.ts +40 -8
  20. package/src/roborockCommunication/RESTAPI/roborockIoTApi.ts +13 -1
  21. package/src/roborockCommunication/Zmodel/mapInfo.ts +9 -6
  22. package/src/roborockCommunication/Zmodel/userData.ts +0 -3
  23. package/src/roborockCommunication/broadcast/abstractClient.ts +10 -3
  24. package/src/roborockCommunication/broadcast/client.ts +1 -1
  25. package/src/roborockCommunication/broadcast/clientRouter.ts +1 -1
  26. package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +3 -1
  27. package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +4 -3
  28. package/src/roborockCommunication/broadcast/messageProcessor.ts +9 -5
  29. package/src/roborockCommunication/helper/messageDeserializer.ts +7 -1
  30. package/src/roborockCommunication/helper/messageSerializer.ts +6 -0
  31. package/src/roborockService.ts +62 -14
  32. package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +5 -4
  33. package/web-for-testing/README.md +47 -0
  34. package/web-for-testing/nodemon.json +7 -0
  35. package/web-for-testing/package-lock.json +6598 -0
  36. package/web-for-testing/package.json +36 -0
  37. package/web-for-testing/src/accountStore.ts +10 -0
  38. package/web-for-testing/src/app.ts +194 -0
  39. package/web-for-testing/tsconfig-ext.json +19 -0
  40. package/web-for-testing/tsconfig.json +23 -0
  41. package/web-for-testing/views/index.ejs +172 -0
  42. package/web-for-testing/watch.mjs +93 -0
@@ -4,7 +4,12 @@ export function getSupportedAreas(rooms, roomMap, log) {
4
4
  log?.debug('getSupportedAreas', debugStringify(rooms));
5
5
  log?.debug('getSupportedAreas', roomMap ? debugStringify(roomMap) : 'undefined');
6
6
  if (!rooms || rooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
7
- log?.error('No rooms found');
7
+ if (!rooms || rooms.length === 0) {
8
+ log?.error('No rooms found');
9
+ }
10
+ if (!roomMap || !roomMap.rooms || roomMap.rooms.length == 0) {
11
+ log?.error('No room map found');
12
+ }
8
13
  return [
9
14
  {
10
15
  areaId: 1,
package/dist/platform.js CHANGED
@@ -26,8 +26,8 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
26
26
  rrHomeId;
27
27
  constructor(matterbridge, log, config) {
28
28
  super(matterbridge, log, config);
29
- if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.5')) {
30
- throw new Error(`This plugin requires Matterbridge version >= "3.1.5". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
29
+ if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.6')) {
30
+ throw new Error(`This plugin requires Matterbridge version >= "3.1.6". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
31
31
  }
32
32
  this.log.info('Initializing platform:', this.config.name);
33
33
  if (config.whiteList === undefined)
@@ -140,7 +140,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
140
140
  if (!configurateSuccess.get(duid)) {
141
141
  continue;
142
142
  }
143
- await this.roborockService.activateDeviceNotify(robot.device);
143
+ this.roborockService.activateDeviceNotify(robot.device);
144
144
  }
145
145
  await this.platformRunner?.requestHomeData();
146
146
  this.log.info('onConfigurateDevice finished');
@@ -157,6 +157,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
157
157
  return false;
158
158
  }
159
159
  if (vacuum.rooms === undefined || vacuum.rooms.length === 0) {
160
+ this.log.error(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
160
161
  const map_info = await this.roborockService.getMapInformation(vacuum.duid);
161
162
  const rooms = map_info?.maps?.[0]?.rooms ?? [];
162
163
  vacuum.rooms = rooms;
@@ -196,7 +197,4 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
196
197
  this.log.logLevel = logLevel;
197
198
  return Promise.resolve();
198
199
  }
199
- sleep(ms) {
200
- return new Promise((resolve) => setTimeout(resolve, ms));
201
- }
202
200
  }
@@ -35,9 +35,19 @@ export class PlatformRunner {
35
35
  async getRoomMapFromDevice(device) {
36
36
  const platform = this.platform;
37
37
  const rooms = device?.rooms ?? [];
38
+ platform.log.error(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
38
39
  if (device && platform.roborockService) {
39
40
  const roomData = await platform.roborockService.getRoomMappings(device.duid);
40
- return new RoomMap(roomData ?? [], rooms);
41
+ if (roomData !== undefined && roomData.length > 0) {
42
+ platform.log.error(`getRoomMapFromDevice - roomData: ${debugStringify(roomData ?? [])}`);
43
+ return new RoomMap(roomData ?? [], rooms);
44
+ }
45
+ const mapInfo = await platform.roborockService.getMapInformation(device.duid);
46
+ if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
47
+ platform.log.error(`getRoomMapFromDevice - mapInfo: ${debugStringify(mapInfo.maps)}`);
48
+ const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.name)]);
49
+ return new RoomMap(roomDataMap, rooms);
50
+ }
41
51
  }
42
52
  return new RoomMap([], rooms);
43
53
  }
@@ -53,8 +63,18 @@ export class PlatformRunner {
53
63
  const rooms = robot.device.rooms ?? [];
54
64
  if (robot.roomInfo === undefined) {
55
65
  const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
56
- robot.roomInfo = new RoomMap(roomData ?? [], rooms);
57
- return robot.roomInfo;
66
+ if (roomData !== undefined && roomData.length > 0) {
67
+ robot.roomInfo = new RoomMap(roomData ?? [], rooms);
68
+ return robot.roomInfo;
69
+ }
70
+ }
71
+ if (robot.roomInfo === undefined) {
72
+ const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
73
+ if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
74
+ platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.maps)}`);
75
+ const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.name)]);
76
+ robot.roomInfo = new RoomMap(roomDataMap, rooms);
77
+ }
58
78
  }
59
79
  return robot.roomInfo;
60
80
  }
@@ -109,13 +129,20 @@ export class PlatformRunner {
109
129
  if (state) {
110
130
  robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
111
131
  }
112
- const currentRoom = data.cleaning_info?.segment_id ?? -1;
113
132
  const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
133
+ const roomMap = await this.getRoomMap(duid);
134
+ const targetRoom = data.cleaning_info?.target_segment_id ?? -1;
135
+ const isTargetMappedArea = currentMappedAreas?.some((x) => x.areaId == targetRoom);
136
+ if (targetRoom !== -1 && isTargetMappedArea) {
137
+ this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
138
+ this.platform.log.debug(`TargetRoom: ${targetRoom}, room name: ${roomMap?.rooms.find((x) => x.id === targetRoom)?.displayName ?? 'unknown'}`);
139
+ robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', targetRoom, platform.log);
140
+ }
141
+ const currentRoom = data.cleaning_info?.segment_id ?? -1;
114
142
  const isMappedArea = currentMappedAreas?.some((x) => x.areaId == currentRoom);
115
143
  if (currentRoom !== -1 && isMappedArea) {
116
- const roomMap = await this.getRoomMap(duid);
117
144
  this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
118
- this.platform.log.debug('CurrentRoom:', currentRoom);
145
+ this.platform.log.debug(`CurrentRoom: ${currentRoom}, room name: ${roomMap?.rooms.find((x) => x.id === currentRoom)?.displayName ?? 'unknown'}`);
119
146
  robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
120
147
  }
121
148
  if (data.battery) {
@@ -81,4 +81,15 @@ export class RoborockIoTApi {
81
81
  return undefined;
82
82
  }
83
83
  }
84
+ async getCustom(url) {
85
+ const result = await this.api.get(url);
86
+ const apiResponse = result.data;
87
+ if (apiResponse.result) {
88
+ return apiResponse.result;
89
+ }
90
+ else {
91
+ this.logger.error('Failed to execute scene');
92
+ return undefined;
93
+ }
94
+ }
84
95
  }
@@ -6,12 +6,14 @@ export class MapInfo {
6
6
  this.maps.push({
7
7
  id: map.mapFlag,
8
8
  name: decodeComponent(map.name)?.toLowerCase(),
9
- rooms: map.rooms.map((room) => {
10
- return {
11
- id: room.iot_name_id,
12
- name: room.iot_name,
13
- };
14
- }),
9
+ rooms: map.rooms && map.rooms.length > 0
10
+ ? map.rooms.map((room) => {
11
+ return {
12
+ id: room.id,
13
+ name: room.iot_name_id,
14
+ };
15
+ })
16
+ : [],
15
17
  });
16
18
  });
17
19
  }
@@ -29,8 +29,15 @@ export class AbstractClient {
29
29
  }
30
30
  async get(duid, request) {
31
31
  return new Promise((resolve, reject) => {
32
- this.syncMessageListener.waitFor(request.messageId, (response) => resolve(response), reject);
32
+ this.syncMessageListener.waitFor(request.messageId, request, (response) => resolve(response), reject);
33
33
  this.send(duid, request);
34
+ })
35
+ .then((result) => {
36
+ return result;
37
+ })
38
+ .catch((error) => {
39
+ this.logger.error(error.message);
40
+ return undefined;
34
41
  });
35
42
  }
36
43
  registerDevice(duid, localKey, pv) {
@@ -21,7 +21,9 @@ export class ConnectionStateListener {
21
21
  }
22
22
  if (this.client.retryCount > 10) {
23
23
  this.logger.error(`Device with DUID ${duid} has exceeded retry limit, not re-registering.`);
24
- this.changeToSecureConnection(duid);
24
+ if (this.changeToSecureConnection) {
25
+ this.changeToSecureConnection(duid);
26
+ }
25
27
  return;
26
28
  }
27
29
  this.client.retryCount++;
@@ -1,3 +1,4 @@
1
+ import { debugStringify } from 'matterbridge/logger';
1
2
  import { Protocol } from '../../model/protocol.js';
2
3
  export class SyncMessageListener {
3
4
  pending = new Map();
@@ -5,11 +6,11 @@ export class SyncMessageListener {
5
6
  constructor(logger) {
6
7
  this.logger = logger;
7
8
  }
8
- waitFor(messageId, resolve, reject) {
9
+ waitFor(messageId, request, resolve, reject) {
9
10
  this.pending.set(messageId, resolve);
10
11
  setTimeout(() => {
11
12
  this.pending.delete(messageId);
12
- reject();
13
+ reject(new Error(`Message timeout for messageId: ${messageId}, request: ${debugStringify(request)}`));
13
14
  }, 10000);
14
15
  }
15
16
  async onMessage(message) {
@@ -25,12 +25,15 @@ export class MessageProcessor {
25
25
  async getDeviceStatus(duid) {
26
26
  const request = new RequestMessage({ method: 'get_status' });
27
27
  const response = await this.client.get(duid, request);
28
- this.logger?.debug('Device status: ', debugStringify(response));
29
- return new DeviceStatus(response);
28
+ if (response) {
29
+ this.logger?.debug('Device status: ', debugStringify(response));
30
+ return new DeviceStatus(response);
31
+ }
32
+ return undefined;
30
33
  }
31
34
  async getRooms(duid, rooms) {
32
35
  const request = new RequestMessage({ method: 'get_room_mapping' });
33
- return this.client.get(duid, request).then((response) => new RoomInfo(rooms, response));
36
+ return this.client.get(duid, request).then((response) => new RoomInfo(rooms, response ?? []));
34
37
  }
35
38
  async gotoDock(duid) {
36
39
  const request = new RequestMessage({ method: 'app_charge' });
@@ -8,6 +8,7 @@ export class MessageDeserializer {
8
8
  context;
9
9
  messageParser;
10
10
  logger;
11
+ supportedVersions = ['1.0', 'A01', 'B01'];
11
12
  constructor(context, logger) {
12
13
  this.context = context;
13
14
  this.logger = logger;
@@ -28,7 +29,7 @@ export class MessageDeserializer {
28
29
  }
29
30
  deserialize(duid, message) {
30
31
  const version = message.toString('latin1', 0, 3);
31
- if (version !== '1.0' && version !== 'A01') {
32
+ if (!this.supportedVersions.includes(version)) {
32
33
  throw new Error('unknown protocol version ' + version);
33
34
  }
34
35
  const crc32 = CRC32.buf(message.subarray(0, message.length - 4)) >>> 0;
@@ -52,6 +53,11 @@ export class MessageDeserializer {
52
53
  const decipher = crypto.createDecipheriv('aes-128-cbc', localKey, iv);
53
54
  data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
54
55
  }
56
+ else if (version == 'B01') {
57
+ const iv = CryptoUtils.md5hex(data.random.toString(16).padStart(8, '0') + '5wwh9ikChRjASpMU8cxg7o1d2E').substring(9, 25);
58
+ const decipher = crypto.createDecipheriv('aes-128-cbc', localKey, iv);
59
+ data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
60
+ }
55
61
  if (data.protocol == Protocol.map_response) {
56
62
  return new ResponseMessage(duid, { dps: { id: 0, result: null } });
57
63
  }
@@ -54,6 +54,12 @@ export class MessageSerializer {
54
54
  const cipher = crypto.createCipheriv('aes-128-cbc', encoder.encode(localKey), iv);
55
55
  encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
56
56
  }
57
+ else if (version == 'B01') {
58
+ const encoder = new TextEncoder();
59
+ const iv = CryptoUtils.md5hex(this.random.toString(16).padStart(8, '0') + '5wwh9ikChRjASpMU8cxg7o1d2E').substring(9, 25);
60
+ const cipher = crypto.createCipheriv('aes-128-cbc', encoder.encode(localKey), iv);
61
+ encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
62
+ }
57
63
  else {
58
64
  throw new Error('unable to build the message: unsupported protocol version: ' + version);
59
65
  }
@@ -15,12 +15,14 @@ export default class RoborockService {
15
15
  messageProcessorMap = new Map();
16
16
  ipMap = new Map();
17
17
  localClientMap = new Map();
18
+ mqttAlwaysOnDevices = new Map();
18
19
  clientManager;
19
20
  refreshInterval;
20
21
  requestDeviceStatusInterval;
21
22
  supportedAreas = new Map();
22
23
  supportedRoutines = new Map();
23
24
  selectedAreas = new Map();
25
+ vacuumNeedAPIV3 = ['roborock.vacuum.ss07'];
24
26
  constructor(authenticateApiSupplier = (logger) => new RoborockAuthenticateApi(logger), iotApiSupplier = (logger, ud) => new RoborockIoTApi(ud, logger), refreshInterval, clientManager, logger) {
25
27
  this.logger = logger;
26
28
  this.loginApi = authenticateApiSupplier(logger);
@@ -77,7 +79,8 @@ export default class RoborockService {
77
79
  this.logger.debug('RoborockService - getMapInformation', duid);
78
80
  assert(this.messageClient !== undefined);
79
81
  return this.messageClient.get(duid, new RequestMessage({ method: 'get_multi_maps_list' })).then((response) => {
80
- return new MapInfo(response[0]);
82
+ this.logger.debug('RoborockService - getMapInformation response', debugStringify(response ?? []));
83
+ return response ? new MapInfo(response[0]) : undefined;
81
84
  });
82
85
  }
83
86
  async changeCleanMode(duid, { suctionPower, waterFlow, distance_off, mopRoute }) {
@@ -147,6 +150,17 @@ export default class RoborockService {
147
150
  async customSend(duid, request) {
148
151
  return this.getMessageProcessor(duid)?.sendCustomMessage(duid, request);
149
152
  }
153
+ async getCustomAPI(url) {
154
+ this.logger.debug('RoborockService - getCustomAPI', url);
155
+ assert(this.iotApi !== undefined);
156
+ try {
157
+ return await this.iotApi.getCustom(url);
158
+ }
159
+ catch (error) {
160
+ this.logger.error(`Failed to get custom API with url ${url}: ${error ? debugStringify(error) : 'undefined'}`);
161
+ return { result: undefined, error: `Failed to get custom API with url ${url}` };
162
+ }
163
+ }
150
164
  stopService() {
151
165
  if (this.messageClient) {
152
166
  this.messageClient.disconnect();
@@ -175,14 +189,14 @@ export default class RoborockService {
175
189
  setDeviceNotify(callback) {
176
190
  this.deviceNotify = callback;
177
191
  }
178
- async activateDeviceNotify(device) {
192
+ activateDeviceNotify(device) {
179
193
  const self = this;
180
194
  this.logger.debug('Requesting device info for device', device.duid);
181
195
  const messageProcessor = this.getMessageProcessor(device.duid);
182
196
  this.requestDeviceStatusInterval = setInterval(async () => {
183
197
  if (messageProcessor) {
184
198
  await messageProcessor.getDeviceStatus(device.duid).then((response) => {
185
- if (self.deviceNotify) {
199
+ if (self.deviceNotify && response) {
186
200
  const message = { duid: device.duid, ...response.errorStatus, ...response.message };
187
201
  self.logger.debug('Device status update', debugStringify(message));
188
202
  self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
@@ -208,6 +222,15 @@ export default class RoborockService {
208
222
  const scenes = (await this.iotApi.getScenes(homeDetails.rrHomeId)) ?? [];
209
223
  const products = new Map();
210
224
  homeData.products.forEach((p) => products.set(p.id, p.model));
225
+ if (homeData.products.some((p) => this.vacuumNeedAPIV3.includes(p.model))) {
226
+ this.logger.debug('Using v3 API for home data retrieval');
227
+ const homeDataV3 = await this.iotApi.getHomev3(homeDetails.rrHomeId);
228
+ if (!homeDataV3) {
229
+ throw new Error('Failed to retrieve the home data from v3 API');
230
+ }
231
+ homeData.devices = [...homeData.devices, ...homeDataV3.devices.filter((d) => !homeData.devices.some((x) => x.duid === d.duid))];
232
+ homeData.receivedDevices = [...homeData.receivedDevices, ...homeDataV3.receivedDevices.filter((d) => !homeData.receivedDevices.some((x) => x.duid === d.duid))];
233
+ }
211
234
  if (homeData.rooms.length === 0) {
212
235
  const homeDataV2 = await this.iotApi.getHomev2(homeDetails.rrHomeId);
213
236
  if (homeDataV2 && homeDataV2.rooms && homeDataV2.rooms.length > 0) {
@@ -259,6 +282,18 @@ export default class RoborockService {
259
282
  const products = new Map();
260
283
  homeData.products.forEach((p) => products.set(p.id, p.model));
261
284
  const devices = homeData.devices.length > 0 ? homeData.devices : homeData.receivedDevices;
285
+ if (homeData.rooms.length === 0) {
286
+ const homeDataV3 = await this.iotApi.getHomev3(homeid);
287
+ if (homeDataV3 && homeDataV3.rooms && homeDataV3.rooms.length > 0) {
288
+ homeData.rooms = homeDataV3.rooms;
289
+ }
290
+ else {
291
+ const homeDataV1 = await this.iotApi.getHome(homeid);
292
+ if (homeDataV1 && homeDataV1.rooms && homeDataV1.rooms.length > 0) {
293
+ homeData.rooms = homeDataV1.rooms;
294
+ }
295
+ }
296
+ }
262
297
  const dvs = devices.map((device) => {
263
298
  return {
264
299
  ...device,
@@ -297,9 +332,9 @@ export default class RoborockService {
297
332
  getRoomMappings(duid) {
298
333
  if (!this.messageClient) {
299
334
  this.logger.warn('messageClient not initialized. Waititing for next execution');
300
- return undefined;
335
+ return Promise.resolve(undefined);
301
336
  }
302
- return this.messageClient.get(duid, new RequestMessage({ method: 'get_room_mapping' }));
337
+ return this.messageClient.get(duid, new RequestMessage({ method: 'get_room_mapping', secure: this.isRequestSecure(duid) }));
303
338
  }
304
339
  async initializeMessageClient(username, device, userdata) {
305
340
  if (this.clientManager === undefined) {
@@ -351,6 +386,15 @@ export default class RoborockService {
351
386
  },
352
387
  });
353
388
  this.messageProcessorMap.set(device.duid, messageProcessor);
389
+ this.logger.debug('Checking if device supports local connection', device.pv, device.data.model, device.duid);
390
+ if (device.pv === 'B01') {
391
+ this.logger.warn('Device does not support local connection', device.duid);
392
+ this.mqttAlwaysOnDevices.set(device.duid, true);
393
+ return true;
394
+ }
395
+ else {
396
+ this.mqttAlwaysOnDevices.set(device.duid, false);
397
+ }
354
398
  this.logger.debug('Local device', device.duid);
355
399
  let localIp = this.ipMap.get(device.duid);
356
400
  try {
@@ -389,7 +433,7 @@ export default class RoborockService {
389
433
  return true;
390
434
  }
391
435
  onLocalClientDisconnect(duid) {
392
- this.logger.debug('Local client disconnected for device', duid);
436
+ this.mqttAlwaysOnDevices.set(duid, true);
393
437
  }
394
438
  sleep(ms) {
395
439
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -399,4 +443,7 @@ export default class RoborockService {
399
443
  this.iotApi = this.iotApiFactory(this.logger, userdata);
400
444
  return userdata;
401
445
  }
446
+ isRequestSecure(duid) {
447
+ return this.mqttAlwaysOnDevices.get(duid) ?? false;
448
+ }
402
449
  }
package/eslint.config.js CHANGED
@@ -9,7 +9,7 @@ import eslintPluginN from 'eslint-plugin-n';
9
9
  export default [
10
10
  {
11
11
  name: 'global ignores',
12
- ignores: ['dist/', 'build/', 'node_modules/', 'coverage/', 'frontend/', 'rock-s0/', 'webui/', 'exampleData/', '.shouldnotcommit/'],
12
+ ignores: ['dist/', 'build/', 'node_modules/', 'coverage/', 'frontend/', 'rock-s0/', 'webui/', 'exampleData/', '.shouldnotcommit/', 'web-for-testing/'],
13
13
  },
14
14
  eslint.configs.recommended,
15
15
  ...tseslint.configs.strict,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.1.0-rc10",
4
+ "version": "1.1.0-rc12",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -37,4 +37,4 @@
37
37
  "debug": true,
38
38
  "unregisterOnShutdown": false,
39
39
  "enableExperimentalFeature": false
40
- }
40
+ }
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc10 by https://github.com/RinDevJunior",
3
+ "description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc12 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": {
@@ -114,7 +119,9 @@
114
119
  "const": true
115
120
  }
116
121
  },
117
- "required": ["enableCleanModeMapping"]
122
+ "required": [
123
+ "enableCleanModeMapping"
124
+ ]
118
125
  },
119
126
  "then": {
120
127
  "properties": {
@@ -159,7 +166,9 @@
159
166
  "default": 25
160
167
  }
161
168
  },
162
- "required": ["distanceOff"]
169
+ "required": [
170
+ "distanceOff"
171
+ ]
163
172
  }
164
173
  }
165
174
  ]
@@ -196,7 +205,9 @@
196
205
  "default": 25
197
206
  }
198
207
  },
199
- "required": ["distanceOff"]
208
+ "required": [
209
+ "distanceOff"
210
+ ]
200
211
  }
201
212
  }
202
213
  ]
@@ -226,20 +237,36 @@
226
237
  "fanMode": {
227
238
  "type": "string",
228
239
  "description": "Suction power mode to use (e.g., 'Quiet', 'Balanced', 'Turbo', 'Max', 'MaxPlus').",
229
- "enum": ["Quiet", "Balanced", "Turbo", "Max", "MaxPlus"],
240
+ "enum": [
241
+ "Quiet",
242
+ "Balanced",
243
+ "Turbo",
244
+ "Max",
245
+ "MaxPlus"
246
+ ],
230
247
  "default": "Balanced"
231
248
  },
232
249
  "waterFlowMode": {
233
250
  "type": "string",
234
251
  "description": "Water flow mode to use (e.g., 'Low', 'Medium', 'High', 'CustomizeWithDistanceOff').",
235
- "enum": ["Low", "Medium", "High", "CustomizeWithDistanceOff"],
252
+ "enum": [
253
+ "Low",
254
+ "Medium",
255
+ "High",
256
+ "CustomizeWithDistanceOff"
257
+ ],
236
258
  "default": "Medium"
237
259
  },
238
260
  "mopRouteMode": {
239
261
  "type": "string",
240
262
  "description": "Mop route intensity to use (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
241
- "enum": ["Standard", "Deep", "DeepPlus", "Fast"],
263
+ "enum": [
264
+ "Standard",
265
+ "Deep",
266
+ "DeepPlus",
267
+ "Fast"
268
+ ],
242
269
  "default": "Standard"
243
270
  }
244
271
  }
245
- }
272
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
- "version": "1.1.0-rc10",
3
+ "version": "1.1.0-rc12",
4
4
  "description": "Matterbridge Roborock Vacuum Plugin",
5
5
  "author": "https://github.com/RinDevJunior",
6
6
  "license": "MIT",
@@ -9,7 +9,14 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
9
9
  log?.debug('getSupportedAreas', roomMap ? debugStringify(roomMap) : 'undefined');
10
10
 
11
11
  if (!rooms || rooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
12
- log?.error('No rooms found');
12
+ if (!rooms || rooms.length === 0) {
13
+ log?.error('No rooms found');
14
+ }
15
+
16
+ if (!roomMap || !roomMap.rooms || roomMap.rooms.length == 0) {
17
+ log?.error('No room map found');
18
+ }
19
+
13
20
  return [
14
21
  {
15
22
  areaId: 1,
package/src/platform.ts CHANGED
@@ -32,9 +32,9 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
32
32
  super(matterbridge, log, config);
33
33
 
34
34
  // Verify that Matterbridge is the correct version
35
- if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.5')) {
35
+ if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.6')) {
36
36
  throw new Error(
37
- `This plugin requires Matterbridge version >= "3.1.5". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`,
37
+ `This plugin requires Matterbridge version >= "3.1.6". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`,
38
38
  );
39
39
  }
40
40
  this.log.info('Initializing platform:', this.config.name);
@@ -199,7 +199,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
199
199
  if (!configurateSuccess.get(duid)) {
200
200
  continue;
201
201
  }
202
- await this.roborockService.activateDeviceNotify(robot.device);
202
+ this.roborockService.activateDeviceNotify(robot.device);
203
203
  }
204
204
 
205
205
  await this.platformRunner?.requestHomeData();
@@ -224,6 +224,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
224
224
  }
225
225
 
226
226
  if (vacuum.rooms === undefined || vacuum.rooms.length === 0) {
227
+ this.log.error(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
227
228
  const map_info = await this.roborockService.getMapInformation(vacuum.duid);
228
229
  const rooms = map_info?.maps?.[0]?.rooms ?? [];
229
230
  vacuum.rooms = rooms;
@@ -279,8 +280,4 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
279
280
  this.log.logLevel = logLevel;
280
281
  return Promise.resolve();
281
282
  }
282
-
283
- private sleep(ms: number): Promise<void> {
284
- return new Promise((resolve) => setTimeout(resolve, ms));
285
- }
286
283
  }