matterbridge-roborock-vacuum-plugin 1.1.0-rc15 → 1.1.0-rc16
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/.github/workflows/build.yml +5 -1
- package/.github/workflows/coverage.yml +5 -1
- package/CHANGELOG.md +62 -0
- package/dist/helper.js +6 -6
- package/dist/initialData/getSupportedAreas.js +12 -8
- package/dist/model/RoomMap.js +7 -6
- package/dist/platform.js +2 -2
- package/dist/platformRunner.js +22 -3
- package/dist/roborockCommunication/Zenum/additionalPropCode.js +4 -0
- package/dist/roborockCommunication/Zmodel/mapInfo.js +18 -16
- package/dist/roborockCommunication/broadcast/messageProcessor.js +2 -3
- package/dist/roborockCommunication/index.js +1 -0
- package/dist/roborockService.js +1 -1
- package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
- package/matterbridge-roborock-vacuum-plugin.schema.json +1 -1
- package/package.json +1 -1
- package/src/helper.ts +6 -7
- package/src/initialData/getSupportedAreas.ts +28 -20
- package/src/model/RoomMap.ts +17 -13
- package/src/platform.ts +2 -2
- package/src/platformRunner.ts +27 -4
- package/src/roborockCommunication/Zenum/additionalPropCode.ts +3 -0
- package/src/roborockCommunication/Zmodel/map.ts +1 -1
- package/src/roborockCommunication/Zmodel/mapInfo.ts +36 -18
- package/src/roborockCommunication/Zmodel/multipleMap.ts +2 -2
- package/src/roborockCommunication/broadcast/messageProcessor.ts +2 -4
- package/src/roborockCommunication/index.ts +1 -0
- package/src/roborockService.ts +4 -1
- package/src/tests/helper.test.ts +113 -0
- package/src/tests/platformRunner2.test.ts +39 -6
- package/src/tests/platformRunner3.test.ts +54 -0
- package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +2 -7
- package/src/tests/roborockService.test.ts +11 -1
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
|
@@ -43,9 +43,9 @@ export async function getRoomMap(duid, platform) {
|
|
|
43
43
|
}
|
|
44
44
|
if (robot.roomInfo === undefined) {
|
|
45
45
|
const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
|
|
46
|
-
if (mapInfo && mapInfo.
|
|
47
|
-
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.
|
|
48
|
-
const roomDataMap = mapInfo.
|
|
46
|
+
if (mapInfo && mapInfo.allRooms && mapInfo.allRooms.length > 0) {
|
|
47
|
+
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.allRooms)}`);
|
|
48
|
+
const roomDataMap = mapInfo.allRooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag]);
|
|
49
49
|
robot.roomInfo = new RoomMap(roomDataMap, rooms);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -54,7 +54,7 @@ export async function getRoomMap(duid, platform) {
|
|
|
54
54
|
export async function getRoomMapFromDevice(device, platform) {
|
|
55
55
|
const rooms = device?.rooms ?? [];
|
|
56
56
|
platform.log.notice('-------------------------------------------0--------------------------------------------------------');
|
|
57
|
-
platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
|
|
57
|
+
platform.log.notice(`getRoomMapFromDevice - device.rooms: ${debugStringify(rooms)}`);
|
|
58
58
|
if (device && platform.roborockService) {
|
|
59
59
|
const roomData = await platform.roborockService.getRoomMappings(device.duid);
|
|
60
60
|
if (roomData !== undefined && roomData.length > 0) {
|
|
@@ -66,8 +66,8 @@ export async function getRoomMapFromDevice(device, platform) {
|
|
|
66
66
|
}
|
|
67
67
|
const mapInfo = await platform.roborockService.getMapInformation(device.duid);
|
|
68
68
|
platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
|
|
69
|
-
if (mapInfo && mapInfo.
|
|
70
|
-
const roomDataMap = mapInfo.
|
|
69
|
+
if (mapInfo && mapInfo.allRooms && mapInfo.allRooms.length > 0) {
|
|
70
|
+
const roomDataMap = mapInfo.allRooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag]);
|
|
71
71
|
const roomMap = new RoomMap(roomDataMap, rooms);
|
|
72
72
|
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
73
73
|
platform.log.notice('-------------------------------------------2--------------------------------------------------------');
|
|
@@ -3,11 +3,13 @@ import { randomInt } from 'node:crypto';
|
|
|
3
3
|
export function getSupportedAreas(vacuumRooms, roomMap, log) {
|
|
4
4
|
log?.debug('getSupportedAreas-vacuum room', debugStringify(vacuumRooms));
|
|
5
5
|
log?.debug('getSupportedAreas-roomMap', roomMap ? debugStringify(roomMap) : 'undefined');
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const noVacuumRooms = !vacuumRooms || vacuumRooms.length === 0;
|
|
7
|
+
const noRoomMap = !roomMap?.rooms || roomMap.rooms.length === 0;
|
|
8
|
+
if (noVacuumRooms || noRoomMap) {
|
|
9
|
+
if (noVacuumRooms) {
|
|
8
10
|
log?.error('No rooms found');
|
|
9
11
|
}
|
|
10
|
-
if (
|
|
12
|
+
if (noRoomMap) {
|
|
11
13
|
log?.error('No room map found');
|
|
12
14
|
}
|
|
13
15
|
return [
|
|
@@ -26,12 +28,13 @@ export function getSupportedAreas(vacuumRooms, roomMap, log) {
|
|
|
26
28
|
];
|
|
27
29
|
}
|
|
28
30
|
const supportedAreas = roomMap.rooms.map((room) => {
|
|
31
|
+
const locationName = room.displayName ?? vacuumRooms.find((r) => r.id === room.globalId || r.id === room.id)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`;
|
|
29
32
|
return {
|
|
30
33
|
areaId: room.id,
|
|
31
34
|
mapId: null,
|
|
32
35
|
areaInfo: {
|
|
33
36
|
locationInfo: {
|
|
34
|
-
locationName
|
|
37
|
+
locationName,
|
|
35
38
|
floorNumber: null,
|
|
36
39
|
areaType: null,
|
|
37
40
|
},
|
|
@@ -41,8 +44,8 @@ export function getSupportedAreas(vacuumRooms, roomMap, log) {
|
|
|
41
44
|
});
|
|
42
45
|
log?.debug('getSupportedAreas - supportedAreas', debugStringify(supportedAreas));
|
|
43
46
|
const duplicated = findDuplicatedAreaIds(supportedAreas, log);
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
if (duplicated) {
|
|
48
|
+
return [
|
|
46
49
|
{
|
|
47
50
|
areaId: 2,
|
|
48
51
|
mapId: null,
|
|
@@ -55,8 +58,9 @@ export function getSupportedAreas(vacuumRooms, roomMap, log) {
|
|
|
55
58
|
landmarkInfo: null,
|
|
56
59
|
},
|
|
57
60
|
},
|
|
58
|
-
]
|
|
59
|
-
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
return supportedAreas;
|
|
60
64
|
}
|
|
61
65
|
function findDuplicatedAreaIds(areas, log) {
|
|
62
66
|
const seen = new Set();
|
package/dist/model/RoomMap.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export class RoomMap {
|
|
2
|
-
rooms
|
|
2
|
+
rooms;
|
|
3
3
|
constructor(roomData, rooms) {
|
|
4
|
-
this.rooms = roomData.map((
|
|
4
|
+
this.rooms = roomData.map(([id, globalId, altId]) => {
|
|
5
|
+
const room = rooms.find((r) => Number(r.id) === Number(globalId) || Number(r.id) === Number(id));
|
|
5
6
|
return {
|
|
6
|
-
id
|
|
7
|
-
globalId: Number(
|
|
8
|
-
displayName:
|
|
9
|
-
alternativeId: `${
|
|
7
|
+
id,
|
|
8
|
+
globalId: globalId !== undefined ? Number(globalId) : undefined,
|
|
9
|
+
displayName: room?.name,
|
|
10
|
+
alternativeId: `${id}${altId}`,
|
|
10
11
|
};
|
|
11
12
|
});
|
|
12
13
|
}
|
package/dist/platform.js
CHANGED
|
@@ -159,8 +159,8 @@ 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?.
|
|
163
|
-
vacuum.rooms = rooms.map((room) => ({ id: room.
|
|
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));
|
package/dist/platformRunner.js
CHANGED
|
@@ -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';
|
|
@@ -217,7 +218,25 @@ export class PlatformRunner {
|
|
|
217
218
|
});
|
|
218
219
|
break;
|
|
219
220
|
}
|
|
220
|
-
case Protocol.additional_props:
|
|
221
|
+
case Protocol.additional_props: {
|
|
222
|
+
platform.log.notice(`Received additional properties for robot ${duid}: ${debugStringify(data)}`);
|
|
223
|
+
const propCode = data.dps[Protocol.additional_props];
|
|
224
|
+
platform.log.debug(`DPS for additional properties: ${propCode}, AdditionalPropCode: ${AdditionalPropCode[propCode]}`);
|
|
225
|
+
if (propCode === AdditionalPropCode.map_change) {
|
|
226
|
+
platform.log.notice('------------------------ get roomData ----------------------------');
|
|
227
|
+
const roomMap = await getRoomMapFromDevice(robot.device, platform);
|
|
228
|
+
platform.log.notice('------------------------ Room map updated ------------------------');
|
|
229
|
+
const supportedAreas = getSupportedAreas(robot.device.rooms, roomMap, platform.log);
|
|
230
|
+
platform.log.notice(`Supported areas: ${debugStringify(supportedAreas)}`);
|
|
231
|
+
platform.log.notice('------------------------ Supported areas updated ------------------');
|
|
232
|
+
platform.roborockService?.setSupportedAreas(duid, supportedAreas);
|
|
233
|
+
platform.roborockService?.setSelectedAreas(duid, []);
|
|
234
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'supportedAreas', supportedAreas, platform.log);
|
|
235
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
236
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
221
240
|
case Protocol.back_type: {
|
|
222
241
|
break;
|
|
223
242
|
}
|
|
@@ -1,28 +1,30 @@
|
|
|
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.
|
|
6
|
-
|
|
7
|
-
id:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
});
|
|
22
|
+
this.allRooms = this.allRooms.filter((room, index, self) => index === self.findIndex((r) => r.globalId === room.globalId));
|
|
21
23
|
}
|
|
22
24
|
getById(id) {
|
|
23
25
|
return this.maps.find((m) => m.id === id)?.name;
|
|
24
26
|
}
|
|
25
27
|
getByName(name) {
|
|
26
|
-
return this.maps.find((m) => m.name === name.toLowerCase())?.id;
|
|
28
|
+
return this.maps.find((m) => m.name?.toLowerCase() === name.toLowerCase())?.id;
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -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
|
|
42
|
+
async getRooms(duid) {
|
|
44
43
|
const request = new RequestMessage({ method: 'get_room_mapping' });
|
|
45
|
-
return this.client.get(duid, request)
|
|
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';
|
package/dist/roborockService.js
CHANGED
|
@@ -351,7 +351,7 @@ export default class RoborockService {
|
|
|
351
351
|
assert(this.iotApi !== undefined);
|
|
352
352
|
return this.iotApi.startScene(sceneId);
|
|
353
353
|
}
|
|
354
|
-
getRoomMappings(duid) {
|
|
354
|
+
async getRoomMappings(duid) {
|
|
355
355
|
if (!this.messageClient) {
|
|
356
356
|
this.logger.warn('messageClient not initialized. Waititing for next execution');
|
|
357
357
|
return Promise.resolve(undefined);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "Matterbridge Roborock Vacuum Plugin",
|
|
3
|
-
"description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-
|
|
3
|
+
"description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc16 by https://github.com/RinDevJunior",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"required": ["username", "password"],
|
|
6
6
|
"properties": {
|
package/package.json
CHANGED
package/src/helper.ts
CHANGED
|
@@ -47,7 +47,6 @@ export async function getRoomMap(duid: string, platform: RoborockMatterbridgePla
|
|
|
47
47
|
if (platform.roborockService === undefined) return undefined;
|
|
48
48
|
|
|
49
49
|
const rooms = robot.device.rooms ?? [];
|
|
50
|
-
// if (platform.robot?.device === undefined || platform.roborockService === undefined) return undefined;
|
|
51
50
|
if (robot.roomInfo === undefined) {
|
|
52
51
|
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
53
52
|
if (roomData !== undefined && roomData.length > 0) {
|
|
@@ -58,10 +57,10 @@ export async function getRoomMap(duid: string, platform: RoborockMatterbridgePla
|
|
|
58
57
|
|
|
59
58
|
if (robot.roomInfo === undefined) {
|
|
60
59
|
const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
|
|
61
|
-
if (mapInfo && mapInfo.
|
|
62
|
-
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.
|
|
60
|
+
if (mapInfo && mapInfo.allRooms && mapInfo.allRooms.length > 0) {
|
|
61
|
+
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.allRooms)}`);
|
|
63
62
|
|
|
64
|
-
const roomDataMap = mapInfo.
|
|
63
|
+
const roomDataMap = mapInfo.allRooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
65
64
|
robot.roomInfo = new RoomMap(roomDataMap, rooms);
|
|
66
65
|
}
|
|
67
66
|
}
|
|
@@ -73,7 +72,7 @@ export async function getRoomMapFromDevice(device: Device, platform: RoborockMat
|
|
|
73
72
|
const rooms = device?.rooms ?? [];
|
|
74
73
|
|
|
75
74
|
platform.log.notice('-------------------------------------------0--------------------------------------------------------');
|
|
76
|
-
platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
|
|
75
|
+
platform.log.notice(`getRoomMapFromDevice - device.rooms: ${debugStringify(rooms)}`);
|
|
77
76
|
|
|
78
77
|
if (device && platform.roborockService) {
|
|
79
78
|
const roomData = await platform.roborockService.getRoomMappings(device.duid);
|
|
@@ -90,8 +89,8 @@ export async function getRoomMapFromDevice(device: Device, platform: RoborockMat
|
|
|
90
89
|
const mapInfo = await platform.roborockService.getMapInformation(device.duid);
|
|
91
90
|
platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
|
|
92
91
|
|
|
93
|
-
if (mapInfo && mapInfo.
|
|
94
|
-
const roomDataMap = mapInfo.
|
|
92
|
+
if (mapInfo && mapInfo.allRooms && mapInfo.allRooms.length > 0) {
|
|
93
|
+
const roomDataMap = mapInfo.allRooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
95
94
|
|
|
96
95
|
const roomMap = new RoomMap(roomDataMap, rooms);
|
|
97
96
|
|
|
@@ -8,15 +8,16 @@ export function getSupportedAreas(vacuumRooms: Room[], roomMap: RoomMap | undefi
|
|
|
8
8
|
log?.debug('getSupportedAreas-vacuum room', debugStringify(vacuumRooms));
|
|
9
9
|
log?.debug('getSupportedAreas-roomMap', roomMap ? debugStringify(roomMap) : 'undefined');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const noVacuumRooms = !vacuumRooms || vacuumRooms.length === 0;
|
|
12
|
+
const noRoomMap = !roomMap?.rooms || roomMap.rooms.length === 0;
|
|
13
|
+
|
|
14
|
+
if (noVacuumRooms || noRoomMap) {
|
|
15
|
+
if (noVacuumRooms) {
|
|
13
16
|
log?.error('No rooms found');
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
if (!roomMap || !roomMap.rooms || roomMap.rooms.length == 0) {
|
|
18
|
+
if (noRoomMap) {
|
|
17
19
|
log?.error('No room map found');
|
|
18
20
|
}
|
|
19
|
-
|
|
20
21
|
return [
|
|
21
22
|
{
|
|
22
23
|
areaId: 1,
|
|
@@ -34,12 +35,14 @@ export function getSupportedAreas(vacuumRooms: Room[], roomMap: RoomMap | undefi
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const supportedAreas: ServiceArea.Area[] = roomMap.rooms.map((room) => {
|
|
38
|
+
const locationName = room.displayName ?? vacuumRooms.find((r) => r.id === room.globalId || r.id === room.id)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`;
|
|
39
|
+
|
|
37
40
|
return {
|
|
38
41
|
areaId: room.id,
|
|
39
42
|
mapId: null,
|
|
40
43
|
areaInfo: {
|
|
41
44
|
locationInfo: {
|
|
42
|
-
locationName
|
|
45
|
+
locationName,
|
|
43
46
|
floorNumber: null,
|
|
44
47
|
areaType: null,
|
|
45
48
|
},
|
|
@@ -52,27 +55,30 @@ export function getSupportedAreas(vacuumRooms: Room[], roomMap: RoomMap | undefi
|
|
|
52
55
|
|
|
53
56
|
const duplicated = findDuplicatedAreaIds(supportedAreas, log);
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
},
|
|
66
|
-
landmarkInfo: null,
|
|
58
|
+
if (duplicated) {
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
areaId: 2,
|
|
62
|
+
mapId: null,
|
|
63
|
+
areaInfo: {
|
|
64
|
+
locationInfo: {
|
|
65
|
+
locationName: 'Unknown',
|
|
66
|
+
floorNumber: null,
|
|
67
|
+
areaType: null,
|
|
67
68
|
},
|
|
69
|
+
landmarkInfo: null,
|
|
68
70
|
},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return supportedAreas;
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
function findDuplicatedAreaIds(areas: ServiceArea.Area[], log?: AnsiLogger): boolean {
|
|
74
79
|
const seen = new Set<number>();
|
|
75
80
|
const duplicates: number[] = [];
|
|
81
|
+
|
|
76
82
|
for (const area of areas) {
|
|
77
83
|
if (seen.has(area.areaId)) {
|
|
78
84
|
duplicates.push(area.areaId);
|
|
@@ -80,9 +86,11 @@ function findDuplicatedAreaIds(areas: ServiceArea.Area[], log?: AnsiLogger): boo
|
|
|
80
86
|
seen.add(area.areaId);
|
|
81
87
|
}
|
|
82
88
|
}
|
|
89
|
+
|
|
83
90
|
if (duplicates.length > 0 && log) {
|
|
84
91
|
const duplicated = areas.filter((x) => duplicates.includes(x.areaId));
|
|
85
92
|
log.error(`Duplicated areaId(s) found: ${debugStringify(duplicated)}`);
|
|
86
93
|
}
|
|
94
|
+
|
|
87
95
|
return duplicates.length > 0;
|
|
88
96
|
}
|
package/src/model/RoomMap.ts
CHANGED
|
@@ -17,30 +17,34 @@ roomMap = {
|
|
|
17
17
|
|
|
18
18
|
import { Room } from '../roborockCommunication/Zmodel/room.js';
|
|
19
19
|
|
|
20
|
+
export interface RoomMapEntry {
|
|
21
|
+
id: number;
|
|
22
|
+
globalId: number | undefined;
|
|
23
|
+
displayName?: string;
|
|
24
|
+
alternativeId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
export class RoomMap {
|
|
21
|
-
readonly rooms:
|
|
22
|
-
id: number;
|
|
23
|
-
globalId: number | undefined;
|
|
24
|
-
displayName: string | undefined;
|
|
25
|
-
alternativeId: string;
|
|
26
|
-
}[] = [];
|
|
28
|
+
readonly rooms: RoomMapEntry[];
|
|
27
29
|
|
|
28
30
|
constructor(roomData: number[][], rooms: Room[]) {
|
|
29
|
-
this.rooms = roomData.map((
|
|
31
|
+
this.rooms = roomData.map(([id, globalId, altId]) => {
|
|
32
|
+
const room = rooms.find((r) => Number(r.id) === Number(globalId) || Number(r.id) === Number(id));
|
|
30
33
|
return {
|
|
31
|
-
id
|
|
32
|
-
globalId: Number(
|
|
33
|
-
displayName:
|
|
34
|
-
alternativeId: `${
|
|
34
|
+
id,
|
|
35
|
+
globalId: globalId !== undefined ? Number(globalId) : undefined,
|
|
36
|
+
displayName: room?.name,
|
|
37
|
+
alternativeId: `${id}${altId}`,
|
|
35
38
|
};
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
// Optionally, add utility methods for clarity
|
|
39
43
|
// getGlobalId(id: number): number | undefined {
|
|
40
|
-
// return this.rooms.find((r) =>
|
|
44
|
+
// return this.rooms.find((r) => r.id === id)?.globalId;
|
|
41
45
|
// }
|
|
42
46
|
|
|
43
47
|
// getRoomId(globalId: number): number | undefined {
|
|
44
|
-
// return this.rooms.find((r) =>
|
|
48
|
+
// return this.rooms.find((r) => r.globalId === globalId)?.id;
|
|
45
49
|
// }
|
|
46
50
|
}
|
package/src/platform.ts
CHANGED
|
@@ -227,8 +227,8 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
227
227
|
if (vacuum.rooms === undefined || vacuum.rooms.length === 0) {
|
|
228
228
|
this.log.notice(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
|
|
229
229
|
const map_info = await this.roborockService.getMapInformation(vacuum.duid);
|
|
230
|
-
const rooms = map_info?.
|
|
231
|
-
vacuum.rooms = rooms.map((room) => ({ id: room.
|
|
230
|
+
const rooms = map_info?.allRooms ?? [];
|
|
231
|
+
vacuum.rooms = rooms.map((room) => ({ id: room.globalId, name: room.displayName }) as Room);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
const roomMap = await getRoomMapFromDevice(vacuum, this);
|
package/src/platformRunner.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
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 { CloudMessageModel } from './model/CloudMessageModel.js';
|
|
5
5
|
import { RoborockMatterbridgePlatform } from './platform.js';
|
|
6
6
|
import { state_to_matter_operational_status, state_to_matter_state } from './share/function.js';
|
|
7
|
-
import { getBatteryState, getBatteryStatus, getOperationalErrorState } from './initialData/index.js';
|
|
7
|
+
import { getBatteryState, getBatteryStatus, getOperationalErrorState, getSupportedAreas } from './initialData/index.js';
|
|
8
8
|
import { NotifyMessageTypes } from './notifyMessageTypes.js';
|
|
9
9
|
import { CloudMessageResult } from './roborockCommunication/Zmodel/messageResult.js';
|
|
10
10
|
import { Protocol } from './roborockCommunication/broadcast/model/protocol.js';
|
|
11
11
|
import { DpsPayload } from './roborockCommunication/broadcast/model/dps.js';
|
|
12
12
|
import { RoborockVacuumCleaner } from './rvc.js';
|
|
13
13
|
import { hasDockingStationError, parseDockingStationStatus } from './model/DockingStationStatus.js';
|
|
14
|
-
import { BatteryMessage, Device, DeviceErrorMessage, DeviceStatusNotify, Home } from './roborockCommunication/index.js';
|
|
14
|
+
import { AdditionalPropCode, BatteryMessage, Device, DeviceErrorMessage, DeviceStatusNotify, Home } from './roborockCommunication/index.js';
|
|
15
15
|
import { OperationStatusCode } from './roborockCommunication/Zenum/operationStatusCode.js';
|
|
16
16
|
import { getCurrentCleanModeFunc } from './share/runtimeHelper.js';
|
|
17
17
|
import { debugStringify } from 'matterbridge/logger';
|
|
@@ -280,7 +280,30 @@ export class PlatformRunner {
|
|
|
280
280
|
});
|
|
281
281
|
break; // Do nothing, handled in local message
|
|
282
282
|
}
|
|
283
|
-
case Protocol.additional_props:
|
|
283
|
+
case Protocol.additional_props: {
|
|
284
|
+
platform.log.notice(`Received additional properties for robot ${duid}: ${debugStringify(data)}`);
|
|
285
|
+
const propCode = data.dps[Protocol.additional_props] as number;
|
|
286
|
+
platform.log.debug(`DPS for additional properties: ${propCode}, AdditionalPropCode: ${AdditionalPropCode[propCode]}`);
|
|
287
|
+
|
|
288
|
+
if (propCode === AdditionalPropCode.map_change) {
|
|
289
|
+
platform.log.notice('------------------------ get roomData ----------------------------');
|
|
290
|
+
|
|
291
|
+
const roomMap = await getRoomMapFromDevice(robot.device, platform);
|
|
292
|
+
|
|
293
|
+
platform.log.notice('------------------------ Room map updated ------------------------');
|
|
294
|
+
const supportedAreas = getSupportedAreas(robot.device.rooms, roomMap, platform.log);
|
|
295
|
+
|
|
296
|
+
platform.log.notice(`Supported areas: ${debugStringify(supportedAreas)}`);
|
|
297
|
+
platform.log.notice('------------------------ Supported areas updated ------------------');
|
|
298
|
+
|
|
299
|
+
platform.roborockService?.setSupportedAreas(duid, supportedAreas);
|
|
300
|
+
platform.roborockService?.setSelectedAreas(duid, []);
|
|
301
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'supportedAreas', supportedAreas, platform.log);
|
|
302
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
303
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
284
307
|
case Protocol.back_type: {
|
|
285
308
|
// TODO: check if this is needed
|
|
286
309
|
break;
|
|
@@ -2,27 +2,45 @@ import decodeComponent from '../helper/nameDecoder.js';
|
|
|
2
2
|
import { RoomInformation } from './map.js';
|
|
3
3
|
import type { MultipleMap } from './multipleMap.js';
|
|
4
4
|
|
|
5
|
+
export interface MapRoom {
|
|
6
|
+
id: number;
|
|
7
|
+
iot_name_id: string;
|
|
8
|
+
tag: number;
|
|
9
|
+
globalId?: number;
|
|
10
|
+
displayName: string;
|
|
11
|
+
mapId?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MapEntry {
|
|
15
|
+
id: number;
|
|
16
|
+
name: string | undefined;
|
|
17
|
+
rooms: MapRoom[];
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
export class MapInfo {
|
|
6
|
-
readonly maps:
|
|
21
|
+
public readonly maps: MapEntry[] = [];
|
|
22
|
+
public readonly allRooms: MapRoom[] = [];
|
|
7
23
|
|
|
8
24
|
constructor(multimap: MultipleMap) {
|
|
9
|
-
multimap.map_info.
|
|
10
|
-
|
|
11
|
-
id:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
+
this.maps = multimap.map_info.map((mapInfo) => {
|
|
26
|
+
const rooms: MapRoom[] = mapInfo.rooms.map((room: RoomInformation) => ({
|
|
27
|
+
id: room.id,
|
|
28
|
+
globalId: parseInt(room.iot_name_id),
|
|
29
|
+
iot_name_id: room.iot_name_id,
|
|
30
|
+
tag: room.tag,
|
|
31
|
+
displayName: room.iot_name,
|
|
32
|
+
mapId: mapInfo.mapFlag,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
this.allRooms.push(...rooms);
|
|
36
|
+
return {
|
|
37
|
+
id: mapInfo.mapFlag,
|
|
38
|
+
name: decodeComponent(mapInfo.name),
|
|
39
|
+
rooms,
|
|
40
|
+
};
|
|
25
41
|
});
|
|
42
|
+
|
|
43
|
+
this.allRooms = this.allRooms.filter((room, index, self) => index === self.findIndex((r) => r.globalId === room.globalId));
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
getById(id: number): string | undefined {
|
|
@@ -30,6 +48,6 @@ export class MapInfo {
|
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
getByName(name: string): number | undefined {
|
|
33
|
-
return this.maps.find((m) => m.name === name.toLowerCase())?.id;
|
|
51
|
+
return this.maps.find((m) => m.name?.toLowerCase() === name.toLowerCase())?.id;
|
|
34
52
|
}
|
|
35
53
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { AnsiLogger, debugStringify } from 'matterbridge/logger';
|
|
2
2
|
import { CloudMessageResult } from '../Zmodel/messageResult.js';
|
|
3
|
-
import { RoomInfo } from '../Zmodel/roomInfo.js';
|
|
4
3
|
import { AbstractMessageHandler } from './listener/abstractMessageHandler.js';
|
|
5
4
|
import { SimpleMessageListener } from './listener/index.js';
|
|
6
5
|
import { RequestMessage } from './model/requestMessage.js';
|
|
7
6
|
import { DeviceStatus } from '../Zmodel/deviceStatus.js';
|
|
8
|
-
import { Room } from '../Zmodel/room.js';
|
|
9
7
|
import { Client } from './client.js';
|
|
10
8
|
import { NetworkInfo } from '../Zmodel/networkInfo.js';
|
|
11
9
|
|
|
@@ -58,9 +56,9 @@ export class MessageProcessor {
|
|
|
58
56
|
return undefined;
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
public async getRooms(duid: string
|
|
59
|
+
public async getRooms(duid: string): Promise<number[][] | undefined> {
|
|
62
60
|
const request = new RequestMessage({ method: 'get_room_mapping' });
|
|
63
|
-
return this.client.get<number[][] | undefined>(duid, request).then((response) => new RoomInfo(rooms, response ?? []));
|
|
61
|
+
return this.client.get<number[][] | undefined>(duid, request); // .then((response) => new RoomInfo(rooms, response ?? []));
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
public async gotoDock(duid: string): Promise<void> {
|
|
@@ -8,6 +8,7 @@ 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
|
|
|
12
13
|
export { Scene } from './Zmodel/scene.js';
|
|
13
14
|
|
package/src/roborockService.ts
CHANGED
|
@@ -375,6 +375,7 @@ export default class RoborockService {
|
|
|
375
375
|
},
|
|
376
376
|
};
|
|
377
377
|
}) as Device[];
|
|
378
|
+
|
|
378
379
|
return result;
|
|
379
380
|
}
|
|
380
381
|
|
|
@@ -444,12 +445,14 @@ export default class RoborockService {
|
|
|
444
445
|
return this.iotApi.startScene(sceneId);
|
|
445
446
|
}
|
|
446
447
|
|
|
447
|
-
public getRoomMappings(duid: string): Promise<number[][] | undefined> {
|
|
448
|
+
public async getRoomMappings(duid: string): Promise<number[][] | undefined> {
|
|
448
449
|
if (!this.messageClient) {
|
|
449
450
|
this.logger.warn('messageClient not initialized. Waititing for next execution');
|
|
450
451
|
return Promise.resolve(undefined);
|
|
451
452
|
}
|
|
452
453
|
return this.messageClient.get(duid, new RequestMessage({ method: 'get_room_mapping', secure: this.isRequestSecure(duid) }));
|
|
454
|
+
|
|
455
|
+
// return await this.getMessageProcessor(duid)?.getRooms(duid);
|
|
453
456
|
}
|
|
454
457
|
|
|
455
458
|
public async initializeMessageClient(username: string, device: Device, userdata: UserData): Promise<void> {
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { getRoomMapFromDevice } from '../helper';
|
|
2
|
+
import { RoomMap } from '../model/RoomMap';
|
|
3
|
+
|
|
4
|
+
// Mocks
|
|
5
|
+
// const mockLog = {
|
|
6
|
+
// notice: (message: string, ...arg: unknown[]) => console.log('NOTICE:', message, ...arg),
|
|
7
|
+
// error: (message: string, ...arg: unknown[]) => console.error('ERROR:', message, ...arg),
|
|
8
|
+
// debug: (message: string, ...arg: unknown[]) => console.debug('DEBUG:', message, ...arg),
|
|
9
|
+
// info: (message: string, ...arg: unknown[]) => console.info('INFO:', message, ...arg),
|
|
10
|
+
// warn: (message: string, ...arg: unknown[]) => console.warn('WARN:', message, ...arg),
|
|
11
|
+
// verbose: (message: string, ...arg: unknown[]) => console.log('VERBOSE:', message, ...arg),
|
|
12
|
+
// };
|
|
13
|
+
|
|
14
|
+
const mockLog = {
|
|
15
|
+
notice: jest.fn(),
|
|
16
|
+
error: jest.fn(),
|
|
17
|
+
debug: jest.fn(),
|
|
18
|
+
info: jest.fn(),
|
|
19
|
+
warn: jest.fn(),
|
|
20
|
+
verbose: jest.fn(),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockRoborockService = {
|
|
24
|
+
getRoomMappings: jest.fn(),
|
|
25
|
+
getMapInformation: jest.fn(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const mockPlatform = {
|
|
29
|
+
log: mockLog,
|
|
30
|
+
roborockService: mockRoborockService,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe('getRoomMapFromDevice', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
jest.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// it('returns RoomMap from getRoomMappings if available', async () => {
|
|
39
|
+
// const device = {
|
|
40
|
+
// duid: '123',
|
|
41
|
+
// rooms: [
|
|
42
|
+
// {
|
|
43
|
+
// 'id': 12461114,
|
|
44
|
+
// 'name': 'Guest bedroom',
|
|
45
|
+
// },
|
|
46
|
+
// {
|
|
47
|
+
// 'id': 12461111,
|
|
48
|
+
// 'name': 'Balcony',
|
|
49
|
+
// },
|
|
50
|
+
// {
|
|
51
|
+
// 'id': 12461109,
|
|
52
|
+
// 'name': 'Master bedroom',
|
|
53
|
+
// },
|
|
54
|
+
// {
|
|
55
|
+
// 'id': 11100849,
|
|
56
|
+
// 'name': 'Study',
|
|
57
|
+
// },
|
|
58
|
+
// {
|
|
59
|
+
// 'id': 11100847,
|
|
60
|
+
// 'name': 'Bedroom',
|
|
61
|
+
// },
|
|
62
|
+
// {
|
|
63
|
+
// 'id': 11100845,
|
|
64
|
+
// 'name': 'Kitchen',
|
|
65
|
+
// },
|
|
66
|
+
// {
|
|
67
|
+
// 'id': 11100842,
|
|
68
|
+
// 'name': 'Living room',
|
|
69
|
+
// },
|
|
70
|
+
// ],
|
|
71
|
+
// };
|
|
72
|
+
// mockRoborockService.getRoomMappings.mockResolvedValue([
|
|
73
|
+
// [1, '11100842', 6],
|
|
74
|
+
// [2, '12461114', 3],
|
|
75
|
+
// [3, '12461109', 2],
|
|
76
|
+
// [4, '12461111', 7],
|
|
77
|
+
// ]);
|
|
78
|
+
// mockRoborockService.getMapInformation.mockResolvedValue(undefined);
|
|
79
|
+
|
|
80
|
+
// const result = await getRoomMapFromDevice(device as any, mockPlatform as any);
|
|
81
|
+
|
|
82
|
+
// //console.log('Result:', result);
|
|
83
|
+
// expect(result).toBeInstanceOf(RoomMap);
|
|
84
|
+
// expect(mockRoborockService.getRoomMappings).toHaveBeenCalledWith('123');
|
|
85
|
+
// expect(result.rooms.length).toBeGreaterThan(0);
|
|
86
|
+
// });
|
|
87
|
+
|
|
88
|
+
it('returns RoomMap from getRoomMappings if available', async () => {
|
|
89
|
+
const device = {
|
|
90
|
+
duid: '123',
|
|
91
|
+
rooms: [
|
|
92
|
+
{ id: 1, name: 'Kitchen' },
|
|
93
|
+
{ id: 2, name: 'Study' },
|
|
94
|
+
{ id: 3, name: 'Living room' },
|
|
95
|
+
{ id: 4, name: 'Bedroom' },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
mockRoborockService.getRoomMappings.mockResolvedValue([
|
|
99
|
+
[1, '11100845', 14],
|
|
100
|
+
[2, '11100849', 9],
|
|
101
|
+
[3, '11100842', 6],
|
|
102
|
+
[4, '11100847', 1],
|
|
103
|
+
]);
|
|
104
|
+
mockRoborockService.getMapInformation.mockResolvedValue(undefined);
|
|
105
|
+
|
|
106
|
+
const result = await getRoomMapFromDevice(device as any, mockPlatform as any);
|
|
107
|
+
|
|
108
|
+
// console.log('Result:', result);
|
|
109
|
+
expect(result).toBeInstanceOf(RoomMap);
|
|
110
|
+
expect(mockRoborockService.getRoomMappings).toHaveBeenCalledWith('123');
|
|
111
|
+
expect(result.rooms.length).toBeGreaterThan(0);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -63,17 +63,50 @@ describe('PlatformRunner.getRoomMapFromDevice', () => {
|
|
|
63
63
|
map_info: [
|
|
64
64
|
{
|
|
65
65
|
mapFlag: 0,
|
|
66
|
-
add_time:
|
|
67
|
-
length:
|
|
68
|
-
name: '',
|
|
69
|
-
bak_maps: [{ mapFlag: 4, add_time:
|
|
66
|
+
add_time: 1753511673,
|
|
67
|
+
length: 9,
|
|
68
|
+
name: 'First Map',
|
|
69
|
+
bak_maps: [{ mapFlag: 4, add_time: 1753578164 }],
|
|
70
70
|
rooms: [
|
|
71
71
|
{ id: 1, tag: 14, iot_name_id: '11100845', iot_name: 'Kitchen' },
|
|
72
72
|
{ id: 2, tag: 9, iot_name_id: '11100849', iot_name: 'Study' },
|
|
73
|
-
{
|
|
73
|
+
{
|
|
74
|
+
id: 3,
|
|
75
|
+
tag: 6,
|
|
76
|
+
iot_name_id: '11100842',
|
|
77
|
+
iot_name: 'Living room',
|
|
78
|
+
},
|
|
74
79
|
{ id: 4, tag: 1, iot_name_id: '11100847', iot_name: 'Bedroom' },
|
|
75
80
|
],
|
|
76
81
|
},
|
|
82
|
+
{
|
|
83
|
+
mapFlag: 1,
|
|
84
|
+
add_time: 1753579596,
|
|
85
|
+
length: 10,
|
|
86
|
+
name: 'Second Map',
|
|
87
|
+
bak_maps: [{ mapFlag: 5, add_time: 1753578579 }],
|
|
88
|
+
rooms: [
|
|
89
|
+
{
|
|
90
|
+
id: 1,
|
|
91
|
+
tag: 6,
|
|
92
|
+
iot_name_id: '11100842',
|
|
93
|
+
iot_name: 'Living room',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 2,
|
|
97
|
+
tag: 3,
|
|
98
|
+
iot_name_id: '12461114',
|
|
99
|
+
iot_name: 'Guest bedroom',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 3,
|
|
103
|
+
tag: 2,
|
|
104
|
+
iot_name_id: '12461109',
|
|
105
|
+
iot_name: 'Master bedroom',
|
|
106
|
+
},
|
|
107
|
+
{ id: 4, tag: 7, iot_name_id: '12461111', iot_name: 'Balcony' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
77
110
|
],
|
|
78
111
|
});
|
|
79
112
|
|
|
@@ -83,6 +116,6 @@ describe('PlatformRunner.getRoomMapFromDevice', () => {
|
|
|
83
116
|
const result = await getRoomMapFromDevice(device as any, platform);
|
|
84
117
|
|
|
85
118
|
expect(result).toBeInstanceOf(RoomMap);
|
|
86
|
-
expect(result.rooms.length).toEqual(
|
|
119
|
+
expect(result.rooms.length).toEqual(7);
|
|
87
120
|
});
|
|
88
121
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { PlatformRunner } from '../platformRunner';
|
|
2
|
+
import { NotifyMessageTypes } from '../notifyMessageTypes';
|
|
3
|
+
import { RoborockMatterbridgePlatform } from '../platform';
|
|
4
|
+
import { RoborockVacuumCleaner } from '../rvc';
|
|
5
|
+
import * as initialDataIndex from '../initialData/index';
|
|
6
|
+
|
|
7
|
+
const getOperationalErrorState = jest.fn().mockReturnValue(2);
|
|
8
|
+
|
|
9
|
+
jest.mock('./src/initialData/index', () => ({
|
|
10
|
+
...initialDataIndex,
|
|
11
|
+
getOperationalErrorState,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe('PlatformRunner.updateRobot', () => {
|
|
15
|
+
let platform: RoborockMatterbridgePlatform;
|
|
16
|
+
let runner: PlatformRunner;
|
|
17
|
+
let robotMock: any;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
robotMock = {
|
|
21
|
+
updateAttribute: jest.fn(),
|
|
22
|
+
getAttribute: jest.fn(),
|
|
23
|
+
device: {
|
|
24
|
+
data: { model: 'test-model' },
|
|
25
|
+
duid: '123456',
|
|
26
|
+
rooms: [],
|
|
27
|
+
},
|
|
28
|
+
serialNumber: '123456',
|
|
29
|
+
dockStationStatus: undefined,
|
|
30
|
+
roomInfo: undefined,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const robots = new Map<string, RoborockVacuumCleaner>();
|
|
34
|
+
robots.set('123456', robotMock);
|
|
35
|
+
|
|
36
|
+
platform = {
|
|
37
|
+
robots: robots,
|
|
38
|
+
log: {
|
|
39
|
+
error: jest.fn(),
|
|
40
|
+
debug: jest.fn(),
|
|
41
|
+
notice: jest.fn(),
|
|
42
|
+
},
|
|
43
|
+
enableExperimentalFeature: undefined,
|
|
44
|
+
} as unknown as RoborockMatterbridgePlatform;
|
|
45
|
+
|
|
46
|
+
runner = new PlatformRunner(platform);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle unknown message types gracefully', async () => {
|
|
50
|
+
const mapUpdated = { duid: '123456', dps: { 128: 4 } };
|
|
51
|
+
await runner['updateFromMQTTMessage'](NotifyMessageTypes.CloudMessage, mapUpdated, '123456');
|
|
52
|
+
expect(platform.log.notice).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { MessageProcessor } from '../../../roborockCommunication/broadcast/messageProcessor';
|
|
2
2
|
import { DeviceStatus } from '../../../roborockCommunication/Zmodel/deviceStatus';
|
|
3
|
-
import { RoomInfo } from '../../../roborockCommunication/Zmodel/roomInfo';
|
|
4
3
|
|
|
5
4
|
describe('MessageProcessor', () => {
|
|
6
5
|
let mockClient: any;
|
|
@@ -51,13 +50,9 @@ describe('MessageProcessor', () => {
|
|
|
51
50
|
});
|
|
52
51
|
|
|
53
52
|
it('getRooms should return RoomInfo', async () => {
|
|
54
|
-
const rooms = [
|
|
55
|
-
{ id: 1, name: 'Room1' },
|
|
56
|
-
{ id: 2, name: 'Room2' },
|
|
57
|
-
];
|
|
58
53
|
mockClient.get.mockResolvedValue([[1, 2]]);
|
|
59
|
-
const result = await processor.getRooms('duid'
|
|
60
|
-
expect(result).
|
|
54
|
+
const result = await processor.getRooms('duid');
|
|
55
|
+
expect(result).not.toBeUndefined();
|
|
61
56
|
});
|
|
62
57
|
|
|
63
58
|
it('gotoDock should call client.send', async () => {
|
|
@@ -55,7 +55,17 @@ describe('RoborockService - startClean', () => {
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('should return MapInfo if response contains maps', async () => {
|
|
58
|
-
const mapData = [
|
|
58
|
+
const mapData = [
|
|
59
|
+
{
|
|
60
|
+
map_info: [
|
|
61
|
+
{
|
|
62
|
+
rooms: [{ id: 1, iot_name_id: 'room1', tag: 0, iot_name: 'Living Room' }],
|
|
63
|
+
mapFlag: 1,
|
|
64
|
+
name: 'Living Room Map',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
] as MultipleMap[];
|
|
59
69
|
mockMessageClient = {
|
|
60
70
|
get: jest.fn(),
|
|
61
71
|
};
|