Skip to content

Commit

Permalink
feat(vendor.roborock): Add support for the S7 Pro Ultra
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Apr 21, 2023
1 parent c39cd7e commit 87636ba
Show file tree
Hide file tree
Showing 6 changed files with 287 additions and 12 deletions.
151 changes: 139 additions & 12 deletions backend/lib/robots/roborock/RoborockQuirkFactory.js
Expand Up @@ -18,20 +18,20 @@ class RoborockQuirkFactory {
return new Quirk({
id: id,
title: "Auto Empty Duration",
description: "Set the dustbin emptying duration when triggered by robot after docking.",
options: ["smart", "quick", "daily", "max"],
description: "Select how long the dock should empty the dustbin on each auto empty cycle.",
options: ["auto", "short", "medium", "long"],
getter: async() => {
const res = await this.robot.sendCommand("get_dust_collection_mode", [], {});

switch (res?.mode) {
case 4:
return "max";
return "long";
case 2:
return "daily";
return "medium";
case 1:
return "quick";
return "short";
case 0:
return "smart";
return "auto";
default:
throw new Error(`Received invalid value ${res?.mode}`);
}
Expand All @@ -40,16 +40,16 @@ class RoborockQuirkFactory {
let val;

switch (value) {
case "max":
case "long":
val = 4;
break;
case "daily":
case "medium":
val = 2;
break;
case "quick":
case "short":
val = 1;
break;
case "smart":
case "auto":
val = 0;
break;
default:
Expand All @@ -59,6 +59,131 @@ class RoborockQuirkFactory {
return this.robot.sendCommand("set_dust_collection_mode", { "mode": val }, {});
}
});
case RoborockQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_MODE:
return new Quirk({
id: id,
title: "Mop Dock Mop Cleaning Mode",
description: "Select how long the mop should be cleaned by the dock on each mop cleaning cycle.",
options: ["short", "medium", "long"],
getter: async() => {
const res = await this.robot.sendCommand("get_wash_towel_mode", [], {});

switch (res?.wash_mode) {
case 2:
return "long";
case 1:
return "medium";
case 0:
return "short";
default:
throw new Error(`Received invalid value ${res?.wash_mode}`);
}
},
setter: async(value) => {
let val;

switch (value) {
case "long":
val = 2;
break;
case "medium":
val = 1;
break;
case "short":
val = 0;
break;
default:
throw new Error(`Received invalid value ${value}`);
}

return this.robot.sendCommand("set_wash_towel_mode", { "wash_mode": val }, {});
}
});
case RoborockQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY:
return new Quirk({
id: id,
title: "Mop Dock Mop Cleaning Frequency",
description: "Determine how often the robot should clean and re-wet its mop during a cleanup.",
options: [
"every_segment",
"every_5_min",
"every_10_min",
"every_15_min",
"every_20_min",
"every_30_min",
"every_45_min"
],
getter: async() => {
const res = await this.robot.sendCommand("get_smart_wash_params", [], {});

if (res?.smart_wash === 1) {
switch (res.wash_interval) {
case 300:
return "every_5_min";
case 600:
return "every_10_min";
case 900:
return "every_15_min";
case 1200:
return "every_20_min";
case 1800:
return "every_30_min";
case 2700:
return "every_45_min";
}

// Fallback to return something if someone set values by other means
if (res.wash_interval < 300) {
return "every_5_min";
} else {
return "every_45_min";
}
} else if (res?.smart_wash === 0) {
return "every_segment";
} else {
throw new Error(`Received invalid value ${res}`);
}
},
setter: async(value) => {
let mode;
let interval;

switch (value) {
case "every_segment":
mode = 0;
interval = 1200;
break;
case "every_5_min":
mode = 1;
interval = 300;
break;
case "every_10_min":
mode = 1;
interval = 600;
break;
case "every_15_min":
mode = 1;
interval = 900;
break;
case "every_20_min":
mode = 1;
interval = 1200;
break;
case "every_30_min":
mode = 1;
interval = 1800;
break;
case "every_45_min":
mode = 1;
interval = 2700;
break;
default:
throw new Error(`Received invalid value ${value}`);
}

return this.robot.sendCommand("set_smart_wash_params", { "smart_wash": mode, wash_interval: interval }, {});
}
});
case RoborockQuirkFactory.KNOWN_QUIRKS.BUTTON_LEDS:
return new Quirk({
id: id,
Expand Down Expand Up @@ -133,7 +258,7 @@ class RoborockQuirkFactory {
return new Quirk({
id: id,
title: "Carpet Handling",
description: "Select how the robot should deal with carpet detected by a dedicated sensor when the mop is attached",
description: "Select how the robot should deal with carpet detected by a dedicated sensor when the mop is attached.",
options: ["raise_mop", "avoid", "ignore"],
getter: async() => {
const res = await this.robot.sendCommand("get_carpet_clean_mode", [], {});
Expand Down Expand Up @@ -173,7 +298,7 @@ class RoborockQuirkFactory {
return new Quirk({
id: id,
title: "Mop Pattern",
description: "Select which movement mode to use while mopping",
description: "Select which movement mode to use while mopping.",
options: ["standard", "deep"],
getter: async () => {
const res = await this.robot.sendCommand("get_mop_mode", [], {});
Expand Down Expand Up @@ -234,6 +359,8 @@ RoborockQuirkFactory.KNOWN_QUIRKS = {
CARPET_HANDLING: "070c07ef-e35b-476f-9f80-6a286fef1a48",
MOP_PATTERN: "767fc859-3383-4485-bfdf-7aa800cf487e",
MANUAL_MAP_SEGMENT_TRIGGER: "3e467ac1-7d14-4e66-b09b-8d0554a3194e",
MOP_DOCK_MOP_CLEANING_FREQUENCY: "c50d98fb-7e29-4d09-a577-70c95ac33239",
MOP_DOCK_MOP_CLEANING_MODE: "b4ca6500-a461-49cb-966a-4726a33ad3df"
};

module.exports = RoborockQuirkFactory;
121 changes: 121 additions & 0 deletions backend/lib/robots/roborock/RoborockS7ProUltraValetudoRobot.js
@@ -0,0 +1,121 @@
const capabilities = require("./capabilities");
const entities = require("../../entities");
const MiioValetudoRobot = require("../MiioValetudoRobot");
const QuirksCapability = require("../../core/capabilities/QuirksCapability");
const RoborockGen4ValetudoRobot = require("./RoborockGen4ValetudoRobot");
const RoborockQuirkFactory = require("./RoborockQuirkFactory");
const RoborockValetudoRobot = require("./RoborockValetudoRobot");
const ValetudoRestrictedZone = require("../../entities/core/ValetudoRestrictedZone");


class RoborockS7ProUltraValetudoRobot extends RoborockGen4ValetudoRobot {
/**
*
* @param {object} options
* @param {import("../../Configuration")} options.config
* @param {import("../../ValetudoEventStore")} options.valetudoEventStore
*/
constructor(options) {
super(
Object.assign(
{},
options,
{
waterGrades: WATER_GRADES,
supportedAttachments: SUPPORTED_ATTACHMENTS
}
)
);


this.registerCapability(new capabilities.RoborockCombinedVirtualRestrictionsCapability({
robot: this,
supportedRestrictedZoneTypes: [
ValetudoRestrictedZone.TYPE.REGULAR,
ValetudoRestrictedZone.TYPE.MOP
]
}));

this.registerCapability(new capabilities.RoborockWaterUsageControlCapability({
robot: this,
presets: Object.keys(this.waterGrades).map(k => {
return new entities.core.ValetudoSelectionPreset({name: k, value: this.waterGrades[k]});
})
}));

[
capabilities.RoborockAutoEmptyDockAutoEmptyControlCapability,
capabilities.RoborockAutoEmptyDockManualTriggerCapability,
capabilities.RoborockMopDockCleanManualTriggerCapability,
capabilities.RoborockKeyLockCapability,
capabilities.RoborockMappingPassCapability
].forEach(capability => {
this.registerCapability(new capability({robot: this}));
});

const quirkFactory = new RoborockQuirkFactory({
robot: this
});
this.registerCapability(new QuirksCapability({
robot: this,
quirks: [
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.AUTO_EMPTY_DURATION),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_MODE),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.BUTTON_LEDS),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.CARPET_HANDLING),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.MOP_PATTERN),
]
}));

this.state.upsertFirstMatchingAttribute(new entities.state.attributes.DockStatusStateAttribute({
value: entities.state.attributes.DockStatusStateAttribute.VALUE.IDLE
}));
}

parseAndUpdateState(data) {
if (data["state"] !== undefined) {
switch (data["state"]) {
case 23:
case 25:
case 26:
this.state.upsertFirstMatchingAttribute(new entities.state.attributes.DockStatusStateAttribute({
value: entities.state.attributes.DockStatusStateAttribute.VALUE.CLEANING
}));
break;
default:
this.state.upsertFirstMatchingAttribute(new entities.state.attributes.DockStatusStateAttribute({
value: entities.state.attributes.DockStatusStateAttribute.VALUE.IDLE
}));
}
}


return super.parseAndUpdateState(data);
}

getModelName() {
return "S7 Pro Ultra";
}

static IMPLEMENTATION_AUTO_DETECTION_HANDLER() {
const deviceConf = MiioValetudoRobot.READ_DEVICE_CONF(RoborockValetudoRobot.DEVICE_CONF_PATH);

return !!(deviceConf && deviceConf.model === "roborock.vacuum.a62");
}
}

const WATER_GRADES = {
[entities.state.attributes.PresetSelectionStateAttribute.INTENSITY.OFF] : 200,
[entities.state.attributes.PresetSelectionStateAttribute.INTENSITY.LOW]: 201,
[entities.state.attributes.PresetSelectionStateAttribute.INTENSITY.MEDIUM]: 202,
[entities.state.attributes.PresetSelectionStateAttribute.INTENSITY.HIGH]: 203
};

const SUPPORTED_ATTACHMENTS = [
entities.state.attributes.AttachmentStateAttribute.TYPE.WATERTANK,
entities.state.attributes.AttachmentStateAttribute.TYPE.MOP,
];


module.exports = RoborockS7ProUltraValetudoRobot;
9 changes: 9 additions & 0 deletions backend/lib/robots/roborock/RoborockValetudoRobot.js
Expand Up @@ -630,6 +630,15 @@ const STATUS_MAP = {
value: stateAttrs.StatusStateAttribute.VALUE.CLEANING,
flag: stateAttrs.StatusStateAttribute.FLAG.SEGMENT
},
23: {
value: stateAttrs.StatusStateAttribute.VALUE.DOCKED
},
25: {
value: stateAttrs.StatusStateAttribute.VALUE.RETURNING
},
26: {
value: stateAttrs.StatusStateAttribute.VALUE.RETURNING
},
29: {
value: stateAttrs.StatusStateAttribute.VALUE.MOVING,
flag: stateAttrs.StatusStateAttribute.FLAG.MAPPING
Expand Down
@@ -0,0 +1,16 @@
const MopDockCleanManualTriggerCapability = require("../../../core/capabilities/MopDockCleanManualTriggerCapability");

/**
* @extends MopDockCleanManualTriggerCapability<import("../RoborockValetudoRobot")>
*/
class RoborockMopDockCleanManualTriggerCapability extends MopDockCleanManualTriggerCapability {
async startCleaning() {
await this.robot.sendCommand("app_start_wash", [], {});
}

async stopCleaning() {
await this.robot.sendCommand("app_stop_wash", [], {});
}
}

module.exports = RoborockMopDockCleanManualTriggerCapability;
1 change: 1 addition & 0 deletions backend/lib/robots/roborock/capabilities/index.js
Expand Up @@ -19,6 +19,7 @@ module.exports = {
RoborockMapSegmentationCapability: require("./RoborockMapSegmentationCapability"),
RoborockMapSnapshotCapability: require("./RoborockMapSnapshotCapability"),
RoborockMappingPassCapability: require("./RoborockMappingPassCapability"),
RoborockMopDockCleanManualTriggerCapability: require("./RoborockMopDockCleanManualTriggerCapability"),
RoborockMultiMapMapResetCapability: require("./RoborockMultiMapMapResetCapability"),
RoborockMultiMapPersistentMapControlCapability: require("./RoborockMultiMapPersistentMapControlCapability"),
RoborockPersistentMapControlCapability: require("./RoborockPersistentMapControlCapability"),
Expand Down
1 change: 1 addition & 0 deletions backend/lib/robots/roborock/index.js
Expand Up @@ -8,6 +8,7 @@ module.exports = {
"RoborockS6MaxVValetudoRobot": require("./RoborockS6MaxVValetudoRobot"),
"RoborockS6PureValetudoRobot": require("./RoborockS6PureValetudoRobot"),
"RoborockS6ValetudoRobot": require("./RoborockS6ValetudoRobot"),
"RoborockS7ProUltraValetudoRobot": require("./RoborockS7ProUltraValetudoRobot"),
"RoborockS7ValetudoRobot": require("./RoborockS7ValetudoRobot"),
"RoborockV1ValetudoRobot": require("./RoborockV1ValetudoRobot")
};

0 comments on commit 87636ba

Please sign in to comment.