Skip to content

Commit

Permalink
feat(vendor.dreame): Manual Control
Browse files Browse the repository at this point in the history
  • Loading branch information
Hypfer committed Aug 8, 2021
1 parent afded40 commit 4cfddd4
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 0 deletions.
13 changes: 13 additions & 0 deletions backend/lib/robots/dreame/DreameGen2ValetudoRobot.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ const MIOT_SERVICES = Object.freeze({
CARPET_MODE: {
PIID: 12
},
MANUAL_CONTROL: {
PIID: 15
},
ERROR_CODE: {
PIID: 18
},
Expand Down Expand Up @@ -529,6 +532,16 @@ class DreameGen2ValetudoRobot extends DreameValetudoRobot {
mappingModeId: 21
}));

this.registerCapability(new capabilities.DreameManualControlCapability({
robot: this,
miot_properties: {
manual_control: {
siid: MIOT_SERVICES.VACUUM_2.SIID,
piid: MIOT_SERVICES.VACUUM_2.PROPERTIES.MANUAL_CONTROL.PIID
}
}
}));

this.state.upsertFirstMatchingAttribute(new entities.state.attributes.AttachmentStateAttribute({
type: entities.state.attributes.AttachmentStateAttribute.TYPE.WATERTANK,
attached: false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const AttributeSubscriber = require("../../../entities/AttributeSubscriber");
const CallbackAttributeSubscriber = require("../../../entities/CallbackAttributeSubscriber");
const ManualControlCapability = require("../../../core/capabilities/ManualControlCapability");
const {StatusStateAttribute} = require("../../../entities/state/attributes");

/**
* @extends ManualControlCapability<import("../DreameGen2ValetudoRobot")>
*/
class DreameManualControlCapability extends ManualControlCapability {
/**
*
* @param {object} options
* @param {object} options.miot_properties
* @param {object} options.miot_properties.manual_control
* @param {number} options.miot_properties.manual_control.siid
* @param {number} options.miot_properties.manual_control.piid
*
* @param {import("../DreameGen2ValetudoRobot")} options.robot
* @class
*/
constructor(options) {
super(Object.assign({}, options, {
supportedMovementCommands: [
ManualControlCapability.MOVEMENT_COMMAND_TYPE.FORWARD,
ManualControlCapability.MOVEMENT_COMMAND_TYPE.BACKWARD,
ManualControlCapability.MOVEMENT_COMMAND_TYPE.ROTATE_CLOCKWISE,
ManualControlCapability.MOVEMENT_COMMAND_TYPE.ROTATE_COUNTERCLOCKWISE
]
}));

this.miot_properties = options.miot_properties;

this.keepAliveTimeout = undefined;

this.robot.state.subscribe(
new CallbackAttributeSubscriber((eventType, status, prevStatus) => {
if (
eventType === AttributeSubscriber.EVENT_TYPE.CHANGE &&
//@ts-ignore
status.value !== StatusStateAttribute.VALUE.MANUAL_CONTROL &&
prevStatus &&
//@ts-ignore
prevStatus.value === StatusStateAttribute.VALUE.MANUAL_CONTROL
) {
this.disableManualControl().then(() => {}).catch(() => {});
}
}),
{attributeClass: StatusStateAttribute.name}
);

this.lastCommand = new Date(0).getTime();
this.active = false;
}

/**
* @returns {Promise<void>}
*/
async enableManualControl() {
if (this.active === false) {
await this.sendRemoteControlCommand(0, 0, true);
this.active = true;
await this.sendAndScheduleKeepAlive();
}
}

/**
* @returns {Promise<void>}
*/
async disableManualControl() {
clearTimeout(this.keepAliveTimeout);
this.active = false;
}

async sendAndScheduleKeepAlive() {
clearTimeout(this.keepAliveTimeout);

if (new Date().getTime() - this.lastCommand >= 700) {
await this.sendRemoteControlCommand(0,0, false);
}

this.keepAliveTimeout = setTimeout(async () => {
await this.sendAndScheduleKeepAlive();
}, 700);
}

/**
* @param {import("../../../core/capabilities/ManualControlCapability").ValetudoManualControlMovementCommandType} movementCommand
* @returns {Promise<void>}
*/
async manualControl(movementCommand) {
switch (movementCommand) {
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.FORWARD:
return this.sendRemoteControlCommand(300, 0, false);
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.BACKWARD:
return this.sendRemoteControlCommand(-300, 0, false);
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.ROTATE_CLOCKWISE:
return this.sendRemoteControlCommand(0, -80, false);
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.ROTATE_COUNTERCLOCKWISE:
return this.sendRemoteControlCommand(0, 80, false);
default:
throw new Error("Invalid movementCommand.");
}
}

/**
* @private
* @param {number} velocity
* @param {number} angle
* @param {boolean} audioHint
* @returns {Promise<void>}
*/
async sendRemoteControlCommand(velocity, angle, audioHint) {
const res = await this.robot.sendCommand("set_properties", [
{
did: this.robot.deviceId,
siid: this.miot_properties.manual_control.siid,
piid: this.miot_properties.manual_control.piid,
value: JSON.stringify({
spdv: velocity,
spdw: angle,
audio: audioHint === true ? "true" : "false",
random: Math.floor(Math.random() * 1000)
})
}
]);
this.lastCommand = new Date().getTime();

if (res?.length === 1) {
if (res[0].code !== 0) {
throw new Error("Error code " + res[0].code);
}
} else {
throw new Error("Received invalid response");
}
}
}

module.exports = DreameManualControlCapability;
1 change: 1 addition & 0 deletions backend/lib/robots/dreame/capabilities/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
DreameConsumableMonitoringCapability: require("./DreameConsumableMonitoringCapability"),
DreameFanSpeedControlCapability: require("./DreameFanSpeedControlCapability"),
DreameLocateCapability: require("./DreameLocateCapability"),
DreameManualControlCapability: require("./DreameManualControlCapability"),
DreameMapResetCapability: require("./DreameMapResetCapability"),
DreameMapSegmentEditCapability: require("./DreameMapSegmentEditCapability"),
DreameMapSegmentRenameCapability: require("./DreameMapSegmentRenameCapability"),
Expand Down

0 comments on commit 4cfddd4

Please sign in to comment.