feat: CarpetSensorModeControlCapability

This commit is contained in:
Sören Beye 2023-07-29 15:48:03 +02:00
parent ef93adf2d5
commit a4261b641e
28 changed files with 598 additions and 149 deletions

View File

@ -0,0 +1,64 @@
const Capability = require("./Capability");
const NotImplementedError = require("../NotImplementedError");
/**
* @template {import("../ValetudoRobot")} T
* @extends Capability<T>
*/
class CarpetSensorModeControlCapability extends Capability {
/**
*
* @param {object} options
* @param {T} options.robot
* @class
*/
constructor(options) {
super(options);
}
/**
* @returns {Promise<CarpetSensorModeControlCapabilityMode>}
*/
async getMode() {
throw new NotImplementedError();
}
/**
*
* @param {CarpetSensorModeControlCapabilityMode} newMode
* @returns {Promise<void>}
*/
async setMode(newMode) {
throw new NotImplementedError();
}
/**
* @returns {{supportedModes: Array<CarpetSensorModeControlCapabilityMode>}}
*/
getProperties() {
return {
supportedModes: []
};
}
getType() {
return CarpetSensorModeControlCapability.TYPE;
}
}
CarpetSensorModeControlCapability.TYPE = "CarpetSensorModeControlCapability";
/**
* @typedef {string} CarpetSensorModeControlCapabilityMode
* @enum {string}
*
*/
CarpetSensorModeControlCapability.MODE = Object.freeze({
OFF: "off",
AVOID: "avoid",
LIFT: "lift",
});
module.exports = CarpetSensorModeControlCapability;

View File

@ -3,6 +3,7 @@ module.exports = {
AutoEmptyDockManualTriggerCapability: require("./AutoEmptyDockManualTriggerCapability"),
BasicControlCapability: require("./BasicControlCapability"),
CarpetModeControlCapability: require("./CarpetModeControlCapability"),
CarpetSensorModeControlCapability: require("./CarpetSensorModeControlCapability"),
CollisionAvoidantNavigationControlCapability: require("./CollisionAvoidantNavigationControlCapability"),
CombinedVirtualRestrictionsCapability: require("./CombinedVirtualRestrictionsCapability"),
ConsumableMonitoringCapability: require("./ConsumableMonitoringCapability"),

View File

@ -125,6 +125,12 @@ class DreameL10SProValetudoRobot extends DreameGen2LidarValetudoRobot {
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
}));
this.registerCapability(new capabilities.DreameCarpetSensorModeControlCapability({
robot: this,
liftSupported: true
}));
[
capabilities.DreameCarpetModeControlCapability,
capabilities.DreameKeyLockCapability,
@ -139,8 +145,6 @@ class DreameL10SProValetudoRobot extends DreameGen2LidarValetudoRobot {
quirks: [
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_MODE_SENSITIVITY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.TIGHT_MOP_PATTERN),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_SENSOR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_LIFT_CARPET_BEHAVIOUR),
]
}));

View File

@ -132,6 +132,11 @@ class DreameL10SUltraValetudoRobot extends DreameGen2LidarValetudoRobot {
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
}));
this.registerCapability(new capabilities.DreameCarpetSensorModeControlCapability({
robot: this,
liftSupported: true
}));
[
capabilities.DreameCarpetModeControlCapability,
@ -154,8 +159,6 @@ class DreameL10SUltraValetudoRobot extends DreameGen2LidarValetudoRobot {
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_MODE_SENSITIVITY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.TIGHT_MOP_PATTERN),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.AUTO_EMPTY_INTERVAL),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_SENSOR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_LIFT_CARPET_BEHAVIOUR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DRYING_TIME),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_DETERGENT),

View File

@ -124,6 +124,12 @@ class DreameL10UltraValetudoRobot extends DreameGen2LidarValetudoRobot {
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
}));
this.registerCapability(new capabilities.DreameCarpetSensorModeControlCapability({
robot: this,
liftSupported: true
}));
[
capabilities.DreameCarpetModeControlCapability,
capabilities.DreameKeyLockCapability,
@ -142,8 +148,6 @@ class DreameL10UltraValetudoRobot extends DreameGen2LidarValetudoRobot {
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_MODE_SENSITIVITY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.TIGHT_MOP_PATTERN),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.AUTO_EMPTY_INTERVAL),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_SENSOR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_LIFT_CARPET_BEHAVIOUR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DRYING_TIME),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WET_DRY_SWITCH),

View File

@ -340,7 +340,7 @@ module.exports = {
MOP_DOCK_WET_DRY_SWITCH: {
PIID: 34
},
MOP_LIFT_CARPET_BEHAVIOUR: {
CARPET_DETECTION_SENSOR_MODE: {
PIID: 36
},
MOP_DOCK_DETERGENT: {

View File

@ -326,90 +326,6 @@ class DreameQuirkFactory {
);
}
});
case DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_SENSOR:
return new Quirk({
id: id,
title: "Carpet detection sensor",
description: "Detect carpets for carpet avoidance using a dedicated sensor",
options: ["on", "off"],
getter: async () => {
const res = await this.helper.readProperty(
DreameMiotServices["GEN2"].VACUUM_2.SIID,
DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.CARPET_DETECTION_SENSOR.PIID
);
switch (res) {
case 1:
return "on";
case 0:
return "off";
default:
throw new Error(`Received invalid value ${res}`);
}
},
setter: async (value) => {
let val;
switch (value) {
case "on":
val = 1;
break;
case "off":
val = 0;
break;
default:
throw new Error(`Received invalid value ${value}`);
}
return this.helper.writeProperty(
DreameMiotServices["GEN2"].VACUUM_2.SIID,
DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.CARPET_DETECTION_SENSOR.PIID,
val
);
}
});
case DreameQuirkFactory.KNOWN_QUIRKS.MOP_LIFT_CARPET_BEHAVIOUR:
return new Quirk({
id: id,
title: "Carpet Behaviour",
description: "Define how the robot should handle carpets when the mop is attached",
options: ["Lift Mop", "Avoid Carpet"],
getter: async () => {
const res = await this.helper.readProperty(
DreameMiotServices["GEN2"].VACUUM_2.SIID,
DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.MOP_LIFT_CARPET_BEHAVIOUR.PIID
);
switch (res) {
case 2:
return "Lift Mop";
case 1:
return "Avoid Carpet";
default:
throw new Error(`Received invalid value ${res}`);
}
},
setter: async (value) => {
let val;
switch (value) {
case "Lift Mop":
val = 2;
break;
case "Avoid Carpet":
val = 1;
break;
default:
throw new Error(`Received invalid value ${value}`);
}
return this.helper.writeProperty(
DreameMiotServices["GEN2"].VACUUM_2.SIID,
DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.MOP_LIFT_CARPET_BEHAVIOUR.PIID,
val
);
}
});
case DreameQuirkFactory.KNOWN_QUIRKS.MOP_DRYING_TIME:
return new Quirk({
id: id,
@ -676,8 +592,6 @@ DreameQuirkFactory.KNOWN_QUIRKS = {
MOP_DOCK_MOP_ONLY_MODE: "6afbb882-c4c4-4672-b008-887454e6e0d1",
MOP_DOCK_MOP_CLEANING_FREQUENCY: "a6709b18-57af-4e11-8b4c-8ae33147ab34",
MOP_DOCK_UV_TREATMENT: "7f97b603-967f-44f0-9dfb-35bcdc21f433",
CARPET_DETECTION_SENSOR: "38362a9d-6c8f-430a-aaaa-fd454e93e816",
MOP_LIFT_CARPET_BEHAVIOUR: "33ea65f7-f9a2-4462-a696-36019340a3e1",
MOP_DRYING_TIME: "516a1025-9c56-46e0-ac9b-a5007088d24a",
MOP_DOCK_DETERGENT: "a2a03d42-c710-45e5-b53a-4bc62778589f",
MOP_DOCK_WET_DRY_SWITCH: "66adac0f-0a16-4049-b6ac-080ef702bb39",

View File

@ -1,3 +1,4 @@
const DreameCarpetSensorModeControlCapability = require("./capabilities/DreameCarpetSensorModeControlCapability");
const DreameMopValetudoRobot = require("./DreameMopValetudoRobot");
const DreameQuirkFactory = require("./DreameQuirkFactory");
const DreameValetudoRobot = require("./DreameValetudoRobot");
@ -18,13 +19,17 @@ class DreameW10ValetudoRobot extends DreameMopValetudoRobot {
robot: this
});
this.registerCapability(new DreameCarpetSensorModeControlCapability({
robot: this,
liftSupported: false
}));
this.registerCapability(new QuirksCapability({
robot: this,
quirks: [
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_ONLY_MODE),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WET_DRY_SWITCH),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_SENSOR),
]
}));
}

View File

@ -124,6 +124,12 @@ class DreameX10PlusValetudoRobot extends DreameGen2LidarValetudoRobot {
piid: DreameGen2ValetudoRobot.MIOT_SERVICES.VACUUM_2.PROPERTIES.MOP_DOCK_SETTINGS.PIID
}));
this.registerCapability(new capabilities.DreameCarpetSensorModeControlCapability({
robot: this,
liftSupported: true
}));
[
capabilities.DreameCarpetModeControlCapability,
capabilities.DreameKeyLockCapability,
@ -145,8 +151,6 @@ class DreameX10PlusValetudoRobot extends DreameGen2LidarValetudoRobot {
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_MODE_SENSITIVITY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.TIGHT_MOP_PATTERN),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.AUTO_EMPTY_INTERVAL),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.CARPET_DETECTION_SENSOR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_LIFT_CARPET_BEHAVIOUR),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_MOP_CLEANING_FREQUENCY),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_WET_DRY_SWITCH),
QuirkFactory.getQuirk(DreameQuirkFactory.KNOWN_QUIRKS.MOP_DOCK_AUTO_REPAIR_TRIGGER),

View File

@ -0,0 +1,103 @@
const CarpetSensorModeControlCapability = require("../../../core/capabilities/CarpetSensorModeControlCapability");
const DreameMiotHelper = require("../DreameMiotHelper");
const DreameMiotServices = require("../DreameMiotServices");
/**
* @extends CarpetSensorModeControlCapability<import("../DreameValetudoRobot")>
*/
class DreameCarpetSensorModeControlCapability extends CarpetSensorModeControlCapability {
/**
* @param {object} options
* @param {import("../DreameValetudoRobot")} options.robot
* @param {boolean} options.liftSupported
*/
constructor(options) {
super(options);
this.liftSupported = options.liftSupported;
this.siid = DreameMiotServices["GEN2"].VACUUM_2.SIID;
this.sensor_piid = DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.CARPET_DETECTION_SENSOR.PIID;
this.mode_piid = DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.CARPET_DETECTION_SENSOR_MODE.PIID;
this.helper = new DreameMiotHelper({robot: this.robot});
}
async getMode() {
const sensorRes = await this.helper.readProperty(this.siid, this.sensor_piid);
if (sensorRes === 0) {
return CarpetSensorModeControlCapability.MODE.OFF;
} else if (sensorRes === 1) {
if (this.liftSupported) {
const modeRes = await this.helper.readProperty(this.siid, this.mode_piid);
switch (modeRes) {
case 2:
return CarpetSensorModeControlCapability.MODE.LIFT;
case 1:
return CarpetSensorModeControlCapability.MODE.AVOID;
default:
throw new Error(`Received invalid mode ${modeRes}`);
}
} else {
return CarpetSensorModeControlCapability.MODE.AVOID;
}
} else {
throw new Error(`Received invalid sensor state ${sensorRes}`);
}
}
async setMode(newMode) {
let sensorVal;
let modeVal;
switch (newMode) {
case CarpetSensorModeControlCapability.MODE.LIFT:
if (!this.liftSupported) {
throw new Error(`Received unsupported mode ${newMode}`);
}
sensorVal = 1;
modeVal = 2;
break;
case CarpetSensorModeControlCapability.MODE.AVOID:
sensorVal = 1;
modeVal = 1;
break;
case CarpetSensorModeControlCapability.MODE.OFF:
sensorVal = 0;
modeVal = undefined;
break;
default:
throw new Error(`Received invalid mode ${newMode}`);
}
await this.helper.writeProperty(this.siid, this.sensor_piid, sensorVal);
if (this.liftSupported && modeVal !== undefined) {
await this.helper.writeProperty(this.siid, this.mode_piid, modeVal);
}
}
getProperties() {
const supportedModes = [
CarpetSensorModeControlCapability.MODE.AVOID,
CarpetSensorModeControlCapability.MODE.OFF,
];
if (this.liftSupported) {
supportedModes.push(
CarpetSensorModeControlCapability.MODE.LIFT
);
}
return {
supportedModes: supportedModes
};
}
}
module.exports = DreameCarpetSensorModeControlCapability;

View File

@ -9,6 +9,7 @@ module.exports = {
DreameAutoEmptyDockManualTriggerCapability: require("./DreameAutoEmptyDockManualTriggerCapability"),
DreameBasicControlCapability: require("./DreameBasicControlCapability"),
DreameCarpetModeControlCapability: require("./DreameCarpetModeControlCapability"),
DreameCarpetSensorModeControlCapability: require("./DreameCarpetSensorModeControlCapability"),
DreameCollisionAvoidantNavigationControlCapability: require("./DreameCollisionAvoidantNavigationControlCapability"),
DreameCombinedVirtualRestrictionsCapability: require("./DreameCombinedVirtualRestrictionsCapability"),
DreameConsumableMonitoringCapability: require("./DreameConsumableMonitoringCapability"),

View File

@ -254,46 +254,6 @@ class RoborockQuirkFactory {
return this.robot.sendCommand("set_flow_led_status", {"status": val}, {});
}
});
case RoborockQuirkFactory.KNOWN_QUIRKS.CARPET_HANDLING:
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.",
options: ["raise_mop", "avoid", "ignore"],
getter: async() => {
const res = await this.robot.sendCommand("get_carpet_clean_mode", [], {});
switch (res?.[0]?.carpet_clean_mode) {
case 2:
return "ignore";
case 1:
return "raise_mop";
case 0:
return "avoid";
default:
throw new Error(`Received invalid value ${res?.[0]?.carpet_clean_mode}`);
}
},
setter: async(value) => {
let val;
switch (value) {
case "ignore":
val = 2;
break;
case "raise_mop":
val = 1;
break;
case "avoid":
val = 0;
break;
default:
throw new Error(`Received invalid value ${value}`);
}
return this.robot.sendCommand("set_carpet_clean_mode", { "carpet_clean_mode": val }, {});
}
});
case RoborockQuirkFactory.KNOWN_QUIRKS.MOP_PATTERN:
return new Quirk({
id: id,
@ -356,7 +316,6 @@ RoborockQuirkFactory.KNOWN_QUIRKS = {
AUTO_EMPTY_DURATION: "7e33281f-d1bd-4e11-a100-b2c792284883",
BUTTON_LEDS: "57ffd1d3-306e-4451-b89c-934ec917fe7e",
STATUS_LED: "1daf5179-0689-48a5-8f1b-0a23e11836dc",
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",

View File

@ -48,6 +48,11 @@ class RoborockS7ProUltraValetudoRobot extends RoborockGen4ValetudoRobot {
hasUltraDock: true
}));
this.registerCapability(new capabilities.RoborockCarpetSensorModeControlCapability({
robot: this,
liftModeId: 1
}));
[
capabilities.RoborockAutoEmptyDockAutoEmptyControlCapability,
capabilities.RoborockAutoEmptyDockManualTriggerCapability,
@ -68,7 +73,6 @@ class RoborockS7ProUltraValetudoRobot extends RoborockGen4ValetudoRobot {
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),
]
}));

View File

@ -42,6 +42,11 @@ class RoborockS7ValetudoRobot extends RoborockGen4ValetudoRobot {
})
}));
this.registerCapability(new capabilities.RoborockCarpetSensorModeControlCapability({
robot: this,
liftModeId: 1
}));
[
capabilities.RoborockConsumableMonitoringCapability,
capabilities.RoborockAutoEmptyDockAutoEmptyControlCapability,
@ -61,7 +66,6 @@ class RoborockS7ValetudoRobot extends RoborockGen4ValetudoRobot {
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.AUTO_EMPTY_DURATION),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.BUTTON_LEDS),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.STATUS_LED),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.CARPET_HANDLING),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.MOP_PATTERN),
]
}));

View File

@ -44,6 +44,11 @@ class RoborockS8ValetudoRobot extends RoborockGen4ValetudoRobot {
})
}));
this.registerCapability(new capabilities.RoborockCarpetSensorModeControlCapability({
robot: this,
liftModeId: 3
}));
[
capabilities.RoborockConsumableMonitoringCapability,
capabilities.RoborockAutoEmptyDockAutoEmptyControlCapability,
@ -66,7 +71,6 @@ class RoborockS8ValetudoRobot extends RoborockGen4ValetudoRobot {
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.AUTO_EMPTY_DURATION),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.BUTTON_LEDS),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.STATUS_LED),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.CARPET_HANDLING),
quirkFactory.getQuirk(RoborockQuirkFactory.KNOWN_QUIRKS.MOP_PATTERN),
]
}));

View File

@ -0,0 +1,66 @@
const CarpetSensorModeControlCapability = require("../../../core/capabilities/CarpetSensorModeControlCapability");
/**
* @extends CarpetSensorModeControlCapability<import("../RoborockValetudoRobot")>
*/
class RoborockCarpetSensorModeControlCapability extends CarpetSensorModeControlCapability {
/**
* @param {object} options
* @param {import("../RoborockValetudoRobot")} options.robot
* @param {number} options.liftModeId
*/
constructor(options) {
super(options);
this.liftModeId = options.liftModeId;
}
async getMode() {
const res = await this.robot.sendCommand("get_carpet_clean_mode", [], {});
switch (res?.[0]?.carpet_clean_mode) {
case 2:
return CarpetSensorModeControlCapability.MODE.OFF;
case 1: //Non-camera based?
case 3: //Camera-based?
return CarpetSensorModeControlCapability.MODE.LIFT;
case 0:
return CarpetSensorModeControlCapability.MODE.AVOID;
default:
throw new Error(`Received invalid value ${res?.[0]?.carpet_clean_mode}`);
}
}
async setMode(newMode) {
let val;
switch (newMode) {
case CarpetSensorModeControlCapability.MODE.OFF:
val = 2;
break;
case CarpetSensorModeControlCapability.MODE.LIFT:
val = this.liftModeId;
break;
case CarpetSensorModeControlCapability.MODE.AVOID:
val = 0;
break;
default:
throw new Error(`Received invalid mode ${newMode}`);
}
return this.robot.sendCommand("set_carpet_clean_mode", { "carpet_clean_mode": val }, {});
}
getProperties() {
return {
supportedModes: [
CarpetSensorModeControlCapability.MODE.LIFT,
CarpetSensorModeControlCapability.MODE.AVOID,
CarpetSensorModeControlCapability.MODE.OFF,
]
};
}
}
module.exports = RoborockCarpetSensorModeControlCapability;

View File

@ -3,6 +3,7 @@ module.exports = {
RoborockAutoEmptyDockManualTriggerCapability: require("./RoborockAutoEmptyDockManualTriggerCapability"),
RoborockBasicControlCapability: require("./RoborockBasicControlCapability"),
RoborockCarpetModeControlCapability: require("./RoborockCarpetModeControlCapability"),
RoborockCarpetSensorModeControlCapability: require("./RoborockCarpetSensorModeControlCapability"),
RoborockCollisionAvoidantNavigationControlCapability: require("./RoborockCollisionAvoidantNavigationControlCapability"),
RoborockCombinedVirtualRestrictionsCapability: require("./RoborockCombinedVirtualRestrictionsCapability"),
RoborockConsumableMonitoringCapability: require("./RoborockConsumableMonitoringCapability"),

View File

@ -87,6 +87,7 @@ const CAPABILITY_TYPE_TO_ROUTER_MAPPING = {
[capabilities.MopDockCleanManualTriggerCapability.TYPE]: capabilityRouters.MopDockCleanManualTriggerCapabilityRouter,
[capabilities.MopDockDryManualTriggerCapability.TYPE]: capabilityRouters.MopDockDryManualTriggerCapabilityRouter,
[capabilities.CollisionAvoidantNavigationControlCapability.TYPE]: capabilityRouters.SimpleToggleCapabilityRouter,
[capabilities.CarpetSensorModeControlCapability.TYPE]: capabilityRouters.CarpetSensorModeControlCapabilityRouter,
};
module.exports = CapabilitiesRouter;

View File

@ -0,0 +1,31 @@
const CapabilityRouter = require("./CapabilityRouter");
class CarpetSensorModeControlCapabilityRouter extends CapabilityRouter {
initRoutes() {
this.router.get("/", async (req, res) => {
try {
res.json({
mode: await this.capability.getMode()
});
} catch (e) {
this.sendErrorResponse(req, res, e);
}
});
this.router.put("/", this.validator, async (req, res) => {
if (req.body.mode) {
try {
await this.capability.setMode(req.body.mode);
res.sendStatus(200);
} catch (e) {
this.sendErrorResponse(req, res, e);
}
} else {
res.sendStatus(400);
}
});
}
}
module.exports = CarpetSensorModeControlCapabilityRouter;

View File

@ -0,0 +1,98 @@
{
"/api/v2/robot/capabilities/CarpetSensorModeControlCapability": {
"get": {
"tags": [
"CarpetSensorModeControlCapability"
],
"summary": "Get current carpet sensor mode",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"off",
"avoid",
"lift"
]
}
}
}
}
}
}
}
},
"put": {
"tags": [
"CarpetSensorModeControlCapability"
],
"summary": "Set carpet sensor mode",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"off",
"avoid",
"lift"
]
}
}
}
}
}
},
"responses": {
"200": {
"$ref": "#/components/responses/200"
},
"400": {
"$ref": "#/components/responses/400"
}
}
}
},
"/api/v2/robot/capabilities/CarpetSensorModeControlCapability/properties": {
"get": {
"tags": [
"CarpetSensorModeControlCapability"
],
"summary": "Get various capability-related properties",
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"supportedModes": {
"type": "array",
"items": {
"type": "string",
"enum": [
"off",
"avoid",
"lift"
]
}
}
}
}
}
}
}
}
}
}
}

View File

@ -1,6 +1,7 @@
module.exports = {
AutoEmptyDockManualTriggerCapabilityRouter: require("./AutoEmptyDockManualTriggerCapabilityRouter"),
BasicControlCapabilityRouter: require("./BasicControlCapabilityRouter"),
CarpetSensorModeControlCapabilityRouter: require("./CarpetSensorModeControlCapabilityRouter"),
CombinedVirtualRestrictionsCapabilityRouter: require("./CombinedVirtualRestrictionsCapabilityRouter"),
ConsumableMonitoringCapabilityRouter: require("./ConsumableMonitoringCapabilityRouter"),
DoNotDisturbCapabilityRouter: require("./DoNotDisturbCapabilityRouter"),
@ -25,5 +26,5 @@ module.exports = {
VoicePackManagementCapabilityRouter: require("./VoicePackManagementCapabilityRouter"),
WifiConfigurationCapabilityRouter: require("./WifiConfigurationCapabilityRouter"),
WifiScanCapabilityRouter: require("./WifiScanCapabilityRouter"),
ZoneCleaningCapabilityRouter: require("./ZoneCleaningCapabilityRouter"),
ZoneCleaningCapabilityRouter: require("./ZoneCleaningCapabilityRouter")
};

View File

@ -3,6 +3,9 @@ import { RawMapData } from "./RawMapData";
import { PresetSelectionState, RobotAttribute } from "./RawRobotState";
import {
Capability,
CarpetSensorMode,
CarpetSensorModeControlProperties,
CarpetSensorModePayload,
CombinedVirtualRestrictionsProperties,
CombinedVirtualRestrictionsUpdateRequestParameters,
ConsumableId,
@ -881,7 +884,7 @@ export const sendManualControlInteraction = async (interaction: ManualControlInt
});
};
export const fetchCombinedVirtualRestrictionsPropertiesProperties = async (): Promise<CombinedVirtualRestrictionsProperties> => {
export const fetchCombinedVirtualRestrictionsProperties = async (): Promise<CombinedVirtualRestrictionsProperties> => {
return valetudoAPI
.get<CombinedVirtualRestrictionsProperties>(
`/robot/capabilities/${Capability.CombinedVirtualRestrictions}/properties`
@ -1036,3 +1039,32 @@ export const sendValetudoCustomizations = async (customizations: ValetudoCustomi
}
});
};
export const sendCarpetSensorMode = async (payload: CarpetSensorModePayload): Promise<void> => {
return valetudoAPI
.put(`/robot/capabilities/${Capability.CarpetSensorModeControl}`, payload)
.then(({status}) => {
if (status !== 200) {
throw new Error("Could not send carpet sensor mode");
}
});
};
export const fetchCarpetSensorMode = async (): Promise<CarpetSensorMode> => {
return valetudoAPI
.get<CarpetSensorModePayload>(`/robot/capabilities/${Capability.CarpetSensorModeControl}`)
.then(({data}) => {
return data.mode;
});
};
export const fetchCarpetSensorModeProperties = async (): Promise<CarpetSensorModeControlProperties> => {
return valetudoAPI
.get<CarpetSensorModeControlProperties>(
`/robot/capabilities/${Capability.CarpetSensorModeControl}/properties`
)
.then(({data}) => {
return data;
});
};

View File

@ -14,7 +14,7 @@ import {
fetchAutoEmptyDockAutoEmptyControlState,
fetchCapabilities,
fetchCarpetModeState,
fetchCombinedVirtualRestrictionsPropertiesProperties,
fetchCombinedVirtualRestrictionsProperties,
fetchConsumableStateInformation,
fetchCurrentStatistics,
fetchCurrentStatisticsProperties,
@ -112,6 +112,9 @@ import {
sendPetObstacleAvoidanceControlState,
fetchCollisionAvoidantNavigationControlState,
sendCollisionAvoidantNavigationControlState,
fetchCarpetSensorModeProperties,
fetchCarpetSensorMode,
sendCarpetSensorMode,
} from "./client";
import {
PresetSelectionState,
@ -122,6 +125,7 @@ import {
import { isAttribute } from "./utils";
import {
Capability,
CarpetSensorMode,
CombinedVirtualRestrictionsUpdateRequestParameters,
ConsumableId,
DoNotDisturbConfiguration,
@ -200,7 +204,9 @@ enum CacheKey {
Quirks = "quirks",
RobotProperties = "robot_properties",
ValetudoCustomizations = "valetudo_customizations",
CollisionAvoidantNavigation = "collision_avoidant_navigation"
CollisionAvoidantNavigation = "collision_avoidant_navigation",
CarpetSensorMode = "carpet_sensor_mode",
CarpetSensorModeProperties = "carpet_sensor_mode_properties",
}
const useOnCommandError = (capability: Capability | string): ((error: unknown) => void) => {
@ -1110,7 +1116,7 @@ export const useManualControlInteraction = () => {
};
export const useCombinedVirtualRestrictionsPropertiesQuery = () => {
return useQuery(CacheKey.CombinedVirtualRestrictionsProperties, fetchCombinedVirtualRestrictionsPropertiesProperties, {
return useQuery(CacheKey.CombinedVirtualRestrictionsProperties, fetchCombinedVirtualRestrictionsProperties, {
staleTime: Infinity
});
};
@ -1282,3 +1288,23 @@ export const useValetudoCustomizationsMutation = () => {
}
);
};
export const useCarpetSensorModeQuery = () => {
return useQuery(CacheKey.CarpetSensorMode, fetchCarpetSensorMode);
};
export const useCarpetSensorModeMutation = () => {
return useValetudoFetchingMutation(
useOnCommandError(Capability.CarpetSensorModeControl),
CacheKey.CarpetSensorMode,
(mode: CarpetSensorMode) => {
return sendCarpetSensorMode({mode: mode}).then(fetchCarpetSensorMode);
}
);
};
export const useCarpetSensorModePropertiesQuery = () => {
return useQuery(CacheKey.CarpetSensorModeProperties, fetchCarpetSensorModeProperties, {
staleTime: Infinity
});
};

View File

@ -5,6 +5,7 @@ export enum Capability {
AutoEmptyDockManualTrigger = "AutoEmptyDockManualTriggerCapability",
BasicControl = "BasicControlCapability",
CarpetModeControl = "CarpetModeControlCapability",
CarpetSensorModeControl = "CarpetSensorModeControlCapability",
CollisionAvoidantNavigation = "CollisionAvoidantNavigationControlCapability",
CombinedVirtualRestrictions = "CombinedVirtualRestrictionsCapability",
ConsumableMonitoring = "ConsumableMonitoringCapability",
@ -538,3 +539,12 @@ export interface RobotProperties {
export interface ValetudoCustomizations {
friendlyName: string;
}
export type CarpetSensorMode = "off" | "avoid" | "lift";
export interface CarpetSensorModePayload {
mode: CarpetSensorMode
}
export interface CarpetSensorModeControlProperties {
supportedModes: Array<CarpetSensorMode>
}

View File

@ -1,5 +1,6 @@
import React from "react";
import {Avatar, ListItem, ListItemAvatar, ListItemText, MenuItem, Select, Typography} from "@mui/material";
import LoadingFade from "../LoadingFade";
export type SelectListMenuItemOption = {
value: string,
@ -11,6 +12,7 @@ export const SelectListMenuItem: React.FunctionComponent<{
currentValue: SelectListMenuItemOption,
setValue: (newValue: SelectListMenuItemOption) => void,
disabled: boolean,
loadingOptions: boolean,
loadError: boolean,
primaryLabel: string,
secondaryLabel: string,
@ -20,6 +22,7 @@ export const SelectListMenuItem: React.FunctionComponent<{
currentValue,
setValue,
disabled,
loadingOptions,
loadError,
primaryLabel,
secondaryLabel,
@ -27,7 +30,9 @@ export const SelectListMenuItem: React.FunctionComponent<{
}): JSX.Element => {
let select;
if (loadError) {
if (loadingOptions) {
select = <LoadingFade/>;
} else if (loadError) {
select = <Typography variant="body2" color="error">Error</Typography>;
} else {
select = (

View File

@ -122,6 +122,7 @@ const UpdateProviderSelectListMenuItem = (): JSX.Element => {
} as UpdaterConfiguration);
}}
disabled={disabled}
loadingOptions={false}
loadError={configurationError}
primaryLabel="Update Channel"
secondaryLabel="Select the channel used by the inbuilt updater"

View File

@ -1,10 +1,14 @@
import {useCapabilitiesSupported} from "../CapabilitiesProvider";
import {
Capability,
CarpetSensorMode,
useAutoEmptyDockAutoEmptyControlMutation,
useAutoEmptyDockAutoEmptyControlQuery,
useCarpetModeStateMutation,
useCarpetModeStateQuery,
useCarpetSensorModeMutation,
useCarpetSensorModePropertiesQuery,
useCarpetSensorModeQuery,
useCollisionAvoidantNavigationControlMutation,
useCollisionAvoidantNavigationControlQuery,
useKeyLockStateMutation,
@ -27,12 +31,14 @@ import {
Pets as PetObstacleAvoidanceControlIcon,
RoundaboutRight as CollisionAvoidantNavigationControlIcon,
Sensors as CarpetModeIcon,
Star as QuirksIcon
Waves as CarpetSensorModeIcon,
Star as QuirksIcon,
} from "@mui/icons-material";
import {SpacerListMenuItem} from "../components/list_menu/SpacerListMenuItem";
import {LinkListMenuItem} from "../components/list_menu/LinkListMenuItem";
import PaperContainer from "../components/PaperContainer";
import {ButtonListMenuItem} from "../components/list_menu/ButtonListMenuItem";
import {SelectListMenuItem, SelectListMenuItemOption} from "../components/list_menu/SelectListMenuItem";
const LocateButtonListMenuItem = (): JSX.Element => {
const {
@ -106,6 +112,87 @@ const CarpetModeControlCapabilitySwitchListMenuItem = () => {
);
};
const CarpetSensorModeControlCapabilitySelectListMenuItem = () => {
const SORT_ORDER = {
"off": 3,
"avoid": 2,
"lift": 1
};
const {
data: carpetSensorModeProperties,
isLoading: carpetSensorModePropertiesLoading,
isError: carpetSensorModePropertiesError
} = useCarpetSensorModePropertiesQuery();
const options: Array<SelectListMenuItemOption> = (
carpetSensorModeProperties?.supportedModes ?? []
).sort((a, b) => {
const aMapped = SORT_ORDER[a] ?? 10;
const bMapped = SORT_ORDER[b] ?? 10;
if (aMapped < bMapped) {
return -1;
} else if (bMapped < aMapped) {
return 1;
} else {
return 0;
}
}).map((val: CarpetSensorMode) => {
let label;
switch (val) {
case "off":
label = "None";
break;
case "avoid":
label = "Avoid Carpet";
break;
case "lift":
label = "Lift Mop";
break;
}
return {
value: val,
label: label
};
});
const {
data: data,
isLoading: isLoading,
isFetching: isFetching,
isError: isError,
} = useCarpetSensorModeQuery();
const {mutate: mutate, isLoading: isChanging} = useCarpetSensorModeMutation();
const loading = isFetching || isChanging;
const disabled = loading || isChanging || isError;
const currentValue = options.find(mode => {
return mode.value === data;
}) ?? {value: "", label: ""};
return (
<SelectListMenuItem
options={options}
currentValue={currentValue}
setValue={(e) => {
mutate(e.value as CarpetSensorMode);
}}
disabled={disabled}
loadingOptions={carpetSensorModePropertiesLoading || isLoading}
loadError={carpetSensorModePropertiesError}
primaryLabel="Carpet Sensor Mode"
secondaryLabel="Select what action the robot should take if it detects carpet while mopping."
icon={<CarpetSensorModeIcon/>}
/>
);
};
const AutoEmptyDockAutoEmptyControlCapabilitySwitchListMenuItem = () => {
const {
data: data,
@ -177,7 +264,7 @@ const PetObstacleAvoidanceControlCapabilitySwitchListMenuItem = () => {
}}
disabled={disabled}
loadError={isError}
primaryLabel={"Pet obstacle Avoidance"}
primaryLabel={"Pet Obstacle Avoidance"}
secondaryLabel={"Avoid obstacles left by pets. Will increase the false positive rate for general obstacle avoidance."}
icon={<PetObstacleAvoidanceControlIcon/>}
/>
@ -219,6 +306,7 @@ const RobotOptions = (): JSX.Element => {
petObstacleAvoidanceControlCapabilitySupported,
collisionAvoidantNavigationControlCapabilitySupported,
carpetModeControlCapabilitySupported,
carpetSensorModeControlCapabilitySupported,
autoEmptyDockAutoEmptyControlCapabilitySupported,
@ -237,6 +325,7 @@ const RobotOptions = (): JSX.Element => {
Capability.PetObstacleAvoidanceControl,
Capability.CollisionAvoidantNavigation,
Capability.CarpetModeControl,
Capability.CarpetSensorModeControl,
Capability.AutoEmptyDockAutoEmptyControl,
@ -289,13 +378,19 @@ const RobotOptions = (): JSX.Element => {
<CarpetModeControlCapabilitySwitchListMenuItem key={"carpetModeControl"}/>
);
}
if (carpetSensorModeControlCapabilitySupported) {
items.push(
<CarpetSensorModeControlCapabilitySelectListMenuItem key={"carpetSensorModeControl"}/>
);
}
return items;
}, [
obstacleAvoidanceControlCapabilitySupported,
petObstacleAvoidanceControlCapabilitySupported,
collisionAvoidantNavigationControlCapabilitySupported,
carpetModeControlCapabilitySupported
carpetModeControlCapabilitySupported,
carpetSensorModeControlCapabilitySupported
]);
const dockListItems = React.useMemo(() => {

View File

@ -55,8 +55,16 @@ const options = {
{name: "PendingMapChangeHandlingCapability", description: "Pending map change handling capability"},
{name: "MappingPassCapability", description: "Mapping pass capability"},
{name: "KeyLockCapability", description: "Key lock capability"},
{name: "AutoEmptyDockManualTriggerCapability", description: "Auto empty dock manual trigger capability"},
{name: "MopDockCleanManualTriggerCapability", description: "Mop Dock clean manual trigger capability"},
{name: "MopDockDryManualTriggerCapability", description: "Mop Dock dry manual trigger capability"},
{name: "OperationModeControlCapability", description: "Operation mode control capability"},
{name: "ObstacleAvoidanceControlCapability", description: "Obstacle avoidance control capability"},
{name: "PetObstacleAvoidanceControlCapability", description: "Pet obstacle avoidance control capability"},
{name: "CarpetSensorModeControlCapability", description: "Carpet sensor mode control capability"},
{name: "CollisionAvoidantNavigationControlCapability", description: "Collision avoidant navigation control capability"},
{name: "TotalStatisticsCapability", description: "Total statistics capability"},
{name: "CurrentStatisticsCapability", description: "Current statistics capability"},
],
components: {
responses: {