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,657 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { AnsiLogger, debugStringify } from 'matterbridge/logger';
|
|
3
|
+
import ClientManager from './clientManager.js';
|
|
4
|
+
import { NotifyMessageTypes } from './notifyMessageTypes.js';
|
|
5
|
+
import { clearInterval } from 'node:timers';
|
|
6
|
+
import {
|
|
7
|
+
RoborockAuthenticateApi,
|
|
8
|
+
UserData,
|
|
9
|
+
RoborockIoTApi,
|
|
10
|
+
ClientRouter,
|
|
11
|
+
MessageProcessor,
|
|
12
|
+
Client,
|
|
13
|
+
Device,
|
|
14
|
+
DeviceStatus,
|
|
15
|
+
Home,
|
|
16
|
+
Protocol,
|
|
17
|
+
RequestMessage,
|
|
18
|
+
VacuumErrorCode,
|
|
19
|
+
ResponseMessage,
|
|
20
|
+
Scene,
|
|
21
|
+
SceneParam,
|
|
22
|
+
MapInfo,
|
|
23
|
+
} from './roborockCommunication/index.js';
|
|
24
|
+
import type { AbstractMessageHandler, AbstractMessageListener, BatteryMessage, DeviceErrorMessage, DeviceStatusNotify, MultipleMap } from './roborockCommunication/index.js';
|
|
25
|
+
import { ServiceArea } from 'matterbridge/matter/clusters';
|
|
26
|
+
import { LocalNetworkClient } from './roborockCommunication/broadcast/client/LocalNetworkClient.js';
|
|
27
|
+
import { RoomIndexMap } from './model/roomIndexMap.js';
|
|
28
|
+
import { DpsPayload } from './roborockCommunication/broadcast/model/dps.js';
|
|
29
|
+
import { CleanModeSetting } from './behaviors/roborock.vacuum/default/default.js';
|
|
30
|
+
export type Factory<A, T> = (logger: AnsiLogger, arg: A) => T;
|
|
31
|
+
|
|
32
|
+
interface MapRoomResponse {
|
|
33
|
+
vacuumRoom?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Security {
|
|
37
|
+
nonce: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default class RoborockService {
|
|
41
|
+
private loginApi: RoborockAuthenticateApi;
|
|
42
|
+
private logger: AnsiLogger;
|
|
43
|
+
private readonly iotApiFactory: Factory<UserData, RoborockIoTApi>;
|
|
44
|
+
|
|
45
|
+
private iotApi?: RoborockIoTApi;
|
|
46
|
+
private userdata?: UserData;
|
|
47
|
+
deviceNotify?: (messageSource: NotifyMessageTypes, homeData: unknown) => void;
|
|
48
|
+
messageClient: ClientRouter | undefined;
|
|
49
|
+
remoteDevices = new Set<string>();
|
|
50
|
+
messageProcessorMap = new Map<string, MessageProcessor>();
|
|
51
|
+
ipMap = new Map<string, string>();
|
|
52
|
+
localClientMap = new Map<string, Client>();
|
|
53
|
+
mqttAlwaysOnDevices = new Map<string, boolean>();
|
|
54
|
+
clientManager: ClientManager;
|
|
55
|
+
refreshInterval: number;
|
|
56
|
+
requestDeviceStatusInterval: NodeJS.Timeout | undefined;
|
|
57
|
+
|
|
58
|
+
// These are properties that are used to store the state of the device
|
|
59
|
+
private supportedAreas = new Map<string, ServiceArea.Area[]>();
|
|
60
|
+
private supportedRoutines = new Map<string, ServiceArea.Area[]>();
|
|
61
|
+
private selectedAreas = new Map<string, number[]>();
|
|
62
|
+
private supportedAreaIndexMaps = new Map<string, RoomIndexMap>();
|
|
63
|
+
|
|
64
|
+
private readonly vacuumNeedAPIV3 = ['roborock.vacuum.ss07'];
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
authenticateApiSupplier: Factory<void, RoborockAuthenticateApi> = (logger) => new RoborockAuthenticateApi(logger),
|
|
68
|
+
iotApiSupplier: Factory<UserData, RoborockIoTApi> = (logger, ud) => new RoborockIoTApi(ud, logger),
|
|
69
|
+
refreshInterval: number,
|
|
70
|
+
clientManager: ClientManager,
|
|
71
|
+
logger: AnsiLogger,
|
|
72
|
+
) {
|
|
73
|
+
this.logger = logger;
|
|
74
|
+
this.loginApi = authenticateApiSupplier(logger);
|
|
75
|
+
this.iotApiFactory = iotApiSupplier;
|
|
76
|
+
this.refreshInterval = refreshInterval;
|
|
77
|
+
this.clientManager = clientManager;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated Use requestVerificationCode and loginWithVerificationCode instead
|
|
82
|
+
*/
|
|
83
|
+
public async loginWithPassword(
|
|
84
|
+
username: string,
|
|
85
|
+
password: string,
|
|
86
|
+
loadSavedUserData: () => Promise<UserData | undefined>,
|
|
87
|
+
savedUserData: (userData: UserData) => Promise<void>,
|
|
88
|
+
): Promise<UserData> {
|
|
89
|
+
let userdata = await loadSavedUserData();
|
|
90
|
+
|
|
91
|
+
if (!userdata) {
|
|
92
|
+
this.logger.debug('No saved user data found, logging in with password');
|
|
93
|
+
userdata = await this.loginApi.loginWithPassword(username, password);
|
|
94
|
+
await savedUserData(userdata);
|
|
95
|
+
} else {
|
|
96
|
+
this.logger.debug('Using saved user data for login', debugStringify(userdata));
|
|
97
|
+
userdata = await this.loginApi.loginWithUserData(username, userdata);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return this.auth(userdata);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Request a verification code to be sent to the user's email
|
|
105
|
+
* @param email - The user's email address
|
|
106
|
+
*/
|
|
107
|
+
public async requestVerificationCode(email: string): Promise<void> {
|
|
108
|
+
return this.loginApi.requestCodeV4(email);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Login with a verification code received via email
|
|
113
|
+
* @param email - The user's email address
|
|
114
|
+
* @param code - The 6-digit verification code
|
|
115
|
+
* @param savedUserData - Callback to save the user data after successful login
|
|
116
|
+
* @returns UserData on successful authentication
|
|
117
|
+
*/
|
|
118
|
+
public async loginWithVerificationCode(email: string, code: string, savedUserData: (userData: UserData) => Promise<void>): Promise<UserData> {
|
|
119
|
+
const userdata = await this.loginApi.loginWithCodeV4(email, code);
|
|
120
|
+
await savedUserData(userdata);
|
|
121
|
+
return this.auth(userdata);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Login with cached user data (token reuse)
|
|
126
|
+
* @param username - The user's email address
|
|
127
|
+
* @param userData - The cached user data with token
|
|
128
|
+
* @returns UserData on successful validation
|
|
129
|
+
*/
|
|
130
|
+
public async loginWithCachedToken(username: string, userData: UserData): Promise<UserData> {
|
|
131
|
+
const validatedUserData = await this.loginApi.loginWithUserData(username, userData);
|
|
132
|
+
return this.auth(validatedUserData);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public getMessageProcessor(duid: string): MessageProcessor | undefined {
|
|
136
|
+
const messageProcessor = this.messageProcessorMap.get(duid);
|
|
137
|
+
if (!messageProcessor) {
|
|
138
|
+
this.logger.error('MessageApi is not initialized.');
|
|
139
|
+
}
|
|
140
|
+
return messageProcessor;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public setSelectedAreas(duid: string, selectedAreas: number[]): void {
|
|
144
|
+
this.logger.debug('RoborockService - setSelectedAreas', selectedAreas);
|
|
145
|
+
const roomIds = selectedAreas.map((areaId) => this.supportedAreaIndexMaps.get(duid)?.getRoomId(areaId)) ?? [];
|
|
146
|
+
this.logger.debug('RoborockService - setSelectedAreas - roomIds', roomIds);
|
|
147
|
+
this.selectedAreas.set(
|
|
148
|
+
duid,
|
|
149
|
+
roomIds.filter((id) => id !== undefined).map((id) => id),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public getSelectedAreas(duid: string): number[] {
|
|
154
|
+
return this.selectedAreas.get(duid) ?? [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public setSupportedAreas(duid: string, supportedAreas: ServiceArea.Area[]): void {
|
|
158
|
+
this.supportedAreas.set(duid, supportedAreas);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public setSupportedAreaIndexMap(duid: string, indexMap: RoomIndexMap): void {
|
|
162
|
+
this.supportedAreaIndexMaps.set(duid, indexMap);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public setSupportedScenes(duid: string, routineAsRooms: ServiceArea.Area[]) {
|
|
166
|
+
this.supportedRoutines.set(duid, routineAsRooms);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public getSupportedAreas(duid: string): ServiceArea.Area[] | undefined {
|
|
170
|
+
return this.supportedAreas.get(duid);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public getSupportedAreasIndexMap(duid: string): RoomIndexMap | undefined {
|
|
174
|
+
return this.supportedAreaIndexMaps.get(duid);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public async getCleanModeData(duid: string): Promise<CleanModeSetting> {
|
|
178
|
+
this.logger.notice('RoborockService - getCleanModeData');
|
|
179
|
+
const data = await this.getMessageProcessor(duid)?.getCleanModeData(duid);
|
|
180
|
+
if (!data) {
|
|
181
|
+
throw new Error('Failed to retrieve clean mode data');
|
|
182
|
+
}
|
|
183
|
+
return data;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public async getRoomIdFromMap(duid: string): Promise<number | undefined> {
|
|
187
|
+
const data = (await this.customGet(duid, new RequestMessage({ method: 'get_map_v1' }))) as MapRoomResponse;
|
|
188
|
+
return data?.vacuumRoom;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public async getMapInformation(duid: string): Promise<MapInfo | undefined> {
|
|
192
|
+
this.logger.debug('RoborockService - getMapInformation', duid);
|
|
193
|
+
assert(this.messageClient !== undefined);
|
|
194
|
+
return this.messageClient.get<MultipleMap[] | undefined>(duid, new RequestMessage({ method: 'get_multi_maps_list' })).then((response) => {
|
|
195
|
+
this.logger.debug('RoborockService - getMapInformation response', debugStringify(response ?? []));
|
|
196
|
+
return response ? new MapInfo(response[0]) : undefined;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public async changeCleanMode(duid: string, { suctionPower, waterFlow, distance_off, mopRoute }: CleanModeSetting): Promise<void> {
|
|
201
|
+
this.logger.notice('RoborockService - changeCleanMode');
|
|
202
|
+
return this.getMessageProcessor(duid)?.changeCleanMode(duid, suctionPower, waterFlow, mopRoute ?? 0, distance_off);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public async startClean(duid: string): Promise<void> {
|
|
206
|
+
const supportedRooms = this.supportedAreas.get(duid) ?? [];
|
|
207
|
+
const supportedRoutines = this.supportedRoutines.get(duid) ?? [];
|
|
208
|
+
const selected = this.selectedAreas.get(duid) ?? [];
|
|
209
|
+
this.logger.debug('RoborockService - begin cleaning', debugStringify({ duid, supportedRooms, supportedRoutines, selected }));
|
|
210
|
+
|
|
211
|
+
if (supportedRoutines.length === 0) {
|
|
212
|
+
if (selected.length == supportedRooms.length || selected.length === 0 || supportedRooms.length === 0) {
|
|
213
|
+
this.logger.debug('RoborockService - startGlobalClean');
|
|
214
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
215
|
+
} else {
|
|
216
|
+
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, selected }));
|
|
217
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, selected, 1);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
const rooms = selected.filter((slt) => supportedRooms.some((a: ServiceArea.Area) => a.areaId == slt));
|
|
221
|
+
const rt = selected.filter((slt) => supportedRoutines.some((a: ServiceArea.Area) => a.areaId == slt));
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* If multiple routines are selected, we log a warning. and continue with global clean
|
|
225
|
+
*/
|
|
226
|
+
if (rt.length > 1) {
|
|
227
|
+
this.logger.warn('RoborockService - Multiple routines selected, which is not supported.', debugStringify({ duid, rt }));
|
|
228
|
+
} else if (rt.length === 1) {
|
|
229
|
+
this.logger.debug('RoborockService - startScene', debugStringify({ duid, rooms }));
|
|
230
|
+
await this.iotApi?.startScene(rt[0]);
|
|
231
|
+
return;
|
|
232
|
+
} else if (rooms.length == supportedRooms.length || rooms.length === 0 || supportedRooms.length === 0) {
|
|
233
|
+
/**
|
|
234
|
+
* If no rooms are selected, or all selected rooms match the supported rooms,
|
|
235
|
+
*/
|
|
236
|
+
this.logger.debug('RoborockService - startGlobalClean');
|
|
237
|
+
this.getMessageProcessor(duid)?.startClean(duid);
|
|
238
|
+
} else if (rooms.length > 0) {
|
|
239
|
+
/**
|
|
240
|
+
* If there are rooms selected
|
|
241
|
+
*/
|
|
242
|
+
this.logger.debug('RoborockService - startRoomClean', debugStringify({ duid, rooms }));
|
|
243
|
+
return this.getMessageProcessor(duid)?.startRoomClean(duid, rooms, 1);
|
|
244
|
+
} else {
|
|
245
|
+
this.logger.warn('RoborockService - something goes wrong.', debugStringify({ duid, rooms, rt, selected, supportedRooms, supportedRoutines }));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public async pauseClean(duid: string): Promise<void> {
|
|
252
|
+
this.logger.debug('RoborockService - pauseClean');
|
|
253
|
+
await this.getMessageProcessor(duid)?.pauseClean(duid);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public async stopAndGoHome(duid: string): Promise<void> {
|
|
257
|
+
this.logger.debug('RoborockService - stopAndGoHome');
|
|
258
|
+
await this.getMessageProcessor(duid)?.gotoDock(duid);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public async resumeClean(duid: string): Promise<void> {
|
|
262
|
+
this.logger.debug('RoborockService - resumeClean');
|
|
263
|
+
await this.getMessageProcessor(duid)?.resumeClean(duid);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public async playSoundToLocate(duid: string): Promise<void> {
|
|
267
|
+
this.logger.debug('RoborockService - findMe');
|
|
268
|
+
await this.getMessageProcessor(duid)?.findMyRobot(duid);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
public async customGet(duid: string, request: RequestMessage): Promise<unknown> {
|
|
272
|
+
this.logger.debug('RoborockService - customSend-message', request.method, request.params, request.secure);
|
|
273
|
+
return this.getMessageProcessor(duid)?.getCustomMessage(duid, request);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public async customSend(duid: string, request: RequestMessage): Promise<void> {
|
|
277
|
+
return this.getMessageProcessor(duid)?.sendCustomMessage(duid, request);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
public async getCustomAPI(url: string): Promise<unknown> {
|
|
281
|
+
this.logger.debug('RoborockService - getCustomAPI', url);
|
|
282
|
+
assert(this.iotApi !== undefined);
|
|
283
|
+
try {
|
|
284
|
+
return await this.iotApi.getCustom(url);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
this.logger.error(`Failed to get custom API with url ${url}: ${error ? debugStringify(error) : 'undefined'}`);
|
|
287
|
+
return { result: undefined, error: `Failed to get custom API with url ${url}` };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
public stopService(): void {
|
|
292
|
+
if (this.messageClient) {
|
|
293
|
+
this.messageClient.disconnect();
|
|
294
|
+
this.messageClient = undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.localClientMap.size > 0) {
|
|
298
|
+
for (const [duid, client] of this.localClientMap.entries()) {
|
|
299
|
+
this.logger.debug('Disconnecting local client for device', duid);
|
|
300
|
+
client.disconnect();
|
|
301
|
+
this.localClientMap.delete(duid);
|
|
302
|
+
this.logger.debug('Local client disconnected for device', duid);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.messageProcessorMap.size > 0) {
|
|
307
|
+
for (const [duid] of this.messageProcessorMap.entries()) {
|
|
308
|
+
this.logger.debug('Disconnecting message processor for device', duid);
|
|
309
|
+
this.messageProcessorMap.delete(duid);
|
|
310
|
+
this.logger.debug('Message processor disconnected for device', duid);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (this.requestDeviceStatusInterval) {
|
|
315
|
+
clearInterval(this.requestDeviceStatusInterval);
|
|
316
|
+
this.requestDeviceStatusInterval = undefined;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
public setDeviceNotify(callback: (messageSource: NotifyMessageTypes, homeData: unknown) => Promise<void>): void {
|
|
321
|
+
this.deviceNotify = callback;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public activateDeviceNotify(device: Device): void {
|
|
325
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
326
|
+
const self = this;
|
|
327
|
+
this.logger.debug('Requesting device info for device', device.duid);
|
|
328
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
329
|
+
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
330
|
+
if (messageProcessor) {
|
|
331
|
+
await messageProcessor.getDeviceStatus(device.duid).then((response: DeviceStatus | undefined) => {
|
|
332
|
+
if (self.deviceNotify && response) {
|
|
333
|
+
const message = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
|
|
334
|
+
self.logger.debug('Socket - Device status update', debugStringify(message));
|
|
335
|
+
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
} else {
|
|
339
|
+
self.logger.error('Local client not initialized');
|
|
340
|
+
}
|
|
341
|
+
}, this.refreshInterval * 1000);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public activateDeviceNotifyOverMQTT(device: Device): void {
|
|
345
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
346
|
+
const self = this;
|
|
347
|
+
this.logger.notice('Requesting device info for device over MQTT', device.duid);
|
|
348
|
+
const messageProcessor = this.getMessageProcessor(device.duid);
|
|
349
|
+
this.requestDeviceStatusInterval = setInterval(async () => {
|
|
350
|
+
if (messageProcessor) {
|
|
351
|
+
await messageProcessor.getDeviceStatusOverMQTT(device.duid).then((response: DeviceStatus | undefined) => {
|
|
352
|
+
if (self.deviceNotify && response) {
|
|
353
|
+
const message = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
|
|
354
|
+
self.logger.debug('MQTT - Device status update', debugStringify(message));
|
|
355
|
+
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
self.logger.error('Local client not initialized');
|
|
360
|
+
}
|
|
361
|
+
}, this.refreshInterval * 500);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
public async listDevices(username: string): Promise<Device[]> {
|
|
365
|
+
assert(this.iotApi !== undefined);
|
|
366
|
+
assert(this.userdata !== undefined);
|
|
367
|
+
|
|
368
|
+
const homeDetails = await this.loginApi.getHomeDetails();
|
|
369
|
+
if (!homeDetails) {
|
|
370
|
+
throw new Error('Failed to retrieve the home details');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const homeData = (await this.iotApi.getHome(homeDetails.rrHomeId)) as Home;
|
|
374
|
+
if (!homeData) {
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const scenes = (await this.iotApi.getScenes(homeDetails.rrHomeId)) ?? [];
|
|
379
|
+
|
|
380
|
+
const products = new Map<string, string>();
|
|
381
|
+
homeData.products.forEach((p) => products.set(p.id, p.model));
|
|
382
|
+
|
|
383
|
+
if (homeData.products.some((p) => this.vacuumNeedAPIV3.includes(p.model))) {
|
|
384
|
+
this.logger.debug('Using v3 API for home data retrieval');
|
|
385
|
+
const homeDataV3 = await this.iotApi.getHomev3(homeDetails.rrHomeId);
|
|
386
|
+
if (!homeDataV3) {
|
|
387
|
+
throw new Error('Failed to retrieve the home data from v3 API');
|
|
388
|
+
}
|
|
389
|
+
homeData.devices = [...homeData.devices, ...homeDataV3.devices.filter((d) => !homeData.devices.some((x) => x.duid === d.duid))];
|
|
390
|
+
homeData.receivedDevices = [...homeData.receivedDevices, ...homeDataV3.receivedDevices.filter((d) => !homeData.receivedDevices.some((x) => x.duid === d.duid))];
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Try to get rooms from v2 API if rooms are empty
|
|
394
|
+
if (homeData.rooms.length === 0) {
|
|
395
|
+
const homeDataV2 = await this.iotApi.getHomev2(homeDetails.rrHomeId);
|
|
396
|
+
if (homeDataV2 && homeDataV2.rooms && homeDataV2.rooms.length > 0) {
|
|
397
|
+
homeData.rooms = homeDataV2.rooms;
|
|
398
|
+
} else {
|
|
399
|
+
const homeDataV3 = await this.iotApi.getHomev3(homeDetails.rrHomeId);
|
|
400
|
+
if (homeDataV3 && homeDataV3.rooms && homeDataV3.rooms.length > 0) {
|
|
401
|
+
homeData.rooms = homeDataV3.rooms;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const devices: Device[] = [...homeData.devices, ...homeData.receivedDevices];
|
|
407
|
+
|
|
408
|
+
const result = devices.map((device) => {
|
|
409
|
+
return {
|
|
410
|
+
...device,
|
|
411
|
+
rrHomeId: homeDetails.rrHomeId,
|
|
412
|
+
rooms: homeData.rooms,
|
|
413
|
+
localKey: device.localKey,
|
|
414
|
+
pv: device.pv,
|
|
415
|
+
serialNumber: device.sn,
|
|
416
|
+
scenes: scenes.filter((sc) => sc.param && (JSON.parse(sc.param) as SceneParam).action.items.some((x) => x.entityId == device.duid)),
|
|
417
|
+
data: {
|
|
418
|
+
id: device.duid,
|
|
419
|
+
firmwareVersion: device.fv,
|
|
420
|
+
serialNumber: device.sn,
|
|
421
|
+
model: homeData.products.find((p) => p.id === device.productId)?.model,
|
|
422
|
+
category: homeData.products.find((p) => p.id === device.productId)?.category,
|
|
423
|
+
batteryLevel: device.deviceStatus?.[Protocol.battery] ?? 100,
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
store: {
|
|
427
|
+
username: username,
|
|
428
|
+
userData: this.userdata as UserData,
|
|
429
|
+
localKey: device.localKey,
|
|
430
|
+
pv: device.pv,
|
|
431
|
+
model: products.get(device.productId),
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}) as Device[];
|
|
435
|
+
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
public async getHomeDataForUpdating(homeid: number): Promise<Home> {
|
|
440
|
+
assert(this.iotApi !== undefined);
|
|
441
|
+
assert(this.userdata !== undefined);
|
|
442
|
+
|
|
443
|
+
const homeData = await this.iotApi.getHomev2(homeid);
|
|
444
|
+
|
|
445
|
+
if (!homeData) {
|
|
446
|
+
throw new Error('Failed to retrieve the home data');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const products = new Map<string, string>();
|
|
450
|
+
homeData.products.forEach((p) => products.set(p.id, p.model));
|
|
451
|
+
const devices: Device[] = homeData.devices.length > 0 ? homeData.devices : homeData.receivedDevices;
|
|
452
|
+
|
|
453
|
+
if (homeData.rooms.length === 0) {
|
|
454
|
+
const homeDataV3 = await this.iotApi.getHomev3(homeid);
|
|
455
|
+
if (homeDataV3 && homeDataV3.rooms && homeDataV3.rooms.length > 0) {
|
|
456
|
+
homeData.rooms = homeDataV3.rooms;
|
|
457
|
+
} else {
|
|
458
|
+
const homeDataV1 = await this.iotApi.getHome(homeid);
|
|
459
|
+
if (homeDataV1 && homeDataV1.rooms && homeDataV1.rooms.length > 0) {
|
|
460
|
+
homeData.rooms = homeDataV1.rooms;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const dvs = devices.map((device) => {
|
|
466
|
+
return {
|
|
467
|
+
...device,
|
|
468
|
+
rrHomeId: homeid,
|
|
469
|
+
rooms: homeData.rooms,
|
|
470
|
+
serialNumber: device.sn,
|
|
471
|
+
data: {
|
|
472
|
+
id: device.duid,
|
|
473
|
+
firmwareVersion: device.fv,
|
|
474
|
+
serialNumber: device.sn,
|
|
475
|
+
model: homeData.products.find((p) => p.id === device.productId)?.model,
|
|
476
|
+
category: homeData.products.find((p) => p.id === device.productId)?.category,
|
|
477
|
+
batteryLevel: device.deviceStatus?.[Protocol.battery] ?? 100,
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
store: {
|
|
481
|
+
userData: this.userdata as UserData,
|
|
482
|
+
localKey: device.localKey,
|
|
483
|
+
pv: device.pv,
|
|
484
|
+
model: products.get(device.productId),
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
}) as Device[];
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
...homeData,
|
|
491
|
+
devices: dvs,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
public async getScenes(homeId: number): Promise<Scene[] | undefined> {
|
|
496
|
+
assert(this.iotApi !== undefined);
|
|
497
|
+
return this.iotApi.getScenes(homeId);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
public async startScene(sceneId: number): Promise<unknown> {
|
|
501
|
+
assert(this.iotApi !== undefined);
|
|
502
|
+
return this.iotApi.startScene(sceneId);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public async getRoomMappings(duid: string): Promise<number[][] | undefined> {
|
|
506
|
+
if (!this.messageClient) {
|
|
507
|
+
this.logger.warn('messageClient not initialized. Waititing for next execution');
|
|
508
|
+
return Promise.resolve(undefined);
|
|
509
|
+
}
|
|
510
|
+
return this.messageClient.get(duid, new RequestMessage({ method: 'get_room_mapping', secure: this.isRequestSecure(duid) }));
|
|
511
|
+
|
|
512
|
+
// return await this.getMessageProcessor(duid)?.getRooms(duid);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
public async initializeMessageClient(username: string, device: Device, userdata: UserData): Promise<void> {
|
|
516
|
+
if (this.clientManager === undefined) {
|
|
517
|
+
this.logger.error('ClientManager not initialized');
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
522
|
+
const self = this;
|
|
523
|
+
this.messageClient = this.clientManager.get(username, userdata);
|
|
524
|
+
this.messageClient.registerDevice(device.duid, device.localKey, device.pv, undefined);
|
|
525
|
+
|
|
526
|
+
this.messageClient.registerMessageListener({
|
|
527
|
+
onMessage: (message: ResponseMessage) => {
|
|
528
|
+
if (message instanceof ResponseMessage) {
|
|
529
|
+
const duid = message.duid;
|
|
530
|
+
|
|
531
|
+
// ignore battery updates here
|
|
532
|
+
if (message.contain(Protocol.battery)) return;
|
|
533
|
+
|
|
534
|
+
if (duid && self.deviceNotify) {
|
|
535
|
+
self.deviceNotify(NotifyMessageTypes.CloudMessage, message);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (message instanceof ResponseMessage && message.contain(Protocol.hello_response)) {
|
|
540
|
+
const dps = message.dps[Protocol.hello_response] as DpsPayload;
|
|
541
|
+
const result = dps.result as Security;
|
|
542
|
+
self.messageClient?.updateNonce(message.duid, result.nonce);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
} as AbstractMessageListener);
|
|
546
|
+
|
|
547
|
+
this.messageClient.connect();
|
|
548
|
+
|
|
549
|
+
while (!this.messageClient.isConnected()) {
|
|
550
|
+
await this.sleep(500);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
this.logger.debug('MessageClient connected');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
public async initializeMessageClientForLocal(device: Device): Promise<boolean> {
|
|
557
|
+
this.logger.debug('Begin get local ip');
|
|
558
|
+
if (this.messageClient === undefined) {
|
|
559
|
+
this.logger.error('messageClient not initialized');
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
564
|
+
const self = this;
|
|
565
|
+
|
|
566
|
+
const messageProcessor = new MessageProcessor(this.messageClient);
|
|
567
|
+
messageProcessor.injectLogger(this.logger);
|
|
568
|
+
messageProcessor.registerListener({
|
|
569
|
+
onError: (message: VacuumErrorCode) => {
|
|
570
|
+
if (self.deviceNotify) {
|
|
571
|
+
self.deviceNotify(NotifyMessageTypes.ErrorOccurred, { duid: device.duid, errorCode: message } as DeviceErrorMessage);
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
onBatteryUpdate: (percentage: number) => {
|
|
575
|
+
if (self.deviceNotify) {
|
|
576
|
+
self.deviceNotify(NotifyMessageTypes.BatteryUpdate, { duid: device.duid, percentage } as BatteryMessage);
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
onStatusChanged: () => {
|
|
580
|
+
// status: DeviceStatus
|
|
581
|
+
// if (self.deviceNotify) {
|
|
582
|
+
// const message: DeviceStatusNotify = { duid: device.duid, ...status.errorStatus, ...status.message } as DeviceStatusNotify;
|
|
583
|
+
// self.logger.debug('Device status update', debugStringify(message));
|
|
584
|
+
// self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
585
|
+
// }
|
|
586
|
+
},
|
|
587
|
+
} as AbstractMessageHandler);
|
|
588
|
+
|
|
589
|
+
this.messageProcessorMap.set(device.duid, messageProcessor);
|
|
590
|
+
|
|
591
|
+
this.logger.debug('Checking if device supports local connection', device.pv, device.data.model, device.duid);
|
|
592
|
+
if (device.pv === 'B01') {
|
|
593
|
+
this.logger.warn('Device does not support local connection', device.duid);
|
|
594
|
+
this.mqttAlwaysOnDevices.set(device.duid, true);
|
|
595
|
+
return true;
|
|
596
|
+
} else {
|
|
597
|
+
this.mqttAlwaysOnDevices.set(device.duid, false);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
this.logger.debug('Local device', device.duid);
|
|
601
|
+
let localIp = this.ipMap.get(device.duid);
|
|
602
|
+
try {
|
|
603
|
+
if (!localIp) {
|
|
604
|
+
this.logger.debug('Requesting network info for device', device.duid);
|
|
605
|
+
const networkInfo = await messageProcessor.getNetworkInfo(device.duid);
|
|
606
|
+
if (!networkInfo || !networkInfo.ip) {
|
|
607
|
+
this.logger.error('Failed to retrieve network info for device', device.duid, 'Network info:', networkInfo);
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
this.logger.debug('Network ip for device', device.duid, 'is', networkInfo.ip);
|
|
612
|
+
|
|
613
|
+
localIp = networkInfo.ip;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (localIp) {
|
|
617
|
+
this.logger.debug('initializing the local connection for this client towards ' + localIp);
|
|
618
|
+
const localClient = this.messageClient.registerClient(device.duid, localIp) as LocalNetworkClient;
|
|
619
|
+
localClient.connect();
|
|
620
|
+
|
|
621
|
+
let count = 0;
|
|
622
|
+
while (!localClient.isConnected() && count < 20) {
|
|
623
|
+
this.logger.debug('Keep waiting for local client to connect');
|
|
624
|
+
count++;
|
|
625
|
+
await this.sleep(500);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (!localClient.isConnected()) {
|
|
629
|
+
throw new Error('Local client did not connect after 10 attempts, something is wrong');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
this.ipMap.set(device.duid, localIp);
|
|
633
|
+
this.localClientMap.set(device.duid, localClient);
|
|
634
|
+
this.logger.debug('LocalClient connected');
|
|
635
|
+
}
|
|
636
|
+
} catch (error) {
|
|
637
|
+
this.logger.error('Error requesting network info', error);
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
private sleep(ms: number): Promise<void> {
|
|
645
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private auth(userdata: UserData): UserData {
|
|
649
|
+
this.userdata = userdata;
|
|
650
|
+
this.iotApi = this.iotApiFactory(this.logger, userdata);
|
|
651
|
+
return userdata;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
private isRequestSecure(duid: string): boolean {
|
|
655
|
+
return this.mqttAlwaysOnDevices.get(duid) ?? false;
|
|
656
|
+
}
|
|
657
|
+
}
|