matterbridge-roborock-vacuum-plugin-regions 1.1.1-jb.1
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 +56 -0
- package/.github/workflows/coverage.yml +59 -0
- package/.github/workflows/publish.yml +37 -0
- package/.tarignore +5 -0
- package/CHANGELOG.md +62 -0
- package/LICENSE +202 -0
- package/README.md +135 -0
- package/README_CLEANMODE.md +29 -0
- package/README_DEV.md +75 -0
- package/README_REPORT_ISSUE.md +34 -0
- package/README_SUPPORTED.md +67 -0
- package/dist/behaviorFactory.js +26 -0
- package/dist/behaviors/BehaviorDeviceGeneric.js +22 -0
- package/dist/behaviors/roborock.vacuum/default/default.js +183 -0
- package/dist/behaviors/roborock.vacuum/default/initalData.js +143 -0
- package/dist/behaviors/roborock.vacuum/default/runtimes.js +21 -0
- package/dist/behaviors/roborock.vacuum/smart/initalData.js +18 -0
- package/dist/behaviors/roborock.vacuum/smart/runtimes.js +11 -0
- package/dist/behaviors/roborock.vacuum/smart/smart.js +119 -0
- package/dist/clientManager.js +17 -0
- package/dist/helper.js +76 -0
- package/dist/index.js +4 -0
- package/dist/initialData/getBatteryStatus.js +24 -0
- package/dist/initialData/getOperationalStates.js +82 -0
- package/dist/initialData/getSupportedAreas.js +120 -0
- package/dist/initialData/getSupportedCleanModes.js +17 -0
- package/dist/initialData/getSupportedRunModes.js +11 -0
- package/dist/initialData/getSupportedScenes.js +26 -0
- package/dist/initialData/index.js +6 -0
- package/dist/model/CloudMessageModel.js +1 -0
- package/dist/model/DockingStationStatus.js +26 -0
- package/dist/model/ExperimentalFeatureSetting.js +30 -0
- package/dist/model/RoomMap.js +19 -0
- package/dist/model/roomIndexMap.js +21 -0
- package/dist/notifyMessageTypes.js +9 -0
- package/dist/platform.js +312 -0
- package/dist/platformRunner.js +90 -0
- package/dist/roborockCommunication/RESTAPI/roborockAuthenticateApi.js +213 -0
- package/dist/roborockCommunication/RESTAPI/roborockIoTApi.js +95 -0
- package/dist/roborockCommunication/Zenum/additionalPropCode.js +4 -0
- package/dist/roborockCommunication/Zenum/authenticateResponseCode.js +7 -0
- package/dist/roborockCommunication/Zenum/dockType.js +4 -0
- package/dist/roborockCommunication/Zenum/operationStatusCode.js +44 -0
- package/dist/roborockCommunication/Zenum/vacuumAndDockErrorCode.js +68 -0
- package/dist/roborockCommunication/Zmodel/apiResponse.js +1 -0
- package/dist/roborockCommunication/Zmodel/authenticateFlowState.js +1 -0
- package/dist/roborockCommunication/Zmodel/authenticateResponse.js +1 -0
- package/dist/roborockCommunication/Zmodel/baseURL.js +1 -0
- package/dist/roborockCommunication/Zmodel/batteryMessage.js +1 -0
- package/dist/roborockCommunication/Zmodel/device.js +1 -0
- package/dist/roborockCommunication/Zmodel/deviceModel.js +28 -0
- package/dist/roborockCommunication/Zmodel/deviceSchema.js +1 -0
- package/dist/roborockCommunication/Zmodel/deviceStatus.js +22 -0
- package/dist/roborockCommunication/Zmodel/dockInfo.js +6 -0
- package/dist/roborockCommunication/Zmodel/home.js +1 -0
- package/dist/roborockCommunication/Zmodel/homeInfo.js +1 -0
- package/dist/roborockCommunication/Zmodel/map.js +1 -0
- package/dist/roborockCommunication/Zmodel/mapInfo.js +29 -0
- package/dist/roborockCommunication/Zmodel/messageResult.js +7 -0
- package/dist/roborockCommunication/Zmodel/multipleMap.js +1 -0
- package/dist/roborockCommunication/Zmodel/networkInfo.js +1 -0
- package/dist/roborockCommunication/Zmodel/product.js +1 -0
- package/dist/roborockCommunication/Zmodel/room.js +1 -0
- package/dist/roborockCommunication/Zmodel/roomInfo.js +22 -0
- package/dist/roborockCommunication/Zmodel/scene.js +16 -0
- package/dist/roborockCommunication/Zmodel/userData.js +1 -0
- package/dist/roborockCommunication/Zmodel/vacuumError.js +27 -0
- package/dist/roborockCommunication/broadcast/abstractClient.js +55 -0
- package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +174 -0
- package/dist/roborockCommunication/broadcast/client/LocalNetworkUDPClient.js +129 -0
- package/dist/roborockCommunication/broadcast/client/MQTTClient.js +139 -0
- package/dist/roborockCommunication/broadcast/client.js +1 -0
- package/dist/roborockCommunication/broadcast/clientRouter.js +82 -0
- package/dist/roborockCommunication/broadcast/listener/abstractConnectionListener.js +1 -0
- package/dist/roborockCommunication/broadcast/listener/abstractMessageHandler.js +1 -0
- package/dist/roborockCommunication/broadcast/listener/abstractMessageListener.js +1 -0
- package/dist/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.js +26 -0
- package/dist/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.js +11 -0
- package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +43 -0
- package/dist/roborockCommunication/broadcast/listener/implementation/generalSyncMessageListener.js +28 -0
- package/dist/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.js +27 -0
- package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +33 -0
- package/dist/roborockCommunication/broadcast/listener/index.js +1 -0
- package/dist/roborockCommunication/broadcast/messageProcessor.js +148 -0
- package/dist/roborockCommunication/broadcast/model/contentMessage.js +1 -0
- package/dist/roborockCommunication/broadcast/model/dps.js +1 -0
- package/dist/roborockCommunication/broadcast/model/headerMessage.js +1 -0
- package/dist/roborockCommunication/broadcast/model/messageContext.js +37 -0
- package/dist/roborockCommunication/broadcast/model/protocol.js +28 -0
- package/dist/roborockCommunication/broadcast/model/requestMessage.js +38 -0
- package/dist/roborockCommunication/broadcast/model/responseMessage.js +14 -0
- package/dist/roborockCommunication/helper/chunkBuffer.js +17 -0
- package/dist/roborockCommunication/helper/cryptoHelper.js +23 -0
- package/dist/roborockCommunication/helper/messageDeserializer.js +98 -0
- package/dist/roborockCommunication/helper/messageSerializer.js +84 -0
- package/dist/roborockCommunication/helper/nameDecoder.js +66 -0
- package/dist/roborockCommunication/helper/sequence.js +16 -0
- package/dist/roborockCommunication/index.js +13 -0
- package/dist/roborockService.js +494 -0
- package/dist/runtimes/handleCloudMessage.js +110 -0
- package/dist/runtimes/handleHomeDataMessage.js +57 -0
- package/dist/runtimes/handleLocalMessage.js +169 -0
- package/dist/rvc.js +51 -0
- package/dist/settings.js +1 -0
- package/dist/share/function.js +93 -0
- package/dist/share/runtimeHelper.js +17 -0
- package/dist/tests/testData/mockData.js +359 -0
- package/eslint.config.js +80 -0
- package/jest.config.js +22 -0
- package/jest.setup.js +2 -0
- package/logo.png +0 -0
- package/matterbridge-roborock-vacuum-plugin.config.json +46 -0
- package/matterbridge-roborock-vacuum-plugin.schema.json +293 -0
- package/misc/status.md +119 -0
- package/package.json +111 -0
- package/prettier.config.js +49 -0
- package/screenshot/IMG_1.PNG +0 -0
- package/screenshot/IMG_2.PNG +0 -0
- package/screenshot/IMG_3.PNG +0 -0
- package/screenshot/IMG_4.PNG +0 -0
- package/screenshot/IMG_5.PNG +0 -0
- package/screenshot/IMG_6.PNG +0 -0
- package/screenshot/IMG_7.PNG +0 -0
- package/src/behaviorFactory.ts +41 -0
- package/src/behaviors/BehaviorDeviceGeneric.ts +31 -0
- package/src/behaviors/roborock.vacuum/default/default.ts +238 -0
- package/src/behaviors/roborock.vacuum/default/initalData.ts +152 -0
- package/src/behaviors/roborock.vacuum/default/runtimes.ts +23 -0
- package/src/behaviors/roborock.vacuum/smart/initalData.ts +20 -0
- package/src/behaviors/roborock.vacuum/smart/runtimes.ts +15 -0
- package/src/behaviors/roborock.vacuum/smart/smart.ts +159 -0
- package/src/clientManager.ts +23 -0
- package/src/helper.ts +97 -0
- package/src/index.ts +16 -0
- package/src/initialData/getBatteryStatus.ts +26 -0
- package/src/initialData/getOperationalStates.ts +94 -0
- package/src/initialData/getSupportedAreas.ts +162 -0
- package/src/initialData/getSupportedCleanModes.ts +22 -0
- package/src/initialData/getSupportedRunModes.ts +14 -0
- package/src/initialData/getSupportedScenes.ts +32 -0
- package/src/initialData/index.ts +6 -0
- package/src/model/CloudMessageModel.ts +11 -0
- package/src/model/DockingStationStatus.ts +41 -0
- package/src/model/ExperimentalFeatureSetting.ts +77 -0
- package/src/model/RoomMap.ts +38 -0
- package/src/model/roomIndexMap.ts +26 -0
- package/src/notifyMessageTypes.ts +8 -0
- package/src/platform.ts +424 -0
- package/src/platformRunner.ts +103 -0
- package/src/roborockCommunication/RESTAPI/roborockAuthenticateApi.ts +302 -0
- package/src/roborockCommunication/RESTAPI/roborockIoTApi.ts +107 -0
- package/src/roborockCommunication/Zenum/additionalPropCode.ts +3 -0
- package/src/roborockCommunication/Zenum/authenticateResponseCode.ts +6 -0
- package/src/roborockCommunication/Zenum/dockType.ts +3 -0
- package/src/roborockCommunication/Zenum/operationStatusCode.ts +43 -0
- package/src/roborockCommunication/Zenum/vacuumAndDockErrorCode.ts +68 -0
- package/src/roborockCommunication/Zmodel/apiResponse.ts +3 -0
- package/src/roborockCommunication/Zmodel/authenticateFlowState.ts +6 -0
- package/src/roborockCommunication/Zmodel/authenticateResponse.ts +5 -0
- package/src/roborockCommunication/Zmodel/baseURL.ts +5 -0
- package/src/roborockCommunication/Zmodel/batteryMessage.ts +16 -0
- package/src/roborockCommunication/Zmodel/device.ts +50 -0
- package/src/roborockCommunication/Zmodel/deviceModel.ts +27 -0
- package/src/roborockCommunication/Zmodel/deviceSchema.ts +8 -0
- package/src/roborockCommunication/Zmodel/deviceStatus.ts +30 -0
- package/src/roborockCommunication/Zmodel/dockInfo.ts +9 -0
- package/src/roborockCommunication/Zmodel/home.ts +13 -0
- package/src/roborockCommunication/Zmodel/homeInfo.ts +5 -0
- package/src/roborockCommunication/Zmodel/map.ts +20 -0
- package/src/roborockCommunication/Zmodel/mapInfo.ts +54 -0
- package/src/roborockCommunication/Zmodel/messageResult.ts +75 -0
- package/src/roborockCommunication/Zmodel/multipleMap.ts +8 -0
- package/src/roborockCommunication/Zmodel/networkInfo.ts +7 -0
- package/src/roborockCommunication/Zmodel/product.ts +9 -0
- package/src/roborockCommunication/Zmodel/room.ts +4 -0
- package/src/roborockCommunication/Zmodel/roomInfo.ts +30 -0
- package/src/roborockCommunication/Zmodel/scene.ts +44 -0
- package/src/roborockCommunication/Zmodel/userData.ts +26 -0
- package/src/roborockCommunication/Zmodel/vacuumError.ts +35 -0
- package/src/roborockCommunication/broadcast/abstractClient.ts +80 -0
- package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +218 -0
- package/src/roborockCommunication/broadcast/client/LocalNetworkUDPClient.ts +157 -0
- package/src/roborockCommunication/broadcast/client/MQTTClient.ts +174 -0
- package/src/roborockCommunication/broadcast/client.ts +19 -0
- package/src/roborockCommunication/broadcast/clientRouter.ts +104 -0
- package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +6 -0
- package/src/roborockCommunication/broadcast/listener/abstractMessageHandler.ts +11 -0
- package/src/roborockCommunication/broadcast/listener/abstractMessageListener.ts +5 -0
- package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +33 -0
- package/src/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.ts +16 -0
- package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +57 -0
- package/src/roborockCommunication/broadcast/listener/implementation/generalSyncMessageListener.ts +38 -0
- package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts +37 -0
- package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +50 -0
- package/src/roborockCommunication/broadcast/listener/index.ts +3 -0
- package/src/roborockCommunication/broadcast/messageProcessor.ts +184 -0
- package/src/roborockCommunication/broadcast/model/contentMessage.ts +5 -0
- package/src/roborockCommunication/broadcast/model/dps.ts +19 -0
- package/src/roborockCommunication/broadcast/model/headerMessage.ts +7 -0
- package/src/roborockCommunication/broadcast/model/messageContext.ts +53 -0
- package/src/roborockCommunication/broadcast/model/protocol.ts +28 -0
- package/src/roborockCommunication/broadcast/model/requestMessage.ts +51 -0
- package/src/roborockCommunication/broadcast/model/responseMessage.ts +19 -0
- package/src/roborockCommunication/helper/chunkBuffer.ts +18 -0
- package/src/roborockCommunication/helper/cryptoHelper.ts +30 -0
- package/src/roborockCommunication/helper/messageDeserializer.ts +119 -0
- package/src/roborockCommunication/helper/messageSerializer.ts +101 -0
- package/src/roborockCommunication/helper/nameDecoder.ts +78 -0
- package/src/roborockCommunication/helper/sequence.ts +18 -0
- package/src/roborockCommunication/index.ts +25 -0
- package/src/roborockService.ts +657 -0
- package/src/runtimes/handleCloudMessage.ts +134 -0
- package/src/runtimes/handleHomeDataMessage.ts +67 -0
- package/src/runtimes/handleLocalMessage.ts +209 -0
- package/src/rvc.ts +97 -0
- package/src/settings.ts +1 -0
- package/src/share/function.ts +103 -0
- package/src/share/runtimeHelper.ts +23 -0
- package/src/tests/behaviors/roborock.vacuum/default/default.test.ts +134 -0
- package/src/tests/behaviors/roborock.vacuum/smart/runtimes.test.ts +64 -0
- package/src/tests/behaviors/roborock.vacuum/smart/smart.test.ts +215 -0
- package/src/tests/helper.test.ts +162 -0
- package/src/tests/initialData/getSupportedAreas.test.ts +181 -0
- package/src/tests/model/DockingStationStatus.test.ts +39 -0
- package/src/tests/platformRunner.test.ts +188 -0
- package/src/tests/platformRunner2.test.ts +228 -0
- package/src/tests/platformRunner3.test.ts +46 -0
- package/src/tests/roborockCommunication/RESTAPI/roborockAuthenticateApi.test.ts +144 -0
- package/src/tests/roborockCommunication/RESTAPI/roborockIoTApi.test.ts +106 -0
- package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +189 -0
- package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +208 -0
- package/src/tests/roborockCommunication/broadcast/clientRouter.test.ts +168 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.test.ts +59 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.test.ts +46 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.test.ts +71 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +86 -0
- package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +126 -0
- package/src/tests/roborockService.setSelectedAreas.test.ts +61 -0
- package/src/tests/roborockService.test.ts +517 -0
- package/src/tests/roborockService2.test.ts +69 -0
- 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/tests/runtimes/handleCloudMessage.test.ts +200 -0
- package/src/tests/runtimes/handleHomeDataMessage.test.ts +54 -0
- package/src/tests/runtimes/handleLocalMessage.test.ts +227 -0
- package/src/tests/testData/mockData.ts +370 -0
- package/src/tests/testData/mockHomeData-a187.json +286 -0
- package/tsconfig.jest.json +21 -0
- package/tsconfig.json +37 -0
- package/tsconfig.production.json +19 -0
- package/tslint.json +9 -0
- package/web-for-testing/README.md +47 -0
- package/web-for-testing/nodemon.json +7 -0
- package/web-for-testing/package-lock.json +6600 -0
- package/web-for-testing/package.json +36 -0
- package/web-for-testing/src/app.ts +194 -0
- package/web-for-testing/tsconfig-ext.json +19 -0
- package/web-for-testing/tsconfig.json +23 -0
- package/web-for-testing/views/index.ejs +172 -0
- package/web-for-testing/watch.mjs +93 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
3
|
+
import { NotifyMessageTypes } from './notifyMessageTypes.js';
|
|
4
|
+
import { clearInterval } from 'node:timers';
|
|
5
|
+
import { RoborockAuthenticateApi, RoborockIoTApi, MessageProcessor, Protocol, RequestMessage, ResponseMessage, MapInfo, } from './roborockCommunication/index.js';
|
|
6
|
+
export default class RoborockService {
|
|
7
|
+
loginApi;
|
|
8
|
+
logger;
|
|
9
|
+
iotApiFactory;
|
|
10
|
+
iotApi;
|
|
11
|
+
userdata;
|
|
12
|
+
deviceNotify;
|
|
13
|
+
messageClient;
|
|
14
|
+
remoteDevices = new Set();
|
|
15
|
+
messageProcessorMap = new Map();
|
|
16
|
+
ipMap = new Map();
|
|
17
|
+
localClientMap = new Map();
|
|
18
|
+
mqttAlwaysOnDevices = new Map();
|
|
19
|
+
clientManager;
|
|
20
|
+
refreshInterval;
|
|
21
|
+
requestDeviceStatusInterval;
|
|
22
|
+
supportedAreas = new Map();
|
|
23
|
+
supportedRoutines = new Map();
|
|
24
|
+
selectedAreas = new Map();
|
|
25
|
+
supportedAreaIndexMaps = new Map();
|
|
26
|
+
vacuumNeedAPIV3 = ['roborock.vacuum.ss07'];
|
|
27
|
+
constructor(authenticateApiSupplier = (logger) => new RoborockAuthenticateApi(logger), iotApiSupplier = (logger, ud) => new RoborockIoTApi(ud, logger), refreshInterval, clientManager, logger) {
|
|
28
|
+
this.logger = logger;
|
|
29
|
+
this.loginApi = authenticateApiSupplier(logger);
|
|
30
|
+
this.iotApiFactory = iotApiSupplier;
|
|
31
|
+
this.refreshInterval = refreshInterval;
|
|
32
|
+
this.clientManager = clientManager;
|
|
33
|
+
}
|
|
34
|
+
async loginWithPassword(username, password, loadSavedUserData, savedUserData) {
|
|
35
|
+
let userdata = await loadSavedUserData();
|
|
36
|
+
if (!userdata) {
|
|
37
|
+
this.logger.debug('No saved user data found, logging in with password');
|
|
38
|
+
userdata = await this.loginApi.loginWithPassword(username, password);
|
|
39
|
+
await savedUserData(userdata);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.logger.debug('Using saved user data for login', debugStringify(userdata));
|
|
43
|
+
userdata = await this.loginApi.loginWithUserData(username, userdata);
|
|
44
|
+
}
|
|
45
|
+
return this.auth(userdata);
|
|
46
|
+
}
|
|
47
|
+
async requestVerificationCode(email) {
|
|
48
|
+
return this.loginApi.requestCodeV4(email);
|
|
49
|
+
}
|
|
50
|
+
async loginWithVerificationCode(email, code, savedUserData) {
|
|
51
|
+
const userdata = await this.loginApi.loginWithCodeV4(email, code);
|
|
52
|
+
await savedUserData(userdata);
|
|
53
|
+
return this.auth(userdata);
|
|
54
|
+
}
|
|
55
|
+
async loginWithCachedToken(username, userData) {
|
|
56
|
+
const validatedUserData = await this.loginApi.loginWithUserData(username, userData);
|
|
57
|
+
return this.auth(validatedUserData);
|
|
58
|
+
}
|
|
59
|
+
getMessageProcessor(duid) {
|
|
60
|
+
const messageProcessor = this.messageProcessorMap.get(duid);
|
|
61
|
+
if (!messageProcessor) {
|
|
62
|
+
this.logger.error('MessageApi is not initialized.');
|
|
63
|
+
}
|
|
64
|
+
return messageProcessor;
|
|
65
|
+
}
|
|
66
|
+
setSelectedAreas(duid, selectedAreas) {
|
|
67
|
+
this.logger.debug('RoborockService - setSelectedAreas', selectedAreas);
|
|
68
|
+
const roomIds = selectedAreas.map((areaId) => this.supportedAreaIndexMaps.get(duid)?.getRoomId(areaId)) ?? [];
|
|
69
|
+
this.logger.debug('RoborockService - setSelectedAreas - roomIds', roomIds);
|
|
70
|
+
this.selectedAreas.set(duid, roomIds.filter((id) => id !== undefined).map((id) => id));
|
|
71
|
+
}
|
|
72
|
+
getSelectedAreas(duid) {
|
|
73
|
+
return this.selectedAreas.get(duid) ?? [];
|
|
74
|
+
}
|
|
75
|
+
setSupportedAreas(duid, supportedAreas) {
|
|
76
|
+
this.supportedAreas.set(duid, supportedAreas);
|
|
77
|
+
}
|
|
78
|
+
setSupportedAreaIndexMap(duid, indexMap) {
|
|
79
|
+
this.supportedAreaIndexMaps.set(duid, indexMap);
|
|
80
|
+
}
|
|
81
|
+
setSupportedScenes(duid, routineAsRooms) {
|
|
82
|
+
this.supportedRoutines.set(duid, routineAsRooms);
|
|
83
|
+
}
|
|
84
|
+
getSupportedAreas(duid) {
|
|
85
|
+
return this.supportedAreas.get(duid);
|
|
86
|
+
}
|
|
87
|
+
getSupportedAreasIndexMap(duid) {
|
|
88
|
+
return this.supportedAreaIndexMaps.get(duid);
|
|
89
|
+
}
|
|
90
|
+
async getCleanModeData(duid) {
|
|
91
|
+
this.logger.notice('RoborockService - getCleanModeData');
|
|
92
|
+
const data = await this.getMessageProcessor(duid)?.getCleanModeData(duid);
|
|
93
|
+
if (!data) {
|
|
94
|
+
throw new Error('Failed to retrieve clean mode data');
|
|
95
|
+
}
|
|
96
|
+
return data;
|
|
97
|
+
}
|
|
98
|
+
async getRoomIdFromMap(duid) {
|
|
99
|
+
const data = (await this.customGet(duid, new RequestMessage({ method: 'get_map_v1' })));
|
|
100
|
+
return data?.vacuumRoom;
|
|
101
|
+
}
|
|
102
|
+
async getMapInformation(duid) {
|
|
103
|
+
this.logger.debug('RoborockService - getMapInformation', duid);
|
|
104
|
+
assert(this.messageClient !== undefined);
|
|
105
|
+
return this.messageClient.get(duid, new RequestMessage({ method: 'get_multi_maps_list' })).then((response) => {
|
|
106
|
+
this.logger.debug('RoborockService - getMapInformation response', debugStringify(response ?? []));
|
|
107
|
+
return response ? new MapInfo(response[0]) : undefined;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async changeCleanMode(duid, { suctionPower, waterFlow, distance_off, mopRoute }) {
|
|
111
|
+
this.logger.notice('RoborockService - changeCleanMode');
|
|
112
|
+
return this.getMessageProcessor(duid)?.changeCleanMode(duid, suctionPower, waterFlow, mopRoute ?? 0, distance_off);
|
|
113
|
+
}
|
|
114
|
+
async startClean(duid) {
|
|
115
|
+
const supportedRooms = this.supportedAreas.get(duid) ?? [];
|
|
116
|
+
const supportedRoutines = this.supportedRoutines.get(duid) ?? [];
|
|
117
|
+
const selected = this.selectedAreas.get(duid) ?? [];
|
|
118
|
+
this.logger.debug('RoborockService - begin cleaning', debugStringify({ duid, supportedRooms, supportedRoutines, selected }));
|
|
119
|
+
if (supportedRoutines.length === 0) {
|
|
120
|
+
if (selected.length == supportedRooms.length || selected.length === 0 || supportedRooms.length === 0) {
|
|
121
|
+
this.logger.debug('RoborockService - startGlobalClean');
|
|
122
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, selected }));
|
|
126
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, selected, 1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const rooms = selected.filter((slt) => supportedRooms.some((a) => a.areaId == slt));
|
|
131
|
+
const rt = selected.filter((slt) => supportedRoutines.some((a) => a.areaId == slt));
|
|
132
|
+
if (rt.length > 1) {
|
|
133
|
+
this.logger.warn('RoborockService - Multiple routines selected, which is not supported.', debugStringify({ duid, rt }));
|
|
134
|
+
}
|
|
135
|
+
else if (rt.length === 1) {
|
|
136
|
+
this.logger.debug('RoborockService - startScene', debugStringify({ duid, rooms }));
|
|
137
|
+
await this.iotApi?.startScene(rt[0]);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
else if (rooms.length == supportedRooms.length || rooms.length === 0 || supportedRooms.length === 0) {
|
|
141
|
+
this.logger.debug('RoborockService - startGlobalClean');
|
|
142
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
143
|
+
}
|
|
144
|
+
else if (rooms.length > 0) {
|
|
145
|
+
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, rooms }));
|
|
146
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, rooms, 1);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
this.logger.warn('RoborockService - something goes wrong.', debugStringify({ duid, rooms, rt, selected, supportedRooms, supportedRoutines }));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async pauseClean(duid) {
|
|
155
|
+
this.logger.debug('RoborockService - pauseClean');
|
|
156
|
+
await this.getMessageProcessor(duid)?.pauseClean(duid);
|
|
157
|
+
}
|
|
158
|
+
async stopAndGoHome(duid) {
|
|
159
|
+
this.logger.debug('RoborockService - stopAndGoHome');
|
|
160
|
+
await this.getMessageProcessor(duid)?.gotoDock(duid);
|
|
161
|
+
}
|
|
162
|
+
async resumeClean(duid) {
|
|
163
|
+
this.logger.debug('RoborockService - resumeClean');
|
|
164
|
+
await this.getMessageProcessor(duid)?.resumeClean(duid);
|
|
165
|
+
}
|
|
166
|
+
async playSoundToLocate(duid) {
|
|
167
|
+
this.logger.debug('RoborockService - findMe');
|
|
168
|
+
await this.getMessageProcessor(duid)?.findMyRobot(duid);
|
|
169
|
+
}
|
|
170
|
+
async customGet(duid, request) {
|
|
171
|
+
this.logger.debug('RoborockService - customSend-message', request.method, request.params, request.secure);
|
|
172
|
+
return this.getMessageProcessor(duid)?.getCustomMessage(duid, request);
|
|
173
|
+
}
|
|
174
|
+
async customSend(duid, request) {
|
|
175
|
+
return this.getMessageProcessor(duid)?.sendCustomMessage(duid, request);
|
|
176
|
+
}
|
|
177
|
+
async getCustomAPI(url) {
|
|
178
|
+
this.logger.debug('RoborockService - getCustomAPI', url);
|
|
179
|
+
assert(this.iotApi !== undefined);
|
|
180
|
+
try {
|
|
181
|
+
return await this.iotApi.getCustom(url);
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
this.logger.error(`Failed to get custom API with url ${url}: ${error ? debugStringify(error) : 'undefined'}`);
|
|
185
|
+
return { result: undefined, error: `Failed to get custom API with url ${url}` };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
stopService() {
|
|
189
|
+
if (this.messageClient) {
|
|
190
|
+
this.messageClient.disconnect();
|
|
191
|
+
this.messageClient = undefined;
|
|
192
|
+
}
|
|
193
|
+
if (this.localClientMap.size > 0) {
|
|
194
|
+
for (const [duid, client] of this.localClientMap.entries()) {
|
|
195
|
+
this.logger.debug('Disconnecting local client for device', duid);
|
|
196
|
+
client.disconnect();
|
|
197
|
+
this.localClientMap.delete(duid);
|
|
198
|
+
this.logger.debug('Local client disconnected for device', duid);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (this.messageProcessorMap.size > 0) {
|
|
202
|
+
for (const [duid] of this.messageProcessorMap.entries()) {
|
|
203
|
+
this.logger.debug('Disconnecting message processor for device', duid);
|
|
204
|
+
this.messageProcessorMap.delete(duid);
|
|
205
|
+
this.logger.debug('Message processor disconnected for device', duid);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (this.requestDeviceStatusInterval) {
|
|
209
|
+
clearInterval(this.requestDeviceStatusInterval);
|
|
210
|
+
this.requestDeviceStatusInterval = undefined;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
setDeviceNotify(callback) {
|
|
214
|
+
this.deviceNotify = callback;
|
|
215
|
+
}
|
|
216
|
+
activateDeviceNotify(device) {
|
|
217
|
+
const self = this;
|
|
218
|
+
this.logger.debug('Requesting device info for device', device.duid);
|
|
219
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
220
|
+
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
221
|
+
if (messageProcessor) {
|
|
222
|
+
await messageProcessor.getDeviceStatus(device.duid).then((response) => {
|
|
223
|
+
if (self.deviceNotify && response) {
|
|
224
|
+
const message = { duid: device.duid, ...response.errorStatus, ...response.message };
|
|
225
|
+
self.logger.debug('Socket - Device status update', debugStringify(message));
|
|
226
|
+
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
self.logger.error('Local client not initialized');
|
|
232
|
+
}
|
|
233
|
+
}, this.refreshInterval * 1000);
|
|
234
|
+
}
|
|
235
|
+
activateDeviceNotifyOverMQTT(device) {
|
|
236
|
+
const self = this;
|
|
237
|
+
this.logger.notice('Requesting device info for device over MQTT', device.duid);
|
|
238
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
239
|
+
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
240
|
+
if (messageProcessor) {
|
|
241
|
+
await messageProcessor.getDeviceStatusOverMQTT(device.duid).then((response) => {
|
|
242
|
+
if (self.deviceNotify && response) {
|
|
243
|
+
const message = { duid: device.duid, ...response.errorStatus, ...response.message };
|
|
244
|
+
self.logger.debug('MQTT - Device status update', debugStringify(message));
|
|
245
|
+
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
self.logger.error('Local client not initialized');
|
|
251
|
+
}
|
|
252
|
+
}, this.refreshInterval * 500);
|
|
253
|
+
}
|
|
254
|
+
async listDevices(username) {
|
|
255
|
+
assert(this.iotApi !== undefined);
|
|
256
|
+
assert(this.userdata !== undefined);
|
|
257
|
+
const homeDetails = await this.loginApi.getHomeDetails();
|
|
258
|
+
if (!homeDetails) {
|
|
259
|
+
throw new Error('Failed to retrieve the home details');
|
|
260
|
+
}
|
|
261
|
+
const homeData = (await this.iotApi.getHome(homeDetails.rrHomeId));
|
|
262
|
+
if (!homeData) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
const scenes = (await this.iotApi.getScenes(homeDetails.rrHomeId)) ?? [];
|
|
266
|
+
const products = new Map();
|
|
267
|
+
homeData.products.forEach((p) => products.set(p.id, p.model));
|
|
268
|
+
if (homeData.products.some((p) => this.vacuumNeedAPIV3.includes(p.model))) {
|
|
269
|
+
this.logger.debug('Using v3 API for home data retrieval');
|
|
270
|
+
const homeDataV3 = await this.iotApi.getHomev3(homeDetails.rrHomeId);
|
|
271
|
+
if (!homeDataV3) {
|
|
272
|
+
throw new Error('Failed to retrieve the home data from v3 API');
|
|
273
|
+
}
|
|
274
|
+
homeData.devices = [...homeData.devices, ...homeDataV3.devices.filter((d) => !homeData.devices.some((x) => x.duid === d.duid))];
|
|
275
|
+
homeData.receivedDevices = [...homeData.receivedDevices, ...homeDataV3.receivedDevices.filter((d) => !homeData.receivedDevices.some((x) => x.duid === d.duid))];
|
|
276
|
+
}
|
|
277
|
+
if (homeData.rooms.length === 0) {
|
|
278
|
+
const homeDataV2 = await this.iotApi.getHomev2(homeDetails.rrHomeId);
|
|
279
|
+
if (homeDataV2 && homeDataV2.rooms && homeDataV2.rooms.length > 0) {
|
|
280
|
+
homeData.rooms = homeDataV2.rooms;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
const homeDataV3 = await this.iotApi.getHomev3(homeDetails.rrHomeId);
|
|
284
|
+
if (homeDataV3 && homeDataV3.rooms && homeDataV3.rooms.length > 0) {
|
|
285
|
+
homeData.rooms = homeDataV3.rooms;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const devices = [...homeData.devices, ...homeData.receivedDevices];
|
|
290
|
+
const result = devices.map((device) => {
|
|
291
|
+
return {
|
|
292
|
+
...device,
|
|
293
|
+
rrHomeId: homeDetails.rrHomeId,
|
|
294
|
+
rooms: homeData.rooms,
|
|
295
|
+
localKey: device.localKey,
|
|
296
|
+
pv: device.pv,
|
|
297
|
+
serialNumber: device.sn,
|
|
298
|
+
scenes: scenes.filter((sc) => sc.param && JSON.parse(sc.param).action.items.some((x) => x.entityId == device.duid)),
|
|
299
|
+
data: {
|
|
300
|
+
id: device.duid,
|
|
301
|
+
firmwareVersion: device.fv,
|
|
302
|
+
serialNumber: device.sn,
|
|
303
|
+
model: homeData.products.find((p) => p.id === device.productId)?.model,
|
|
304
|
+
category: homeData.products.find((p) => p.id === device.productId)?.category,
|
|
305
|
+
batteryLevel: device.deviceStatus?.[Protocol.battery] ?? 100,
|
|
306
|
+
},
|
|
307
|
+
store: {
|
|
308
|
+
username: username,
|
|
309
|
+
userData: this.userdata,
|
|
310
|
+
localKey: device.localKey,
|
|
311
|
+
pv: device.pv,
|
|
312
|
+
model: products.get(device.productId),
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
});
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
async getHomeDataForUpdating(homeid) {
|
|
319
|
+
assert(this.iotApi !== undefined);
|
|
320
|
+
assert(this.userdata !== undefined);
|
|
321
|
+
const homeData = await this.iotApi.getHomev2(homeid);
|
|
322
|
+
if (!homeData) {
|
|
323
|
+
throw new Error('Failed to retrieve the home data');
|
|
324
|
+
}
|
|
325
|
+
const products = new Map();
|
|
326
|
+
homeData.products.forEach((p) => products.set(p.id, p.model));
|
|
327
|
+
const devices = homeData.devices.length > 0 ? homeData.devices : homeData.receivedDevices;
|
|
328
|
+
if (homeData.rooms.length === 0) {
|
|
329
|
+
const homeDataV3 = await this.iotApi.getHomev3(homeid);
|
|
330
|
+
if (homeDataV3 && homeDataV3.rooms && homeDataV3.rooms.length > 0) {
|
|
331
|
+
homeData.rooms = homeDataV3.rooms;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
const homeDataV1 = await this.iotApi.getHome(homeid);
|
|
335
|
+
if (homeDataV1 && homeDataV1.rooms && homeDataV1.rooms.length > 0) {
|
|
336
|
+
homeData.rooms = homeDataV1.rooms;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const dvs = devices.map((device) => {
|
|
341
|
+
return {
|
|
342
|
+
...device,
|
|
343
|
+
rrHomeId: homeid,
|
|
344
|
+
rooms: homeData.rooms,
|
|
345
|
+
serialNumber: device.sn,
|
|
346
|
+
data: {
|
|
347
|
+
id: device.duid,
|
|
348
|
+
firmwareVersion: device.fv,
|
|
349
|
+
serialNumber: device.sn,
|
|
350
|
+
model: homeData.products.find((p) => p.id === device.productId)?.model,
|
|
351
|
+
category: homeData.products.find((p) => p.id === device.productId)?.category,
|
|
352
|
+
batteryLevel: device.deviceStatus?.[Protocol.battery] ?? 100,
|
|
353
|
+
},
|
|
354
|
+
store: {
|
|
355
|
+
userData: this.userdata,
|
|
356
|
+
localKey: device.localKey,
|
|
357
|
+
pv: device.pv,
|
|
358
|
+
model: products.get(device.productId),
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
});
|
|
362
|
+
return {
|
|
363
|
+
...homeData,
|
|
364
|
+
devices: dvs,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
async getScenes(homeId) {
|
|
368
|
+
assert(this.iotApi !== undefined);
|
|
369
|
+
return this.iotApi.getScenes(homeId);
|
|
370
|
+
}
|
|
371
|
+
async startScene(sceneId) {
|
|
372
|
+
assert(this.iotApi !== undefined);
|
|
373
|
+
return this.iotApi.startScene(sceneId);
|
|
374
|
+
}
|
|
375
|
+
async getRoomMappings(duid) {
|
|
376
|
+
if (!this.messageClient) {
|
|
377
|
+
this.logger.warn('messageClient not initialized. Waititing for next execution');
|
|
378
|
+
return Promise.resolve(undefined);
|
|
379
|
+
}
|
|
380
|
+
return this.messageClient.get(duid, new RequestMessage({ method: 'get_room_mapping', secure: this.isRequestSecure(duid) }));
|
|
381
|
+
}
|
|
382
|
+
async initializeMessageClient(username, device, userdata) {
|
|
383
|
+
if (this.clientManager === undefined) {
|
|
384
|
+
this.logger.error('ClientManager not initialized');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
const self = this;
|
|
388
|
+
this.messageClient = this.clientManager.get(username, userdata);
|
|
389
|
+
this.messageClient.registerDevice(device.duid, device.localKey, device.pv, undefined);
|
|
390
|
+
this.messageClient.registerMessageListener({
|
|
391
|
+
onMessage: (message) => {
|
|
392
|
+
if (message instanceof ResponseMessage) {
|
|
393
|
+
const duid = message.duid;
|
|
394
|
+
if (message.contain(Protocol.battery))
|
|
395
|
+
return;
|
|
396
|
+
if (duid && self.deviceNotify) {
|
|
397
|
+
self.deviceNotify(NotifyMessageTypes.CloudMessage, message);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (message instanceof ResponseMessage && message.contain(Protocol.hello_response)) {
|
|
401
|
+
const dps = message.dps[Protocol.hello_response];
|
|
402
|
+
const result = dps.result;
|
|
403
|
+
self.messageClient?.updateNonce(message.duid, result.nonce);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
this.messageClient.connect();
|
|
408
|
+
while (!this.messageClient.isConnected()) {
|
|
409
|
+
await this.sleep(500);
|
|
410
|
+
}
|
|
411
|
+
this.logger.debug('MessageClient connected');
|
|
412
|
+
}
|
|
413
|
+
async initializeMessageClientForLocal(device) {
|
|
414
|
+
this.logger.debug('Begin get local ip');
|
|
415
|
+
if (this.messageClient === undefined) {
|
|
416
|
+
this.logger.error('messageClient not initialized');
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const self = this;
|
|
420
|
+
const messageProcessor = new MessageProcessor(this.messageClient);
|
|
421
|
+
messageProcessor.injectLogger(this.logger);
|
|
422
|
+
messageProcessor.registerListener({
|
|
423
|
+
onError: (message) => {
|
|
424
|
+
if (self.deviceNotify) {
|
|
425
|
+
self.deviceNotify(NotifyMessageTypes.ErrorOccurred, { duid: device.duid, errorCode: message });
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
onBatteryUpdate: (percentage) => {
|
|
429
|
+
if (self.deviceNotify) {
|
|
430
|
+
self.deviceNotify(NotifyMessageTypes.BatteryUpdate, { duid: device.duid, percentage });
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
onStatusChanged: () => {
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
this.messageProcessorMap.set(device.duid, messageProcessor);
|
|
437
|
+
this.logger.debug('Checking if device supports local connection', device.pv, device.data.model, device.duid);
|
|
438
|
+
if (device.pv === 'B01') {
|
|
439
|
+
this.logger.warn('Device does not support local connection', device.duid);
|
|
440
|
+
this.mqttAlwaysOnDevices.set(device.duid, true);
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
this.mqttAlwaysOnDevices.set(device.duid, false);
|
|
445
|
+
}
|
|
446
|
+
this.logger.debug('Local device', device.duid);
|
|
447
|
+
let localIp = this.ipMap.get(device.duid);
|
|
448
|
+
try {
|
|
449
|
+
if (!localIp) {
|
|
450
|
+
this.logger.debug('Requesting network info for device', device.duid);
|
|
451
|
+
const networkInfo = await messageProcessor.getNetworkInfo(device.duid);
|
|
452
|
+
if (!networkInfo || !networkInfo.ip) {
|
|
453
|
+
this.logger.error('Failed to retrieve network info for device', device.duid, 'Network info:', networkInfo);
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
this.logger.debug('Network ip for device', device.duid, 'is', networkInfo.ip);
|
|
457
|
+
localIp = networkInfo.ip;
|
|
458
|
+
}
|
|
459
|
+
if (localIp) {
|
|
460
|
+
this.logger.debug('initializing the local connection for this client towards ' + localIp);
|
|
461
|
+
const localClient = this.messageClient.registerClient(device.duid, localIp);
|
|
462
|
+
localClient.connect();
|
|
463
|
+
let count = 0;
|
|
464
|
+
while (!localClient.isConnected() && count < 20) {
|
|
465
|
+
this.logger.debug('Keep waiting for local client to connect');
|
|
466
|
+
count++;
|
|
467
|
+
await this.sleep(500);
|
|
468
|
+
}
|
|
469
|
+
if (!localClient.isConnected()) {
|
|
470
|
+
throw new Error('Local client did not connect after 10 attempts, something is wrong');
|
|
471
|
+
}
|
|
472
|
+
this.ipMap.set(device.duid, localIp);
|
|
473
|
+
this.localClientMap.set(device.duid, localClient);
|
|
474
|
+
this.logger.debug('LocalClient connected');
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
this.logger.error('Error requesting network info', error);
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
sleep(ms) {
|
|
484
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
485
|
+
}
|
|
486
|
+
auth(userdata) {
|
|
487
|
+
this.userdata = userdata;
|
|
488
|
+
this.iotApi = this.iotApiFactory(this.logger, userdata);
|
|
489
|
+
return userdata;
|
|
490
|
+
}
|
|
491
|
+
isRequestSecure(duid) {
|
|
492
|
+
return this.mqttAlwaysOnDevices.get(duid) ?? false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getRunningMode } from '../initialData/getSupportedRunModes.js';
|
|
2
|
+
import { hasDockingStationError } from '../model/DockingStationStatus.js';
|
|
3
|
+
import { AdditionalPropCode, Protocol } from '../roborockCommunication/index.js';
|
|
4
|
+
import { state_to_matter_operational_status, state_to_matter_state } from '../share/function.js';
|
|
5
|
+
import { RvcCleanMode, RvcOperationalState, RvcRunMode, ServiceArea } from 'matterbridge/matter/clusters';
|
|
6
|
+
import { triggerDssError } from './handleLocalMessage.js';
|
|
7
|
+
import { getRoomMapFromDevice, isStatusUpdate } from '../helper.js';
|
|
8
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
9
|
+
import { NotifyMessageTypes } from '../notifyMessageTypes.js';
|
|
10
|
+
import { getCurrentCleanModeFunc } from '../share/runtimeHelper.js';
|
|
11
|
+
import { getSupportedAreas } from '../initialData/getSupportedAreas.js';
|
|
12
|
+
export async function handleCloudMessage(data, platform, runner, duid) {
|
|
13
|
+
const messageTypes = Object.keys(data.dps).map(Number);
|
|
14
|
+
const robot = platform.robots.get(duid);
|
|
15
|
+
if (robot === undefined) {
|
|
16
|
+
platform.log.error(`Error3: Robot with DUID ${duid} not found`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
messageTypes.forEach(async (messageType) => {
|
|
20
|
+
switch (messageType) {
|
|
21
|
+
case Protocol.status_update: {
|
|
22
|
+
const status = Number(data.dps[messageType]);
|
|
23
|
+
const matterState = state_to_matter_state(status);
|
|
24
|
+
if (matterState) {
|
|
25
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
26
|
+
}
|
|
27
|
+
const operationalStateId = state_to_matter_operational_status(status);
|
|
28
|
+
if (operationalStateId) {
|
|
29
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
30
|
+
if (!(dssHasError && triggerDssError(robot, platform))) {
|
|
31
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case Protocol.rpc_response: {
|
|
37
|
+
const response = data.dps[messageType];
|
|
38
|
+
if (!isStatusUpdate(response.result)) {
|
|
39
|
+
platform.log.debug('Ignore message:', debugStringify(data));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let roboStatus;
|
|
43
|
+
if (Array.isArray(response.result) && response.result.length > 0) {
|
|
44
|
+
roboStatus = response.result[0];
|
|
45
|
+
}
|
|
46
|
+
if (roboStatus) {
|
|
47
|
+
const message = { ...roboStatus };
|
|
48
|
+
platform.log.debug('rpc_response:', debugStringify(message));
|
|
49
|
+
await runner.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, duid, true);
|
|
50
|
+
}
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case Protocol.suction_power:
|
|
54
|
+
case Protocol.water_box_mode: {
|
|
55
|
+
await platform.roborockService?.getCleanModeData(duid).then((cleanModeData) => {
|
|
56
|
+
if (cleanModeData) {
|
|
57
|
+
const currentCleanMode = getCurrentCleanModeFunc(robot.device.data.model, platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)({
|
|
58
|
+
suctionPower: cleanModeData.suctionPower,
|
|
59
|
+
waterFlow: cleanModeData.waterFlow,
|
|
60
|
+
distance_off: cleanModeData.distance_off,
|
|
61
|
+
mopRoute: cleanModeData.mopRoute,
|
|
62
|
+
});
|
|
63
|
+
platform.log.debug(`Clean mode data: ${debugStringify(cleanModeData)}`);
|
|
64
|
+
platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
65
|
+
if (currentCleanMode) {
|
|
66
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case Protocol.additional_props: {
|
|
73
|
+
platform.log.notice(`Received additional properties for robot ${duid}: ${debugStringify(data)}`);
|
|
74
|
+
const propCode = data.dps[Protocol.additional_props];
|
|
75
|
+
platform.log.debug(`DPS for additional properties: ${propCode}, AdditionalPropCode: ${AdditionalPropCode[propCode]}`);
|
|
76
|
+
if (propCode === AdditionalPropCode.map_change) {
|
|
77
|
+
await handleMapChange(robot, platform, duid);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case Protocol.back_type: {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
default: {
|
|
85
|
+
platform.log.notice(`Unknown message type ${messageType}, protocol: ${Protocol[messageType]}, message: ${debugStringify(data)}`);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export async function handleMapChange(robot, platform, duid) {
|
|
92
|
+
const enableMultipleMap = (platform.enableExperimentalFeature?.enableExperimentalFeature && platform.enableExperimentalFeature?.advancedFeature?.enableMultipleMap) ?? false;
|
|
93
|
+
if (!enableMultipleMap)
|
|
94
|
+
return;
|
|
95
|
+
await getRoomMapFromDevice(robot.device, platform).then((roomMap) => {
|
|
96
|
+
const { supportedAreas, supportedMaps, roomIndexMap } = getSupportedAreas(robot.device.rooms, roomMap, enableMultipleMap, platform.log);
|
|
97
|
+
platform.log.debug(`handleMapChange - supportedAreas: ${debugStringify(supportedAreas)}`);
|
|
98
|
+
platform.log.debug(`handleMapChange - supportedMaps: ${debugStringify(supportedMaps)}`);
|
|
99
|
+
platform.log.debug(`handleMapChange - roomIndexMap: `, roomIndexMap);
|
|
100
|
+
platform.roborockService?.setSupportedAreas(duid, supportedAreas);
|
|
101
|
+
platform.roborockService?.setSelectedAreas(duid, []);
|
|
102
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'supportedAreas', supportedAreas, platform.log);
|
|
103
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'selectedAreas', [], platform.log);
|
|
104
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', null, platform.log);
|
|
105
|
+
if (enableMultipleMap) {
|
|
106
|
+
platform.roborockService?.setSupportedAreaIndexMap(duid, roomIndexMap);
|
|
107
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'supportedMaps', supportedMaps, platform.log);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PowerSource, RvcOperationalState, RvcRunMode } from 'matterbridge/matter/clusters';
|
|
2
|
+
import { getVacuumProperty } from '../helper.js';
|
|
3
|
+
import { debugStringify } from 'matterbridge/logger';
|
|
4
|
+
import { getBatteryState, getBatteryStatus } from '../initialData/index.js';
|
|
5
|
+
import { state_to_matter_operational_status, state_to_matter_state } from '../share/function.js';
|
|
6
|
+
import { OperationStatusCode } from '../roborockCommunication/Zenum/operationStatusCode.js';
|
|
7
|
+
import { getRunningMode } from '../initialData/getSupportedRunModes.js';
|
|
8
|
+
import { hasDockingStationError } from '../model/DockingStationStatus.js';
|
|
9
|
+
import { triggerDssError } from './handleLocalMessage.js';
|
|
10
|
+
export async function updateFromHomeData(homeData, platform) {
|
|
11
|
+
if (platform.robots.size === 0)
|
|
12
|
+
return;
|
|
13
|
+
const devices = homeData.devices.filter((d) => platform.robots.has(d.duid));
|
|
14
|
+
for (const device of devices) {
|
|
15
|
+
const robot = platform.robots.get(device.duid);
|
|
16
|
+
if (robot === undefined) {
|
|
17
|
+
platform.log.error(`Error5: Robot with DUID ${device.duid} not found`);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const deviceData = robot.device.data;
|
|
21
|
+
if (!device || deviceData === undefined) {
|
|
22
|
+
platform.log.error('Device not found in home data');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
device.schema = homeData.products.find((prd) => prd.id === device.productId || prd.model === device.data.model)?.schema ?? [];
|
|
26
|
+
platform.log.debug('updateFromHomeData-homeData:', debugStringify(homeData));
|
|
27
|
+
platform.log.debug('updateFromHomeData-device:', debugStringify(device));
|
|
28
|
+
platform.log.debug('updateFromHomeData-schema:' + debugStringify(device.schema));
|
|
29
|
+
platform.log.debug('updateFromHomeData-battery:' + debugStringify(device.deviceStatus));
|
|
30
|
+
const batteryLevel = getVacuumProperty(device, 'battery');
|
|
31
|
+
if (batteryLevel) {
|
|
32
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel ? batteryLevel * 2 : 200, platform.log);
|
|
33
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
34
|
+
}
|
|
35
|
+
const state = getVacuumProperty(device, 'state');
|
|
36
|
+
const matterState = state_to_matter_state(state);
|
|
37
|
+
if (!state || !matterState) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
|
|
41
|
+
if (matterState) {
|
|
42
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
43
|
+
}
|
|
44
|
+
if (batteryLevel) {
|
|
45
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
|
|
46
|
+
}
|
|
47
|
+
const operationalStateId = state_to_matter_operational_status(state);
|
|
48
|
+
if (operationalStateId) {
|
|
49
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
50
|
+
platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(robot.dockStationStatus ?? {})}`);
|
|
51
|
+
if (!(dssHasError && triggerDssError(robot, platform))) {
|
|
52
|
+
platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
|
|
53
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|