matterbridge-roborock-vacuum-plugin 1.0.7-rc03 → 1.0.8-rc01
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/dist/behaviorFactory.js +3 -3
- package/dist/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.js +56 -11
- package/dist/behaviors/roborock.vacuum/S7_MAXV/a27.js +50 -7
- package/dist/model/CleanModeSettings.js +5 -0
- package/dist/platform.js +10 -1
- package/dist/roborockCommunication/broadcast/abstractClient.js +2 -2
- package/dist/roborockCommunication/broadcast/client/LocalNetworkClient.js +7 -7
- package/dist/roborockCommunication/broadcast/client/MQTTClient.js +1 -1
- package/dist/roborockCommunication/broadcast/messageProcessor.js +23 -9
- package/dist/roborockCommunication/helper/messageDeserializer.js +9 -6
- package/dist/roborockCommunication/helper/messageSerializer.js +3 -1
- package/dist/roborockService.js +5 -2
- package/dist/share/function.js +6 -6
- package/matterbridge-roborock-vacuum-plugin.config.json +1 -1
- package/matterbridge-roborock-vacuum-plugin.schema.json +118 -5
- package/package.json +1 -1
- package/src/behaviorFactory.ts +10 -3
- package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.test.ts +201 -0
- package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/a187.ts +68 -12
- package/src/behaviors/roborock.vacuum/QREVO_EDGE_5V1/runtimes.test.ts +79 -0
- package/src/behaviors/roborock.vacuum/S7_MAXV/a27.test.ts +134 -0
- package/src/behaviors/roborock.vacuum/S7_MAXV/a27.ts +62 -9
- package/src/model/CleanModeSettings.ts +17 -0
- package/src/platform.ts +13 -1
- package/src/roborockCommunication/broadcast/abstractClient.ts +2 -2
- package/src/roborockCommunication/broadcast/client/LocalNetworkClient.ts +9 -8
- package/src/roborockCommunication/broadcast/client/MQTTClient.ts +1 -1
- package/src/roborockCommunication/broadcast/messageProcessor.ts +24 -9
- package/src/roborockCommunication/helper/messageDeserializer.ts +12 -11
- package/src/roborockCommunication/helper/messageSerializer.ts +4 -1
- package/src/roborockService.ts +9 -2
- package/src/share/function.ts +6 -6
- package/dist/appliances.js +0 -6
- package/src/appliances.ts +0 -15
package/dist/behaviorFactory.js
CHANGED
|
@@ -3,16 +3,16 @@ import { setCommandHandlerA187 } from './behaviors/roborock.vacuum/QREVO_EDGE_5V
|
|
|
3
3
|
import { setDefaultCommandHandler } from './behaviors/roborock.vacuum/default/default.js';
|
|
4
4
|
import { DeviceModel } from './roborockCommunication/Zmodel/deviceModel.js';
|
|
5
5
|
import { setCommandHandlerA27 } from './behaviors/roborock.vacuum/S7_MAXV/a27.js';
|
|
6
|
-
export function configurateBehavior(model, duid, roborockService, logger) {
|
|
6
|
+
export function configurateBehavior(model, duid, roborockService, cleanModeSettings, logger) {
|
|
7
7
|
switch (model) {
|
|
8
8
|
case DeviceModel.QREVO_EDGE_5V1: {
|
|
9
9
|
const deviceHandler = new BehaviorDeviceGeneric(logger);
|
|
10
|
-
setCommandHandlerA187(duid, deviceHandler, logger, roborockService);
|
|
10
|
+
setCommandHandlerA187(duid, deviceHandler, logger, roborockService, cleanModeSettings);
|
|
11
11
|
return deviceHandler;
|
|
12
12
|
}
|
|
13
13
|
case DeviceModel.S7_MAXV: {
|
|
14
14
|
const deviceHandler = new BehaviorDeviceGeneric(logger);
|
|
15
|
-
setCommandHandlerA27(duid, deviceHandler, logger, roborockService);
|
|
15
|
+
setCommandHandlerA27(duid, deviceHandler, logger, roborockService, cleanModeSettings);
|
|
16
16
|
return deviceHandler;
|
|
17
17
|
}
|
|
18
18
|
default: {
|
|
@@ -27,15 +27,16 @@ export var MopWaterFlowA187;
|
|
|
27
27
|
MopWaterFlowA187[MopWaterFlowA187["Medium"] = 202] = "Medium";
|
|
28
28
|
MopWaterFlowA187[MopWaterFlowA187["High"] = 203] = "High";
|
|
29
29
|
MopWaterFlowA187[MopWaterFlowA187["Custom"] = 204] = "Custom";
|
|
30
|
+
MopWaterFlowA187[MopWaterFlowA187["CustomizeWithDistanceOff"] = 207] = "CustomizeWithDistanceOff";
|
|
30
31
|
MopWaterFlowA187[MopWaterFlowA187["Smart"] = 209] = "Smart";
|
|
31
32
|
})(MopWaterFlowA187 || (MopWaterFlowA187 = {}));
|
|
32
33
|
export var MopRouteA187;
|
|
33
34
|
(function (MopRouteA187) {
|
|
34
|
-
MopRouteA187[MopRouteA187["Fast"] = 304] = "Fast";
|
|
35
35
|
MopRouteA187[MopRouteA187["Standard"] = 300] = "Standard";
|
|
36
36
|
MopRouteA187[MopRouteA187["Deep"] = 301] = "Deep";
|
|
37
37
|
MopRouteA187[MopRouteA187["Custom"] = 302] = "Custom";
|
|
38
38
|
MopRouteA187[MopRouteA187["DeepPlus"] = 303] = "DeepPlus";
|
|
39
|
+
MopRouteA187[MopRouteA187["Fast"] = 304] = "Fast";
|
|
39
40
|
MopRouteA187[MopRouteA187["Smart"] = 306] = "Smart";
|
|
40
41
|
})(MopRouteA187 || (MopRouteA187 = {}));
|
|
41
42
|
const RvcRunMode = {
|
|
@@ -51,13 +52,13 @@ const RvcCleanMode = {
|
|
|
51
52
|
[8]: 'Custom',
|
|
52
53
|
};
|
|
53
54
|
const CleanSetting = {
|
|
54
|
-
[4]: { suctionPower: 0, waterFlow: 0, mopRoute: MopRouteA187.Smart },
|
|
55
|
-
[5]: { suctionPower: VacuumSuctionPowerA187.Off, waterFlow: MopWaterFlowA187.Medium, mopRoute: MopRouteA187.
|
|
56
|
-
[6]: { suctionPower: VacuumSuctionPowerA187.Balanced, waterFlow: MopWaterFlowA187.Off, mopRoute: MopRouteA187.
|
|
57
|
-
[7]: { suctionPower: VacuumSuctionPowerA187.Balanced, waterFlow: MopWaterFlowA187.Medium, mopRoute: MopRouteA187.
|
|
58
|
-
[8]: { suctionPower: VacuumSuctionPowerA187.Custom, waterFlow: MopWaterFlowA187.Custom, mopRoute: MopRouteA187.Custom },
|
|
55
|
+
[4]: { suctionPower: 0, waterFlow: 0, distance_off: 0, mopRoute: MopRouteA187.Smart },
|
|
56
|
+
[5]: { suctionPower: VacuumSuctionPowerA187.Off, waterFlow: MopWaterFlowA187.Medium, distance_off: 0, mopRoute: MopRouteA187.Standard },
|
|
57
|
+
[6]: { suctionPower: VacuumSuctionPowerA187.Balanced, waterFlow: MopWaterFlowA187.Off, distance_off: 0, mopRoute: MopRouteA187.Standard },
|
|
58
|
+
[7]: { suctionPower: VacuumSuctionPowerA187.Balanced, waterFlow: MopWaterFlowA187.Medium, distance_off: 0, mopRoute: MopRouteA187.Standard },
|
|
59
|
+
[8]: { suctionPower: VacuumSuctionPowerA187.Custom, waterFlow: MopWaterFlowA187.Custom, distance_off: 0, mopRoute: MopRouteA187.Custom },
|
|
59
60
|
};
|
|
60
|
-
export function setCommandHandlerA187(duid, handler, logger, roborockService) {
|
|
61
|
+
export function setCommandHandlerA187(duid, handler, logger, roborockService, cleanModeSettings) {
|
|
61
62
|
handler.setCommandHandler('changeToMode', async (newMode) => {
|
|
62
63
|
const activity = RvcRunMode[newMode] || RvcCleanMode[newMode];
|
|
63
64
|
switch (activity) {
|
|
@@ -67,20 +68,64 @@ export function setCommandHandlerA187(duid, handler, logger, roborockService) {
|
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
case 'Smart Plan':
|
|
70
|
-
case 'Mop':
|
|
71
|
-
case 'Vacuum':
|
|
72
|
-
case 'Vac & Mop':
|
|
73
71
|
case 'Custom': {
|
|
74
72
|
const setting = CleanSetting[newMode];
|
|
75
|
-
logger.notice(`BehaviorA187-ChangeCleanMode to: ${activity},
|
|
73
|
+
logger.notice(`BehaviorA187-ChangeCleanMode to: ${activity}, setting: ${debugStringify(setting)}`);
|
|
76
74
|
await roborockService.changeCleanMode(duid, setting);
|
|
77
75
|
return;
|
|
78
76
|
}
|
|
77
|
+
case 'Mop':
|
|
78
|
+
case 'Vacuum':
|
|
79
|
+
case 'Vac & Mop': {
|
|
80
|
+
const setting = cleanModeSettings ? getSettingFromCleanMode(activity, cleanModeSettings) : CleanSetting[newMode];
|
|
81
|
+
logger.notice(`BehaviorA187-ChangeCleanMode to: ${activity}, setting: ${debugStringify(setting ?? {})}`);
|
|
82
|
+
if (setting) {
|
|
83
|
+
await roborockService.changeCleanMode(duid, setting);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
79
87
|
default:
|
|
80
88
|
logger.notice('BehaviorA187-changeToMode-Unknown: ', newMode);
|
|
81
89
|
return;
|
|
82
90
|
}
|
|
83
91
|
});
|
|
92
|
+
const getSettingFromCleanMode = (activity, cleanModeSettings) => {
|
|
93
|
+
switch (activity) {
|
|
94
|
+
case 'Mop': {
|
|
95
|
+
const mopSetting = cleanModeSettings?.mopping;
|
|
96
|
+
const waterFlow = MopWaterFlowA187[mopSetting?.waterFlowMode] ?? MopWaterFlowA187.Medium;
|
|
97
|
+
const distance_off = waterFlow == MopWaterFlowA187.CustomizeWithDistanceOff ? 210 - 5 * (mopSetting?.distanceOff ?? 25) : 0;
|
|
98
|
+
return {
|
|
99
|
+
suctionPower: VacuumSuctionPowerA187.Off,
|
|
100
|
+
waterFlow,
|
|
101
|
+
distance_off,
|
|
102
|
+
mopRoute: MopRouteA187[mopSetting?.mopRouteMode] ?? MopRouteA187.Standard,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
case 'Vacuum': {
|
|
106
|
+
const vacuumSetting = cleanModeSettings?.vacuuming;
|
|
107
|
+
return {
|
|
108
|
+
suctionPower: VacuumSuctionPowerA187[vacuumSetting?.fanMode] ?? VacuumSuctionPowerA187.Balanced,
|
|
109
|
+
waterFlow: MopWaterFlowA187.Off,
|
|
110
|
+
distance_off: 0,
|
|
111
|
+
mopRoute: MopRouteA187[vacuumSetting?.mopRouteMode] ?? MopRouteA187.Standard,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
case 'Vac & Mop': {
|
|
115
|
+
const vacmopSetting = cleanModeSettings?.vacmop;
|
|
116
|
+
const waterFlow = MopWaterFlowA187[vacmopSetting?.waterFlowMode] ?? MopWaterFlowA187.Medium;
|
|
117
|
+
const distance_off = waterFlow == MopWaterFlowA187.CustomizeWithDistanceOff ? 210 - 5 * (vacmopSetting?.distanceOff ?? 25) : 0;
|
|
118
|
+
return {
|
|
119
|
+
suctionPower: VacuumSuctionPowerA187[vacmopSetting?.fanMode] ?? VacuumSuctionPowerA187.Balanced,
|
|
120
|
+
waterFlow,
|
|
121
|
+
distance_off,
|
|
122
|
+
mopRoute: MopRouteA187[vacmopSetting?.mopRouteMode] ?? MopRouteA187.Standard,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
default:
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
84
129
|
handler.setCommandHandler('selectAreas', async (newAreas) => {
|
|
85
130
|
logger.notice(`BehaviorA187-selectAreas: ${newAreas}`);
|
|
86
131
|
roborockService.setSelectedAreas(duid, newAreas ?? []);
|
|
@@ -47,12 +47,12 @@ const RvcCleanMode = {
|
|
|
47
47
|
[8]: 'Custom',
|
|
48
48
|
};
|
|
49
49
|
const CleanSetting = {
|
|
50
|
-
[5]: { suctionPower: VacuumSuctionPowerA27.Off, waterFlow: MopWaterFlowA27.Medium, mopRoute: MopRouteA27.
|
|
51
|
-
[6]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Off, mopRoute: MopRouteA27.
|
|
52
|
-
[7]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Medium, mopRoute: MopRouteA27.
|
|
53
|
-
[8]: { suctionPower: VacuumSuctionPowerA27.Custom, waterFlow: MopWaterFlowA27.Custom, mopRoute: MopRouteA27.Custom },
|
|
50
|
+
[5]: { suctionPower: VacuumSuctionPowerA27.Off, waterFlow: MopWaterFlowA27.Medium, distance_off: 0, mopRoute: MopRouteA27.Standard },
|
|
51
|
+
[6]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Off, distance_off: 0, mopRoute: MopRouteA27.Standard },
|
|
52
|
+
[7]: { suctionPower: VacuumSuctionPowerA27.Balanced, waterFlow: MopWaterFlowA27.Medium, distance_off: 0, mopRoute: MopRouteA27.Standard },
|
|
53
|
+
[8]: { suctionPower: VacuumSuctionPowerA27.Custom, waterFlow: MopWaterFlowA27.Custom, distance_off: 0, mopRoute: MopRouteA27.Custom },
|
|
54
54
|
};
|
|
55
|
-
export function setCommandHandlerA27(duid, handler, logger, roborockService) {
|
|
55
|
+
export function setCommandHandlerA27(duid, handler, logger, roborockService, cleanModeSettings) {
|
|
56
56
|
handler.setCommandHandler('changeToMode', async (newMode) => {
|
|
57
57
|
const activity = RvcRunMode[newMode] || RvcCleanMode[newMode];
|
|
58
58
|
switch (activity) {
|
|
@@ -63,10 +63,18 @@ export function setCommandHandlerA27(duid, handler, logger, roborockService) {
|
|
|
63
63
|
}
|
|
64
64
|
case 'Mop':
|
|
65
65
|
case 'Vacuum':
|
|
66
|
-
case 'Vac & Mop':
|
|
66
|
+
case 'Vac & Mop': {
|
|
67
|
+
const setting = cleanModeSettings ? getSettingFromCleanMode(activity, cleanModeSettings) : CleanSetting[newMode];
|
|
68
|
+
logger.notice(`BehaviorA27-ChangeCleanMode to: ${activity}, setting: ${debugStringify(setting ?? {})}`);
|
|
69
|
+
if (setting) {
|
|
70
|
+
await roborockService.changeCleanMode(duid, setting);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
67
74
|
case 'Custom': {
|
|
68
75
|
const setting = CleanSetting[newMode];
|
|
69
|
-
logger.notice(`BehaviorA27-ChangeCleanMode to: ${activity},
|
|
76
|
+
logger.notice(`BehaviorA27-ChangeCleanMode to: ${activity}, setting: ${debugStringify(setting)}`);
|
|
77
|
+
await roborockService.changeCleanMode(duid, setting);
|
|
70
78
|
return;
|
|
71
79
|
}
|
|
72
80
|
default:
|
|
@@ -74,6 +82,41 @@ export function setCommandHandlerA27(duid, handler, logger, roborockService) {
|
|
|
74
82
|
return;
|
|
75
83
|
}
|
|
76
84
|
});
|
|
85
|
+
const getSettingFromCleanMode = (activity, cleanModeSettings) => {
|
|
86
|
+
switch (activity) {
|
|
87
|
+
case 'Mop': {
|
|
88
|
+
const mopSetting = cleanModeSettings?.mopping;
|
|
89
|
+
const waterFlow = MopWaterFlowA27[mopSetting?.waterFlowMode] ?? MopWaterFlowA27.Medium;
|
|
90
|
+
return {
|
|
91
|
+
suctionPower: VacuumSuctionPowerA27.Off,
|
|
92
|
+
waterFlow,
|
|
93
|
+
distance_off: 0,
|
|
94
|
+
mopRoute: MopRouteA27[mopSetting?.mopRouteMode] ?? MopRouteA27.Standard,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
case 'Vacuum': {
|
|
98
|
+
const vacuumSetting = cleanModeSettings?.vacuuming;
|
|
99
|
+
return {
|
|
100
|
+
suctionPower: VacuumSuctionPowerA27[vacuumSetting?.fanMode] ?? VacuumSuctionPowerA27.Balanced,
|
|
101
|
+
waterFlow: MopWaterFlowA27.Off,
|
|
102
|
+
distance_off: 0,
|
|
103
|
+
mopRoute: MopRouteA27[vacuumSetting?.mopRouteMode] ?? MopRouteA27.Standard,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
case 'Vac & Mop': {
|
|
107
|
+
const vacmopSetting = cleanModeSettings?.vacmop;
|
|
108
|
+
const waterFlow = MopWaterFlowA27[vacmopSetting?.waterFlowMode] ?? MopWaterFlowA27.Medium;
|
|
109
|
+
return {
|
|
110
|
+
suctionPower: VacuumSuctionPowerA27[vacmopSetting?.fanMode] ?? VacuumSuctionPowerA27.Balanced,
|
|
111
|
+
waterFlow,
|
|
112
|
+
distance_off: 0,
|
|
113
|
+
mopRoute: MopRouteA27[vacmopSetting?.mopRouteMode] ?? MopRouteA27.Standard,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
default:
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
77
120
|
handler.setCommandHandler('selectAreas', async (newAreas) => {
|
|
78
121
|
logger.notice(`BehaviorA27-selectAreas: ${newAreas}`);
|
|
79
122
|
roborockService.setSelectedAreas(duid, newAreas ?? []);
|
package/dist/platform.js
CHANGED
|
@@ -91,6 +91,11 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
91
91
|
}
|
|
92
92
|
async onConfigure() {
|
|
93
93
|
await super.onConfigure();
|
|
94
|
+
if (this.config.enableExperimentalFeature) {
|
|
95
|
+
const cleanModeSettings = this.config.cleanModeSettings;
|
|
96
|
+
this.log.notice(`Experimental Feature has been enable`);
|
|
97
|
+
this.log.notice(`cleanModeSettings ${debugStringify(cleanModeSettings)}`);
|
|
98
|
+
}
|
|
94
99
|
const self = this;
|
|
95
100
|
this.rvcInterval = setInterval(async () => {
|
|
96
101
|
self.platformRunner?.requestHomeData();
|
|
@@ -108,11 +113,15 @@ export class RoborockMatterbridgePlatform extends MatterbridgeDynamicPlatform {
|
|
|
108
113
|
this.log.error('Initializing: No supported devices found');
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
116
|
+
let cleanModeSettings = undefined;
|
|
117
|
+
if (this.config.enableExperimentalFeature) {
|
|
118
|
+
cleanModeSettings = this.config.cleanModeSettings;
|
|
119
|
+
}
|
|
111
120
|
const self = this;
|
|
112
121
|
await this.roborockService.initializeMessageClientForLocal(vacuum);
|
|
113
122
|
const roomMap = await this.platformRunner.getRoomMapFromDevice(vacuum);
|
|
114
123
|
this.log.debug('Initializing - roomMap: ', debugStringify(roomMap));
|
|
115
|
-
const behaviorHandler = configurateBehavior(vacuum.data.model, vacuum.duid, this.roborockService, this.log);
|
|
124
|
+
const behaviorHandler = configurateBehavior(vacuum.data.model, vacuum.duid, this.roborockService, cleanModeSettings, this.log);
|
|
116
125
|
this.roborockService.setSupportedAreas(vacuum.duid, getSupportedAreas(vacuum.rooms, roomMap, this.log));
|
|
117
126
|
this.robot = new RoborockVacuumCleaner(username, vacuum, roomMap, this.log);
|
|
118
127
|
this.robot.configurateHandler(behaviorHandler);
|
|
@@ -14,8 +14,8 @@ export class AbstractClient {
|
|
|
14
14
|
logger;
|
|
15
15
|
constructor(logger, context) {
|
|
16
16
|
this.context = context;
|
|
17
|
-
this.serializer = new MessageSerializer(this.context);
|
|
18
|
-
this.deserializer = new MessageDeserializer(this.context);
|
|
17
|
+
this.serializer = new MessageSerializer(this.context, logger);
|
|
18
|
+
this.deserializer = new MessageDeserializer(this.context, logger);
|
|
19
19
|
this.syncMessageListener = new SyncMessageListener(logger);
|
|
20
20
|
this.messageListeners.register(this.syncMessageListener);
|
|
21
21
|
this.logger = logger;
|
|
@@ -87,31 +87,31 @@ export class LocalNetworkClient extends AbstractClient {
|
|
|
87
87
|
}
|
|
88
88
|
try {
|
|
89
89
|
this.buffer.append(message);
|
|
90
|
-
const
|
|
91
|
-
if (!this.isMessageComplete(
|
|
90
|
+
const receivedBuffer = this.buffer.get();
|
|
91
|
+
if (!this.isMessageComplete(receivedBuffer)) {
|
|
92
92
|
return;
|
|
93
93
|
}
|
|
94
94
|
this.buffer.reset();
|
|
95
95
|
let offset = 0;
|
|
96
|
-
while (offset + 4 <=
|
|
97
|
-
const segmentLength =
|
|
96
|
+
while (offset + 4 <= receivedBuffer.length) {
|
|
97
|
+
const segmentLength = receivedBuffer.readUInt32BE(offset);
|
|
98
98
|
if (segmentLength == 17) {
|
|
99
99
|
offset += 4 + segmentLength;
|
|
100
100
|
continue;
|
|
101
101
|
}
|
|
102
102
|
try {
|
|
103
|
-
const currentBuffer =
|
|
103
|
+
const currentBuffer = receivedBuffer.subarray(offset + 4, offset + segmentLength + 4);
|
|
104
104
|
const response = this.deserializer.deserialize(this.duid, currentBuffer);
|
|
105
105
|
await this.messageListeners.onMessage(response);
|
|
106
106
|
}
|
|
107
107
|
catch (error) {
|
|
108
|
-
this.logger.error('unable to process message: ' + error);
|
|
108
|
+
this.logger.error('LocalNetworkClient: unable to process message with error: ' + error);
|
|
109
109
|
}
|
|
110
110
|
offset += 4 + segmentLength;
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
catch (error) {
|
|
114
|
-
this.logger.error('
|
|
114
|
+
this.logger.error('LocalNetworkClient: read socket buffer error: ' + error);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
isMessageComplete(buffer) {
|
|
@@ -97,7 +97,7 @@ export class MQTTClient extends AbstractClient {
|
|
|
97
97
|
await this.messageListeners.onMessage(response);
|
|
98
98
|
}
|
|
99
99
|
catch (error) {
|
|
100
|
-
this.logger.error('unable to process message
|
|
100
|
+
this.logger.error('MQTTClient: unable to process message with error: ' + topic + ': ' + error);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -63,25 +63,39 @@ export class MessageProcessor {
|
|
|
63
63
|
const request = new RequestMessage(def);
|
|
64
64
|
return this.client.send(duid, request);
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.logger?.warn('XXXXXXX: ', debugStringify(response));
|
|
69
|
-
return response;
|
|
66
|
+
getCustomMessage(duid, def) {
|
|
67
|
+
return this.client.get(duid, def);
|
|
70
68
|
}
|
|
71
69
|
async findMyRobot(duid) {
|
|
72
70
|
const request = new RequestMessage({ method: 'find_me' });
|
|
73
71
|
return this.client.send(duid, request);
|
|
74
72
|
}
|
|
75
|
-
async changeCleanMode(duid, suctionPower, waterFlow, mopRoute) {
|
|
76
|
-
this.logger?.notice(`Change clean mode for ${duid} to suctionPower: ${suctionPower}, waterFlow: ${waterFlow}`);
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
async changeCleanMode(duid, suctionPower, waterFlow, mopRoute, distance_off) {
|
|
74
|
+
this.logger?.notice(`Change clean mode for ${duid} to suctionPower: ${suctionPower}, waterFlow: ${waterFlow}, mopRoute: ${mopRoute}, distance_off: ${distance_off}`);
|
|
75
|
+
const currentMopMode = await this.getCustomMessage(duid, new RequestMessage({ method: 'get_custom_mode' }));
|
|
76
|
+
const smartMopMode = 110;
|
|
77
|
+
const smartMopRoute = 306;
|
|
78
|
+
const customMopMode = 106;
|
|
79
|
+
const customMopRoute = 302;
|
|
80
|
+
if (currentMopMode == smartMopMode && mopRoute == smartMopRoute)
|
|
81
|
+
return;
|
|
82
|
+
if (currentMopMode == customMopMode && mopRoute == customMopRoute)
|
|
83
|
+
return;
|
|
84
|
+
if (currentMopMode == smartMopMode) {
|
|
85
|
+
await this.client.send(duid, new RequestMessage({ method: 'set_mop_mode', params: [customMopRoute] }));
|
|
79
86
|
}
|
|
80
87
|
if (suctionPower && suctionPower != 0) {
|
|
81
88
|
await this.client.send(duid, new RequestMessage({ method: 'set_custom_mode', params: [suctionPower] }));
|
|
82
89
|
}
|
|
83
|
-
|
|
90
|
+
const CustomizeWithDistanceOff = 207;
|
|
91
|
+
if (waterFlow && waterFlow == CustomizeWithDistanceOff && distance_off && distance_off != 0) {
|
|
92
|
+
await this.client.send(duid, new RequestMessage({ method: 'set_water_box_custom_mode', params: { 'water_box_mode': waterFlow, 'distance_off': distance_off } }));
|
|
93
|
+
}
|
|
94
|
+
else if (waterFlow && waterFlow != 0) {
|
|
84
95
|
await this.client.send(duid, new RequestMessage({ method: 'set_water_box_custom_mode', params: [waterFlow] }));
|
|
85
96
|
}
|
|
97
|
+
if (mopRoute && mopRoute != 0) {
|
|
98
|
+
await this.client.send(duid, new RequestMessage({ method: 'set_mop_mode', params: [mopRoute] }));
|
|
99
|
+
}
|
|
86
100
|
}
|
|
87
101
|
}
|
|
@@ -7,10 +7,12 @@ import { CryptoUtils, MessageUtils } from './cryptoHelper.js';
|
|
|
7
7
|
import { Protocol } from '../broadcast/model/protocol.js';
|
|
8
8
|
export class MessageDeserializer {
|
|
9
9
|
context;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
messageParser;
|
|
11
|
+
logger;
|
|
12
|
+
constructor(context, logger) {
|
|
12
13
|
this.context = context;
|
|
13
|
-
this.
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
this.messageParser = new Parser()
|
|
14
16
|
.endianess('big')
|
|
15
17
|
.string('version', {
|
|
16
18
|
length: 3,
|
|
@@ -31,13 +33,13 @@ export class MessageDeserializer {
|
|
|
31
33
|
throw new Error('unknown protocol version ' + version);
|
|
32
34
|
}
|
|
33
35
|
const crc32 = CRC32.buf(message.subarray(0, message.length - 4)) >>> 0;
|
|
34
|
-
const expectedCrc32 = message.
|
|
36
|
+
const expectedCrc32 = message.readUInt32BE(message.length - 4);
|
|
35
37
|
if (crc32 != expectedCrc32) {
|
|
36
|
-
|
|
38
|
+
throw new Error(`Wrong CRC32 ${crc32}, expected ${expectedCrc32}`);
|
|
37
39
|
}
|
|
38
40
|
const localKey = this.context.getLocalKey(duid);
|
|
39
41
|
assert(localKey, 'unable to retrieve local key for ' + duid);
|
|
40
|
-
const data = this.
|
|
42
|
+
const data = this.messageParser.parse(message);
|
|
41
43
|
if (version == '1.0') {
|
|
42
44
|
const aesKey = CryptoUtils.md5bin(MessageUtils.encodeTimestamp(data.timestamp) + localKey + MessageUtils.SALT);
|
|
43
45
|
const decipher = crypto.createDecipheriv('aes-128-ecb', aesKey, null);
|
|
@@ -55,6 +57,7 @@ export class MessageDeserializer {
|
|
|
55
57
|
return this.deserializeProtocolRpcResponse(duid, data);
|
|
56
58
|
}
|
|
57
59
|
else {
|
|
60
|
+
this.logger.error('unknown protocol: ' + data.protocol);
|
|
58
61
|
return new ResponseMessage(duid, { dps: { id: 0, result: null } });
|
|
59
62
|
}
|
|
60
63
|
}
|
|
@@ -3,10 +3,12 @@ import crypto from 'crypto';
|
|
|
3
3
|
import CRC32 from 'crc-32';
|
|
4
4
|
export class MessageSerializer {
|
|
5
5
|
context;
|
|
6
|
+
logger;
|
|
6
7
|
seq = 1;
|
|
7
8
|
random = 4711;
|
|
8
|
-
constructor(context) {
|
|
9
|
+
constructor(context, logger) {
|
|
9
10
|
this.context = context;
|
|
11
|
+
this.logger = logger;
|
|
10
12
|
}
|
|
11
13
|
serialize(duid, request) {
|
|
12
14
|
const messageId = request.messageId;
|
package/dist/roborockService.js
CHANGED
|
@@ -48,9 +48,9 @@ export default class RoborockService {
|
|
|
48
48
|
getSupportedAreas(duid) {
|
|
49
49
|
return this.supportedAreas.get(duid);
|
|
50
50
|
}
|
|
51
|
-
async changeCleanMode(duid, { suctionPower, waterFlow, mopRoute }) {
|
|
51
|
+
async changeCleanMode(duid, { suctionPower, waterFlow, distance_off, mopRoute }) {
|
|
52
52
|
this.logger.notice('changeCleanMode');
|
|
53
|
-
return this.messageProcessor?.changeCleanMode(duid, suctionPower, waterFlow, mopRoute);
|
|
53
|
+
return this.messageProcessor?.changeCleanMode(duid, suctionPower, waterFlow, mopRoute, distance_off);
|
|
54
54
|
}
|
|
55
55
|
async startClean(duid) {
|
|
56
56
|
const areas = this.supportedAreas.get(duid);
|
|
@@ -89,6 +89,9 @@ export default class RoborockService {
|
|
|
89
89
|
this.logger.debug('customGetInSecure-message', method);
|
|
90
90
|
return this.getMessageProcessor()?.getCustomMessage(duid, new RequestMessage({ method, secure: true }));
|
|
91
91
|
}
|
|
92
|
+
async customSend(duid, request) {
|
|
93
|
+
return this.getMessageProcessor()?.sendCustomMessage(duid, request);
|
|
94
|
+
}
|
|
92
95
|
stopService() {
|
|
93
96
|
if (this.messageClient) {
|
|
94
97
|
this.messageClient.disconnect();
|
package/dist/share/function.js
CHANGED
|
@@ -63,12 +63,6 @@ export function state_to_matter_operational_status(state) {
|
|
|
63
63
|
case OperationStatusCode.ZoneCleanMopCleaning:
|
|
64
64
|
case OperationStatusCode.ZoneCleanMopMopping:
|
|
65
65
|
case OperationStatusCode.RobotStatusMopping:
|
|
66
|
-
case OperationStatusCode.WashingTheMop:
|
|
67
|
-
case OperationStatusCode.WashingTheMop2:
|
|
68
|
-
case OperationStatusCode.ReturnToDock:
|
|
69
|
-
case OperationStatusCode.GoingToWashTheMop:
|
|
70
|
-
case OperationStatusCode.BackToDockWashingDuster:
|
|
71
|
-
case OperationStatusCode.EmptyingDustContainer:
|
|
72
66
|
return RvcOperationalState.OperationalState.Running;
|
|
73
67
|
case OperationStatusCode.InError:
|
|
74
68
|
case OperationStatusCode.ChargingError:
|
|
@@ -82,7 +76,13 @@ export function state_to_matter_operational_status(state) {
|
|
|
82
76
|
case OperationStatusCode.ShuttingDown:
|
|
83
77
|
case OperationStatusCode.Locked:
|
|
84
78
|
return RvcOperationalState.OperationalState.Stopped;
|
|
79
|
+
case OperationStatusCode.ReturnToDock:
|
|
85
80
|
case OperationStatusCode.ReturningDock:
|
|
81
|
+
case OperationStatusCode.WashingTheMop:
|
|
82
|
+
case OperationStatusCode.WashingTheMop2:
|
|
83
|
+
case OperationStatusCode.GoingToWashTheMop:
|
|
84
|
+
case OperationStatusCode.BackToDockWashingDuster:
|
|
85
|
+
case OperationStatusCode.EmptyingDustContainer:
|
|
86
86
|
return RvcOperationalState.OperationalState.SeekingCharger;
|
|
87
87
|
case OperationStatusCode.Idle:
|
|
88
88
|
case OperationStatusCode.Sleeping:
|
|
@@ -1,8 +1,8 @@
|
|
|
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.0.8-rc01 by https://github.com/RinDevJunior",
|
|
4
4
|
"type": "object",
|
|
5
|
-
"required": [],
|
|
5
|
+
"required": ["username", "password"],
|
|
6
6
|
"properties": {
|
|
7
7
|
"name": {
|
|
8
8
|
"description": "Plugin name",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"ui:widget": "hidden"
|
|
18
18
|
},
|
|
19
19
|
"whiteList": {
|
|
20
|
-
"description": "(ONLY SET IF YOU HAVE MULTIPLE VACUUMS) Only THE FIRST ONE device with
|
|
20
|
+
"description": "(ONLY SET IF YOU HAVE MULTIPLE VACUUMS) Only THE FIRST ONE device with <Name>-<duid> in the list will be exposed. If the list is empty, THE FIRST device will be exposed.",
|
|
21
21
|
"type": "array",
|
|
22
22
|
"items": {
|
|
23
23
|
"type": "string"
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"password": {
|
|
33
33
|
"description": "Roborock password (required for the plugin to work)",
|
|
34
|
-
"type": "string"
|
|
34
|
+
"type": "string",
|
|
35
|
+
"ui:widget": "password"
|
|
35
36
|
},
|
|
36
37
|
"refreshInterval": {
|
|
37
38
|
"description": "Refresh interval in seconds (default: 60)",
|
|
@@ -43,6 +44,103 @@
|
|
|
43
44
|
"type": "boolean",
|
|
44
45
|
"default": false
|
|
45
46
|
},
|
|
47
|
+
"cleanModeSettings": {
|
|
48
|
+
"description": "Configure default cleaning parameters for vacuum.",
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"vacuuming": {
|
|
52
|
+
"description": "Vacuuming only",
|
|
53
|
+
"type": "object",
|
|
54
|
+
"properties": {
|
|
55
|
+
"fanMode": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "Suction power mode to use in Vacuuming only mode (e.g., 'Quiet', 'Balanced', 'Turbo', 'Max', 'MaxPlus').",
|
|
58
|
+
"default": "Balanced"
|
|
59
|
+
},
|
|
60
|
+
"mopRouteMode": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Mop route intensity to use in Vacuuming only mode (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
|
|
63
|
+
"default": "Standard"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"mopping": {
|
|
68
|
+
"description": "Mopping only",
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"waterFlowMode": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Water flow mode to use in Mopping only mode (e.g., 'Low', 'Medium', 'High', 'CustomizeWithDistanceOff').",
|
|
74
|
+
"default": "Medium"
|
|
75
|
+
},
|
|
76
|
+
"mopRouteMode": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "Mop route intensity to use in Mopping only mode (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
|
|
79
|
+
"default": "Standard"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"allOf": [
|
|
83
|
+
{
|
|
84
|
+
"if": {
|
|
85
|
+
"properties": {
|
|
86
|
+
"waterFlowMode": { "const": "CustomizeWithDistanceOff" }
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"then": {
|
|
90
|
+
"properties": {
|
|
91
|
+
"distanceOff": {
|
|
92
|
+
"type": "number",
|
|
93
|
+
"description": "Distance Off - Default = 25",
|
|
94
|
+
"default": 25
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"required": ["distanceOff"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
},
|
|
102
|
+
"vacmop": {
|
|
103
|
+
"description": "Vacuuming and Mopping",
|
|
104
|
+
"type": "object",
|
|
105
|
+
"properties": {
|
|
106
|
+
"fanMode": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"description": "Suction power mode to use in Vacuuming and Mopping mode (e.g., 'Quiet', 'Balanced', 'Turbo', 'Max', 'MaxPlus').",
|
|
109
|
+
"default": "Balanced"
|
|
110
|
+
},
|
|
111
|
+
"waterFlowMode": {
|
|
112
|
+
"type": "string",
|
|
113
|
+
"description": "Water flow mode to use in Vacuuming and Mopping mode (e.g., 'Low', 'Medium', 'High', 'CustomizeWithDistanceOff').",
|
|
114
|
+
"default": "Medium"
|
|
115
|
+
},
|
|
116
|
+
"mopRouteMode": {
|
|
117
|
+
"type": "string",
|
|
118
|
+
"description": "Mop route intensity to use in Vacuuming and Mopping mode (e.g., 'Standard', 'Deep', 'DeepPlus', 'Fast').",
|
|
119
|
+
"default": "Standard"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"allOf": [
|
|
123
|
+
{
|
|
124
|
+
"if": {
|
|
125
|
+
"properties": {
|
|
126
|
+
"waterFlowMode": { "const": "CustomizeWithDistanceOff" }
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
"then": {
|
|
130
|
+
"properties": {
|
|
131
|
+
"distanceOff": {
|
|
132
|
+
"type": "number",
|
|
133
|
+
"description": "Distance Off - Default = 25",
|
|
134
|
+
"default": 25
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"required": ["distanceOff"]
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
46
144
|
"debug": {
|
|
47
145
|
"description": "Enable the debug for the plugin (development only)",
|
|
48
146
|
"type": "boolean",
|
|
@@ -53,5 +151,20 @@
|
|
|
53
151
|
"type": "boolean",
|
|
54
152
|
"default": false
|
|
55
153
|
}
|
|
56
|
-
}
|
|
154
|
+
},
|
|
155
|
+
"dependencies": {
|
|
156
|
+
"cleanModeSettings": ["enableExperimentalFeature"]
|
|
157
|
+
},
|
|
158
|
+
"allOf": [
|
|
159
|
+
{
|
|
160
|
+
"if": {
|
|
161
|
+
"properties": { "enableExperimentalFeature": { "const": false } }
|
|
162
|
+
},
|
|
163
|
+
"then": {
|
|
164
|
+
"properties": {
|
|
165
|
+
"cleanModeSettings": false
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
]
|
|
57
170
|
}
|