matterbridge-roborock-vacuum-plugin 1.0.8-rc09 → 1.1.0-rc02
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 +1 -1
- package/dist/platform.js +49 -27
- package/dist/platformRunner.js +109 -82
- package/dist/rvc.js +3 -4
- package/jest.config.js +2 -1
- package/matterbridge-roborock-vacuum-plugin.config.json +4 -3
- package/matterbridge-roborock-vacuum-plugin.schema.json +43 -11
- package/package.json +1 -1
- package/src/model/ExperimentalFeatureSetting.ts +1 -0
- package/src/platform.ts +60 -29
- package/src/platformRunner.ts +128 -81
- package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts +0 -1
- package/src/roborockService.ts +1 -1
- package/src/rvc.ts +3 -3
- package/src/tests/platformRunner.test.ts +138 -7
- package/src/tests/roborockCommunication/broadcast/client/LocalNetworkClient.test.ts +211 -0
- package/src/tests/roborockCommunication/broadcast/client/MQTTClient.test.ts +333 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedConnectionListener.test.ts +59 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/chainedMessageListener.test.ts +46 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.test.ts +71 -0
- package/src/tests/roborockCommunication/broadcast/listener/implementation/syncMessageListener.test.ts +85 -0
- package/tsconfig.jest.json +6 -0
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ To get the **DUID** for your devices, you have two options:
|
|
|
69
69
|
### 🚧 Project Status
|
|
70
70
|
|
|
71
71
|
- **Under active development**
|
|
72
|
-
- Requires **`matterbridge@3.
|
|
72
|
+
- Requires **`matterbridge@3.1.3`**
|
|
73
73
|
- ⚠️ **Known Issue:** Vacuum may appear as **two devices** in Apple Home
|
|
74
74
|
|
|
75
75
|
---
|
package/dist/platform.js
CHANGED
|
@@ -13,20 +13,20 @@ import { getSupportedAreas, getSupportedScenes } from './initialData/index.js';
|
|
|
13
13
|
import NodePersist from 'node-persist';
|
|
14
14
|
import Path from 'node:path';
|
|
15
15
|
export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
16
|
-
|
|
16
|
+
robots = new Map();
|
|
17
17
|
rvcInterval;
|
|
18
18
|
roborockService;
|
|
19
19
|
clientManager;
|
|
20
20
|
platformRunner;
|
|
21
|
-
devices;
|
|
22
|
-
serialNumber;
|
|
21
|
+
devices = new Map();
|
|
23
22
|
cleanModeSettings;
|
|
24
23
|
enableExperimentalFeature;
|
|
25
24
|
persist;
|
|
25
|
+
rrHomeId;
|
|
26
26
|
constructor(matterbridge, log, config) {
|
|
27
27
|
super(matterbridge, log, config);
|
|
28
|
-
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.
|
|
29
|
-
throw new Error(`This plugin requires Matterbridge version >= "3.
|
|
28
|
+
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.3')) {
|
|
29
|
+
throw new Error(`This plugin requires Matterbridge version >= "3.1.3". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`);
|
|
30
30
|
}
|
|
31
31
|
this.log.info('Initializing platform:', this.config.name);
|
|
32
32
|
if (config.whiteList === undefined)
|
|
@@ -64,22 +64,31 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
64
64
|
this.log.debug('Initializing - userData:', debugStringify(userData));
|
|
65
65
|
const devices = await this.roborockService.listDevices(username);
|
|
66
66
|
this.log.notice('Initializing - devices: ', debugStringify(devices));
|
|
67
|
-
let
|
|
67
|
+
let vacuums = [];
|
|
68
68
|
if (this.config.whiteList.length > 0) {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
69
|
+
const whiteList = (this.config.whiteList ?? []);
|
|
70
|
+
for (const item of whiteList) {
|
|
71
|
+
const duid = item.split('-')[1];
|
|
72
|
+
const vacuum = devices.find((d) => d.duid == duid);
|
|
73
|
+
if (vacuum) {
|
|
74
|
+
vacuums.push(vacuum);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
78
|
else {
|
|
74
|
-
|
|
79
|
+
vacuums = devices.filter((d) => isSupportedDevice(d.data.model));
|
|
75
80
|
}
|
|
76
|
-
if (
|
|
81
|
+
if (vacuums.length === 0) {
|
|
77
82
|
this.log.error('Initializing: No device found');
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
if (!this.enableExperimentalFeature || !this.enableExperimentalFeature.advancedFeature.enableServerMode) {
|
|
86
|
+
vacuums = [vacuums[0]];
|
|
87
|
+
}
|
|
88
|
+
for (const vacuum of vacuums) {
|
|
89
|
+
await this.roborockService.initializeMessageClient(username, vacuum, userData);
|
|
90
|
+
this.devices.set(vacuum.serialNumber, vacuum);
|
|
91
|
+
}
|
|
83
92
|
await this.onConfigurateDevice();
|
|
84
93
|
this.log.notice('onStart finished');
|
|
85
94
|
}
|
|
@@ -96,13 +105,31 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
96
105
|
this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
|
|
97
106
|
return;
|
|
98
107
|
}
|
|
99
|
-
const vacuum = this.devices.get(this.serialNumber);
|
|
100
108
|
const username = this.config.username;
|
|
101
|
-
if (
|
|
109
|
+
if (this.devices.size === 0 || !username) {
|
|
102
110
|
this.log.error('Initializing: No supported devices found');
|
|
103
111
|
return;
|
|
104
112
|
}
|
|
105
113
|
const self = this;
|
|
114
|
+
for (const vacuum of this.devices.values()) {
|
|
115
|
+
await this.configurateDevice(vacuum);
|
|
116
|
+
this.rrHomeId = vacuum.rrHomeId;
|
|
117
|
+
}
|
|
118
|
+
this.roborockService.setDeviceNotify(async function (messageSource, homeData) {
|
|
119
|
+
await self.platformRunner?.updateRobot(messageSource, homeData);
|
|
120
|
+
});
|
|
121
|
+
for (const robot of this.robots.values()) {
|
|
122
|
+
await this.roborockService.activateDeviceNotify(robot.device);
|
|
123
|
+
}
|
|
124
|
+
await this.platformRunner?.requestHomeData();
|
|
125
|
+
this.log.info('onConfigurateDevice finished');
|
|
126
|
+
}
|
|
127
|
+
async configurateDevice(vacuum) {
|
|
128
|
+
const username = this.config.username;
|
|
129
|
+
if (this.platformRunner === undefined || this.roborockService === undefined) {
|
|
130
|
+
this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
106
133
|
await this.roborockService.initializeMessageClientForLocal(vacuum);
|
|
107
134
|
const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
|
|
108
135
|
this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
|
|
@@ -113,19 +140,14 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
113
140
|
routineAsRoom = getSupportedScenes(vacuum.scenes, this.log);
|
|
114
141
|
this.roborockService.setSupportedScenes(vacuum.duid, routineAsRoom);
|
|
115
142
|
}
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
const robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature, this.log);
|
|
144
|
+
robot.configurateHandler(behaviorHandler);
|
|
118
145
|
this.log.info('vacuum:', debugStringify(vacuum));
|
|
119
|
-
this.setSelectDevice(
|
|
120
|
-
if (this.validateDevice(
|
|
121
|
-
await this.registerDevice(
|
|
146
|
+
this.setSelectDevice(robot.serialNumber ?? '', robot.deviceName ?? '', undefined, 'hub');
|
|
147
|
+
if (this.validateDevice(robot.deviceName ?? '')) {
|
|
148
|
+
await this.registerDevice(robot);
|
|
122
149
|
}
|
|
123
|
-
this.
|
|
124
|
-
await self.platformRunner?.updateRobot(messageSource, homeData);
|
|
125
|
-
});
|
|
126
|
-
await this.roborockService.activateDeviceNotify(this.robot.device);
|
|
127
|
-
await self.platformRunner?.requestHomeData();
|
|
128
|
-
this.log.info('onConfigurateDevice finished');
|
|
150
|
+
this.robots.set(robot.serialNumber ?? '', robot);
|
|
129
151
|
}
|
|
130
152
|
async onShutdown(reason) {
|
|
131
153
|
await super.onShutdown(reason);
|
package/dist/platformRunner.js
CHANGED
|
@@ -25,11 +25,11 @@ export class PlatformRunner {
|
|
|
25
25
|
}
|
|
26
26
|
async requestHomeData() {
|
|
27
27
|
const platform = this.platform;
|
|
28
|
-
if (
|
|
28
|
+
if (platform.robots.size === 0 || !platform.rrHomeId)
|
|
29
29
|
return;
|
|
30
30
|
if (platform.roborockService === undefined)
|
|
31
31
|
return;
|
|
32
|
-
const homeData = await platform.roborockService.getHomeDataForUpdating(platform.
|
|
32
|
+
const homeData = await platform.roborockService.getHomeDataForUpdating(platform.rrHomeId);
|
|
33
33
|
await this.updateRobot(NotifyMessageTypes.HomeData, homeData);
|
|
34
34
|
}
|
|
35
35
|
async getRoomMapFromDevice(device) {
|
|
@@ -41,21 +41,32 @@ export class PlatformRunner {
|
|
|
41
41
|
}
|
|
42
42
|
return new RoomMap([], rooms);
|
|
43
43
|
}
|
|
44
|
-
async getRoomMap() {
|
|
44
|
+
async getRoomMap(duid) {
|
|
45
45
|
const platform = this.platform;
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
46
|
+
const robot = platform.robots.get(duid);
|
|
47
|
+
if (robot === undefined) {
|
|
48
|
+
platform.log.error(`Error6: Robot with DUID ${duid} not found`);
|
|
48
49
|
return undefined;
|
|
49
|
-
if (platform.robot.roomInfo === undefined) {
|
|
50
|
-
const roomData = await platform.roborockService.getRoomMappings(platform.robot.device.duid);
|
|
51
|
-
platform.robot.roomInfo = new RoomMap(roomData ?? [], rooms);
|
|
52
|
-
return platform.robot.roomInfo;
|
|
53
50
|
}
|
|
54
|
-
|
|
51
|
+
if (platform.roborockService === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
const rooms = robot.device.rooms ?? [];
|
|
54
|
+
if (robot.roomInfo === undefined) {
|
|
55
|
+
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
56
|
+
robot.roomInfo = new RoomMap(roomData ?? [], rooms);
|
|
57
|
+
return robot.roomInfo;
|
|
58
|
+
}
|
|
59
|
+
return robot.roomInfo;
|
|
55
60
|
}
|
|
56
|
-
async updateFromMQTTMessage(messageSource, messageData, tracked = false) {
|
|
61
|
+
async updateFromMQTTMessage(messageSource, messageData, duid = '', tracked = false) {
|
|
57
62
|
const platform = this.platform;
|
|
58
|
-
|
|
63
|
+
duid = duid || messageData?.duid || '';
|
|
64
|
+
const robot = platform.robots.get(duid);
|
|
65
|
+
if (robot === undefined) {
|
|
66
|
+
platform.log.error(`Error1: Robot with DUID ${duid} not found`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const deviceData = robot.device.data;
|
|
59
70
|
if (deviceData === undefined) {
|
|
60
71
|
platform.log.error('Device data is undefined');
|
|
61
72
|
return;
|
|
@@ -63,24 +74,17 @@ export class PlatformRunner {
|
|
|
63
74
|
if (!tracked) {
|
|
64
75
|
platform.log.debug(`${messageSource} updateFromMQTTMessage: ${debugStringify(messageData)}`);
|
|
65
76
|
}
|
|
66
|
-
|
|
67
|
-
if (platform.robot === undefined)
|
|
68
|
-
return;
|
|
69
|
-
if (!platform.robot.serialNumber) {
|
|
77
|
+
if (!robot.serialNumber) {
|
|
70
78
|
platform.log.error('Robot serial number is undefined');
|
|
71
79
|
return;
|
|
72
80
|
}
|
|
73
|
-
if (platform.robot.serialNumber !== duid) {
|
|
74
|
-
platform.log.notice(`DUID mismatch: ${duid}, device serial number: ${platform.robot.serialNumber}`);
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
81
|
switch (messageSource) {
|
|
78
82
|
case NotifyMessageTypes.ErrorOccurred: {
|
|
79
83
|
const message = messageData;
|
|
80
84
|
const operationalStateId = getOperationalErrorState(message.errorCode);
|
|
81
85
|
if (operationalStateId) {
|
|
82
86
|
platform.log.error(`Error occurred: ${message.errorCode}`);
|
|
83
|
-
|
|
87
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
84
88
|
}
|
|
85
89
|
break;
|
|
86
90
|
}
|
|
@@ -88,32 +92,37 @@ export class PlatformRunner {
|
|
|
88
92
|
const message = messageData;
|
|
89
93
|
const batteryLevel = message.percentage;
|
|
90
94
|
if (batteryLevel) {
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
|
|
96
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
93
97
|
}
|
|
94
98
|
break;
|
|
95
99
|
}
|
|
96
100
|
case NotifyMessageTypes.LocalMessage: {
|
|
97
101
|
const data = messageData;
|
|
102
|
+
const robot = platform.robots.get(duid);
|
|
103
|
+
if (!robot) {
|
|
104
|
+
platform.log.error(`Error2: Robot with DUID ${duid} not found`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
98
107
|
if (data) {
|
|
99
108
|
const state = state_to_matter_state(data.state);
|
|
100
109
|
if (state) {
|
|
101
|
-
|
|
110
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
|
|
102
111
|
}
|
|
103
112
|
const currentRoom = data.cleaning_info?.segment_id ?? -1;
|
|
104
113
|
const currentMappedAreas = this.platform.roborockService?.getSupportedAreas(duid);
|
|
105
114
|
const isMappedArea = currentMappedAreas?.some((x) => x.areaId == currentRoom);
|
|
106
115
|
if (currentRoom !== -1 && isMappedArea) {
|
|
107
|
-
const roomMap = await this.getRoomMap();
|
|
116
|
+
const roomMap = await this.getRoomMap(duid);
|
|
108
117
|
this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
|
|
109
118
|
this.platform.log.debug('CurrentRoom:', currentRoom);
|
|
110
|
-
|
|
119
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
|
|
111
120
|
}
|
|
112
121
|
if (data.battery) {
|
|
113
122
|
const batteryLevel = data.battery;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
|
|
124
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(data.state, data.battery), platform.log);
|
|
125
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
117
126
|
}
|
|
118
127
|
const currentCleanModeSetting = {
|
|
119
128
|
suctionPower: data.cleaning_info?.fan_power ?? data.fan_power,
|
|
@@ -127,10 +136,10 @@ export class PlatformRunner {
|
|
|
127
136
|
const currentCleanMode = getCurrentCleanModeFunc(deviceData.model, this.platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)(currentCleanModeSetting);
|
|
128
137
|
this.platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
129
138
|
if (currentCleanMode) {
|
|
130
|
-
|
|
139
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
131
140
|
}
|
|
132
141
|
}
|
|
133
|
-
this.processAdditionalProps(
|
|
142
|
+
this.processAdditionalProps(robot, data, duid);
|
|
134
143
|
}
|
|
135
144
|
break;
|
|
136
145
|
}
|
|
@@ -149,19 +158,24 @@ export class PlatformRunner {
|
|
|
149
158
|
const platform = this.platform;
|
|
150
159
|
const messageTypes = Object.keys(data.dps).map(Number);
|
|
151
160
|
const self = this;
|
|
161
|
+
const robot = platform.robots.get(duid);
|
|
162
|
+
if (robot === undefined) {
|
|
163
|
+
platform.log.error(`Error3: Robot with DUID ${duid} not found`);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
152
166
|
messageTypes.forEach(async (messageType) => {
|
|
153
167
|
switch (messageType) {
|
|
154
168
|
case Protocol.status_update: {
|
|
155
169
|
const status = Number(data.dps[messageType]);
|
|
156
170
|
const matterState = state_to_matter_state(status);
|
|
157
171
|
if (matterState) {
|
|
158
|
-
|
|
172
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
159
173
|
}
|
|
160
174
|
const operationalStateId = state_to_matter_operational_status(status);
|
|
161
175
|
if (operationalStateId) {
|
|
162
|
-
const dssHasError = hasDockingStationError(
|
|
163
|
-
if (!(dssHasError && self.triggerDssError())) {
|
|
164
|
-
|
|
176
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
177
|
+
if (!(dssHasError && self.triggerDssError(robot))) {
|
|
178
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
165
179
|
}
|
|
166
180
|
}
|
|
167
181
|
break;
|
|
@@ -177,17 +191,17 @@ export class PlatformRunner {
|
|
|
177
191
|
roboStatus = response.result[0];
|
|
178
192
|
}
|
|
179
193
|
if (roboStatus) {
|
|
180
|
-
const message = {
|
|
194
|
+
const message = { ...roboStatus };
|
|
181
195
|
platform.log.debug('rpc_response:', debugStringify(message));
|
|
182
|
-
await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, true);
|
|
196
|
+
await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, duid, true);
|
|
183
197
|
}
|
|
184
198
|
break;
|
|
185
199
|
}
|
|
186
200
|
case Protocol.suction_power:
|
|
187
201
|
case Protocol.water_box_mode: {
|
|
188
202
|
await platform.roborockService?.getCleanModeData(duid).then((cleanModeData) => {
|
|
189
|
-
if (cleanModeData
|
|
190
|
-
const currentCleanMode = getCurrentCleanModeFunc(
|
|
203
|
+
if (cleanModeData) {
|
|
204
|
+
const currentCleanMode = getCurrentCleanModeFunc(robot.device.data.model, platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false)({
|
|
191
205
|
suctionPower: cleanModeData.suctionPower,
|
|
192
206
|
waterFlow: cleanModeData.waterFlow,
|
|
193
207
|
distance_off: cleanModeData.distance_off,
|
|
@@ -196,7 +210,7 @@ export class PlatformRunner {
|
|
|
196
210
|
platform.log.debug(`Clean mode data: ${debugStringify(cleanModeData)}`);
|
|
197
211
|
platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
198
212
|
if (currentCleanMode) {
|
|
199
|
-
|
|
213
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
200
214
|
}
|
|
201
215
|
}
|
|
202
216
|
});
|
|
@@ -213,15 +227,19 @@ export class PlatformRunner {
|
|
|
213
227
|
}
|
|
214
228
|
});
|
|
215
229
|
}
|
|
216
|
-
async processAdditionalProps(robot, message) {
|
|
217
|
-
const dssStatus = this.getDssStatus(message);
|
|
230
|
+
async processAdditionalProps(robot, message, duid) {
|
|
231
|
+
const dssStatus = this.getDssStatus(message, duid);
|
|
218
232
|
if (dssStatus) {
|
|
219
|
-
this.triggerDssError();
|
|
233
|
+
this.triggerDssError(robot);
|
|
220
234
|
}
|
|
221
235
|
}
|
|
222
|
-
getDssStatus(message) {
|
|
236
|
+
getDssStatus(message, duid) {
|
|
223
237
|
const platform = this.platform;
|
|
224
|
-
const robot = platform.
|
|
238
|
+
const robot = platform.robots.get(duid);
|
|
239
|
+
if (robot === undefined) {
|
|
240
|
+
platform.log.error(`Error4: Robot with DUID ${duid} not found`);
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
225
243
|
if (platform.enableExperimentalFeature &&
|
|
226
244
|
platform.enableExperimentalFeature.enableExperimentalFeature &&
|
|
227
245
|
platform.enableExperimentalFeature.advancedFeature.includeDockStationStatus &&
|
|
@@ -247,52 +265,61 @@ export class PlatformRunner {
|
|
|
247
265
|
}
|
|
248
266
|
updateFromHomeData(homeData) {
|
|
249
267
|
const platform = this.platform;
|
|
250
|
-
if (platform.
|
|
268
|
+
if (platform.robots.size === 0)
|
|
251
269
|
return;
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
270
|
+
const devices = homeData.devices.filter((d) => platform.robots.has(d.duid));
|
|
271
|
+
for (const device of devices) {
|
|
272
|
+
const robot = platform.robots.get(device.duid);
|
|
273
|
+
if (robot === undefined) {
|
|
274
|
+
platform.log.error(`Error5: Robot with DUID ${device.duid} not found`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const deviceData = robot.device.data;
|
|
278
|
+
if (!device || deviceData === undefined) {
|
|
279
|
+
platform.log.error('Device not found in home data');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
device.schema = homeData.products.find((prd) => prd.id === device.productId || prd.model === device.data.model)?.schema ?? [];
|
|
283
|
+
this.platform.log.debug('updateFromHomeData-homeData:', debugStringify(homeData));
|
|
284
|
+
this.platform.log.debug('updateFromHomeData-device:', debugStringify(device));
|
|
285
|
+
const batteryLevel = getVacuumProperty(device, 'battery');
|
|
286
|
+
this.platform.log.debug('updateFromHomeData-schema:' + debugStringify(device.schema));
|
|
287
|
+
this.platform.log.debug('updateFromHomeData-battery:' + debugStringify(device.deviceStatus));
|
|
288
|
+
if (batteryLevel) {
|
|
289
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel ? batteryLevel * 2 : 200, platform.log);
|
|
290
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
291
|
+
}
|
|
292
|
+
const state = getVacuumProperty(device, 'state');
|
|
293
|
+
const matterState = state_to_matter_state(state);
|
|
294
|
+
if (!state || !matterState) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
this.platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
|
|
298
|
+
if (matterState) {
|
|
299
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
300
|
+
}
|
|
301
|
+
const operationalStateId = state_to_matter_operational_status(state);
|
|
302
|
+
if (operationalStateId) {
|
|
303
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
304
|
+
this.platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(robot.dockStationStatus ?? {})}`);
|
|
305
|
+
if (!(dssHasError && this.triggerDssError(robot))) {
|
|
306
|
+
this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
|
|
307
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (batteryLevel) {
|
|
311
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
|
|
282
312
|
}
|
|
283
|
-
}
|
|
284
|
-
if (batteryLevel) {
|
|
285
|
-
platform.robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
|
|
286
313
|
}
|
|
287
314
|
}
|
|
288
|
-
triggerDssError() {
|
|
315
|
+
triggerDssError(robot) {
|
|
289
316
|
const platform = this.platform;
|
|
290
|
-
const currentOperationState =
|
|
317
|
+
const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState');
|
|
291
318
|
if (currentOperationState === RvcOperationalState.OperationalState.Error) {
|
|
292
319
|
return true;
|
|
293
320
|
}
|
|
294
321
|
if (currentOperationState === RvcOperationalState.OperationalState.Docked) {
|
|
295
|
-
|
|
322
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
|
|
296
323
|
return true;
|
|
297
324
|
}
|
|
298
325
|
return false;
|
package/dist/rvc.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { RoboticVacuumCleaner } from 'matterbridge';
|
|
1
|
+
import { RoboticVacuumCleaner } from 'matterbridge/devices';
|
|
2
2
|
import { getOperationalStates, getSupportedAreas, getSupportedCleanModes, getSupportedRunModes } from './initialData/index.js';
|
|
3
3
|
import { RvcOperationalState } from 'matterbridge/matter/clusters';
|
|
4
4
|
export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
|
|
5
5
|
username;
|
|
6
6
|
device;
|
|
7
|
-
rrHomeId;
|
|
8
7
|
roomInfo;
|
|
9
8
|
dockStationStatus;
|
|
10
9
|
constructor(username, device, roomMap, routineAsRoom, enableExperimentalFeature, log) {
|
|
@@ -15,10 +14,10 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
|
|
|
15
14
|
log.debug(`Creating RoborockVacuumCleaner for device: ${deviceName}, model: ${device.data.model}, forceRunAtDefault: ${enableExperimentalFeature?.advancedFeature?.forceRunAtDefault}`);
|
|
16
15
|
log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
|
|
17
16
|
log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
|
|
18
|
-
|
|
17
|
+
const bridgeMode = enableExperimentalFeature?.advancedFeature.enableServerMode ? 'server' : undefined;
|
|
18
|
+
super(deviceName, device.duid, bridgeMode, supportedRunModes[0].mode, supportedRunModes, cleanModes[0].mode, cleanModes, undefined, undefined, RvcOperationalState.OperationalState.Docked, getOperationalStates(), supportedAreas, undefined, supportedAreas[0].areaId);
|
|
19
19
|
this.username = username;
|
|
20
20
|
this.device = device;
|
|
21
|
-
this.rrHomeId = device.rrHomeId;
|
|
22
21
|
}
|
|
23
22
|
configurateHandler(behaviorHandler) {
|
|
24
23
|
this.addCommandHandler('identify', async ({ request, cluster, attributes, endpoint }) => {
|
package/jest.config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export default {
|
|
2
|
+
preset: 'ts-jest/presets/default-esm',
|
|
2
3
|
testEnvironment: 'node',
|
|
3
4
|
extensionsToTreatAsEsm: ['.ts'],
|
|
4
5
|
moduleNameMapper: {
|
|
@@ -15,7 +16,7 @@ export default {
|
|
|
15
16
|
],
|
|
16
17
|
},
|
|
17
18
|
transformIgnorePatterns: ['/node_modules/'],
|
|
18
|
-
testPathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/'],
|
|
19
|
+
testPathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/', 'webui', '.shouldnotcommit'],
|
|
19
20
|
coveragePathIgnorePatterns: ['/node_modules/', '/dist/', '/frontend/', '/src/mock/'],
|
|
20
21
|
setupFiles: ['./jest.setup.js'],
|
|
21
22
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "matterbridge-roborock-vacuum-plugin",
|
|
3
3
|
"type": "DynamicPlatform",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0-rc02",
|
|
5
5
|
"whiteList": [],
|
|
6
6
|
"blackList": [],
|
|
7
7
|
"useInterval": true,
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"showRoutinesAsRooms": false,
|
|
12
12
|
"includeDockStationStatus": false,
|
|
13
13
|
"forceRunAtDefault": false,
|
|
14
|
-
"useVacationModeToSendVacuumToDock": false
|
|
14
|
+
"useVacationModeToSendVacuumToDock": false,
|
|
15
|
+
"enableServerMode": false
|
|
15
16
|
},
|
|
16
17
|
"cleanModeSettings": {
|
|
17
18
|
"enableCleanModeMapping": false,
|
|
@@ -35,4 +36,4 @@
|
|
|
35
36
|
"debug": true,
|
|
36
37
|
"unregisterOnShutdown": false,
|
|
37
38
|
"enableExperimentalFeature": false
|
|
38
|
-
}
|
|
39
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"title": "Matterbridge Roborock Vacuum Plugin",
|
|
3
|
-
"description": "matterbridge-roborock-vacuum-plugin v. 1.0
|
|
3
|
+
"description": "matterbridge-roborock-vacuum-plugin v. 1.1.0-rc02 by https://github.com/RinDevJunior",
|
|
4
4
|
"type": "object",
|
|
5
|
-
"required": [
|
|
5
|
+
"required": [
|
|
6
|
+
"username",
|
|
7
|
+
"password"
|
|
8
|
+
],
|
|
6
9
|
"properties": {
|
|
7
10
|
"name": {
|
|
8
11
|
"description": "Plugin name",
|
|
@@ -57,7 +60,9 @@
|
|
|
57
60
|
"const": true
|
|
58
61
|
}
|
|
59
62
|
},
|
|
60
|
-
"required": [
|
|
63
|
+
"required": [
|
|
64
|
+
"enableExperimentalFeature"
|
|
65
|
+
]
|
|
61
66
|
},
|
|
62
67
|
"then": {
|
|
63
68
|
"properties": {
|
|
@@ -81,7 +86,12 @@
|
|
|
81
86
|
"default": false
|
|
82
87
|
},
|
|
83
88
|
"useVacationModeToSendVacuumToDock": {
|
|
84
|
-
"title": "Use Vacation Mode To Send Vacuum To Dock",
|
|
89
|
+
"title": "Use \"Vacation\" Mode To Send Vacuum To Dock",
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"default": false
|
|
92
|
+
},
|
|
93
|
+
"enableServerMode": {
|
|
94
|
+
"description": "Enable the Robot Vacuum Cleaner in server mode (Each vacuum will have its own server).",
|
|
85
95
|
"type": "boolean",
|
|
86
96
|
"default": false
|
|
87
97
|
}
|
|
@@ -104,7 +114,9 @@
|
|
|
104
114
|
"const": true
|
|
105
115
|
}
|
|
106
116
|
},
|
|
107
|
-
"required": [
|
|
117
|
+
"required": [
|
|
118
|
+
"enableCleanModeMapping"
|
|
119
|
+
]
|
|
108
120
|
},
|
|
109
121
|
"then": {
|
|
110
122
|
"properties": {
|
|
@@ -149,7 +161,9 @@
|
|
|
149
161
|
"default": 25
|
|
150
162
|
}
|
|
151
163
|
},
|
|
152
|
-
"required": [
|
|
164
|
+
"required": [
|
|
165
|
+
"distanceOff"
|
|
166
|
+
]
|
|
153
167
|
}
|
|
154
168
|
}
|
|
155
169
|
]
|
|
@@ -186,7 +200,9 @@
|
|
|
186
200
|
"default": 25
|
|
187
201
|
}
|
|
188
202
|
},
|
|
189
|
-
"required": [
|
|
203
|
+
"required": [
|
|
204
|
+
"distanceOff"
|
|
205
|
+
]
|
|
190
206
|
}
|
|
191
207
|
}
|
|
192
208
|
]
|
|
@@ -216,20 +232,36 @@
|
|
|
216
232
|
"fanMode": {
|
|
217
233
|
"type": "string",
|
|
218
234
|
"description": "Suction power mode to use (e.g., 'Quiet', 'Balanced', 'Turbo', 'Max', 'MaxPlus').",
|
|
219
|
-
"enum": [
|
|
235
|
+
"enum": [
|
|
236
|
+
"Quiet",
|
|
237
|
+
"Balanced",
|
|
238
|
+
"Turbo",
|
|
239
|
+
"Max",
|
|
240
|
+
"MaxPlus"
|
|
241
|
+
],
|
|
220
242
|
"default": "Balanced"
|
|
221
243
|
},
|
|
222
244
|
"waterFlowMode": {
|
|
223
245
|
"type": "string",
|
|
224
246
|
"description": "Water flow mode to use (e.g., 'Low', 'Medium', 'High', 'CustomizeWithDistanceOff').",
|
|
225
|
-
"enum": [
|
|
247
|
+
"enum": [
|
|
248
|
+
"Low",
|
|
249
|
+
"Medium",
|
|
250
|
+
"High",
|
|
251
|
+
"CustomizeWithDistanceOff"
|
|
252
|
+
],
|
|
226
253
|
"default": "Medium"
|
|
227
254
|
},
|
|
228
255
|
"mopRouteMode": {
|
|
229
256
|
"type": "string",
|
|
230
257
|
"description": "Mop route intensity to use (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
|
|
231
|
-
"enum": [
|
|
258
|
+
"enum": [
|
|
259
|
+
"Standard",
|
|
260
|
+
"Deep",
|
|
261
|
+
"DeepPlus",
|
|
262
|
+
"Fast"
|
|
263
|
+
],
|
|
232
264
|
"default": "Standard"
|
|
233
265
|
}
|
|
234
266
|
}
|
|
235
|
-
}
|
|
267
|
+
}
|