matterbridge-roborock-vacuum-plugin 1.1.0-rc03 → 1.1.0-rc05
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/.github/workflows/build.yml +5 -0
- package/.github/workflows/coverage.yml +5 -0
- package/dist/platform.js +1 -1
- package/dist/roborockService.js +53 -39
- package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
- package/matterbridge-roborock-vacuum-plugin.schema.json +1 -1
- package/package.json +1 -1
- package/src/platform.ts +2 -1
- package/src/roborockService.ts +54 -39
- package/src/tests/roborockService.test.ts +250 -1
- package/src/tests/roborockService2.test.ts +69 -0
|
@@ -13,6 +13,11 @@ jobs:
|
|
|
13
13
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
14
14
|
|
|
15
15
|
steps:
|
|
16
|
+
- name: Cancel Previous Runs
|
|
17
|
+
uses: styfle/cancel-workflow-action@0.12.0
|
|
18
|
+
with:
|
|
19
|
+
access_token: ${{ github.token }}
|
|
20
|
+
|
|
16
21
|
- name: Checkout code
|
|
17
22
|
uses: actions/checkout@v4
|
|
18
23
|
|
package/dist/platform.js
CHANGED
|
@@ -82,7 +82,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
82
82
|
this.log.error('Initializing: No device found');
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
-
if (!this.enableExperimentalFeature || !this.enableExperimentalFeature
|
|
85
|
+
if (!this.enableExperimentalFeature?.enableExperimentalFeature || !this.enableExperimentalFeature?.advancedFeature?.enableServerMode) {
|
|
86
86
|
vacuums = [vacuums[0]];
|
|
87
87
|
}
|
|
88
88
|
for (const vacuum of vacuums) {
|
package/dist/roborockService.js
CHANGED
|
@@ -12,9 +12,9 @@ export default class RoborockService {
|
|
|
12
12
|
deviceNotify;
|
|
13
13
|
messageClient;
|
|
14
14
|
remoteDevices = new Set();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
messageProcessorMap = new Map();
|
|
16
|
+
ipMap = new Map();
|
|
17
|
+
localClientMap = new Map();
|
|
18
18
|
clientManager;
|
|
19
19
|
refreshInterval;
|
|
20
20
|
requestDeviceStatusInterval;
|
|
@@ -32,11 +32,12 @@ export default class RoborockService {
|
|
|
32
32
|
const userdata = await this.loginApi.loginWithPassword(username, password);
|
|
33
33
|
return this.auth(userdata);
|
|
34
34
|
}
|
|
35
|
-
getMessageProcessor() {
|
|
36
|
-
|
|
35
|
+
getMessageProcessor(duid) {
|
|
36
|
+
const messageProcessor = this.messageProcessorMap.get(duid);
|
|
37
|
+
if (!messageProcessor) {
|
|
37
38
|
this.logger.error('MessageApi is not initialized.');
|
|
38
39
|
}
|
|
39
|
-
return
|
|
40
|
+
return messageProcessor;
|
|
40
41
|
}
|
|
41
42
|
setSelectedAreas(duid, selectedAreas) {
|
|
42
43
|
this.logger.debug('RoborockService - setSelectedAreas', selectedAreas);
|
|
@@ -53,7 +54,7 @@ export default class RoborockService {
|
|
|
53
54
|
}
|
|
54
55
|
async getCleanModeData(duid) {
|
|
55
56
|
this.logger.notice('RoborockService - getCleanModeData');
|
|
56
|
-
const data = await this.
|
|
57
|
+
const data = await this.getMessageProcessor(duid)?.getCleanModeData(duid);
|
|
57
58
|
if (!data) {
|
|
58
59
|
throw new Error('Failed to retrieve clean mode data');
|
|
59
60
|
}
|
|
@@ -61,7 +62,7 @@ export default class RoborockService {
|
|
|
61
62
|
}
|
|
62
63
|
async changeCleanMode(duid, { suctionPower, waterFlow, distance_off, mopRoute }) {
|
|
63
64
|
this.logger.notice('RoborockService - changeCleanMode');
|
|
64
|
-
return this.
|
|
65
|
+
return this.getMessageProcessor(duid)?.changeCleanMode(duid, suctionPower, waterFlow, mopRoute, distance_off);
|
|
65
66
|
}
|
|
66
67
|
async startClean(duid) {
|
|
67
68
|
const supportedRooms = this.supportedAreas.get(duid) ?? [];
|
|
@@ -71,11 +72,11 @@ export default class RoborockService {
|
|
|
71
72
|
if (supportedRoutines.length === 0) {
|
|
72
73
|
if (selected.length == supportedRooms.length || selected.length === 0 || supportedRooms.length === 0) {
|
|
73
74
|
this.logger.debug('RoborockService - startGlobalClean');
|
|
74
|
-
this.getMessageProcessor()?.startClean(duid);
|
|
75
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
75
76
|
}
|
|
76
77
|
else {
|
|
77
78
|
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, selected }));
|
|
78
|
-
return this.
|
|
79
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, selected, 1);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
else {
|
|
@@ -91,11 +92,11 @@ export default class RoborockService {
|
|
|
91
92
|
}
|
|
92
93
|
else if (rooms.length == supportedRooms.length || rooms.length === 0 || supportedRooms.length === 0) {
|
|
93
94
|
this.logger.debug('RoborockService - startGlobalClean');
|
|
94
|
-
this.getMessageProcessor()?.startClean(duid);
|
|
95
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
95
96
|
}
|
|
96
97
|
else if (rooms.length > 0) {
|
|
97
98
|
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, rooms }));
|
|
98
|
-
return this.
|
|
99
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, rooms, 1);
|
|
99
100
|
}
|
|
100
101
|
else {
|
|
101
102
|
this.logger.warn('RoborockService - something goes wrong.', debugStringify({ duid, rooms, rt, selected, supportedRooms, supportedRoutines }));
|
|
@@ -105,42 +106,50 @@ export default class RoborockService {
|
|
|
105
106
|
}
|
|
106
107
|
async pauseClean(duid) {
|
|
107
108
|
this.logger.debug('RoborockService - pauseClean');
|
|
108
|
-
await this.getMessageProcessor()?.pauseClean(duid);
|
|
109
|
+
await this.getMessageProcessor(duid)?.pauseClean(duid);
|
|
109
110
|
}
|
|
110
111
|
async stopAndGoHome(duid) {
|
|
111
112
|
this.logger.debug('RoborockService - stopAndGoHome');
|
|
112
|
-
await this.getMessageProcessor()?.gotoDock(duid);
|
|
113
|
+
await this.getMessageProcessor(duid)?.gotoDock(duid);
|
|
113
114
|
}
|
|
114
115
|
async resumeClean(duid) {
|
|
115
116
|
this.logger.debug('RoborockService - resumeClean');
|
|
116
|
-
await this.getMessageProcessor()?.resumeClean(duid);
|
|
117
|
+
await this.getMessageProcessor(duid)?.resumeClean(duid);
|
|
117
118
|
}
|
|
118
119
|
async playSoundToLocate(duid) {
|
|
119
120
|
this.logger.debug('RoborockService - findMe');
|
|
120
|
-
await this.getMessageProcessor()?.findMyRobot(duid);
|
|
121
|
+
await this.getMessageProcessor(duid)?.findMyRobot(duid);
|
|
121
122
|
}
|
|
122
123
|
async customGet(duid, method) {
|
|
123
124
|
this.logger.debug('RoborockService - customSend-message', method);
|
|
124
|
-
return this.getMessageProcessor()?.getCustomMessage(duid, new RequestMessage({ method }));
|
|
125
|
+
return this.getMessageProcessor(duid)?.getCustomMessage(duid, new RequestMessage({ method }));
|
|
125
126
|
}
|
|
126
127
|
async customGetInSecure(duid, method) {
|
|
127
128
|
this.logger.debug('RoborockService - customGetInSecure-message', method);
|
|
128
|
-
return this.getMessageProcessor()?.getCustomMessage(duid, new RequestMessage({ method, secure: true }));
|
|
129
|
+
return this.getMessageProcessor(duid)?.getCustomMessage(duid, new RequestMessage({ method, secure: true }));
|
|
129
130
|
}
|
|
130
131
|
async customSend(duid, request) {
|
|
131
|
-
return this.getMessageProcessor()?.sendCustomMessage(duid, request);
|
|
132
|
+
return this.getMessageProcessor(duid)?.sendCustomMessage(duid, request);
|
|
132
133
|
}
|
|
133
134
|
stopService() {
|
|
134
135
|
if (this.messageClient) {
|
|
135
136
|
this.messageClient.disconnect();
|
|
136
137
|
this.messageClient = undefined;
|
|
137
138
|
}
|
|
138
|
-
if (this.
|
|
139
|
-
this.
|
|
140
|
-
|
|
139
|
+
if (this.localClientMap.size > 0) {
|
|
140
|
+
for (const [duid, client] of this.localClientMap.entries()) {
|
|
141
|
+
this.logger.debug('Disconnecting local client for device', duid);
|
|
142
|
+
client.disconnect();
|
|
143
|
+
this.localClientMap.delete(duid);
|
|
144
|
+
this.logger.debug('Local client disconnected for device', duid);
|
|
145
|
+
}
|
|
141
146
|
}
|
|
142
|
-
if (this.
|
|
143
|
-
this.
|
|
147
|
+
if (this.messageProcessorMap.size > 0) {
|
|
148
|
+
for (const [duid] of this.messageProcessorMap.entries()) {
|
|
149
|
+
this.logger.debug('Disconnecting message processor for device', duid);
|
|
150
|
+
this.messageProcessorMap.delete(duid);
|
|
151
|
+
this.logger.debug('Message processor disconnected for device', duid);
|
|
152
|
+
}
|
|
144
153
|
}
|
|
145
154
|
if (this.requestDeviceStatusInterval) {
|
|
146
155
|
clearInterval(this.requestDeviceStatusInterval);
|
|
@@ -153,9 +162,10 @@ export default class RoborockService {
|
|
|
153
162
|
async activateDeviceNotify(device) {
|
|
154
163
|
const self = this;
|
|
155
164
|
this.logger.debug('Requesting device info for device', device.duid);
|
|
165
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
156
166
|
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
157
|
-
if (
|
|
158
|
-
await
|
|
167
|
+
if (messageProcessor) {
|
|
168
|
+
await messageProcessor.getDeviceStatus(device.duid).then((response) => {
|
|
159
169
|
if (self.deviceNotify) {
|
|
160
170
|
const message = { duid: device.duid, ...response.errorStatus, ...response.message };
|
|
161
171
|
self.logger.debug('Device status update', debugStringify(message));
|
|
@@ -307,9 +317,9 @@ export default class RoborockService {
|
|
|
307
317
|
return;
|
|
308
318
|
}
|
|
309
319
|
const self = this;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
320
|
+
const messageProcessor = new MessageProcessor(this.messageClient);
|
|
321
|
+
messageProcessor.injectLogger(this.logger);
|
|
322
|
+
messageProcessor.registerListener({
|
|
313
323
|
onError: (message) => {
|
|
314
324
|
if (self.deviceNotify) {
|
|
315
325
|
self.deviceNotify(NotifyMessageTypes.ErrorOccurred, { duid: device.duid, errorCode: message });
|
|
@@ -323,26 +333,30 @@ export default class RoborockService {
|
|
|
323
333
|
onStatusChanged: () => {
|
|
324
334
|
},
|
|
325
335
|
});
|
|
336
|
+
this.messageProcessorMap.set(device.duid, messageProcessor);
|
|
326
337
|
this.logger.debug('Local device', device.duid);
|
|
338
|
+
let localIp = this.ipMap.get(device.duid);
|
|
327
339
|
try {
|
|
328
|
-
if (!
|
|
340
|
+
if (!localIp) {
|
|
329
341
|
this.logger.debug('Requesting network info for device', device.duid);
|
|
330
|
-
const networkInfo = await
|
|
331
|
-
|
|
342
|
+
const networkInfo = await messageProcessor.getNetworkInfo(device.duid);
|
|
343
|
+
localIp = networkInfo.ip;
|
|
332
344
|
}
|
|
333
|
-
if (
|
|
334
|
-
this.logger.debug('initializing the local connection for this client towards ' +
|
|
335
|
-
|
|
336
|
-
|
|
345
|
+
if (localIp) {
|
|
346
|
+
this.logger.debug('initializing the local connection for this client towards ' + localIp);
|
|
347
|
+
const localClient = this.messageClient.registerClient(device.duid, localIp);
|
|
348
|
+
localClient.connect();
|
|
337
349
|
let count = 0;
|
|
338
|
-
while (!
|
|
350
|
+
while (!localClient.isConnected() && count < 20) {
|
|
339
351
|
this.logger.debug('Keep waiting for local client to connect');
|
|
340
352
|
count++;
|
|
341
|
-
await this.sleep(
|
|
353
|
+
await this.sleep(200);
|
|
342
354
|
}
|
|
343
|
-
if (!
|
|
355
|
+
if (!localClient.isConnected()) {
|
|
344
356
|
throw new Error('Local client did not connect after 10 attempts, something is wrong');
|
|
345
357
|
}
|
|
358
|
+
this.ipMap.set(device.duid, localIp);
|
|
359
|
+
this.localClientMap.set(device.duid, localClient);
|
|
346
360
|
this.logger.debug('LocalClient connected');
|
|
347
361
|
}
|
|
348
362
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "Matterbridge Roborock Vacuum Plugin",
|
|
3
|
-
"description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-
|
|
3
|
+
"description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc05 by https://github.com/RinDevJunior",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"required": [
|
|
6
6
|
"username",
|
package/package.json
CHANGED
package/src/platform.ts
CHANGED
|
@@ -111,7 +111,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
if (!this.enableExperimentalFeature || !this.enableExperimentalFeature
|
|
114
|
+
if (!this.enableExperimentalFeature?.enableExperimentalFeature || !this.enableExperimentalFeature?.advancedFeature?.enableServerMode) {
|
|
115
115
|
vacuums = [vacuums[0]]; // If server mode is not enabled, only use the first vacuum
|
|
116
116
|
}
|
|
117
117
|
// else {
|
|
@@ -179,6 +179,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
179
179
|
this.log.info('onConfigurateDevice finished');
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
// Running in loop to configurate devices
|
|
182
183
|
private async configurateDevice(vacuum: Device) {
|
|
183
184
|
const username = this.config.username as string;
|
|
184
185
|
|
package/src/roborockService.ts
CHANGED
|
@@ -42,9 +42,9 @@ export default class RoborockService {
|
|
|
42
42
|
deviceNotify?: (messageSource: NotifyMessageTypes, homeData: unknown) => void;
|
|
43
43
|
messageClient: ClientRouter | undefined;
|
|
44
44
|
remoteDevices = new Set<string>();
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
messageProcessorMap = new Map<string, MessageProcessor>();
|
|
46
|
+
ipMap = new Map<string, string>();
|
|
47
|
+
localClientMap = new Map<string, Client>();
|
|
48
48
|
clientManager: ClientManager;
|
|
49
49
|
refreshInterval: number;
|
|
50
50
|
requestDeviceStatusInterval: NodeJS.Timeout | undefined;
|
|
@@ -73,11 +73,12 @@ export default class RoborockService {
|
|
|
73
73
|
return this.auth(userdata);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
public getMessageProcessor(): MessageProcessor | undefined {
|
|
77
|
-
|
|
76
|
+
public getMessageProcessor(duid: string): MessageProcessor | undefined {
|
|
77
|
+
const messageProcessor = this.messageProcessorMap.get(duid);
|
|
78
|
+
if (!messageProcessor) {
|
|
78
79
|
this.logger.error('MessageApi is not initialized.');
|
|
79
80
|
}
|
|
80
|
-
return
|
|
81
|
+
return messageProcessor;
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
public setSelectedAreas(duid: string, selectedAreas: number[]): void {
|
|
@@ -99,7 +100,7 @@ export default class RoborockService {
|
|
|
99
100
|
|
|
100
101
|
public async getCleanModeData(duid: string): Promise<{ suctionPower: number; waterFlow: number; distance_off: number; mopRoute: number }> {
|
|
101
102
|
this.logger.notice('RoborockService - getCleanModeData');
|
|
102
|
-
const data = await this.
|
|
103
|
+
const data = await this.getMessageProcessor(duid)?.getCleanModeData(duid);
|
|
103
104
|
if (!data) {
|
|
104
105
|
throw new Error('Failed to retrieve clean mode data');
|
|
105
106
|
}
|
|
@@ -111,7 +112,7 @@ export default class RoborockService {
|
|
|
111
112
|
{ suctionPower, waterFlow, distance_off, mopRoute }: { suctionPower: number; waterFlow: number; distance_off: number; mopRoute: number },
|
|
112
113
|
): Promise<void> {
|
|
113
114
|
this.logger.notice('RoborockService - changeCleanMode');
|
|
114
|
-
return this.
|
|
115
|
+
return this.getMessageProcessor(duid)?.changeCleanMode(duid, suctionPower, waterFlow, mopRoute, distance_off);
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
public async startClean(duid: string): Promise<void> {
|
|
@@ -123,10 +124,10 @@ export default class RoborockService {
|
|
|
123
124
|
if (supportedRoutines.length === 0) {
|
|
124
125
|
if (selected.length == supportedRooms.length || selected.length === 0 || supportedRooms.length === 0) {
|
|
125
126
|
this.logger.debug('RoborockService - startGlobalClean');
|
|
126
|
-
this.getMessageProcessor()?.startClean(duid);
|
|
127
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
127
128
|
} else {
|
|
128
129
|
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, selected }));
|
|
129
|
-
return this.
|
|
130
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, selected, 1);
|
|
130
131
|
}
|
|
131
132
|
} else {
|
|
132
133
|
const rooms = selected.filter((slt) => supportedRooms.some((a: ServiceArea.Area) => a.areaId == slt));
|
|
@@ -146,13 +147,13 @@ export default class RoborockService {
|
|
|
146
147
|
* If no rooms are selected, or all selected rooms match the supported rooms,
|
|
147
148
|
*/
|
|
148
149
|
this.logger.debug('RoborockService - startGlobalClean');
|
|
149
|
-
this.getMessageProcessor()?.startClean(duid);
|
|
150
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
150
151
|
} else if (rooms.length > 0) {
|
|
151
152
|
/**
|
|
152
153
|
* If there are rooms selected
|
|
153
154
|
*/
|
|
154
155
|
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, rooms }));
|
|
155
|
-
return this.
|
|
156
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, rooms, 1);
|
|
156
157
|
} else {
|
|
157
158
|
this.logger.warn('RoborockService - something goes wrong.', debugStringify({ duid, rooms, rt, selected, supportedRooms, supportedRoutines }));
|
|
158
159
|
return;
|
|
@@ -162,36 +163,36 @@ export default class RoborockService {
|
|
|
162
163
|
|
|
163
164
|
public async pauseClean(duid: string): Promise<void> {
|
|
164
165
|
this.logger.debug('RoborockService - pauseClean');
|
|
165
|
-
await this.getMessageProcessor()?.pauseClean(duid);
|
|
166
|
+
await this.getMessageProcessor(duid)?.pauseClean(duid);
|
|
166
167
|
}
|
|
167
168
|
|
|
168
169
|
public async stopAndGoHome(duid: string): Promise<void> {
|
|
169
170
|
this.logger.debug('RoborockService - stopAndGoHome');
|
|
170
|
-
await this.getMessageProcessor()?.gotoDock(duid);
|
|
171
|
+
await this.getMessageProcessor(duid)?.gotoDock(duid);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
public async resumeClean(duid: string): Promise<void> {
|
|
174
175
|
this.logger.debug('RoborockService - resumeClean');
|
|
175
|
-
await this.getMessageProcessor()?.resumeClean(duid);
|
|
176
|
+
await this.getMessageProcessor(duid)?.resumeClean(duid);
|
|
176
177
|
}
|
|
177
178
|
|
|
178
179
|
public async playSoundToLocate(duid: string): Promise<void> {
|
|
179
180
|
this.logger.debug('RoborockService - findMe');
|
|
180
|
-
await this.getMessageProcessor()?.findMyRobot(duid);
|
|
181
|
+
await this.getMessageProcessor(duid)?.findMyRobot(duid);
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
public async customGet(duid: string, method: string): Promise<unknown> {
|
|
184
185
|
this.logger.debug('RoborockService - customSend-message', method);
|
|
185
|
-
return this.getMessageProcessor()?.getCustomMessage(duid, new RequestMessage({ method }));
|
|
186
|
+
return this.getMessageProcessor(duid)?.getCustomMessage(duid, new RequestMessage({ method }));
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
public async customGetInSecure(duid: string, method: string): Promise<unknown> {
|
|
189
190
|
this.logger.debug('RoborockService - customGetInSecure-message', method);
|
|
190
|
-
return this.getMessageProcessor()?.getCustomMessage(duid, new RequestMessage({ method, secure: true }));
|
|
191
|
+
return this.getMessageProcessor(duid)?.getCustomMessage(duid, new RequestMessage({ method, secure: true }));
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
public async customSend(duid: string, request: RequestMessage): Promise<void> {
|
|
194
|
-
return this.getMessageProcessor()?.sendCustomMessage(duid, request);
|
|
195
|
+
return this.getMessageProcessor(duid)?.sendCustomMessage(duid, request);
|
|
195
196
|
}
|
|
196
197
|
|
|
197
198
|
public stopService(): void {
|
|
@@ -200,13 +201,21 @@ export default class RoborockService {
|
|
|
200
201
|
this.messageClient = undefined;
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
if (this.
|
|
204
|
-
this.
|
|
205
|
-
|
|
204
|
+
if (this.localClientMap.size > 0) {
|
|
205
|
+
for (const [duid, client] of this.localClientMap.entries()) {
|
|
206
|
+
this.logger.debug('Disconnecting local client for device', duid);
|
|
207
|
+
client.disconnect();
|
|
208
|
+
this.localClientMap.delete(duid);
|
|
209
|
+
this.logger.debug('Local client disconnected for device', duid);
|
|
210
|
+
}
|
|
206
211
|
}
|
|
207
212
|
|
|
208
|
-
if (this.
|
|
209
|
-
this.
|
|
213
|
+
if (this.messageProcessorMap.size > 0) {
|
|
214
|
+
for (const [duid] of this.messageProcessorMap.entries()) {
|
|
215
|
+
this.logger.debug('Disconnecting message processor for device', duid);
|
|
216
|
+
this.messageProcessorMap.delete(duid);
|
|
217
|
+
this.logger.debug('Message processor disconnected for device', duid);
|
|
218
|
+
}
|
|
210
219
|
}
|
|
211
220
|
|
|
212
221
|
if (this.requestDeviceStatusInterval) {
|
|
@@ -223,9 +232,10 @@ export default class RoborockService {
|
|
|
223
232
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
224
233
|
const self = this;
|
|
225
234
|
this.logger.debug('Requesting device info for device', device.duid);
|
|
235
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
226
236
|
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
227
|
-
if (
|
|
228
|
-
await
|
|
237
|
+
if (messageProcessor) {
|
|
238
|
+
await messageProcessor.getDeviceStatus(device.duid).then((response: DeviceStatus) => {
|
|
229
239
|
if (self.deviceNotify) {
|
|
230
240
|
const message = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
|
|
231
241
|
self.logger.debug('Device status update', debugStringify(message));
|
|
@@ -407,9 +417,9 @@ export default class RoborockService {
|
|
|
407
417
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
408
418
|
const self = this;
|
|
409
419
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
420
|
+
const messageProcessor = new MessageProcessor(this.messageClient);
|
|
421
|
+
messageProcessor.injectLogger(this.logger);
|
|
422
|
+
messageProcessor.registerListener({
|
|
413
423
|
onError: (message: VacuumErrorCode) => {
|
|
414
424
|
if (self.deviceNotify) {
|
|
415
425
|
self.deviceNotify(NotifyMessageTypes.ErrorOccurred, { duid: device.duid, errorCode: message } as DeviceErrorMessage);
|
|
@@ -430,30 +440,35 @@ export default class RoborockService {
|
|
|
430
440
|
},
|
|
431
441
|
} as AbstractMessageHandler);
|
|
432
442
|
|
|
443
|
+
this.messageProcessorMap.set(device.duid, messageProcessor);
|
|
444
|
+
|
|
433
445
|
this.logger.debug('Local device', device.duid);
|
|
446
|
+
let localIp = this.ipMap.get(device.duid);
|
|
434
447
|
try {
|
|
435
|
-
if (!
|
|
448
|
+
if (!localIp) {
|
|
436
449
|
this.logger.debug('Requesting network info for device', device.duid);
|
|
437
|
-
const networkInfo = await
|
|
438
|
-
|
|
450
|
+
const networkInfo = await messageProcessor.getNetworkInfo(device.duid);
|
|
451
|
+
localIp = networkInfo.ip;
|
|
439
452
|
}
|
|
440
453
|
|
|
441
|
-
if (
|
|
442
|
-
this.logger.debug('initializing the local connection for this client towards ' +
|
|
443
|
-
|
|
444
|
-
|
|
454
|
+
if (localIp) {
|
|
455
|
+
this.logger.debug('initializing the local connection for this client towards ' + localIp);
|
|
456
|
+
const localClient = this.messageClient.registerClient(device.duid, localIp) as LocalNetworkClient;
|
|
457
|
+
localClient.connect();
|
|
445
458
|
|
|
446
459
|
let count = 0;
|
|
447
|
-
while (!
|
|
460
|
+
while (!localClient.isConnected() && count < 20) {
|
|
448
461
|
this.logger.debug('Keep waiting for local client to connect');
|
|
449
462
|
count++;
|
|
450
|
-
await this.sleep(
|
|
463
|
+
await this.sleep(200);
|
|
451
464
|
}
|
|
452
465
|
|
|
453
|
-
if (!
|
|
466
|
+
if (!localClient.isConnected()) {
|
|
454
467
|
throw new Error('Local client did not connect after 10 attempts, something is wrong');
|
|
455
468
|
}
|
|
456
469
|
|
|
470
|
+
this.ipMap.set(device.duid, localIp);
|
|
471
|
+
this.localClientMap.set(device.duid, localClient);
|
|
457
472
|
this.logger.debug('LocalClient connected');
|
|
458
473
|
}
|
|
459
474
|
} catch (error) {
|
|
@@ -2,6 +2,7 @@ import { AnsiLogger } from 'matterbridge/logger';
|
|
|
2
2
|
import { ServiceArea } from 'matterbridge/matter/clusters';
|
|
3
3
|
import RoborockService from '../roborockService';
|
|
4
4
|
import { MessageProcessor } from '../roborockCommunication/broadcast/messageProcessor';
|
|
5
|
+
import { Device } from '../roborockCommunication';
|
|
5
6
|
|
|
6
7
|
describe('RoborockService - startClean', () => {
|
|
7
8
|
let roborockService: RoborockService;
|
|
@@ -22,7 +23,7 @@ describe('RoborockService - startClean', () => {
|
|
|
22
23
|
} as any;
|
|
23
24
|
|
|
24
25
|
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
25
|
-
roborockService['
|
|
26
|
+
roborockService['messageProcessorMap'] = new Map<string, MessageProcessor>([['test-duid', mockMessageProcessor]]);
|
|
26
27
|
});
|
|
27
28
|
|
|
28
29
|
it('should start global clean when no areas or selected areas are provided', async () => {
|
|
@@ -152,4 +153,252 @@ describe('RoborockService - startClean', () => {
|
|
|
152
153
|
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - startRoomClean', expect.anything());
|
|
153
154
|
expect(mockMessageProcessor.startRoomClean).toHaveBeenCalledWith(duid, [1, 3], 1);
|
|
154
155
|
});
|
|
156
|
+
|
|
157
|
+
it('should initialize and store MessageProcessor for the given duid', () => {
|
|
158
|
+
const duid = 'test-duid';
|
|
159
|
+
roborockService.initializeMessageClientForLocal({ duid } as Device);
|
|
160
|
+
const storedProcessor = roborockService['messageProcessorMap'].get(duid);
|
|
161
|
+
expect(storedProcessor).not.toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('RoborockService - basic setters/getters', () => {
|
|
166
|
+
let roborockService: RoborockService;
|
|
167
|
+
let mockLogger: AnsiLogger;
|
|
168
|
+
|
|
169
|
+
beforeEach(() => {
|
|
170
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
171
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('setSelectedAreas should set selected areas', () => {
|
|
175
|
+
roborockService.setSelectedAreas('duid', [1, 2]);
|
|
176
|
+
expect(roborockService['selectedAreas'].get('duid')).toEqual([1, 2]);
|
|
177
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - setSelectedAreas', [1, 2]);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('setSupportedAreas should set supported areas', () => {
|
|
181
|
+
const areas = [{ areaId: 1, mapId: null, areaInfo: {} as any }];
|
|
182
|
+
roborockService.setSupportedAreas('duid', areas);
|
|
183
|
+
expect(roborockService['supportedAreas'].get('duid')).toEqual(areas);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('setSupportedScenes should set supported routines', () => {
|
|
187
|
+
const routines = [{ areaId: 99, mapId: null, areaInfo: {} as any }];
|
|
188
|
+
roborockService.setSupportedScenes('duid', routines);
|
|
189
|
+
expect(roborockService['supportedRoutines'].get('duid')).toEqual(routines);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('getSupportedAreas should return supported areas', () => {
|
|
193
|
+
const areas = [{ areaId: 1, mapId: null, areaInfo: {} as any }];
|
|
194
|
+
roborockService['supportedAreas'].set('duid', areas);
|
|
195
|
+
expect(roborockService.getSupportedAreas('duid')).toEqual(areas);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('RoborockService - getMessageProcessor', () => {
|
|
200
|
+
let roborockService: RoborockService;
|
|
201
|
+
let mockLogger: AnsiLogger;
|
|
202
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
203
|
+
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
206
|
+
mockMessageProcessor = { startClean: jest.fn() } as any;
|
|
207
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should return processor if present', () => {
|
|
211
|
+
roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
|
|
212
|
+
expect(roborockService.getMessageProcessor('duid')).toBe(mockMessageProcessor);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should log error if processor not present', () => {
|
|
216
|
+
expect(roborockService.getMessageProcessor('unknown')).toBeUndefined();
|
|
217
|
+
expect(mockLogger.error).toHaveBeenCalledWith('MessageApi is not initialized.');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('RoborockService - getCleanModeData', () => {
|
|
222
|
+
let roborockService: RoborockService;
|
|
223
|
+
let mockLogger: AnsiLogger;
|
|
224
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
225
|
+
|
|
226
|
+
beforeEach(() => {
|
|
227
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
228
|
+
mockMessageProcessor = { getCleanModeData: jest.fn() } as any;
|
|
229
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
230
|
+
roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should return clean mode data', async () => {
|
|
234
|
+
mockMessageProcessor.getCleanModeData.mockResolvedValue({ suctionPower: 1, waterFlow: 2, distance_off: 3, mopRoute: 4 });
|
|
235
|
+
const result = await roborockService.getCleanModeData('duid');
|
|
236
|
+
expect(result).toEqual({ suctionPower: 1, waterFlow: 2, distance_off: 3, mopRoute: 4 });
|
|
237
|
+
expect(mockLogger.notice).toHaveBeenCalledWith('RoborockService - getCleanModeData');
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe('RoborockService - changeCleanMode', () => {
|
|
242
|
+
let roborockService: RoborockService;
|
|
243
|
+
let mockLogger: AnsiLogger;
|
|
244
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
245
|
+
|
|
246
|
+
beforeEach(() => {
|
|
247
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
248
|
+
mockMessageProcessor = { changeCleanMode: jest.fn() } as any;
|
|
249
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
250
|
+
roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should call changeCleanMode on processor', async () => {
|
|
254
|
+
await roborockService.changeCleanMode('duid', { suctionPower: 1, waterFlow: 2, distance_off: 3, mopRoute: 4 });
|
|
255
|
+
expect(mockLogger.notice).toHaveBeenCalledWith('RoborockService - changeCleanMode');
|
|
256
|
+
expect(mockMessageProcessor.changeCleanMode).toHaveBeenCalledWith('duid', 1, 2, 4, 3);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
describe('RoborockService - pause/stop/resume/playSound', () => {
|
|
261
|
+
let roborockService: RoborockService;
|
|
262
|
+
let mockLogger: AnsiLogger;
|
|
263
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
264
|
+
|
|
265
|
+
beforeEach(() => {
|
|
266
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
267
|
+
mockMessageProcessor = {
|
|
268
|
+
pauseClean: jest.fn(),
|
|
269
|
+
gotoDock: jest.fn(),
|
|
270
|
+
resumeClean: jest.fn(),
|
|
271
|
+
findMyRobot: jest.fn(),
|
|
272
|
+
} as any;
|
|
273
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
274
|
+
roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('pauseClean should call processor and log', async () => {
|
|
278
|
+
await roborockService.pauseClean('duid');
|
|
279
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - pauseClean');
|
|
280
|
+
expect(mockMessageProcessor.pauseClean).toHaveBeenCalledWith('duid');
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('stopAndGoHome should call processor and log', async () => {
|
|
284
|
+
await roborockService.stopAndGoHome('duid');
|
|
285
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - stopAndGoHome');
|
|
286
|
+
expect(mockMessageProcessor.gotoDock).toHaveBeenCalledWith('duid');
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('resumeClean should call processor and log', async () => {
|
|
290
|
+
await roborockService.resumeClean('duid');
|
|
291
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - resumeClean');
|
|
292
|
+
expect(mockMessageProcessor.resumeClean).toHaveBeenCalledWith('duid');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('playSoundToLocate should call processor and log', async () => {
|
|
296
|
+
await roborockService.playSoundToLocate('duid');
|
|
297
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - findMe');
|
|
298
|
+
expect(mockMessageProcessor.findMyRobot).toHaveBeenCalledWith('duid');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('RoborockService - customGet/customGetInSecure/customSend', () => {
|
|
303
|
+
let roborockService: RoborockService;
|
|
304
|
+
let mockLogger: AnsiLogger;
|
|
305
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
306
|
+
|
|
307
|
+
beforeEach(() => {
|
|
308
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
309
|
+
mockMessageProcessor = {
|
|
310
|
+
getCustomMessage: jest.fn(),
|
|
311
|
+
sendCustomMessage: jest.fn(),
|
|
312
|
+
} as any;
|
|
313
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
314
|
+
roborockService['messageProcessorMap'].set('duid', mockMessageProcessor);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('customGet should call getCustomMessage', async () => {
|
|
318
|
+
mockMessageProcessor.getCustomMessage.mockResolvedValue('result');
|
|
319
|
+
const result = await roborockService.customGet('duid', 'method');
|
|
320
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - customSend-message', 'method');
|
|
321
|
+
expect(mockMessageProcessor.getCustomMessage).toHaveBeenCalledWith('duid', expect.any(Object));
|
|
322
|
+
expect(result).toBe('result');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('customGetInSecure should call getCustomMessage with secure', async () => {
|
|
326
|
+
mockMessageProcessor.getCustomMessage.mockResolvedValue('secureResult');
|
|
327
|
+
const result = await roborockService.customGetInSecure('duid', 'method');
|
|
328
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('RoborockService - customGetInSecure-message', 'method');
|
|
329
|
+
expect(mockMessageProcessor.getCustomMessage).toHaveBeenCalledWith('duid', expect.objectContaining({ secure: true }));
|
|
330
|
+
expect(result).toBe('secureResult');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('customSend should call sendCustomMessage', async () => {
|
|
334
|
+
const req = { foo: 'bar' } as any;
|
|
335
|
+
await roborockService.customSend('duid', req);
|
|
336
|
+
expect(mockMessageProcessor.sendCustomMessage).toHaveBeenCalledWith('duid', req);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('RoborockService - stopService', () => {
|
|
341
|
+
let roborockService: RoborockService;
|
|
342
|
+
let mockLogger: AnsiLogger;
|
|
343
|
+
let mockMessageClient: any;
|
|
344
|
+
let mockLocalClient: any;
|
|
345
|
+
let mockMessageProcessor: any;
|
|
346
|
+
|
|
347
|
+
beforeEach(() => {
|
|
348
|
+
mockLogger = { debug: jest.fn(), notice: jest.fn(), error: jest.fn(), warn: jest.fn() } as any;
|
|
349
|
+
mockMessageClient = { disconnect: jest.fn() };
|
|
350
|
+
mockLocalClient = { disconnect: jest.fn(), isConnected: jest.fn() };
|
|
351
|
+
mockMessageProcessor = {};
|
|
352
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, mockLogger);
|
|
353
|
+
roborockService.messageClient = mockMessageClient;
|
|
354
|
+
roborockService.localClientMap.set('duid', mockLocalClient);
|
|
355
|
+
roborockService.messageProcessorMap.set('duid', mockMessageProcessor);
|
|
356
|
+
roborockService.requestDeviceStatusInterval = setInterval(() => {
|
|
357
|
+
jest.fn();
|
|
358
|
+
}, 1000);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
afterEach(() => {
|
|
362
|
+
jest.clearAllTimers();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should disconnect messageClient, localClient, remove processors, clear interval', () => {
|
|
366
|
+
roborockService.stopService();
|
|
367
|
+
expect(mockMessageClient.disconnect).toHaveBeenCalled();
|
|
368
|
+
expect(mockLocalClient.disconnect).toHaveBeenCalled();
|
|
369
|
+
expect(roborockService.localClientMap.size).toBe(0);
|
|
370
|
+
expect(roborockService.messageProcessorMap.size).toBe(0);
|
|
371
|
+
expect(roborockService.requestDeviceStatusInterval).toBeUndefined();
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe('RoborockService - setDeviceNotify', () => {
|
|
376
|
+
it('should set deviceNotify callback', () => {
|
|
377
|
+
const roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, {} as any);
|
|
378
|
+
const cb = jest.fn();
|
|
379
|
+
roborockService.setDeviceNotify(cb);
|
|
380
|
+
expect(roborockService.deviceNotify).toBe(cb);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
describe('RoborockService - sleep', () => {
|
|
385
|
+
it('should resolve after ms', async () => {
|
|
386
|
+
const roborockService = new RoborockService(jest.fn(), jest.fn(), 10, {} as any, {} as any);
|
|
387
|
+
const start = Date.now();
|
|
388
|
+
await roborockService['sleep'](100);
|
|
389
|
+
expect(Date.now() - start).toBeGreaterThanOrEqual(100);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('RoborockService - auth', () => {
|
|
394
|
+
it('should set userdata and iotApi', () => {
|
|
395
|
+
const mockLogger = {} as any;
|
|
396
|
+
const mockIotApiFactory = jest.fn().mockReturnValue('iotApi');
|
|
397
|
+
const roborockService = new RoborockService(jest.fn(), mockIotApiFactory, 10, {} as any, mockLogger);
|
|
398
|
+
const userData = { foo: 'bar' } as any;
|
|
399
|
+
const result = roborockService['auth'](userData);
|
|
400
|
+
expect(roborockService['userdata']).toBe(userData);
|
|
401
|
+
expect(roborockService['iotApi']).toBe('iotApi');
|
|
402
|
+
expect(result).toBe(userData);
|
|
403
|
+
});
|
|
155
404
|
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AnsiLogger } from 'matterbridge/logger';
|
|
2
|
+
import RoborockService from '../roborockService';
|
|
3
|
+
import { MessageProcessor } from '../roborockCommunication/broadcast/messageProcessor';
|
|
4
|
+
import { Device, DeviceStatus } from '../roborockCommunication';
|
|
5
|
+
|
|
6
|
+
jest.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
describe('RoborockService - activateDeviceNotify', () => {
|
|
9
|
+
let roborockService: RoborockService;
|
|
10
|
+
let mockLogger: AnsiLogger;
|
|
11
|
+
let mockMessageProcessor: jest.Mocked<MessageProcessor>;
|
|
12
|
+
let mockDeviceNotify: jest.Mock;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockLogger = {
|
|
16
|
+
debug: jest.fn(),
|
|
17
|
+
notice: jest.fn(),
|
|
18
|
+
error: jest.fn(),
|
|
19
|
+
warn: jest.fn(),
|
|
20
|
+
} as any;
|
|
21
|
+
|
|
22
|
+
mockMessageProcessor = {
|
|
23
|
+
getDeviceStatus: jest.fn(),
|
|
24
|
+
} as any;
|
|
25
|
+
|
|
26
|
+
mockDeviceNotify = jest.fn(() => {
|
|
27
|
+
jest.fn();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
roborockService = new RoborockService(jest.fn(), jest.fn(), 1, {} as any, mockLogger); // refreshInterval = 1
|
|
31
|
+
roborockService['deviceNotify'] = mockDeviceNotify;
|
|
32
|
+
roborockService['getMessageProcessor'] = jest.fn().mockReturnValue(mockMessageProcessor);
|
|
33
|
+
roborockService['refreshInterval'] = 1;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should call getDeviceStatus periodically and notify with status message', async () => {
|
|
37
|
+
const duid = 'test-duid';
|
|
38
|
+
const device: Device = { duid } as Device;
|
|
39
|
+
|
|
40
|
+
const fakeStatus: DeviceStatus = {
|
|
41
|
+
errorStatus: { errorCode: 0 },
|
|
42
|
+
message: { battery: 80 },
|
|
43
|
+
} as any;
|
|
44
|
+
|
|
45
|
+
(roborockService['getMessageProcessor'] as jest.Mock).mockReturnValue(mockMessageProcessor);
|
|
46
|
+
mockMessageProcessor.getDeviceStatus.mockResolvedValue(fakeStatus);
|
|
47
|
+
|
|
48
|
+
await roborockService.activateDeviceNotify(device);
|
|
49
|
+
|
|
50
|
+
jest.advanceTimersByTime(2000); // 1s = 1 cycle
|
|
51
|
+
|
|
52
|
+
expect(mockLogger.debug).toHaveBeenCalledWith('Requesting device info for device', duid);
|
|
53
|
+
expect(mockMessageProcessor.getDeviceStatus).toHaveBeenCalledWith(duid);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should log error if message processor is not found', async () => {
|
|
57
|
+
const duid = 'not-found-duid';
|
|
58
|
+
const device: Device = { duid } as Device;
|
|
59
|
+
|
|
60
|
+
(roborockService['getMessageProcessor'] as jest.Mock).mockReturnValue(undefined);
|
|
61
|
+
|
|
62
|
+
await roborockService.activateDeviceNotify(device);
|
|
63
|
+
|
|
64
|
+
jest.advanceTimersByTime(1000); // trigger the interval
|
|
65
|
+
|
|
66
|
+
expect(mockLogger.error).toHaveBeenCalledWith('Local client not initialized');
|
|
67
|
+
expect(mockDeviceNotify).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
});
|