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.
Files changed (37) hide show
  1. package/dist/initialData/getSupportedAreas.js +7 -6
  2. package/dist/model/RoomMap.js +1 -0
  3. package/dist/platform.js +2 -2
  4. package/dist/platformRunner.js +51 -23
  5. package/dist/roborockCommunication/Zmodel/mapInfo.js +3 -1
  6. package/dist/roborockCommunication/broadcast/abstractClient.js +1 -1
  7. package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +25 -25
  8. package/dist/roborockCommunication/broadcast/client/LocalNetworkUDPClient.js +129 -0
  9. package/dist/roborockCommunication/broadcast/client/MQTTClient.js +53 -25
  10. package/dist/roborockCommunication/broadcast/clientRouter.js +2 -2
  11. package/dist/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.js +7 -2
  12. package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +9 -8
  13. package/dist/roborockCommunication/helper/messageDeserializer.js +1 -1
  14. package/dist/roborockService.js +5 -5
  15. package/matterbridge-roborock-vacuum-plugin.config.json +2 -2
  16. package/matterbridge-roborock-vacuum-plugin.schema.json +10 -37
  17. package/package.json +2 -1
  18. package/src/initialData/getSupportedAreas.ts +8 -6
  19. package/src/model/RoomMap.ts +2 -0
  20. package/src/platform.ts +3 -2
  21. package/src/platformRunner.ts +68 -25
  22. package/src/roborockCommunication/Zmodel/map.ts +1 -0
  23. package/src/roborockCommunication/Zmodel/mapInfo.ts +5 -4
  24. package/src/roborockCommunication/broadcast/abstractClient.ts +1 -2
  25. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +34 -28
  26. package/src/roborockCommunication/broadcast/client/LocalNetworkUDPClient.ts +157 -0
  27. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +69 -36
  28. package/src/roborockCommunication/broadcast/clientRouter.ts +2 -2
  29. package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +2 -1
  30. package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +8 -2
  31. package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +11 -8
  32. package/src/roborockCommunication/helper/messageDeserializer.ts +2 -3
  33. package/src/roborockService.ts +6 -6
  34. package/src/tests/initialData/getSupportedAreas.test.ts +27 -0
  35. package/src/tests/platformRunner2.test.ts +91 -0
  36. package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +1 -7
  37. package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +13 -106
@@ -3,27 +3,26 @@ export class ConnectionStateListener {
3
3
  client;
4
4
  clientName;
5
5
  shouldReconnect;
6
- changeToSecureConnection;
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 onDisconnected(duid) {
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
- this.client.connect();
35
+ setTimeout(() => {
36
+ this.client.connect();
37
+ }, 3000);
37
38
  this.client.isInDisconnectingStep = false;
38
39
  }
39
40
  async onError(duid, message) {
@@ -13,7 +13,7 @@ export class MessageDeserializer {
13
13
  this.context = context;
14
14
  this.logger = logger;
15
15
  this.messageParser = new Parser()
16
- .endianess('big')
16
+ .endianness('big')
17
17
  .string('version', {
18
18
  length: 3,
19
19
  })
@@ -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, this.onLocalClientDisconnect);
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(200);
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-rc12",
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-rc12 by https://github.com/RinDevJunior",
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-rc12",
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(rooms: Room[], roomMap: RoomMap | undefined, log?: AnsiLogger): ServiceArea.Area[] {
8
- log?.debug('getSupportedAreas', debugStringify(rooms));
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 (!rooms || rooms.length === 0 || !roomMap?.rooms || roomMap.rooms.length == 0) {
12
- if (!rooms || rooms.length === 0) {
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 ?? rooms.find((r) => r.id == room.globalId)?.name ?? `Unknown Room ${randomInt(1000, 9999)}`,
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
@@ -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.error(`Fetching map information for device: ${vacuum.name} (${vacuum.duid}) to get rooms`);
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);
@@ -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
- platform.log.error(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
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.error(`getRoomMapFromDevice - roomData: ${debugStringify(roomData ?? [])}`);
52
- return new RoomMap(roomData ?? [], rooms);
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
- platform.log.error(`getRoomMapFromDevice - mapInfo: ${debugStringify(mapInfo.maps)}`);
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
- const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.name)] as [number, number]);
60
- return new RoomMap(roomDataMap, rooms);
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.name)] as [number, number]);
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
- const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
172
- const roomMap = await this.getRoomMap(duid);
173
- const targetRoom = data.cleaning_info?.target_segment_id ?? -1;
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
- const currentRoom = data.cleaning_info?.segment_id ?? -1;
183
- const isMappedArea = currentMappedAreas?.some((x) => x.areaId == currentRoom);
184
- if (currentRoom !== -1 && isMappedArea) {
185
- this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
186
- this.platform.log.debug(`CurrentRoom: ${currentRoom}, room name: ${roomMap?.rooms.find((x) => x.id === currentRoom)?.displayName ?? 'unknown'}`);
187
- robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
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.debug(`currentCleanModeSetting: ${debugStringify(currentCleanModeSetting)}`);
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] ?? messageType} ,`, debugStringify(data));
360
+ platform.log.notice(`Unknown message type ${messageType}, protocol: ${Protocol[messageType]}, message: ${debugStringify(data)}`);
318
361
  break;
319
362
  }
320
363
  }
@@ -4,6 +4,7 @@ export interface Map {
4
4
  length: number;
5
5
  name: string;
6
6
  rooms: RoomInformation[];
7
+ bak_maps: { mapFlag: number; add_time: number }[];
7
8
  }
8
9
 
9
10
  export interface RoomInformation {
@@ -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: Room[] }[] = [];
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
- name: room.iot_name_id,
20
- } as Room;
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.changeToSecureConnection, this.shouldReconnect);
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, inject: (duid: string) => void) {
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
- this.socket.destroy();
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, this.onConnect.bind(this));
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
- const address = this.socket?.address();
78
- this.logger.debug(`${this.duid} connected to ${this.ip}, address: ${address ? debugStringify(address) : 'undefined'}`);
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.logger.notice('LocalNetworkClient: Socket has ended.');
87
- this.connected = false;
89
+ await this.destroySocket('Socket has ended.');
90
+ await this.connectionListeners.onDisconnected(this.duid, 'Socket has ended.');
91
+ }
88
92
 
89
- if (this.socket) {
90
- this.socket.destroy();
91
- this.socket = undefined;
92
- }
93
- if (this.pingInterval) {
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
- await this.connectionListeners.onDisconnected(this.duid);
101
+ private async onTimeout(): Promise<void> {
102
+ this.logger.error(`LocalNetworkClient: Socket for ${this.duid} timed out.`);
98
103
  }
99
104
 
100
- private async onDisconnect(): Promise<void> {
101
- this.logger.notice('LocalNetworkClient: Socket has disconnected.');
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(result: Error): Promise<void> {
116
- this.logger.error('LocalNetworkClient: Socket connection error: ' + result);
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
- await this.connectionListeners.onError(this.duid, result.toString());
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