Skip to content

Commit

Permalink
feat(vendor.viomi): Introduce ViomiOperationModeControlCapability
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Nov 17, 2022
1 parent 1f06b86 commit d764438
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 132 deletions.
10 changes: 5 additions & 5 deletions backend/lib/robots/viomi/ViomiCommonAttributes.js
Expand Up @@ -6,9 +6,9 @@ const ValetudoSensor = require("../../entities/core/ValetudoSensor");

/** @enum {number} */
const ViomiOperationMode = Object.freeze({
VACUUM: 0,
MIXED: 1,
MOP: 2,
[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM]: 0,
[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM_AND_MOP]: 1,
[stateAttrs.PresetSelectionStateAttribute.MODE.MOP]: 2,
});

/** @enum {number} */
Expand All @@ -35,10 +35,10 @@ const ViomiArea = Object.freeze({

/** @enum {number} */
const ViomiMovementMode = Object.freeze({
NORMAL_CLEANING: 0, // goes in straight lines with vacuum motor on
VACUUM: 0, // goes in straight lines with vacuum motor on
VACUUM_AND_MOP: 1, // back and forth mopping movement with vacuum motor on
OUTLINE: 2, // only clean the rooms outline
MOP_NO_VACUUM: 3, // same as VACUUM_AND_MOP, but the vacuum motor is turned off
MOP: 3, // same as VACUUM_AND_MOP, but the vacuum motor is turned off
});

const ViomiZoneCleaningCommand = Object.freeze({
Expand Down
29 changes: 16 additions & 13 deletions backend/lib/robots/viomi/ViomiValetudoRobot.js
Expand Up @@ -46,14 +46,20 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
carpetModeEnabled: undefined,
lastOperationType: null,
lastOperationAdditionalParams: [],
operationMode: undefined,
vendorMapId: 0
};

this.registerCapability(new capabilities.ViomiBasicControlCapability({
robot: this
}));

this.registerCapability(new capabilities.ViomiOperationModeControlCapability({
robot: this,
presets: Object.keys(attributes.ViomiOperationMode).map(k => {
return new ValetudoSelectionPreset({name: k, value: attributes.ViomiOperationMode[k]});
}),
}));

this.registerCapability(new capabilities.ViomiFanSpeedControlCapability({
robot: this,
presets: Object.keys(this.fanSpeeds).map(k => {
Expand Down Expand Up @@ -288,7 +294,7 @@ class ViomiValetudoRobot extends MiioValetudoRobot {
// If status is an error, mark it as such
statusValue = stateAttrs.StatusStateAttribute.VALUE.ERROR;
} else if (status === undefined) {
// If it is not an error but we don't have any status data, use the status code from the error
// If it is not an error, but we don't have any status data, use the status code from the error
statusValue = error.value;
}
}
Expand Down Expand Up @@ -427,17 +433,14 @@ class ViomiValetudoRobot extends MiioValetudoRobot {

// Viomi naming is abysmal
if (data["is_mop"] !== undefined) {
switch (data["is_mop"]) {
case attributes.ViomiOperationMode.VACUUM:
this.ephemeralState.operationMode = stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM;
break;
case attributes.ViomiOperationMode.MIXED:
this.ephemeralState.operationMode = stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM_AND_MOP;
break;
case attributes.ViomiOperationMode.MOP:
this.ephemeralState.operationMode = stateAttrs.PresetSelectionStateAttribute.MODE.MOP;
break;
}
let matchingOperationMode = Object.keys(attributes.ViomiOperationMode).find(key => {
return attributes.ViomiOperationMode[key] === data["is_mop"];
});

this.state.upsertFirstMatchingAttribute(new stateAttrs.PresetSelectionStateAttribute({
type: stateAttrs.PresetSelectionStateAttribute.TYPE.OPERATION_MODE,
value: matchingOperationMode
}));
}

if (data["mop_type"] !== undefined) {
Expand Down
113 changes: 18 additions & 95 deletions backend/lib/robots/viomi/capabilities/ViomiBasicControlCapability.js
Expand Up @@ -10,80 +10,36 @@ const stateAttrs = require("../../../entities/state/attributes");
* The vacuum expects the control software to remember the parameters of the last cleaning command, and re-submit them
* when pausing or resuming.
*
* For this reasons, no other capability should run the following commands on their own:
* For this reason, no other capability should run the following commands on their own:
* - set_mode - use setRectangularZoneMode() or stop() instead
* - set_mode_withroom - use setModeWithSegments() instead
* - set_pointclean - use setModeCleanSpot() instead
*
* They should instead prepare their data, retrieve this capability and use it to perform the operation.
* This capability will take care of remembering the parameters for subsequent pause/resume commands.
*
* @extends BasicControlCapability<import("../ViomiValetudoRobot")>
*/
class ViomiBasicControlCapability extends BasicControlCapability {

/**
* Automatically sets mop mode depending on what tools are currently installed
*
* @public
*/
getVacuumOperationModeFromInstalledAccessories() {
const dustbinAttribute = this.robot.state.getFirstMatchingAttribute({
attributeClass: stateAttrs.AttachmentStateAttribute.name,
attributeType: stateAttrs.AttachmentStateAttribute.TYPE.DUSTBIN
});
const waterboxAttribute = this.robot.state.getFirstMatchingAttribute({
attributeClass: stateAttrs.AttachmentStateAttribute.name,
attributeType: stateAttrs.AttachmentStateAttribute.TYPE.WATERTANK
});
const mopAttribute = this.robot.state.getFirstMatchingAttribute({
attributeClass: stateAttrs.AttachmentStateAttribute.name,
attributeType: stateAttrs.AttachmentStateAttribute.TYPE.MOP
});

if (mopAttribute?.attached) {
if (waterboxAttribute?.attached && dustbinAttribute?.attached) {
return attributes.ViomiOperationMode.MIXED;
}
return attributes.ViomiOperationMode.MOP;
}
return attributes.ViomiOperationMode.VACUUM;
}

/**
* Automatically set movement mode based on the previously computed operation mode
*
* @private
* @param {any} operationMode
* @param {boolean} [outline] Vacuum along the edges
*/
getVacuumMovementMode(operationMode, outline) {
if (outline) {
return attributes.ViomiMovementMode.OUTLINE;
}
getVacuumMovementMode(outline) {
const OperationModeAttribute = this.robot.state.getFirstMatchingAttribute({
attributeClass: stateAttrs.PresetSelectionStateAttribute.name,
attributeType: stateAttrs.PresetSelectionStateAttribute.TYPE.OPERATION_MODE
});

switch (operationMode) {
case attributes.ViomiOperationMode.MIXED:
switch (OperationModeAttribute?.value) {
case attributes.ViomiOperationMode[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM_AND_MOP]:
return attributes.ViomiMovementMode.VACUUM_AND_MOP;
case attributes.ViomiOperationMode.MOP:
return attributes.ViomiMovementMode.MOP_NO_VACUUM;
case attributes.ViomiOperationMode.VACUUM:
return attributes.ViomiMovementMode.NORMAL_CLEANING;
}
}

/**
* Adjust mop operation mode if it doesn't match the one previously set,
* The value should be retrieved using getVacuumOperationModeFromInstalledAccessories()
*
* @public
* @param {any} operationMode
* @returns {Promise<void>}
*/
async ensureCleaningOperationMode(operationMode) {
const curOperationMode = this.robot.ephemeralState.operationMode;
if (!curOperationMode || curOperationMode !== operationMode) {
await this.robot.sendCommand("set_mop", [operationMode]);
case attributes.ViomiOperationMode[stateAttrs.PresetSelectionStateAttribute.MODE.MOP]:
return attributes.ViomiMovementMode.MOP;
case attributes.ViomiOperationMode[stateAttrs.PresetSelectionStateAttribute.MODE.VACUUM]:
return attributes.ViomiMovementMode.VACUUM;
default:
return attributes.ViomiMovementMode.VACUUM;
}
}

Expand All @@ -96,11 +52,7 @@ class ViomiBasicControlCapability extends BasicControlCapability {
* @returns {Promise<void>}
*/
async setModeWithSegments(operation, segmentIds) {
const operationMode = this.getVacuumOperationModeFromInstalledAccessories();
if (operation === attributes.ViomiOperation.START) {
await this.ensureCleaningOperationMode(operationMode);
}
const movementMode = this.getVacuumMovementMode(operationMode, false);
const movementMode = this.getVacuumMovementMode();

if (segmentIds === undefined || segmentIds === null) {
segmentIds = [];
Expand All @@ -118,29 +70,6 @@ class ViomiBasicControlCapability extends BasicControlCapability {
}
}

/**
* Start or resume cleaning around the specified location.
*
* @public
* @param {any} operation Whether to start, stop or pause
* @param {number} x
* @param {number} y
* @returns {Promise<void>}
*/
async setModeCleanSpot(operation, x, y) {
if (operation === attributes.ViomiOperation.START) {
const operationMode = this.getVacuumOperationModeFromInstalledAccessories();
await this.ensureCleaningOperationMode(operationMode);
}

await this.robot.sendCommand("set_pointclean", [operation, x, y]);

if (operation !== attributes.ViomiOperation.STOP) {
this.robot.ephemeralState.lastOperationType = stateAttrs.StatusStateAttribute.FLAG.SPOT;
this.robot.ephemeralState.lastOperationAdditionalParams = [x, y];
}
}

/**
* Start, pause or resume vacuum after sending "set_zone". This can be used by ZoneCleaningCapability to start
* cleaning after sending the rectangular area, then later for pausing and resuming.
Expand Down Expand Up @@ -180,9 +109,6 @@ class ViomiBasicControlCapability extends BasicControlCapability {
case stateAttrs.StatusStateAttribute.FLAG.ZONE:
await this.setRectangularZoneMode(attributes.ViomiOperation.START);
break;
case stateAttrs.StatusStateAttribute.FLAG.SPOT:
await this.setModeCleanSpot(attributes.ViomiOperation.START, lastOperationAdditionalParams[0], lastOperationAdditionalParams[1]);
break;
default:
await this.setModeWithSegments(attributes.ViomiOperation.START);
break;
Expand All @@ -198,17 +124,16 @@ class ViomiBasicControlCapability extends BasicControlCapability {
const statusAttribute = this.robot.state.getFirstMatchingAttributeByConstructor(
stateAttrs.StatusStateAttribute
);
const lastOperation = this.robot.ephemeralState.lastOperationType;

if (statusAttribute && statusAttribute.value === stateAttrs.StatusStateAttribute.VALUE.RETURNING) {
// With the "stop returning" command as opposed to the common "stop" the voice provides the correct feedback
await this.robot.sendCommand("set_charge", [0]);
} else if (lastOperation === stateAttrs.StatusStateAttribute.FLAG.SPOT) {
await this.setModeCleanSpot(attributes.ViomiOperation.STOP, 0, 0);
} else if (statusAttribute && statusAttribute.value === stateAttrs.StatusStateAttribute.VALUE.CLEANING) {
await this.setRectangularZoneMode(attributes.ViomiOperation.STOP);
} else {
await this.robot.sendCommand("set_mode", [attributes.ViomiOperation.STOP]);
}

this.robot.ephemeralState.lastOperationType = null;
this.robot.ephemeralState.lastOperationAdditionalParams = [];
}
Expand Down Expand Up @@ -240,9 +165,7 @@ class ViomiBasicControlCapability extends BasicControlCapability {
return;
}

if (lastOperation === stateAttrs.StatusStateAttribute.FLAG.SPOT) {
await this.setModeCleanSpot(attributes.ViomiOperation.PAUSE, lastOperationAdditionalParams[0], lastOperationAdditionalParams[1]);
} else if (lastOperation === stateAttrs.StatusStateAttribute.FLAG.ZONE) {
if (lastOperation === stateAttrs.StatusStateAttribute.FLAG.ZONE) {
await this.setRectangularZoneMode(attributes.ViomiOperation.PAUSE_RECTANGULAR_ZONE);
} else {
await this.setModeWithSegments(attributes.ViomiOperation.PAUSE, lastOperationAdditionalParams);
Expand Down
@@ -0,0 +1,25 @@
const OperationModeControlCapability = require("../../../core/capabilities/OperationModeControlCapability");

/**
* @extends OperationModeControlCapability<import("../ViomiValetudoRobot")>
*/
class ViomiOperationModeControlCapability extends OperationModeControlCapability {
/**
* @param {string} preset
* @returns {Promise<void>}
*/
async selectPreset(preset) {
const matchedPreset = this.presets.find(p => {
return p.name === preset;
});

if (matchedPreset) {
await this.robot.sendCommand("set_mop", [matchedPreset.value]);
} else {
throw new Error("Invalid Preset");
}
}

}

module.exports = ViomiOperationModeControlCapability;
@@ -1,6 +1,5 @@
const attributes = require("../ViomiCommonAttributes");
const BasicControlCapability = require("../../../core/capabilities/BasicControlCapability");
const Logger = require("../../../Logger");
const ThreeIRobotixMapParser = require("../../3irobotix/ThreeIRobotixMapParser");
const ZoneCleaningCapability = require("../../../core/capabilities/ZoneCleaningCapability");

Expand All @@ -24,8 +23,6 @@ class ViomiZoneCleaningCapability extends ZoneCleaningCapability {
let areas = [];
const basicControlCap = this.getBasicControlCapability();

const operationMode = basicControlCap.getVacuumOperationModeFromInstalledAccessories();
await basicControlCap.ensureCleaningOperationMode(operationMode);

// The app sends set_uploadmap [1] when the "draw area" button is pressed.
// The robot seems to end up in a weird state if we don't do this.
Expand All @@ -35,22 +32,20 @@ class ViomiZoneCleaningCapability extends ZoneCleaningCapability {
const pA = ThreeIRobotixMapParser.CONVERT_TO_THREEIROBOTIX_COORDINATES(zone.points.pA.x, zone.points.pA.y);
const pC = ThreeIRobotixMapParser.CONVERT_TO_THREEIROBOTIX_COORDINATES(zone.points.pC.x, zone.points.pC.y);

for (let j = 0; j < zone.iterations; j++) {
areas.push([areas.length,
attributes.ViomiArea.NORMAL,
pA.x.toFixed(4),
pA.y.toFixed(4),
pA.x.toFixed(4),
pC.y.toFixed(4),
pC.x.toFixed(4),
pC.y.toFixed(4),
pC.x.toFixed(4),
pA.y.toFixed(4),
].join("_"));
}
areas.push([areas.length,
attributes.ViomiArea.NORMAL,
pA.x.toFixed(4),
pA.y.toFixed(4),
pA.x.toFixed(4),
pC.y.toFixed(4),
pC.x.toFixed(4),
pC.y.toFixed(4),
pC.x.toFixed(4),
pA.y.toFixed(4),
].join("_"));
});

Logger.trace("areas to clean: ", areas);

await this.robot.sendCommand("set_zone", [areas.length].concat(areas), {});
await basicControlCap.setRectangularZoneMode(attributes.ViomiOperation.START);
}
Expand All @@ -66,7 +61,7 @@ class ViomiZoneCleaningCapability extends ZoneCleaningCapability {
},
iterationCount: {
min: 1,
max: 10 //completely arbitrary. Is this correct?
max: 1
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion backend/lib/robots/viomi/capabilities/index.js
Expand Up @@ -12,10 +12,11 @@ module.exports = {
ViomiMapSegmentEditCapability: require("./ViomiMapSegmentEditCapability"),
ViomiMapSegmentRenameCapability: require("./ViomiMapSegmentRenameCapability"),
ViomiMapSegmentationCapability: require("./ViomiMapSegmentationCapability"),
ViomiOperationModeControlCapability: require("./ViomiOperationModeControlCapability"),
ViomiPersistentMapControlCapability: require("./ViomiPersistentMapControlCapability"),
ViomiSpeakerTestCapability: require("./ViomiSpeakerTestCapability"),
ViomiSpeakerVolumeControlCapability: require("./ViomiSpeakerVolumeControlCapability"),
ViomiVoicePackManagementCapability: require("./ViomiVoicePackManagementCapability"),
ViomiWaterUsageControlCapability: require("./ViomiWaterUsageControlCapability"),
ViomiZoneCleaningCapability: require("./ViomiZoneCleaningCapability")
ViomiZoneCleaningCapability: require("./ViomiZoneCleaningCapability"),
};

0 comments on commit d764438

Please sign in to comment.