Version 0.3.0

* Added periodic refresh of robot state
* Improved go to dock switch to be enabled as soon as possible without
manual refresh
* Improved switches to indicate the time an action lasts
* Improved eco mode to not be overwritten by robot state
This commit is contained in:
naofireblade 2017-05-06 23:51:58 +02:00
parent b2ddecc827
commit 36e8496108
4 changed files with 184 additions and 92 deletions

View File

@ -14,3 +14,10 @@
## 0.2.1 ## 0.2.1
* Improved the go to dock command * Improved the go to dock command
## 0.3.0
* Added periodic refresh of robot state
* Improved go to dock switch to be enabled as soon as possible without manual refresh
* Improved switches to indicate the time an action lasts
* Improved eco mode to not be overwritten by robot state

View File

@ -28,13 +28,16 @@ Feel free to leave any feedback [here](https://github.com/naofireblade/homebridg
Add the following information to your config file. Change the values for name, email and password. Add the following information to your config file. Change the values for name, email and password.
The parameter **refresh** is optional (default 120 seconds) and adjusts in what interval changes in the robot state will be pushed to homekit (e.g. when starting the robot with the neato app). The minimum is 60 seconds. You can disable this by entering 0.
```json ```json
"accessories": [ "accessories": [
{ {
"accessory": "NeatoVacuumRobot", "accessory": "NeatoVacuumRobot",
"name": "YourRobot", "name": "YourRobot",
"email": "YourEmail", "email": "YourEmail",
"password": "YourPassword" "password": "YourPassword",
"refresh": "120"
} }
] ]
``` ```
@ -43,4 +46,4 @@ Add the following information to your config file. Change the values for name, e
- BotVac Connected (Firmware 2.2.0) - BotVac Connected (Firmware 2.2.0)
If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) your experience with this plugin. If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) about your experience with this plugin.

256
index.js
View File

@ -1,9 +1,18 @@
"use strict"; "use strict";
var inherits = require('util').inherits; var inherits = require('util').inherits,
var debug = require('debug')('homebridge-neato'); debug = require('debug')('homebridge-neato'),
var botvac = require('node-botvac'); botvac = require('node-botvac'),
var Service, Characteristic; Service,
Characteristic,
vacuumRobotCleanService,
vacuumRobotGoToDockService,
vacuumRobotDockStateService,
vacuumRobotEcoService,
vacuumRobotScheduleService,
vacuumRobotBatteryService,
refresh,
timer
module.exports = function (homebridge) { module.exports = function (homebridge) {
Service = homebridge.hap.Service; Service = homebridge.hap.Service;
@ -18,8 +27,28 @@ function NeatoVacuumRobot(log, config) {
this.email = config['email']; this.email = config['email'];
this.password = config['password']; this.password = config['password'];
// load refresh time
// default 120s
this.refresh = ('refresh' in config ? parseInt(config['refresh']) : 120);
// must be integer and positive
if (typeof this.refresh !=='number' || (this.refresh%1)!==0 || this.refresh < 0) {
this.refresh = 0;
}
// minimum 60s
if (this.refresh != 0 && this.refresh < 60) {
this.refresh = 60;
}
this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean");
this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock");
this.vacuumRobotDockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState");
this.vacuumRobotEcoService = new Service.Switch(this.name + " Eco Mode", "eco");
this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule");
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
this.lastUpdate = null; this.lastUpdate = null;
this.robot = null; this.robot = null;
this.getStateTimer();
} }
NeatoVacuumRobot.prototype = { NeatoVacuumRobot.prototype = {
@ -35,26 +64,20 @@ NeatoVacuumRobot.prototype = {
.setCharacteristic(Characteristic.Model, this.name) .setCharacteristic(Characteristic.Model, this.name)
.setCharacteristic(Characteristic.SerialNumber, this.serial); .setCharacteristic(Characteristic.SerialNumber, this.serial);
this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean"); this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this));
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.clean.bind(this));
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this)); this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock"); this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this));
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.dock.bind(this)); this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this));
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getCanGoToDock.bind(this));
this.vacuumRobotDockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState"); this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this));
this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDockState.bind(this));
this.vacuumRobotEcoService = new Service.Switch(this.name + " Eco Mode", "eco"); this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('set', this.eco.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this)); this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this));
this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule"); this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this));
this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.schedule.bind(this));
this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this)); this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this));
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
@ -62,92 +85,115 @@ NeatoVacuumRobot.prototype = {
this.vacuumRobotScheduleService, this.vacuumRobotBatteryService]; this.vacuumRobotScheduleService, this.vacuumRobotBatteryService];
}, },
clean: function (on, callback) { setClean: function (on, callback) {
let that = this; let that = this;
if (on) { this.getStateAndRobot(function (error, result) {
this.getState(function (error, result) { if (on) {
if (that.robot.canResume === true) { if (that.robot.canResume || that.robot.canStart) {
debug("Resume cleaning"); // wait for robot to start and then disable the old timer and enable it again (with a shorter interval)
that.robot.resumeCleaning(function (error, result) { setTimeout(function() {
that.log(result); clearTimeout(that.timer);
}); that.getStateTimer();
}, 10000);
if (that.robot.canResume) {
debug("Resume cleaning");
that.robot.resumeCleaning(callback);
}
else {
debug("Start cleaning");
that.robot.startCleaning(that.robot.eco, callback);
}
} }
else { else {
debug("Start cleaning"); debug("Already cleaning");
that.robot.startCleaning(that.robot.eco, function (error, result) { callback();
that.log(result); }
}
else {
if (that.robot.canPause) {
debug("Pause cleaning");
that.robot.pauseCleaning(callback);
}
else {
debug("Already stopped");
callback();
}
}
});
},
setGoToDock: function (on, callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
if (on) {
if (that.robot.canPause) {
debug("Pause cleaning to go to dock");
that.robot.pauseCleaning(function (error, result) {
setTimeout(function() {
debug("Go to dock");
that.robot.sendToBase(callback);
}, 1000);
}); });
} }
}); else if (that.robot.canGoToBase)
} {
else { debug("Go to dock");
debug("Pause cleaning"); that.robot.sendToBase(callback);
this.robot.pauseCleaning(false, function (error, result) { }
that.log(result); else {
}); debug("Can't go to dock at the moment");
} callback();
callback(); }
} else {
// dont allow manual setting the switch to off
setTimeout(function() {
that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, true);
callback();
},1000);
}
});
}, },
dock: function (on, callback) { setEco: function (on, callback) {
let that = this;
if (on) {
debug("Pause cleaning to go to dock");
this.robot.pauseCleaning(false, function (error, result) {
that.log(result);
});
setTimeout(function() {
debug("Go to dock");
that.robot.sendToBase(false, function (error, result) {
that.log(result);
});
}, 3000);
}
callback();
},
eco: function (on, callback) {
debug(on ? "Enable eco mode" : "Disable eco mode"); debug(on ? "Enable eco mode" : "Disable eco mode");
this.robot.eco = on; this.robot.eco = on;
callback(); callback();
}, },
schedule: function (on, callback) { setSchedule: function (on, callback) {
let that = this; let that = this;
if (on) { this.getStateAndRobot(function (error, result) {
debug("Enable schedule"); if (on) {
this.robot.enableSchedule(false, function (error, result) { debug("Enable schedule");
that.log(result); that.robot.enableSchedule(callback);
}); }
} else {
else { debug("Disable schedule");
debug("Disable schedule"); that.robot.disableSchedule(callback);
this.robot.disableSchedule(false, function (error, result) { }
that.log(result); });
});
}
callback();
}, },
getClean: function(callback) { getClean: function(callback) {
let that = this; let that = this;
this.getState(function (error, result) { this.getStateAndRobot(function (error, result) {
debug("Is cleaning: " + that.robot.canPause); debug("Is cleaning: " + that.robot.canPause);
callback(false, that.robot.canPause); callback(false, that.robot.canPause);
}); });
}, },
getCanGoToDock: function(callback) { getGoToDock: function(callback) {
let that = this; let that = this;
this.getState(function (error, result) { this.getStateAndRobot(function (error, result) {
debug("Can go to dock: " + that.robot.dockHasBeenSeen); debug("Can go to dock: " + that.robot.dockHasBeenSeen);
callback(false, !that.robot.dockHasBeenSeen); callback(false, !that.robot.dockHasBeenSeen);
}); });
}, },
getDockState: function(callback) { getDock: function(callback) {
let that = this; let that = this;
this.getState(function (error, result) { this.getStateAndRobot(function (error, result) {
debug("Is docked: " + that.robot.isDocked); debug("Is docked: " + that.robot.isDocked);
debug(that.robot); debug(that.robot);
callback(false, that.robot.isDocked); callback(false, that.robot.isDocked);
@ -155,16 +201,13 @@ NeatoVacuumRobot.prototype = {
}, },
getEco: function(callback) { getEco: function(callback) {
let that = this; // dont load eco here, because we cant save the eco state on the robot
this.getState(function (error, result) { callback(false, this.robot.eco);
debug("Eco mode: " + that.robot.eco);
callback(false, that.robot.eco);
});
}, },
getSchedule: function(callback) { getSchedule: function(callback) {
let that = this; let that = this;
this.getState(function (error, result) { this.getStateAndRobot(function (error, result) {
debug("Schedule: " + that.robot.isScheduleEnabled); debug("Schedule: " + that.robot.isScheduleEnabled);
callback(false, that.robot.isScheduleEnabled); callback(false, that.robot.isScheduleEnabled);
}); });
@ -173,7 +216,7 @@ NeatoVacuumRobot.prototype = {
getBatteryLevel: function(callback) { getBatteryLevel: function(callback) {
let that = this; let that = this;
this.getState(function (error, result) { this.getStateAndRobot(function (error, result) {
debug("Battery: " + that.robot.charge); debug("Battery: " + that.robot.charge);
callback(false, that.robot.charge); callback(false, that.robot.charge);
}); });
@ -181,32 +224,32 @@ NeatoVacuumRobot.prototype = {
getBatteryChargingState: function(callback) { getBatteryChargingState: function(callback) {
let that = this; let that = this;
this.getState(function (error, result) { this.getStateAndRobot(function (error, result) {
debug("Is charging: " + that.robot.isCharging); debug("Is charging: " + that.robot.isCharging);
callback(false, that.robot.isCharging); callback(false, that.robot.isCharging);
}); });
}, },
getState: function(callback) { getStateAndRobot: function(callback) {
let that = this; let that = this;
if (this.robot === null) if (this.robot === null)
{ {
this.getRobot(function (error, result) { this.getRobot(function (error, result) {
that._getState(callback); that.getState(callback);
}); });
} }
else { else {
that._getState(callback); that.getState(callback);
} }
}, },
_getState: function(callback) { getState: function(callback) {
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) { if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) {
debug("Get info (cached)"); debug("Get state (cached)");
callback(); callback();
} }
else { else {
debug("Get info (new)"); debug("Get state (new)");
let that = this; let that = this;
this.robot.getState(function (error, result) { this.robot.getState(function (error, result) {
that.lastUpdate = new Date(); that.lastUpdate = new Date();
@ -215,6 +258,45 @@ NeatoVacuumRobot.prototype = {
} }
}, },
getStateTimer: function() {
debug("Timer called");
let that = this;
this.getStateAndRobot(function (error, result) {
// only update these values if the state is different from the current one, otherwise we might accidentally start an action
if (that.vacuumRobotCleanService.getCharacteristic(Characteristic.On).value !== that.robot.canPause) {
that.vacuumRobotCleanService.setCharacteristic(Characteristic.On, that.robot.canPause);
}
if (that.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value !== !that.robot.dockHasBeenSeen) {
that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, !that.robot.dockHasBeenSeen);
}
if (that.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== that.robot.isScheduleEnabled) {
that.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, that.robot.isScheduleEnabled);
}
// no commands here, values can be updated without problems
that.vacuumRobotDockStateService.setCharacteristic(Characteristic.OccupancyDetected, that.robot.isDocked);
that.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, that.robot.charge);
that.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, that.robot.isCharging);
// dont update eco, because we cant write that value onto the robot and dont want it to be overwritten in our plugin
if (that.robot.canPause) {
debug("Short timer set: 10s");
that.timer = setTimeout(that.getStateTimer.bind(that), 10 * 1000);
}
else if (that.refresh != 0) {
debug("Long timer set: " + that.refresh + "s");
that.timer = setTimeout(that.getStateTimer.bind(that), that.refresh * 1000);
}
else {
debug("Disabled timer");
}
});
},
getRobot: function(callback) { getRobot: function(callback) {
debug("Get robot"); debug("Get robot");
let client = new botvac.Client(); let client = new botvac.Client();

View File

@ -1,6 +1,6 @@
{ {
"name": "homebridge-neato", "name": "homebridge-neato",
"version": "0.2.1", "version": "0.3.0",
"description": "A Neato vacuum robot plugin for homebridge.", "description": "A Neato vacuum robot plugin for homebridge.",
"license": "MIT", "license": "MIT",
"keywords": [ "keywords": [