matterbridge-roborock-vacuum-plugin 1.1.0-rc11 → 1.1.0-rc13
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/dist/initialData/getSupportedAreas.js +12 -6
- package/dist/model/RoomMap.js +1 -0
- package/dist/platform.js +4 -3
- package/dist/platformRunner.js +67 -12
- package/dist/roborockCommunication/Zmodel/mapInfo.js +4 -2
- package/dist/roborockCommunication/broadcast/abstractClient.js +1 -1
- package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +25 -25
- package/dist/roborockCommunication/broadcast/client/LocalNetworkUDPClient.js +129 -0
- package/dist/roborockCommunication/broadcast/client/MQTTClient.js +53 -25
- package/dist/roborockCommunication/broadcast/clientRouter.js +2 -2
- package/dist/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.js +7 -2
- package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +9 -6
- package/dist/roborockCommunication/helper/messageDeserializer.js +1 -1
- package/dist/roborockService.js +5 -7
- package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
- package/matterbridge-roborock-vacuum-plugin.schema.json +1 -1
- package/package.json +2 -1
- package/src/initialData/getSupportedAreas.ts +15 -6
- package/src/model/RoomMap.ts +2 -0
- package/src/platform.ts +5 -3
- package/src/platformRunner.ts +88 -13
- package/src/roborockCommunication/Zmodel/device.ts +0 -1
- package/src/roborockCommunication/Zmodel/map.ts +1 -0
- package/src/roborockCommunication/Zmodel/mapInfo.ts +6 -5
- package/src/roborockCommunication/broadcast/abstractClient.ts +1 -2
- package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +34 -28
- package/src/roborockCommunication/broadcast/client/LocalNetworkUDPClient.ts +157 -0
- package/src/roborockCommunication/broadcast/client/MQTTClient.ts +69 -36
- package/src/roborockCommunication/broadcast/clientRouter.ts +2 -2
- package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +2 -1
- package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +8 -2
- package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +11 -6
- package/src/roborockCommunication/helper/messageDeserializer.ts +2 -3
- package/src/roborockService.ts +6 -9
- package/src/tests/initialData/getSupportedAreas.test.ts +27 -0
- package/src/tests/platformRunner2.test.ts +91 -0
- package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +1 -7
- package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +13 -106
- package/web-for-testing/src/accountStore.ts +2 -0
package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js
CHANGED
|
@@ -3,25 +3,26 @@ export class ConnectionStateListener {
|
|
|
3
3
|
client;
|
|
4
4
|
clientName;
|
|
5
5
|
shouldReconnect;
|
|
6
|
-
|
|
7
|
-
constructor(logger, client, clientName, changeToSecureConnection, shouldReconnect = false) {
|
|
6
|
+
constructor(logger, client, clientName, shouldReconnect = false) {
|
|
8
7
|
this.logger = logger;
|
|
9
8
|
this.client = client;
|
|
10
9
|
this.clientName = clientName;
|
|
11
10
|
this.shouldReconnect = shouldReconnect;
|
|
12
|
-
this.changeToSecureConnection = changeToSecureConnection;
|
|
13
11
|
}
|
|
14
12
|
async onConnected(duid) {
|
|
15
13
|
this.logger.notice(`Device ${duid} connected to ${this.clientName}`);
|
|
16
14
|
}
|
|
17
|
-
async
|
|
15
|
+
async onReconnect(duid, message) {
|
|
16
|
+
this.logger.info(`Device ${duid} reconnected to ${this.clientName} with message: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
async onDisconnected(duid, message) {
|
|
19
|
+
this.logger.error(`Device ${duid} disconnected from ${this.clientName} with message: ${message}`);
|
|
18
20
|
if (!this.shouldReconnect) {
|
|
19
21
|
this.logger.notice(`Device ${duid} disconnected from ${this.clientName}, but re-registration is disabled.`);
|
|
20
22
|
return;
|
|
21
23
|
}
|
|
22
24
|
if (this.client.retryCount > 10) {
|
|
23
25
|
this.logger.error(`Device with DUID ${duid} has exceeded retry limit, not re-registering.`);
|
|
24
|
-
this.changeToSecureConnection(duid);
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
28
|
this.client.retryCount++;
|
|
@@ -31,7 +32,9 @@ export class ConnectionStateListener {
|
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
this.logger.info(`Re-registering device with DUID ${duid} to ${this.clientName}`);
|
|
34
|
-
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
this.client.connect();
|
|
37
|
+
}, 3000);
|
|
35
38
|
this.client.isInDisconnectingStep = false;
|
|
36
39
|
}
|
|
37
40
|
async onError(duid, message) {
|
package/dist/roborockService.js
CHANGED
|
@@ -54,6 +54,9 @@ export default class RoborockService {
|
|
|
54
54
|
this.logger.debug('RoborockService - setSelectedAreas', selectedAreas);
|
|
55
55
|
this.selectedAreas.set(duid, selectedAreas);
|
|
56
56
|
}
|
|
57
|
+
getSelectedAreas(duid) {
|
|
58
|
+
return this.selectedAreas.get(duid) ?? [];
|
|
59
|
+
}
|
|
57
60
|
setSupportedAreas(duid, supportedAreas) {
|
|
58
61
|
this.supportedAreas.set(duid, supportedAreas);
|
|
59
62
|
}
|
|
@@ -260,7 +263,6 @@ export default class RoborockService {
|
|
|
260
263
|
model: homeData.products.find((p) => p.id === device.productId)?.model,
|
|
261
264
|
category: homeData.products.find((p) => p.id === device.productId)?.category,
|
|
262
265
|
batteryLevel: device.deviceStatus?.[Protocol.battery] ?? 100,
|
|
263
|
-
schema: homeData.products.find((p) => p.id === device.productId)?.schema,
|
|
264
266
|
},
|
|
265
267
|
store: {
|
|
266
268
|
username: username,
|
|
@@ -308,7 +310,6 @@ export default class RoborockService {
|
|
|
308
310
|
model: homeData.products.find((p) => p.id === device.productId)?.model,
|
|
309
311
|
category: homeData.products.find((p) => p.id === device.productId)?.category,
|
|
310
312
|
batteryLevel: device.deviceStatus?.[Protocol.battery] ?? 100,
|
|
311
|
-
schema: homeData.products.find((p) => p.id === device.productId)?.schema,
|
|
312
313
|
},
|
|
313
314
|
store: {
|
|
314
315
|
userData: this.userdata,
|
|
@@ -412,13 +413,13 @@ export default class RoborockService {
|
|
|
412
413
|
}
|
|
413
414
|
if (localIp) {
|
|
414
415
|
this.logger.debug('initializing the local connection for this client towards ' + localIp);
|
|
415
|
-
const localClient = this.messageClient.registerClient(device.duid, localIp
|
|
416
|
+
const localClient = this.messageClient.registerClient(device.duid, localIp);
|
|
416
417
|
localClient.connect();
|
|
417
418
|
let count = 0;
|
|
418
419
|
while (!localClient.isConnected() && count < 20) {
|
|
419
420
|
this.logger.debug('Keep waiting for local client to connect');
|
|
420
421
|
count++;
|
|
421
|
-
await this.sleep(
|
|
422
|
+
await this.sleep(500);
|
|
422
423
|
}
|
|
423
424
|
if (!localClient.isConnected()) {
|
|
424
425
|
throw new Error('Local client did not connect after 10 attempts, something is wrong');
|
|
@@ -434,9 +435,6 @@ export default class RoborockService {
|
|
|
434
435
|
}
|
|
435
436
|
return true;
|
|
436
437
|
}
|
|
437
|
-
onLocalClientDisconnect(duid) {
|
|
438
|
-
this.mqttAlwaysOnDevices.set(duid, true);
|
|
439
|
-
}
|
|
440
438
|
sleep(ms) {
|
|
441
439
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
442
440
|
}
|
|
@@ -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-rc13 by https://github.com/RinDevJunior",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"required": ["username", "password"],
|
|
6
6
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-roborock-vacuum-plugin",
|
|
3
|
-
"version": "1.1.0-
|
|
3
|
+
"version": "1.1.0-rc13",
|
|
4
4
|
"description": "Matterbridge Roborock Vacuum Plugin",
|
|
5
5
|
"author": "https://github.com/RinDevJunior",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@log4js-node/log4js-api": "^1.0.2",
|
|
38
|
+
"@types/binary-parser": "^1.5.5",
|
|
38
39
|
"@types/node-persist": "^3.1.8",
|
|
39
40
|
"axios": "^1.9.0",
|
|
40
41
|
"binary-parser": "^2.2.1",
|
|
@@ -4,12 +4,19 @@ import RoomMap from '../model/RoomMap.js';
|
|
|
4
4
|
import { Room } from '../roborockCommunication/Zmodel/room.js';
|
|
5
5
|
import { randomInt } from 'node:crypto';
|
|
6
6
|
|
|
7
|
-
export function getSupportedAreas(
|
|
8
|
-
log?.debug('getSupportedAreas', debugStringify(
|
|
9
|
-
log?.debug('getSupportedAreas', roomMap ? debugStringify(roomMap) : 'undefined');
|
|
7
|
+
export function getSupportedAreas(vacuumRooms: Room[], roomMap: RoomMap | undefined, log?: AnsiLogger): ServiceArea.Area[] {
|
|
8
|
+
log?.debug('getSupportedAreas-vacuum room', debugStringify(vacuumRooms));
|
|
9
|
+
log?.debug('getSupportedAreas-roomMap', roomMap ? debugStringify(roomMap) : 'undefined');
|
|
10
|
+
|
|
11
|
+
if (!vacuumRooms || vacuumRooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
|
|
12
|
+
if (!vacuumRooms || vacuumRooms.length === 0) {
|
|
13
|
+
log?.error('No rooms found');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!roomMap || !roomMap.rooms || roomMap.rooms.length == 0) {
|
|
17
|
+
log?.error('No room map found');
|
|
18
|
+
}
|
|
10
19
|
|
|
11
|
-
if (!rooms || rooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
|
|
12
|
-
log?.error('No rooms found');
|
|
13
20
|
return [
|
|
14
21
|
{
|
|
15
22
|
areaId: 1,
|
|
@@ -32,7 +39,7 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
|
|
|
32
39
|
mapId: null,
|
|
33
40
|
areaInfo: {
|
|
34
41
|
locationInfo: {
|
|
35
|
-
locationName: room.displayName ??
|
|
42
|
+
locationName: room.displayName ?? vacuumRooms.find((r) => r.id == room.globalId || r.id == room.id)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`,
|
|
36
43
|
floorNumber: null,
|
|
37
44
|
areaType: null,
|
|
38
45
|
},
|
|
@@ -41,6 +48,8 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
|
|
|
41
48
|
};
|
|
42
49
|
});
|
|
43
50
|
|
|
51
|
+
log?.debug('getSupportedAreas - supportedAreas', debugStringify(supportedAreas));
|
|
52
|
+
|
|
44
53
|
const duplicated = findDuplicatedAreaIds(supportedAreas, log);
|
|
45
54
|
|
|
46
55
|
return duplicated
|
package/src/model/RoomMap.ts
CHANGED
|
@@ -22,6 +22,7 @@ export default class RoomMap {
|
|
|
22
22
|
id: number;
|
|
23
23
|
globalId: number | undefined;
|
|
24
24
|
displayName: string | undefined;
|
|
25
|
+
alternativeId: string;
|
|
25
26
|
}[] = [];
|
|
26
27
|
|
|
27
28
|
constructor(roomData: number[][], rooms: Room[]) {
|
|
@@ -30,6 +31,7 @@ export default class RoomMap {
|
|
|
30
31
|
id: entry[0],
|
|
31
32
|
globalId: Number(entry[1]),
|
|
32
33
|
displayName: rooms.find((r) => Number(r.id) == Number(entry[1]))?.name,
|
|
34
|
+
alternativeId: `${entry[0]}${entry[2]}`,
|
|
33
35
|
};
|
|
34
36
|
});
|
|
35
37
|
}
|
package/src/platform.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { CleanModeSettings, createDefaultExperimentalFeatureSetting, Experimenta
|
|
|
15
15
|
import { ServiceArea } from 'matterbridge/matter/clusters';
|
|
16
16
|
import NodePersist from 'node-persist';
|
|
17
17
|
import Path from 'node:path';
|
|
18
|
+
import { Room } from './roborockCommunication/Zmodel/room.js';
|
|
18
19
|
|
|
19
20
|
export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
20
21
|
robots: Map<string, RoborockVacuumCleaner> = new Map<string, RoborockVacuumCleaner>();
|
|
@@ -32,9 +33,9 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
32
33
|
super(matterbridge, log, config);
|
|
33
34
|
|
|
34
35
|
// Verify that Matterbridge is the correct version
|
|
35
|
-
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.
|
|
36
|
+
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.6')) {
|
|
36
37
|
throw new Error(
|
|
37
|
-
`This plugin requires Matterbridge version >= "3.1.
|
|
38
|
+
`This plugin requires Matterbridge version >= "3.1.6". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`,
|
|
38
39
|
);
|
|
39
40
|
}
|
|
40
41
|
this.log.info('Initializing platform:', this.config.name);
|
|
@@ -224,9 +225,10 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
if (vacuum.rooms === undefined || vacuum.rooms.length === 0) {
|
|
228
|
+
this.log.notice(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
|
|
227
229
|
const map_info = await this.roborockService.getMapInformation(vacuum.duid);
|
|
228
230
|
const rooms = map_info?.maps?.[0]?.rooms ?? [];
|
|
229
|
-
vacuum.rooms = rooms;
|
|
231
|
+
vacuum.rooms = rooms.map((room) => ({ id: room.id, name: room.displayName }) as Room);
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
|
package/src/platformRunner.ts
CHANGED
|
@@ -45,14 +45,39 @@ export class PlatformRunner {
|
|
|
45
45
|
const platform = this.platform;
|
|
46
46
|
const rooms = device?.rooms ?? [];
|
|
47
47
|
|
|
48
|
+
platform.log.notice('-------------------------------------------0--------------------------------------------------------');
|
|
49
|
+
platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
|
|
50
|
+
|
|
48
51
|
if (device && platform.roborockService) {
|
|
49
52
|
const roomData = await platform.roborockService.getRoomMappings(device.duid);
|
|
50
|
-
|
|
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
|
+
}
|
|
51
75
|
}
|
|
76
|
+
|
|
52
77
|
return new RoomMap([], rooms);
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
80
|
+
private async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
56
81
|
const platform = this.platform;
|
|
57
82
|
|
|
58
83
|
const robot = platform.robots.get(duid);
|
|
@@ -67,8 +92,20 @@ export class PlatformRunner {
|
|
|
67
92
|
// if (platform.robot?.device === undefined || platform.roborockService === undefined) return undefined;
|
|
68
93
|
if (robot.roomInfo === undefined) {
|
|
69
94
|
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
109
|
}
|
|
73
110
|
|
|
74
111
|
return robot.roomInfo;
|
|
@@ -144,15 +181,51 @@ export class PlatformRunner {
|
|
|
144
181
|
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
|
|
145
182
|
}
|
|
146
183
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
184
|
+
if (data.state === OperationStatusCode.Idle) {
|
|
185
|
+
const selectedAreas = platform.roborockService?.getSelectedAreas(duid) ?? [];
|
|
186
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', selectedAreas, platform.log);
|
|
187
|
+
}
|
|
150
188
|
|
|
151
|
-
if (
|
|
189
|
+
if (state === RvcRunMode.ModeTag.Cleaning && !data.cleaning_info) {
|
|
190
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
191
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
192
|
+
} else {
|
|
193
|
+
const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
|
|
152
194
|
const roomMap = await this.getRoomMap(duid);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
195
|
+
|
|
196
|
+
// Get current room from segment_id
|
|
197
|
+
const segment_id = data.cleaning_info?.segment_id ?? -1;
|
|
198
|
+
const target_segment_id = data.cleaning_info?.target_segment_id ?? -1;
|
|
199
|
+
|
|
200
|
+
let target_room_id = roomMap?.rooms.find((x) => x.id === segment_id || x.alternativeId === segment_id.toString())?.id ?? -1;
|
|
201
|
+
this.platform.log.debug(`Target segment id: ${segment_id}, targetRoom: ${target_room_id}`);
|
|
202
|
+
|
|
203
|
+
const isMappedArea = currentMappedAreas?.some((x) => x.areaId == segment_id);
|
|
204
|
+
if (segment_id !== -1 && isMappedArea) {
|
|
205
|
+
this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
|
|
206
|
+
this.platform.log.debug(
|
|
207
|
+
`Part1: CurrentRoom: ${segment_id}, room name: ${roomMap?.rooms.find((x) => x.id === segment_id || x.alternativeId === segment_id.toString())?.displayName ?? 'unknown'}`,
|
|
208
|
+
);
|
|
209
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', segment_id, platform.log);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (segment_id == -1) {
|
|
213
|
+
const isTargetMappedArea = currentMappedAreas?.some((x) => x.areaId == target_segment_id);
|
|
214
|
+
target_room_id = roomMap?.rooms.find((x) => x.id == target_segment_id || x.alternativeId === target_segment_id.toString())?.id ?? -1;
|
|
215
|
+
this.platform.log.debug(`Target segment id: ${target_segment_id}, targetRoom: ${target_room_id}`);
|
|
216
|
+
|
|
217
|
+
if (target_segment_id !== -1 && isTargetMappedArea) {
|
|
218
|
+
this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
|
|
219
|
+
this.platform.log.debug(
|
|
220
|
+
`Part2: TargetRoom: ${target_segment_id}, room name: ${roomMap?.rooms.find((x) => x.id === target_segment_id || x.alternativeId === segment_id.toString())?.displayName ?? 'unknown'}`,
|
|
221
|
+
);
|
|
222
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', target_segment_id, platform.log);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (target_segment_id == -1 && segment_id == -1) {
|
|
227
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
228
|
+
}
|
|
156
229
|
}
|
|
157
230
|
|
|
158
231
|
if (data.battery) {
|
|
@@ -167,10 +240,12 @@ export class PlatformRunner {
|
|
|
167
240
|
waterFlow: data.cleaning_info?.water_box_status ?? data.water_box_mode,
|
|
168
241
|
distance_off: data.distance_off,
|
|
169
242
|
mopRoute: data.cleaning_info?.mop_mode ?? data.mop_mode,
|
|
243
|
+
segment_id: data.cleaning_info?.segment_id,
|
|
244
|
+
target_segment_id: data.cleaning_info?.target_segment_id,
|
|
170
245
|
};
|
|
171
246
|
|
|
172
247
|
this.platform.log.debug(`data: ${debugStringify(data)}`);
|
|
173
|
-
this.platform.log.
|
|
248
|
+
this.platform.log.notice(`currentCleanModeSetting: ${debugStringify(currentCleanModeSetting)}`);
|
|
174
249
|
|
|
175
250
|
if (currentCleanModeSetting.mopRoute && currentCleanModeSetting.suctionPower && currentCleanModeSetting.waterFlow) {
|
|
176
251
|
const currentCleanMode = getCurrentCleanModeFunc(
|
|
@@ -282,7 +357,7 @@ export class PlatformRunner {
|
|
|
282
357
|
break;
|
|
283
358
|
}
|
|
284
359
|
default: {
|
|
285
|
-
platform.log.notice(`Unknown message type: ${Protocol[messageType]
|
|
360
|
+
platform.log.notice(`Unknown message type ${messageType}, protocol: ${Protocol[messageType]}, message: ${debugStringify(data)}`);
|
|
286
361
|
break;
|
|
287
362
|
}
|
|
288
363
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import decodeComponent from '../helper/nameDecoder.js';
|
|
2
2
|
import { RoomInformation } from './map.js';
|
|
3
3
|
import type { MultipleMap } from './multipleMap.js';
|
|
4
|
-
import { Room } from './room.js';
|
|
5
4
|
|
|
6
5
|
export class MapInfo {
|
|
7
|
-
readonly maps: { id: number; name: string | undefined; rooms:
|
|
6
|
+
readonly maps: { id: number; name: string | undefined; rooms: { id: number; iot_name_id: string; tag: number; displayName: string }[] }[] = [];
|
|
8
7
|
|
|
9
8
|
constructor(multimap: MultipleMap) {
|
|
10
9
|
multimap.map_info.forEach((map) => {
|
|
@@ -15,9 +14,11 @@ export class MapInfo {
|
|
|
15
14
|
map.rooms && map.rooms.length > 0
|
|
16
15
|
? map.rooms.map((room: RoomInformation) => {
|
|
17
16
|
return {
|
|
18
|
-
id: room.
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
id: room.id,
|
|
18
|
+
iot_name_id: room.iot_name_id,
|
|
19
|
+
tag: room.tag,
|
|
20
|
+
displayName: room.iot_name,
|
|
21
|
+
};
|
|
21
22
|
})
|
|
22
23
|
: [],
|
|
23
24
|
});
|
|
@@ -25,7 +25,6 @@ export abstract class AbstractClient implements Client {
|
|
|
25
25
|
|
|
26
26
|
protected abstract clientName: string;
|
|
27
27
|
protected abstract shouldReconnect: boolean;
|
|
28
|
-
protected abstract changeToSecureConnection: (duid: string) => void;
|
|
29
28
|
|
|
30
29
|
private readonly context: MessageContext;
|
|
31
30
|
private readonly syncMessageListener: SyncMessageListener;
|
|
@@ -41,7 +40,7 @@ export abstract class AbstractClient implements Client {
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
protected initializeConnectionStateListener() {
|
|
44
|
-
const connectionStateListener = new ConnectionStateListener(this.logger, this, this.clientName, this.
|
|
43
|
+
const connectionStateListener = new ConnectionStateListener(this.logger, this, this.clientName, this.shouldReconnect);
|
|
45
44
|
this.connectionListeners.register(connectionStateListener);
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -9,7 +9,6 @@ import { Sequence } from '../../helper/sequence.js';
|
|
|
9
9
|
import { ChunkBuffer } from '../../helper/chunkBuffer.js';
|
|
10
10
|
|
|
11
11
|
export class LocalNetworkClient extends AbstractClient {
|
|
12
|
-
protected override changeToSecureConnection: (duid: string) => void;
|
|
13
12
|
protected override clientName = 'LocalNetworkClient';
|
|
14
13
|
protected override shouldReconnect = true;
|
|
15
14
|
|
|
@@ -20,29 +19,32 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
20
19
|
duid: string;
|
|
21
20
|
ip: string;
|
|
22
21
|
|
|
23
|
-
constructor(logger: AnsiLogger, context: MessageContext, duid: string, ip: string
|
|
22
|
+
constructor(logger: AnsiLogger, context: MessageContext, duid: string, ip: string) {
|
|
24
23
|
super(logger, context);
|
|
25
24
|
this.duid = duid;
|
|
26
25
|
this.ip = ip;
|
|
27
26
|
this.messageIdSeq = new Sequence(100000, 999999);
|
|
28
27
|
|
|
29
28
|
this.initializeConnectionStateListener();
|
|
30
|
-
this.changeToSecureConnection = inject;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
public connect(): void {
|
|
34
32
|
if (this.socket) {
|
|
35
|
-
|
|
36
|
-
this.socket = undefined;
|
|
37
|
-
return;
|
|
33
|
+
return; // Already connected
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
this.socket = new Socket();
|
|
37
|
+
|
|
38
|
+
// Socket event listeners
|
|
41
39
|
this.socket.on('close', this.onDisconnect.bind(this));
|
|
42
40
|
this.socket.on('end', this.onEnd.bind(this));
|
|
43
41
|
this.socket.on('error', this.onError.bind(this));
|
|
42
|
+
this.socket.on('connect', this.onConnect.bind(this));
|
|
43
|
+
this.socket.on('timeout', this.onTimeout.bind(this));
|
|
44
|
+
|
|
45
|
+
// Data event listener
|
|
44
46
|
this.socket.on('data', this.onMessage.bind(this));
|
|
45
|
-
this.socket.connect(58867, this.ip
|
|
47
|
+
this.socket.connect(58867, this.ip);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
public async disconnect(): Promise<void> {
|
|
@@ -61,7 +63,7 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
61
63
|
|
|
62
64
|
public async send(duid: string, request: RequestMessage): Promise<void> {
|
|
63
65
|
if (!this.socket || !this.connected) {
|
|
64
|
-
this.logger.error(`${duid}: socket is not online, ${debugStringify(request)}`);
|
|
66
|
+
this.logger.error(`${duid}: socket is not online, , ${debugStringify(request)}`);
|
|
65
67
|
return;
|
|
66
68
|
}
|
|
67
69
|
|
|
@@ -73,32 +75,35 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
private async onConnect(): Promise<void> {
|
|
78
|
+
this.logger.debug(`LocalNetworkClient: ${this.duid} connected to ${this.ip}`);
|
|
79
|
+
this.logger.debug(`LocalNetworkClient: ${this.duid} socket writable: ${this.socket?.writable}, readable: ${this.socket?.readable}`);
|
|
76
80
|
this.connected = true;
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
this.retryCount = 0;
|
|
82
|
+
|
|
79
83
|
await this.sendHelloMessage();
|
|
80
84
|
this.pingInterval = setInterval(this.sendPingRequest.bind(this), 5000);
|
|
81
85
|
await this.connectionListeners.onConnected(this.duid);
|
|
82
|
-
this.retryCount = 0;
|
|
83
86
|
}
|
|
84
87
|
|
|
85
88
|
private async onEnd(): Promise<void> {
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
89
|
+
await this.destroySocket('Socket has ended.');
|
|
90
|
+
await this.connectionListeners.onDisconnected(this.duid, 'Socket has ended.');
|
|
91
|
+
}
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
clearInterval(this.pingInterval);
|
|
93
|
+
private async onDisconnect(hadError: boolean): Promise<void> {
|
|
94
|
+
await this.destroySocket(`Socket disconnected. Had error: ${hadError}`);
|
|
95
|
+
|
|
96
|
+
if (!hadError) {
|
|
97
|
+
await this.connectionListeners.onDisconnected(this.duid, 'Socket disconnected. Had no error.');
|
|
95
98
|
}
|
|
99
|
+
}
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
private async onTimeout(): Promise<void> {
|
|
102
|
+
this.logger.error(`LocalNetworkClient: Socket for ${this.duid} timed out.`);
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
private async
|
|
101
|
-
this.logger.
|
|
105
|
+
private async destroySocket(message: string): Promise<void> {
|
|
106
|
+
this.logger.error(`LocalNetworkClient: Destroying socket for ${this.duid} due to: ${message}`);
|
|
102
107
|
this.connected = false;
|
|
103
108
|
|
|
104
109
|
if (this.socket) {
|
|
@@ -108,12 +113,10 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
108
113
|
if (this.pingInterval) {
|
|
109
114
|
clearInterval(this.pingInterval);
|
|
110
115
|
}
|
|
111
|
-
|
|
112
|
-
await this.connectionListeners.onDisconnected(this.duid);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
private async onError(
|
|
116
|
-
this.logger.error('LocalNetworkClient: Socket connection error: ' +
|
|
118
|
+
private async onError(error: Error): Promise<void> {
|
|
119
|
+
this.logger.error('LocalNetworkClient: Socket connection error: ' + error.message);
|
|
117
120
|
this.connected = false;
|
|
118
121
|
|
|
119
122
|
if (this.socket) {
|
|
@@ -121,12 +124,15 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
121
124
|
this.socket = undefined;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
|
|
127
|
+
if (this.pingInterval) {
|
|
128
|
+
clearInterval(this.pingInterval);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await this.connectionListeners.onError(this.duid, error.message);
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
private async onMessage(message: Buffer): Promise<void> {
|
|
128
135
|
if (!this.socket) {
|
|
129
|
-
this.logger.error('unable to receive data if there is no socket available');
|
|
130
136
|
return;
|
|
131
137
|
}
|
|
132
138
|
|