matterbridge-roborock-vacuum-plugin 1.0.8-rc02 → 1.0.8-rc04

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 (91) hide show
  1. package/.github/workflows/build.yml +47 -0
  2. package/.github/workflows/coverage.yml +50 -0
  3. package/README.md +6 -0
  4. package/dist/behaviors/BehaviorDeviceGeneric.js +0 -9
  5. package/dist/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.js +2 -9
  6. package/dist/behaviors/roborock.vacuum/S7_MAXV/a27.js +2 -9
  7. package/dist/behaviors/roborock.vacuum/S8_PRO_ULTRA/a51.js +2 -9
  8. package/dist/behaviors/roborock.vacuum/default/default.js +2 -9
  9. package/dist/initialData/getSupportedAreas.js +1 -1
  10. package/dist/initialData/getSupportedScenes.js +2 -2
  11. package/dist/platform.js +2 -21
  12. package/dist/platformRunner.js +29 -22
  13. package/dist/roborockCommunication/RESTAPI/roborockAuthenticateApi.js +1 -1
  14. package/dist/roborockCommunication/RESTAPI/roborockIoTApi.js +1 -1
  15. package/dist/roborockCommunication/Zmodel/batteryMessage.js +1 -0
  16. package/dist/roborockCommunication/Zmodel/deviceModel.js +1 -1
  17. package/dist/roborockCommunication/broadcast/abstractClient.js +1 -1
  18. package/dist/roborockCommunication/broadcast/client/MQTTClient.js +3 -2
  19. package/dist/roborockCommunication/broadcast/clientRouter.js +2 -2
  20. package/dist/roborockCommunication/broadcast/listener/implementation/syncMessageListener.js +2 -1
  21. package/dist/roborockCommunication/broadcast/model/messageContext.js +1 -1
  22. package/dist/roborockCommunication/broadcast/model/requestMessage.js +1 -1
  23. package/dist/roborockCommunication/helper/cryptoHelper.js +21 -25
  24. package/dist/roborockCommunication/helper/messageDeserializer.js +3 -3
  25. package/dist/roborockCommunication/helper/messageSerializer.js +3 -3
  26. package/dist/roborockService.js +2 -1
  27. package/dist/rvc.js +1 -1
  28. package/dist/share/function.js +0 -2
  29. package/eslint.config.js +65 -61
  30. package/jest.config.js +20 -20
  31. package/jest.setup.js +1 -1
  32. package/matterbridge-roborock-vacuum-plugin.config.json +26 -1
  33. package/matterbridge-roborock-vacuum-plugin.schema.json +6 -1
  34. package/package.json +4 -2
  35. package/prettier.config.js +18 -18
  36. package/src/behaviors/BehaviorDeviceGeneric.ts +5 -13
  37. package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.ts +16 -18
  38. package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.ts +1 -1
  39. package/src/behaviors/roborock.vacuum/S7_MAXV/a27.ts +15 -17
  40. package/src/behaviors/roborock.vacuum/S7_MAXV/runtimes.ts +1 -1
  41. package/src/behaviors/roborock.vacuum/S8_PRO_ULTRA/a51.ts +14 -16
  42. package/src/behaviors/roborock.vacuum/S8_PRO_ULTRA/runtimes.ts +1 -1
  43. package/src/behaviors/roborock.vacuum/default/default.ts +13 -15
  44. package/src/behaviors/roborock.vacuum/default/runtimes.ts +1 -1
  45. package/src/clientManager.ts +1 -1
  46. package/src/initialData/getSupportedAreas.ts +1 -1
  47. package/src/initialData/getSupportedScenes.ts +2 -2
  48. package/src/model/CloudMessageModel.ts +8 -8
  49. package/src/model/DockingStationStatus.ts +3 -3
  50. package/src/model/ExperimentalFeatureSetting.ts +1 -0
  51. package/src/platform.ts +4 -24
  52. package/src/platformRunner.ts +51 -40
  53. package/src/roborockCommunication/RESTAPI/roborockAuthenticateApi.ts +2 -2
  54. package/src/roborockCommunication/RESTAPI/roborockIoTApi.ts +4 -4
  55. package/src/roborockCommunication/Zenum/operationStatusCode.ts +1 -1
  56. package/src/roborockCommunication/Zmodel/batteryMessage.ts +14 -0
  57. package/src/roborockCommunication/Zmodel/deviceModel.ts +1 -1
  58. package/src/roborockCommunication/Zmodel/deviceSchema.ts +1 -1
  59. package/src/roborockCommunication/Zmodel/messageResult.ts +1 -1
  60. package/src/roborockCommunication/Zmodel/room.ts +1 -1
  61. package/src/roborockCommunication/Zmodel/scene.ts +2 -2
  62. package/src/roborockCommunication/broadcast/abstractClient.ts +4 -3
  63. package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +2 -2
  64. package/src/roborockCommunication/broadcast/client/MQTTClient.ts +6 -6
  65. package/src/roborockCommunication/broadcast/clientRouter.ts +2 -2
  66. package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts +3 -3
  67. package/src/roborockCommunication/broadcast/listener/implementation/syncMessageListener.ts +5 -4
  68. package/src/roborockCommunication/broadcast/messageProcessor.ts +3 -3
  69. package/src/roborockCommunication/broadcast/model/dps.ts +2 -2
  70. package/src/roborockCommunication/broadcast/model/messageContext.ts +2 -2
  71. package/src/roborockCommunication/broadcast/model/protocol.ts +2 -2
  72. package/src/roborockCommunication/broadcast/model/requestMessage.ts +5 -5
  73. package/src/roborockCommunication/helper/cryptoHelper.ts +22 -26
  74. package/src/roborockCommunication/helper/messageDeserializer.ts +6 -7
  75. package/src/roborockCommunication/helper/messageSerializer.ts +3 -3
  76. package/src/roborockCommunication/helper/nameDecoder.ts +3 -3
  77. package/src/roborockCommunication/index.ts +1 -0
  78. package/src/roborockService.ts +37 -27
  79. package/src/rvc.ts +14 -14
  80. package/src/share/function.ts +0 -2
  81. package/src/tests/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.test.ts +5 -5
  82. package/src/tests/behaviors/roborock.vacuum/S7_MAXV/a27.test.ts +2 -2
  83. package/src/tests/model/DockingStationStatus.test.ts +39 -0
  84. package/src/tests/platformRunner.test.ts +57 -0
  85. package/src/tests/testData/mockHomeData-a187.json +286 -0
  86. package/dist/extensions/AxiosStaticExtensions.js +0 -22
  87. package/dist/extensions/index.js +0 -1
  88. package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.test.ts +0 -79
  89. package/src/behaviors/roborock.vacuum/S7_MAXV/a27.test.ts +0 -134
  90. package/src/extensions/AxiosStaticExtensions.ts +0 -35
  91. package/src/extensions/index.ts +0 -1
@@ -0,0 +1,47 @@
1
+ name: Build, lint and test
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ node-version: [18.x, 20.x, 22.x, 24.x]
13
+ os: [ubuntu-latest, windows-latest, macos-latest]
14
+
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Use Node.js ${{ matrix.node-version }}
20
+ uses: actions/setup-node@v4
21
+ with:
22
+ node-version: ${{ matrix.node-version }}
23
+ registry-url: 'https://registry.npmjs.org/'
24
+
25
+ - name: Clean cache
26
+ run: npm cache clean --force
27
+
28
+ - name: Verify Node.js version
29
+ run: node -v
30
+
31
+ - name: Verify Npm version
32
+ run: npm -v
33
+
34
+ - name: Install dependencies
35
+ run: npm ci
36
+
37
+ - name: Install dependencies
38
+ run: npm run precondition && npm run deepClean && npm install && npm link matterbridge
39
+
40
+ - name: Lint the project
41
+ run: npm run lint
42
+
43
+ - name: Test the project
44
+ run: npm run test
45
+
46
+ - name: Build (optional)
47
+ run: npm run build
@@ -0,0 +1,50 @@
1
+ name: Analyze code coverage
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ publish:
7
+ runs-on: ubuntu-latest
8
+
9
+ steps:
10
+ - name: Checkout code
11
+ uses: actions/checkout@v4
12
+
13
+ - name: Use Node.js
14
+ uses: actions/setup-node@v4
15
+ with:
16
+ node-version: '20'
17
+ registry-url: 'https://registry.npmjs.org/'
18
+
19
+ - name: Clean cache
20
+ run: npm cache clean --force
21
+
22
+ - name: Verify Node.js version
23
+ run: node -v
24
+
25
+ - name: Verify Npm version
26
+ run: npm -v
27
+
28
+ - name: Install dependencies
29
+ run: npm ci
30
+
31
+ - name: Install dependencies
32
+ run: npm run precondition && npm run deepClean && npm install && npm link matterbridge
33
+
34
+ - name: Lint the project
35
+ run: npm run lint
36
+
37
+ - name: Test the project
38
+ run: npm run test
39
+
40
+ - name: Test with coverage
41
+ run: npm run test -- --coverage
42
+
43
+ - name: Upload coverage reports to Codecov
44
+ uses: codecov/codecov-action@v5
45
+ with:
46
+ token: ${{ secrets.CODECOV_TOKEN }}
47
+ slug: RinDevJunior/matterbridge-roborock-vacuum-plugin
48
+
49
+ - name: Build (optional)
50
+ run: npm run build
package/README.md CHANGED
@@ -11,6 +11,12 @@
11
11
  <a href="https://www.npmjs.com/package/matterbridge-roborock-vacuum-plugin">
12
12
  <img src="https://img.shields.io/npm/dt/matterbridge-roborock-vacuum-plugin.svg" alt="npm downloads" />
13
13
  </a>
14
+ <a href="https://github.com/RinDevJunior/matterbridge-roborock-vacuum-plugin/actions/workflows/publish.yml/badge.svg">
15
+ <img src="https://github.com/RinDevJunior/matterbridge-roborock-vacuum-plugin/actions/workflows/publish.yml/badge.svg" alt="nodejs ci" />
16
+ </a>
17
+ <a href="https://codecov.io/gh/RinDevJunior/matterbridge-roborock-vacuum-plugin">
18
+ <img src="https://codecov.io/gh/RinDevJunior/matterbridge-roborock-vacuum-plugin/branch/main/graph/badge.svg" alt="codecov" />
19
+ </a>
14
20
  <a href="https://www.npmjs.com/package/matterbridge">
15
21
  <img src="https://img.shields.io/badge/powered%20by-matterbridge-blue" alt="powered by Matterbridge" />
16
22
  </a>
@@ -17,15 +17,6 @@ export class BehaviorDeviceGeneric {
17
17
  await handler(...args);
18
18
  }
19
19
  }
20
- export class BehaviorGeneric extends Behavior {
21
- static id;
22
- }
23
20
  export class BehaviorRoborock extends Behavior {
24
21
  static id = 'roborock.vacuum.axx';
25
22
  }
26
- (function (BehaviorRoborock) {
27
- class State {
28
- device;
29
- }
30
- BehaviorRoborock.State = State;
31
- })(BehaviorRoborock || (BehaviorRoborock = {}));
@@ -2,13 +2,6 @@ import { debugStringify } from 'matterbridge/logger';
2
2
  import { BehaviorRoborock } from '../../BehaviorDeviceGeneric.js';
3
3
  export class BehaviorA187 extends BehaviorRoborock {
4
4
  }
5
- export var BehaviorRoborockA187;
6
- (function (BehaviorRoborockA187) {
7
- class State {
8
- device;
9
- }
10
- BehaviorRoborockA187.State = State;
11
- })(BehaviorRoborockA187 || (BehaviorRoborockA187 = {}));
12
5
  export var VacuumSuctionPowerA187;
13
6
  (function (VacuumSuctionPowerA187) {
14
7
  VacuumSuctionPowerA187[VacuumSuctionPowerA187["Quiet"] = 101] = "Quiet";
@@ -142,8 +135,8 @@ export function setCommandHandlerA187(duid, handler, logger, roborockService, cl
142
135
  logger.notice('BehaviorA187-GoHome');
143
136
  await roborockService.stopAndGoHome(duid);
144
137
  });
145
- handler.setCommandHandler('PlaySoundToLocate', async (identifyTime) => {
146
- logger.notice('BehaviorA187-PlaySoundToLocate');
138
+ handler.setCommandHandler('playSoundToLocate', async () => {
139
+ logger.notice('BehaviorA187-playSoundToLocate');
147
140
  await roborockService.playSoundToLocate(duid);
148
141
  });
149
142
  }
@@ -2,13 +2,6 @@ import { debugStringify } from 'matterbridge/logger';
2
2
  import { BehaviorRoborock } from '../../BehaviorDeviceGeneric.js';
3
3
  export class BehaviorA27 extends BehaviorRoborock {
4
4
  }
5
- export var BehaviorRoborockA27;
6
- (function (BehaviorRoborockA27) {
7
- class State {
8
- device;
9
- }
10
- BehaviorRoborockA27.State = State;
11
- })(BehaviorRoborockA27 || (BehaviorRoborockA27 = {}));
12
5
  export var VacuumSuctionPowerA27;
13
6
  (function (VacuumSuctionPowerA27) {
14
7
  VacuumSuctionPowerA27[VacuumSuctionPowerA27["Quiet"] = 101] = "Quiet";
@@ -133,8 +126,8 @@ export function setCommandHandlerA27(duid, handler, logger, roborockService, cle
133
126
  logger.notice('BehaviorA27-GoHome');
134
127
  await roborockService.stopAndGoHome(duid);
135
128
  });
136
- handler.setCommandHandler('PlaySoundToLocate', async (identifyTime) => {
137
- logger.notice('BehaviorA27-PlaySoundToLocate');
129
+ handler.setCommandHandler('playSoundToLocate', async () => {
130
+ logger.notice('BehaviorA27-playSoundToLocate');
138
131
  await roborockService.playSoundToLocate(duid);
139
132
  });
140
133
  }
@@ -2,13 +2,6 @@ import { debugStringify } from 'matterbridge/logger';
2
2
  import { BehaviorRoborock } from '../../BehaviorDeviceGeneric.js';
3
3
  export class BehaviorA51 extends BehaviorRoborock {
4
4
  }
5
- export var BehaviorRoborockA51;
6
- (function (BehaviorRoborockA51) {
7
- class State {
8
- device;
9
- }
10
- BehaviorRoborockA51.State = State;
11
- })(BehaviorRoborockA51 || (BehaviorRoborockA51 = {}));
12
5
  export var VacuumSuctionPowerA51;
13
6
  (function (VacuumSuctionPowerA51) {
14
7
  VacuumSuctionPowerA51[VacuumSuctionPowerA51["Quiet"] = 101] = "Quiet";
@@ -97,8 +90,8 @@ export function setCommandHandlerA51(duid, handler, logger, roborockService, cle
97
90
  logger.notice('BehaviorA51-GoHome');
98
91
  await roborockService.stopAndGoHome(duid);
99
92
  });
100
- handler.setCommandHandler('PlaySoundToLocate', async (identifyTime) => {
101
- logger.notice('BehaviorA51-PlaySoundToLocate');
93
+ handler.setCommandHandler('playSoundToLocate', async () => {
94
+ logger.notice('BehaviorA51-playSoundToLocate');
102
95
  await roborockService.playSoundToLocate(duid);
103
96
  });
104
97
  const getSettingFromCleanMode = (activity, cleanModeSettings) => {
@@ -2,13 +2,6 @@ import { debugStringify } from 'matterbridge/logger';
2
2
  import { BehaviorRoborock } from '../../BehaviorDeviceGeneric.js';
3
3
  export class DefaultBehavior extends BehaviorRoborock {
4
4
  }
5
- export var DefaultBehaviorRoborock;
6
- (function (DefaultBehaviorRoborock) {
7
- class State {
8
- device;
9
- }
10
- DefaultBehaviorRoborock.State = State;
11
- })(DefaultBehaviorRoborock || (DefaultBehaviorRoborock = {}));
12
5
  export var VacuumSuctionPower;
13
6
  (function (VacuumSuctionPower) {
14
7
  VacuumSuctionPower[VacuumSuctionPower["Quiet"] = 101] = "Quiet";
@@ -97,8 +90,8 @@ export function setDefaultCommandHandler(duid, handler, logger, roborockService,
97
90
  logger.notice('DefaultBehavior-GoHome');
98
91
  await roborockService.stopAndGoHome(duid);
99
92
  });
100
- handler.setCommandHandler('PlaySoundToLocate', async (identifyTime) => {
101
- logger.notice('DefaultBehavior-PlaySoundToLocate');
93
+ handler.setCommandHandler('playSoundToLocate', async () => {
94
+ logger.notice('DefaultBehavior-playSoundToLocate');
102
95
  await roborockService.playSoundToLocate(duid);
103
96
  });
104
97
  const getSettingFromCleanMode = (activity, cleanModeSettings) => {
@@ -19,7 +19,7 @@ export function getSupportedAreas(rooms, roomMap, log) {
19
19
  },
20
20
  ];
21
21
  }
22
- const supportedAreas = roomMap.rooms.map((room, index) => {
22
+ const supportedAreas = roomMap.rooms.map((room) => {
23
23
  return {
24
24
  areaId: room.id,
25
25
  mapId: null,
@@ -1,5 +1,5 @@
1
1
  import { debugStringify } from 'matterbridge/logger';
2
- import { randomInt } from 'crypto';
2
+ import { randomInt } from 'node:crypto';
3
3
  export function getSupportedScenes(scenes, log) {
4
4
  log?.debug('getSupportedScenes', debugStringify(scenes));
5
5
  if (!scenes || scenes.length === 0) {
@@ -10,7 +10,7 @@ export function getSupportedScenes(scenes, log) {
10
10
  .filter((s) => s.enabled && s.id)
11
11
  .map((scene) => {
12
12
  return {
13
- areaId: scene.id + randomInt(5000, 9000),
13
+ areaId: scene.id ?? 0 + randomInt(5000, 9000),
14
14
  mapId: null,
15
15
  areaInfo: {
16
16
  locationInfo: {
package/dist/platform.js CHANGED
@@ -1,4 +1,3 @@
1
- import './extensions/index.js';
2
1
  import { MatterbridgeDynamicPlatform } from 'matterbridge';
3
2
  import * as axios from 'axios';
4
3
  import { debugStringify } from 'matterbridge/logger';
@@ -38,7 +37,6 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
38
37
  }
39
38
  async onStart(reason) {
40
39
  this.log.notice('onStart called with reason:', reason ?? 'none');
41
- const self = this;
42
40
  await this.ready;
43
41
  await this.clearSelect();
44
42
  if (this.config.username === undefined || this.config.password === undefined) {
@@ -46,24 +44,6 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
46
44
  return;
47
45
  }
48
46
  const axiosInstance = axios.default ?? axios;
49
- axiosInstance.interceptors.request.use((request) => {
50
- self.log.debug('Axios Request:', {
51
- method: request.method,
52
- url: request.url,
53
- data: request.data,
54
- headers: request.headers,
55
- });
56
- return request;
57
- });
58
- axiosInstance.interceptors.response.use((response) => {
59
- self.log.debug('Axios Response:', {
60
- status: response.status,
61
- data: response.data,
62
- headers: response.headers,
63
- url: response.config.url,
64
- });
65
- return response;
66
- });
67
47
  this.enableExperimentalFeature = this.config.enableExperimental;
68
48
  if (this.enableExperimentalFeature?.enableExperimentalFeature && this.enableExperimentalFeature?.cleanModeSettings?.enableCleanModeMapping) {
69
49
  this.cleanModeSettings = this.enableExperimentalFeature.cleanModeSettings;
@@ -144,7 +124,8 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
144
124
  async onShutdown(reason) {
145
125
  await super.onShutdown(reason);
146
126
  this.log.notice('onShutdown called with reason:', reason ?? 'none');
147
- this.rvcInterval && clearInterval(this.rvcInterval);
127
+ if (this.rvcInterval)
128
+ clearInterval(this.rvcInterval);
148
129
  if (this.roborockService)
149
130
  this.roborockService.stopService();
150
131
  if (this.config.unregisterOnShutdown === true)
@@ -30,7 +30,7 @@ export class PlatformRunner {
30
30
  if (platform.roborockService === undefined)
31
31
  return;
32
32
  const homeData = await platform.roborockService.getHomeDataForUpdating(platform.robot.rrHomeId);
33
- await platform.platformRunner?.updateRobot(NotifyMessageTypes.HomeData, homeData);
33
+ await this.updateRobot(NotifyMessageTypes.HomeData, homeData);
34
34
  }
35
35
  async getRoomMapFromDevice(device) {
36
36
  const platform = this.platform;
@@ -76,24 +76,25 @@ export class PlatformRunner {
76
76
  }
77
77
  switch (messageSource) {
78
78
  case NotifyMessageTypes.ErrorOccurred: {
79
- const errorCode = messageData.errorCode;
80
- const operationalStateId = getOperationalErrorState(errorCode);
79
+ const message = messageData;
80
+ const operationalStateId = getOperationalErrorState(message.errorCode);
81
81
  if (operationalStateId) {
82
- platform.log.error(`Error occurred: ${errorCode}`);
82
+ platform.log.error(`Error occurred: ${message.errorCode}`);
83
83
  platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
84
84
  }
85
85
  break;
86
86
  }
87
87
  case NotifyMessageTypes.BatteryUpdate: {
88
- const batteryLevel = messageData.percentage;
89
- if (messageData.percentage && batteryLevel) {
88
+ const message = messageData;
89
+ const batteryLevel = message.percentage;
90
+ if (batteryLevel) {
90
91
  platform.robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
91
92
  platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
92
93
  }
93
94
  break;
94
95
  }
95
96
  case NotifyMessageTypes.LocalMessage: {
96
- const data = messageData.statusType;
97
+ const data = messageData;
97
98
  if (data) {
98
99
  const state = state_to_matter_state(data.state);
99
100
  if (state) {
@@ -123,10 +124,7 @@ export class PlatformRunner {
123
124
  break;
124
125
  }
125
126
  case NotifyMessageTypes.CloudMessage: {
126
- var data = messageData.data;
127
- if (!data) {
128
- data = messageData;
129
- }
127
+ const data = messageData;
130
128
  if (!data)
131
129
  return;
132
130
  this.handlerCloudMessage(data, duid, deviceData.model);
@@ -146,11 +144,11 @@ export class PlatformRunner {
146
144
  const status = Number(data.dps[messageType]);
147
145
  const matterState = state_to_matter_state(status);
148
146
  if (matterState) {
149
- platform.robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(model, matterState), platform.log);
147
+ platform.robot?.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(model, matterState), platform.log);
150
148
  }
151
149
  const operationalStateId = state_to_matter_operational_status(status);
152
150
  if (operationalStateId) {
153
- platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
151
+ platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
154
152
  }
155
153
  break;
156
154
  }
@@ -160,9 +158,12 @@ export class PlatformRunner {
160
158
  platform.log.debug('Ignore message:', debugStringify(data));
161
159
  return;
162
160
  }
163
- const roboStatus = response.result[0];
161
+ let roboStatus;
162
+ if (Array.isArray(response.result) && response.result.length > 0) {
163
+ roboStatus = response.result[0];
164
+ }
164
165
  if (roboStatus) {
165
- const message = { duid: duid, statusType: { ...roboStatus } };
166
+ const message = { duid: duid, ...roboStatus };
166
167
  platform.log.debug('rpc_response:', debugStringify(message));
167
168
  await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, true);
168
169
  }
@@ -172,7 +173,7 @@ export class PlatformRunner {
172
173
  const fanPower = data.dps[messageType];
173
174
  const currentCleanMode = getCurrentCleanModeFromFanPowerFunc(model)(fanPower);
174
175
  if (currentCleanMode) {
175
- platform.robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
176
+ platform.robot?.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
176
177
  }
177
178
  break;
178
179
  }
@@ -180,7 +181,7 @@ export class PlatformRunner {
180
181
  const water_box_mode = data.dps[messageType];
181
182
  const currentCleanMode = getCurrentCleanModeFromWaterBoxModeFunc(model)(water_box_mode);
182
183
  if (currentCleanMode) {
183
- platform.robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
184
+ platform.robot?.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
184
185
  }
185
186
  break;
186
187
  }
@@ -197,7 +198,10 @@ export class PlatformRunner {
197
198
  }
198
199
  async processAdditionalProps(robot, message) {
199
200
  const platform = this.platform;
200
- if (platform.config.enableExperimentalFeature && message.dss !== undefined) {
201
+ if (platform.enableExperimentalFeature &&
202
+ platform.enableExperimentalFeature.enableExperimentalFeature &&
203
+ platform.enableExperimentalFeature.advancedFeature.includeDockStationStatus &&
204
+ message.dss !== undefined) {
201
205
  const dss = parseDockingStationStatus(message.dss);
202
206
  this.platform.log.debug('DockingStationStatus:', debugStringify(dss));
203
207
  const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState');
@@ -205,12 +209,15 @@ export class PlatformRunner {
205
209
  robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
206
210
  }
207
211
  }
208
- if (message.fan_power !== undefined) {
209
- const fanPower = message.fan_power;
210
- }
211
212
  }
212
213
  isStatusUpdate(result) {
213
- return result && Array.isArray(result) && result.length > 0 && result[0].msg_ver !== undefined && result[0].msg_ver !== null;
214
+ return (Array.isArray(result) &&
215
+ result.length > 0 &&
216
+ typeof result[0] === 'object' &&
217
+ result[0] !== null &&
218
+ 'msg_ver' in result[0] &&
219
+ result[0].msg_ver !== undefined &&
220
+ result[0].msg_ver !== null);
214
221
  }
215
222
  updateFromHomeData(homeData) {
216
223
  const platform = this.platform;
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import crypto from 'crypto';
2
+ import crypto from 'node:crypto';
3
3
  import { URLSearchParams } from 'node:url';
4
4
  export class RoborockAuthenticateApi {
5
5
  logger;
@@ -1,5 +1,5 @@
1
1
  import axios from 'axios';
2
- import crypto from 'crypto';
2
+ import crypto from 'node:crypto';
3
3
  import { debugStringify } from 'matterbridge/logger';
4
4
  export class RoborockIoTApi {
5
5
  logger;
@@ -8,8 +8,8 @@ export var DeviceModel;
8
8
  DeviceModel["S6_MAXV"] = "roborock.vacuum.a10";
9
9
  DeviceModel["S6_PURE"] = "roborock.vacuum.a08";
10
10
  DeviceModel["Q7"] = "roborock.vacuum.a40";
11
+ DeviceModel["Q7_PLUS"] = "roborock.vacuum.axx";
11
12
  DeviceModel["Q7_MAX"] = "roborock.vacuum.a38";
12
- DeviceModel["Q7_PLUS"] = "roborock.vacuum.a40";
13
13
  DeviceModel["S7"] = "roborock.vacuum.a15";
14
14
  DeviceModel["S7_MAXV"] = "roborock.vacuum.a27";
15
15
  DeviceModel["S7_MAXV_ULTRA"] = "roborock.vacuum.a65";
@@ -22,7 +22,7 @@ export class AbstractClient {
22
22
  }
23
23
  async get(duid, request) {
24
24
  return new Promise((resolve, reject) => {
25
- this.syncMessageListener.waitFor(request.messageId, resolve, reject);
25
+ this.syncMessageListener.waitFor(request.messageId, (response) => resolve(response), reject);
26
26
  this.send(duid, request);
27
27
  });
28
28
  }
@@ -1,5 +1,5 @@
1
1
  import mqtt from 'mqtt';
2
- import { CryptoUtils } from '../../helper/cryptoHelper.js';
2
+ import * as CryptoUtils from '../../helper/cryptoHelper.js';
3
3
  import { AbstractClient } from '../abstractClient.js';
4
4
  import { debugStringify } from 'matterbridge/logger';
5
5
  export class MQTTClient extends AbstractClient {
@@ -23,6 +23,7 @@ export class MQTTClient extends AbstractClient {
23
23
  password: this.mqttPassword,
24
24
  keepalive: 30,
25
25
  log: (...args) => {
26
+ this.logger.debug('MQTTClient args:: ' + args[0]);
26
27
  },
27
28
  });
28
29
  this.client.on('connect', this.onConnect.bind(this));
@@ -67,7 +68,7 @@ export class MQTTClient extends AbstractClient {
67
68
  }
68
69
  this.client.subscribe('rr/m/o/' + this.rriot.u + '/' + this.mqttUsername + '/#', this.onSubscribe.bind(this));
69
70
  }
70
- async onSubscribe(err, granted) {
71
+ async onSubscribe(err) {
71
72
  if (!err) {
72
73
  return;
73
74
  }
@@ -41,13 +41,13 @@ export class ClientRouter {
41
41
  }
42
42
  connect() {
43
43
  this.mqttClient.connect();
44
- this.localClients.forEach((client, duid) => {
44
+ this.localClients.forEach((client) => {
45
45
  client.connect();
46
46
  });
47
47
  }
48
48
  async disconnect() {
49
49
  await this.mqttClient.disconnect();
50
- this.localClients.forEach((client, duid) => {
50
+ this.localClients.forEach((client) => {
51
51
  client.disconnect();
52
52
  });
53
53
  }
@@ -17,7 +17,8 @@ export class SyncMessageListener {
17
17
  const dps = message.get(Protocol.rpc_response);
18
18
  const messageId = dps.id;
19
19
  const responseHandler = this.pending.get(messageId);
20
- if (dps.result.length == 1 && dps.result[0] == 'ok') {
20
+ const result = dps.result;
21
+ if (result && result.length == 1 && result[0] == 'ok') {
21
22
  return;
22
23
  }
23
24
  if (responseHandler) {
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from 'node:crypto';
2
- import { CryptoUtils } from '../../helper/cryptoHelper.js';
2
+ import * as CryptoUtils from '../../helper/cryptoHelper.js';
3
3
  export class MessageContext {
4
4
  endpoint;
5
5
  nonce;
@@ -1,5 +1,5 @@
1
1
  import { Protocol } from './protocol.js';
2
- import { randomInt } from 'crypto';
2
+ import { randomInt } from 'node:crypto';
3
3
  export class RequestMessage {
4
4
  messageId;
5
5
  protocol;
@@ -1,27 +1,23 @@
1
- import crypto from 'crypto';
2
- export class CryptoUtils {
3
- static md5bin(str) {
4
- return crypto.createHash('md5').update(str).digest();
5
- }
6
- static md5hex(str) {
7
- return crypto.createHash('md5').update(str).digest('hex');
8
- }
9
- static decryptECB(encrypted, aesKey) {
10
- const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
11
- decipher.setAutoPadding(false);
12
- let decrypted = decipher.update(encrypted, 'binary', 'utf8');
13
- decrypted += decipher.final('utf8');
14
- return this.removePadding(decrypted);
15
- }
16
- static removePadding(str) {
17
- const paddingLength = str.charCodeAt(str.length - 1);
18
- return str.slice(0, -paddingLength);
19
- }
1
+ import crypto from 'node:crypto';
2
+ export function md5bin(str) {
3
+ return crypto.createHash('md5').update(str).digest();
20
4
  }
21
- export class MessageUtils {
22
- static SALT = 'TXdfu$jyZ#TZHsg4';
23
- static encodeTimestamp(timestamp) {
24
- const hex = timestamp.toString(16).padStart(8, '0').split('');
25
- return [5, 6, 3, 7, 1, 2, 0, 4].map((idx) => hex[idx]).join('');
26
- }
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('');
27
23
  }
@@ -1,8 +1,8 @@
1
- import crypto from 'crypto';
1
+ import crypto from 'node:crypto';
2
2
  import CRC32 from 'crc-32';
3
3
  import { Parser } from 'binary-parser';
4
4
  import { ResponseMessage } from '../broadcast/model/responseMessage.js';
5
- import { CryptoUtils, MessageUtils } from './cryptoHelper.js';
5
+ import * as CryptoUtils from './cryptoHelper.js';
6
6
  import { Protocol } from '../broadcast/model/protocol.js';
7
7
  export class MessageDeserializer {
8
8
  context;
@@ -43,7 +43,7 @@ export class MessageDeserializer {
43
43
  }
44
44
  const data = this.messageParser.parse(message);
45
45
  if (version == '1.0') {
46
- const aesKey = CryptoUtils.md5bin(MessageUtils.encodeTimestamp(data.timestamp) + localKey + MessageUtils.SALT);
46
+ const aesKey = CryptoUtils.md5bin(CryptoUtils.encodeTimestamp(data.timestamp) + localKey + CryptoUtils.SALT);
47
47
  const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
48
48
  data.payload = Buffer.concat([decipher.update(data.payload), decipher.final()]);
49
49
  }
@@ -1,5 +1,5 @@
1
- import { CryptoUtils, MessageUtils } from './cryptoHelper.js';
2
- import crypto from 'crypto';
1
+ import * as CryptoUtils from './cryptoHelper.js';
2
+ import crypto from 'node:crypto';
3
3
  import CRC32 from 'crc-32';
4
4
  export class MessageSerializer {
5
5
  context;
@@ -44,7 +44,7 @@ export class MessageSerializer {
44
44
  const payload = JSON.stringify(payloadData);
45
45
  let encrypted;
46
46
  if (version == '1.0') {
47
- const aesKey = CryptoUtils.md5bin(MessageUtils.encodeTimestamp(payloadData.t) + localKey + MessageUtils.SALT);
47
+ const aesKey = CryptoUtils.md5bin(CryptoUtils.encodeTimestamp(payloadData.t) + localKey + CryptoUtils.SALT);
48
48
  const cipher = crypto.createCipheriv('aes-128-ecb', aesKey, null);
49
49
  encrypted = Buffer.concat([cipher.update(payload), cipher.final()]);
50
50
  }
@@ -78,7 +78,8 @@ export default class RoborockService {
78
78
  }
79
79
  else if (rt.length === 1) {
80
80
  this.logger.debug('RoborockService - startScene', debugStringify({ duid, rooms }));
81
- return this.iotApi?.startScene(rt[0]);
81
+ await this.iotApi?.startScene(rt[0]);
82
+ return;
82
83
  }
83
84
  else if (rooms.length == supportedRooms.length || rooms.length === 0 || supportedRooms.length === 0) {
84
85
  this.logger.debug('RoborockService - startGlobalClean');