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.
Files changed (261) hide show
  1. package/.github/workflows/build.yml +56 -0
  2. package/.github/workflows/coverage.yml +59 -0
  3. package/.github/workflows/publish.yml +37 -0
  4. package/.tarignore +5 -0
  5. package/CHANGELOG.md +62 -0
  6. package/LICENSE +202 -0
  7. package/README.md +135 -0
  8. package/README_CLEANMODE.md +29 -0
  9. package/README_DEV.md +75 -0
  10. package/README_REPORT_ISSUE.md +34 -0
  11. package/README_SUPPORTED.md +67 -0
  12. package/dist/behaviorFactory.js +26 -0
  13. package/dist/behaviors/BehaviorDeviceGeneric.js +22 -0
  14. package/dist/behaviors/roborock.vacuum/default/default.js +183 -0
  15. package/dist/behaviors/roborock.vacuum/default/initalData.js +143 -0
  16. package/dist/behaviors/roborock.vacuum/default/runtimes.js +21 -0
  17. package/dist/behaviors/roborock.vacuum/smart/initalData.js +18 -0
  18. package/dist/behaviors/roborock.vacuum/smart/runtimes.js +11 -0
  19. package/dist/behaviors/roborock.vacuum/smart/smart.js +119 -0
  20. package/dist/clientManager.js +17 -0
  21. package/dist/helper.js +76 -0
  22. package/dist/index.js +4 -0
  23. package/dist/initialData/getBatteryStatus.js +24 -0
  24. package/dist/initialData/getOperationalStates.js +82 -0
  25. package/dist/initialData/getSupportedAreas.js +120 -0
  26. package/dist/initialData/getSupportedCleanModes.js +17 -0
  27. package/dist/initialData/getSupportedRunModes.js +11 -0
  28. package/dist/initialData/getSupportedScenes.js +26 -0
  29. package/dist/initialData/index.js +6 -0
  30. package/dist/model/CloudMessageModel.js +1 -0
  31. package/dist/model/DockingStationStatus.js +26 -0
  32. package/dist/model/ExperimentalFeatureSetting.js +30 -0
  33. package/dist/model/RoomMap.js +19 -0
  34. package/dist/model/roomIndexMap.js +21 -0
  35. package/dist/notifyMessageTypes.js +9 -0
  36. package/dist/platform.js +312 -0
  37. package/dist/platformRunner.js +90 -0
  38. package/dist/roborockCommunication/RESTAPI/roborockAuthenticateApi.js +213 -0
  39. package/dist/roborockCommunication/RESTAPI/roborockIoTApi.js +95 -0
  40. package/dist/roborockCommunication/Zenum/additionalPropCode.js +4 -0
  41. package/dist/roborockCommunication/Zenum/authenticateResponseCode.js +7 -0
  42. package/dist/roborockCommunication/Zenum/dockType.js +4 -0
  43. package/dist/roborockCommunication/Zenum/operationStatusCode.js +44 -0
  44. package/dist/roborockCommunication/Zenum/vacuumAndDockErrorCode.js +68 -0
  45. package/dist/roborockCommunication/Zmodel/apiResponse.js +1 -0
  46. package/dist/roborockCommunication/Zmodel/authenticateFlowState.js +1 -0
  47. package/dist/roborockCommunication/Zmodel/authenticateResponse.js +1 -0
  48. package/dist/roborockCommunication/Zmodel/baseURL.js +1 -0
  49. package/dist/roborockCommunication/Zmodel/batteryMessage.js +1 -0
  50. package/dist/roborockCommunication/Zmodel/device.js +1 -0
  51. package/dist/roborockCommunication/Zmodel/deviceModel.js +28 -0
  52. package/dist/roborockCommunication/Zmodel/deviceSchema.js +1 -0
  53. package/dist/roborockCommunication/Zmodel/deviceStatus.js +22 -0
  54. package/dist/roborockCommunication/Zmodel/dockInfo.js +6 -0
  55. package/dist/roborockCommunication/Zmodel/home.js +1 -0
  56. package/dist/roborockCommunication/Zmodel/homeInfo.js +1 -0
  57. package/dist/roborockCommunication/Zmodel/map.js +1 -0
  58. package/dist/roborockCommunication/Zmodel/mapInfo.js +29 -0
  59. package/dist/roborockCommunication/Zmodel/messageResult.js +7 -0
  60. package/dist/roborockCommunication/Zmodel/multipleMap.js +1 -0
  61. package/dist/roborockCommunication/Zmodel/networkInfo.js +1 -0
  62. package/dist/roborockCommunication/Zmodel/product.js +1 -0
  63. package/dist/roborockCommunication/Zmodel/room.js +1 -0
  64. package/dist/roborockCommunication/Zmodel/roomInfo.js +22 -0
  65. package/dist/roborockCommunication/Zmodel/scene.js +16 -0
  66. package/dist/roborockCommunication/Zmodel/userData.js +1 -0
  67. package/dist/roborockCommunication/Zmodel/vacuumError.js +27 -0
  68. package/dist/roborockCommunication/broadcast/abstractClient.js +55 -0
  69. package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +174 -0
  70. package/dist/roborockCommunication/broadcast/client/LocalNetworkUDPClient.js +129 -0
  71. package/dist/roborockCommunication/broadcast/client/MQTTClient.js +139 -0
  72. package/dist/roborockCommunication/broadcast/client.js +1 -0
  73. package/dist/roborockCommunication/broadcast/clientRouter.js +82 -0
  74. package/dist/roborockCommunication/broadcast/listener/abstractConnectionListener.js +1 -0
  75. package/dist/roborockCommunication/broadcast/listener/abstractMessageHandler.js +1 -0
  76. package/dist/roborockCommunication/broadcast/listener/abstractMessageListener.js +1 -0
  77. package/dist/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.js +26 -0
  78. package/dist/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.js +11 -0
  79. package/dist/roborockCommunication/broadcast/listener/implementation/connectionStateListener.js +43 -0
  80. package/dist/roborockCommunication/broadcast/listener/implementation/generalSyncMessageListener.js +28 -0
  81. package/dist/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.js +27 -0
  82. package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +33 -0
  83. package/dist/roborockCommunication/broadcast/listener/index.js +1 -0
  84. package/dist/roborockCommunication/broadcast/messageProcessor.js +148 -0
  85. package/dist/roborockCommunication/broadcast/model/contentMessage.js +1 -0
  86. package/dist/roborockCommunication/broadcast/model/dps.js +1 -0
  87. package/dist/roborockCommunication/broadcast/model/headerMessage.js +1 -0
  88. package/dist/roborockCommunication/broadcast/model/messageContext.js +37 -0
  89. package/dist/roborockCommunication/broadcast/model/protocol.js +28 -0
  90. package/dist/roborockCommunication/broadcast/model/requestMessage.js +38 -0
  91. package/dist/roborockCommunication/broadcast/model/responseMessage.js +14 -0
  92. package/dist/roborockCommunication/helper/chunkBuffer.js +17 -0
  93. package/dist/roborockCommunication/helper/cryptoHelper.js +23 -0
  94. package/dist/roborockCommunication/helper/messageDeserializer.js +98 -0
  95. package/dist/roborockCommunication/helper/messageSerializer.js +84 -0
  96. package/dist/roborockCommunication/helper/nameDecoder.js +66 -0
  97. package/dist/roborockCommunication/helper/sequence.js +16 -0
  98. package/dist/roborockCommunication/index.js +13 -0
  99. package/dist/roborockService.js +494 -0
  100. package/dist/runtimes/handleCloudMessage.js +110 -0
  101. package/dist/runtimes/handleHomeDataMessage.js +57 -0
  102. package/dist/runtimes/handleLocalMessage.js +169 -0
  103. package/dist/rvc.js +51 -0
  104. package/dist/settings.js +1 -0
  105. package/dist/share/function.js +93 -0
  106. package/dist/share/runtimeHelper.js +17 -0
  107. package/dist/tests/testData/mockData.js +359 -0
  108. package/eslint.config.js +80 -0
  109. package/jest.config.js +22 -0
  110. package/jest.setup.js +2 -0
  111. package/logo.png +0 -0
  112. package/matterbridge-roborock-vacuum-plugin.config.json +46 -0
  113. package/matterbridge-roborock-vacuum-plugin.schema.json +293 -0
  114. package/misc/status.md +119 -0
  115. package/package.json +111 -0
  116. package/prettier.config.js +49 -0
  117. package/screenshot/IMG_1.PNG +0 -0
  118. package/screenshot/IMG_2.PNG +0 -0
  119. package/screenshot/IMG_3.PNG +0 -0
  120. package/screenshot/IMG_4.PNG +0 -0
  121. package/screenshot/IMG_5.PNG +0 -0
  122. package/screenshot/IMG_6.PNG +0 -0
  123. package/screenshot/IMG_7.PNG +0 -0
  124. package/src/behaviorFactory.ts +41 -0
  125. package/src/behaviors/BehaviorDeviceGeneric.ts +31 -0
  126. package/src/behaviors/roborock.vacuum/default/default.ts +238 -0
  127. package/src/behaviors/roborock.vacuum/default/initalData.ts +152 -0
  128. package/src/behaviors/roborock.vacuum/default/runtimes.ts +23 -0
  129. package/src/behaviors/roborock.vacuum/smart/initalData.ts +20 -0
  130. package/src/behaviors/roborock.vacuum/smart/runtimes.ts +15 -0
  131. package/src/behaviors/roborock.vacuum/smart/smart.ts +159 -0
  132. package/src/clientManager.ts +23 -0
  133. package/src/helper.ts +97 -0
  134. package/src/index.ts +16 -0
  135. package/src/initialData/getBatteryStatus.ts +26 -0
  136. package/src/initialData/getOperationalStates.ts +94 -0
  137. package/src/initialData/getSupportedAreas.ts +162 -0
  138. package/src/initialData/getSupportedCleanModes.ts +22 -0
  139. package/src/initialData/getSupportedRunModes.ts +14 -0
  140. package/src/initialData/getSupportedScenes.ts +32 -0
  141. package/src/initialData/index.ts +6 -0
  142. package/src/model/CloudMessageModel.ts +11 -0
  143. package/src/model/DockingStationStatus.ts +41 -0
  144. package/src/model/ExperimentalFeatureSetting.ts +77 -0
  145. package/src/model/RoomMap.ts +38 -0
  146. package/src/model/roomIndexMap.ts +26 -0
  147. package/src/notifyMessageTypes.ts +8 -0
  148. package/src/platform.ts +424 -0
  149. package/src/platformRunner.ts +103 -0
  150. package/src/roborockCommunication/RESTAPI/roborockAuthenticateApi.ts +302 -0
  151. package/src/roborockCommunication/RESTAPI/roborockIoTApi.ts +107 -0
  152. package/src/roborockCommunication/Zenum/additionalPropCode.ts +3 -0
  153. package/src/roborockCommunication/Zenum/authenticateResponseCode.ts +6 -0
  154. package/src/roborockCommunication/Zenum/dockType.ts +3 -0
  155. package/src/roborockCommunication/Zenum/operationStatusCode.ts +43 -0
  156. package/src/roborockCommunication/Zenum/vacuumAndDockErrorCode.ts +68 -0
  157. package/src/roborockCommunication/Zmodel/apiResponse.ts +3 -0
  158. package/src/roborockCommunication/Zmodel/authenticateFlowState.ts +6 -0
  159. package/src/roborockCommunication/Zmodel/authenticateResponse.ts +5 -0
  160. package/src/roborockCommunication/Zmodel/baseURL.ts +5 -0
  161. package/src/roborockCommunication/Zmodel/batteryMessage.ts +16 -0
  162. package/src/roborockCommunication/Zmodel/device.ts +50 -0
  163. package/src/roborockCommunication/Zmodel/deviceModel.ts +27 -0
  164. package/src/roborockCommunication/Zmodel/deviceSchema.ts +8 -0
  165. package/src/roborockCommunication/Zmodel/deviceStatus.ts +30 -0
  166. package/src/roborockCommunication/Zmodel/dockInfo.ts +9 -0
  167. package/src/roborockCommunication/Zmodel/home.ts +13 -0
  168. package/src/roborockCommunication/Zmodel/homeInfo.ts +5 -0
  169. package/src/roborockCommunication/Zmodel/map.ts +20 -0
  170. package/src/roborockCommunication/Zmodel/mapInfo.ts +54 -0
  171. package/src/roborockCommunication/Zmodel/messageResult.ts +75 -0
  172. package/src/roborockCommunication/Zmodel/multipleMap.ts +8 -0
  173. package/src/roborockCommunication/Zmodel/networkInfo.ts +7 -0
  174. package/src/roborockCommunication/Zmodel/product.ts +9 -0
  175. package/src/roborockCommunication/Zmodel/room.ts +4 -0
  176. package/src/roborockCommunication/Zmodel/roomInfo.ts +30 -0
  177. package/src/roborockCommunication/Zmodel/scene.ts +44 -0
  178. package/src/roborockCommunication/Zmodel/userData.ts +26 -0
  179. package/src/roborockCommunication/Zmodel/vacuumError.ts +35 -0
  180. package/src/roborockCommunication/broadcast/abstractClient.ts +80 -0
  181. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +218 -0
  182. package/src/roborockCommunication/broadcast/client/LocalNetworkUDPClient.ts +157 -0
  183. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +174 -0
  184. package/src/roborockCommunication/broadcast/client.ts +19 -0
  185. package/src/roborockCommunication/broadcast/clientRouter.ts +104 -0
  186. package/src/roborockCommunication/broadcast/listener/abstractConnectionListener.ts +6 -0
  187. package/src/roborockCommunication/broadcast/listener/abstractMessageHandler.ts +11 -0
  188. package/src/roborockCommunication/broadcast/listener/abstractMessageListener.ts +5 -0
  189. package/src/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.ts +33 -0
  190. package/src/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.ts +16 -0
  191. package/src/roborockCommunication/broadcast/listener/implementation/connectionStateListener.ts +57 -0
  192. package/src/roborockCommunication/broadcast/listener/implementation/generalSyncMessageListener.ts +38 -0
  193. package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts +37 -0
  194. package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +50 -0
  195. package/src/roborockCommunication/broadcast/listener/index.ts +3 -0
  196. package/src/roborockCommunication/broadcast/messageProcessor.ts +184 -0
  197. package/src/roborockCommunication/broadcast/model/contentMessage.ts +5 -0
  198. package/src/roborockCommunication/broadcast/model/dps.ts +19 -0
  199. package/src/roborockCommunication/broadcast/model/headerMessage.ts +7 -0
  200. package/src/roborockCommunication/broadcast/model/messageContext.ts +53 -0
  201. package/src/roborockCommunication/broadcast/model/protocol.ts +28 -0
  202. package/src/roborockCommunication/broadcast/model/requestMessage.ts +51 -0
  203. package/src/roborockCommunication/broadcast/model/responseMessage.ts +19 -0
  204. package/src/roborockCommunication/helper/chunkBuffer.ts +18 -0
  205. package/src/roborockCommunication/helper/cryptoHelper.ts +30 -0
  206. package/src/roborockCommunication/helper/messageDeserializer.ts +119 -0
  207. package/src/roborockCommunication/helper/messageSerializer.ts +101 -0
  208. package/src/roborockCommunication/helper/nameDecoder.ts +78 -0
  209. package/src/roborockCommunication/helper/sequence.ts +18 -0
  210. package/src/roborockCommunication/index.ts +25 -0
  211. package/src/roborockService.ts +657 -0
  212. package/src/runtimes/handleCloudMessage.ts +134 -0
  213. package/src/runtimes/handleHomeDataMessage.ts +67 -0
  214. package/src/runtimes/handleLocalMessage.ts +209 -0
  215. package/src/rvc.ts +97 -0
  216. package/src/settings.ts +1 -0
  217. package/src/share/function.ts +103 -0
  218. package/src/share/runtimeHelper.ts +23 -0
  219. package/src/tests/behaviors/roborock.vacuum/default/default.test.ts +134 -0
  220. package/src/tests/behaviors/roborock.vacuum/smart/runtimes.test.ts +64 -0
  221. package/src/tests/behaviors/roborock.vacuum/smart/smart.test.ts +215 -0
  222. package/src/tests/helper.test.ts +162 -0
  223. package/src/tests/initialData/getSupportedAreas.test.ts +181 -0
  224. package/src/tests/model/DockingStationStatus.test.ts +39 -0
  225. package/src/tests/platformRunner.test.ts +188 -0
  226. package/src/tests/platformRunner2.test.ts +228 -0
  227. package/src/tests/platformRunner3.test.ts +46 -0
  228. package/src/tests/roborockCommunication/RESTAPI/roborockAuthenticateApi.test.ts +144 -0
  229. package/src/tests/roborockCommunication/RESTAPI/roborockIoTApi.test.ts +106 -0
  230. package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +189 -0
  231. package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +208 -0
  232. package/src/tests/roborockCommunication/broadcast/clientRouter.test.ts +168 -0
  233. package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.test.ts +59 -0
  234. package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.test.ts +46 -0
  235. package/src/tests/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.test.ts +71 -0
  236. package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +86 -0
  237. package/src/tests/roborockCommunication/broadcast/messageProcessor.test.ts +126 -0
  238. package/src/tests/roborockService.setSelectedAreas.test.ts +61 -0
  239. package/src/tests/roborockService.test.ts +517 -0
  240. package/src/tests/roborockService2.test.ts +69 -0
  241. package/src/tests/roborockService3.test.ts +133 -0
  242. package/src/tests/roborockService4.test.ts +76 -0
  243. package/src/tests/roborockService5.test.ts +79 -0
  244. package/src/tests/runtimes/handleCloudMessage.test.ts +200 -0
  245. package/src/tests/runtimes/handleHomeDataMessage.test.ts +54 -0
  246. package/src/tests/runtimes/handleLocalMessage.test.ts +227 -0
  247. package/src/tests/testData/mockData.ts +370 -0
  248. package/src/tests/testData/mockHomeData-a187.json +286 -0
  249. package/tsconfig.jest.json +21 -0
  250. package/tsconfig.json +37 -0
  251. package/tsconfig.production.json +19 -0
  252. package/tslint.json +9 -0
  253. package/web-for-testing/README.md +47 -0
  254. package/web-for-testing/nodemon.json +7 -0
  255. package/web-for-testing/package-lock.json +6600 -0
  256. package/web-for-testing/package.json +36 -0
  257. package/web-for-testing/src/app.ts +194 -0
  258. package/web-for-testing/tsconfig-ext.json +19 -0
  259. package/web-for-testing/tsconfig.json +23 -0
  260. package/web-for-testing/views/index.ejs +172 -0
  261. package/web-for-testing/watch.mjs +93 -0
@@ -0,0 +1,33 @@
1
+ import { debugStringify } from 'matterbridge/logger';
2
+ import { Protocol } from '../../model/protocol.js';
3
+ export class SyncMessageListener {
4
+ pending = new Map();
5
+ logger;
6
+ constructor(logger) {
7
+ this.logger = logger;
8
+ }
9
+ waitFor(messageId, request, resolve, reject) {
10
+ this.pending.set(messageId, resolve);
11
+ setTimeout(() => {
12
+ this.pending.delete(messageId);
13
+ reject(new Error(`Message timeout for messageId: ${messageId}, request: ${debugStringify(request)}`));
14
+ }, 10000);
15
+ }
16
+ async onMessage(message) {
17
+ if (message.contain(Protocol.rpc_response)) {
18
+ const dps = message.get(Protocol.rpc_response);
19
+ const messageId = dps.id;
20
+ const responseHandler = this.pending.get(messageId);
21
+ if (responseHandler) {
22
+ responseHandler(dps.result);
23
+ }
24
+ this.pending.delete(messageId);
25
+ return;
26
+ }
27
+ if (message.contain(Protocol.map_response)) {
28
+ const dps = message.get(Protocol.map_response);
29
+ this.pending.delete(dps.id);
30
+ return;
31
+ }
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ export { SimpleMessageListener } from './implementation/simpleMessageListener.js';
@@ -0,0 +1,148 @@
1
+ import { debugStringify } from 'matterbridge/logger';
2
+ import { SimpleMessageListener } from './listener/index.js';
3
+ import { RequestMessage } from './model/requestMessage.js';
4
+ import { DeviceStatus } from '../Zmodel/deviceStatus.js';
5
+ export class MessageProcessor {
6
+ client;
7
+ messageListener;
8
+ logger;
9
+ constructor(client) {
10
+ this.client = client;
11
+ this.messageListener = new SimpleMessageListener();
12
+ this.client.registerMessageListener(this.messageListener);
13
+ }
14
+ injectLogger(logger) {
15
+ this.logger = logger;
16
+ }
17
+ registerListener(listener) {
18
+ this.messageListener.registerListener(listener);
19
+ }
20
+ async getNetworkInfo(duid) {
21
+ const request = new RequestMessage({ method: 'get_network_info' });
22
+ return await this.client.get(duid, request);
23
+ }
24
+ async getDeviceStatus(duid) {
25
+ const request = new RequestMessage({ method: 'get_status' });
26
+ const response = await this.client.get(duid, request);
27
+ if (response) {
28
+ this.logger?.debug('Device status: ', debugStringify(response));
29
+ return new DeviceStatus(response);
30
+ }
31
+ return undefined;
32
+ }
33
+ async getDeviceStatusOverMQTT(duid) {
34
+ const request = new RequestMessage({ method: 'get_status', secure: true });
35
+ const response = await this.client.get(duid, request);
36
+ if (response) {
37
+ this.logger?.debug('MQTT - Device status: ', debugStringify(response));
38
+ return new DeviceStatus(response);
39
+ }
40
+ return undefined;
41
+ }
42
+ async getRooms(duid) {
43
+ const request = new RequestMessage({ method: 'get_room_mapping' });
44
+ return this.client.get(duid, request);
45
+ }
46
+ async gotoDock(duid) {
47
+ const request = new RequestMessage({ method: 'app_charge' });
48
+ return this.client.send(duid, request);
49
+ }
50
+ async startClean(duid) {
51
+ const request = new RequestMessage({ method: 'app_start' });
52
+ return this.client.send(duid, request);
53
+ }
54
+ async startRoomClean(duid, roomIds, repeat) {
55
+ const request = new RequestMessage({
56
+ method: 'app_segment_clean',
57
+ params: [{ segments: roomIds, repeat: repeat }],
58
+ });
59
+ return this.client.send(duid, request);
60
+ }
61
+ async pauseClean(duid) {
62
+ const request = new RequestMessage({ method: 'app_pause' });
63
+ return this.client.send(duid, request);
64
+ }
65
+ async resumeClean(duid) {
66
+ const request = new RequestMessage({ method: 'app_resume' });
67
+ return this.client.send(duid, request);
68
+ }
69
+ async stopClean(duid) {
70
+ const request = new RequestMessage({ method: 'app_stop' });
71
+ return this.client.send(duid, request);
72
+ }
73
+ async sendCustomMessage(duid, def) {
74
+ const request = new RequestMessage(def);
75
+ return this.client.send(duid, request);
76
+ }
77
+ getCustomMessage(duid, def) {
78
+ return this.client.get(duid, def);
79
+ }
80
+ async findMyRobot(duid) {
81
+ const request = new RequestMessage({ method: 'find_me' });
82
+ return this.client.get(duid, request);
83
+ }
84
+ async getCleanModeData(duid) {
85
+ const currentMopMode = await this.getCustomMessage(duid, new RequestMessage({ method: 'get_mop_mode' }));
86
+ const suctionPowerRaw = await this.getCustomMessage(duid, new RequestMessage({ method: 'get_custom_mode' }));
87
+ const waterFlowRaw = await this.getCustomMessage(duid, new RequestMessage({ method: 'get_water_box_custom_mode' }));
88
+ let suctionPower;
89
+ let waterFlow;
90
+ let mopRoute;
91
+ let distance_off = 0;
92
+ if (Array.isArray(suctionPowerRaw)) {
93
+ suctionPower = suctionPowerRaw[0];
94
+ }
95
+ else {
96
+ suctionPower = suctionPowerRaw;
97
+ }
98
+ if (Array.isArray(currentMopMode)) {
99
+ mopRoute = currentMopMode[0];
100
+ }
101
+ else {
102
+ mopRoute = currentMopMode;
103
+ }
104
+ if (typeof waterFlowRaw === 'object' && waterFlowRaw !== null && 'water_box_mode' in waterFlowRaw) {
105
+ waterFlow = waterFlowRaw.water_box_mode;
106
+ if ('distance_off' in waterFlowRaw) {
107
+ distance_off = waterFlowRaw.distance_off ?? 0;
108
+ }
109
+ }
110
+ else {
111
+ waterFlow = waterFlowRaw;
112
+ }
113
+ return {
114
+ suctionPower: suctionPower,
115
+ waterFlow: waterFlow,
116
+ distance_off: distance_off,
117
+ mopRoute: mopRoute,
118
+ };
119
+ }
120
+ async changeCleanMode(duid, suctionPower, waterFlow, mopRoute, distance_off) {
121
+ this.logger?.notice(`Change clean mode for ${duid} to suctionPower: ${suctionPower}, waterFlow: ${waterFlow}, mopRoute: ${mopRoute}, distance_off: ${distance_off}`);
122
+ const currentMopMode = await this.getCustomMessage(duid, new RequestMessage({ method: 'get_custom_mode' }));
123
+ const smartMopMode = 110;
124
+ const smartMopRoute = 306;
125
+ const customMopMode = 106;
126
+ const customMopRoute = 302;
127
+ if (currentMopMode == smartMopMode && mopRoute == smartMopRoute)
128
+ return;
129
+ if (currentMopMode == customMopMode && mopRoute == customMopRoute)
130
+ return;
131
+ if (currentMopMode == smartMopMode) {
132
+ await this.client.send(duid, new RequestMessage({ method: 'set_mop_mode', params: [customMopRoute] }));
133
+ }
134
+ if (suctionPower && suctionPower != 0) {
135
+ await this.client.send(duid, new RequestMessage({ method: 'set_custom_mode', params: [suctionPower] }));
136
+ }
137
+ const CustomizeWithDistanceOff = 207;
138
+ if (waterFlow && waterFlow == CustomizeWithDistanceOff && distance_off && distance_off != 0) {
139
+ await this.client.send(duid, new RequestMessage({ method: 'set_water_box_custom_mode', params: { 'water_box_mode': waterFlow, 'distance_off': distance_off } }));
140
+ }
141
+ else if (waterFlow && waterFlow != 0) {
142
+ await this.client.send(duid, new RequestMessage({ method: 'set_water_box_custom_mode', params: [waterFlow] }));
143
+ }
144
+ if (mopRoute && mopRoute != 0) {
145
+ await this.client.send(duid, new RequestMessage({ method: 'set_mop_mode', params: [mopRoute] }));
146
+ }
147
+ }
148
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { randomBytes, randomInt } from 'node:crypto';
2
+ import * as CryptoUtils from '../../helper/cryptoHelper.js';
3
+ export class MessageContext {
4
+ endpoint;
5
+ devices = new Map();
6
+ nonce;
7
+ serializeNonce;
8
+ constructor(userdata) {
9
+ this.endpoint = CryptoUtils.md5bin(userdata.rriot.k).subarray(8, 14).toString('base64');
10
+ this.nonce = randomInt(1000, 1000000);
11
+ this.serializeNonce = randomBytes(16);
12
+ }
13
+ registerDevice(duid, localKey, pv, nonce) {
14
+ this.devices.set(duid, { localKey: localKey, protocolVersion: pv, nonce });
15
+ }
16
+ updateNonce(duid, nonce) {
17
+ const device = this.devices.get(duid);
18
+ if (device) {
19
+ device.nonce = nonce;
20
+ }
21
+ }
22
+ getSerializeNonceAsHex() {
23
+ return this.serializeNonce.toString('hex').toUpperCase();
24
+ }
25
+ getLocalKey(duid) {
26
+ return this.devices.get(duid)?.localKey;
27
+ }
28
+ getProtocolVersion(duid) {
29
+ return this.devices.get(duid)?.protocolVersion;
30
+ }
31
+ getDeviceNonce(duid) {
32
+ return this.devices.get(duid)?.nonce;
33
+ }
34
+ getEndpoint() {
35
+ return this.endpoint;
36
+ }
37
+ }
@@ -0,0 +1,28 @@
1
+ export var Protocol;
2
+ (function (Protocol) {
3
+ Protocol[Protocol["hello_request"] = 0] = "hello_request";
4
+ Protocol[Protocol["hello_response"] = 1] = "hello_response";
5
+ Protocol[Protocol["ping_request"] = 2] = "ping_request";
6
+ Protocol[Protocol["ping_response"] = 3] = "ping_response";
7
+ Protocol[Protocol["general_request"] = 4] = "general_request";
8
+ Protocol[Protocol["general_response"] = 5] = "general_response";
9
+ Protocol[Protocol["rpc_request"] = 101] = "rpc_request";
10
+ Protocol[Protocol["rpc_response"] = 102] = "rpc_response";
11
+ Protocol[Protocol["error"] = 120] = "error";
12
+ Protocol[Protocol["status_update"] = 121] = "status_update";
13
+ Protocol[Protocol["battery"] = 122] = "battery";
14
+ Protocol[Protocol["suction_power"] = 123] = "suction_power";
15
+ Protocol[Protocol["water_box_mode"] = 124] = "water_box_mode";
16
+ Protocol[Protocol["main_brush_work_time"] = 125] = "main_brush_work_time";
17
+ Protocol[Protocol["side_brush_work_time"] = 126] = "side_brush_work_time";
18
+ Protocol[Protocol["filter_work_time"] = 127] = "filter_work_time";
19
+ Protocol[Protocol["additional_props"] = 128] = "additional_props";
20
+ Protocol[Protocol["task_complete"] = 130] = "task_complete";
21
+ Protocol[Protocol["task_cancel_low_power"] = 131] = "task_cancel_low_power";
22
+ Protocol[Protocol["task_cancel_in_motion"] = 132] = "task_cancel_in_motion";
23
+ Protocol[Protocol["charge_status"] = 133] = "charge_status";
24
+ Protocol[Protocol["drying_status"] = 134] = "drying_status";
25
+ Protocol[Protocol["back_type"] = 139] = "back_type";
26
+ Protocol[Protocol["map_response"] = 301] = "map_response";
27
+ Protocol[Protocol["some_thing_happened_when_socket_closed"] = 500] = "some_thing_happened_when_socket_closed";
28
+ })(Protocol || (Protocol = {}));
@@ -0,0 +1,38 @@
1
+ import { Protocol } from './protocol.js';
2
+ import { randomInt } from 'node:crypto';
3
+ export class RequestMessage {
4
+ messageId;
5
+ protocol;
6
+ method;
7
+ params;
8
+ secure;
9
+ timestamp;
10
+ nonce;
11
+ constructor(args) {
12
+ this.messageId = args.messageId ?? randomInt(10000, 32767);
13
+ this.protocol = args.protocol ?? Protocol.rpc_request;
14
+ this.method = args.method;
15
+ this.params = args.params;
16
+ this.secure = args.secure ?? false;
17
+ this.nonce = args.nonce ?? randomInt(10000, 32767);
18
+ this.timestamp = args.timestamp ?? Math.floor(Date.now() / 1000);
19
+ }
20
+ toMqttRequest() {
21
+ return this;
22
+ }
23
+ toLocalRequest() {
24
+ if (this.protocol == Protocol.rpc_request) {
25
+ return new RequestMessage({
26
+ messageId: this.messageId,
27
+ protocol: Protocol.general_request,
28
+ method: this.method,
29
+ params: this.params,
30
+ secure: this.secure,
31
+ timestamp: this.timestamp,
32
+ });
33
+ }
34
+ else {
35
+ return this;
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,14 @@
1
+ export class ResponseMessage {
2
+ duid;
3
+ dps;
4
+ constructor(duid, dps) {
5
+ this.duid = duid;
6
+ this.dps = dps;
7
+ }
8
+ contain(index) {
9
+ return this.dps[index.toString()] !== undefined;
10
+ }
11
+ get(index) {
12
+ return this.dps[index.toString()];
13
+ }
14
+ }
@@ -0,0 +1,17 @@
1
+ export class ChunkBuffer {
2
+ buffer = Buffer.alloc(0);
3
+ get() {
4
+ return this.buffer;
5
+ }
6
+ reset() {
7
+ this.buffer = Buffer.alloc(0);
8
+ }
9
+ append(message) {
10
+ if (this.buffer.length == 0) {
11
+ this.buffer = message;
12
+ }
13
+ else {
14
+ this.buffer = Buffer.concat([this.buffer, message]);
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,23 @@
1
+ import crypto from 'node:crypto';
2
+ export function md5bin(str) {
3
+ return crypto.createHash('md5').update(str).digest();
4
+ }
5
+ export function md5hex(str) {
6
+ return crypto.createHash('md5').update(str).digest('hex');
7
+ }
8
+ export function decryptECB(encrypted, aesKey) {
9
+ const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
10
+ decipher.setAutoPadding(false);
11
+ let decrypted = decipher.update(encrypted, 'binary', 'utf8');
12
+ decrypted += decipher.final('utf8');
13
+ return removePadding(decrypted);
14
+ }
15
+ function removePadding(str) {
16
+ const paddingLength = str.charCodeAt(str.length - 1);
17
+ return str.slice(0, -paddingLength);
18
+ }
19
+ export const SALT = 'TXdfu$jyZ#TZHsg4';
20
+ export function encodeTimestamp(timestamp) {
21
+ const hex = timestamp.toString(16).padStart(8, '0').split('');
22
+ return [5, 6, 3, 7, 1, 2, 0, 4].map((idx) => hex[idx]).join('');
23
+ }
@@ -0,0 +1,98 @@
1
+ import crypto from 'node:crypto';
2
+ import CRC32 from 'crc-32';
3
+ import { ResponseMessage } from '../broadcast/model/responseMessage.js';
4
+ import * as CryptoUtils from './cryptoHelper.js';
5
+ import { Protocol } from '../broadcast/model/protocol.js';
6
+ import { Parser } from 'binary-parser/dist/binary_parser.js';
7
+ export class MessageDeserializer {
8
+ context;
9
+ headerMessageParser;
10
+ contentMessageParser;
11
+ logger;
12
+ supportedVersions = ['1.0', 'A01', 'B01'];
13
+ constructor(context, logger) {
14
+ this.context = context;
15
+ this.logger = logger;
16
+ this.headerMessageParser = new Parser()
17
+ .endianness('big')
18
+ .string('version', {
19
+ length: 3,
20
+ })
21
+ .uint32('seq')
22
+ .uint32('nonce')
23
+ .uint32('timestamp')
24
+ .uint16('protocol');
25
+ this.contentMessageParser = new Parser()
26
+ .endianness('big')
27
+ .uint16('payloadLen')
28
+ .buffer('payload', {
29
+ length: 'payloadLen',
30
+ })
31
+ .uint32('crc32');
32
+ }
33
+ deserialize(duid, message) {
34
+ const header = this.headerMessageParser.parse(message);
35
+ if (!this.supportedVersions.includes(header.version)) {
36
+ throw new Error('unknown protocol version ' + header.version);
37
+ }
38
+ if (header.protocol === Protocol.hello_response || header.protocol === Protocol.ping_response) {
39
+ const dpsValue = {
40
+ id: header.seq,
41
+ result: {
42
+ version: header.version,
43
+ nonce: header.nonce,
44
+ },
45
+ };
46
+ return new ResponseMessage(duid, { [header.protocol.toString()]: dpsValue });
47
+ }
48
+ const data = this.contentMessageParser.parse(message.subarray(this.headerMessageParser.sizeOf()));
49
+ const crc32 = CRC32.buf(message.subarray(0, message.length - 4)) >>> 0;
50
+ const expectedCrc32 = message.readUInt32BE(message.length - 4);
51
+ if (crc32 != expectedCrc32) {
52
+ throw new Error(`Wrong CRC32 ${crc32}, expected ${expectedCrc32}`);
53
+ }
54
+ const localKey = this.context.getLocalKey(duid);
55
+ if (!localKey) {
56
+ this.logger.notice(`Unable to retrieve local key for ${duid}, it should be from other vacuums`);
57
+ return new ResponseMessage(duid, { dps: { id: 0, result: null } });
58
+ }
59
+ if (header.version == '1.0') {
60
+ const aesKey = CryptoUtils.md5bin(CryptoUtils.encodeTimestamp(header.timestamp) + localKey + CryptoUtils.SALT);
61
+ const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
62
+ data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
63
+ }
64
+ else if (header.version == 'A01') {
65
+ const iv = CryptoUtils.md5hex(header.nonce.toString(16).padStart(8, '0') + '726f626f726f636b2d67a6d6da').substring(8, 24);
66
+ const decipher = crypto.createDecipheriv('aes-128-cbc', localKey, iv);
67
+ data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
68
+ }
69
+ else if (header.version == 'B01') {
70
+ const iv = CryptoUtils.md5hex(header.nonce.toString(16).padStart(8, '0') + '5wwh9ikChRjASpMU8cxg7o1d2E').substring(9, 25);
71
+ const decipher = crypto.createDecipheriv('aes-128-cbc', localKey, iv);
72
+ data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
73
+ }
74
+ if (header.protocol == Protocol.map_response) {
75
+ return new ResponseMessage(duid, { dps: { id: 0, result: null } });
76
+ }
77
+ if (header.protocol == Protocol.rpc_response || header.protocol == Protocol.general_request) {
78
+ return this.deserializeRpcResponse(duid, data);
79
+ }
80
+ else {
81
+ this.logger.error('unknown protocol: ' + header.protocol);
82
+ return new ResponseMessage(duid, { dps: { id: 0, result: null } });
83
+ }
84
+ }
85
+ deserializeRpcResponse(duid, data) {
86
+ const payload = JSON.parse(data.payload.toString());
87
+ const dps = payload.dps;
88
+ this.parseJsonInDps(dps, Protocol.general_request);
89
+ this.parseJsonInDps(dps, Protocol.rpc_response);
90
+ return new ResponseMessage(duid, dps);
91
+ }
92
+ parseJsonInDps(dps, index) {
93
+ const indexString = index.toString();
94
+ if (dps[indexString] !== undefined) {
95
+ dps[indexString] = JSON.parse(dps[indexString]);
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,84 @@
1
+ import * as CryptoUtils from './cryptoHelper.js';
2
+ import crypto from 'node:crypto';
3
+ import CRC32 from 'crc-32';
4
+ import { Protocol } from '../broadcast/model/protocol.js';
5
+ export class MessageSerializer {
6
+ context;
7
+ logger;
8
+ sequence = 1;
9
+ supportedVersions = ['1.0', 'A01', 'B01'];
10
+ constructor(context, logger) {
11
+ this.context = context;
12
+ this.logger = logger;
13
+ }
14
+ serialize(duid, request) {
15
+ const messageId = request.messageId;
16
+ const buffer = this.buildBuffer(duid, messageId, request);
17
+ return { messageId: messageId, buffer: buffer };
18
+ }
19
+ buildPayload(messageId, request) {
20
+ const data = {
21
+ id: messageId,
22
+ method: request.method ?? '',
23
+ params: request.params,
24
+ security: undefined,
25
+ result: undefined,
26
+ };
27
+ if (request.secure) {
28
+ data.security = {
29
+ endpoint: this.context.getEndpoint(),
30
+ nonce: this.context.getSerializeNonceAsHex(),
31
+ };
32
+ }
33
+ return {
34
+ dps: {
35
+ [request.protocol]: JSON.stringify(data),
36
+ },
37
+ t: request.timestamp,
38
+ };
39
+ }
40
+ buildBuffer(duid, messageId, request) {
41
+ const version = this.context.getProtocolVersion(duid);
42
+ if (!version || !this.supportedVersions.includes(version)) {
43
+ throw new Error('unknown protocol version ' + version);
44
+ }
45
+ let encrypted;
46
+ if (request.protocol === Protocol.hello_response || request.protocol === Protocol.ping_response) {
47
+ encrypted = Buffer.alloc(0);
48
+ }
49
+ const localKey = this.context.getLocalKey(duid);
50
+ const payloadData = this.buildPayload(messageId, request);
51
+ const payload = JSON.stringify(payloadData);
52
+ if (version == '1.0') {
53
+ const aesKey = CryptoUtils.md5bin(CryptoUtils.encodeTimestamp(payloadData.t) + localKey + CryptoUtils.SALT);
54
+ const cipher = crypto.createCipheriv('aes-128-ecb', aesKey, null);
55
+ encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
56
+ }
57
+ else if (version == 'A01') {
58
+ const encoder = new TextEncoder();
59
+ const iv = CryptoUtils.md5hex(request.nonce.toString(16).padStart(8, '0') + '726f626f726f636b2d67a6d6da').substring(8, 24);
60
+ const cipher = crypto.createCipheriv('aes-128-cbc', encoder.encode(localKey), iv);
61
+ encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
62
+ }
63
+ else if (version == 'B01') {
64
+ const encoder = new TextEncoder();
65
+ const iv = CryptoUtils.md5hex(request.nonce.toString(16).padStart(8, '0') + '5wwh9ikChRjASpMU8cxg7o1d2E').substring(9, 25);
66
+ const cipher = crypto.createCipheriv('aes-128-cbc', encoder.encode(localKey), iv);
67
+ encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
68
+ }
69
+ else {
70
+ throw new Error('unable to build the message: unsupported protocol version: ' + version);
71
+ }
72
+ const msg = Buffer.alloc(23 + encrypted.length);
73
+ msg.write(version);
74
+ msg.writeUint32BE(this.sequence++ & 0xffffffff, 3);
75
+ msg.writeUint32BE(request.nonce & 0xffffffff, 7);
76
+ msg.writeUint32BE(request.timestamp, 11);
77
+ msg.writeUint16BE(request.protocol, 15);
78
+ msg.writeUint16BE(encrypted.length, 17);
79
+ encrypted.copy(msg, 19);
80
+ const crc32 = CRC32.buf(msg.subarray(0, msg.length - 4)) >>> 0;
81
+ msg.writeUint32BE(crc32, msg.length - 4);
82
+ return msg;
83
+ }
84
+ }
@@ -0,0 +1,66 @@
1
+ const token = '%[a-f0-9]{2}';
2
+ const singleMatcher = new RegExp('(' + token + ')|([^%]+?)', 'gi');
3
+ const multiMatcher = new RegExp('(' + token + ')+', 'gi');
4
+ function decodeComponents(components, split = undefined) {
5
+ try {
6
+ return [decodeURIComponent(components.join(''))];
7
+ }
8
+ catch {
9
+ }
10
+ if (components.length === 1) {
11
+ return components;
12
+ }
13
+ split = split || 1;
14
+ const left = components.slice(0, split);
15
+ const right = components.slice(split);
16
+ return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right));
17
+ }
18
+ function decode(input) {
19
+ try {
20
+ return decodeURIComponent(input);
21
+ }
22
+ catch {
23
+ let tokens = input.match(singleMatcher) || [];
24
+ for (let i = 1; i < tokens.length; i++) {
25
+ input = decodeComponents(tokens, i).join('');
26
+ tokens = input.match(singleMatcher) || [];
27
+ }
28
+ return input;
29
+ }
30
+ }
31
+ function customDecodeURIComponent(input) {
32
+ const replaceMap = {
33
+ '%FE%FF': '\uFFFD\uFFFD',
34
+ '%FF%FE': '\uFFFD\uFFFD',
35
+ };
36
+ let match = multiMatcher.exec(input);
37
+ while (match) {
38
+ try {
39
+ replaceMap[match[0]] = decodeURIComponent(match[0]);
40
+ }
41
+ catch {
42
+ const result = decode(match[0]);
43
+ if (result !== match[0]) {
44
+ replaceMap[match[0]] = result;
45
+ }
46
+ }
47
+ match = multiMatcher.exec(input);
48
+ }
49
+ replaceMap['%C2'] = '\uFFFD';
50
+ const entries = Object.keys(replaceMap);
51
+ for (const key of entries) {
52
+ input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
53
+ }
54
+ return input;
55
+ }
56
+ export default function decodeComponent(encodedURI) {
57
+ if (encodedURI === undefined) {
58
+ return undefined;
59
+ }
60
+ try {
61
+ return decodeURIComponent(encodedURI);
62
+ }
63
+ catch {
64
+ return customDecodeURIComponent(encodedURI);
65
+ }
66
+ }
@@ -0,0 +1,16 @@
1
+ export class Sequence {
2
+ min;
3
+ max;
4
+ current;
5
+ constructor(min, max) {
6
+ this.min = min;
7
+ this.max = max;
8
+ this.current = min;
9
+ }
10
+ next() {
11
+ if (this.current > this.max) {
12
+ this.current = this.min;
13
+ }
14
+ return this.current++;
15
+ }
16
+ }
@@ -0,0 +1,13 @@
1
+ export { RoborockAuthenticateApi } from './RESTAPI/roborockAuthenticateApi.js';
2
+ export { RoborockIoTApi } from './RESTAPI/roborockIoTApi.js';
3
+ export { MessageProcessor } from './broadcast/messageProcessor.js';
4
+ export { VacuumErrorCode } from './Zenum/vacuumAndDockErrorCode.js';
5
+ export { RequestMessage } from './broadcast/model/requestMessage.js';
6
+ export { Protocol } from './broadcast/model/protocol.js';
7
+ export { ClientRouter } from './broadcast/clientRouter.js';
8
+ export { DeviceStatus } from './Zmodel/deviceStatus.js';
9
+ export { ResponseMessage } from './broadcast/model/responseMessage.js';
10
+ export { MapInfo } from './Zmodel/mapInfo.js';
11
+ export { AdditionalPropCode } from './Zenum/additionalPropCode.js';
12
+ export { AuthenticateResponseCode } from './Zenum/authenticateResponseCode.js';
13
+ export { Scene } from './Zmodel/scene.js';