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,227 @@
1
+ import { handleLocalMessage } from '../../runtimes/handleLocalMessage';
2
+ import { OperationStatusCode } from '../../roborockCommunication/Zenum/operationStatusCode';
3
+ import { ServiceArea, PowerSource } from 'matterbridge/matter/clusters';
4
+ import { cloudMessageResult1, cloudMessageResult2, cloudMessageResult3, mapInfo } from '../testData/mockData';
5
+ import { RoomIndexMap } from '../../model/roomIndexMap';
6
+
7
+ // Mocks
8
+ const mockUpdateAttribute = jest.fn();
9
+ const mockGetSupportedAreas = jest.fn();
10
+ const mockGetSupportedAreasIndexMap = jest.fn();
11
+ const mockGetRoomMap = jest.fn();
12
+
13
+ jest.mock('./src/helper', () => ({
14
+ getRoomMap: mockGetRoomMap,
15
+ }));
16
+
17
+ const mockLog = {
18
+ error: jest.fn(),
19
+ debug: jest.fn(),
20
+ notice: jest.fn(),
21
+ info: jest.fn(),
22
+ warn: jest.fn(),
23
+ /* eslint-disable no-console */
24
+ fatal: jest.fn().mockImplementation((message: string, ...arg: unknown[]) => console.info(message, ...arg)),
25
+ };
26
+
27
+ const getMockRobot = () => ({
28
+ device: { data: { model: 'test-model' } },
29
+ updateAttribute: mockUpdateAttribute,
30
+ getAttribute: jest.fn(() => undefined),
31
+ });
32
+
33
+ describe('handleLocalMessage -- FF ON', () => {
34
+ const getMockPlatform = (robotExists = true) => ({
35
+ robots: new Map(robotExists ? [['duid1', getMockRobot()]] : []),
36
+ log: mockLog,
37
+ roborockService: {
38
+ getSelectedAreas: jest.fn(() => ['area1']),
39
+ getSupportedAreas: mockGetSupportedAreas,
40
+ getSupportedAreasIndexMap: mockGetSupportedAreasIndexMap,
41
+ getMapInformation: jest.fn().mockReturnValue(mapInfo),
42
+ },
43
+ enableExperimentalFeature: {
44
+ enableExperimentalFeature: true,
45
+ advancedFeature: {
46
+ enableMultipleMap: true,
47
+ },
48
+ },
49
+ });
50
+
51
+ beforeEach(() => {
52
+ jest.clearAllMocks();
53
+ });
54
+
55
+ it('logs error if robot not found', async () => {
56
+ const platform = getMockPlatform(false);
57
+ await handleLocalMessage({ state: 0 } as any, platform as any, 'duid1');
58
+ expect(mockLog.error).toHaveBeenCalledWith('Robot with DUID duid1 not found.');
59
+ });
60
+
61
+ it('updates selectedAreas on Idle', async () => {
62
+ const platform = getMockPlatform();
63
+ await handleLocalMessage({ state: OperationStatusCode.Idle } as any, platform as any, 'duid1');
64
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'selectedAreas', ['area1'], mockLog);
65
+ });
66
+
67
+ it('updates currentArea and selectedAreas to null/[] when cleaning_info is missing', async () => {
68
+ const platform = getMockPlatform();
69
+ await handleLocalMessage({ state: 5 } as any, platform as any, 'duid1');
70
+ await new Promise(process.nextTick);
71
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', null, mockLog);
72
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'selectedAreas', [], mockLog);
73
+ });
74
+
75
+ it('updates battery attributes if battery present', async () => {
76
+ const platform = getMockPlatform();
77
+ await handleLocalMessage({ state: 0, battery: 50 } as any, platform as any, 'duid1');
78
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(PowerSource.Cluster.id, 'batPercentRemaining', 100, mockLog);
79
+ });
80
+
81
+ it('updates currentArea based on segment_id', async () => {
82
+ const platform = getMockPlatform();
83
+
84
+ // currentMappedAreas
85
+ mockGetSupportedAreas.mockReturnValue([
86
+ { areaId: 100, mapId: 0, areaInfo: { locationInfo: { locationName: 'Kitchen', floorNumber: 0, areaType: null }, landmarkInfo: null } },
87
+ { areaId: 101, mapId: 0, areaInfo: { locationInfo: { locationName: 'Study', floorNumber: 0, areaType: null }, landmarkInfo: null } },
88
+ { areaId: 102, mapId: 0, areaInfo: { locationInfo: { locationName: 'Living room', floorNumber: 0, areaType: null }, landmarkInfo: null } },
89
+ { areaId: 103, mapId: 0, areaInfo: { locationInfo: { locationName: 'Bedroom', floorNumber: 0, areaType: null }, landmarkInfo: null } },
90
+ { areaId: 104, mapId: 1, areaInfo: { locationInfo: { locationName: 'Dining room', floorNumber: 1, areaType: null }, landmarkInfo: null } },
91
+ { areaId: 105, mapId: 1, areaInfo: { locationInfo: { locationName: 'Guest bedroom', floorNumber: 1, areaType: null }, landmarkInfo: null } },
92
+ { areaId: 106, mapId: 1, areaInfo: { locationInfo: { locationName: 'Master bedroom', floorNumber: 1, areaType: null }, landmarkInfo: null } },
93
+ { areaId: 107, mapId: 1, areaInfo: { locationInfo: { locationName: 'Balcony', floorNumber: 1, areaType: null }, landmarkInfo: null } },
94
+ { areaId: 108, mapId: 1, areaInfo: { locationInfo: { locationName: 'Living room', floorNumber: 1, areaType: null }, landmarkInfo: null } },
95
+ ]);
96
+
97
+ const roomIndexMap = new RoomIndexMap(
98
+ new Map([
99
+ [100, { roomId: 1, mapId: 0 }],
100
+ [101, { roomId: 2, mapId: 0 }],
101
+ [102, { roomId: 3, mapId: 0 }],
102
+ [103, { roomId: 4, mapId: 0 }],
103
+ [104, { roomId: 1, mapId: 1 }],
104
+ [105, { roomId: 2, mapId: 1 }],
105
+ [106, { roomId: 3, mapId: 1 }],
106
+ [107, { roomId: 4, mapId: 1 }],
107
+ [108, { roomId: 5, mapId: 1 }],
108
+ ]),
109
+ );
110
+
111
+ // roomIndexMap
112
+ mockGetSupportedAreasIndexMap.mockReturnValue(roomIndexMap);
113
+
114
+ await handleLocalMessage(cloudMessageResult3, platform as any, 'duid1');
115
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', null, mockLog);
116
+ expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining('No cleaning_info'));
117
+
118
+ await handleLocalMessage(cloudMessageResult1 as any, platform as any, 'duid1');
119
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', 102, mockLog);
120
+
121
+ await handleLocalMessage(cloudMessageResult2 as any, platform as any, 'duid1');
122
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', 103, mockLog);
123
+ });
124
+ });
125
+
126
+ describe('handleLocalMessage -- FF OFF', () => {
127
+ const getMockPlatform = (robotExists = true) => ({
128
+ robots: new Map(robotExists ? [['duid1', getMockRobot()]] : []),
129
+ log: mockLog,
130
+ roborockService: {
131
+ getSelectedAreas: jest.fn(() => ['area1']),
132
+ getSupportedAreas: mockGetSupportedAreas,
133
+ getSupportedAreasIndexMap: mockGetSupportedAreasIndexMap,
134
+ getMapInformation: jest.fn().mockReturnValue(mapInfo),
135
+ },
136
+ enableExperimentalFeature: {
137
+ enableExperimentalFeature: false,
138
+ advancedFeature: {
139
+ enableMultipleMap: false,
140
+ },
141
+ },
142
+ });
143
+
144
+ beforeEach(() => {
145
+ jest.clearAllMocks();
146
+ });
147
+
148
+ it('logs error if robot not found', async () => {
149
+ const platform = getMockPlatform(false);
150
+ await handleLocalMessage({ state: 0 } as any, platform as any, 'duid1');
151
+ expect(mockLog.error).toHaveBeenCalledWith('Robot with DUID duid1 not found.');
152
+ });
153
+
154
+ it('updates selectedAreas on Idle', async () => {
155
+ const platform = getMockPlatform();
156
+ await handleLocalMessage({ state: OperationStatusCode.Idle } as any, platform as any, 'duid1');
157
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'selectedAreas', ['area1'], mockLog);
158
+ });
159
+
160
+ it('updates currentArea and selectedAreas to null/[] when cleaning_info is missing', async () => {
161
+ const platform = getMockPlatform();
162
+ await handleLocalMessage({ state: 5 } as any, platform as any, 'duid1');
163
+ await new Promise(process.nextTick);
164
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', null, mockLog);
165
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'selectedAreas', [], mockLog);
166
+ });
167
+
168
+ it('updates battery attributes if battery present', async () => {
169
+ const platform = getMockPlatform();
170
+ await handleLocalMessage({ state: 0, battery: 50 } as any, platform as any, 'duid1');
171
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(PowerSource.Cluster.id, 'batPercentRemaining', 100, mockLog);
172
+ });
173
+
174
+ it('updates currentArea based on segment_id', async () => {
175
+ const platform = getMockPlatform();
176
+ // currentMappedAreas
177
+ mockGetSupportedAreas.mockReturnValue([
178
+ { areaId: 1, mapId: 0, areaInfo: { locationInfo: { locationName: 'Kitchen', floorNumber: 0, areaType: null }, landmarkInfo: null } },
179
+ { areaId: 2, mapId: 0, areaInfo: { locationInfo: { locationName: 'Study', floorNumber: 0, areaType: null }, landmarkInfo: null } },
180
+ { areaId: 3, mapId: 0, areaInfo: { locationInfo: { locationName: 'Living room', floorNumber: 0, areaType: null }, landmarkInfo: null } },
181
+ { areaId: 4, mapId: 0, areaInfo: { locationInfo: { locationName: 'Bedroom', floorNumber: 0, areaType: null }, landmarkInfo: null } },
182
+ ]);
183
+
184
+ // roomIndexMap
185
+ mockGetSupportedAreasIndexMap.mockReturnValue({
186
+ indexMap: new Map([
187
+ [1, { roomId: 1, mapId: 0 }],
188
+ [2, { roomId: 2, mapId: 0 }],
189
+ [3, { roomId: 3, mapId: 0 }],
190
+ [4, { roomId: 4, mapId: 0 }],
191
+ ]),
192
+ roomMap: new Map([
193
+ [1, 1],
194
+ [2, 2],
195
+ [3, 3],
196
+ [4, 4],
197
+ ]),
198
+
199
+ getAreaId(roomId: number): Area | undefined {
200
+ const index = this.roomMap.get(roomId);
201
+ if (index === undefined) {
202
+ return undefined;
203
+ }
204
+ return { roomId: index, mapId: this.indexMap.get(index).mapId };
205
+ },
206
+
207
+ getRoomId(areaId: number): number | undefined {
208
+ return this.indexMap.get(areaId)?.roomId;
209
+ },
210
+ });
211
+
212
+ await handleLocalMessage(cloudMessageResult3 as any, platform as any, 'duid1');
213
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', null, mockLog);
214
+ expect(mockLog.debug).toHaveBeenCalledWith(expect.stringContaining('No cleaning_info'));
215
+
216
+ await handleLocalMessage(cloudMessageResult1 as any, platform as any, 'duid1');
217
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', 3, mockLog);
218
+
219
+ await handleLocalMessage(cloudMessageResult2 as any, platform as any, 'duid1');
220
+ expect(mockUpdateAttribute).toHaveBeenCalledWith(ServiceArea.Cluster.id, 'currentArea', 4, mockLog);
221
+ });
222
+ });
223
+
224
+ interface Area {
225
+ roomId: number;
226
+ mapId: number;
227
+ }
@@ -0,0 +1,370 @@
1
+ import { ServiceArea } from 'matterbridge/matter/clusters';
2
+ import { Home, MapInfo } from '../../roborockCommunication/index.js';
3
+ import { CloudMessageResult } from '../../roborockCommunication/Zmodel/messageResult.js';
4
+
5
+ export const supportedAreas: ServiceArea.Area[] = [
6
+ { areaId: 100, mapId: 0, areaInfo: { locationInfo: { locationName: 'Kitchen', floorNumber: 0, areaType: null }, landmarkInfo: null } },
7
+ { areaId: 101, mapId: 0, areaInfo: { locationInfo: { locationName: 'Study', floorNumber: 0, areaType: null }, landmarkInfo: null } },
8
+ { areaId: 102, mapId: 0, areaInfo: { locationInfo: { locationName: 'Living room', floorNumber: 0, areaType: null }, landmarkInfo: null } },
9
+ { areaId: 103, mapId: 0, areaInfo: { locationInfo: { locationName: 'Bedroom', floorNumber: 0, areaType: null }, landmarkInfo: null } },
10
+ { areaId: 104, mapId: 1, areaInfo: { locationInfo: { locationName: 'Living room', floorNumber: 1, areaType: null }, landmarkInfo: null } },
11
+ { areaId: 105, mapId: 1, areaInfo: { locationInfo: { locationName: 'Guest bedroom', floorNumber: 1, areaType: null }, landmarkInfo: null } },
12
+ { areaId: 106, mapId: 1, areaInfo: { locationInfo: { locationName: 'Master bedroom', floorNumber: 1, areaType: null }, landmarkInfo: null } },
13
+ { areaId: 107, mapId: 1, areaInfo: { locationInfo: { locationName: 'Balcony', floorNumber: 1, areaType: null }, landmarkInfo: null } },
14
+ ];
15
+
16
+ export const supportedMaps: ServiceArea.Map[] = [
17
+ { mapId: 0, name: 'First Map' },
18
+ { mapId: 1, name: 'Second Map' },
19
+ ];
20
+
21
+ export const roomIndexMap = {
22
+ indexMap: new Map([
23
+ [100, { roomId: 1, mapId: 0 }],
24
+ [101, { roomId: 2, mapId: 0 }],
25
+ [102, { roomId: 3, mapId: 0 }],
26
+ [103, { roomId: 4, mapId: 0 }],
27
+ [104, { roomId: 1, mapId: 1 }],
28
+ [105, { roomId: 2, mapId: 1 }],
29
+ [106, { roomId: 3, mapId: 1 }],
30
+ [107, { roomId: 4, mapId: 1 }],
31
+ ]),
32
+ roomMap: new Map([
33
+ [1, 104],
34
+ [2, 105],
35
+ [3, 106],
36
+ [4, 107],
37
+ ]),
38
+ };
39
+
40
+ export const mapInfo = new MapInfo({
41
+ max_multi_map: 1,
42
+ max_bak_map: 1,
43
+ multi_map_count: 1,
44
+ map_info: [
45
+ {
46
+ mapFlag: 0,
47
+ add_time: 1753511673,
48
+ length: 9,
49
+ name: 'First Map',
50
+ bak_maps: [{ mapFlag: 4, add_time: 1753578164 }],
51
+ rooms: [
52
+ { id: 1, tag: 14, iot_name_id: '11100845', iot_name: 'Kitchen' },
53
+ { id: 2, tag: 9, iot_name_id: '11100849', iot_name: 'Study' },
54
+ {
55
+ id: 3,
56
+ tag: 6,
57
+ iot_name_id: '11100842',
58
+ iot_name: 'Living room',
59
+ },
60
+ { id: 4, tag: 1, iot_name_id: '11100847', iot_name: 'Bedroom' },
61
+ ],
62
+ },
63
+ {
64
+ mapFlag: 1,
65
+ add_time: 1753579596,
66
+ length: 10,
67
+ name: 'Second Map',
68
+ bak_maps: [{ mapFlag: 5, add_time: 1753578579 }],
69
+ rooms: [
70
+ {
71
+ id: 1,
72
+ tag: 6,
73
+ iot_name_id: '11100842',
74
+ iot_name: 'Living room',
75
+ },
76
+ {
77
+ id: 2,
78
+ tag: 3,
79
+ iot_name_id: '12461114',
80
+ iot_name: 'Guest bedroom',
81
+ },
82
+ {
83
+ id: 3,
84
+ tag: 2,
85
+ iot_name_id: '12461109',
86
+ iot_name: 'Master bedroom',
87
+ },
88
+ { id: 4, tag: 7, iot_name_id: '12461111', iot_name: 'Balcony' },
89
+ ],
90
+ },
91
+ ],
92
+ });
93
+
94
+ export const roomData = [
95
+ [1, '11100845', 14],
96
+ [2, '11100849', 9],
97
+ [3, '11100842', 6],
98
+ [4, '11100847', 1],
99
+ ];
100
+
101
+ export const cloudMessageResult1: CloudMessageResult = {
102
+ msg_ver: 2,
103
+ msg_seq: 424,
104
+ state: 5,
105
+ battery: 100,
106
+ clean_time: 2823,
107
+ clean_area: 36002500,
108
+ error_code: 0,
109
+ map_present: 1,
110
+ in_cleaning: 0,
111
+ in_returning: 0,
112
+ in_fresh_state: 1,
113
+ lab_status: 3,
114
+ water_box_status: 1,
115
+ back_type: -1,
116
+ wash_phase: 0,
117
+ wash_ready: 1,
118
+ wash_status: 0,
119
+ fan_power: 110,
120
+ dnd_enabled: 0,
121
+ map_status: 3,
122
+ is_locating: 0,
123
+ lock_status: 0,
124
+ water_box_mode: 209,
125
+ distance_off: 155,
126
+ water_box_carriage_status: 1,
127
+ mop_forbidden_enable: 1,
128
+ camera_status: 1,
129
+ is_exploring: 0,
130
+ adbumper_status: [0, 0, 0],
131
+ water_shortage_status: 0,
132
+ dock_type: 14,
133
+ dust_collection_status: 0,
134
+ auto_dust_collection: 1,
135
+ avoid_count: 209,
136
+ mop_mode: 306,
137
+ debug_mode: 0,
138
+ in_warmup: 0,
139
+ collision_avoid_status: 1,
140
+ switch_map_mode: 1,
141
+ dock_error_status: 0,
142
+ charge_status: 1,
143
+ unsave_map_reason: 0,
144
+ unsave_map_flag: -1,
145
+ dry_status: 0,
146
+ rdt: 0,
147
+ clean_percent: 0,
148
+ extra_time: 860,
149
+ rss: 2,
150
+ dss: 168,
151
+ common_status: 2,
152
+ last_clean_t: 1754063701,
153
+ replenish_mode: 0,
154
+ repeat: 1,
155
+ kct: 0,
156
+ subdivision_sets: 0,
157
+ cleaning_info: { target_segment_id: -1, segment_id: 3, fan_power: 102, water_box_status: 202, mop_mode: 306 },
158
+ exit_dock: 0,
159
+ seq_type: 0,
160
+ };
161
+
162
+ export const cloudMessageResult2: CloudMessageResult = {
163
+ msg_ver: 2,
164
+ msg_seq: 424,
165
+ state: 5,
166
+ battery: 100,
167
+ clean_time: 2823,
168
+ clean_area: 36002500,
169
+ error_code: 0,
170
+ map_present: 1,
171
+ in_cleaning: 0,
172
+ in_returning: 0,
173
+ in_fresh_state: 1,
174
+ lab_status: 3,
175
+ water_box_status: 1,
176
+ back_type: -1,
177
+ wash_phase: 0,
178
+ wash_ready: 1,
179
+ wash_status: 0,
180
+ fan_power: 110,
181
+ dnd_enabled: 0,
182
+ map_status: 3,
183
+ is_locating: 0,
184
+ lock_status: 0,
185
+ water_box_mode: 209,
186
+ distance_off: 155,
187
+ water_box_carriage_status: 1,
188
+ mop_forbidden_enable: 1,
189
+ camera_status: 1,
190
+ is_exploring: 0,
191
+ adbumper_status: [0, 0, 0],
192
+ water_shortage_status: 0,
193
+ dock_type: 14,
194
+ dust_collection_status: 0,
195
+ auto_dust_collection: 1,
196
+ avoid_count: 209,
197
+ mop_mode: 306,
198
+ debug_mode: 0,
199
+ in_warmup: 0,
200
+ collision_avoid_status: 1,
201
+ switch_map_mode: 1,
202
+ dock_error_status: 0,
203
+ charge_status: 1,
204
+ unsave_map_reason: 0,
205
+ unsave_map_flag: -1,
206
+ dry_status: 0,
207
+ rdt: 0,
208
+ clean_percent: 0,
209
+ extra_time: 860,
210
+ rss: 2,
211
+ dss: 168,
212
+ common_status: 2,
213
+ last_clean_t: 1754063701,
214
+ replenish_mode: 0,
215
+ repeat: 1,
216
+ kct: 0,
217
+ subdivision_sets: 0,
218
+ cleaning_info: { target_segment_id: 4, segment_id: -1, fan_power: 102, water_box_status: 202, mop_mode: 306 },
219
+ exit_dock: 0,
220
+ seq_type: 0,
221
+ };
222
+
223
+ export const cloudMessageResult3: CloudMessageResult = {
224
+ msg_ver: 2,
225
+ msg_seq: 1579,
226
+ state: 5,
227
+ battery: 94,
228
+ clean_time: 567,
229
+ clean_area: 36002500,
230
+ error_code: 0,
231
+ map_present: 1,
232
+ in_cleaning: 0,
233
+ in_returning: 0,
234
+ in_fresh_state: 1,
235
+ lab_status: 3,
236
+ water_box_status: 1,
237
+ fan_power: 104,
238
+ dnd_enabled: 0,
239
+ map_status: 3,
240
+ is_locating: 0,
241
+ lock_status: 0,
242
+ water_box_mode: 202,
243
+ distance_off: 60,
244
+ water_box_carriage_status: 0,
245
+ mop_forbidden_enable: 0,
246
+ adbumper_status: [0, 0, 0],
247
+ dock_type: 5,
248
+ dust_collection_status: 0,
249
+ auto_dust_collection: 1,
250
+ debug_mode: 0,
251
+ switch_map_mode: 0,
252
+ dock_error_status: 0,
253
+ charge_status: 1,
254
+ };
255
+
256
+ export const homeData: Home = {
257
+ id: 3645093,
258
+ name: 'My Home',
259
+ products: [
260
+ {
261
+ id: '2CjvhDFL7Q9NdJQmhE86zn',
262
+ name: 'Roborock Qrevo Edge Series',
263
+ model: 'test-model',
264
+ category: 'robot.vacuum.cleaner',
265
+ schema: [
266
+ { id: 101, name: 'rpc_request', code: 'rpc_request', mode: 'rw', type: 'RAW', property: null },
267
+ { id: 102, name: 'rpc_response', code: 'rpc_response', mode: 'rw', type: 'RAW', property: null },
268
+ { id: 120, name: '错误代码', code: 'error_code', mode: 'ro', type: 'ENUM', property: '{"range": [""]}' },
269
+ { id: 121, name: '设备状态', code: 'state', mode: 'ro', type: 'ENUM', property: '{"range": [""]}' },
270
+ { id: 122, name: '设备电量', code: 'battery', mode: 'ro', type: 'ENUM', property: '{"range": [""]}' },
271
+ { id: 123, name: '清扫模式', code: 'fan_power', mode: 'rw', type: 'ENUM', property: '{"range": [""]}' },
272
+ { id: 124, name: '拖地模式', code: 'water_box_mode', mode: 'rw', type: 'ENUM', property: '{"range": [""]}' },
273
+ { id: 125, name: '主刷寿命', code: 'main_brush_life', mode: 'rw', type: 'VALUE', property: '{"max": 100, "min": 0, "step": 1, "unit": "null", "scale": 1}' },
274
+ { id: 126, name: '边刷寿命', code: 'side_brush_life', mode: 'rw', type: 'VALUE', property: '{"max": 100, "min": 0, "step": 1, "unit": "null", "scale": 1}' },
275
+ { id: 127, name: '滤网寿命', code: 'filter_life', mode: 'rw', type: 'VALUE', property: '{"max": 100, "min": 0, "step": 1, "unit": "null", "scale": 1}' },
276
+ { id: 128, name: '额外状态', code: 'additional_props', mode: 'ro', type: 'RAW', property: null },
277
+ { id: 130, name: '完成事件', code: 'task_complete', mode: 'ro', type: 'RAW', property: null },
278
+ { id: 131, name: '电量不足任务取消', code: 'task_cancel_low_power', mode: 'ro', type: 'RAW', property: null },
279
+ { id: 132, name: '运动中任务取消', code: 'task_cancel_in_motion', mode: 'ro', type: 'RAW', property: null },
280
+ { id: 133, name: '充电状态', code: 'charge_status', mode: 'ro', type: 'RAW', property: null },
281
+ { id: 134, name: '烘干状态', code: 'drying_status', mode: 'ro', type: 'RAW', property: null },
282
+ { id: 135, name: '离线原因细分', code: 'offline_status', mode: 'ro', type: 'RAW', property: null },
283
+ { id: 139, name: '回基站目的', code: 'back_type', mode: 'ro', type: 'RAW', property: null },
284
+ ],
285
+ },
286
+ ],
287
+ devices: [
288
+ {
289
+ duid: 'test-duid',
290
+ name: 'Roborock Qrevo Edge 5V1',
291
+ activeTime: 1749443275,
292
+ createTime: 1746940587,
293
+ localKey: 'v0OKpWXwBmiCk4ku',
294
+ productId: '2CjvhDFL7Q9NdJQmhE86zn',
295
+ online: true,
296
+ fv: '02.28.34',
297
+ pv: '1.0',
298
+ sn: 'RCIEBS50900224',
299
+ featureSet: '2247397454282751',
300
+ newFeatureSet: '00040040282834C9C2FA8F5C7EDEFFFE',
301
+ deviceStatus: { 120: 0, 121: 8, 122: 100, 123: 110, 124: 209, 125: 93, 126: 69, 127: 86, 128: 0, 133: 1, 134: 0, 135: 0, 139: 0 },
302
+ silentOtaSwitch: true,
303
+ rrHomeId: 3645093,
304
+ rooms: [
305
+ { id: 11100849, name: 'Study' },
306
+ { id: 11100847, name: 'Bedroom' },
307
+ { id: 11100845, name: 'Kitchen' },
308
+ { id: 11100842, name: 'Living room' },
309
+ ],
310
+ serialNumber: 'RCIEBS50900224',
311
+ data: {
312
+ id: 'test-duid',
313
+ firmwareVersion: '02.28.34',
314
+ serialNumber: 'RCIEBS50900224',
315
+ model: 'test-model',
316
+ category: 'robot.vacuum.cleaner',
317
+ batteryLevel: 100,
318
+ },
319
+ store: {
320
+ userData: {
321
+ uid: 3635748,
322
+ tokentype: '',
323
+ token: 'rr65af7107da5840:txP8ZF7dj8v7xUMkoFMzZA==:01981b12f83a7723a1cbef8c8e89a7e1',
324
+ rruid: 'rr65af7107da5840',
325
+ region: 'us',
326
+ countrycode: '84',
327
+ country: 'VN',
328
+ nickname: 'Ryan',
329
+ rriot: {
330
+ u: '6BtaRwE14spvanEazqX0kQ',
331
+ s: 'OsErWk',
332
+ h: '195Xn4u3fe',
333
+ k: 'ofKw7nJc',
334
+ r: { r: 'US', a: 'https://api-us.roborock.com', m: 'ssl://mqtt-us-2.roborock.com:8883', l: 'https://wood-us.roborock.com' },
335
+ },
336
+ },
337
+ localKey: 'v0OKpWXwBmiCk4ku',
338
+ pv: '1.0',
339
+ model: 'test-model',
340
+ },
341
+ schema: [
342
+ { id: 101, name: 'rpc_request', code: 'rpc_request', mode: 'rw', type: 'RAW', property: null },
343
+ { id: 102, name: 'rpc_response', code: 'rpc_response', mode: 'rw', type: 'RAW', property: null },
344
+ { id: 120, name: '错误代码', code: 'error_code', mode: 'ro', type: 'ENUM', property: '{"range": [""]}' },
345
+ { id: 121, name: '设备状态', code: 'state', mode: 'ro', type: 'ENUM', property: '{"range": [""]}' },
346
+ { id: 122, name: '设备电量', code: 'battery', mode: 'ro', type: 'ENUM', property: '{"range": [""]}' },
347
+ { id: 123, name: '清扫模式', code: 'fan_power', mode: 'rw', type: 'ENUM', property: '{"range": [""]}' },
348
+ { id: 124, name: '拖地模式', code: 'water_box_mode', mode: 'rw', type: 'ENUM', property: '{"range": [""]}' },
349
+ { id: 125, name: '主刷寿命', code: 'main_brush_life', mode: 'rw', type: 'VALUE', property: '{"max": 100, "min": 0, "step": 1, "unit": "null", "scale": 1}' },
350
+ { id: 126, name: '边刷寿命', code: 'side_brush_life', mode: 'rw', type: 'VALUE', property: '{"max": 100, "min": 0, "step": 1, "unit": "null", "scale": 1}' },
351
+ { id: 127, name: '滤网寿命', code: 'filter_life', mode: 'rw', type: 'VALUE', property: '{"max": 100, "min": 0, "step": 1, "unit": "null", "scale": 1}' },
352
+ { id: 128, name: '额外状态', code: 'additional_props', mode: 'ro', type: 'RAW', property: null },
353
+ { id: 130, name: '完成事件', code: 'task_complete', mode: 'ro', type: 'RAW', property: null },
354
+ { id: 131, name: '电量不足任务取消', code: 'task_cancel_low_power', mode: 'ro', type: 'RAW', property: null },
355
+ { id: 132, name: '运动中任务取消', code: 'task_cancel_in_motion', mode: 'ro', type: 'RAW', property: null },
356
+ { id: 133, name: '充电状态', code: 'charge_status', mode: 'ro', type: 'RAW', property: null },
357
+ { id: 134, name: '烘干状态', code: 'drying_status', mode: 'ro', type: 'RAW', property: null },
358
+ { id: 135, name: '离线原因细分', code: 'offline_status', mode: 'ro', type: 'RAW', property: null },
359
+ { id: 139, name: '回基站目的', code: 'back_type', mode: 'ro', type: 'RAW', property: null },
360
+ ],
361
+ },
362
+ ],
363
+ receivedDevices: [],
364
+ rooms: [
365
+ { id: 11100849, name: 'Study' },
366
+ { id: 11100847, name: 'Bedroom' },
367
+ { id: 11100845, name: 'Kitchen' },
368
+ { id: 11100842, name: 'Living room' },
369
+ ],
370
+ };