Valetudo/backend/lib/robots/dreame/capabilities/DreameManualControlCapabili...

143 lines
5.0 KiB
JavaScript

const AttributeSubscriber = require("../../../entities/AttributeSubscriber");
const CallbackAttributeSubscriber = require("../../../entities/CallbackAttributeSubscriber");
const DreameMiotHelper = require("../DreameMiotHelper");
const DreameMiotServices = require("../DreameMiotServices");
const ManualControlCapability = require("../../../core/capabilities/ManualControlCapability");
const StatusStateAttribute = require("../../../entities/state/attributes/StatusStateAttribute");
/**
* @extends ManualControlCapability<import("../DreameGen2ValetudoRobot")>
*/
class DreameManualControlCapability extends ManualControlCapability {
/**
*
* @param {object} options
* @param {import("../DreameGen2ValetudoRobot")} options.robot
*/
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 = {
manual_control: {
siid: DreameMiotServices["GEN2"].VACUUM_2.SIID,
piid: DreameMiotServices["GEN2"].VACUUM_2.PROPERTIES.MANUAL_CONTROL.PIID
}
};
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(() => {
/* intentional as there is nowhere to send feedback to */
}).catch(() => {
/* intentional as there is nowhere to send feedback to */
});
}
}),
{attributeClass: StatusStateAttribute.name}
);
this.lastCommand = new Date(0).getTime();
this.active = false;
this.helper = new DreameMiotHelper({robot: this.robot});
}
/**
* @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;
}
/**
* @returns {Promise<boolean>}
*/
async manualControlActive() {
return this.active;
}
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(250, 0, false);
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.BACKWARD:
return this.sendRemoteControlCommand(-250, 0, false);
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.ROTATE_CLOCKWISE:
return this.sendRemoteControlCommand(0, -45, false);
case ManualControlCapability.MOVEMENT_COMMAND_TYPE.ROTATE_COUNTERCLOCKWISE:
return this.sendRemoteControlCommand(0, 45, 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) {
await this.helper.writeProperty(
this.miot_properties.manual_control.siid,
this.miot_properties.manual_control.piid,
JSON.stringify({
spdv: velocity,
spdw: angle,
audio: audioHint === true ? "true" : "false",
random: Math.floor(Math.random() * 1000)
})
);
this.lastCommand = new Date().getTime();
}
}
module.exports = DreameManualControlCapability;