11 Commits

Author SHA1 Message Date
naofireblade
2fec762498 Version 0.4.4
* Fixed config parameter to disable switches/sensors not optional
2017-07-25 08:54:52 +02:00
naofireblade
857af55e99 Version 0.4.3
* Fixed config parameter to disable switches/sensors not optional
2017-07-25 08:52:17 +02:00
naofireblade
20e0a9b909 Version 0.4.2
* Added config parameter to disable switches/sensors
2017-07-24 19:52:32 +02:00
Arne
afdff765b0 Wording 2017-06-07 10:51:14 +02:00
Arne
dcef6653ff Wording 2017-06-07 10:50:44 +02:00
Arne
82bf19c548 Wording 2017-06-07 10:48:46 +02:00
Arne
1084bff0ee Wording 2017-06-07 10:48:11 +02:00
Arne
5aa66835dd Wording 2017-06-07 10:47:01 +02:00
naofireblade
08ef90f7b0 Version 0.4.1
* Added config parameter for extraCareNavigation
2017-06-06 17:25:02 +02:00
naofireblade
bfb03b5d5d Version 0.4.0
* Added support for multiple robots
* Added log output when user requests accessory identify
* Changed plugin to platform instead of single accessory
* Removed parameter name from config
2017-06-05 16:46:45 +02:00
naofireblade
56e85c92e0 Wording and Bugfixes
* Fixed a bug that refresh is not disabled when set to 0
2017-05-20 19:41:25 +02:00
4 changed files with 200 additions and 129 deletions

View File

@@ -26,4 +26,27 @@
## 0.3.1
* Added experimental support for newer Neato Botvac models
* Added support for Neato BotVac D5 Connected
## 0.3.2
* Fixed a bug that refresh is not disabled when set to 0
## 0.4.0
* Added support for multiple robots
* Added log output when user requests accessory identify
* Changed plugin to platform instead of single accessory
* Removed parameter name from config
## 0.4.1
* Added config parameter for extraCareNavigation
## 0.4.2
* Added config parameter to disable switches/sensors
## 0.4.4
* Fixed config parameter to disable switches/sensors not optional

View File

@@ -4,7 +4,9 @@ This is a plugin for [homebridge](https://github.com/nfarina/homebridge) to cont
Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues).
# Features
If you update from a previous version 0.3.x you have to adapt your config.
## Features
- Start and pause cleaning
- Return to dock\*
@@ -13,39 +15,61 @@ Feel free to leave any feedback [here](https://github.com/naofireblade/homebridg
- Get battery info
- Get dock info
- Periodic refresh of robot state
- Support for multiple robots
- Extra care navigation
\* The robot needs to clean for some seconds before he knows where his dock is. After this time the switch to send him home will be automatically available.
\* Available after some seconds of cleaning.
**Hint:** To control the robot with your own commands just set up a scene with the name of your choice.
# Installation
## Installation
1. Install homebridge using: `npm install -g homebridge`
2. Install this plugin using: `npm install -g homebridge-neato`
3. If you don't have a Neato account yet create one [here](https://www.neatorobotics.com/create-account/).
4. Update your configuration file. See the sample below.
### Configuration
## Configuration
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 email and password.
The parameter **refresh** is optional (default 0=off) and adjusts in what interval (seconds) changes of the robot state will be pushed to homekit. The minimum refresh time is 60 seconds. You need this only when you set up rules based on the robot state and start him outside of homekit (e.g. with the Neato app).
### Simple
```json
"accessories": [
"platforms": [
{
"accessory": "NeatoVacuumRobot",
"name": "YourRobot",
"platform": "NeatoVacuumRobot",
"email": "YourEmail",
"password": "YourPassword",
"refresh": "0"
"password": "YourPassword"
}
]
```
# Tested robots
### Advanced
The following config contains advanced optional settings that are off when not specified.
The parameter **refresh** sets in what interval (seconds) changes of the robot state will be pushed to homekit. The minimum refresh time is 60 seconds. You need this only when you set up rules based on the robot state and start him outside of homekit (e.g. with the Neato app).
The parameter **extraCareNavigation** sets if supporting models (currently Neato D3 and D5) should take extra care of your furniture while cleaning.
The parameter **disabled** accepts a list of switches/sensors that can be disabled in the neato homekit plugin (e.g. dock, dockstate, eco, schedule).
```json
"platforms": [
{
"platform": "NeatoVacuumRobot",
"email": "YourEmail",
"password": "YourPassword",
"refresh": "120",
"extraCareNavigation": true,
"disabled": ["dock", "dockstate", "eco"]
}
]
```
## Tested robots
- BotVac Connected (Firmware 2.2.0)
- BotVac D3 Connected
- BotVac D5 Connected
If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) about your experience with this plugin.

250
index.js
View File

@@ -4,35 +4,89 @@ var inherits = require('util').inherits,
botvac = require('node-botvac'),
Service,
Characteristic,
vacuumRobotCleanService,
vacuumRobotGoToDockService,
vacuumRobotDockStateService,
vacuumRobotEcoService,
vacuumRobotScheduleService,
vacuumRobotBatteryService,
refresh,
timer
Characteristic
module.exports = function (homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerAccessory("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobot);
homebridge.registerPlatform("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobotPlatform);
}
function NeatoVacuumRobot(log, config) {
function NeatoVacuumRobotPlatform(log, config) {
this.log = log;
this.name = config['name'];
this.serial = "1-3-3-7";
this.email = config['email'];
this.password = config['password'];
this.hiddenServices = ('disabled' in config ? config['disabled'] : '');
this.careNavigation = ('extraCareNavigation' in config && config['extraCareNavigation'] ? 2 : 1);
debug("Extra Care Navigation: " + this.careNavigation);
// default off
this.refresh = ('refresh' in config ? parseInt(config['refresh']) : 0);
// must be integer and positive
this.refresh = (typeof this.refresh !=='number' || (this.refresh%1)!==0 || this.refresh < 0) ? 0 : this.refresh;
// minimum 60s
this.refresh = (0 < this.refresh < 60) ? 60 : this.refresh;
this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh;
}
NeatoVacuumRobotPlatform.prototype = {
accessories: function(callback) {
this.accessories = [];
let that = this;
this.robots = this.getRobots(function () {
for (var i = 0; i < that.robots.length; i++) {
that.log("Found robot #" + (i+1) + ": " + that.robots[i].name);
var robotAccessory = new NeatoVacuumRobotAccessory(that.robots[i], that);
that.accessories.push(robotAccessory);
}
callback(that.accessories);
});
},
getRobots: function(callback) {
debug("Get all robots");
let client = new botvac.Client();
let that = this;
client.authorize(this.email, this.password, false, function (error) {
if (error) {
that.log(error);
that.log.error("Can't log on to neato cloud. Please check your credentials.");
callback();
}
else {
client.getRobots(function (error, robots) {
if (error) {
that.log(error);
that.log.error("Successful login but can't connect to your neato robot.");
callback();
}
else {
if (robots.length === 0) {
that.log.error("Successful login but no robots associated with your account.");
callback();
}
else {
that.robots = robots;
callback();
}
}
});
}
});
}
}
function NeatoVacuumRobotAccessory(robot, platform) {
this.platform = platform;
this.log = platform.log;
this.refresh = platform.refresh;
this.careNavigation = platform.careNavigation;
this.hiddenServices = platform.hiddenServices;
this.robot = robot;
this.name = robot.name;
this.lastUpdate = null;
this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean");
this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock");
@@ -41,23 +95,34 @@ function NeatoVacuumRobot(log, config) {
this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule");
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
this.lastUpdate = null;
this.robot = null;
this.getStateTimer();
this.updateRobotTimer();
}
NeatoVacuumRobot.prototype = {
NeatoVacuumRobotAccessory.prototype = {
identify: function (callback) {
this.log("Identify requested");
callback();
let that = this;
this.updateRobot(function() {
// hide serial and secret in log
let _serial = that.robot._serial;
let _secret = that.robot._secret;
that.robot._serial = "*****";
that.robot._secret = "*****";
that.log(that.robot);
that.robot._serial = _serial;
that.robot._secret = _secret;
callback();
});
},
getServices: function () {
debug(this.robot._serial);
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Name, this.robot.name)
.setCharacteristic(Characteristic.Manufacturer, "Neato Robotics")
.setCharacteristic(Characteristic.Model, this.name)
.setCharacteristic(Characteristic.SerialNumber, this.serial);
.setCharacteristic(Characteristic.Model, "Coming soon")
.setCharacteristic(Characteristic.SerialNumber, this.robot._serial);
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this));
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
@@ -76,42 +141,51 @@ NeatoVacuumRobot.prototype = {
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
return [this.informationService, this.vacuumRobotCleanService, this.vacuumRobotGoToDockService, this.vacuumRobotDockStateService, this.vacuumRobotEcoService,
this.vacuumRobotScheduleService, this.vacuumRobotBatteryService];
this.services = [this.informationService, this.vacuumRobotCleanService, this.vacuumRobotBatteryService];
if (this.hiddenServices.indexOf('dock') === -1)
this.services.push(this.vacuumRobotGoToDockService);
if (this.hiddenServices.indexOf('dockstate') === -1)
this.services.push(this.vacuumRobotDockStateService);
if (this.hiddenServices.indexOf('eco') === -1)
this.services.push(this.vacuumRobotEcoService);
if (this.hiddenServices.indexOf('schedule') === -1)
this.services.push(this.vacuumRobotScheduleService);
return this.services;
},
setClean: function (on, callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
this.updateRobot(function (error, result) {
if (on) {
if (that.robot.canResume || that.robot.canStart) {
// wait for robot to start and then disable the old timer and enable it again (with a shorter interval)
// wait for robot to start and then start a short timer to recognize when he can go to dock or is finished
setTimeout(function() {
clearTimeout(that.timer);
that.getStateTimer();
that.updateRobotTimer();
}, 10000);
if (that.robot.canResume) {
debug("Resume cleaning");
debug(that.name + ": Resume cleaning");
that.robot.resumeCleaning(callback);
}
else {
debug("Start cleaning");
that.robot.startCleaning(that.robot.eco, 2, callback);
debug(that.name + ": Start cleaning (" + that.careNavigation + ")");
that.robot.startCleaning(that.robot.eco, that.careNavigation, callback);
}
}
else {
debug("Already cleaning");
debug(that.name + ": Cant start, maybe already cleaning");
callback();
}
}
else {
if (that.robot.canPause) {
debug("Pause cleaning");
debug(that.name + ": Pause cleaning");
that.robot.pauseCleaning(callback);
}
else {
debug("Already stopped");
debug(that.name + ": Already stopped");
callback();
}
}
@@ -120,10 +194,10 @@ NeatoVacuumRobot.prototype = {
setGoToDock: function (on, callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
this.updateRobot(function (error, result) {
if (on) {
if (that.robot.canPause) {
debug("Pause cleaning to go to dock");
debug(that.name + ": Pause cleaning to go to dock");
that.robot.pauseCleaning(function (error, result) {
setTimeout(function() {
debug("Go to dock");
@@ -133,35 +207,34 @@ NeatoVacuumRobot.prototype = {
}
else if (that.robot.canGoToBase)
{
debug("Go to dock");
debug(that.name + ": Go to dock");
that.robot.sendToBase(callback);
}
else {
debug("Can't go to dock at the moment");
debug(that.name + ": Can't go to dock at the moment");
callback();
}
} else {
debug(that.robot);
callback();
}
});
},
setEco: function (on, callback) {
debug(on ? "Enable eco mode" : "Disable eco mode");
debug(this.name + ": " + (on ? "Enable eco mode" : "Disable eco mode"));
this.robot.eco = on;
callback();
},
setSchedule: function (on, callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
this.updateRobot(function (error, result) {
if (on) {
debug("Enable schedule");
debug(that.name + ": Enable schedule");
that.robot.enableSchedule(callback);
}
else {
debug("Disable schedule");
debug(that.name + ": Disable schedule");
that.robot.disableSchedule(callback);
}
});
@@ -169,24 +242,24 @@ NeatoVacuumRobot.prototype = {
getClean: function(callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
debug("Is cleaning: " + that.robot.canPause);
this.updateRobot(function (error, result) {
debug(that.name + ": Is cleaning: " + that.robot.canPause);
callback(false, that.robot.canPause);
});
},
getGoToDock: function(callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
debug("Can go to dock: " + that.robot.dockHasBeenSeen);
this.updateRobot(function (error, result) {
debug(that.name + ": Can go to dock: " + that.robot.dockHasBeenSeen);
callback(false, !that.robot.dockHasBeenSeen);
});
},
getDock: function(callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
debug("Is docked: " + that.robot.isDocked);
this.updateRobot(function (error, result) {
debug(that.name + ": Is docked: " + that.robot.isDocked);
callback(false, that.robot.isDocked);
});
},
@@ -198,8 +271,8 @@ NeatoVacuumRobot.prototype = {
getSchedule: function(callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
debug("Schedule: " + that.robot.isScheduleEnabled);
this.updateRobot(function (error, result) {
debug(that.name + ": Schedule: " + that.robot.isScheduleEnabled);
callback(false, that.robot.isScheduleEnabled);
});
},
@@ -207,41 +280,28 @@ NeatoVacuumRobot.prototype = {
getBatteryLevel: function(callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
debug("Battery: " + that.robot.charge);
this.updateRobot(function (error, result) {
debug(that.name + ": Battery: " + that.robot.charge);
callback(false, that.robot.charge);
});
},
getBatteryChargingState: function(callback) {
let that = this;
this.getStateAndRobot(function (error, result) {
debug("Is charging: " + that.robot.isCharging);
this.updateRobot(function (error, result) {
debug(that.name + ": Is charging: " + that.robot.isCharging);
callback(false, that.robot.isCharging);
});
},
getStateAndRobot: function(callback) {
updateRobot: function(callback) {
let that = this;
if (this.robot === null)
{
this.getRobot(function (error, result) {
that.getState(callback);
});
}
else {
that.getState(callback);
}
},
getState: function(callback) {
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) {
debug("Get state (cached)");
debug(this.name + ": Update (cached)");
callback();
}
else {
debug("Get state (new)");
let that = this;
debug(this.name + ": Update (online)");
this.robot.getState(function (error, result) {
that.lastUpdate = new Date();
callback();
@@ -249,10 +309,10 @@ NeatoVacuumRobot.prototype = {
}
},
getStateTimer: function() {
debug("Timer called");
updateRobotTimer: function() {
let that = this;
this.getStateAndRobot(function (error, result) {
debug(this.name + ": Timer called");
this.updateRobot(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) {
@@ -275,52 +335,16 @@ NeatoVacuumRobot.prototype = {
// 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: 30s");
that.timer = setTimeout(that.getStateTimer.bind(that), 30 * 1000);
debug(that.name + ": Timer set (cleaning): 30s");
that.timer = setTimeout(that.updateRobotTimer.bind(that), 30 * 1000);
}
else if (that.refresh != 0) {
debug("Long timer set: " + that.refresh + "s");
that.timer = setTimeout(that.getStateTimer.bind(that), that.refresh * 1000);
debug(that.name + ": Timer set (user): " + that.refresh + "s");
that.timer = setTimeout(that.updateRobotTimer.bind(that), that.refresh * 1000);
}
else {
debug("Disabled timer");
debug(that.name + ": Timer stopped");
}
});
},
getRobot: function(callback) {
debug("Get robot");
let client = new botvac.Client();
let that = this;
client.authorize(this.email, this.password, false, function (error) {
if (error) {
that.log(error);
that.log.error("Can't log on to neato cloud. Please check your credentials.")
}
else {
client.getRobots(function (error, robots) {
if (error) {
that.log(error);
that.log.error("Successful login but can't connect to your neato robot.")
}
else {
if (robots.length === 0) {
that.log.error("Successful login but no robots associated with your account.")
}
else {
that.robot = robots[0];
that.log("Found robot: " + that.robot.name);
that.getState(function (error, result) {
debug(that.robot);
})
if (robots.length > 1){
that.log.warn("Found more then one robot in your account. This plugin currently just supports one. First one found will be used.")
}
callback();
}
}
});
}
});
}
}

View File

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