matterbridge-roborock-vacuum-plugin 1.1.0-rc12 → 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 +7 -6
- package/dist/model/RoomMap.js +1 -0
- package/dist/platform.js +2 -2
- package/dist/platformRunner.js +51 -23
- package/dist/roborockCommunication/Zmodel/mapInfo.js +3 -1
- 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 -8
- package/dist/roborockCommunication/helper/messageDeserializer.js +1 -1
- package/dist/roborockService.js +5 -5
- package/matterbridge-roborock-vacuum-plugin.config.json +2 -2
- package/matterbridge-roborock-vacuum-plugin.schema.json +10 -37
- package/package.json +2 -1
- package/src/initialData/getSupportedAreas.ts +8 -6
- package/src/model/RoomMap.ts +2 -0
- package/src/platform.ts +3 -2
- package/src/platformRunner.ts +68 -25
- package/src/roborockCommunication/Zmodel/map.ts +1 -0
- package/src/roborockCommunication/Zmodel/mapInfo.ts +5 -4
- 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 -8
- package/src/roborockCommunication/helper/messageDeserializer.ts +2 -3
- package/src/roborockService.ts +6 -6
- 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/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js
CHANGED
|
@@ -3,27 +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
|
-
if (this.changeToSecureConnection) {
|
|
25
|
-
this.changeToSecureConnection(duid);
|
|
26
|
-
}
|
|
27
26
|
return;
|
|
28
27
|
}
|
|
29
28
|
this.client.retryCount++;
|
|
@@ -33,7 +32,9 @@ export class ConnectionStateListener {
|
|
|
33
32
|
return;
|
|
34
33
|
}
|
|
35
34
|
this.logger.info(`Re-registering device with DUID ${duid} to ${this.clientName}`);
|
|
36
|
-
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
this.client.connect();
|
|
37
|
+
}, 3000);
|
|
37
38
|
this.client.isInDisconnectingStep = false;
|
|
38
39
|
}
|
|
39
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
|
}
|
|
@@ -410,13 +413,13 @@ export default class RoborockService {
|
|
|
410
413
|
}
|
|
411
414
|
if (localIp) {
|
|
412
415
|
this.logger.debug('initializing the local connection for this client towards ' + localIp);
|
|
413
|
-
const localClient = this.messageClient.registerClient(device.duid, localIp
|
|
416
|
+
const localClient = this.messageClient.registerClient(device.duid, localIp);
|
|
414
417
|
localClient.connect();
|
|
415
418
|
let count = 0;
|
|
416
419
|
while (!localClient.isConnected() && count < 20) {
|
|
417
420
|
this.logger.debug('Keep waiting for local client to connect');
|
|
418
421
|
count++;
|
|
419
|
-
await this.sleep(
|
|
422
|
+
await this.sleep(500);
|
|
420
423
|
}
|
|
421
424
|
if (!localClient.isConnected()) {
|
|
422
425
|
throw new Error('Local client did not connect after 10 attempts, something is wrong');
|
|
@@ -432,9 +435,6 @@ export default class RoborockService {
|
|
|
432
435
|
}
|
|
433
436
|
return true;
|
|
434
437
|
}
|
|
435
|
-
onLocalClientDisconnect(duid) {
|
|
436
|
-
this.mqttAlwaysOnDevices.set(duid, true);
|
|
437
|
-
}
|
|
438
438
|
sleep(ms) {
|
|
439
439
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
440
440
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-roborock-vacuum-plugin",
|
|
3
3
|
"type": "DynamicPlatform",
|
|
4
|
-
"version": "1.1.0-
|
|
4
|
+
"version": "1.1.0-rc13",
|
|
5
5
|
"whiteList": [],
|
|
6
6
|
"blackList": [],
|
|
7
7
|
"useInterval": true,
|
|
@@ -37,4 +37,4 @@
|
|
|
37
37
|
"debug": true,
|
|
38
38
|
"unregisterOnShutdown": false,
|
|
39
39
|
"enableExperimentalFeature": false
|
|
40
|
-
}
|
|
40
|
+
}
|
|
@@ -1,11 +1,8 @@
|
|
|
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
|
-
"required": [
|
|
6
|
-
"username",
|
|
7
|
-
"password"
|
|
8
|
-
],
|
|
5
|
+
"required": ["username", "password"],
|
|
9
6
|
"properties": {
|
|
10
7
|
"name": {
|
|
11
8
|
"description": "Plugin name",
|
|
@@ -60,9 +57,7 @@
|
|
|
60
57
|
"const": true
|
|
61
58
|
}
|
|
62
59
|
},
|
|
63
|
-
"required": [
|
|
64
|
-
"enableExperimentalFeature"
|
|
65
|
-
]
|
|
60
|
+
"required": ["enableExperimentalFeature"]
|
|
66
61
|
},
|
|
67
62
|
"then": {
|
|
68
63
|
"properties": {
|
|
@@ -119,9 +114,7 @@
|
|
|
119
114
|
"const": true
|
|
120
115
|
}
|
|
121
116
|
},
|
|
122
|
-
"required": [
|
|
123
|
-
"enableCleanModeMapping"
|
|
124
|
-
]
|
|
117
|
+
"required": ["enableCleanModeMapping"]
|
|
125
118
|
},
|
|
126
119
|
"then": {
|
|
127
120
|
"properties": {
|
|
@@ -166,9 +159,7 @@
|
|
|
166
159
|
"default": 25
|
|
167
160
|
}
|
|
168
161
|
},
|
|
169
|
-
"required": [
|
|
170
|
-
"distanceOff"
|
|
171
|
-
]
|
|
162
|
+
"required": ["distanceOff"]
|
|
172
163
|
}
|
|
173
164
|
}
|
|
174
165
|
]
|
|
@@ -205,9 +196,7 @@
|
|
|
205
196
|
"default": 25
|
|
206
197
|
}
|
|
207
198
|
},
|
|
208
|
-
"required": [
|
|
209
|
-
"distanceOff"
|
|
210
|
-
]
|
|
199
|
+
"required": ["distanceOff"]
|
|
211
200
|
}
|
|
212
201
|
}
|
|
213
202
|
]
|
|
@@ -237,36 +226,20 @@
|
|
|
237
226
|
"fanMode": {
|
|
238
227
|
"type": "string",
|
|
239
228
|
"description": "Suction power mode to use (e.g., 'Quiet', 'Balanced', 'Turbo', 'Max', 'MaxPlus').",
|
|
240
|
-
"enum": [
|
|
241
|
-
"Quiet",
|
|
242
|
-
"Balanced",
|
|
243
|
-
"Turbo",
|
|
244
|
-
"Max",
|
|
245
|
-
"MaxPlus"
|
|
246
|
-
],
|
|
229
|
+
"enum": ["Quiet", "Balanced", "Turbo", "Max", "MaxPlus"],
|
|
247
230
|
"default": "Balanced"
|
|
248
231
|
},
|
|
249
232
|
"waterFlowMode": {
|
|
250
233
|
"type": "string",
|
|
251
234
|
"description": "Water flow mode to use (e.g., 'Low', 'Medium', 'High', 'CustomizeWithDistanceOff').",
|
|
252
|
-
"enum": [
|
|
253
|
-
"Low",
|
|
254
|
-
"Medium",
|
|
255
|
-
"High",
|
|
256
|
-
"CustomizeWithDistanceOff"
|
|
257
|
-
],
|
|
235
|
+
"enum": ["Low", "Medium", "High", "CustomizeWithDistanceOff"],
|
|
258
236
|
"default": "Medium"
|
|
259
237
|
},
|
|
260
238
|
"mopRouteMode": {
|
|
261
239
|
"type": "string",
|
|
262
240
|
"description": "Mop route intensity to use (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
|
|
263
|
-
"enum": [
|
|
264
|
-
"Standard",
|
|
265
|
-
"Deep",
|
|
266
|
-
"DeepPlus",
|
|
267
|
-
"Fast"
|
|
268
|
-
],
|
|
241
|
+
"enum": ["Standard", "Deep", "DeepPlus", "Fast"],
|
|
269
242
|
"default": "Standard"
|
|
270
243
|
}
|
|
271
244
|
}
|
|
272
|
-
}
|
|
245
|
+
}
|
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,12 @@ 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
10
|
|
|
11
|
-
if (!
|
|
12
|
-
if (!
|
|
11
|
+
if (!vacuumRooms || vacuumRooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
|
|
12
|
+
if (!vacuumRooms || vacuumRooms.length === 0) {
|
|
13
13
|
log?.error('No rooms found');
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -39,7 +39,7 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
|
|
|
39
39
|
mapId: null,
|
|
40
40
|
areaInfo: {
|
|
41
41
|
locationInfo: {
|
|
42
|
-
locationName: room.displayName ??
|
|
42
|
+
locationName: room.displayName ?? vacuumRooms.find((r) => r.id == room.globalId || r.id == room.id)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`,
|
|
43
43
|
floorNumber: null,
|
|
44
44
|
areaType: null,
|
|
45
45
|
},
|
|
@@ -48,6 +48,8 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
|
|
|
48
48
|
};
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
+
log?.debug('getSupportedAreas - supportedAreas', debugStringify(supportedAreas));
|
|
52
|
+
|
|
51
53
|
const duplicated = findDuplicatedAreaIds(supportedAreas, log);
|
|
52
54
|
|
|
53
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>();
|
|
@@ -224,10 +225,10 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
224
225
|
}
|
|
225
226
|
|
|
226
227
|
if (vacuum.rooms === undefined || vacuum.rooms.length === 0) {
|
|
227
|
-
this.log.
|
|
228
|
+
this.log.notice(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
|
|
228
229
|
const map_info = await this.roborockService.getMapInformation(vacuum.duid);
|
|
229
230
|
const rooms = map_info?.maps?.[0]?.rooms ?? [];
|
|
230
|
-
vacuum.rooms = rooms;
|
|
231
|
+
vacuum.rooms = rooms.map((room) => ({ id: room.id, name: room.displayName }) as Room);
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
|
package/src/platformRunner.ts
CHANGED
|
@@ -44,27 +44,40 @@ export class PlatformRunner {
|
|
|
44
44
|
public async getRoomMapFromDevice(device: Device): Promise<RoomMap> {
|
|
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) {
|
|
51
|
-
platform.log.
|
|
52
|
-
|
|
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;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const mapInfo = await platform.roborockService.getMapInformation(device.duid);
|
|
64
|
+
platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
|
|
65
|
+
|
|
56
66
|
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
57
|
-
|
|
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);
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
72
|
+
platform.log.notice('-------------------------------------------2--------------------------------------------------------');
|
|
73
|
+
return roomMap;
|
|
61
74
|
}
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
return new RoomMap([], rooms);
|
|
65
78
|
}
|
|
66
79
|
|
|
67
|
-
async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
80
|
+
private async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
68
81
|
const platform = this.platform;
|
|
69
82
|
|
|
70
83
|
const robot = platform.robots.get(duid);
|
|
@@ -90,7 +103,7 @@ export class PlatformRunner {
|
|
|
90
103
|
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
91
104
|
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.maps)}`);
|
|
92
105
|
|
|
93
|
-
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.
|
|
106
|
+
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
94
107
|
robot.roomInfo = new RoomMap(roomDataMap, rooms);
|
|
95
108
|
}
|
|
96
109
|
}
|
|
@@ -168,23 +181,51 @@ export class PlatformRunner {
|
|
|
168
181
|
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
|
|
169
182
|
}
|
|
170
183
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const isTargetMappedArea = currentMappedAreas?.some((x) => x.areaId == targetRoom);
|
|
175
|
-
|
|
176
|
-
if (targetRoom !== -1 && isTargetMappedArea) {
|
|
177
|
-
this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
|
|
178
|
-
this.platform.log.debug(`TargetRoom: ${targetRoom}, room name: ${roomMap?.rooms.find((x) => x.id === targetRoom)?.displayName ?? 'unknown'}`);
|
|
179
|
-
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', targetRoom, platform.log);
|
|
184
|
+
if (data.state === OperationStatusCode.Idle) {
|
|
185
|
+
const selectedAreas = platform.roborockService?.getSelectedAreas(duid) ?? [];
|
|
186
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', selectedAreas, platform.log);
|
|
180
187
|
}
|
|
181
188
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
this.platform.
|
|
187
|
-
|
|
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);
|
|
194
|
+
const roomMap = await this.getRoomMap(duid);
|
|
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
|
+
}
|
|
188
229
|
}
|
|
189
230
|
|
|
190
231
|
if (data.battery) {
|
|
@@ -199,10 +240,12 @@ export class PlatformRunner {
|
|
|
199
240
|
waterFlow: data.cleaning_info?.water_box_status ?? data.water_box_mode,
|
|
200
241
|
distance_off: data.distance_off,
|
|
201
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,
|
|
202
245
|
};
|
|
203
246
|
|
|
204
247
|
this.platform.log.debug(`data: ${debugStringify(data)}`);
|
|
205
|
-
this.platform.log.
|
|
248
|
+
this.platform.log.notice(`currentCleanModeSetting: ${debugStringify(currentCleanModeSetting)}`);
|
|
206
249
|
|
|
207
250
|
if (currentCleanModeSetting.mopRoute && currentCleanModeSetting.suctionPower && currentCleanModeSetting.waterFlow) {
|
|
208
251
|
const currentCleanMode = getCurrentCleanModeFunc(
|
|
@@ -314,7 +357,7 @@ export class PlatformRunner {
|
|
|
314
357
|
break;
|
|
315
358
|
}
|
|
316
359
|
default: {
|
|
317
|
-
platform.log.notice(`Unknown message type: ${Protocol[messageType]
|
|
360
|
+
platform.log.notice(`Unknown message type ${messageType}, protocol: ${Protocol[messageType]}, message: ${debugStringify(data)}`);
|
|
318
361
|
break;
|
|
319
362
|
}
|
|
320
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) => {
|
|
@@ -16,8 +15,10 @@ export class MapInfo {
|
|
|
16
15
|
? map.rooms.map((room: RoomInformation) => {
|
|
17
16
|
return {
|
|
18
17
|
id: room.id,
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|