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/package.json
CHANGED
package/src/platform.ts
CHANGED
|
@@ -17,24 +17,24 @@ import NodePersist from 'node-persist';
|
|
|
17
17
|
import Path from 'node:path';
|
|
18
18
|
|
|
19
19
|
export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
20
|
-
|
|
20
|
+
robots: Map<string, RoborockVacuumCleaner> = new Map<string, RoborockVacuumCleaner>();
|
|
21
21
|
rvcInterval: NodeJS.Timeout | undefined;
|
|
22
22
|
roborockService: RoborockService | undefined;
|
|
23
23
|
clientManager: ClientManager;
|
|
24
24
|
platformRunner: PlatformRunner | undefined;
|
|
25
|
-
devices: Map<string, Device
|
|
26
|
-
serialNumber: string | undefined;
|
|
25
|
+
devices: Map<string, Device> = new Map<string, Device>();
|
|
27
26
|
cleanModeSettings: CleanModeSettings | undefined;
|
|
28
27
|
enableExperimentalFeature: ExperimentalFeatureSetting | undefined;
|
|
29
28
|
persist: NodePersist.LocalStorage;
|
|
29
|
+
rrHomeId: number | undefined;
|
|
30
30
|
|
|
31
31
|
constructor(matterbridge: Matterbridge, log: AnsiLogger, config: PlatformConfig) {
|
|
32
32
|
super(matterbridge, log, config);
|
|
33
33
|
|
|
34
34
|
// Verify that Matterbridge is the correct version
|
|
35
|
-
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.
|
|
35
|
+
if (this.verifyMatterbridgeVersion === undefined || typeof this.verifyMatterbridgeVersion !== 'function' || !this.verifyMatterbridgeVersion('3.1.3')) {
|
|
36
36
|
throw new Error(
|
|
37
|
-
`This plugin requires Matterbridge version >= "3.
|
|
37
|
+
`This plugin requires Matterbridge version >= "3.1.3". Please update Matterbridge from ${this.matterbridge.matterbridgeVersion} to the latest version in the frontend.`,
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
this.log.info('Initializing platform:', this.config.name);
|
|
@@ -92,22 +92,33 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
92
92
|
const devices = await this.roborockService.listDevices(username);
|
|
93
93
|
this.log.notice('Initializing - devices: ', debugStringify(devices));
|
|
94
94
|
|
|
95
|
-
let
|
|
95
|
+
let vacuums: Device[] = [];
|
|
96
96
|
if ((this.config.whiteList as string[]).length > 0) {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
97
|
+
const whiteList = (this.config.whiteList ?? []) as string[];
|
|
98
|
+
for (const item of whiteList) {
|
|
99
|
+
const duid = item.split('-')[1];
|
|
100
|
+
const vacuum = devices.find((d) => d.duid == duid);
|
|
101
|
+
if (vacuum) {
|
|
102
|
+
vacuums.push(vacuum);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
100
105
|
} else {
|
|
101
|
-
|
|
106
|
+
vacuums = devices.filter((d) => isSupportedDevice(d.data.model));
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
if (
|
|
109
|
+
if (vacuums.length === 0) {
|
|
105
110
|
this.log.error('Initializing: No device found');
|
|
106
111
|
return;
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
this.
|
|
110
|
-
|
|
113
|
+
|
|
114
|
+
if (!this.enableExperimentalFeature || !this.enableExperimentalFeature.advancedFeature.enableServerMode) {
|
|
115
|
+
vacuums = [vacuums[0]]; // If server mode is not enabled, only use the first vacuum
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (const vacuum of vacuums) {
|
|
119
|
+
await this.roborockService.initializeMessageClient(username, vacuum, userData);
|
|
120
|
+
this.devices.set(vacuum.serialNumber, vacuum);
|
|
121
|
+
}
|
|
111
122
|
|
|
112
123
|
await this.onConfigurateDevice();
|
|
113
124
|
this.log.notice('onStart finished');
|
|
@@ -131,15 +142,42 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
131
142
|
this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
|
|
132
143
|
return;
|
|
133
144
|
}
|
|
134
|
-
|
|
145
|
+
|
|
135
146
|
const username = this.config.username as string;
|
|
136
|
-
if (
|
|
147
|
+
if (this.devices.size === 0 || !username) {
|
|
137
148
|
this.log.error('Initializing: No supported devices found');
|
|
138
149
|
return;
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
142
153
|
const self = this;
|
|
154
|
+
|
|
155
|
+
for (const vacuum of this.devices.values()) {
|
|
156
|
+
await this.configurateDevice(vacuum);
|
|
157
|
+
this.rrHomeId = vacuum.rrHomeId;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.roborockService.setDeviceNotify(async function (messageSource: NotifyMessageTypes, homeData: unknown) {
|
|
161
|
+
await self.platformRunner?.updateRobot(messageSource, homeData);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
for (const robot of this.robots.values()) {
|
|
165
|
+
await this.roborockService.activateDeviceNotify(robot.device);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
await this.platformRunner?.requestHomeData();
|
|
169
|
+
|
|
170
|
+
this.log.info('onConfigurateDevice finished');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async configurateDevice(vacuum: Device) {
|
|
174
|
+
const username = this.config.username as string;
|
|
175
|
+
|
|
176
|
+
if (this.platformRunner === undefined || this.roborockService === undefined) {
|
|
177
|
+
this.log.error('Initializing: PlatformRunner or RoborockService is undefined');
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
143
181
|
await this.roborockService.initializeMessageClientForLocal(vacuum);
|
|
144
182
|
const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
|
|
145
183
|
|
|
@@ -162,24 +200,17 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
162
200
|
this.roborockService.setSupportedScenes(vacuum.duid, routineAsRoom);
|
|
163
201
|
}
|
|
164
202
|
|
|
165
|
-
|
|
166
|
-
|
|
203
|
+
const robot = new RoborockVacuumCleaner(username, vacuum, roomMap, routineAsRoom, this.enableExperimentalFeature, this.log);
|
|
204
|
+
robot.configurateHandler(behaviorHandler);
|
|
167
205
|
|
|
168
206
|
this.log.info('vacuum:', debugStringify(vacuum));
|
|
169
207
|
|
|
170
|
-
this.setSelectDevice(
|
|
171
|
-
if (this.validateDevice(
|
|
172
|
-
await this.registerDevice(
|
|
208
|
+
this.setSelectDevice(robot.serialNumber ?? '', robot.deviceName ?? '', undefined, 'hub');
|
|
209
|
+
if (this.validateDevice(robot.deviceName ?? '')) {
|
|
210
|
+
await this.registerDevice(robot);
|
|
173
211
|
}
|
|
174
212
|
|
|
175
|
-
this.
|
|
176
|
-
await self.platformRunner?.updateRobot(messageSource, homeData);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
await this.roborockService.activateDeviceNotify(this.robot.device);
|
|
180
|
-
await self.platformRunner?.requestHomeData();
|
|
181
|
-
|
|
182
|
-
this.log.info('onConfigurateDevice finished');
|
|
213
|
+
this.robots.set(robot.serialNumber ?? '', robot);
|
|
183
214
|
}
|
|
184
215
|
|
|
185
216
|
override async onShutdown(reason?: string) {
|
package/src/platformRunner.ts
CHANGED
|
@@ -34,10 +34,10 @@ export class PlatformRunner {
|
|
|
34
34
|
|
|
35
35
|
public async requestHomeData(): Promise<void> {
|
|
36
36
|
const platform = this.platform;
|
|
37
|
-
if (
|
|
37
|
+
if (platform.robots.size === 0 || !platform.rrHomeId) return;
|
|
38
38
|
if (platform.roborockService === undefined) return;
|
|
39
39
|
|
|
40
|
-
const homeData = await platform.roborockService.getHomeDataForUpdating(platform.
|
|
40
|
+
const homeData = await platform.roborockService.getHomeDataForUpdating(platform.rrHomeId);
|
|
41
41
|
await this.updateRobot(NotifyMessageTypes.HomeData, homeData);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -52,21 +52,39 @@ export class PlatformRunner {
|
|
|
52
52
|
return new RoomMap([], rooms);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
async getRoomMap(): Promise<RoomMap | undefined> {
|
|
55
|
+
async getRoomMap(duid: string): Promise<RoomMap | undefined> {
|
|
56
56
|
const platform = this.platform;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return platform.robot.roomInfo;
|
|
57
|
+
|
|
58
|
+
const robot = platform.robots.get(duid);
|
|
59
|
+
if (robot === undefined) {
|
|
60
|
+
platform.log.error(`Error6: Robot with DUID ${duid} not found`);
|
|
61
|
+
return undefined;
|
|
63
62
|
}
|
|
64
|
-
|
|
63
|
+
|
|
64
|
+
if (platform.roborockService === undefined) return undefined;
|
|
65
|
+
|
|
66
|
+
const rooms = robot.device.rooms ?? [];
|
|
67
|
+
// if (platform.robot?.device === undefined || platform.roborockService === undefined) return undefined;
|
|
68
|
+
if (robot.roomInfo === undefined) {
|
|
69
|
+
const roomData = await platform.roborockService.getRoomMappings(robot.device.duid);
|
|
70
|
+
robot.roomInfo = new RoomMap(roomData ?? [], rooms);
|
|
71
|
+
return robot.roomInfo;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return robot.roomInfo;
|
|
65
75
|
}
|
|
66
76
|
|
|
67
|
-
private async updateFromMQTTMessage(messageSource: NotifyMessageTypes, messageData: unknown, tracked = false): Promise<void> {
|
|
77
|
+
private async updateFromMQTTMessage(messageSource: NotifyMessageTypes, messageData: unknown, duid = '', tracked = false): Promise<void> {
|
|
68
78
|
const platform = this.platform;
|
|
69
|
-
|
|
79
|
+
duid = duid || (messageData as DeviceStatusNotify)?.duid || '';
|
|
80
|
+
|
|
81
|
+
const robot = platform.robots.get(duid);
|
|
82
|
+
if (robot === undefined) {
|
|
83
|
+
platform.log.error(`Error1: Robot with DUID ${duid} not found`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const deviceData = robot.device.data;
|
|
70
88
|
if (deviceData === undefined) {
|
|
71
89
|
platform.log.error('Device data is undefined');
|
|
72
90
|
return;
|
|
@@ -76,18 +94,16 @@ export class PlatformRunner {
|
|
|
76
94
|
platform.log.debug(`${messageSource} updateFromMQTTMessage: ${debugStringify(messageData as DeviceStatusNotify)}`);
|
|
77
95
|
}
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
if (platform.robot === undefined) return;
|
|
81
|
-
if (!platform.robot.serialNumber) {
|
|
97
|
+
if (!robot.serialNumber) {
|
|
82
98
|
platform.log.error('Robot serial number is undefined');
|
|
83
99
|
return;
|
|
84
100
|
}
|
|
85
101
|
|
|
86
102
|
// duid is set as device serial number
|
|
87
|
-
if (platform.robot.serialNumber !== duid) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
103
|
+
// if (platform.robot.serialNumber !== duid) {
|
|
104
|
+
// platform.log.notice(`DUID mismatch: ${duid}, device serial number: ${platform.robot.serialNumber}`);
|
|
105
|
+
// return;
|
|
106
|
+
// }
|
|
91
107
|
|
|
92
108
|
switch (messageSource) {
|
|
93
109
|
case NotifyMessageTypes.ErrorOccurred: {
|
|
@@ -95,7 +111,7 @@ export class PlatformRunner {
|
|
|
95
111
|
const operationalStateId = getOperationalErrorState(message.errorCode);
|
|
96
112
|
if (operationalStateId) {
|
|
97
113
|
platform.log.error(`Error occurred: ${message.errorCode}`);
|
|
98
|
-
|
|
114
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
99
115
|
|
|
100
116
|
// const errorState = getErrorFromErrorCode(message.errorCode);
|
|
101
117
|
// if (operationalStateId === RvcOperationalState.OperationalState.Error && errorState) {
|
|
@@ -108,18 +124,24 @@ export class PlatformRunner {
|
|
|
108
124
|
const message = messageData as BatteryMessage;
|
|
109
125
|
const batteryLevel = message.percentage;
|
|
110
126
|
if (batteryLevel) {
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
|
|
128
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
113
129
|
}
|
|
114
130
|
break;
|
|
115
131
|
}
|
|
116
132
|
|
|
117
133
|
case NotifyMessageTypes.LocalMessage: {
|
|
118
134
|
const data = messageData as CloudMessageResult;
|
|
135
|
+
const robot = platform.robots.get(duid);
|
|
136
|
+
if (!robot) {
|
|
137
|
+
platform.log.error(`Error2: Robot with DUID ${duid} not found`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
119
141
|
if (data) {
|
|
120
142
|
const state = state_to_matter_state(data.state);
|
|
121
143
|
if (state) {
|
|
122
|
-
|
|
144
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(state), platform.log);
|
|
123
145
|
}
|
|
124
146
|
|
|
125
147
|
const currentRoom = data.cleaning_info?.segment_id ?? -1;
|
|
@@ -127,17 +149,17 @@ export class PlatformRunner {
|
|
|
127
149
|
const isMappedArea = currentMappedAreas?.some((x) => x.areaId == currentRoom);
|
|
128
150
|
|
|
129
151
|
if (currentRoom !== -1 && isMappedArea) {
|
|
130
|
-
const roomMap = await this.getRoomMap();
|
|
152
|
+
const roomMap = await this.getRoomMap(duid);
|
|
131
153
|
this.platform.log.debug(`RoomMap: ${roomMap ? debugStringify(roomMap) : 'undefined'}`);
|
|
132
154
|
this.platform.log.debug('CurrentRoom:', currentRoom);
|
|
133
|
-
|
|
155
|
+
robot.updateAttribute(ServiceArea.Cluster.id, 'currentArea', currentRoom, platform.log);
|
|
134
156
|
}
|
|
135
157
|
|
|
136
158
|
if (data.battery) {
|
|
137
159
|
const batteryLevel = data.battery as number;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
160
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel * 2, platform.log);
|
|
161
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(data.state, data.battery), platform.log);
|
|
162
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
const currentCleanModeSetting = {
|
|
@@ -158,11 +180,11 @@ export class PlatformRunner {
|
|
|
158
180
|
this.platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
159
181
|
|
|
160
182
|
if (currentCleanMode) {
|
|
161
|
-
|
|
183
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
162
184
|
}
|
|
163
185
|
}
|
|
164
186
|
|
|
165
|
-
this.processAdditionalProps(
|
|
187
|
+
this.processAdditionalProps(robot, data, duid);
|
|
166
188
|
}
|
|
167
189
|
break;
|
|
168
190
|
}
|
|
@@ -185,6 +207,12 @@ export class PlatformRunner {
|
|
|
185
207
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
186
208
|
const self = this;
|
|
187
209
|
|
|
210
|
+
const robot = platform.robots.get(duid);
|
|
211
|
+
if (robot === undefined) {
|
|
212
|
+
platform.log.error(`Error3: Robot with DUID ${duid} not found`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
188
216
|
// Known: 122, 121, 102,
|
|
189
217
|
// Unknown: 128, 139
|
|
190
218
|
messageTypes.forEach(async (messageType) => {
|
|
@@ -193,14 +221,14 @@ export class PlatformRunner {
|
|
|
193
221
|
const status = Number(data.dps[messageType]);
|
|
194
222
|
const matterState = state_to_matter_state(status);
|
|
195
223
|
if (matterState) {
|
|
196
|
-
|
|
224
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
197
225
|
}
|
|
198
226
|
|
|
199
227
|
const operationalStateId = state_to_matter_operational_status(status);
|
|
200
228
|
if (operationalStateId) {
|
|
201
|
-
const dssHasError = hasDockingStationError(
|
|
202
|
-
if (!(dssHasError && self.triggerDssError())) {
|
|
203
|
-
|
|
229
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
230
|
+
if (!(dssHasError && self.triggerDssError(robot))) {
|
|
231
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
204
232
|
}
|
|
205
233
|
}
|
|
206
234
|
break;
|
|
@@ -219,18 +247,18 @@ export class PlatformRunner {
|
|
|
219
247
|
}
|
|
220
248
|
|
|
221
249
|
if (roboStatus) {
|
|
222
|
-
const message = {
|
|
250
|
+
const message = { ...roboStatus } as DeviceStatusNotify;
|
|
223
251
|
platform.log.debug('rpc_response:', debugStringify(message));
|
|
224
|
-
await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, true);
|
|
252
|
+
await self.updateFromMQTTMessage(NotifyMessageTypes.LocalMessage, message, duid, true);
|
|
225
253
|
}
|
|
226
254
|
break;
|
|
227
255
|
}
|
|
228
256
|
case Protocol.suction_power:
|
|
229
257
|
case Protocol.water_box_mode: {
|
|
230
258
|
await platform.roborockService?.getCleanModeData(duid).then((cleanModeData) => {
|
|
231
|
-
if (cleanModeData
|
|
259
|
+
if (cleanModeData) {
|
|
232
260
|
const currentCleanMode = getCurrentCleanModeFunc(
|
|
233
|
-
|
|
261
|
+
robot.device.data.model,
|
|
234
262
|
platform.enableExperimentalFeature?.advancedFeature?.forceRunAtDefault ?? false,
|
|
235
263
|
)({
|
|
236
264
|
suctionPower: cleanModeData.suctionPower,
|
|
@@ -242,7 +270,7 @@ export class PlatformRunner {
|
|
|
242
270
|
platform.log.debug(`Clean mode data: ${debugStringify(cleanModeData)}`);
|
|
243
271
|
platform.log.debug(`Current clean mode: ${currentCleanMode}`);
|
|
244
272
|
if (currentCleanMode) {
|
|
245
|
-
|
|
273
|
+
robot.updateAttribute(RvcCleanMode.Cluster.id, 'currentMode', currentCleanMode, platform.log);
|
|
246
274
|
}
|
|
247
275
|
}
|
|
248
276
|
});
|
|
@@ -261,17 +289,22 @@ export class PlatformRunner {
|
|
|
261
289
|
});
|
|
262
290
|
}
|
|
263
291
|
|
|
264
|
-
private async processAdditionalProps(robot: RoborockVacuumCleaner, message: CloudMessageResult): Promise<void> {
|
|
292
|
+
private async processAdditionalProps(robot: RoborockVacuumCleaner, message: CloudMessageResult, duid: string): Promise<void> {
|
|
265
293
|
// dss -> DockingStationStatus
|
|
266
|
-
const dssStatus = this.getDssStatus(message);
|
|
294
|
+
const dssStatus = this.getDssStatus(message, duid);
|
|
267
295
|
if (dssStatus) {
|
|
268
|
-
this.triggerDssError();
|
|
296
|
+
this.triggerDssError(robot);
|
|
269
297
|
}
|
|
270
298
|
}
|
|
271
299
|
|
|
272
|
-
private getDssStatus(message: CloudMessageResult): RvcOperationalState.OperationalState | undefined {
|
|
300
|
+
private getDssStatus(message: CloudMessageResult, duid: string): RvcOperationalState.OperationalState | undefined {
|
|
273
301
|
const platform = this.platform;
|
|
274
|
-
const robot = platform.
|
|
302
|
+
const robot = platform.robots.get(duid);
|
|
303
|
+
if (robot === undefined) {
|
|
304
|
+
platform.log.error(`Error4: Robot with DUID ${duid} not found`);
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
307
|
+
|
|
275
308
|
if (
|
|
276
309
|
platform.enableExperimentalFeature &&
|
|
277
310
|
platform.enableExperimentalFeature.enableExperimentalFeature &&
|
|
@@ -304,60 +337,74 @@ export class PlatformRunner {
|
|
|
304
337
|
|
|
305
338
|
private updateFromHomeData(homeData: Home): void {
|
|
306
339
|
const platform = this.platform;
|
|
307
|
-
if (platform.robot === undefined) return;
|
|
308
|
-
const device = homeData.devices.find((d: Device) => d.duid === platform.robot?.serialNumber);
|
|
309
|
-
const deviceData = platform.robot?.device.data;
|
|
310
|
-
if (!device || deviceData === undefined) {
|
|
311
|
-
platform.log.error('Device not found in home data');
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
340
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
this.platform.log.debug('updateFromHomeData-device:', debugStringify(device));
|
|
341
|
+
if (platform.robots.size === 0) return;
|
|
342
|
+
const devices = homeData.devices.filter((d: Device) => platform.robots.has(d.duid));
|
|
318
343
|
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
344
|
+
for (const device of devices) {
|
|
345
|
+
const robot = platform.robots.get(device.duid);
|
|
346
|
+
if (robot === undefined) {
|
|
347
|
+
platform.log.error(`Error5: Robot with DUID ${device.duid} not found`);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
324
350
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
this.platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
|
|
351
|
+
const deviceData = robot.device.data;
|
|
352
|
+
if (!device || deviceData === undefined) {
|
|
353
|
+
platform.log.error('Device not found in home data');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
331
356
|
|
|
332
|
-
|
|
333
|
-
platform.
|
|
334
|
-
|
|
357
|
+
device.schema = homeData.products.find((prd) => prd.id === device.productId || prd.model === device.data.model)?.schema ?? [];
|
|
358
|
+
this.platform.log.debug('updateFromHomeData-homeData:', debugStringify(homeData));
|
|
359
|
+
this.platform.log.debug('updateFromHomeData-device:', debugStringify(device));
|
|
335
360
|
|
|
336
|
-
|
|
361
|
+
const batteryLevel = getVacuumProperty(device, 'battery');
|
|
337
362
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
363
|
+
this.platform.log.debug('updateFromHomeData-schema:' + debugStringify(device.schema));
|
|
364
|
+
this.platform.log.debug('updateFromHomeData-battery:' + debugStringify(device.deviceStatus));
|
|
365
|
+
|
|
366
|
+
if (batteryLevel) {
|
|
367
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batPercentRemaining', batteryLevel ? batteryLevel * 2 : 200, platform.log);
|
|
368
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeLevel', getBatteryStatus(batteryLevel), platform.log);
|
|
344
369
|
}
|
|
345
|
-
}
|
|
346
370
|
|
|
347
|
-
|
|
348
|
-
|
|
371
|
+
const state = getVacuumProperty(device, 'state');
|
|
372
|
+
const matterState = state_to_matter_state(state);
|
|
373
|
+
if (!state || !matterState) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
this.platform.log.debug(`updateFromHomeData-RvcRunMode code: ${state} name: ${OperationStatusCode[state]}, matterState: ${RvcRunMode.ModeTag[matterState]}`);
|
|
377
|
+
|
|
378
|
+
if (matterState) {
|
|
379
|
+
robot.updateAttribute(RvcRunMode.Cluster.id, 'currentMode', getRunningMode(matterState), platform.log);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const operationalStateId = state_to_matter_operational_status(state);
|
|
383
|
+
|
|
384
|
+
if (operationalStateId) {
|
|
385
|
+
const dssHasError = hasDockingStationError(robot.dockStationStatus);
|
|
386
|
+
this.platform.log.debug(`dssHasError: ${dssHasError}, dockStationStatus: ${debugStringify(robot.dockStationStatus ?? {})}`);
|
|
387
|
+
if (!(dssHasError && this.triggerDssError(robot))) {
|
|
388
|
+
this.platform.log.debug(`updateFromHomeData-OperationalState: ${RvcOperationalState.OperationalState[operationalStateId]}`);
|
|
389
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', operationalStateId, platform.log);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (batteryLevel) {
|
|
394
|
+
robot.updateAttribute(PowerSource.Cluster.id, 'batChargeState', getBatteryState(state, batteryLevel), platform.log);
|
|
395
|
+
}
|
|
349
396
|
}
|
|
350
397
|
}
|
|
351
398
|
|
|
352
|
-
private triggerDssError(): boolean {
|
|
399
|
+
private triggerDssError(robot: RoborockVacuumCleaner): boolean {
|
|
353
400
|
const platform = this.platform;
|
|
354
|
-
const currentOperationState =
|
|
401
|
+
const currentOperationState = robot.getAttribute(RvcOperationalState.Cluster.id, 'operationalState') as RvcOperationalState.OperationalState;
|
|
355
402
|
if (currentOperationState === RvcOperationalState.OperationalState.Error) {
|
|
356
403
|
return true;
|
|
357
404
|
}
|
|
358
405
|
|
|
359
406
|
if (currentOperationState === RvcOperationalState.OperationalState.Docked) {
|
|
360
|
-
|
|
407
|
+
robot.updateAttribute(RvcOperationalState.Cluster.id, 'operationalState', RvcOperationalState.OperationalState.Error, platform.log);
|
|
361
408
|
return true;
|
|
362
409
|
}
|
|
363
410
|
|
package/src/roborockCommunication/broadcast/listener/implementation/simpleMessageListener.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { ResponseMessage } from '../../model/responseMessage.js';
|
|
|
2
2
|
import { AbstractMessageListener } from '../index.js';
|
|
3
3
|
import { Protocol } from '../../model/protocol.js';
|
|
4
4
|
import { AbstractMessageHandler } from '../abstractMessageHandler.js';
|
|
5
|
-
// import { DeviceStatus } from '../../../Zmodel/deviceStatus.js';
|
|
6
5
|
|
|
7
6
|
export class SimpleMessageListener implements AbstractMessageListener {
|
|
8
7
|
private handler: AbstractMessageHandler | undefined;
|
package/src/roborockService.ts
CHANGED
|
@@ -227,7 +227,7 @@ export default class RoborockService {
|
|
|
227
227
|
if (this.messageProcessor) {
|
|
228
228
|
await this.messageProcessor.getDeviceStatus(device.duid).then((response: DeviceStatus) => {
|
|
229
229
|
if (self.deviceNotify) {
|
|
230
|
-
const message
|
|
230
|
+
const message = { duid: device.duid, ...response.errorStatus, ...response.message } as DeviceStatusNotify;
|
|
231
231
|
self.logger.debug('Device status update', debugStringify(message));
|
|
232
232
|
self.deviceNotify(NotifyMessageTypes.LocalMessage, message);
|
|
233
233
|
}
|
package/src/rvc.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RoboticVacuumCleaner } from 'matterbridge';
|
|
1
|
+
import { RoboticVacuumCleaner } from 'matterbridge/devices';
|
|
2
2
|
import RoomMap from './model/RoomMap.js';
|
|
3
3
|
import { Device } from './roborockCommunication/index.js';
|
|
4
4
|
import { getOperationalStates, getSupportedAreas, getSupportedCleanModes, getSupportedRunModes } from './initialData/index.js';
|
|
@@ -11,7 +11,6 @@ import { DockingStationStatus } from './model/DockingStationStatus.js';
|
|
|
11
11
|
export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
|
|
12
12
|
username: string | undefined;
|
|
13
13
|
device: Device;
|
|
14
|
-
rrHomeId: number;
|
|
15
14
|
roomInfo: RoomMap | undefined;
|
|
16
15
|
dockStationStatus: DockingStationStatus | undefined;
|
|
17
16
|
|
|
@@ -34,9 +33,11 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
|
|
|
34
33
|
log.debug(`Supported Clean Modes: ${JSON.stringify(cleanModes)}`);
|
|
35
34
|
log.debug(`Supported Run Modes: ${JSON.stringify(supportedRunModes)}`);
|
|
36
35
|
|
|
36
|
+
const bridgeMode = enableExperimentalFeature?.advancedFeature.enableServerMode ? 'server' : undefined;
|
|
37
37
|
super(
|
|
38
38
|
deviceName, // name
|
|
39
39
|
device.duid, // serial
|
|
40
|
+
bridgeMode, // mode
|
|
40
41
|
supportedRunModes[0].mode, // currentRunMode
|
|
41
42
|
supportedRunModes, // supportedRunModes
|
|
42
43
|
cleanModes[0].mode, // currentCleanMode
|
|
@@ -52,7 +53,6 @@ export class RoborockVacuumCleaner extends RoboticVacuumCleaner {
|
|
|
52
53
|
|
|
53
54
|
this.username = username;
|
|
54
55
|
this.device = device;
|
|
55
|
-
this.rrHomeId = device.rrHomeId;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
public configurateHandler(behaviorHandler: BehaviorFactoryResult): void {
|