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.
Files changed (39) hide show
  1. package/dist/initialData/getSupportedAreas.js +12 -6
  2. package/dist/model/RoomMap.js +1 -0
  3. package/dist/platform.js +4 -3
  4. package/dist/platformRunner.js +67 -12
  5. package/dist/roborockCommunication/Zmodel/mapInfo.js +4 -2
  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 -6
  13. package/dist/roborockCommunication/helper/messageDeserializer.js +1 -1
  14. package/dist/roborockService.js +5 -7
  15. package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
  16. package/matterbridge-roborock-vacuum-plugin.schema.json +1 -1
  17. package/package.json +2 -1
  18. package/src/initialData/getSupportedAreas.ts +15 -6
  19. package/src/model/RoomMap.ts +2 -0
  20. package/src/platform.ts +5 -3
  21. package/src/platformRunner.ts +88 -13
  22. package/src/roborockCommunication/Zmodel/device.ts +0 -1
  23. package/src/roborockCommunication/Zmodel/map.ts +1 -0
  24. package/src/roborockCommunication/Zmodel/mapInfo.ts +6 -5
  25. package/src/roborockCommunication/broadcast/abstractClient.ts +1 -2
  26. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +34 -28
  27. package/src/roborockCommunication/broadcast/client/LocalNetworkUDPClient.ts +157 -0
  28. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +69 -36
  29. package/src/roborockCommunication/broadcast/clientRouter.ts +2 -2
  30. package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +2 -1
  31. package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +8 -2
  32. package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +11 -6
  33. package/src/roborockCommunication/helper/messageDeserializer.ts +2 -3
  34. package/src/roborockService.ts +6 -9
  35. package/src/tests/initialData/getSupportedAreas.test.ts +27 -0
  36. package/src/tests/platformRunner2.test.ts +91 -0
  37. package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +1 -7
  38. package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +13 -106
  39. package/web-for-testing/src/accountStore.ts +2 -0
@@ -3,25 +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
- 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
- this.client.connect();
35
+ setTimeout(() => {
36
+ this.client.connect();
37
+ }, 3000);
35
38
  this.client.isInDisconnectingStep = false;
36
39
  }
37
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
  }
@@ -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, this.onLocalClientDisconnect);
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(200);
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,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.1.0-rc11",
4
+ "version": "1.1.0-rc13",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc11 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
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-rc11",
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(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
+
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 ?? 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)}`,
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
@@ -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.5')) {
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.5". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`,
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);
@@ -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
- return new RoomMap(roomData ?? [], rooms);
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
- robot.roomInfo = new RoomMap(roomData ?? [], rooms);
71
- return robot.roomInfo;
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
- const currentRoom = data.cleaning_info?.segment_id ?? -1;
148
- const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
149
- const isMappedArea = currentMappedAreas?.some((x) => x.areaId == currentRoom);
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 (currentRoom !== -1 && isMappedArea) {
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
- this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
154
- this.platform.log.debug('CurrentRoom:', currentRoom);
155
- robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
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.debug(`currentCleanModeSetting: ${debugStringify(currentCleanModeSetting)}`);
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] ?? messageType} ,`, debugStringify(data));
360
+ platform.log.notice(`Unknown message type ${messageType}, protocol: ${Protocol[messageType]}, message: ${debugStringify(data)}`);
286
361
  break;
287
362
  }
288
363
  }
@@ -36,7 +36,6 @@ export interface Device {
36
36
  model: string;
37
37
  category: string;
38
38
  batteryLevel: number;
39
- // schema: DeviceSchema[];
40
39
  };
41
40
 
42
41
  scenes: Scene[];
@@ -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) => {
@@ -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.iot_name_id,
19
- name: room.iot_name,
20
- } as unknown as Room;
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.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