matterbridge-roborock-vacuum-plugin 1.0.8-rc06 → 1.0.8-rc08

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,11 +74,14 @@ To get the **DUID** for your devices, you have two options:
74
74
 
75
75
  ---
76
76
 
77
- **➡️ [See Supported & Tested Roborock Devices](./README_SUPPORTED.md)**
78
77
 
79
- > 📋 **Apple Home ↔️ Roborock Clean Mode Mapping:**
80
- > For a detailed table of how Apple Home clean modes map to Roborock settings, see
81
- > 👉 [Apple Home ↔️ Roborock Clean Mode Mapping](./README_CLEANMODE.md)
78
+ ### ➡️ [See Supported & Tested Roborock Devices](./README_SUPPORTED.md)
79
+
80
+
81
+ 📋 **Apple Home ↔️ Roborock Clean Mode Mapping:**
82
+ For a detailed table of how Apple Home clean modes map to Roborock settings, see 👉 [Apple Home ↔️ Roborock Clean Mode Mapping](./README_CLEANMODE.md)
83
+
84
+ ---
82
85
 
83
86
  ### 📦 Prerequisites
84
87
 
@@ -19,8 +19,8 @@ export function getDefaultSupportedRunModes() {
19
19
  },
20
20
  ];
21
21
  }
22
- export function getDefaultSupportedCleanModes() {
23
- return [
22
+ export function getDefaultSupportedCleanModes(enableExperimentalFeature) {
23
+ const result = [
24
24
  {
25
25
  label: RvcCleanModeDisplayMap[5],
26
26
  mode: 5,
@@ -96,12 +96,18 @@ export function getDefaultSupportedCleanModes() {
96
96
  mode: 69,
97
97
  modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Quick }],
98
98
  },
99
- {
100
- label: RvcCleanModeDisplayMap[99],
101
- mode: 99,
102
- modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Vacation }],
103
- },
104
99
  ];
100
+ if (enableExperimentalFeature?.advancedFeature?.useVacationModeToSendVacuumToDock ?? false) {
101
+ return [
102
+ ...result,
103
+ {
104
+ label: RvcCleanModeDisplayMap[99],
105
+ mode: 99,
106
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Vacation }],
107
+ },
108
+ ];
109
+ }
110
+ return result;
105
111
  }
106
112
  export function getDefaultOperationalStates() {
107
113
  return [
@@ -1,7 +1,7 @@
1
1
  import { RvcCleanMode } from 'matterbridge/matter/clusters';
2
2
  import { RvcCleanMode as RvcCleanModeDisplayMap } from './smart.js';
3
3
  import { getDefaultSupportedCleanModes } from '../default/initalData.js';
4
- export function getSupportedCleanModesSmart() {
4
+ export function getSupportedCleanModesSmart(enableExperimentalFeature) {
5
5
  return [
6
6
  {
7
7
  label: RvcCleanModeDisplayMap[4],
@@ -13,6 +13,6 @@ export function getSupportedCleanModesSmart() {
13
13
  mode: 5,
14
14
  modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Day }],
15
15
  },
16
- ...getDefaultSupportedCleanModes().filter((x) => x.mode !== 4 && x.mode !== 5),
16
+ ...getDefaultSupportedCleanModes(enableExperimentalFeature).filter((x) => x.mode !== 4 && x.mode !== 5),
17
17
  ];
18
18
  }
@@ -51,7 +51,7 @@ export function setCommandHandlerSmart(duid, handler, logger, roborockService, c
51
51
  break;
52
52
  }
53
53
  case 'Go Vacation': {
54
- logger.notice('DefaultBehavior-GoHome');
54
+ logger.notice('BehaviorSmart-GoHome');
55
55
  await roborockService.stopAndGoHome(duid);
56
56
  break;
57
57
  }
@@ -1,17 +1,17 @@
1
1
  import { getSupportedCleanModesSmart } from '../behaviors/roborock.vacuum/smart/initalData.js';
2
2
  import { getDefaultSupportedCleanModes } from '../behaviors/roborock.vacuum/default/initalData.js';
3
3
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
4
- export function getSupportedCleanModes(model, forceRunAtDefault) {
5
- if (forceRunAtDefault) {
6
- return getDefaultSupportedCleanModes();
4
+ export function getSupportedCleanModes(model, enableExperimentalFeature) {
5
+ if (enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false) {
6
+ return getDefaultSupportedCleanModes(enableExperimentalFeature);
7
7
  }
8
8
  switch (model) {
9
9
  case DeviceModel.QREVO_EDGE_5V1:
10
- return getSupportedCleanModesSmart();
10
+ return getSupportedCleanModesSmart(enableExperimentalFeature);
11
11
  case DeviceModel.S7_MAXV:
12
12
  case DeviceModel.S8_PRO_ULTRA:
13
13
  case DeviceModel.S6_PURE:
14
14
  default:
15
- return getDefaultSupportedCleanModes();
15
+ return getDefaultSupportedCleanModes(enableExperimentalFeature);
16
16
  }
17
17
  }
@@ -15,6 +15,9 @@ export function parseDockingStationStatus(dss) {
15
15
  };
16
16
  }
17
17
  export function hasDockingStationError(status) {
18
+ if (!status) {
19
+ return false;
20
+ }
18
21
  return (status.cleanFluidStatus === DockingStationStatusType.Error ||
19
22
  status.waterBoxFilterStatus === DockingStationStatusType.Error ||
20
23
  status.dustBagStatus === DockingStationStatusType.Error ||
package/dist/platform.js CHANGED
@@ -113,7 +113,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
113
113
  routineAsRoom = getSupportedScenes(vacuum.scenes, this.log);
114
114
  this.roborockService.setSupportedScenes(vacuum.duid, routineAsRoom);
115
115
  }
116
- this.robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false, this.log);
116
+ this.robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature, this.log);
117
117
  this.robot.configurateHandler(behaviorHandler);
118
118
  this.log.info('vacuum:', debugStringify(vacuum));
119
119
  this.setSelectDevice(this.robot.serialNumber ?? '', this.robot.deviceName ?? '', undefined, 'hub');
@@ -159,7 +159,10 @@ export class PlatformRunner {
159
159
  }
160
160
  const operationalStateId = state_to_matter_operational_status(status);
161
161
  if (operationalStateId) {
162
- platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
162
+ const dssHasError = hasDockingStationError(platform.robot?.dockStationStatus);
163
+ if (!(dssHasError && self.triggerDssError())) {
164
+ platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
165
+ }
163
166
  }
164
167
  break;
165
168
  }
@@ -211,18 +214,27 @@ export class PlatformRunner {
211
214
  });
212
215
  }
213
216
  async processAdditionalProps(robot, message) {
217
+ const dssStatus = this.getDssStatus(message);
218
+ if (dssStatus) {
219
+ this.triggerDssError();
220
+ }
221
+ }
222
+ getDssStatus(message) {
214
223
  const platform = this.platform;
224
+ const robot = platform.robot;
215
225
  if (platform.enableExperimentalFeature &&
216
226
  platform.enableExperimentalFeature.enableExperimentalFeature &&
217
227
  platform.enableExperimentalFeature.advancedFeature.includeDockStationStatus &&
218
228
  message.dss !== undefined) {
219
229
  const dss = parseDockingStationStatus(message.dss);
220
- this.platform.log.debug('DockingStationStatus:', debugStringify(dss));
221
- const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState');
222
- if (dss && hasDockingStationError(dss) && currentOperationState !== RvcOperationalState.OperationalState.Running) {
223
- robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
230
+ if (dss && robot) {
231
+ robot.dockStationStatus = dss;
232
+ }
233
+ if (dss && hasDockingStationError(dss)) {
234
+ return RvcOperationalState.OperationalState.Error;
224
235
  }
225
236
  }
237
+ return undefined;
226
238
  }
227
239
  isStatusUpdate(result) {
228
240
  return (Array.isArray(result) &&
@@ -262,11 +274,27 @@ export class PlatformRunner {
262
274
  }
263
275
  const operationalStateId = state_to_matter_operational_status(state);
264
276
  if (operationalStateId) {
265
- this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
266
- platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
277
+ const dssHasError = hasDockingStationError(platform.robot?.dockStationStatus);
278
+ this.platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(platform.robot?.dockStationStatus ?? {})}`);
279
+ if (!(dssHasError && this.triggerDssError())) {
280
+ this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
281
+ platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
282
+ }
267
283
  }
268
284
  if (batteryLevel) {
269
285
  platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
270
286
  }
271
287
  }
288
+ triggerDssError() {
289
+ const platform = this.platform;
290
+ const currentOperationState = platform?.robot?.getAttribute(RvcOperationalState.Cluster.id, 'operationalState');
291
+ if (currentOperationState === RvcOperationalState.OperationalState.Error) {
292
+ return true;
293
+ }
294
+ if (currentOperationState === RvcOperationalState.OperationalState.Docked) {
295
+ platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
296
+ return true;
297
+ }
298
+ return false;
299
+ }
272
300
  }
@@ -56,7 +56,7 @@ export class LocalNetworkClient extends AbstractClient {
56
56
  await this.connectionListeners.onConnected();
57
57
  }
58
58
  async onDisconnect() {
59
- this.logger.info('Socket has disconnected.');
59
+ this.logger.notice('LocalNetworkClient: Socket has disconnected.');
60
60
  this.connected = false;
61
61
  if (this.socket) {
62
62
  this.socket.destroy();
@@ -68,7 +68,7 @@ export class LocalNetworkClient extends AbstractClient {
68
68
  await this.connectionListeners.onDisconnected();
69
69
  }
70
70
  async onError(result) {
71
- this.logger.error('Socket connection error: ' + result);
71
+ this.logger.error('LocalNetworkClient: Socket connection error: ' + result);
72
72
  this.connected = false;
73
73
  if (this.socket) {
74
74
  this.socket.destroy();
@@ -158,7 +158,7 @@ export default class RoborockService {
158
158
  await this.messageProcessor.getDeviceStatus(device.duid).then((response) => {
159
159
  if (self.deviceNotify) {
160
160
  const message = { duid: device.duid, ...response.errorStatus, ...response.message };
161
- self.logger.debug('Device status update xxx', debugStringify(message));
161
+ self.logger.debug('Device status update', debugStringify(message));
162
162
  self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
163
163
  }
164
164
  });
@@ -332,16 +332,21 @@ export default class RoborockService {
332
332
  this.logger.debug('initializing the local connection for this client towards ' + this.ip);
333
333
  this.localClient = this.messageClient.registerClient(device.duid, this.ip);
334
334
  this.localClient.connect();
335
- while (!this.localClient.isConnected()) {
335
+ let count = 0;
336
+ while (!this.localClient.isConnected() && count < 20) {
337
+ this.logger.debug('Keep waiting for local client to connect');
338
+ count++;
336
339
  await this.sleep(500);
337
340
  }
341
+ if (!this.localClient.isConnected()) {
342
+ throw new Error('Local client did not connect after 10 attempts, something is wrong');
343
+ }
338
344
  this.logger.debug('LocalClient connected');
339
345
  }
340
346
  }
341
347
  catch (error) {
342
348
  this.logger.error('Error requesting network info', error);
343
349
  }
344
- this.logger.debug('Local client connected');
345
350
  }
346
351
  sleep(ms) {
347
352
  return new Promise((resolve) => setTimeout(resolve, ms));
package/dist/rvc.js CHANGED
@@ -6,12 +6,13 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
6
6
  device;
7
7
  rrHomeId;
8
8
  roomInfo;
9
- constructor(username, device, roomMap, routineAsRoom, forceRunAtDefault, log) {
10
- const cleanModes = getSupportedCleanModes(device.data.model, forceRunAtDefault);
9
+ dockStationStatus;
10
+ constructor(username, device, roomMap, routineAsRoom, enableExperimentalFeature, log) {
11
+ const cleanModes = getSupportedCleanModes(device.data.model, enableExperimentalFeature);
11
12
  const supportedRunModes = getSupportedRunModes();
12
13
  const supportedAreas = [...getSupportedAreas(device.rooms, roomMap, log), ...routineAsRoom];
13
14
  const deviceName = `${device.name}-${device.duid}`.replace(/\s+/g, '');
14
- log.debug(`Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${forceRunAtDefault}`);
15
+ log.debug(`Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${enableExperimentalFeature?.advancedFeature?.forceRunAtDefault}`);
15
16
  log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
16
17
  log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
17
18
  super(deviceName, device.duid, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(), supportedAreas, undefined, supportedAreas[0].areaId);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.0.8-rc06",
4
+ "version": "1.0.8-rc08",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -10,7 +10,8 @@
10
10
  "advancedFeature": {
11
11
  "showRoutinesAsRooms": false,
12
12
  "includeDockStationStatus": false,
13
- "forceRunAtDefault": false
13
+ "forceRunAtDefault": false,
14
+ "useVacationModeToSendVacuumToDock": false
14
15
  },
15
16
  "cleanModeSettings": {
16
17
  "enableCleanModeMapping": false,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.0.8-rc06 by https://github.com/RinDevJunior",
3
+ "description": "matterbridge-roborock-vacuum-plugin v. 1.0.8-rc08 by https://github.com/RinDevJunior",
4
4
  "type": "object",
5
5
  "required": ["username", "password"],
6
6
  "properties": {
@@ -79,6 +79,11 @@
79
79
  "title": "Force Run At Default Implementation",
80
80
  "type": "boolean",
81
81
  "default": false
82
+ },
83
+ "useVacationModeToSendVacuumToDock": {
84
+ "title": "Use Vacation Mode To Send Vacuum To Dock",
85
+ "type": "boolean",
86
+ "default": false
82
87
  }
83
88
  }
84
89
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
- "version": "1.0.8-rc06",
3
+ "version": "1.0.8-rc08",
4
4
  "description": "Matterbridge Roborock Vacuum Plugin",
5
5
  "author": "https://github.com/RinDevJunior",
6
6
  "license": "MIT",
@@ -1,5 +1,6 @@
1
1
  import { RvcCleanMode, RvcOperationalState, RvcRunMode } from 'matterbridge/matter/clusters';
2
2
  import { RvcCleanMode as RvcCleanModeDisplayMap } from './default.js';
3
+ import { ExperimentalFeatureSetting } from '../../../model/ExperimentalFeatureSetting.js';
3
4
 
4
5
  export function getDefaultSupportedRunModes(): RvcRunMode.ModeOption[] {
5
6
  return [
@@ -21,8 +22,8 @@ export function getDefaultSupportedRunModes(): RvcRunMode.ModeOption[] {
21
22
  ];
22
23
  }
23
24
 
24
- export function getDefaultSupportedCleanModes(): RvcCleanMode.ModeOption[] {
25
- return [
25
+ export function getDefaultSupportedCleanModes(enableExperimentalFeature: ExperimentalFeatureSetting | undefined): RvcCleanMode.ModeOption[] {
26
+ const result = [
26
27
  {
27
28
  label: RvcCleanModeDisplayMap[5],
28
29
  mode: 5,
@@ -101,13 +102,20 @@ export function getDefaultSupportedCleanModes(): RvcCleanMode.ModeOption[] {
101
102
  mode: 69,
102
103
  modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Quick }],
103
104
  },
104
-
105
- {
106
- label: RvcCleanModeDisplayMap[99],
107
- mode: 99,
108
- modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Vacation }],
109
- },
110
105
  ];
106
+
107
+ if (enableExperimentalFeature?.advancedFeature?.useVacationModeToSendVacuumToDock ?? false) {
108
+ return [
109
+ ...result,
110
+ {
111
+ label: RvcCleanModeDisplayMap[99],
112
+ mode: 99,
113
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Vacation }],
114
+ },
115
+ ];
116
+ }
117
+
118
+ return result;
111
119
  }
112
120
 
113
121
  export function getDefaultOperationalStates(): RvcOperationalState.OperationalStateStruct[] {
@@ -1,8 +1,9 @@
1
1
  import { RvcCleanMode } from 'matterbridge/matter/clusters';
2
2
  import { RvcCleanMode as RvcCleanModeDisplayMap } from './smart.js';
3
3
  import { getDefaultSupportedCleanModes } from '../default/initalData.js';
4
+ import { ExperimentalFeatureSetting } from '../../../model/ExperimentalFeatureSetting.js';
4
5
 
5
- export function getSupportedCleanModesSmart(): RvcCleanMode.ModeOption[] {
6
+ export function getSupportedCleanModesSmart(enableExperimentalFeature: ExperimentalFeatureSetting | undefined): RvcCleanMode.ModeOption[] {
6
7
  return [
7
8
  {
8
9
  label: RvcCleanModeDisplayMap[4],
@@ -14,6 +15,6 @@ export function getSupportedCleanModesSmart(): RvcCleanMode.ModeOption[] {
14
15
  mode: 5,
15
16
  modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Day }],
16
17
  },
17
- ...getDefaultSupportedCleanModes().filter((x) => x.mode !== 4 && x.mode !== 5), // Exclude modes 4 and 5 which are already defined
18
+ ...getDefaultSupportedCleanModes(enableExperimentalFeature).filter((x) => x.mode !== 4 && x.mode !== 5), // Exclude modes 4 and 5 which are already defined
18
19
  ];
19
20
  }
@@ -83,7 +83,7 @@ export function setCommandHandlerSmart(
83
83
  }
84
84
 
85
85
  case 'Go Vacation': {
86
- logger.notice('DefaultBehavior-GoHome');
86
+ logger.notice('BehaviorSmart-GoHome');
87
87
  await roborockService.stopAndGoHome(duid);
88
88
  break;
89
89
  }
@@ -2,20 +2,21 @@ import { RvcCleanMode } from 'matterbridge/matter/clusters';
2
2
  import { getSupportedCleanModesSmart } from '../behaviors/roborock.vacuum/smart/initalData.js';
3
3
  import { getDefaultSupportedCleanModes } from '../behaviors/roborock.vacuum/default/initalData.js';
4
4
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
5
+ import { ExperimentalFeatureSetting } from '../model/ExperimentalFeatureSetting.js';
5
6
 
6
- export function getSupportedCleanModes(model: string, forceRunAtDefault: boolean): RvcCleanMode.ModeOption[] {
7
- if (forceRunAtDefault) {
8
- return getDefaultSupportedCleanModes();
7
+ export function getSupportedCleanModes(model: string, enableExperimentalFeature: ExperimentalFeatureSetting | undefined): RvcCleanMode.ModeOption[] {
8
+ if (enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false) {
9
+ return getDefaultSupportedCleanModes(enableExperimentalFeature);
9
10
  }
10
11
 
11
12
  switch (model) {
12
13
  case DeviceModel.QREVO_EDGE_5V1:
13
- return getSupportedCleanModesSmart();
14
+ return getSupportedCleanModesSmart(enableExperimentalFeature);
14
15
 
15
16
  case DeviceModel.S7_MAXV:
16
17
  case DeviceModel.S8_PRO_ULTRA:
17
18
  case DeviceModel.S6_PURE:
18
19
  default:
19
- return getDefaultSupportedCleanModes();
20
+ return getDefaultSupportedCleanModes(enableExperimentalFeature);
20
21
  }
21
22
  }
@@ -25,7 +25,11 @@ export function parseDockingStationStatus(dss: number): DockingStationStatus {
25
25
  };
26
26
  }
27
27
 
28
- export function hasDockingStationError(status: DockingStationStatus): boolean {
28
+ export function hasDockingStationError(status: DockingStationStatus | undefined): boolean {
29
+ if (!status) {
30
+ return false;
31
+ }
32
+
29
33
  return (
30
34
  status.cleanFluidStatus === DockingStationStatusType.Error ||
31
35
  status.waterBoxFilterStatus === DockingStationStatusType.Error ||
@@ -4,6 +4,7 @@ export interface ExperimentalFeatureSetting {
4
4
  showRoutinesAsRoom: boolean;
5
5
  includeDockStationStatus: boolean;
6
6
  forceRunAtDefault: boolean;
7
+ useVacationModeToSendVacuumToDock: boolean;
7
8
  };
8
9
  cleanModeSettings: CleanModeSettings;
9
10
  }
package/src/platform.ts CHANGED
@@ -162,7 +162,7 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
162
162
  this.roborockService.setSupportedScenes(vacuum.duid, routineAsRoom);
163
163
  }
164
164
 
165
- this.robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false, this.log);
165
+ this.robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature, this.log);
166
166
  this.robot.configurateHandler(behaviorHandler);
167
167
 
168
168
  this.log.info('vacuum:', debugStringify(vacuum));
@@ -192,7 +192,10 @@ export class PlatformRunner {
192
192
 
193
193
  const operationalStateId = state_to_matter_operational_status(status);
194
194
  if (operationalStateId) {
195
- platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
195
+ const dssHasError = hasDockingStationError(platform.robot?.dockStationStatus);
196
+ if (!(dssHasError && self.triggerDssError())) {
197
+ platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
198
+ }
196
199
  }
197
200
  break;
198
201
  }
@@ -254,7 +257,15 @@ export class PlatformRunner {
254
257
 
255
258
  private async processAdditionalProps(robot: RoborockVacuumCleaner, message: CloudMessageResult): Promise<void> {
256
259
  // dss -> DockingStationStatus
260
+ const dssStatus = this.getDssStatus(message);
261
+ if (dssStatus) {
262
+ this.triggerDssError();
263
+ }
264
+ }
265
+
266
+ private getDssStatus(message: CloudMessageResult): RvcOperationalState.OperationalState | undefined {
257
267
  const platform = this.platform;
268
+ const robot = platform.robot;
258
269
  if (
259
270
  platform.enableExperimentalFeature &&
260
271
  platform.enableExperimentalFeature.enableExperimentalFeature &&
@@ -262,15 +273,15 @@ export class PlatformRunner {
262
273
  message.dss !== undefined
263
274
  ) {
264
275
  const dss = parseDockingStationStatus(message.dss);
265
- this.platform.log.debug('DockingStationStatus:', debugStringify(dss));
266
-
267
- const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState') as RvcOperationalState.OperationalState;
276
+ if (dss && robot) {
277
+ robot.dockStationStatus = dss;
278
+ }
268
279
 
269
- // Only update docking station status if it is not running
270
- if (dss && hasDockingStationError(dss) && currentOperationState !== RvcOperationalState.OperationalState.Running) {
271
- robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
280
+ if (dss && hasDockingStationError(dss)) {
281
+ return RvcOperationalState.OperationalState.Error;
272
282
  }
273
283
  }
284
+ return undefined;
274
285
  }
275
286
 
276
287
  private isStatusUpdate(result: unknown): boolean {
@@ -317,13 +328,33 @@ export class PlatformRunner {
317
328
  }
318
329
 
319
330
  const operationalStateId = state_to_matter_operational_status(state);
331
+
320
332
  if (operationalStateId) {
321
- this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
322
- platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
333
+ const dssHasError = hasDockingStationError(platform.robot?.dockStationStatus);
334
+ this.platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(platform.robot?.dockStationStatus ?? {})}`);
335
+ if (!(dssHasError && this.triggerDssError())) {
336
+ this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
337
+ platform.robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
338
+ }
323
339
  }
324
340
 
325
341
  if (batteryLevel) {
326
342
  platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
327
343
  }
328
344
  }
345
+
346
+ private triggerDssError(): boolean {
347
+ const platform = this.platform;
348
+ const currentOperationState = platform?.robot?.getAttribute(RvcOperationalState.Cluster.id, 'operationalState') as RvcOperationalState.OperationalState;
349
+ if (currentOperationState === RvcOperationalState.OperationalState.Error) {
350
+ return true;
351
+ }
352
+
353
+ if (currentOperationState === RvcOperationalState.OperationalState.Docked) {
354
+ platform.robot?.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
355
+ return true;
356
+ }
357
+
358
+ return false;
359
+ }
329
360
  }
@@ -67,7 +67,7 @@ export class LocalNetworkClient extends AbstractClient {
67
67
  }
68
68
 
69
69
  private async onDisconnect(): Promise<void> {
70
- this.logger.info('Socket has disconnected.');
70
+ this.logger.notice('LocalNetworkClient: Socket has disconnected.');
71
71
  this.connected = false;
72
72
 
73
73
  if (this.socket) {
@@ -82,7 +82,7 @@ export class LocalNetworkClient extends AbstractClient {
82
82
  }
83
83
 
84
84
  private async onError(result: Error): Promise<void> {
85
- this.logger.error('Socket connection error: ' + result);
85
+ this.logger.error('LocalNetworkClient: Socket connection error: ' + result);
86
86
  this.connected = false;
87
87
 
88
88
  if (this.socket) {
@@ -29,6 +29,7 @@ import type {
29
29
  DeviceStatusNotify,
30
30
  } from './roborockCommunication/index.js';
31
31
  import { ServiceArea } from 'matterbridge/matter/clusters';
32
+ import { LocalNetworkClient } from './roborockCommunication/broadcast/client/LocalNetworkClient.js';
32
33
  export type Factory<A, T> = (logger: AnsiLogger, arg: A) => T;
33
34
 
34
35
  export default class RoborockService {
@@ -227,7 +228,7 @@ export default class RoborockService {
227
228
  await this.messageProcessor.getDeviceStatus(device.duid).then((response: DeviceStatus) => {
228
229
  if (self.deviceNotify) {
229
230
  const message: DeviceStatusNotify = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
230
- self.logger.debug('Device status update xxx', debugStringify(message));
231
+ self.logger.debug('Device status update', debugStringify(message));
231
232
  self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
232
233
  }
233
234
  });
@@ -431,19 +432,25 @@ export default class RoborockService {
431
432
 
432
433
  if (this.ip) {
433
434
  this.logger.debug('initializing the local connection for this client towards ' + this.ip);
434
- this.localClient = this.messageClient.registerClient(device.duid, this.ip);
435
+ this.localClient = this.messageClient.registerClient(device.duid, this.ip) as LocalNetworkClient;
435
436
  this.localClient.connect();
436
437
 
437
- while (!this.localClient.isConnected()) {
438
+ let count = 0;
439
+ while (!this.localClient.isConnected() && count < 20) {
440
+ this.logger.debug('Keep waiting for local client to connect');
441
+ count++;
438
442
  await this.sleep(500);
439
443
  }
444
+
445
+ if (!this.localClient.isConnected()) {
446
+ throw new Error('Local client did not connect after 10 attempts, something is wrong');
447
+ }
448
+
440
449
  this.logger.debug('LocalClient connected');
441
450
  }
442
451
  } catch (error) {
443
452
  this.logger.error('Error requesting network info', error);
444
453
  }
445
-
446
- this.logger.debug('Local client connected');
447
454
  }
448
455
 
449
456
  private sleep(ms: number): Promise<void> {
package/src/rvc.ts CHANGED
@@ -5,20 +5,32 @@ import { getOperationalStates, getSupportedAreas, getSupportedCleanModes, getSup
5
5
  import { AnsiLogger } from 'matterbridge/logger';
6
6
  import { BehaviorFactoryResult } from './behaviorFactory.js';
7
7
  import { ModeBase, RvcOperationalState, ServiceArea } from 'matterbridge/matter/clusters';
8
+ import { ExperimentalFeatureSetting } from './model/ExperimentalFeatureSetting.js';
9
+ import { DockingStationStatus } from './model/DockingStationStatus.js';
8
10
 
9
11
  export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
10
12
  username: string | undefined;
11
13
  device: Device;
12
14
  rrHomeId: number;
13
15
  roomInfo: RoomMap | undefined;
16
+ dockStationStatus: DockingStationStatus | undefined;
14
17
 
15
- constructor(username: string, device: Device, roomMap: RoomMap, routineAsRoom: ServiceArea.Area[], forceRunAtDefault: boolean, log: AnsiLogger) {
16
- const cleanModes = getSupportedCleanModes(device.data.model, forceRunAtDefault);
18
+ constructor(
19
+ username: string,
20
+ device: Device,
21
+ roomMap: RoomMap,
22
+ routineAsRoom: ServiceArea.Area[],
23
+ enableExperimentalFeature: ExperimentalFeatureSetting | undefined,
24
+ log: AnsiLogger,
25
+ ) {
26
+ const cleanModes = getSupportedCleanModes(device.data.model, enableExperimentalFeature);
17
27
  const supportedRunModes = getSupportedRunModes();
18
28
  const supportedAreas = [...getSupportedAreas(device.rooms, roomMap, log), ...routineAsRoom];
19
29
  const deviceName = `${device.name}-${device.duid}`.replace(/\s+/g, '');
20
30
 
21
- log.debug(`Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${forceRunAtDefault}`);
31
+ log.debug(
32
+ `Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${enableExperimentalFeature?.advancedFeature?.forceRunAtDefault}`,
33
+ );
22
34
  log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
23
35
  log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
24
36