Skip to content

Commit 12abbd3

Browse files
committed
feat(vendor.dreame): L20 Ultra (MR813)
1 parent bc2445d commit 12abbd3

4 files changed

Lines changed: 320 additions & 3 deletions

File tree

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
const capabilities = require("./capabilities");
2+
const DreameGen2ValetudoRobot = require("./DreameGen2ValetudoRobot");
3+
const DreameGen4ValetudoRobot = require("./DreameGen4ValetudoRobot");
4+
5+
const DreameQuirkFactory = require("./DreameQuirkFactory");
6+
const DreameValetudoRobot = require("./DreameValetudoRobot");
7+
const entities = require("../../entities");
8+
const Logger = require("../../Logger");
9+
const MiioValetudoRobot = require("../MiioValetudoRobot");
10+
const QuirksCapability = require("../../core/capabilities/QuirksCapability");
11+
const ValetudoSelectionPreset = require("../../entities/core/ValetudoSelectionPreset");
12+
const {IMAGE_FILE_FORMAT} = require("../../utils/const");
13+
14+
const stateAttrs = entities.state.attributes;
15+
16+
class DreameL20UltraValetudoRobot extends DreameGen4ValetudoRobot {
17+
18+
/**
19+
*
20+
* @param {object} options
21+
* @param {import("../../Configuration")} options.config
22+
* @param {import("../../ValetudoEventStore")} options.valetudoEventStore
23+
*/
24+
constructor(options) {
25+
super(
26+
Object.assign(
27+
{},
28+
{
29+
operationModes: Object.freeze({
30+
[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM_AND_MOP]: 0,
31+
[stateAttrs.PresetSelectionStateAttribute.MODE.MOP]: 1,
32+
[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM]: 2,
33+
[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM_THEN_MOP]: 3,
34+
}),
35+
highResolutionWaterGrades: true
36+
},
37+
options,
38+
)
39+
);
40+
41+
this.registerCapability(new capabilities.DreameMapSegmentationCapability({
42+
robot: this,
43+
miot_actions: {
44+
start: {
45+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
46+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.ACTIONS.START.AIID
47+
}
48+
},
49+
miot_properties: {
50+
mode: {
51+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MODE.PIID
52+
},
53+
additionalCleanupParameters: {
54+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.ADDITIONAL_CLEANUP_PROPERTIES.PIID
55+
}
56+
},
57+
segmentCleaningModeId: 18,
58+
iterationsSupported: 4,
59+
customOrderSupported: true,
60+
newOrder: true
61+
}));
62+
63+
64+
const quirkFactory = new DreameQuirkFactory({
65+
robot: this
66+
});
67+
68+
this.registerCapability(new capabilities.DreameWaterUsageControlCapability({
69+
robot: this,
70+
presets: Object.keys(this.waterGrades).map(k => {
71+
return new ValetudoSelectionPreset({name: k, value: this.waterGrades[k]});
72+
}),
73+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.MOP_EXPANSION.SIID,
74+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.MOP_EXPANSION.PROPERTIES.HIGH_RES_WATER_USAGE.PIID
75+
}));
76+
77+
this.registerCapability(new capabilities.DreameZoneCleaningCapability({
78+
robot: this,
79+
miot_actions: {
80+
start: {
81+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
82+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.ACTIONS.START.AIID
83+
}
84+
},
85+
miot_properties: {
86+
mode: {
87+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MODE.PIID
88+
},
89+
additionalCleanupParameters: {
90+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.ADDITIONAL_CLEANUP_PROPERTIES.PIID
91+
}
92+
},
93+
zoneCleaningModeId: 19,
94+
maxZoneCount: 4
95+
}));
96+
97+
this.registerCapability(new capabilities.DreameConsumableMonitoringCapability({
98+
robot: this,
99+
miot_properties: {
100+
main_brush: {
101+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAIN_BRUSH.SIID,
102+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAIN_BRUSH.PROPERTIES.TIME_LEFT.PIID
103+
},
104+
side_brush: {
105+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.SIDE_BRUSH.SIID,
106+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.SIDE_BRUSH.PROPERTIES.TIME_LEFT.PIID
107+
},
108+
filter: {
109+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.FILTER.SIID,
110+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.FILTER.PROPERTIES.TIME_LEFT.PIID
111+
},
112+
sensor: {
113+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.SENSOR.SIID,
114+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.SENSOR.PROPERTIES.TIME_LEFT.PIID
115+
},
116+
wheel: {
117+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.WHEEL.SIID,
118+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.WHEEL.PROPERTIES.TIME_LEFT.PIID
119+
}
120+
},
121+
miot_actions: {
122+
reset_main_brush: {
123+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAIN_BRUSH.SIID,
124+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAIN_BRUSH.ACTIONS.RESET.AIID
125+
},
126+
reset_side_brush: {
127+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.SIDE_BRUSH.SIID,
128+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.SIDE_BRUSH.ACTIONS.RESET.AIID
129+
},
130+
reset_filter: {
131+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.FILTER.SIID,
132+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.FILTER.ACTIONS.RESET.AIID
133+
},
134+
reset_sensor: {
135+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.SENSOR.SIID,
136+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.SENSOR.ACTIONS.RESET.AIID
137+
},
138+
reset_wheel: {
139+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.WHEEL.SIID,
140+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.WHEEL.ACTIONS.RESET.AIID
141+
}
142+
},
143+
}));
144+
145+
this.registerCapability(new capabilities.DreameOperationModeControlCapability({
146+
robot: this,
147+
presets: Object.keys(this.operationModes).map(k => {
148+
return new ValetudoSelectionPreset({name: k, value: this.operationModes[k]});
149+
}),
150+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
151+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
152+
}));
153+
154+
this.registerCapability(new capabilities.DreameCarpetSensorModeControlCapability({
155+
robot: this,
156+
liftSupported: true,
157+
detachSupported: true
158+
}));
159+
160+
this.registerCapability(new capabilities.DreameObstacleImagesCapability({
161+
robot: this,
162+
fileFormat: IMAGE_FILE_FORMAT.JPG,
163+
dimensions: {
164+
width: 672,
165+
height: 504
166+
}
167+
}));
168+
169+
this.registerCapability(new capabilities.DreameMapSegmentMaterialControlCapability({
170+
robot: this,
171+
miot_actions: {
172+
map_edit: {
173+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAP.SIID,
174+
aiid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAP.ACTIONS.EDIT.AIID
175+
}
176+
},
177+
miot_properties: {
178+
mapDetails: {
179+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAP.PROPERTIES.MAP_DETAILS.PIID
180+
},
181+
actionResult: {
182+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.MAP.PROPERTIES.ACTION_RESULT.PIID
183+
}
184+
},
185+
supportedMaterials: [
186+
capabilities.DreameMapSegmentMaterialControlCapability.MATERIAL.GENERIC,
187+
capabilities.DreameMapSegmentMaterialControlCapability.MATERIAL.TILE,
188+
capabilities.DreameMapSegmentMaterialControlCapability.MATERIAL.WOOD_VERTICAL,
189+
capabilities.DreameMapSegmentMaterialControlCapability.MATERIAL.WOOD_HORIZONTAL,
190+
]
191+
}));
192+
193+
194+
[
195+
capabilities.DreameCarpetModeControlCapability,
196+
capabilities.DreameKeyLockCapability,
197+
capabilities.DreameAutoEmptyDockManualTriggerCapability,
198+
capabilities.DreameMopDockCleanManualTriggerCapability,
199+
capabilities.DreameMopDockDryManualTriggerCapability,
200+
capabilities.DreameAICameraGoToLocationCapability,
201+
capabilities.DreameAICameraLineLaserObstacleAvoidanceControlCapability,
202+
capabilities.DreamePetObstacleAvoidanceControlCapability,
203+
capabilities.DreameCollisionAvoidantNavigationControlCapability,
204+
capabilities.DreameAutoEmptyDockAutoEmptyIntervalControlCapabilityV2,
205+
capabilities.DreameMopExtensionControlCapabilityV1,
206+
capabilities.DreameCameraLightControlCapability,
207+
capabilities.DreameMopTwistControlCapabilityV2,
208+
capabilities.DreameMopExtensionFurnitureLegHandlingControlCapability,
209+
capabilities.DreameMopDockMopAutoDryingControlCapability,
210+
capabilities.DreameFloorMaterialDirectionAwareNavigationControlCapability,
211+
capabilities.DreameCleanRouteControlCapabilityV2,
212+
capabilities.DreameMopDockMopDryingTimeControlCapability,
213+
].forEach(capability => {
214+
this.registerCapability(new capability({robot: this}));
215+
});
216+
217+
this.registerCapability(new QuirksCapability({
218+
robot: this,
219+
quirks: [
220+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_MODE_SENSITIVITY),
221+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY),
222+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DRYING_TIME),
223+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_DETERGENT),
224+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WET_DRY_SWITCH),
225+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_AUTO_REPAIR_TRIGGER),
226+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.DRAIN_INTERNAL_WATER_TANK),
227+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_AUTO_DEEP_CLEANING),
228+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WATER_USAGE),
229+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.DETACH_MOPS),
230+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_CLEANING_PROCESS_TRIGGER),
231+
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.WATER_HOOKUP_TEST_TRIGGER),
232+
]
233+
}));
234+
235+
this.state.upsertFirstMatchingAttribute(new entities.state.attributes.DockStatusStateAttribute({
236+
value: entities.state.attributes.DockStatusStateAttribute.VALUE.IDLE
237+
}));
238+
239+
this.state.upsertFirstMatchingAttribute(new entities.state.attributes.AttachmentStateAttribute({
240+
type: entities.state.attributes.AttachmentStateAttribute.TYPE.MOP,
241+
attached: false
242+
}));
243+
}
244+
245+
parseAndUpdateState(data) {
246+
if (!Array.isArray(data)) {
247+
Logger.error("Received non-array state", data);
248+
return;
249+
}
250+
251+
// Filter out everything that might confuse the regular state parsing
252+
return super.parseAndUpdateState(data.filter(e => {
253+
return (
254+
!(
255+
e.siid === DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID &&
256+
e.piid === DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.WATER_USAGE.PIID
257+
)
258+
);
259+
}));
260+
}
261+
262+
getStatePropertiesToPoll() {
263+
const superProps = super.getStatePropertiesToPoll();
264+
265+
return [
266+
...superProps.filter(e => {
267+
return !(
268+
e.siid === DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID &&
269+
e.piid === DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.WATER_USAGE.PIID
270+
);
271+
}),
272+
273+
{
274+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.AUTO_EMPTY_DOCK.SIID,
275+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.AUTO_EMPTY_DOCK.PROPERTIES.STATE.PIID
276+
},
277+
{
278+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
279+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_STATUS.PIID
280+
},
281+
{
282+
siid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.SIID,
283+
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
284+
}
285+
];
286+
}
287+
288+
getModelName() {
289+
return "L20 Ultra";
290+
}
291+
292+
getModelDetails() {
293+
return Object.assign(
294+
{},
295+
super.getModelDetails(),
296+
{
297+
supportedAttachments: [
298+
stateAttrs.AttachmentStateAttribute.TYPE.MOP,
299+
]
300+
}
301+
);
302+
}
303+
304+
305+
static IMPLEMENTATION_AUTO_DETECTION_HANDLER() {
306+
const deviceConf = MiioValetudoRobot.READ_DEVICE_CONF(DreameValetudoRobot.DEVICE_CONF_PATH);
307+
308+
// R2253 is a different bot entirely and not supportable
309+
return [
310+
"dreame.vacuum.r2394j",
311+
"dreame.vacuum.r2394k",
312+
"dreame.vacuum.r2394s",
313+
].includes(deviceConf?.model);
314+
}
315+
}
316+
317+
318+
module.exports = DreameL20UltraValetudoRobot;

backend/lib/robots/dreame/DreameX30UltraValetudoRobot.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,9 @@ class DreameX30UltraValetudoRobot extends DreameGen4ValetudoRobot {
226226
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.DRAIN_INTERNAL_WATER_TANK),
227227
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_AUTO_DEEP_CLEANING),
228228
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WATER_USAGE),
229-
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.EDGE_EXTENSION_FREQUENCY),
230229
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.DETACH_MOPS),
231230
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_CLEANING_PROCESS_TRIGGER),
232231
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.WATER_HOOKUP_TEST_TRIGGER),
233-
quirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.SIDE_BRUSH_ON_CARPET),
234232
]
235233
}));
236234

backend/lib/robots/dreame/capabilities/DreameCleanRouteControlCapabilityV2.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class DreameCleanRouteControlCapabilityV2 extends CleanRouteControlCapability {
2121
this.piid = DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.MISC_TUNABLES.PIID;
2222

2323
this.helper = new DreameMiotHelper({robot: this.robot});
24-
this.quickSupported = !options.quickSupported; //default true
24+
this.quickSupported = options.quickSupported ?? true;
2525
}
2626

2727
async getRoute() {

backend/lib/robots/dreame/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module.exports = {
1212
"DreameL10SProValetudoRobot": require("./DreameL10SProValetudoRobot"),
1313
"DreameL10SUltraValetudoRobot": require("./DreameL10SUltraValetudoRobot"),
1414
"DreameL10UltraValetudoRobot": require("./DreameL10UltraValetudoRobot"),
15+
"DreameL20UltraValetudoRobot": require("./DreameL20UltraValetudoRobot"),
1516
"DreameL40UltraValetudoRobot": require("./DreameL40UltraValetudoRobot"),
1617
"DreameMovaP10ProUltraValetudoRobot": require("./DreameMovaP10ProUltraValetudoRobot"),
1718
"DreameMovaS20UltraValetudoRobot": require("./DreameMovaS20UltraValetudoRobot"),

0 commit comments

Comments
 (0)