matterbridge-roborock-vacuum-plugin 1.1.0-rc15 → 1.1.0-rc17

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 (43) hide show
  1. package/.github/workflows/build.yml +5 -1
  2. package/.github/workflows/coverage.yml +5 -1
  3. package/CHANGELOG.md +62 -0
  4. package/dist/helper.js +24 -21
  5. package/dist/initialData/getSupportedAreas.js +91 -48
  6. package/dist/model/ExperimentalFeatureSetting.js +1 -0
  7. package/dist/model/RoomMap.js +13 -7
  8. package/dist/model/roomIndexMap.js +17 -0
  9. package/dist/platform.js +7 -5
  10. package/dist/platformRunner.js +32 -5
  11. package/dist/roborockCommunication/Zenum/additionalPropCode.js +4 -0
  12. package/dist/roborockCommunication/Zmodel/mapInfo.js +17 -16
  13. package/dist/roborockCommunication/broadcast/messageProcessor.js +2 -3
  14. package/dist/roborockCommunication/index.js +1 -0
  15. package/dist/roborockService.js +11 -2
  16. package/dist/rvc.js +5 -2
  17. package/matterbridge-roborock-vacuum-plugin.config.json +4 -3
  18. package/matterbridge-roborock-vacuum-plugin.schema.json +42 -10
  19. package/package.json +1 -1
  20. package/src/helper.ts +28 -26
  21. package/src/initialData/getSupportedAreas.ts +115 -45
  22. package/src/model/ExperimentalFeatureSetting.ts +2 -0
  23. package/src/model/RoomMap.ts +33 -15
  24. package/src/model/roomIndexMap.ts +20 -0
  25. package/src/platform.ts +8 -5
  26. package/src/platformRunner.ts +41 -6
  27. package/src/roborockCommunication/Zenum/additionalPropCode.ts +3 -0
  28. package/src/roborockCommunication/Zmodel/map.ts +2 -2
  29. package/src/roborockCommunication/Zmodel/mapInfo.ts +37 -18
  30. package/src/roborockCommunication/Zmodel/multipleMap.ts +2 -2
  31. package/src/roborockCommunication/broadcast/messageProcessor.ts +2 -4
  32. package/src/roborockCommunication/index.ts +1 -0
  33. package/src/roborockService.ts +20 -2
  34. package/src/rvc.ts +7 -2
  35. package/src/tests/helper.test.ts +113 -0
  36. package/src/tests/initialData/getSupportedAreas.test.ts +80 -7
  37. package/src/tests/platformRunner2.test.ts +145 -5
  38. package/src/tests/platformRunner3.test.ts +54 -0
  39. package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +2 -7
  40. package/src/tests/roborockService.test.ts +22 -1
  41. package/web-for-testing/package-lock.json +14 -12
  42. package/web-for-testing/package.json +1 -1
  43. package/web-for-testing/src/accountStore.ts +0 -10
@@ -1,6 +1,10 @@
1
1
  name: Build, lint and test
2
2
 
3
- on: [push, pull_request]
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
4
8
 
5
9
  jobs:
6
10
  publish:
@@ -1,6 +1,10 @@
1
1
  name: Analyze code coverage
2
2
 
3
- on: [push, pull_request]
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - dev
4
8
 
5
9
  jobs:
6
10
  publish:
package/CHANGELOG.md ADDED
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ---
6
+
7
+ ## [1.1.0-rc15] - 2025-07-26
8
+ ### Changed
9
+ - Refactor codebase.
10
+ - Correct LocalNetworkClient connecting process.
11
+ - Update and fix unit tests.
12
+ - Various lint fixes.
13
+
14
+ ## [1.1.0-rc14] - 2025-07-26
15
+ ### Added
16
+ - Initialize sync device status via MQTT.
17
+
18
+ ## [1.1.0-rc13] - 2025-07-25
19
+ ### Added
20
+ - More unit tests.
21
+ - Increased unit test coverage.
22
+
23
+ ### Fixed
24
+ - Several bugs related to socket connection issues.
25
+ - Improve reconnect logic and cleaning status reporting.
26
+
27
+ ## [1.1.0-rc11] - 2025-07-22
28
+ ### Fixed
29
+ - Unable to get room from `get_room_mapping`.
30
+ - Can not get room from `get_multi_maps_list`.
31
+
32
+ ## [1.1.0-rc10] - 2025-07-22
33
+ ### Fixed
34
+ - Stop retrying to connect socket when exceeding retry count.
35
+ - Lint and unit test updates.
36
+
37
+ ## [1.1.0-rc09] - 2025-07-20
38
+ ### Fixed
39
+ - Do not re-connect MQTT unnecessarily.
40
+ - Reconnect when socket or MQTT is unexpectedly disconnected.
41
+
42
+ ### Enhanced
43
+ - Save user data for subsequent logins.
44
+ - Unit test improvements.
45
+
46
+ ## [1.1.0-rc07] - 2025-07-17
47
+ ### Changed
48
+ - Enable server mode fixes.
49
+ - Improved error logging when plugin can't connect to device.
50
+
51
+ ## [1.1.0-rc06] - 2025-07-16
52
+ ### Fixed
53
+ - Can not start plugin when server mode is disabled.
54
+
55
+ ## [1.1.0-rc04] - 2025-07-16
56
+ ### Added
57
+ - Unit tests.
58
+ - GitHub Actions improvements.
59
+
60
+ ---
61
+
62
+ **Note:** This changelog includes only the most recent 30 commits. For the full commit history, view more on [GitHub](https://github.com/RinDevJunior/matterbridge-roborock-vacuum-plugin/commits?sort=updated&direction=desc).
package/dist/helper.js CHANGED
@@ -27,6 +27,7 @@ export function isStatusUpdate(result) {
27
27
  }
28
28
  export async function getRoomMap(duid, platform) {
29
29
  const robot = platform.robots.get(duid);
30
+ const enableMultipleMap = (platform.enableExperimentalFeature?.enableExperimentalFeature && platform.enableExperimentalFeature?.advancedFeature.enableMultipleMap) ?? false;
30
31
  if (robot === undefined) {
31
32
  platform.log.error(`Error6: Robot with DUID ${duid} not found`);
32
33
  return undefined;
@@ -35,44 +36,46 @@ export async function getRoomMap(duid, platform) {
35
36
  return undefined;
36
37
  const rooms = robot.device.rooms ?? [];
37
38
  if (robot.roomInfo === undefined) {
38
- const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
39
- if (roomData !== undefined && roomData.length > 0) {
40
- robot.roomInfo = new RoomMap(roomData ?? [], rooms);
41
- return robot.roomInfo;
39
+ const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
40
+ if (mapInfo && mapInfo.allRooms && mapInfo.allRooms.length > 0) {
41
+ platform.log.info(`getRoomMap - mapInfo: ${debugStringify(mapInfo.allRooms)}`);
42
+ robot.roomInfo = new RoomMap(mapInfo.allRooms, rooms, mapInfo.maps, enableMultipleMap);
42
43
  }
43
44
  }
44
45
  if (robot.roomInfo === undefined) {
45
- const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
46
- if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
47
- platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.maps)}`);
48
- const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag]);
49
- robot.roomInfo = new RoomMap(roomDataMap, rooms);
46
+ const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
47
+ if (roomData !== undefined && roomData.length > 0) {
48
+ const roomDataMap = roomData.map((r) => ({ id: r[0], iot_name_id: String(r[1]), globalId: r[1], tag: r[2], mapId: 0, displayName: undefined }));
49
+ robot.roomInfo = new RoomMap(roomDataMap, rooms, [], enableMultipleMap);
50
+ return robot.roomInfo;
50
51
  }
51
52
  }
52
53
  return robot.roomInfo;
53
54
  }
54
55
  export async function getRoomMapFromDevice(device, platform) {
55
56
  const rooms = device?.rooms ?? [];
57
+ const enableMultipleMap = (platform.enableExperimentalFeature?.enableExperimentalFeature && platform.enableExperimentalFeature?.advancedFeature.enableMultipleMap) ?? false;
56
58
  platform.log.notice('-------------------------------------------0--------------------------------------------------------');
57
- platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
59
+ platform.log.notice(`getRoomMapFromDevice - device.rooms: ${debugStringify(rooms)}`);
58
60
  if (device && platform.roborockService) {
61
+ const mapInfo = await platform.roborockService.getMapInformation(device.duid);
62
+ platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
63
+ if (mapInfo && mapInfo.allRooms && mapInfo.allRooms.length > 0) {
64
+ const roomDataMap = mapInfo.allRooms;
65
+ const roomMap = new RoomMap(roomDataMap, rooms, mapInfo.maps, enableMultipleMap);
66
+ platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
67
+ platform.log.notice('-------------------------------------------2--------------------------------------------------------');
68
+ return roomMap;
69
+ }
59
70
  const roomData = await platform.roborockService.getRoomMappings(device.duid);
60
71
  if (roomData !== undefined && roomData.length > 0) {
61
72
  platform.log.notice(`getRoomMapFromDevice - roomData: ${debugStringify(roomData ?? [])}`);
62
- const roomMap = new RoomMap(roomData ?? [], rooms);
73
+ const roomDataMap = roomData.map((r) => ({ id: r[0], iot_name_id: String(r[1]), globalId: r[1], tag: r[2], mapId: 0, displayName: undefined }));
74
+ const roomMap = new RoomMap(roomDataMap ?? [], rooms, [], enableMultipleMap);
63
75
  platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
64
76
  platform.log.notice('-------------------------------------------1--------------------------------------------------------');
65
77
  return roomMap;
66
78
  }
67
- const mapInfo = await platform.roborockService.getMapInformation(device.duid);
68
- platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
69
- if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
70
- const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag]);
71
- const roomMap = new RoomMap(roomDataMap, rooms);
72
- platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
73
- platform.log.notice('-------------------------------------------2--------------------------------------------------------');
74
- return roomMap;
75
- }
76
79
  }
77
- return new RoomMap([], rooms);
80
+ return new RoomMap([], rooms, [], enableMultipleMap);
78
81
  }
@@ -1,77 +1,120 @@
1
1
  import { debugStringify } from 'matterbridge/logger';
2
2
  import { randomInt } from 'node:crypto';
3
- export function getSupportedAreas(vacuumRooms, roomMap, log) {
3
+ import { RoomIndexMap } from '../model/roomIndexMap.js';
4
+ export function getSupportedAreas(vacuumRooms, roomMap, enableMultipleMap = false, log) {
4
5
  log?.debug('getSupportedAreas-vacuum room', debugStringify(vacuumRooms));
5
6
  log?.debug('getSupportedAreas-roomMap', roomMap ? debugStringify(roomMap) : 'undefined');
6
- if (!vacuumRooms || vacuumRooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
7
- if (!vacuumRooms || vacuumRooms.length === 0) {
7
+ const noVacuumRooms = !vacuumRooms || vacuumRooms.length === 0;
8
+ const noRoomMap = !roomMap?.rooms || roomMap.rooms.length === 0;
9
+ if (noVacuumRooms || noRoomMap) {
10
+ if (noVacuumRooms) {
8
11
  log?.error('No rooms found');
9
12
  }
10
- if (!roomMap || !roomMap.rooms || roomMap.rooms.length == 0) {
13
+ if (noRoomMap) {
11
14
  log?.error('No room map found');
12
15
  }
13
- return [
14
- {
15
- areaId: 1,
16
- mapId: null,
17
- areaInfo: {
18
- locationInfo: {
19
- locationName: 'Unknown',
20
- floorNumber: null,
21
- areaType: null,
16
+ return {
17
+ supportedAreas: [
18
+ {
19
+ areaId: 1,
20
+ mapId: null,
21
+ areaInfo: {
22
+ locationInfo: {
23
+ locationName: 'Unknown',
24
+ floorNumber: null,
25
+ areaType: null,
26
+ },
27
+ landmarkInfo: null,
22
28
  },
23
- landmarkInfo: null,
24
29
  },
25
- },
26
- ];
30
+ ],
31
+ supportedMaps: [],
32
+ roomIndexMap: new RoomIndexMap(new Map([[1, { roomId: 1, mapId: null }]])),
33
+ };
27
34
  }
28
- const supportedAreas = roomMap.rooms.map((room) => {
35
+ const { supportedAreas, indexMap } = processValidData(enableMultipleMap, vacuumRooms, roomMap);
36
+ const duplicated = findDuplicatedAreaIds(supportedAreas, log);
37
+ if (duplicated) {
29
38
  return {
30
- areaId: room.id,
31
- mapId: null,
32
- areaInfo: {
33
- locationInfo: {
34
- locationName: room.displayName ?? vacuumRooms.find((r) => r.id == room.globalId || r.id == room.id)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`,
35
- floorNumber: null,
36
- areaType: null,
39
+ supportedAreas: [
40
+ {
41
+ areaId: 2,
42
+ mapId: null,
43
+ areaInfo: {
44
+ locationInfo: {
45
+ locationName: 'Unknown - Duplicated Areas Found',
46
+ floorNumber: null,
47
+ areaType: null,
48
+ },
49
+ landmarkInfo: null,
50
+ },
37
51
  },
38
- landmarkInfo: null,
39
- },
52
+ ],
53
+ supportedMaps: [],
54
+ roomIndexMap: new RoomIndexMap(new Map([[2, { roomId: 2, mapId: null }]])),
40
55
  };
41
- });
56
+ }
57
+ const supportedMaps = getSupportedMaps(enableMultipleMap, supportedAreas, roomMap);
42
58
  log?.debug('getSupportedAreas - supportedAreas', debugStringify(supportedAreas));
43
- const duplicated = findDuplicatedAreaIds(supportedAreas, log);
44
- return duplicated
45
- ? [
46
- {
47
- areaId: 2,
48
- mapId: null,
49
- areaInfo: {
50
- locationInfo: {
51
- locationName: 'Unknown',
52
- floorNumber: null,
53
- areaType: null,
54
- },
55
- landmarkInfo: null,
56
- },
57
- },
58
- ]
59
- : supportedAreas;
59
+ log?.debug('getSupportedAreas - supportedMaps', debugStringify(supportedMaps));
60
+ const roomIndexMap = new RoomIndexMap(indexMap);
61
+ return {
62
+ supportedAreas,
63
+ supportedMaps,
64
+ roomIndexMap,
65
+ };
60
66
  }
61
67
  function findDuplicatedAreaIds(areas, log) {
62
68
  const seen = new Set();
63
69
  const duplicates = [];
64
70
  for (const area of areas) {
65
- if (seen.has(area.areaId)) {
66
- duplicates.push(area.areaId);
71
+ const key = `${area.areaId}=${area.mapId}`;
72
+ if (seen.has(key)) {
73
+ duplicates.push({ areaId: area.areaId, mapId: area.mapId ?? 0 });
67
74
  }
68
75
  else {
69
- seen.add(area.areaId);
76
+ seen.add(key);
70
77
  }
71
78
  }
72
79
  if (duplicates.length > 0 && log) {
73
- const duplicated = areas.filter((x) => duplicates.includes(x.areaId));
80
+ const duplicated = areas.filter(({ areaId, mapId }) => duplicates.some((y) => y.areaId === areaId && y.mapId === (mapId ?? 0)));
74
81
  log.error(`Duplicated areaId(s) found: ${debugStringify(duplicated)}`);
75
82
  }
76
83
  return duplicates.length > 0;
77
84
  }
85
+ function processValidData(enableMultipleMap, vacuumRooms, roomMap) {
86
+ const indexMap = new Map();
87
+ const supportedAreas = roomMap?.rooms !== undefined && roomMap.rooms.length > 0
88
+ ? roomMap.rooms.map((room, index) => {
89
+ const locationName = room.displayName ?? vacuumRooms.find((r) => r.id === room.globalId || r.id === room.id)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`;
90
+ const areaId = index + 100;
91
+ const mapId = enableMultipleMap ? (room.mapId ?? null) : null;
92
+ indexMap.set(areaId, { roomId: room.id, mapId: room.mapId ?? null });
93
+ return {
94
+ areaId: areaId,
95
+ mapId: mapId,
96
+ areaInfo: {
97
+ locationInfo: {
98
+ locationName: locationName,
99
+ floorNumber: room.mapId ?? null,
100
+ areaType: null,
101
+ },
102
+ landmarkInfo: null,
103
+ },
104
+ };
105
+ })
106
+ : [];
107
+ return {
108
+ supportedAreas,
109
+ indexMap,
110
+ };
111
+ }
112
+ function getSupportedMaps(enableMultipleMap, supportedAreas, roomMap) {
113
+ if (enableMultipleMap) {
114
+ return (roomMap?.mapInfo?.map((map) => ({
115
+ mapId: map.id,
116
+ name: map.name ?? `Map ${map.id}`,
117
+ })) ?? []);
118
+ }
119
+ return [];
120
+ }
@@ -8,6 +8,7 @@ export function createDefaultExperimentalFeatureSetting() {
8
8
  useVacationModeToSendVacuumToDock: false,
9
9
  enableServerMode: false,
10
10
  alwaysExecuteAuthentication: false,
11
+ enableMultipleMap: false,
11
12
  },
12
13
  cleanModeSettings: {
13
14
  enableCleanModeMapping: false,
@@ -1,13 +1,19 @@
1
1
  export class RoomMap {
2
- rooms = [];
3
- constructor(roomData, rooms) {
4
- this.rooms = roomData.map((entry) => {
2
+ rooms;
3
+ mapInfo;
4
+ constructor(roomData, rooms, mapInfo, enableMultipleMap) {
5
+ const mapid = mapInfo[0]?.id ?? 0;
6
+ const roomDataTmp = enableMultipleMap ? roomData : roomData.filter((room) => room.mapId === undefined || room.mapId === mapid);
7
+ this.rooms = roomDataTmp.map(({ id, globalId, tag, mapId }) => {
8
+ const room = rooms.find((r) => Number(r.id) === Number(globalId) || Number(r.id) === Number(id));
5
9
  return {
6
- id: entry[0],
7
- globalId: Number(entry[1]),
8
- displayName: rooms.find((r) => Number(r.id) == Number(entry[1]))?.name,
9
- alternativeId: `${entry[0]}${entry[2]}`,
10
+ id,
11
+ globalId: globalId !== undefined ? Number(globalId) : undefined,
12
+ displayName: room?.name,
13
+ alternativeId: `${id}${tag}`,
14
+ mapId,
10
15
  };
11
16
  });
17
+ this.mapInfo = mapInfo;
12
18
  }
13
19
  }
@@ -0,0 +1,17 @@
1
+ export class RoomIndexMap {
2
+ indexMap;
3
+ roomMap;
4
+ constructor(roomMap) {
5
+ this.indexMap = roomMap;
6
+ this.roomMap = new Map();
7
+ for (const [areaId, { roomId }] of roomMap.entries()) {
8
+ this.roomMap.set(roomId, areaId);
9
+ }
10
+ }
11
+ getAreaId(roomId) {
12
+ return this.roomMap.get(roomId);
13
+ }
14
+ getRoomId(areaId) {
15
+ return this.indexMap.get(areaId)?.roomId;
16
+ }
17
+ }
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.7')) {
30
- throw new Error(`This plugin requires Matterbridge version >= "3.1.7". 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.8')) {
30
+ throw new Error(`This plugin requires Matterbridge version >= "3.1.8". 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)
@@ -159,14 +159,16 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
159
159
  if (vacuum.rooms === undefined || vacuum.rooms.length === 0) {
160
160
  this.log.notice(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
161
161
  const map_info = await this.roborockService.getMapInformation(vacuum.duid);
162
- const rooms = map_info?.maps?.[0]?.rooms ?? [];
163
- vacuum.rooms = rooms.map((room) => ({ id: room.id, name: room.displayName }));
162
+ const rooms = map_info?.allRooms ?? [];
163
+ vacuum.rooms = rooms.map((room) => ({ id: room.globalId, name: room.displayName }));
164
164
  }
165
165
  const roomMap = await getRoomMapFromDevice(vacuum, this);
166
166
  this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
167
167
  const behaviorHandler = configurateBehavior(vacuum.data.model, vacuum.duid, this.roborockService, this.cleanModeSettings, this.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false, this.log);
168
- const supportedAreas = getSupportedAreas(vacuum.rooms, roomMap, this.log);
168
+ const enableMultipleMap = this.enableExperimentalFeature?.enableExperimentalFeature && this.enableExperimentalFeature.advancedFeature?.enableMultipleMap;
169
+ const { supportedAreas, roomIndexMap } = getSupportedAreas(vacuum.rooms, roomMap, enableMultipleMap, this.log);
169
170
  this.roborockService.setSupportedAreas(vacuum.duid, supportedAreas);
171
+ this.roborockService.setSupportedAreaIndexMap(vacuum.duid, roomIndexMap);
170
172
  let routineAsRoom = [];
171
173
  if (this.enableExperimentalFeature?.enableExperimentalFeature && this.enableExperimentalFeature.advancedFeature?.showRoutinesAsRoom) {
172
174
  routineAsRoom = getSupportedScenes(vacuum.scenes, this.log);
@@ -1,11 +1,12 @@
1
1
  import { RvcRunMode, PowerSource, ServiceArea, RvcOperationalState, RvcCleanMode } from 'matterbridge/matter/clusters';
2
- import { getRoomMap, getVacuumProperty, isStatusUpdate } from './helper.js';
2
+ import { getRoomMap, getRoomMapFromDevice, getVacuumProperty, isStatusUpdate } from './helper.js';
3
3
  import { getRunningMode } from './initialData/getSupportedRunModes.js';
4
4
  import { state_to_matter_operational_status, state_to_matter_state } from './share/function.js';
5
- import { getBatteryState, getBatteryStatus, getOperationalErrorState } from './initialData/index.js';
5
+ import { getBatteryState, getBatteryStatus, getOperationalErrorState, getSupportedAreas } from './initialData/index.js';
6
6
  import { NotifyMessageTypes } from './notifyMessageTypes.js';
7
7
  import { Protocol } from './roborockCommunication/broadcast/model/protocol.js';
8
8
  import { hasDockingStationError, parseDockingStationStatus } from './model/DockingStationStatus.js';
9
+ import { AdditionalPropCode } from './roborockCommunication/index.js';
9
10
  import { OperationStatusCode } from './roborockCommunication/Zenum/operationStatusCode.js';
10
11
  import { getCurrentCleanModeFunc } from './share/runtimeHelper.js';
11
12
  import { debugStringify } from 'matterbridge/logger';
@@ -92,6 +93,7 @@ export class PlatformRunner {
92
93
  }
93
94
  else {
94
95
  const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
96
+ const roomIndexMap = this.platform.roborockService?.getSupportedAreasIndexMap(duid);
95
97
  const roomMap = await getRoomMap(duid, this.platform);
96
98
  const segment_id = data.cleaning_info?.segment_id ?? -1;
97
99
  const target_segment_id = data.cleaning_info?.target_segment_id ?? -1;
@@ -101,7 +103,8 @@ export class PlatformRunner {
101
103
  if (segment_id !== -1 && isMappedArea) {
102
104
  this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
103
105
  this.platform.log.debug(`Part1: CurrentRoom: ${segment_id}, room name: ${roomMap?.rooms.find((x) => x.id === segment_id || x.alternativeId === segment_id.toString())?.displayName ?? 'unknown'}`);
104
- robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', segment_id, platform.log);
106
+ const areaId = roomIndexMap?.getAreaId(segment_id) ?? null;
107
+ robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', areaId, platform.log);
105
108
  }
106
109
  if (segment_id == -1) {
107
110
  const isTargetMappedArea = currentMappedAreas?.some((x) => x.areaId == target_segment_id);
@@ -110,7 +113,8 @@ export class PlatformRunner {
110
113
  if (target_segment_id !== -1 && isTargetMappedArea) {
111
114
  this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
112
115
  this.platform.log.debug(`Part2: TargetRoom: ${target_segment_id}, room name: ${roomMap?.rooms.find((x) => x.id === target_segment_id || x.alternativeId === segment_id.toString())?.displayName ?? 'unknown'}`);
113
- robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', target_segment_id, platform.log);
116
+ const areaId = roomIndexMap?.getAreaId(target_segment_id) ?? null;
117
+ robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', areaId, platform.log);
114
118
  }
115
119
  }
116
120
  if (target_segment_id == -1 && segment_id == -1) {
@@ -217,7 +221,30 @@ export class PlatformRunner {
217
221
  });
218
222
  break;
219
223
  }
220
- case Protocol.additional_props:
224
+ case Protocol.additional_props: {
225
+ platform.log.notice(`Received additional properties for robot ${duid}: ${debugStringify(data)}`);
226
+ const propCode = data.dps[Protocol.additional_props];
227
+ platform.log.debug(`DPS for additional properties: ${propCode}, AdditionalPropCode: ${AdditionalPropCode[propCode]}`);
228
+ const enableMultipleMap = (platform.enableExperimentalFeature?.enableExperimentalFeature && platform.enableExperimentalFeature?.advancedFeature?.enableMultipleMap) ?? false;
229
+ if (propCode === AdditionalPropCode.map_change) {
230
+ platform.log.notice('------------------------ get roomData ----------------------------');
231
+ const roomMap = await getRoomMapFromDevice(robot.device, platform);
232
+ platform.log.notice('------------------------ Room map updated ------------------------');
233
+ const { supportedAreas, supportedMaps, roomIndexMap } = getSupportedAreas(robot.device.rooms, roomMap, enableMultipleMap, platform.log);
234
+ platform.log.notice(`Supported areas: ${debugStringify(supportedAreas)}`);
235
+ platform.log.notice('------------------------ Supported areas updated ------------------');
236
+ platform.roborockService?.setSupportedAreas(duid, supportedAreas);
237
+ platform.roborockService?.setSelectedAreas(duid, []);
238
+ robot.updateAttribute(ServiceArea.Cluster.id, 'supportedAreas', supportedAreas, platform.log);
239
+ robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
240
+ robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
241
+ if (enableMultipleMap) {
242
+ platform.roborockService?.setSupportedAreaIndexMap(duid, roomIndexMap);
243
+ robot.updateAttribute(ServiceArea.Cluster.id, 'supportedMaps', supportedMaps, platform.log);
244
+ }
245
+ }
246
+ break;
247
+ }
221
248
  case Protocol.back_type: {
222
249
  break;
223
250
  }
@@ -0,0 +1,4 @@
1
+ export var AdditionalPropCode;
2
+ (function (AdditionalPropCode) {
3
+ AdditionalPropCode[AdditionalPropCode["map_change"] = 4] = "map_change";
4
+ })(AdditionalPropCode || (AdditionalPropCode = {}));
@@ -1,28 +1,29 @@
1
1
  import decodeComponent from '../helper/nameDecoder.js';
2
2
  export class MapInfo {
3
3
  maps = [];
4
+ allRooms = [];
4
5
  constructor(multimap) {
5
- multimap.map_info.forEach((map) => {
6
- this.maps.push({
7
- id: map.mapFlag,
8
- name: decodeComponent(map.name)?.toLowerCase(),
9
- rooms: map.rooms && map.rooms.length > 0
10
- ? map.rooms.map((room) => {
11
- return {
12
- id: room.id,
13
- iot_name_id: room.iot_name_id,
14
- tag: room.tag,
15
- displayName: room.iot_name,
16
- };
17
- })
18
- : [],
19
- });
6
+ this.maps = multimap.map_info.map((mapInfo) => {
7
+ const rooms = mapInfo.rooms?.map((room) => ({
8
+ id: room.id,
9
+ globalId: parseInt(room.iot_name_id),
10
+ iot_name_id: room.iot_name_id,
11
+ tag: room.tag,
12
+ displayName: room.iot_name,
13
+ mapId: mapInfo.mapFlag,
14
+ })) ?? [];
15
+ this.allRooms.push(...rooms);
16
+ return {
17
+ id: mapInfo.mapFlag,
18
+ name: decodeComponent(mapInfo.name),
19
+ rooms,
20
+ };
20
21
  });
21
22
  }
22
23
  getById(id) {
23
24
  return this.maps.find((m) => m.id === id)?.name;
24
25
  }
25
26
  getByName(name) {
26
- return this.maps.find((m) => m.name === name.toLowerCase())?.id;
27
+ return this.maps.find((m) => m.name?.toLowerCase() === name.toLowerCase())?.id;
27
28
  }
28
29
  }
@@ -1,5 +1,4 @@
1
1
  import { debugStringify } from 'matterbridge/logger';
2
- import { RoomInfo } from '../Zmodel/roomInfo.js';
3
2
  import { SimpleMessageListener } from './listener/index.js';
4
3
  import { RequestMessage } from './model/requestMessage.js';
5
4
  import { DeviceStatus } from '../Zmodel/deviceStatus.js';
@@ -40,9 +39,9 @@ export class MessageProcessor {
40
39
  }
41
40
  return undefined;
42
41
  }
43
- async getRooms(duid, rooms) {
42
+ async getRooms(duid) {
44
43
  const request = new RequestMessage({ method: 'get_room_mapping' });
45
- return this.client.get(duid, request).then((response) => new RoomInfo(rooms, response ?? []));
44
+ return this.client.get(duid, request);
46
45
  }
47
46
  async gotoDock(duid) {
48
47
  const request = new RequestMessage({ method: 'app_charge' });
@@ -8,4 +8,5 @@ export { ClientRouter } from './broadcast/clientRouter.js';
8
8
  export { DeviceStatus } from './Zmodel/deviceStatus.js';
9
9
  export { ResponseMessage } from './broadcast/model/responseMessage.js';
10
10
  export { MapInfo } from './Zmodel/mapInfo.js';
11
+ export { AdditionalPropCode } from './Zenum/additionalPropCode.js';
11
12
  export { Scene } from './Zmodel/scene.js';
@@ -22,6 +22,7 @@ export default class RoborockService {
22
22
  supportedAreas = new Map();
23
23
  supportedRoutines = new Map();
24
24
  selectedAreas = new Map();
25
+ supportedAreaIndexMaps = new Map();
25
26
  vacuumNeedAPIV3 = ['roborock.vacuum.ss07'];
26
27
  constructor(authenticateApiSupplier = (logger) => new RoborockAuthenticateApi(logger), iotApiSupplier = (logger, ud) => new RoborockIoTApi(ud, logger), refreshInterval, clientManager, logger) {
27
28
  this.logger = logger;
@@ -52,7 +53,9 @@ export default class RoborockService {
52
53
  }
53
54
  setSelectedAreas(duid, selectedAreas) {
54
55
  this.logger.debug('RoborockService - setSelectedAreas', selectedAreas);
55
- this.selectedAreas.set(duid, selectedAreas);
56
+ const roomIds = selectedAreas.map((areaId) => this.supportedAreaIndexMaps.get(duid)?.getRoomId(areaId)) ?? [];
57
+ this.logger.debug('RoborockService - setSelectedAreas - roomIds', roomIds);
58
+ this.selectedAreas.set(duid, roomIds.filter((id) => id !== undefined).map((id) => id));
56
59
  }
57
60
  getSelectedAreas(duid) {
58
61
  return this.selectedAreas.get(duid) ?? [];
@@ -60,12 +63,18 @@ export default class RoborockService {
60
63
  setSupportedAreas(duid, supportedAreas) {
61
64
  this.supportedAreas.set(duid, supportedAreas);
62
65
  }
66
+ setSupportedAreaIndexMap(duid, indexMap) {
67
+ this.supportedAreaIndexMaps.set(duid, indexMap);
68
+ }
63
69
  setSupportedScenes(duid, routineAsRooms) {
64
70
  this.supportedRoutines.set(duid, routineAsRooms);
65
71
  }
66
72
  getSupportedAreas(duid) {
67
73
  return this.supportedAreas.get(duid);
68
74
  }
75
+ getSupportedAreasIndexMap(duid) {
76
+ return this.supportedAreaIndexMaps.get(duid);
77
+ }
69
78
  async getCleanModeData(duid) {
70
79
  this.logger.notice('RoborockService - getCleanModeData');
71
80
  const data = await this.getMessageProcessor(duid)?.getCleanModeData(duid);
@@ -351,7 +360,7 @@ export default class RoborockService {
351
360
  assert(this.iotApi !== undefined);
352
361
  return this.iotApi.startScene(sceneId);
353
362
  }
354
- getRoomMappings(duid) {
363
+ async getRoomMappings(duid) {
355
364
  if (!this.messageClient) {
356
365
  this.logger.warn('messageClient not initialized. Waititing for next execution');
357
366
  return Promise.resolve(undefined);
package/dist/rvc.js CHANGED
@@ -9,14 +9,17 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
9
9
  constructor(username, device, roomMap, routineAsRoom, enableExperimentalFeature, log) {
10
10
  const cleanModes = getSupportedCleanModes(device.data.model, enableExperimentalFeature);
11
11
  const supportedRunModes = getSupportedRunModes();
12
- const supportedAreas = [...getSupportedAreas(device.rooms, roomMap, log), ...routineAsRoom];
12
+ const enableMultipleMap = enableExperimentalFeature?.enableExperimentalFeature && enableExperimentalFeature?.advancedFeature?.enableMultipleMap;
13
+ const { supportedAreas, supportedMaps } = getSupportedAreas(device.rooms, roomMap, enableMultipleMap, log);
14
+ const supportedAreaAndRoutines = [...supportedAreas, ...routineAsRoom];
13
15
  const deviceName = `${device.name}-${device.duid}`.replace(/\s+/g, '');
14
16
  log.debug(`Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${enableExperimentalFeature?.advancedFeature?.forceRunAtDefault}`);
15
17
  log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
16
18
  log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
17
19
  log.debug(`Supported Areas: ${JSON.stringify(supportedAreas)}`);
20
+ log.debug(`Supported Maps: ${JSON.stringify(supportedMaps)}`);
18
21
  const bridgeMode = enableExperimentalFeature?.enableExperimentalFeature && enableExperimentalFeature?.advancedFeature?.enableServerMode ? 'server' : undefined;
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);
22
+ super(deviceName, device.duid, bridgeMode, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(), supportedAreaAndRoutines, undefined, supportedAreas[0].areaId, supportedMaps);
20
23
  this.username = username;
21
24
  this.device = device;
22
25
  }