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,133 @@
1
+ import { AnsiLogger } from 'matterbridge/logger';
2
+ import RoborockService from '../roborockService';
3
+
4
+ describe('RoborockService - listDevices', () => {
5
+ let roborockService: RoborockService;
6
+ let mockLogger: AnsiLogger;
7
+ let mockIotApi: any;
8
+ let mockLoginApi: any;
9
+
10
+ beforeEach(() => {
11
+ mockLogger = { debug: jest.fn(), error: jest.fn() } as any;
12
+ mockIotApi = {
13
+ getHome: jest.fn(),
14
+ getScenes: jest.fn(),
15
+ getHomev3: jest.fn(),
16
+ getHomev2: jest.fn(),
17
+ };
18
+ mockLoginApi = {
19
+ getHomeDetails: jest.fn(),
20
+ };
21
+ roborockService = new RoborockService(
22
+ () => mockLoginApi,
23
+ () => mockIotApi,
24
+ 10,
25
+ {} as any,
26
+ mockLogger,
27
+ );
28
+ roborockService['iotApi'] = mockIotApi;
29
+ roborockService['userdata'] = { foo: 'bar' } as any;
30
+ });
31
+
32
+ it('should throw if homeDetails is missing', async () => {
33
+ mockLoginApi.getHomeDetails.mockResolvedValue(undefined);
34
+ await expect(roborockService.listDevices('user')).rejects.toThrow('Failed to retrieve the home details');
35
+ });
36
+
37
+ it('should return empty array if homeData is missing', async () => {
38
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
39
+ mockIotApi.getHome.mockResolvedValue(undefined);
40
+ const result = await roborockService.listDevices('user');
41
+ expect(result).toEqual([]);
42
+ });
43
+
44
+ it('should return devices with correct mapping', async () => {
45
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
46
+ mockIotApi.getHome.mockResolvedValue({
47
+ products: [{ id: 'p1', model: 'm1', category: 'cat1' }],
48
+ devices: [{ duid: 'd1', localKey: 'lk', pv: 'pv', sn: 'sn', productId: 'p1', deviceStatus: { battery: 50 }, fv: 'fw', data: {} }],
49
+ receivedDevices: [],
50
+ rooms: [{ id: 1 }],
51
+ });
52
+ mockIotApi.getScenes.mockResolvedValue([{ param: JSON.stringify({ action: { items: [{ entityId: 'd1' }] } }) }]);
53
+ const result = await roborockService.listDevices('user');
54
+ expect(result.length).toBe(1);
55
+ expect(result[0].duid).toBe('d1');
56
+ expect(result[0].data.batteryLevel).toBe(100);
57
+ expect(result[0].scenes.length).toBe(1);
58
+ });
59
+
60
+ it('should throw if getHomev3 fails when v3 API is needed', async () => {
61
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
62
+ mockIotApi.getHome.mockResolvedValue({
63
+ products: [{ id: 'p1', model: 'roborock.vacuum.ss07' }],
64
+ devices: [],
65
+ receivedDevices: [],
66
+ rooms: [],
67
+ });
68
+ mockIotApi.getHomev3.mockResolvedValue(undefined);
69
+ await expect(roborockService.listDevices('user')).rejects.toThrow('Failed to retrieve the home data from v3 API');
70
+ });
71
+
72
+ it('should merge v3 devices and receivedDevices if v3 API is needed', async () => {
73
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
74
+ mockIotApi.getHome.mockResolvedValue({
75
+ products: [{ id: 'p1', model: 'roborock.vacuum.ss07' }],
76
+ devices: [],
77
+ receivedDevices: [],
78
+ rooms: [],
79
+ });
80
+ mockIotApi.getHomev3.mockResolvedValue({
81
+ devices: [{ duid: 'd2', localKey: 'lk2', pv: 'pv2', sn: 'sn2', productId: 'p1', deviceStatus: {}, fv: 'fw2', data: {} }],
82
+ receivedDevices: [{ duid: 'rd2', localKey: 'lk3', pv: 'pv3', sn: 'sn3', productId: 'p1', deviceStatus: {}, fv: 'fw3', data: {} }],
83
+ rooms: [],
84
+ });
85
+ mockIotApi.getScenes.mockResolvedValue([]);
86
+ const result = await roborockService.listDevices('user');
87
+ expect(result.some((d) => d.duid === 'd2')).toBe(true);
88
+ expect(result.some((d) => d.duid === 'rd2')).toBe(true);
89
+ });
90
+
91
+ it('should fallback batteryLevel to 100 if not present', async () => {
92
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
93
+ mockIotApi.getHome.mockResolvedValue({
94
+ products: [{ id: 'p1', model: 'm1', category: 'cat1' }],
95
+ devices: [{ duid: 'd1', localKey: 'lk', pv: 'pv', sn: 'sn', productId: 'p1', deviceStatus: {}, fv: 'fw', data: {} }],
96
+ receivedDevices: [],
97
+ rooms: [{ id: 1 }],
98
+ });
99
+ mockIotApi.getScenes.mockResolvedValue([]);
100
+ const result = await roborockService.listDevices('user');
101
+ expect(result[0].data.batteryLevel).toBe(100);
102
+ });
103
+
104
+ it('should filter scenes correctly for devices', async () => {
105
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
106
+ mockIotApi.getHome.mockResolvedValue({
107
+ products: [{ id: 'p1', model: 'm1', category: 'cat1' }],
108
+ devices: [{ duid: 'd1', localKey: 'lk', pv: 'pv', sn: 'sn', productId: 'p1', deviceStatus: {}, fv: 'fw', data: {} }],
109
+ receivedDevices: [],
110
+ rooms: [{ id: 1 }],
111
+ });
112
+ mockIotApi.getScenes.mockResolvedValue([
113
+ { param: JSON.stringify({ action: { items: [{ entityId: 'd1' }, { entityId: 'd2' }] } }) },
114
+ { param: JSON.stringify({ action: { items: [{ entityId: 'd2' }] } }) },
115
+ ]);
116
+ const result = await roborockService.listDevices('user');
117
+ expect(result[0].scenes.length).toBe(1);
118
+ });
119
+
120
+ it('should handle rooms fallback from v2 and v3 APIs', async () => {
121
+ mockLoginApi.getHomeDetails.mockResolvedValue({ rrHomeId: 1 });
122
+ mockIotApi.getHome.mockResolvedValue({
123
+ products: [{ id: 'p1', model: 'm1', category: 'cat1' }],
124
+ devices: [],
125
+ receivedDevices: [],
126
+ rooms: [],
127
+ });
128
+ mockIotApi.getHomev2.mockResolvedValue({ rooms: [{ id: 2 }], products: [], devices: [], receivedDevices: [] });
129
+ mockIotApi.getScenes.mockResolvedValue([]);
130
+ const result = await roborockService.listDevices('user');
131
+ expect(result.length).toBe(0);
132
+ });
133
+ });
@@ -0,0 +1,76 @@
1
+ import RoborockService from '../roborockService';
2
+
3
+ describe('getHomeDataForUpdating', () => {
4
+ let service;
5
+ let mockIotApi;
6
+ let mockLogger;
7
+ const homeid = 123;
8
+ let mockLoginApi: any;
9
+
10
+ beforeEach(() => {
11
+ mockIotApi = {
12
+ getHomev2: jest.fn(),
13
+ getHomev3: jest.fn(),
14
+ getHome: jest.fn(),
15
+ };
16
+ mockLogger = { debug: jest.fn(), error: jest.fn(), warn: jest.fn() };
17
+ mockLoginApi = {
18
+ getHomeDetails: jest.fn(),
19
+ };
20
+ service = new RoborockService(
21
+ () => mockLoginApi,
22
+ () => mockIotApi,
23
+ 10,
24
+ {} as any,
25
+ mockLogger,
26
+ );
27
+ service.iotApi = mockIotApi;
28
+ service.userdata = { user: 'test' };
29
+ });
30
+
31
+ it('returns home data from v2 API when rooms are present', async () => {
32
+ const homeData = {
33
+ products: [{ id: 'p1', model: 'm1' }],
34
+ devices: [{ duid: 'd1', productId: 'p1', sn: 'sn1', fv: '1.0', localKey: 'lk', pv: 'pv', deviceStatus: { 8: 50 } }],
35
+ receivedDevices: [],
36
+ rooms: [{ id: 1 }],
37
+ };
38
+ mockIotApi.getHomev2.mockResolvedValue(homeData);
39
+ const result = await service.getHomeDataForUpdating(homeid);
40
+ expect(result.devices[0].rooms).toEqual(homeData.rooms);
41
+ expect(result.devices[0].data.batteryLevel).toBe(100);
42
+ });
43
+
44
+ it('falls back to v3 API for rooms if v2 rooms are empty', async () => {
45
+ const homeData = {
46
+ products: [{ id: 'p1', model: 'm1' }],
47
+ devices: [{ duid: 'd1', productId: 'p1', sn: 'sn1', fv: '1.0', localKey: 'lk', pv: 'pv', deviceStatus: {} }],
48
+ receivedDevices: [],
49
+ rooms: [],
50
+ };
51
+ const v3Data = { rooms: [{ id: 2 }] };
52
+ mockIotApi.getHomev2.mockResolvedValue(homeData);
53
+ mockIotApi.getHomev3.mockResolvedValue(v3Data);
54
+ const result = await service.getHomeDataForUpdating(homeid);
55
+ expect(result.devices[0].rooms).toEqual(v3Data.rooms);
56
+ });
57
+
58
+ it('falls back to v1 API for rooms if v2 and v3 rooms are empty', async () => {
59
+ const homeData = {
60
+ products: [{ id: 'p1', model: 'm1' }],
61
+ devices: [{ duid: 'd1', productId: 'p1', sn: 'sn1', fv: '1.0', localKey: 'lk', pv: 'pv', deviceStatus: {} }],
62
+ receivedDevices: [],
63
+ rooms: [],
64
+ };
65
+ mockIotApi.getHomev2.mockResolvedValue(homeData);
66
+ mockIotApi.getHomev3.mockResolvedValue({ rooms: [] });
67
+ mockIotApi.getHome.mockResolvedValue({ rooms: [{ id: 3 }] });
68
+ const result = await service.getHomeDataForUpdating(homeid);
69
+ expect(result.devices[0].rooms).toEqual([{ id: 3 }]);
70
+ });
71
+
72
+ it('throws error if home data cannot be retrieved', async () => {
73
+ mockIotApi.getHomev2.mockResolvedValue(undefined);
74
+ await expect(service.getHomeDataForUpdating(homeid)).rejects.toThrow('Failed to retrieve the home data');
75
+ });
76
+ });
@@ -0,0 +1,79 @@
1
+ import RoborockService from '../roborockService';
2
+
3
+ describe('initializeMessageClientForLocal', () => {
4
+ let service: any;
5
+ let mockMessageClient: any;
6
+ let mockLogger: any;
7
+ let mockDevice: any;
8
+ let mockMessageProcessor: any;
9
+ let mockLocalClient: any;
10
+ let mockLoginApi: any;
11
+ let mockIotApi: any;
12
+
13
+ beforeEach(() => {
14
+ mockLogger = { debug: jest.fn(), error: jest.fn(), warn: jest.fn() };
15
+ mockMessageClient = {
16
+ registerClient: jest.fn(),
17
+ isConnected: jest.fn(),
18
+ registerMessageListener: jest.fn(),
19
+ };
20
+ mockDevice = { duid: 'd1', pv: 'pv', data: { model: 'm1' }, localKey: 'lk' };
21
+ mockMessageProcessor = {
22
+ injectLogger: jest.fn(),
23
+ registerListener: jest.fn(),
24
+ getNetworkInfo: jest.fn(),
25
+ };
26
+ mockLocalClient = {
27
+ connect: jest.fn(),
28
+ isConnected: jest.fn(),
29
+ };
30
+ mockLoginApi = {
31
+ getHomeDetails: jest.fn(),
32
+ };
33
+ service = new RoborockService(
34
+ () => mockLoginApi,
35
+ () => mockIotApi,
36
+ 10,
37
+ {} as any,
38
+ mockLogger,
39
+ );
40
+ service.messageClient = mockMessageClient;
41
+ service.deviceNotify = jest.fn();
42
+ service.ipMap = new Map();
43
+ service.localClientMap = new Map();
44
+ service.mqttAlwaysOnDevices = new Map();
45
+ });
46
+
47
+ afterEach(() => {
48
+ jest.restoreAllMocks();
49
+ });
50
+
51
+ it('returns false if messageClient is undefined', async () => {
52
+ service.messageClient = undefined;
53
+ const result = await service.initializeMessageClientForLocal(mockDevice);
54
+ expect(result).toBe(false);
55
+ expect(mockLogger.error).toHaveBeenCalledWith('messageClient not initialized');
56
+ });
57
+
58
+ it('returns true and sets mqttAlwaysOnDevices if device.pv is B01', async () => {
59
+ mockDevice.pv = 'B01';
60
+ const result = await service.initializeMessageClientForLocal(mockDevice);
61
+ expect(result).toBe(true);
62
+ expect(service.mqttAlwaysOnDevices.get(mockDevice.duid)).toBe(true);
63
+ expect(mockLogger.warn).toHaveBeenCalledWith('Device does not support local connection', mockDevice.duid);
64
+ });
65
+
66
+ it('returns false if local client does not connect after attempts', async () => {
67
+ mockMessageProcessor.getNetworkInfo.mockResolvedValue({ ip: '1.2.3.4' });
68
+ jest.spyOn(service, 'getMessageProcessor').mockReturnValue(mockMessageProcessor);
69
+ mockMessageClient.registerClient.mockReturnValue(mockLocalClient);
70
+ mockLocalClient.connect.mockImplementation(() => {
71
+ void 0;
72
+ });
73
+ mockLocalClient.isConnected.mockReturnValue(false);
74
+ service.ipMap.delete(mockDevice.duid);
75
+ const result = await service.initializeMessageClientForLocal(mockDevice);
76
+ expect(result).toBe(false);
77
+ expect(mockLogger.error).toHaveBeenCalledWith('Error requesting network info', expect.any(Error));
78
+ });
79
+ });
@@ -0,0 +1,200 @@
1
+ import { AdditionalPropCode, Protocol } from '../../roborockCommunication/index.js';
2
+ import { handleCloudMessage } from '../../runtimes/handleCloudMessage';
3
+ import { mapInfo, roomData, roomIndexMap, supportedAreas, supportedMaps } from '../testData/mockData.js';
4
+
5
+ // Mocks for dependencies
6
+ const mockUpdateAttribute = jest.fn().mockImplementation((clusterId: number, attributeName: string, value: any, log: any) => {
7
+ log.debug(`Mock updateAttribute called with clusterId: ${clusterId}, attributeName: ${attributeName}, value: ${value}`);
8
+ });
9
+ const mockSetSupportedAreas = jest.fn();
10
+ const mockSetSelectedAreas = jest.fn();
11
+ const mockSetSupportedAreaIndexMap = jest.fn();
12
+ const mockGetCleanModeData = jest.fn();
13
+ const mockUpdateFromMQTTMessage = jest.fn();
14
+ const mockGetRoomMapFromDevice = jest.fn();
15
+ const mockGetSupportedAreas = jest.fn();
16
+
17
+ jest.mock('./src/helper', () => ({
18
+ getRoomMapFromDevice: (...args: any[]) => mockGetRoomMapFromDevice(...args),
19
+ isStatusUpdate: (result: any) => result === 'status',
20
+ }));
21
+ jest.mock('./src/initialData/getSupportedAreas', () => ({
22
+ getSupportedAreas: (...args: any[]) => mockGetSupportedAreas(...args),
23
+ }));
24
+
25
+ const duid = 'test-duid';
26
+ const robot = {
27
+ updateAttribute: mockUpdateAttribute,
28
+ device: {
29
+ data: { model: 'test-model' },
30
+ rooms: [
31
+ { id: 11100845, name: 'Kitchen' },
32
+ { id: 11100849, name: 'Study' },
33
+ { id: 11100842, name: 'Living room' },
34
+ { id: 11100847, name: 'Bedroom' },
35
+ { id: 12469150, name: 'Dining room' },
36
+ { id: 12461114, name: 'Guest bedroom' },
37
+ { id: 12461109, name: 'Master bedroom' },
38
+ { id: 12461111, name: 'Balcony' },
39
+ { id: 11100842, name: 'Living room' },
40
+ ],
41
+ },
42
+ dockStationStatus: {},
43
+ };
44
+ const platform = {
45
+ robots: new Map([[duid, robot]]),
46
+ log: {
47
+ error: jest.fn(),
48
+ debug: jest.fn(),
49
+ notice: jest.fn(),
50
+ /* eslint-disable no-console */
51
+ fatal: jest.fn().mockImplementation((message: string, ...arg: unknown[]) => console.info(message, ...arg)),
52
+ },
53
+ roborockService: {
54
+ getCleanModeData: mockGetCleanModeData,
55
+ setSupportedAreas: mockSetSupportedAreas,
56
+ setSelectedAreas: mockSetSelectedAreas,
57
+ setSupportedAreaIndexMap: mockSetSupportedAreaIndexMap,
58
+ getMapInformation: jest.fn().mockResolvedValue(mapInfo),
59
+ getRoomMappings: jest.fn().mockResolvedValue(roomData),
60
+ },
61
+ enableExperimentalFeature: {
62
+ enableExperimentalFeature: true,
63
+ advancedFeature: {
64
+ enableMultipleMap: true,
65
+ },
66
+ },
67
+ };
68
+ const runner = { updateFromMQTTMessage: mockUpdateFromMQTTMessage };
69
+
70
+ describe('handleCloudMessage', () => {
71
+ beforeEach(() => {
72
+ jest.clearAllMocks();
73
+ });
74
+
75
+ it('handles status_update', async () => {
76
+ const data = { duid: 'test-duid', dps: { 121: 6, 128: 3, 139: 5 } };
77
+ await handleCloudMessage(data as any, platform as any, runner as any, duid);
78
+ await new Promise(process.nextTick);
79
+ expect(mockUpdateAttribute).toHaveBeenCalled();
80
+ });
81
+
82
+ it('handles rpc_response with status', async () => {
83
+ const data = {
84
+ duid: 'test-duid',
85
+ dps: {
86
+ 102: {
87
+ id: 19937,
88
+ result: [
89
+ {
90
+ msg_ver: 2,
91
+ msg_seq: 2609,
92
+ state: 5,
93
+ battery: 99,
94
+ clean_time: 12,
95
+ clean_area: 0,
96
+ error_code: 0,
97
+ map_present: 1,
98
+ in_cleaning: 1,
99
+ in_returning: 0,
100
+ in_fresh_state: 0,
101
+ lab_status: 3,
102
+ water_box_status: 1,
103
+ fan_power: 101,
104
+ dnd_enabled: 0,
105
+ map_status: 3,
106
+ is_locating: 0,
107
+ lock_status: 0,
108
+ water_box_mode: 202,
109
+ distance_off: 60,
110
+ water_box_carriage_status: 0,
111
+ mop_forbidden_enable: 0,
112
+ adbumper_status: [0, 0, 0],
113
+ dock_type: 5,
114
+ dust_collection_status: 0,
115
+ auto_dust_collection: 1,
116
+ debug_mode: 0,
117
+ switch_map_mode: 0,
118
+ dock_error_status: 0,
119
+ charge_status: 1,
120
+ },
121
+ ],
122
+ },
123
+ },
124
+ };
125
+
126
+ await handleCloudMessage(data as any, platform as any, runner as any, duid);
127
+ await new Promise(process.nextTick);
128
+ expect(mockUpdateFromMQTTMessage).toHaveBeenCalled();
129
+ });
130
+
131
+ it('handles additional_props with map_change', async () => {
132
+ mockGetRoomMapFromDevice.mockResolvedValueOnce({});
133
+ mockGetSupportedAreas.mockReturnValueOnce({
134
+ supportedAreas,
135
+ supportedMaps,
136
+ roomIndexMap,
137
+ });
138
+ const data = { dps: { [Protocol.additional_props]: AdditionalPropCode.map_change } };
139
+ await handleCloudMessage(data as any, platform as any, runner as any, duid);
140
+
141
+ await new Promise(process.nextTick);
142
+ expect(mockSetSupportedAreas).toHaveBeenCalled();
143
+ expect(mockSetSelectedAreas).toHaveBeenCalled();
144
+ expect(mockUpdateAttribute).toHaveBeenCalled();
145
+ });
146
+
147
+ it('logs error if robot not found', async () => {
148
+ const fakePlatform = { ...platform, robots: new Map() };
149
+ const data = { dps: { [Protocol.status_update]: 1 } };
150
+ await handleCloudMessage(data as any, fakePlatform as any, runner as any, duid);
151
+ await new Promise(process.nextTick);
152
+ expect(platform.log.error).toHaveBeenCalled();
153
+ });
154
+
155
+ it('handles unknown message type', async () => {
156
+ const data = { dps: { 999: 42 } };
157
+ await handleCloudMessage(data as any, platform as any, runner as any, duid);
158
+ await new Promise(process.nextTick);
159
+ expect(platform.log.notice).toHaveBeenCalled();
160
+ });
161
+
162
+ it('handles map_change', async () => {
163
+ const data = { dps: { 128: 4 } };
164
+ mockGetRoomMapFromDevice.mockReturnValue({
165
+ rooms: [
166
+ { id: 1, globalId: 11100845, displayName: 'Kitchen', alternativeId: '114', mapId: 0 },
167
+ { id: 2, globalId: 11100849, displayName: 'Study', alternativeId: '29', mapId: 0 },
168
+ { id: 3, globalId: 11100842, displayName: 'Living room', alternativeId: '36', mapId: 0 },
169
+ { id: 4, globalId: 11100847, displayName: 'Bedroom', alternativeId: '41', mapId: 0 },
170
+ ],
171
+ mapInfo: [
172
+ {
173
+ id: 0,
174
+ name: 'First Map',
175
+ rooms: [
176
+ { id: 1, globalId: 11100845, iot_name_id: '11100845', tag: 14, displayName: 'Kitchen', mapId: 0 },
177
+ { id: 2, globalId: 11100849, iot_name_id: '11100849', tag: 9, displayName: 'Study', mapId: 0 },
178
+ { id: 3, globalId: 11100842, iot_name_id: '11100842', tag: 6, displayName: 'Living room', mapId: 0 },
179
+ { id: 4, globalId: 11100847, iot_name_id: '11100847', tag: 1, displayName: 'Bedroom', mapId: 0 },
180
+ ],
181
+ },
182
+ {
183
+ id: 1,
184
+ name: 'Second Map',
185
+ rooms: [
186
+ { id: 1, globalId: 12469150, iot_name_id: '12469150', tag: 13, displayName: 'Dining room', mapId: 1 },
187
+ { id: 2, globalId: 12461114, iot_name_id: '12461114', tag: 3, displayName: 'Guest bedroom', mapId: 1 },
188
+ { id: 3, globalId: 12461109, iot_name_id: '12461109', tag: 2, displayName: 'Master bedroom', mapId: 1 },
189
+ { id: 4, globalId: 12461111, iot_name_id: '12461111', tag: 7, displayName: 'Balcony', mapId: 1 },
190
+ { id: 5, globalId: 11100842, iot_name_id: '11100842', tag: 6, displayName: 'Living room', mapId: 1 },
191
+ ],
192
+ },
193
+ ],
194
+ });
195
+
196
+ await handleCloudMessage(data as any, platform as any, runner as any, duid);
197
+ await new Promise(process.nextTick);
198
+ expect(platform.log.notice).toHaveBeenCalled();
199
+ });
200
+ });
@@ -0,0 +1,54 @@
1
+ import { DeviceData } from '../../roborockCommunication/Zmodel/device';
2
+ import { updateFromHomeData } from '../../runtimes/handleHomeDataMessage';
3
+ import { homeData } from '../testData/mockData';
4
+ import { PowerSource, RvcRunMode } from 'matterbridge/matter/clusters';
5
+
6
+ // Mocks
7
+ const mockUpdateAttribute = jest.fn();
8
+ const duid = 'test-duid';
9
+ const robot = {
10
+ updateAttribute: mockUpdateAttribute,
11
+ device: { data: { model: 'test-model' } as DeviceData | undefined },
12
+ dockStationStatus: {},
13
+ };
14
+ const platform = {
15
+ robots: new Map([[duid, robot]]),
16
+ log: {
17
+ error: jest.fn(),
18
+ debug: jest.fn(),
19
+ notice: jest.fn(),
20
+ /* eslint-disable no-console */
21
+ fatal: jest.fn().mockImplementation((message: string, ...arg: unknown[]) => console.info(message, ...arg)),
22
+ },
23
+ roborockService: {},
24
+ enableExperimentalFeature: {},
25
+ };
26
+
27
+ describe('updateFromHomeData', () => {
28
+ beforeEach(() => {
29
+ jest.clearAllMocks();
30
+ });
31
+
32
+ it('should update robot attributes when valid data is provided', async () => {
33
+ await updateFromHomeData(homeData, platform as any);
34
+
35
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(PowerSource.Cluster.id, 'batPercentRemaining', 200, expect.anything());
36
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(PowerSource.Cluster.id, 'batChargeLevel', 0, expect.anything());
37
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(RvcRunMode.Cluster.id, 'currentMode', 1, expect.anything());
38
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(PowerSource.Cluster.id, 'batChargeState', PowerSource.BatChargeState.IsAtFullCharge, expect.anything());
39
+ expect(platform.log.error).not.toHaveBeenCalled();
40
+ });
41
+
42
+ it('should log error if robot is not found', async () => {
43
+ platform.robots.clear();
44
+ await updateFromHomeData(homeData, platform as any);
45
+ expect(platform.log.error).not.toHaveBeenCalledWith(expect.stringContaining('Robot with DUID'));
46
+ });
47
+
48
+ it('should log error if device data is undefined', async () => {
49
+ platform.robots.clear();
50
+ platform.robots.set('test-duid', { ...robot, device: { data: undefined } });
51
+ await updateFromHomeData(homeData, platform as any);
52
+ expect(platform.log.error).toHaveBeenCalledWith('Device not found in home data');
53
+ });
54
+ });