Skip to content

Commit

Permalink
add new mqtt command and REST API for manual movement control
Browse files Browse the repository at this point in the history
  • Loading branch information
rand256 committed Aug 8, 2022
1 parent a2c5eab commit e2ed176
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 3 deletions.
29 changes: 26 additions & 3 deletions lib/MqttClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ const CUSTOM_COMMANDS = {
STORE_MAP: "store_map",
GET_DESTINATIONS: "get_destinations",
PLAY_SOUND: "play_sound",
SET_WATER_GRADE: "set_water_grade"
SET_WATER_GRADE: "set_water_grade",
SEND_RC_COMMAND: "remote_control"
};

//TODO: since this is also displayed in the UI it should be moved somewhere else
Expand Down Expand Up @@ -821,16 +822,38 @@ MqttClient.prototype.handleCustomCommand = function (message) {
case CUSTOM_COMMANDS.SET_WATER_GRADE:
if (this.vacuum.features.water_usage_ctrl) {
if (Object.keys(WATER_GRADES).includes(msg.grade)) {
this.vacuum.setWaterGrade(WATER_GRADES[msg.grade], (err, data) => this.publishCommandStatus("set_water_grade",data,err));
this.vacuum.setWaterGrade(WATER_GRADES[msg.grade], (err, data) => this.publishCommandStatus(msg.command,data,err));
} else if (parseInt(msg.grade)) {
this.vacuum.setWaterGrade(parseInt(msg.grade), (err, data) => this.publishCommandStatus("set_water_grade",data,err));
this.vacuum.setWaterGrade(parseInt(msg.grade), (err, data) => this.publishCommandStatus(msg.command,data,err));
} else {
this.publishCommandStatus(msg.command,null,"Unsupported water grade value specified.");
}
} else {
this.publishCommandStatus(msg.command,null,"Setting water grade is not supported on this device.");
}
break;
/**
* send a RC mode command to the device
* starting and stopping RC mode is done automatically, you can try sending multiple commands in a row
* {
* "command": "remote_control",
* "angle": -1.3, (between -3.14 and 3.14)
* "velocity": 0.0, (between -0.3 and 0.3)
* "duration": 1000 (in ms)
* "startdelay": 7500 (optional, in ms)
* }
* startdelay is the delay the device needs to wait after starting RC mode to be able to actually perform RC commands (default: 8 seconds, maybe your device would be faster)
* see https://github.com/marcelrv/XiaomiRobotVacuumProtocol/blob/master/rc.md for other parameters which are passed directly to `app_rc_move` miio command
*/
case CUSTOM_COMMANDS.SEND_RC_COMMAND:
if (msg.angle !== undefined && !isNaN(parseFloat(msg.angle)) &&
msg.velocity !== undefined && !isNaN(parseFloat(msg.velocity)) &&
msg.duration !== undefined && !isNaN(parseInt(msg.duration))) {
this.vacuum.autoManualControl(parseFloat(msg.angle), parseFloat(msg.velocity), parseInt(msg.duration), parseInt(msg.startdelay) || null, (err, data) => this.publishCommandStatus(msg.command,data,err));
} else {
this.publishCommandStatus(msg.command,null,"Invalid args supplied.");
}
break;
default:
this.publishCommandStatus(msg.command,null,"Received invalid custom command: " + JSON.stringify(msg));
}
Expand Down
60 changes: 60 additions & 0 deletions lib/miio/Vacuum.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ const Vacuum = function(valetudo) {
this.handshakeInProgress = false;
this.handshakeTimeout = null;

this.autoRCTimer = 0;
this.autoRCDelayed = false;
this.autoRCQueue = [];
this.autoRCSeq = 0;

this.idleTimeoutTimer = 0;
this.resetQueues(0xff);

Expand Down Expand Up @@ -389,6 +394,61 @@ Vacuum.prototype.setManualControl = function(angle, velocity, duration, sequence
this.sendMessage("app_rc_move", [{"omega": angle, "velocity": velocity, "seqnum": sequenceId, "duration": duration}], {}, callback)
};

Vacuum.prototype.autoManualControl = function(angle, velocity, duration, startdelay, callback) {
const self = this;
const sendCommand = function(angle, velocity, duration, startdelay, callback) {
self.autoRCDelayed = true;
clearTimeout(self.autoRCTimer);
self.autoRCTimer = setTimeout(() => {
self.stopManualControl(err => {
self.autoRCTimer = 0;
});
},5e3+duration);
self.setManualControl(angle, velocity, duration, self.autoRCSeq++, (err,res) => {
callback(err,res);
setTimeout(() => {
if (self.autoRCQueue.length) {
let args = self.autoRCQueue.shift();
sendCommand(...args);
} else {
self.autoRCDelayed = false;
}
},5e2+duration);
});

};
this.getCurrentStatus((err,res) => {
if (err) {
return callback(err);
}
if (res.state === 7 || self.autoRCDelayed) { // already in RC mode or waiting to enter it
if (!self.autoRCDelayed) {
// process immediately if there's no delay
sendCommand(angle, velocity, duration, startdelay, callback);
} else {
// or add the request to RC queue to run it later
clearTimeout(self.autoRCTimer);
self.autoRCQueue.push([angle, velocity, duration, startdelay, callback]);
}
} else if ([2,3,10,12].includes(res.state)) { // idle, sleep, ...
self.autoRCDelayed = true;
self.startManualControl(err => {
if (err) {
self.autoRCDelayed = false;
return callback(err);
}
// apparently sequence number MUST start with 1 (when 0 it is ignored)
self.autoRCSeq = 1;
// we need to use some LONG ENOUGH delay to wait after starting remote control mode
// since otherwise the command seems to be discarded
setTimeout(() => sendCommand(angle, velocity, duration, startdelay, callback), startdelay || 8e3);
});
} else {
callback("device is at inappropriate state to run autoRC commands (acceptable: 2,3,10,12, now: " + res.state + ")");
}
});
};

/**
* Returns carpet detection parameter like
* {
Expand Down
12 changes: 12 additions & 0 deletions lib/webserver/WebServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,18 @@ const WebServer = function (valetudo) {
}
});

this.app.put("/api/auto_manual_control", function (req, res) {
if (req.body && req.body.angle !== undefined && req.body.velocity !== undefined && req.body.duration !== undefined) {
self.vacuum.autoManualControl(req.body.angle, req.body.velocity, req.body.duration, parseInt(req.body.startdelay) || null, function (err, data) {
if (err) {
res.status(500).send(err.toString());
} else {
res.json(data);
}
});
}
});

this.app.get("/api/carpet_mode", function (req, res) {
self.vacuum.getCarpetMode(function (err, data) {
if (err) {
Expand Down

0 comments on commit e2ed176

Please sign in to comment.