matterbridge-roborock-vacuum-plugin 1.0.7-rc01 → 1.0.7-rc03

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
@@ -42,13 +42,23 @@
42
42
  ---
43
43
  ### 🆔 How to Get Your DUID
44
44
 
45
- To get the **DUID** for your devices. Follow the steps below to retrieve it:
45
+ To get the **DUID** for your devices, you have two options:
46
46
 
47
+ **Option 1: From Matterbridge Logs**
47
48
  1. **Start Matterbridge** with the plugin enabled.
48
49
  2. **Watch the Docker console logs directly** (not the Matterbridge UI logs, as they may be truncated).
49
50
  3. Look for the log message that says:
50
51
  ```text
51
52
  Initializing - devices: [...]
53
+ ```
54
+
55
+ **Option 2: From the Roborock App**
56
+ 1. Open the **Roborock app** on your phone.
57
+ 2. Go to your **Device**.
58
+ 3. Tap **Settings** > **Product Info**.
59
+ 4. Find the **DID** field. The value will look like `rr_xxxxxxx`.
60
+ 5. **Remove the `rr_` prefix** from the DID value. The remaining string is your DUID.
61
+
52
62
  ---
53
63
  ### 🚧 Project Status
54
64
 
@@ -2,6 +2,7 @@ import { BehaviorDeviceGeneric } from './behaviors/BehaviorDeviceGeneric.js';
2
2
  import { setCommandHandlerA187 } from './behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.js';
3
3
  import { setDefaultCommandHandler } from './behaviors/roborock.vacuum/default/default.js';
4
4
  import { DeviceModel } from './roborockCommunication/Zmodel/deviceModel.js';
5
+ import { setCommandHandlerA27 } from './behaviors/roborock.vacuum/S7_MAXV/a27.js';
5
6
  export function configurateBehavior(model, duid, roborockService, logger) {
6
7
  switch (model) {
7
8
  case DeviceModel.QREVO_EDGE_5V1: {
@@ -9,6 +10,11 @@ export function configurateBehavior(model, duid, roborockService, logger) {
9
10
  setCommandHandlerA187(duid, deviceHandler, logger, roborockService);
10
11
  return deviceHandler;
11
12
  }
13
+ case DeviceModel.S7_MAXV: {
14
+ const deviceHandler = new BehaviorDeviceGeneric(logger);
15
+ setCommandHandlerA27(duid, deviceHandler, logger, roborockService);
16
+ return deviceHandler;
17
+ }
12
18
  default: {
13
19
  const deviceHandler = new BehaviorDeviceGeneric(logger);
14
20
  setDefaultCommandHandler(duid, deviceHandler, logger, roborockService);
@@ -0,0 +1,97 @@
1
+ import { debugStringify } from 'matterbridge/logger';
2
+ import { BehaviorRoborock } from '../../BehaviorDeviceGeneric.js';
3
+ export class BehaviorA27 extends BehaviorRoborock {
4
+ }
5
+ export var BehaviorRoborockA27;
6
+ (function (BehaviorRoborockA27) {
7
+ class State {
8
+ device;
9
+ }
10
+ BehaviorRoborockA27.State = State;
11
+ })(BehaviorRoborockA27 || (BehaviorRoborockA27 = {}));
12
+ export var VacuumSuctionPowerA27;
13
+ (function (VacuumSuctionPowerA27) {
14
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["Quiet"] = 101] = "Quiet";
15
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["Balanced"] = 102] = "Balanced";
16
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["Turbo"] = 103] = "Turbo";
17
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["Max"] = 104] = "Max";
18
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["Off"] = 105] = "Off";
19
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["Custom"] = 106] = "Custom";
20
+ VacuumSuctionPowerA27[VacuumSuctionPowerA27["MaxPlus"] = 108] = "MaxPlus";
21
+ })(VacuumSuctionPowerA27 || (VacuumSuctionPowerA27 = {}));
22
+ export var MopWaterFlowA27;
23
+ (function (MopWaterFlowA27) {
24
+ MopWaterFlowA27[MopWaterFlowA27["Off"] = 200] = "Off";
25
+ MopWaterFlowA27[MopWaterFlowA27["Low"] = 201] = "Low";
26
+ MopWaterFlowA27[MopWaterFlowA27["Medium"] = 202] = "Medium";
27
+ MopWaterFlowA27[MopWaterFlowA27["High"] = 203] = "High";
28
+ MopWaterFlowA27[MopWaterFlowA27["Custom"] = 204] = "Custom";
29
+ })(MopWaterFlowA27 || (MopWaterFlowA27 = {}));
30
+ export var MopRouteA27;
31
+ (function (MopRouteA27) {
32
+ MopRouteA27[MopRouteA27["Standard"] = 300] = "Standard";
33
+ MopRouteA27[MopRouteA27["Deep"] = 301] = "Deep";
34
+ MopRouteA27[MopRouteA27["Custom"] = 302] = "Custom";
35
+ MopRouteA27[MopRouteA27["DeepPlus"] = 303] = "DeepPlus";
36
+ MopRouteA27[MopRouteA27["Fast"] = 304] = "Fast";
37
+ })(MopRouteA27 || (MopRouteA27 = {}));
38
+ const RvcRunMode = {
39
+ [1]: 'Idle',
40
+ [2]: 'Cleaning',
41
+ [3]: 'Mapping',
42
+ };
43
+ const RvcCleanMode = {
44
+ [5]: 'Mop',
45
+ [6]: 'Vacuum',
46
+ [7]: 'Vac & Mop',
47
+ [8]: 'Custom',
48
+ };
49
+ const CleanSetting = {
50
+ [5]: { suctionPower: VacuumSuctionPowerA27.Off, waterFlow: MopWaterFlowA27.Medium, mopRoute: MopRouteA27.Custom },
51
+ [6]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Off, mopRoute: MopRouteA27.Custom },
52
+ [7]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Medium, mopRoute: MopRouteA27.Custom },
53
+ [8]: { suctionPower: VacuumSuctionPowerA27.Custom, waterFlow: MopWaterFlowA27.Custom, mopRoute: MopRouteA27.Custom },
54
+ };
55
+ export function setCommandHandlerA27(duid, handler, logger, roborockService) {
56
+ handler.setCommandHandler('changeToMode', async (newMode) => {
57
+ const activity = RvcRunMode[newMode] || RvcCleanMode[newMode];
58
+ switch (activity) {
59
+ case 'Cleaning': {
60
+ logger.notice('BehaviorA27-ChangeRunMode to: ', activity);
61
+ await roborockService.startClean(duid);
62
+ return;
63
+ }
64
+ case 'Mop':
65
+ case 'Vacuum':
66
+ case 'Vac & Mop':
67
+ case 'Custom': {
68
+ const setting = CleanSetting[newMode];
69
+ logger.notice(`BehaviorA27-ChangeCleanMode to: ${activity}, code: ${debugStringify(setting)}`);
70
+ return;
71
+ }
72
+ default:
73
+ logger.notice('BehaviorA27-changeToMode-Unknown: ', newMode);
74
+ return;
75
+ }
76
+ });
77
+ handler.setCommandHandler('selectAreas', async (newAreas) => {
78
+ logger.notice(`BehaviorA27-selectAreas: ${newAreas}`);
79
+ roborockService.setSelectedAreas(duid, newAreas ?? []);
80
+ });
81
+ handler.setCommandHandler('pause', async () => {
82
+ logger.notice('BehaviorA27-Pause');
83
+ await roborockService.pauseClean(duid);
84
+ });
85
+ handler.setCommandHandler('resume', async () => {
86
+ logger.notice('BehaviorA27-Resume');
87
+ await roborockService.resumeClean(duid);
88
+ });
89
+ handler.setCommandHandler('goHome', async () => {
90
+ logger.notice('BehaviorA27-GoHome');
91
+ await roborockService.stopAndGoHome(duid);
92
+ });
93
+ handler.setCommandHandler('PlaySoundToLocate', async (identifyTime) => {
94
+ logger.notice('BehaviorA27-PlaySoundToLocate');
95
+ await roborockService.playSoundToLocate(duid);
96
+ });
97
+ }
@@ -0,0 +1,76 @@
1
+ import { RvcCleanMode, RvcOperationalState, RvcRunMode } from 'matterbridge/matter/clusters';
2
+ export function getSupportedRunModesA27() {
3
+ return [
4
+ {
5
+ label: 'Idle',
6
+ mode: 1,
7
+ modeTags: [{ value: RvcRunMode.ModeTag.Idle }],
8
+ },
9
+ {
10
+ label: 'Cleaning',
11
+ mode: 2,
12
+ modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }],
13
+ },
14
+ {
15
+ label: 'Mapping',
16
+ mode: 3,
17
+ modeTags: [{ value: RvcRunMode.ModeTag.Mapping }],
18
+ },
19
+ ];
20
+ }
21
+ export function getSupportedCleanModesA27() {
22
+ return [
23
+ {
24
+ label: 'Mop',
25
+ mode: 5,
26
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Auto }],
27
+ },
28
+ {
29
+ label: 'Vacuum',
30
+ mode: 6,
31
+ modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Auto }],
32
+ },
33
+ {
34
+ label: 'Mop & Vacuum',
35
+ mode: 7,
36
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.DeepClean }],
37
+ },
38
+ {
39
+ label: 'Custom',
40
+ mode: 8,
41
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Quick }],
42
+ },
43
+ ];
44
+ }
45
+ export function getOperationalStatesA27() {
46
+ return [
47
+ {
48
+ operationalStateId: RvcOperationalState.OperationalState.Stopped,
49
+ operationalStateLabel: 'Stopped',
50
+ },
51
+ {
52
+ operationalStateId: RvcOperationalState.OperationalState.Running,
53
+ operationalStateLabel: 'Running',
54
+ },
55
+ {
56
+ operationalStateId: RvcOperationalState.OperationalState.Paused,
57
+ operationalStateLabel: 'Paused',
58
+ },
59
+ {
60
+ operationalStateId: RvcOperationalState.OperationalState.Error,
61
+ operationalStateLabel: 'Error',
62
+ },
63
+ {
64
+ operationalStateId: RvcOperationalState.OperationalState.SeekingCharger,
65
+ operationalStateLabel: 'SeekingCharger',
66
+ },
67
+ {
68
+ operationalStateId: RvcOperationalState.OperationalState.Charging,
69
+ operationalStateLabel: 'Charging',
70
+ },
71
+ {
72
+ operationalStateId: RvcOperationalState.OperationalState.Docked,
73
+ operationalStateLabel: 'Docked',
74
+ },
75
+ ];
76
+ }
@@ -0,0 +1,33 @@
1
+ import { MopWaterFlowA27, VacuumSuctionPowerA27 } from './a27.js';
2
+ export function getCurrentCleanModeA27(fan_power, water_box_mode) {
3
+ if (!fan_power || !water_box_mode)
4
+ return undefined;
5
+ if (fan_power == VacuumSuctionPowerA27.Custom || water_box_mode == MopWaterFlowA27.Custom)
6
+ return 8;
7
+ if (fan_power == VacuumSuctionPowerA27.Off)
8
+ return 5;
9
+ if (water_box_mode == MopWaterFlowA27.Off)
10
+ return 6;
11
+ else
12
+ return 7;
13
+ }
14
+ export function getCurrentCleanModeFromFanPowerA27(fan_power) {
15
+ if (!fan_power)
16
+ return undefined;
17
+ if (fan_power == VacuumSuctionPowerA27.Custom)
18
+ return 8;
19
+ if (fan_power == VacuumSuctionPowerA27.Off)
20
+ return 5;
21
+ else
22
+ return undefined;
23
+ }
24
+ export function getCurrentCleanModeFromWaterBoxModeA27(water_box_mode) {
25
+ if (!water_box_mode)
26
+ return undefined;
27
+ if (water_box_mode == MopWaterFlowA27.Custom)
28
+ return 8;
29
+ if (water_box_mode == MopWaterFlowA27.Off)
30
+ return 6;
31
+ else
32
+ return undefined;
33
+ }
@@ -3,10 +3,13 @@ import { getOperationalStatesA187 } from '../behaviors/roborock.vacuum/QREVO_EDG
3
3
  import { getDefaultOperationalStates } from '../behaviors/roborock.vacuum/default/initalData.js';
4
4
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
5
5
  import { VacuumErrorCode } from '../roborockCommunication/Zenum/vacuumAndDockErrorCode.js';
6
+ import { getOperationalStatesA27 } from '../behaviors/roborock.vacuum/S7_MAXV/initalData.js';
6
7
  export function getOperationalStates(model) {
7
8
  switch (model) {
8
9
  case DeviceModel.QREVO_EDGE_5V1:
9
10
  return getOperationalStatesA187();
11
+ case DeviceModel.S7_MAXV:
12
+ return getOperationalStatesA27();
10
13
  default:
11
14
  return getDefaultOperationalStates();
12
15
  }
@@ -19,13 +19,13 @@ export function getSupportedAreas(rooms, roomMap, log) {
19
19
  },
20
20
  ];
21
21
  }
22
- const supportedAreas = rooms.map((room, index) => {
22
+ const supportedAreas = roomMap.rooms.map((room, index) => {
23
23
  return {
24
- areaId: roomMap.getRoomId(room.id) ?? index + 1,
24
+ areaId: room.id,
25
25
  mapId: null,
26
26
  areaInfo: {
27
27
  locationInfo: {
28
- locationName: room.name,
28
+ locationName: room.displayName ?? rooms.find((r) => r.id == room.globalId)?.name ?? 'Unknown Room',
29
29
  floorNumber: null,
30
30
  areaType: null,
31
31
  },
@@ -37,7 +37,7 @@ export function getSupportedAreas(rooms, roomMap, log) {
37
37
  return duplicated
38
38
  ? [
39
39
  {
40
- areaId: 1,
40
+ areaId: 2,
41
41
  mapId: null,
42
42
  areaInfo: {
43
43
  locationInfo: {
@@ -1,10 +1,13 @@
1
1
  import { getSupportedCleanModesA187 } from '../behaviors/roborock.vacuum/QREVO_EDGE_5V1/initalData.js';
2
2
  import { getDefaultSupportedCleanModes } from '../behaviors/roborock.vacuum/default/initalData.js';
3
3
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
4
+ import { getSupportedCleanModesA27 } from '../behaviors/roborock.vacuum/S7_MAXV/initalData.js';
4
5
  export function getSupportedCleanModes(model) {
5
6
  switch (model) {
6
7
  case DeviceModel.QREVO_EDGE_5V1:
7
8
  return getSupportedCleanModesA187();
9
+ case DeviceModel.S7_MAXV:
10
+ return getSupportedCleanModesA27();
8
11
  default:
9
12
  return getDefaultSupportedCleanModes();
10
13
  }
@@ -1,6 +1,7 @@
1
1
  import { getSupportedRunModesA187 } from '../behaviors/roborock.vacuum/QREVO_EDGE_5V1/initalData.js';
2
2
  import { getDefaultSupportedRunModes } from '../behaviors/roborock.vacuum/default/initalData.js';
3
3
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
4
+ import { getSupportedRunModesA27 } from '../behaviors/roborock.vacuum/S7_MAXV/initalData.js';
4
5
  export function getRunningMode(model, modeTag) {
5
6
  if (!model || !modeTag)
6
7
  return null;
@@ -12,6 +13,8 @@ export function getSupportedRunModes(model) {
12
13
  switch (model) {
13
14
  case DeviceModel.QREVO_EDGE_5V1:
14
15
  return getSupportedRunModesA187();
16
+ case DeviceModel.S7_MAXV:
17
+ return getSupportedRunModesA27();
15
18
  default:
16
19
  return getDefaultSupportedRunModes();
17
20
  }
@@ -9,10 +9,4 @@ export default class RoomMap {
9
9
  };
10
10
  });
11
11
  }
12
- getGlobalId(id) {
13
- return this.rooms.find((r) => Number(r.id) == Number(id))?.globalId;
14
- }
15
- getRoomId(globalId) {
16
- return this.rooms.find((r) => Number(r.globalId) == Number(globalId))?.id;
17
- }
18
12
  }
package/dist/platform.js CHANGED
@@ -73,13 +73,14 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
73
73
  let vacuum = undefined;
74
74
  if (this.config.whiteList.length > 0) {
75
75
  const firstDUID = this.config.whiteList[0];
76
- vacuum = devices.find((d) => d.duid == firstDUID);
76
+ const duid = firstDUID.split('-')[1];
77
+ vacuum = devices.find((d) => d.duid == duid);
77
78
  }
78
79
  else {
79
80
  vacuum = devices.find((d) => isSupportedDevice(d.data.model));
80
81
  }
81
82
  if (!vacuum) {
82
- this.log.error('Initializing: No supported devices found');
83
+ this.log.error('Initializing: No device found');
83
84
  return;
84
85
  }
85
86
  await this.roborockService.initializeMessageClient(username, vacuum, userData);
package/dist/rvc.js CHANGED
@@ -10,7 +10,8 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
10
10
  const cleanModes = getSupportedCleanModes(device.data.model);
11
11
  const supportedRunModes = getSupportedRunModes(device.data.model);
12
12
  const supportedAreas = getSupportedAreas(device.rooms, roomMap, log);
13
- super(device.name, device.duid, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(device.data.model), supportedAreas, undefined, supportedAreas[0].areaId);
13
+ const deviceName = `${device.name}-${device.duid}`.replace(/\s+/g, '');
14
+ super(deviceName, device.duid, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(device.data.model), supportedAreas, undefined, supportedAreas[0].areaId);
14
15
  this.username = username;
15
16
  this.device = device;
16
17
  this.rrHomeId = device.rrHomeId;
@@ -1,10 +1,14 @@
1
1
  import { getCurrentCleanModeA187, getCurrentCleanModeFromFanPowerA187, getCurrentCleanModeFromWaterBoxModeA187 } from '../behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.js';
2
+ import { getCurrentCleanModeA27, getCurrentCleanModeFromFanPowerA27, getCurrentCleanModeFromWaterBoxModeA27 } from '../behaviors/roborock.vacuum/S7_MAXV/runtimes.js';
2
3
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
3
4
  export function getCurrentCleanModeFunc(model) {
4
5
  switch (model) {
5
6
  case DeviceModel.QREVO_EDGE_5V1: {
6
7
  return getCurrentCleanModeA187;
7
8
  }
9
+ case DeviceModel.S7_MAXV: {
10
+ return getCurrentCleanModeA27;
11
+ }
8
12
  default:
9
13
  return (_, __) => undefined;
10
14
  }
@@ -14,6 +18,9 @@ export function getCurrentCleanModeFromFanPowerFunc(model) {
14
18
  case DeviceModel.QREVO_EDGE_5V1: {
15
19
  return getCurrentCleanModeFromFanPowerA187;
16
20
  }
21
+ case DeviceModel.S7_MAXV: {
22
+ return getCurrentCleanModeFromFanPowerA27;
23
+ }
17
24
  default:
18
25
  return (_) => undefined;
19
26
  }
@@ -23,6 +30,9 @@ export function getCurrentCleanModeFromWaterBoxModeFunc(model) {
23
30
  case DeviceModel.QREVO_EDGE_5V1: {
24
31
  return getCurrentCleanModeFromWaterBoxModeA187;
25
32
  }
33
+ case DeviceModel.S7_MAXV: {
34
+ return getCurrentCleanModeFromWaterBoxModeA27;
35
+ }
26
36
  default:
27
37
  return (_) => undefined;
28
38
  }
package/jest.config.js ADDED
@@ -0,0 +1,19 @@
1
+ export default {
2
+ testEnvironment: 'node',
3
+ extensionsToTreatAsEsm: ['.ts'],
4
+ moduleNameMapper: {
5
+ '^(\\.{1,2}/.*)\\.js$': '$1',
6
+ },
7
+ transform: {
8
+ '^.+\\.ts$': [
9
+ 'ts-jest',
10
+ {
11
+ useESM: true,
12
+ tsconfig: 'tsconfig.jest.json',
13
+ },
14
+ ],
15
+ },
16
+ transformIgnorePatterns: ['/node_modules/'],
17
+ testPathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/'],
18
+ coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/', '/src/mock/'],
19
+ };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
3
  "type": "DynamicPlatform",
4
- "version": "1.0.7-rc01",
4
+ "version": "1.0.7-rc03",
5
5
  "whiteList": [],
6
6
  "blackList": [],
7
7
  "useInterval": true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "title": "Matterbridge Roborock Vacuum Plugin",
3
- "description": "matterbridge-roborock-vacuum-plugin v. 1.0.7-rc01 by https://github.com/RinDevJunior",
3
+ "description": "matterbridge-roborock-vacuum-plugin v. 1.0.7-rc03 by https://github.com/RinDevJunior",
4
4
  "type": "object",
5
5
  "required": [],
6
6
  "properties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "matterbridge-roborock-vacuum-plugin",
3
- "version": "1.0.7-rc01",
3
+ "version": "1.0.7-rc03",
4
4
  "description": "Matterbridge Roborock Vacuum Plugin",
5
5
  "author": "https://github.com/RinDevJunior",
6
6
  "license": "MIT",
@@ -4,6 +4,7 @@ import { EndpointCommandsA187, setCommandHandlerA187 } from './behaviors/roboroc
4
4
  import RoborockService from './roborockService.js';
5
5
  import { DefaultEndpointCommands, setDefaultCommandHandler } from './behaviors/roborock.vacuum/default/default.js';
6
6
  import { DeviceModel } from './roborockCommunication/Zmodel/deviceModel.js';
7
+ import { EndpointCommandsA27, setCommandHandlerA27 } from './behaviors/roborock.vacuum/S7_MAXV/a27.js';
7
8
 
8
9
  export type BehaviorFactoryResult = BehaviorDeviceGeneric<DefaultEndpointCommands> | BehaviorDeviceGeneric<EndpointCommandsA187>;
9
10
 
@@ -15,6 +16,12 @@ export function configurateBehavior(model: string, duid: string, roborockService
15
16
  return deviceHandler;
16
17
  }
17
18
 
19
+ case DeviceModel.S7_MAXV: {
20
+ const deviceHandler = new BehaviorDeviceGeneric<EndpointCommandsA27>(logger);
21
+ setCommandHandlerA27(duid, deviceHandler, logger, roborockService);
22
+ return deviceHandler;
23
+ }
24
+
18
25
  default: {
19
26
  const deviceHandler = new BehaviorDeviceGeneric<DefaultEndpointCommands>(logger);
20
27
  setDefaultCommandHandler(duid, deviceHandler, logger, roborockService);
@@ -0,0 +1,121 @@
1
+ import { MaybePromise } from 'matterbridge/matter';
2
+ import { AnsiLogger, debugStringify } from 'matterbridge/logger';
3
+ import { BehaviorDeviceGeneric, BehaviorRoborock, DeviceCommands } from '../../BehaviorDeviceGeneric.js';
4
+ import RoborockService from '../../../roborockService.js';
5
+
6
+ export interface EndpointCommandsA27 extends DeviceCommands {
7
+ selectAreas: (newAreas: any) => MaybePromise;
8
+ changeToMode: (newMode: number) => MaybePromise;
9
+ pause: () => MaybePromise;
10
+ resume: () => MaybePromise;
11
+ goHome: () => MaybePromise;
12
+ PlaySoundToLocate: (identifyTime: number) => MaybePromise;
13
+ }
14
+
15
+ export class BehaviorA27 extends BehaviorRoborock {
16
+ declare state: BehaviorRoborockA27.State;
17
+ }
18
+
19
+ export namespace BehaviorRoborockA27 {
20
+ export class State {
21
+ device!: BehaviorDeviceGeneric<EndpointCommandsA27>;
22
+ }
23
+ }
24
+
25
+ //suction_power
26
+ export enum VacuumSuctionPowerA27 {
27
+ Quiet = 101,
28
+ Balanced = 102,
29
+ Turbo = 103,
30
+ Max = 104,
31
+ Off = 105,
32
+ Custom = 106,
33
+ MaxPlus = 108,
34
+ }
35
+
36
+ //water_box_mode
37
+ export enum MopWaterFlowA27 {
38
+ Off = 200,
39
+ Low = 201,
40
+ Medium = 202,
41
+ High = 203,
42
+ Custom = 204,
43
+ }
44
+
45
+ //set_mop_mode
46
+ export enum MopRouteA27 {
47
+ Standard = 300,
48
+ Deep = 301,
49
+ Custom = 302,
50
+ DeepPlus = 303,
51
+ Fast = 304,
52
+ }
53
+
54
+ const RvcRunMode: Record<number, string> = {
55
+ [1]: 'Idle', //DO NOT HANDLE HERE,
56
+ [2]: 'Cleaning',
57
+ [3]: 'Mapping',
58
+ };
59
+ const RvcCleanMode: Record<number, string> = {
60
+ [5]: 'Mop',
61
+ [6]: 'Vacuum',
62
+ [7]: 'Vac & Mop',
63
+ [8]: 'Custom',
64
+ };
65
+
66
+ const CleanSetting: Record<number, { suctionPower: number; waterFlow: number; mopRoute: number }> = {
67
+ [5]: { suctionPower: VacuumSuctionPowerA27.Off, waterFlow: MopWaterFlowA27.Medium, mopRoute: MopRouteA27.Custom }, //'Mop'
68
+ [6]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Off, mopRoute: MopRouteA27.Custom }, //'Vacuum'
69
+ [7]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Medium, mopRoute: MopRouteA27.Custom }, //'Vac & Mop'
70
+ [8]: { suctionPower: VacuumSuctionPowerA27.Custom, waterFlow: MopWaterFlowA27.Custom, mopRoute: MopRouteA27.Custom }, // 'Custom'
71
+ };
72
+
73
+ export function setCommandHandlerA27(duid: string, handler: BehaviorDeviceGeneric<DeviceCommands>, logger: AnsiLogger, roborockService: RoborockService): void {
74
+ handler.setCommandHandler('changeToMode', async (newMode: number) => {
75
+ const activity = RvcRunMode[newMode] || RvcCleanMode[newMode];
76
+ switch (activity) {
77
+ case 'Cleaning': {
78
+ logger.notice('BehaviorA27-ChangeRunMode to: ', activity);
79
+ await roborockService.startClean(duid);
80
+ return;
81
+ }
82
+ case 'Mop':
83
+ case 'Vacuum':
84
+ case 'Vac & Mop':
85
+ case 'Custom': {
86
+ const setting = CleanSetting[newMode];
87
+ logger.notice(`BehaviorA27-ChangeCleanMode to: ${activity}, code: ${debugStringify(setting)}`);
88
+ //await roborockService.changeCleanMode(duid, setting);
89
+ return;
90
+ }
91
+ default:
92
+ logger.notice('BehaviorA27-changeToMode-Unknown: ', newMode);
93
+ return;
94
+ }
95
+ });
96
+
97
+ handler.setCommandHandler('selectAreas', async (newAreas: number[]) => {
98
+ logger.notice(`BehaviorA27-selectAreas: ${newAreas}`);
99
+ roborockService.setSelectedAreas(duid, newAreas ?? []);
100
+ });
101
+
102
+ handler.setCommandHandler('pause', async () => {
103
+ logger.notice('BehaviorA27-Pause');
104
+ await roborockService.pauseClean(duid);
105
+ });
106
+
107
+ handler.setCommandHandler('resume', async () => {
108
+ logger.notice('BehaviorA27-Resume');
109
+ await roborockService.resumeClean(duid);
110
+ });
111
+
112
+ handler.setCommandHandler('goHome', async () => {
113
+ logger.notice('BehaviorA27-GoHome');
114
+ await roborockService.stopAndGoHome(duid);
115
+ });
116
+
117
+ handler.setCommandHandler('PlaySoundToLocate', async (identifyTime: number) => {
118
+ logger.notice('BehaviorA27-PlaySoundToLocate');
119
+ await roborockService.playSoundToLocate(duid);
120
+ });
121
+ }
@@ -0,0 +1,80 @@
1
+ import { RvcCleanMode, RvcOperationalState, RvcRunMode } from 'matterbridge/matter/clusters';
2
+
3
+ export function getSupportedRunModesA27(): RvcRunMode.ModeOption[] {
4
+ return [
5
+ {
6
+ label: 'Idle',
7
+ mode: 1,
8
+ modeTags: [{ value: RvcRunMode.ModeTag.Idle }],
9
+ },
10
+ {
11
+ label: 'Cleaning',
12
+ mode: 2,
13
+ modeTags: [{ value: RvcRunMode.ModeTag.Cleaning }],
14
+ },
15
+ {
16
+ label: 'Mapping',
17
+ mode: 3,
18
+ modeTags: [{ value: RvcRunMode.ModeTag.Mapping }],
19
+ },
20
+ ];
21
+ }
22
+
23
+ export function getSupportedCleanModesA27(): RvcCleanMode.ModeOption[] {
24
+ return [
25
+ {
26
+ label: 'Mop',
27
+ mode: 5,
28
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Auto }],
29
+ },
30
+ {
31
+ label: 'Vacuum',
32
+ mode: 6,
33
+ modeTags: [{ value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Auto }],
34
+ },
35
+ {
36
+ label: 'Mop & Vacuum',
37
+ mode: 7,
38
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.DeepClean }],
39
+ },
40
+
41
+ {
42
+ label: 'Custom',
43
+ mode: 8,
44
+ modeTags: [{ value: RvcCleanMode.ModeTag.Mop }, { value: RvcCleanMode.ModeTag.Vacuum }, { value: RvcCleanMode.ModeTag.Quick }],
45
+ },
46
+ ];
47
+ }
48
+
49
+ export function getOperationalStatesA27(): RvcOperationalState.OperationalStateStruct[] {
50
+ return [
51
+ {
52
+ operationalStateId: RvcOperationalState.OperationalState.Stopped,
53
+ operationalStateLabel: 'Stopped',
54
+ },
55
+ {
56
+ operationalStateId: RvcOperationalState.OperationalState.Running,
57
+ operationalStateLabel: 'Running',
58
+ },
59
+ {
60
+ operationalStateId: RvcOperationalState.OperationalState.Paused,
61
+ operationalStateLabel: 'Paused',
62
+ },
63
+ {
64
+ operationalStateId: RvcOperationalState.OperationalState.Error,
65
+ operationalStateLabel: 'Error',
66
+ },
67
+ {
68
+ operationalStateId: RvcOperationalState.OperationalState.SeekingCharger,
69
+ operationalStateLabel: 'SeekingCharger',
70
+ },
71
+ {
72
+ operationalStateId: RvcOperationalState.OperationalState.Charging,
73
+ operationalStateLabel: 'Charging',
74
+ },
75
+ {
76
+ operationalStateId: RvcOperationalState.OperationalState.Docked,
77
+ operationalStateLabel: 'Docked',
78
+ },
79
+ ];
80
+ }
@@ -0,0 +1,26 @@
1
+ import { MopWaterFlowA27, VacuumSuctionPowerA27 } from './a27.js';
2
+
3
+ export function getCurrentCleanModeA27(fan_power: number | undefined, water_box_mode: number | undefined): number | undefined {
4
+ if (!fan_power || !water_box_mode) return undefined;
5
+ if (fan_power == VacuumSuctionPowerA27.Custom || water_box_mode == MopWaterFlowA27.Custom) return 8; // 'Custom',
6
+ if (fan_power == VacuumSuctionPowerA27.Off) return 5; // 'Mop',
7
+ if (water_box_mode == MopWaterFlowA27.Off)
8
+ return 6; // 'Vacuum',
9
+ else return 7; //Vac & Mop
10
+ }
11
+
12
+ export function getCurrentCleanModeFromFanPowerA27(fan_power: number | undefined): number | undefined {
13
+ if (!fan_power) return undefined;
14
+ if (fan_power == VacuumSuctionPowerA27.Custom) return 8; // 'Custom',
15
+ if (fan_power == VacuumSuctionPowerA27.Off)
16
+ return 5; // 'Mop',
17
+ else return undefined;
18
+ }
19
+
20
+ export function getCurrentCleanModeFromWaterBoxModeA27(water_box_mode: number | undefined): number | undefined {
21
+ if (!water_box_mode) return undefined;
22
+ if (water_box_mode == MopWaterFlowA27.Custom) return 8; // 'Custom',
23
+ if (water_box_mode == MopWaterFlowA27.Off)
24
+ return 6; // 'Vacuum',
25
+ else return undefined;
26
+ }
@@ -3,11 +3,14 @@ import { getOperationalStatesA187 } from '../behaviors/roborock.vacuum/QREVO_EDG
3
3
  import { getDefaultOperationalStates } from '../behaviors/roborock.vacuum/default/initalData.js';
4
4
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
5
5
  import { VacuumErrorCode } from '../roborockCommunication/Zenum/vacuumAndDockErrorCode.js';
6
+ import { getOperationalStatesA27 } from '../behaviors/roborock.vacuum/S7_MAXV/initalData.js';
6
7
 
7
8
  export function getOperationalStates(model: string): RvcOperationalState.OperationalStateStruct[] {
8
9
  switch (model) {
9
10
  case DeviceModel.QREVO_EDGE_5V1:
10
11
  return getOperationalStatesA187();
12
+ case DeviceModel.S7_MAXV:
13
+ return getOperationalStatesA27();
11
14
  default:
12
15
  return getDefaultOperationalStates();
13
16
  }
@@ -0,0 +1,55 @@
1
+ import { getSupportedAreas } from '../initialData/getSupportedAreas';
2
+ import RoomMap from '../model/RoomMap';
3
+ import { jest } from '@jest/globals';
4
+
5
+ const mockLogger = {
6
+ debug: jest.fn(),
7
+ error: jest.fn(),
8
+ };
9
+
10
+ describe('getSupportedAreas', () => {
11
+ beforeEach(() => {
12
+ jest.clearAllMocks();
13
+ });
14
+
15
+ it('returns default area when rooms and roomMap are empty', () => {
16
+ const result = getSupportedAreas(
17
+ [
18
+ { id: 2775739, name: 'Garage' },
19
+ { id: 1474466, name: 'Outside' },
20
+ { id: 1459590, name: 'AAA room' },
21
+ { id: 1459587, name: 'BBB’s Room' },
22
+ { id: 1459580, name: 'CCC’s room' },
23
+ { id: 1458155, name: 'Dining room' },
24
+ { id: 1457889, name: 'Bathroom' },
25
+ { id: 1457888, name: 'Living room' },
26
+ { id: 991195, name: 'Downstairs Bathroom' },
27
+ { id: 991190, name: 'Garage Entryway' },
28
+ { id: 991187, name: 'TV Room' },
29
+ { id: 991185, name: 'Bedroom' },
30
+ { id: 612205, name: 'DDD’s Room' },
31
+ { id: 612204, name: 'Upstairs Bathroom' },
32
+ { id: 612202, name: 'Hallway' },
33
+ { id: 612200, name: 'EEE Room' },
34
+ { id: 609148, name: 'Dining Room' },
35
+ { id: 609146, name: 'Kitchen' },
36
+ { id: 609145, name: 'Living Room' },
37
+ ],
38
+ {
39
+ rooms: [
40
+ { id: 16, globalId: 609146, displayName: 'Kitchen' },
41
+ { id: 17, globalId: 1457889, displayName: 'Bathroom' },
42
+ { id: 18, globalId: 612202, displayName: 'Hallway' },
43
+ { id: 19, globalId: 1458155, displayName: 'Dining room' },
44
+ { id: 20, globalId: 1457888, displayName: 'Living room' },
45
+ { id: 21, globalId: 1459580, displayName: 'BBB’s room' },
46
+ { id: 22, globalId: 612205, displayName: 'CCC’s Room' },
47
+ { id: 23, globalId: 1474466, displayName: 'Outside' },
48
+ ],
49
+ } as RoomMap,
50
+ mockLogger as any,
51
+ );
52
+
53
+ expect(result.length).toEqual(8);
54
+ });
55
+ });
@@ -3,23 +3,6 @@ import { ServiceArea } from 'matterbridge/matter/clusters';
3
3
  import RoomMap from '../model/RoomMap.js';
4
4
  import { Room } from '../roborockCommunication/Zmodel/room.js';
5
5
 
6
- /*
7
- rooms = [
8
- { id: 123456, name: 'Study' },
9
- { id: 123457, name: 'Bedroom' },
10
- { id: 123458, name: 'Kitchen' },
11
- { id: 123459, name: 'Living room' }
12
- ]
13
- roomMap = {
14
- rooms: [
15
- { id: 1, globalId: "123456", displayName: undefined },
16
- { id: 2, globalId: "123457", displayName: undefined },
17
- { id: 3, globalId: "123458", displayName: undefined },
18
- { id: 4, globalId: "123459", displayName: undefined },
19
- ],
20
- };
21
- */
22
-
23
6
  export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, log?: AnsiLogger): ServiceArea.Area[] {
24
7
  log?.debug('getSupportedAreas', debugStringify(rooms));
25
8
  log?.debug('getSupportedAreas', roomMap ? debugStringify(roomMap) : 'undefined');
@@ -42,13 +25,13 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
42
25
  ];
43
26
  }
44
27
 
45
- const supportedAreas: ServiceArea.Area[] = rooms.map((room, index) => {
28
+ const supportedAreas: ServiceArea.Area[] = roomMap.rooms.map((room, index) => {
46
29
  return {
47
- areaId: roomMap.getRoomId(room.id) ?? index + 1,
30
+ areaId: room.id,
48
31
  mapId: null,
49
32
  areaInfo: {
50
33
  locationInfo: {
51
- locationName: room.name,
34
+ locationName: room.displayName ?? rooms.find((r) => r.id == room.globalId)?.name ?? 'Unknown Room',
52
35
  floorNumber: null,
53
36
  areaType: null,
54
37
  },
@@ -62,7 +45,7 @@ export function getSupportedAreas(rooms: Room[], roomMap: RoomMap | undefined, l
62
45
  return duplicated
63
46
  ? [
64
47
  {
65
- areaId: 1,
48
+ areaId: 2,
66
49
  mapId: null,
67
50
  areaInfo: {
68
51
  locationInfo: {
@@ -2,11 +2,14 @@ import { RvcCleanMode } from 'matterbridge/matter/clusters';
2
2
  import { getSupportedCleanModesA187 } from '../behaviors/roborock.vacuum/QREVO_EDGE_5V1/initalData.js';
3
3
  import { getDefaultSupportedCleanModes } from '../behaviors/roborock.vacuum/default/initalData.js';
4
4
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
5
+ import { getSupportedCleanModesA27 } from '../behaviors/roborock.vacuum/S7_MAXV/initalData.js';
5
6
 
6
7
  export function getSupportedCleanModes(model: string): RvcCleanMode.ModeOption[] {
7
8
  switch (model) {
8
9
  case DeviceModel.QREVO_EDGE_5V1:
9
10
  return getSupportedCleanModesA187();
11
+ case DeviceModel.S7_MAXV:
12
+ return getSupportedCleanModesA27();
10
13
  default:
11
14
  return getDefaultSupportedCleanModes();
12
15
  }
@@ -2,6 +2,7 @@ import { RvcRunMode } from 'matterbridge/matter/clusters';
2
2
  import { getSupportedRunModesA187 } from '../behaviors/roborock.vacuum/QREVO_EDGE_5V1/initalData.js';
3
3
  import { getDefaultSupportedRunModes } from '../behaviors/roborock.vacuum/default/initalData.js';
4
4
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
5
+ import { getSupportedRunModesA27 } from '../behaviors/roborock.vacuum/S7_MAXV/initalData.js';
5
6
 
6
7
  export function getRunningMode(model: string | undefined, modeTag: RvcRunMode.ModeTag | undefined): number | null {
7
8
  if (!model || !modeTag) return null;
@@ -15,6 +16,8 @@ export function getSupportedRunModes(model: string): RvcRunMode.ModeOption[] {
15
16
  switch (model) {
16
17
  case DeviceModel.QREVO_EDGE_5V1:
17
18
  return getSupportedRunModesA187();
19
+ case DeviceModel.S7_MAXV:
20
+ return getSupportedRunModesA27();
18
21
  default:
19
22
  return getDefaultSupportedRunModes();
20
23
  }
@@ -34,11 +34,11 @@ export default class RoomMap {
34
34
  });
35
35
  }
36
36
 
37
- getGlobalId(id: number): number | undefined {
38
- return this.rooms.find((r) => Number(r.id) == Number(id))?.globalId;
39
- }
37
+ // getGlobalId(id: number): number | undefined {
38
+ // return this.rooms.find((r) => Number(r.id) == Number(id))?.globalId;
39
+ // }
40
40
 
41
- getRoomId(globalId: number): number | undefined {
42
- return this.rooms.find((r) => Number(r.globalId) == Number(globalId))?.id;
43
- }
41
+ // getRoomId(globalId: number): number | undefined {
42
+ // return this.rooms.find((r) => Number(r.globalId) == Number(globalId))?.id;
43
+ // }
44
44
  }
package/src/platform.ts CHANGED
@@ -97,13 +97,14 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
97
97
  let vacuum: Device | undefined = undefined;
98
98
  if ((this.config.whiteList as string[]).length > 0) {
99
99
  const firstDUID = (this.config.whiteList as string[])[0];
100
- vacuum = devices.find((d) => d.duid == firstDUID);
100
+ const duid = firstDUID.split('-')[1];
101
+ vacuum = devices.find((d) => d.duid == duid);
101
102
  } else {
102
103
  vacuum = devices.find((d) => isSupportedDevice(d.data.model));
103
104
  }
104
105
 
105
106
  if (!vacuum) {
106
- this.log.error('Initializing: No supported devices found');
107
+ this.log.error('Initializing: No device found');
107
108
  return;
108
109
  }
109
110
  await this.roborockService.initializeMessageClient(username, vacuum, userData);
package/src/rvc.ts CHANGED
@@ -16,8 +16,9 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
16
16
  const cleanModes = getSupportedCleanModes(device.data.model);
17
17
  const supportedRunModes = getSupportedRunModes(device.data.model);
18
18
  const supportedAreas = getSupportedAreas(device.rooms, roomMap, log);
19
+ const deviceName = `${device.name}-${device.duid}`.replace(/\s+/g, '');
19
20
  super(
20
- device.name, //name
21
+ deviceName, //name
21
22
  device.duid, //serial
22
23
  supportedRunModes[0].mode, //currentRunMode
23
24
  supportedRunModes, //supportedRunModes
@@ -1,4 +1,5 @@
1
1
  import { getCurrentCleanModeA187, getCurrentCleanModeFromFanPowerA187, getCurrentCleanModeFromWaterBoxModeA187 } from '../behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.js';
2
+ import { getCurrentCleanModeA27, getCurrentCleanModeFromFanPowerA27, getCurrentCleanModeFromWaterBoxModeA27 } from '../behaviors/roborock.vacuum/S7_MAXV/runtimes.js';
2
3
  import { DeviceModel } from '../roborockCommunication/Zmodel/deviceModel.js';
3
4
 
4
5
  export type CleanModeFunc1 = (fan_power: number | undefined, water_box_mode: number | undefined) => number | undefined;
@@ -9,6 +10,9 @@ export function getCurrentCleanModeFunc(model: string): CleanModeFunc1 {
9
10
  case DeviceModel.QREVO_EDGE_5V1: {
10
11
  return getCurrentCleanModeA187;
11
12
  }
13
+ case DeviceModel.S7_MAXV: {
14
+ return getCurrentCleanModeA27;
15
+ }
12
16
  default:
13
17
  return (_, __) => undefined;
14
18
  }
@@ -19,6 +23,9 @@ export function getCurrentCleanModeFromFanPowerFunc(model: string): CleanModeFun
19
23
  case DeviceModel.QREVO_EDGE_5V1: {
20
24
  return getCurrentCleanModeFromFanPowerA187;
21
25
  }
26
+ case DeviceModel.S7_MAXV: {
27
+ return getCurrentCleanModeFromFanPowerA27;
28
+ }
22
29
  default:
23
30
  return (_) => undefined;
24
31
  }
@@ -29,6 +36,9 @@ export function getCurrentCleanModeFromWaterBoxModeFunc(model: string): CleanMod
29
36
  case DeviceModel.QREVO_EDGE_5V1: {
30
37
  return getCurrentCleanModeFromWaterBoxModeA187;
31
38
  }
39
+ case DeviceModel.S7_MAXV: {
40
+ return getCurrentCleanModeFromWaterBoxModeA27;
41
+ }
32
42
  default:
33
43
  return (_) => undefined;
34
44
  }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ // enable isolatedModules just for ts-jest
5
+ "isolatedModules": true
6
+ },
7
+ "include": [
8
+ "**/*.spec.ts",
9
+ "**/*.test.ts",
10
+ "**/__test__/*"
11
+ ]
12
+ }