matterbridge-roborock-vacuum-plugin 1.1.0-rc14 → 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 +62 -0
- package/dist/initialData/getSupportedAreas.js +12 -8
- package/dist/model/RoomMap.js +8 -7
- package/dist/platform.js +4 -4
- package/dist/platformRunner.js +24 -68
- package/dist/roborockCommunication/Zenum/additionalPropCode.js +4 -0
- package/dist/roborockCommunication/Zmodel/mapInfo.js +18 -16
- package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +2 -2
- package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +0 -4
- package/dist/roborockCommunication/broadcast/messageProcessor.js +3 -4
- 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 +82 -0
- package/src/initialData/getSupportedAreas.ts +29 -21
- package/src/model/RoomMap.ts +18 -14
- package/src/platform.ts +4 -4
- package/src/platformRunner.ts +29 -89
- 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/client/LocalNetworkClient.ts +2 -3
- package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +4 -4
- package/src/roborockCommunication/broadcast/messageProcessor.ts +4 -6
- package/src/roborockCommunication/index.ts +1 -0
- package/src/roborockService.ts +4 -1
- package/src/rvc.ts +1 -1
- package/src/tests/helper.test.ts +113 -0
- package/src/tests/platformRunner2.test.ts +43 -13
- package/src/tests/platformRunner3.test.ts +54 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +3 -3
- package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +3 -8
- package/src/tests/roborockService.test.ts +11 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnsiLogger, debugStringify } from 'matterbridge/logger';
|
|
2
2
|
import { ServiceArea } from 'matterbridge/matter/clusters';
|
|
3
|
-
import RoomMap from '../model/RoomMap.js';
|
|
3
|
+
import { RoomMap } from '../model/RoomMap.js';
|
|
4
4
|
import { Room } from '../roborockCommunication/Zmodel/room.js';
|
|
5
5
|
import { randomInt } from 'node:crypto';
|
|
6
6
|
|
|
@@ -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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
export interface RoomMapEntry {
|
|
21
|
+
id: number;
|
|
22
|
+
globalId: number | undefined;
|
|
23
|
+
displayName?: string;
|
|
24
|
+
alternativeId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class RoomMap {
|
|
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
|
@@ -4,7 +4,7 @@ import { AnsiLogger, debugStringify, LogLevel } from 'matterbridge/logger';
|
|
|
4
4
|
import RoborockService from './roborockService.js';
|
|
5
5
|
import { PLUGIN_NAME } from './settings.js';
|
|
6
6
|
import ClientManager from './clientManager.js';
|
|
7
|
-
import { isSupportedDevice } from './helper.js';
|
|
7
|
+
import { getRoomMapFromDevice, isSupportedDevice } from './helper.js';
|
|
8
8
|
import { PlatformRunner } from './platformRunner.js';
|
|
9
9
|
import { RoborockVacuumCleaner } from './rvc.js';
|
|
10
10
|
import { configurateBehavior } from './behaviorFactory.js';
|
|
@@ -227,11 +227,11 @@ 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
|
-
const roomMap = await
|
|
234
|
+
const roomMap = await getRoomMapFromDevice(vacuum, this);
|
|
235
235
|
|
|
236
236
|
this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
|
|
237
237
|
|
package/src/platformRunner.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { RvcRunMode, PowerSource, ServiceArea, RvcOperationalState, RvcCleanMode } from 'matterbridge/matter/clusters';
|
|
2
|
-
import { getVacuumProperty } 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
|
|
8
|
-
import { getBatteryState, getBatteryStatus, getOperationalErrorState } from './initialData/index.js';
|
|
7
|
+
import { getBatteryState, getBatteryStatus, getOperationalErrorState, getSupportedAreas } from './initialData/index.js';
|
|
9
8
|
import { NotifyMessageTypes } from './notifyMessageTypes.js';
|
|
10
9
|
import { CloudMessageResult } from './roborockCommunication/Zmodel/messageResult.js';
|
|
11
10
|
import { Protocol } from './roborockCommunication/broadcast/model/protocol.js';
|
|
12
11
|
import { DpsPayload } from './roborockCommunication/broadcast/model/dps.js';
|
|
13
12
|
import { RoborockVacuumCleaner } from './rvc.js';
|
|
14
13
|
import { hasDockingStationError, parseDockingStationStatus } from './model/DockingStationStatus.js';
|
|
15
|
-
import { BatteryMessage, Device, DeviceErrorMessage, DeviceStatusNotify, Home } from './roborockCommunication/index.js';
|
|
14
|
+
import { AdditionalPropCode, BatteryMessage, Device, DeviceErrorMessage, DeviceStatusNotify, Home } from './roborockCommunication/index.js';
|
|
16
15
|
import { OperationStatusCode } from './roborockCommunication/Zenum/operationStatusCode.js';
|
|
17
16
|
import { getCurrentCleanModeFunc } from './share/runtimeHelper.js';
|
|
18
17
|
import { debugStringify } from 'matterbridge/logger';
|
|
@@ -41,76 +40,6 @@ export class PlatformRunner {
|
|
|
41
40
|
await this.updateRobot(NotifyMessageTypes.HomeData, homeData);
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
public async getRoomMapFromDevice(device: Device): Promise<RoomMap> {
|
|
45
|
-
const platform = this.platform;
|
|
46
|
-
const rooms = device?.rooms ?? [];
|
|
47
|
-
|
|
48
|
-
platform.log.notice('-------------------------------------------0--------------------------------------------------------');
|
|
49
|
-
platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
|
|
50
|
-
|
|
51
|
-
if (device && platform.roborockService) {
|
|
52
|
-
const roomData = await platform.roborockService.getRoomMappings(device.duid);
|
|
53
|
-
if (roomData !== undefined && roomData.length > 0) {
|
|
54
|
-
platform.log.notice(`getRoomMapFromDevice - roomData: ${debugStringify(roomData ?? [])}`);
|
|
55
|
-
|
|
56
|
-
const roomMap = new RoomMap(roomData ?? [], rooms);
|
|
57
|
-
|
|
58
|
-
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
59
|
-
platform.log.notice('-------------------------------------------1--------------------------------------------------------');
|
|
60
|
-
return roomMap;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const mapInfo = await platform.roborockService.getMapInformation(device.duid);
|
|
64
|
-
platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
|
|
65
|
-
|
|
66
|
-
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
67
|
-
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
68
|
-
|
|
69
|
-
const roomMap = new RoomMap(roomDataMap, rooms);
|
|
70
|
-
|
|
71
|
-
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
72
|
-
platform.log.notice('-------------------------------------------2--------------------------------------------------------');
|
|
73
|
-
return roomMap;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return new RoomMap([], rooms);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
81
|
-
const platform = this.platform;
|
|
82
|
-
|
|
83
|
-
const robot = platform.robots.get(duid);
|
|
84
|
-
if (robot === undefined) {
|
|
85
|
-
platform.log.error(`Error6: Robot with DUID ${duid} not found`);
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (platform.roborockService === undefined) return undefined;
|
|
90
|
-
|
|
91
|
-
const rooms = robot.device.rooms ?? [];
|
|
92
|
-
// if (platform.robot?.device === undefined || platform.roborockService === undefined) return undefined;
|
|
93
|
-
if (robot.roomInfo === undefined) {
|
|
94
|
-
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
95
|
-
if (roomData !== undefined && roomData.length > 0) {
|
|
96
|
-
robot.roomInfo = new RoomMap(roomData ?? [], rooms);
|
|
97
|
-
return robot.roomInfo;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (robot.roomInfo === undefined) {
|
|
102
|
-
const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
|
|
103
|
-
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
104
|
-
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.maps)}`);
|
|
105
|
-
|
|
106
|
-
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
107
|
-
robot.roomInfo = new RoomMap(roomDataMap, rooms);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return robot.roomInfo;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
43
|
private async updateFromMQTTMessage(messageSource: NotifyMessageTypes, messageData: unknown, duid = '', tracked = false): Promise<void> {
|
|
115
44
|
const platform = this.platform;
|
|
116
45
|
duid = duid || (messageData as DeviceStatusNotify)?.duid || '';
|
|
@@ -191,7 +120,7 @@ export class PlatformRunner {
|
|
|
191
120
|
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
192
121
|
} else {
|
|
193
122
|
const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
|
|
194
|
-
const roomMap = await
|
|
123
|
+
const roomMap = await getRoomMap(duid, this.platform);
|
|
195
124
|
|
|
196
125
|
// Get current room from segment_id
|
|
197
126
|
const segment_id = data.cleaning_info?.segment_id ?? -1;
|
|
@@ -311,7 +240,7 @@ export class PlatformRunner {
|
|
|
311
240
|
case Protocol.rpc_response: {
|
|
312
241
|
const response = data.dps[messageType] as DpsPayload;
|
|
313
242
|
// ignore network info
|
|
314
|
-
if (!
|
|
243
|
+
if (!isStatusUpdate(response.result)) {
|
|
315
244
|
platform.log.debug('Ignore message:', debugStringify(data));
|
|
316
245
|
return;
|
|
317
246
|
}
|
|
@@ -351,7 +280,30 @@ export class PlatformRunner {
|
|
|
351
280
|
});
|
|
352
281
|
break; // Do nothing, handled in local message
|
|
353
282
|
}
|
|
354
|
-
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
|
+
}
|
|
355
307
|
case Protocol.back_type: {
|
|
356
308
|
// TODO: check if this is needed
|
|
357
309
|
break;
|
|
@@ -398,18 +350,6 @@ export class PlatformRunner {
|
|
|
398
350
|
return undefined;
|
|
399
351
|
}
|
|
400
352
|
|
|
401
|
-
private isStatusUpdate(result: unknown): boolean {
|
|
402
|
-
return (
|
|
403
|
-
Array.isArray(result) &&
|
|
404
|
-
result.length > 0 &&
|
|
405
|
-
typeof result[0] === 'object' &&
|
|
406
|
-
result[0] !== null &&
|
|
407
|
-
'msg_ver' in result[0] &&
|
|
408
|
-
(result[0] as CloudMessageResult).msg_ver !== undefined &&
|
|
409
|
-
(result[0] as CloudMessageResult).msg_ver !== null
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
353
|
private updateFromHomeData(homeData: Home): void {
|
|
414
354
|
const platform = this.platform;
|
|
415
355
|
|
|
@@ -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
|
}
|
|
@@ -80,12 +80,11 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
80
80
|
private async onConnect(): Promise<void> {
|
|
81
81
|
this.logger.debug(` [LocalNetworkClient]: ${this.duid} connected to ${this.ip}`);
|
|
82
82
|
this.logger.debug(` [LocalNetworkClient]: ${this.duid} socket writable: ${this.socket?.writable}, readable: ${this.socket?.readable}`);
|
|
83
|
+
this.connected = true;
|
|
84
|
+
this.retryCount = 0;
|
|
83
85
|
|
|
84
86
|
await this.sendHelloMessage();
|
|
85
87
|
this.pingInterval = setInterval(this.sendPingRequest.bind(this), 5000);
|
|
86
|
-
|
|
87
|
-
this.connected = true;
|
|
88
|
-
this.retryCount = 0;
|
|
89
88
|
await this.connectionListeners.onConnected(this.duid);
|
|
90
89
|
}
|
|
91
90
|
|
|
@@ -28,10 +28,10 @@ export class SyncMessageListener implements AbstractMessageListener {
|
|
|
28
28
|
const messageId = dps.id;
|
|
29
29
|
|
|
30
30
|
const responseHandler = this.pending.get(messageId);
|
|
31
|
-
const result = dps.result as Record<string, unknown>;
|
|
32
|
-
if (result && result.length == 1 && result[0] == 'ok') {
|
|
33
|
-
|
|
34
|
-
}
|
|
31
|
+
// const result = dps.result as Record<string, unknown>;
|
|
32
|
+
// if (result && result.length == 1 && result[0] == 'ok') {
|
|
33
|
+
// return;
|
|
34
|
+
// }
|
|
35
35
|
|
|
36
36
|
if (responseHandler) {
|
|
37
37
|
responseHandler(dps.result as ResponseMessage);
|
|
@@ -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> {
|
|
@@ -105,9 +103,9 @@ export class MessageProcessor {
|
|
|
105
103
|
return this.client.get(duid, def);
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
public async findMyRobot(duid: string): Promise<
|
|
106
|
+
public async findMyRobot(duid: string): Promise<unknown> {
|
|
109
107
|
const request = new RequestMessage({ method: 'find_me' });
|
|
110
|
-
return this.client.
|
|
108
|
+
return this.client.get(duid, request);
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
public async getCleanModeData(duid: string): Promise<{ suctionPower: number; waterFlow: number; distance_off: number; mopRoute: number }> {
|
|
@@ -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> {
|
package/src/rvc.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RoboticVacuumCleaner } from 'matterbridge/devices';
|
|
2
|
-
import RoomMap from './model/RoomMap.js';
|
|
2
|
+
import { RoomMap } from './model/RoomMap.js';
|
|
3
3
|
import { Device } from './roborockCommunication/index.js';
|
|
4
4
|
import { getOperationalStates, getSupportedAreas, getSupportedCleanModes, getSupportedRunModes } from './initialData/index.js';
|
|
5
5
|
import { AnsiLogger } from 'matterbridge/logger';
|
|
@@ -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
|
+
});
|