matterbridge-roborock-vacuum-plugin 1.1.0-rc13 → 1.1.0-rc15
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/README.md +1 -1
- package/dist/helper.js +62 -0
- package/dist/model/RoomMap.js +1 -1
- package/dist/platform.js +4 -4
- package/dist/platformRunner.js +3 -66
- package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +25 -25
- package/dist/roborockCommunication/broadcast/client/MQTTClient.js +1 -2
- package/dist/roborockCommunication/broadcast/clientRouter.js +1 -1
- package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +2 -2
- package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +0 -4
- package/dist/roborockCommunication/broadcast/messageProcessor.js +10 -1
- package/dist/roborockCommunication/broadcast/model/protocol.js +9 -0
- package/dist/roborockService.js +20 -1
- package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
- package/matterbridge-roborock-vacuum-plugin.schema.json +1 -1
- package/misc/status.md +119 -0
- package/package.json +1 -1
- package/src/helper.ts +83 -0
- package/src/initialData/getSupportedAreas.ts +1 -1
- package/src/model/RoomMap.ts +1 -1
- package/src/platform.ts +4 -4
- package/src/platformRunner.ts +3 -86
- package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +32 -31
- package/src/roborockCommunication/broadcast/client/MQTTClient.ts +2 -2
- package/src/roborockCommunication/broadcast/clientRouter.ts +1 -1
- package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +2 -3
- package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +4 -4
- package/src/roborockCommunication/broadcast/messageProcessor.ts +14 -7
- package/src/roborockCommunication/broadcast/model/protocol.ts +9 -0
- package/src/roborockService.ts +21 -1
- package/src/rvc.ts +1 -1
- package/src/tests/platformRunner2.test.ts +4 -7
- package/src/tests/roborockCommunication/RESTAPI/roborockAuthenticateApi.test.ts +136 -0
- package/src/tests/roborockCommunication/RESTAPI/roborockIoTApi.test.ts +106 -0
- package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +3 -5
- package/src/tests/roborockCommunication/broadcast/clientRouter.test.ts +168 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +3 -3
- package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +131 -0
- package/src/tests/roborockService.test.ts +102 -2
- package/src/tests/roborockService3.test.ts +133 -0
- package/src/tests/roborockService4.test.ts +76 -0
- package/src/tests/roborockService5.test.ts +79 -0
package/src/helper.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
2
|
+
import { RoomMap } from './model/RoomMap.js';
|
|
3
|
+
import { RoborockMatterbridgePlatform } from './platform.js';
|
|
1
4
|
import { Device } from './roborockCommunication/index.js';
|
|
5
|
+
import { CloudMessageResult } from './roborockCommunication/Zmodel/messageResult.js';
|
|
2
6
|
|
|
3
7
|
export function getVacuumProperty(device: Device, property: string): number | undefined {
|
|
4
8
|
if (device) {
|
|
@@ -20,3 +24,82 @@ export function getVacuumProperty(device: Device, property: string): number | un
|
|
|
20
24
|
export function isSupportedDevice(model: string): boolean {
|
|
21
25
|
return model.startsWith('roborock.vacuum.');
|
|
22
26
|
}
|
|
27
|
+
|
|
28
|
+
export function isStatusUpdate(result: unknown): boolean {
|
|
29
|
+
return (
|
|
30
|
+
Array.isArray(result) &&
|
|
31
|
+
result.length > 0 &&
|
|
32
|
+
typeof result[0] === 'object' &&
|
|
33
|
+
result[0] !== null &&
|
|
34
|
+
'msg_ver' in result[0] &&
|
|
35
|
+
(result[0] as CloudMessageResult).msg_ver !== undefined &&
|
|
36
|
+
(result[0] as CloudMessageResult).msg_ver !== null
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function getRoomMap(duid: string, platform: RoborockMatterbridgePlatform): Promise<RoomMap | undefined> {
|
|
41
|
+
const robot = platform.robots.get(duid);
|
|
42
|
+
if (robot === undefined) {
|
|
43
|
+
platform.log.error(`Error6: Robot with DUID ${duid} not found`);
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (platform.roborockService === undefined) return undefined;
|
|
48
|
+
|
|
49
|
+
const rooms = robot.device.rooms ?? [];
|
|
50
|
+
// if (platform.robot?.device === undefined || platform.roborockService === undefined) return undefined;
|
|
51
|
+
if (robot.roomInfo === undefined) {
|
|
52
|
+
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
53
|
+
if (roomData !== undefined && roomData.length > 0) {
|
|
54
|
+
robot.roomInfo = new RoomMap(roomData ?? [], rooms);
|
|
55
|
+
return robot.roomInfo;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (robot.roomInfo === undefined) {
|
|
60
|
+
const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
|
|
61
|
+
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
62
|
+
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.maps)}`);
|
|
63
|
+
|
|
64
|
+
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
65
|
+
robot.roomInfo = new RoomMap(roomDataMap, rooms);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return robot.roomInfo;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function getRoomMapFromDevice(device: Device, platform: RoborockMatterbridgePlatform): Promise<RoomMap> {
|
|
73
|
+
const rooms = device?.rooms ?? [];
|
|
74
|
+
|
|
75
|
+
platform.log.notice('-------------------------------------------0--------------------------------------------------------');
|
|
76
|
+
platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
|
|
77
|
+
|
|
78
|
+
if (device && platform.roborockService) {
|
|
79
|
+
const roomData = await platform.roborockService.getRoomMappings(device.duid);
|
|
80
|
+
if (roomData !== undefined && roomData.length > 0) {
|
|
81
|
+
platform.log.notice(`getRoomMapFromDevice - roomData: ${debugStringify(roomData ?? [])}`);
|
|
82
|
+
|
|
83
|
+
const roomMap = new RoomMap(roomData ?? [], rooms);
|
|
84
|
+
|
|
85
|
+
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
86
|
+
platform.log.notice('-------------------------------------------1--------------------------------------------------------');
|
|
87
|
+
return roomMap;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const mapInfo = await platform.roborockService.getMapInformation(device.duid);
|
|
91
|
+
platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
|
|
92
|
+
|
|
93
|
+
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
94
|
+
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
95
|
+
|
|
96
|
+
const roomMap = new RoomMap(roomDataMap, rooms);
|
|
97
|
+
|
|
98
|
+
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
99
|
+
platform.log.notice('-------------------------------------------2--------------------------------------------------------');
|
|
100
|
+
return roomMap;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new RoomMap([], rooms);
|
|
105
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AnsiLogger, debugStringify } from 'matterbridge/logger';
|
|
2
2
|
import { ServiceArea } from 'matterbridge/matter/clusters';
|
|
3
|
-
import RoomMap from '../model/RoomMap.js';
|
|
3
|
+
import { RoomMap } from '../model/RoomMap.js';
|
|
4
4
|
import { Room } from '../roborockCommunication/Zmodel/room.js';
|
|
5
5
|
import { randomInt } from 'node:crypto';
|
|
6
6
|
|
package/src/model/RoomMap.ts
CHANGED
package/src/platform.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { AnsiLogger, debugStringify, LogLevel } from 'matterbridge/logger';
|
|
|
4
4
|
import RoborockService from './roborockService.js';
|
|
5
5
|
import { PLUGIN_NAME } from './settings.js';
|
|
6
6
|
import ClientManager from './clientManager.js';
|
|
7
|
-
import { isSupportedDevice } from './helper.js';
|
|
7
|
+
import { getRoomMapFromDevice, isSupportedDevice } from './helper.js';
|
|
8
8
|
import { PlatformRunner } from './platformRunner.js';
|
|
9
9
|
import { RoborockVacuumCleaner } from './rvc.js';
|
|
10
10
|
import { configurateBehavior } from './behaviorFactory.js';
|
|
@@ -33,9 +33,9 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
33
33
|
super(matterbridge, log, config);
|
|
34
34
|
|
|
35
35
|
// Verify that Matterbridge is the correct version
|
|
36
|
-
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.7')) {
|
|
37
37
|
throw new Error(
|
|
38
|
-
`This plugin requires Matterbridge version >= "3.1.
|
|
38
|
+
`This plugin requires Matterbridge version >= "3.1.7". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`,
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
this.log.info('Initializing platform:', this.config.name);
|
|
@@ -231,7 +231,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
231
231
|
vacuum.rooms = rooms.map((room) => ({ id: room.id, name: room.displayName }) as Room);
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
const roomMap = await
|
|
234
|
+
const roomMap = await getRoomMapFromDevice(vacuum, this);
|
|
235
235
|
|
|
236
236
|
this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
|
|
237
237
|
|
package/src/platformRunner.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { RvcRunMode, PowerSource, ServiceArea, RvcOperationalState, RvcCleanMode } from 'matterbridge/matter/clusters';
|
|
2
|
-
import { getVacuumProperty } from './helper.js';
|
|
2
|
+
import { getRoomMap, getVacuumProperty, isStatusUpdate } from './helper.js';
|
|
3
3
|
import { getRunningMode } from './initialData/getSupportedRunModes.js';
|
|
4
4
|
import { CloudMessageModel } from './model/CloudMessageModel.js';
|
|
5
5
|
import { RoborockMatterbridgePlatform } from './platform.js';
|
|
6
6
|
import { state_to_matter_operational_status, state_to_matter_state } from './share/function.js';
|
|
7
|
-
import RoomMap from './model/RoomMap.js';
|
|
8
7
|
import { getBatteryState, getBatteryStatus, getOperationalErrorState } from './initialData/index.js';
|
|
9
8
|
import { NotifyMessageTypes } from './notifyMessageTypes.js';
|
|
10
9
|
import { CloudMessageResult } from './roborockCommunication/Zmodel/messageResult.js';
|
|
@@ -41,76 +40,6 @@ export class PlatformRunner {
|
|
|
41
40
|
await this.updateRobot(NotifyMessageTypes.HomeData, homeData);
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
public async getRoomMapFromDevice(device: Device): Promise<RoomMap> {
|
|
45
|
-
const platform = this.platform;
|
|
46
|
-
const rooms = device?.rooms ?? [];
|
|
47
|
-
|
|
48
|
-
platform.log.notice('-------------------------------------------0--------------------------------------------------------');
|
|
49
|
-
platform.log.notice(`getRoomMapFromDevice: ${debugStringify(rooms)}`);
|
|
50
|
-
|
|
51
|
-
if (device && platform.roborockService) {
|
|
52
|
-
const roomData = await platform.roborockService.getRoomMappings(device.duid);
|
|
53
|
-
if (roomData !== undefined && roomData.length > 0) {
|
|
54
|
-
platform.log.notice(`getRoomMapFromDevice - roomData: ${debugStringify(roomData ?? [])}`);
|
|
55
|
-
|
|
56
|
-
const roomMap = new RoomMap(roomData ?? [], rooms);
|
|
57
|
-
|
|
58
|
-
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
59
|
-
platform.log.notice('-------------------------------------------1--------------------------------------------------------');
|
|
60
|
-
return roomMap;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const mapInfo = await platform.roborockService.getMapInformation(device.duid);
|
|
64
|
-
platform.log.notice(`getRoomMapFromDevice - mapInfo: ${mapInfo ? debugStringify(mapInfo) : 'undefined'}`);
|
|
65
|
-
|
|
66
|
-
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
67
|
-
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
68
|
-
|
|
69
|
-
const roomMap = new RoomMap(roomDataMap, rooms);
|
|
70
|
-
|
|
71
|
-
platform.log.notice(`getRoomMapFromDevice - roomMap: ${debugStringify(roomMap)}`);
|
|
72
|
-
platform.log.notice('-------------------------------------------2--------------------------------------------------------');
|
|
73
|
-
return roomMap;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return new RoomMap([], rooms);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
81
|
-
const platform = this.platform;
|
|
82
|
-
|
|
83
|
-
const robot = platform.robots.get(duid);
|
|
84
|
-
if (robot === undefined) {
|
|
85
|
-
platform.log.error(`Error6: Robot with DUID ${duid} not found`);
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (platform.roborockService === undefined) return undefined;
|
|
90
|
-
|
|
91
|
-
const rooms = robot.device.rooms ?? [];
|
|
92
|
-
// if (platform.robot?.device === undefined || platform.roborockService === undefined) return undefined;
|
|
93
|
-
if (robot.roomInfo === undefined) {
|
|
94
|
-
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
95
|
-
if (roomData !== undefined && roomData.length > 0) {
|
|
96
|
-
robot.roomInfo = new RoomMap(roomData ?? [], rooms);
|
|
97
|
-
return robot.roomInfo;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (robot.roomInfo === undefined) {
|
|
102
|
-
const mapInfo = await platform.roborockService.getMapInformation(robot.device.duid);
|
|
103
|
-
if (mapInfo && mapInfo.maps && mapInfo.maps.length > 0) {
|
|
104
|
-
platform.log.error(`getRoomMap - mapInfo: ${debugStringify(mapInfo.maps)}`);
|
|
105
|
-
|
|
106
|
-
const roomDataMap = mapInfo.maps[0].rooms.map((r) => [r.id, parseInt(r.iot_name_id), r.tag] as [number, number, number]);
|
|
107
|
-
robot.roomInfo = new RoomMap(roomDataMap, rooms);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return robot.roomInfo;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
43
|
private async updateFromMQTTMessage(messageSource: NotifyMessageTypes, messageData: unknown, duid = '', tracked = false): Promise<void> {
|
|
115
44
|
const platform = this.platform;
|
|
116
45
|
duid = duid || (messageData as DeviceStatusNotify)?.duid || '';
|
|
@@ -191,7 +120,7 @@ export class PlatformRunner {
|
|
|
191
120
|
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
192
121
|
} else {
|
|
193
122
|
const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
|
|
194
|
-
const roomMap = await
|
|
123
|
+
const roomMap = await getRoomMap(duid, this.platform);
|
|
195
124
|
|
|
196
125
|
// Get current room from segment_id
|
|
197
126
|
const segment_id = data.cleaning_info?.segment_id ?? -1;
|
|
@@ -311,7 +240,7 @@ export class PlatformRunner {
|
|
|
311
240
|
case Protocol.rpc_response: {
|
|
312
241
|
const response = data.dps[messageType] as DpsPayload;
|
|
313
242
|
// ignore network info
|
|
314
|
-
if (!
|
|
243
|
+
if (!isStatusUpdate(response.result)) {
|
|
315
244
|
platform.log.debug('Ignore message:', debugStringify(data));
|
|
316
245
|
return;
|
|
317
246
|
}
|
|
@@ -398,18 +327,6 @@ export class PlatformRunner {
|
|
|
398
327
|
return undefined;
|
|
399
328
|
}
|
|
400
329
|
|
|
401
|
-
private isStatusUpdate(result: unknown): boolean {
|
|
402
|
-
return (
|
|
403
|
-
Array.isArray(result) &&
|
|
404
|
-
result.length > 0 &&
|
|
405
|
-
typeof result[0] === 'object' &&
|
|
406
|
-
result[0] !== null &&
|
|
407
|
-
'msg_ver' in result[0] &&
|
|
408
|
-
(result[0] as CloudMessageResult).msg_ver !== undefined &&
|
|
409
|
-
(result[0] as CloudMessageResult).msg_ver !== null
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
330
|
private updateFromHomeData(homeData: Home): void {
|
|
414
331
|
const platform = this.platform;
|
|
415
332
|
|
|
@@ -16,6 +16,7 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
16
16
|
private buffer: ChunkBuffer = new ChunkBuffer();
|
|
17
17
|
private messageIdSeq: Sequence;
|
|
18
18
|
private pingInterval?: NodeJS.Timeout;
|
|
19
|
+
private keepConnectionAliveInterval: NodeJS.Timeout | undefined = undefined;
|
|
19
20
|
duid: string;
|
|
20
21
|
ip: string;
|
|
21
22
|
|
|
@@ -45,6 +46,8 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
45
46
|
// Data event listener
|
|
46
47
|
this.socket.on('data', this.onMessage.bind(this));
|
|
47
48
|
this.socket.connect(58867, this.ip);
|
|
49
|
+
|
|
50
|
+
this.keepConnectionAlive();
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
public async disconnect(): Promise<void> {
|
|
@@ -75,8 +78,8 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
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}`);
|
|
81
|
+
this.logger.debug(` [LocalNetworkClient]: ${this.duid} connected to ${this.ip}`);
|
|
82
|
+
this.logger.debug(` [LocalNetworkClient]: ${this.duid} socket writable: ${this.socket?.writable}, readable: ${this.socket?.readable}`);
|
|
80
83
|
this.connected = true;
|
|
81
84
|
this.retryCount = 0;
|
|
82
85
|
|
|
@@ -85,25 +88,8 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
85
88
|
await this.connectionListeners.onConnected(this.duid);
|
|
86
89
|
}
|
|
87
90
|
|
|
88
|
-
private async onEnd(): Promise<void> {
|
|
89
|
-
await this.destroySocket('Socket has ended.');
|
|
90
|
-
await this.connectionListeners.onDisconnected(this.duid, 'Socket has ended.');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
91
|
private async onDisconnect(hadError: boolean): Promise<void> {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (!hadError) {
|
|
97
|
-
await this.connectionListeners.onDisconnected(this.duid, 'Socket disconnected. Had no error.');
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
private async onTimeout(): Promise<void> {
|
|
102
|
-
this.logger.error(`LocalNetworkClient: Socket for ${this.duid} timed out.`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private async destroySocket(message: string): Promise<void> {
|
|
106
|
-
this.logger.error(`LocalNetworkClient: Destroying socket for ${this.duid} due to: ${message}`);
|
|
92
|
+
this.logger.info(` [LocalNetworkClient]: ${this.duid} socket disconnected. Had error: ${hadError}`);
|
|
107
93
|
this.connected = false;
|
|
108
94
|
|
|
109
95
|
if (this.socket) {
|
|
@@ -113,22 +99,20 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
113
99
|
if (this.pingInterval) {
|
|
114
100
|
clearInterval(this.pingInterval);
|
|
115
101
|
}
|
|
102
|
+
await this.connectionListeners.onDisconnected(this.duid, 'Socket disconnected. Had no error.');
|
|
116
103
|
}
|
|
117
104
|
|
|
118
105
|
private async onError(error: Error): Promise<void> {
|
|
119
|
-
this.logger.error(
|
|
120
|
-
this.
|
|
121
|
-
|
|
122
|
-
if (this.socket) {
|
|
123
|
-
this.socket.destroy();
|
|
124
|
-
this.socket = undefined;
|
|
125
|
-
}
|
|
106
|
+
this.logger.error(` [LocalNetworkClient]: Socket error for ${this.duid}: ${error.message}`);
|
|
107
|
+
await this.connectionListeners.onError(this.duid, error.message);
|
|
108
|
+
}
|
|
126
109
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
110
|
+
private async onTimeout(): Promise<void> {
|
|
111
|
+
this.logger.error(` [LocalNetworkClient]: Socket for ${this.duid} timed out.`);
|
|
112
|
+
}
|
|
130
113
|
|
|
131
|
-
|
|
114
|
+
private async onEnd(): Promise<void> {
|
|
115
|
+
this.logger.debug(` [LocalNetworkClient]: ${this.duid} socket ended.`);
|
|
132
116
|
}
|
|
133
117
|
|
|
134
118
|
private async onMessage(message: Buffer): Promise<void> {
|
|
@@ -212,4 +196,21 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
212
196
|
});
|
|
213
197
|
await this.send(this.duid, request);
|
|
214
198
|
}
|
|
199
|
+
|
|
200
|
+
private keepConnectionAlive(): void {
|
|
201
|
+
if (this.keepConnectionAliveInterval) {
|
|
202
|
+
clearTimeout(this.keepConnectionAliveInterval);
|
|
203
|
+
this.keepConnectionAliveInterval.unref();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.keepConnectionAliveInterval = setInterval(
|
|
207
|
+
() => {
|
|
208
|
+
if (this.socket === undefined || !this.connected || !this.socket.writable || this.socket.readable) {
|
|
209
|
+
this.logger.debug(` [LocalNetworkClient]: ${this.duid} socket is not writable or readable, reconnecting...`);
|
|
210
|
+
this.connect();
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
60 * 60 * 1000,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
215
216
|
}
|
|
@@ -36,8 +36,8 @@ export class MQTTClient extends AbstractClient {
|
|
|
36
36
|
username: this.mqttUsername,
|
|
37
37
|
password: this.mqttPassword,
|
|
38
38
|
keepalive: 30,
|
|
39
|
-
log: (
|
|
40
|
-
this.logger.debug(`MQTTClient args: ${debugStringify(args)}`);
|
|
39
|
+
log: () => {
|
|
40
|
+
// ...args: unknown[] this.logger.debug(`MQTTClient args: ${debugStringify(args)}`);
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
43
|
|
|
@@ -15,9 +15,9 @@ export class ClientRouter implements Client {
|
|
|
15
15
|
protected readonly messageListeners = new ChainedMessageListener();
|
|
16
16
|
|
|
17
17
|
private readonly context: MessageContext;
|
|
18
|
-
private readonly mqttClient: MQTTClient;
|
|
19
18
|
private readonly localClients = new Map<string, LocalNetworkClient>();
|
|
20
19
|
private readonly logger: AnsiLogger;
|
|
20
|
+
private mqttClient: MQTTClient;
|
|
21
21
|
|
|
22
22
|
public constructor(logger: AnsiLogger, userdata: UserData) {
|
|
23
23
|
this.context = new MessageContext(userdata);
|
package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts
CHANGED
|
@@ -43,11 +43,10 @@ export class ConnectionStateListener implements AbstractConnectionListener {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
this.logger.info(`Re-registering device with DUID ${duid} to ${this.clientName}`);
|
|
47
|
-
|
|
48
46
|
setTimeout(() => {
|
|
47
|
+
this.logger.info(`Re-registering device with DUID ${duid} to ${this.clientName}`);
|
|
49
48
|
this.client.connect();
|
|
50
|
-
},
|
|
49
|
+
}, 10000);
|
|
51
50
|
|
|
52
51
|
this.client.isInDisconnectingStep = false;
|
|
53
52
|
}
|
|
@@ -28,10 +28,10 @@ export class SyncMessageListener implements AbstractMessageListener {
|
|
|
28
28
|
const messageId = dps.id;
|
|
29
29
|
|
|
30
30
|
const responseHandler = this.pending.get(messageId);
|
|
31
|
-
const result = dps.result as Record<string, unknown>;
|
|
32
|
-
if (result && result.length == 1 && result[0] == 'ok') {
|
|
33
|
-
|
|
34
|
-
}
|
|
31
|
+
// const result = dps.result as Record<string, unknown>;
|
|
32
|
+
// if (result && result.length == 1 && result[0] == 'ok') {
|
|
33
|
+
// return;
|
|
34
|
+
// }
|
|
35
35
|
|
|
36
36
|
if (responseHandler) {
|
|
37
37
|
responseHandler(dps.result as ResponseMessage);
|
|
@@ -34,11 +34,6 @@ export class MessageProcessor {
|
|
|
34
34
|
return await this.client.get(duid, request);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// public async getDeviceStatus(duid: string): Promise<DeviceStatus> {
|
|
38
|
-
// const request = new RequestMessage({ method: 'get_status' });
|
|
39
|
-
// return this.client.get<CloudMessageResult[]>(duid, request).then((response) => new DeviceStatus(response[0]));
|
|
40
|
-
// }
|
|
41
|
-
|
|
42
37
|
public async getDeviceStatus(duid: string): Promise<DeviceStatus | undefined> {
|
|
43
38
|
const request = new RequestMessage({ method: 'get_status' });
|
|
44
39
|
const response = await this.client.get<CloudMessageResult[]>(duid, request);
|
|
@@ -51,6 +46,18 @@ export class MessageProcessor {
|
|
|
51
46
|
return undefined;
|
|
52
47
|
}
|
|
53
48
|
|
|
49
|
+
public async getDeviceStatusOverMQTT(duid: string): Promise<DeviceStatus | undefined> {
|
|
50
|
+
const request = new RequestMessage({ method: 'get_status', secure: true });
|
|
51
|
+
const response = await this.client.get<CloudMessageResult[]>(duid, request);
|
|
52
|
+
|
|
53
|
+
if (response) {
|
|
54
|
+
this.logger?.debug('MQTT - Device status: ', debugStringify(response));
|
|
55
|
+
return new DeviceStatus(response);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
public async getRooms(duid: string, rooms: Room[]): Promise<RoomInfo> {
|
|
55
62
|
const request = new RequestMessage({ method: 'get_room_mapping' });
|
|
56
63
|
return this.client.get<number[][] | undefined>(duid, request).then((response) => new RoomInfo(rooms, response ?? []));
|
|
@@ -98,9 +105,9 @@ export class MessageProcessor {
|
|
|
98
105
|
return this.client.get(duid, def);
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
public async findMyRobot(duid: string): Promise<
|
|
108
|
+
public async findMyRobot(duid: string): Promise<unknown> {
|
|
102
109
|
const request = new RequestMessage({ method: 'find_me' });
|
|
103
|
-
return this.client.
|
|
110
|
+
return this.client.get(duid, request);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
public async getCleanModeData(duid: string): Promise<{ suctionPower: number; waterFlow: number; distance_off: number; mopRoute: number }> {
|
|
@@ -12,8 +12,17 @@ export enum Protocol {
|
|
|
12
12
|
battery = 122,
|
|
13
13
|
suction_power = 123,
|
|
14
14
|
water_box_mode = 124,
|
|
15
|
+
main_brush_work_time = 125,
|
|
16
|
+
side_brush_work_time = 126,
|
|
17
|
+
filter_work_time = 127,
|
|
15
18
|
additional_props = 128,
|
|
19
|
+
task_complete = 130,
|
|
20
|
+
task_cancel_low_power = 131,
|
|
21
|
+
task_cancel_in_motion = 132,
|
|
22
|
+
charge_status = 133,
|
|
23
|
+
drying_status = 134,
|
|
16
24
|
back_type = 139, // WTF is this
|
|
17
25
|
map_response = 301,
|
|
26
|
+
some_thing_happened_when_socket_closed = 500,
|
|
18
27
|
}
|
|
19
28
|
// "deviceStatus":{"120":0,"121":8,"122":100,"123":110,"124":209,"125":99,"126":96,"127":97,"128":0,"133":1,"134":1,"135":0,"139":0}
|
package/src/roborockService.ts
CHANGED
|
@@ -274,7 +274,7 @@ export default class RoborockService {
|
|
|
274
274
|
await messageProcessor.getDeviceStatus(device.duid).then((response: DeviceStatus | undefined) => {
|
|
275
275
|
if (self.deviceNotify && response) {
|
|
276
276
|
const message = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
|
|
277
|
-
self.logger.debug('Device status update', debugStringify(message));
|
|
277
|
+
self.logger.debug('Socket - Device status update', debugStringify(message));
|
|
278
278
|
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
279
279
|
}
|
|
280
280
|
});
|
|
@@ -284,6 +284,26 @@ export default class RoborockService {
|
|
|
284
284
|
}, this.refreshInterval * 1000);
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
public activateDeviceNotifyOverMQTT(device: Device): void {
|
|
288
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
289
|
+
const self = this;
|
|
290
|
+
this.logger.notice('Requesting device info for device over MQTT', device.duid);
|
|
291
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
292
|
+
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
293
|
+
if (messageProcessor) {
|
|
294
|
+
await messageProcessor.getDeviceStatusOverMQTT(device.duid).then((response: DeviceStatus | undefined) => {
|
|
295
|
+
if (self.deviceNotify && response) {
|
|
296
|
+
const message = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
|
|
297
|
+
self.logger.debug('MQTT - Device status update', debugStringify(message));
|
|
298
|
+
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
self.logger.error('Local client not initialized');
|
|
303
|
+
}
|
|
304
|
+
}, this.refreshInterval * 500);
|
|
305
|
+
}
|
|
306
|
+
|
|
287
307
|
public async listDevices(username: string): Promise<Device[]> {
|
|
288
308
|
assert(this.iotApi !== undefined);
|
|
289
309
|
assert(this.userdata !== undefined);
|
package/src/rvc.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RoboticVacuumCleaner } from 'matterbridge/devices';
|
|
2
|
-
import RoomMap from './model/RoomMap.js';
|
|
2
|
+
import { RoomMap } from './model/RoomMap.js';
|
|
3
3
|
import { Device } from './roborockCommunication/index.js';
|
|
4
4
|
import { getOperationalStates, getSupportedAreas, getSupportedCleanModes, getSupportedRunModes } from './initialData/index.js';
|
|
5
5
|
import { AnsiLogger } from 'matterbridge/logger';
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { PlatformRunner } from '../platformRunner';
|
|
1
|
+
import { getRoomMapFromDevice } from '../helper';
|
|
2
|
+
import { RoomMap } from '../model/RoomMap';
|
|
4
3
|
import { MapInfo } from '../roborockCommunication';
|
|
5
4
|
|
|
6
5
|
describe('PlatformRunner.getRoomMapFromDevice', () => {
|
|
7
6
|
let platform: any;
|
|
8
|
-
let runner: PlatformRunner;
|
|
9
7
|
|
|
10
8
|
beforeEach(() => {
|
|
11
9
|
platform = {
|
|
@@ -19,7 +17,6 @@ describe('PlatformRunner.getRoomMapFromDevice', () => {
|
|
|
19
17
|
getMapInformation: jest.fn(),
|
|
20
18
|
},
|
|
21
19
|
};
|
|
22
|
-
runner = new PlatformRunner(platform as RoborockMatterbridgePlatform);
|
|
23
20
|
});
|
|
24
21
|
|
|
25
22
|
it('returns RoomMap with roomData from getRoomMappings if available', async () => {
|
|
@@ -42,7 +39,7 @@ describe('PlatformRunner.getRoomMapFromDevice', () => {
|
|
|
42
39
|
platform.roborockService.getRoomMappings.mockResolvedValue(roomData);
|
|
43
40
|
platform.roborockService.getMapInformation.mockResolvedValue(undefined);
|
|
44
41
|
|
|
45
|
-
const result = await
|
|
42
|
+
const result = await getRoomMapFromDevice(device as any, platform);
|
|
46
43
|
|
|
47
44
|
expect(result).toBeInstanceOf(RoomMap);
|
|
48
45
|
expect(result.rooms.length).toEqual(4);
|
|
@@ -83,7 +80,7 @@ describe('PlatformRunner.getRoomMapFromDevice', () => {
|
|
|
83
80
|
platform.roborockService.getRoomMappings.mockResolvedValue(undefined);
|
|
84
81
|
platform.roborockService.getMapInformation.mockResolvedValue(mapInfo);
|
|
85
82
|
|
|
86
|
-
const result = await
|
|
83
|
+
const result = await getRoomMapFromDevice(device as any, platform);
|
|
87
84
|
|
|
88
85
|
expect(result).toBeInstanceOf(RoomMap);
|
|
89
86
|
expect(result.rooms.length).toEqual(4);
|