70 Commits

Author SHA1 Message Date
Arne
7594843bb3 Fixed boundary issues 2019-09-21 14:47:41 +02:00
Arne
0ac882414a Added feature to start cleaning of a new room. 2019-09-21 14:28:31 +02:00
Arne
6a57eaa57e Bumped version to 0.7.0-beta.0 2019-09-21 14:25:49 +02:00
Arne
8822670f9b Fixed boundary cleaning 2019-09-19 12:10:17 +02:00
Arne
150b8973ee Changed coffee button 2019-09-19 11:26:42 +02:00
Arne
ca2af3968c Updated debug lib 2019-09-19 11:26:27 +02:00
Arne
6f19a189b2 Reworked room cleaning (WIP) 2019-09-19 11:25:36 +02:00
Arne
5139929bce Formatting only 2019-09-19 09:37:16 +02:00
Arne
a3b64b7c53 Added option to buy me a coffee 2019-09-17 22:27:06 +02:00
Arne
0338580c0a Version 0.6.3 2019-08-14 23:45:19 +02:00
Arne
54f303394e Fixed homebridge crash when 2 zones have the same name 2019-08-14 23:43:03 +02:00
Arne
c43a666378 Fixed homebridge crash when homebridge has no internet connection or the neato servers are offline 2019-08-14 23:06:03 +02:00
Arne
20c54a02e8 Fixed homebridge crash when robot has a map without zones 2019-08-14 22:48:23 +02:00
Arne
1436e4b342 Fixed homebridge startup failed when robot does not support mapping 2019-07-31 10:05:59 +02:00
Arne
db8305bbee Fixed homebridge startup failed when robot does not support zone cleaning 2019-07-20 22:37:27 +02:00
Arne
f795781d2a Code cleanup 2019-07-20 22:27:16 +02:00
Arne
3cfde323a8 Added gitignore 2019-07-20 22:20:31 +02:00
Arne
4a97487400 Added contributor and bumped version to v0.6.0 2019-07-14 23:35:09 +02:00
Arne
5ea9dde49c Merge pull request #28 from az0uz/feature/zone_cleaning
Support for zone cleaning
2019-07-14 23:32:43 +02:00
Arne
8ef24c9d40 Update package.json 2019-07-10 10:27:11 +02:00
Antoine de Maleprade
979dc40ccf fixed uuid reset when restarting homebridge, removed logs 2019-05-06 20:11:04 -07:00
Antoine de Maleprade
7d824ee0b9 Working zone cleaning, implemented multiple accesories to have a room per switch in homekit 2019-04-28 17:35:49 -07:00
Antoine de Maleprade
b87e49e12f fix: zone services discovery 2019-04-27 22:52:49 -07:00
Antoine de Maleprade
afe7d690ef Added zone cleaning 2019-04-27 22:06:07 -07:00
Arne
442b91c347 Versionbump to v0.5.2 2019-04-09 12:46:08 +02:00
Arne
55020c005b Merge pull request #26 from btutal/master
Add Schema for Config
2019-04-09 12:43:31 +02:00
Berkay
7c72aabb25 Add Schema for Config
Add schema file for homebridge-config-ui-x and similar plugins
2019-04-08 22:26:30 +02:00
Arne
e1754e02ed Fixed console log in node-botvac 2018-11-17 13:27:33 +01:00
Arne
0b864eb229 Updated readme 2018-11-14 22:42:38 +01:00
Arne
1d1fa121a4 Updated readme 2018-11-14 18:24:52 +01:00
Arne
6c6bb1c204 Updated readme and changelog 2018-11-14 18:22:27 +01:00
Arne
577e62ea32 Improved refresh interval 2018-11-13 21:47:39 +01:00
Arne
fbec31602f NoGo Lines and options sync
ADDED
- NoGo lines button
- Extra care navigation button
- Syncing cleaning options from last run

CHANGED
- Goto dock button is now always off
- Better error handling
- More detailed debug messages
- Updated node-botvac dependency to 0.1.6

REMOVED
- Extra care navigation option parameter (is now a button)
2018-11-11 15:53:50 +01:00
naofireblade
42fa0399f8 Version 0.4.7
* Fixed an exception when no robot is associated with the account
2018-03-26 19:14:54 +02:00
Arne
0de549dd41 Merge pull request #12 from ghulands/fix_no_robots
Set an empty array if there are no robots on the account
2018-03-25 22:01:32 +02:00
Greg Hulands
7fef07df0a Set an empty array if there are no robots on the account 2018-03-24 22:54:12 -07:00
Arne
9e9dd80547 Added badges 2018-02-08 12:14:48 +01:00
Arne
b7ec93953e Updated list of supported robots 2018-01-11 08:01:18 +01:00
naofireblade
ad41c1679d Version 0.4.6
* Added error log while refreshing robot state
* Fixed a rare bug where the robot stops after some seconds of cleaning
2017-12-10 13:14:12 +01:00
naofireblade
89f6f233a5 Added errorlog while refreshing robot state 2017-10-15 19:34:02 +02:00
naofireblade
88f217e2b1 Version 0.4.5
* Fixed compatibility with homebridge 0.4.23 (occupancy sensor not
working)
2017-09-04 00:13:44 +02:00
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
naofireblade
d6dd94979b Offical support for D5 Connected 2017-05-20 13:34:21 +02:00
naofireblade
6deca89d27 Updated dependencies 2017-05-18 19:14:59 +02:00
naofireblade
5eb5b9934d Support for basic-2 services 2017-05-18 18:59:14 +02:00
naofireblade
9a5c3f942c Error Handling 2017-05-13 21:17:46 +02:00
naofireblade
8f6f3dc72d Wording 2017-05-13 21:15:13 +02:00
naofireblade
0f8fb1dedf Wording 2017-05-07 17:20:54 +02:00
naofireblade
e01fb8df28 Bugfixes and changes in periodic refresh
* periodic refresh is now optional (minimum 60s)
* refresh while cleaning is now 30s
* fixed a bug where the robot starts to drive home after some minutes of
cleaning
2017-05-07 16:31:41 +02:00
naofireblade
8cb6ecf8a5 Improved refresh parameter 2017-05-06 23:57:36 +02:00
naofireblade
36e8496108 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
2017-05-06 23:51:58 +02:00
naofireblade
b2ddecc827 Improved go to dock command 2017-05-04 19:24:38 +02:00
naofireblade
5dbdb18c58 Set theme jekyll-theme-cayman 2017-04-30 18:07:17 +02:00
naofireblade
d49cab1e97 Set theme jekyll-theme-cayman 2017-04-30 18:06:45 +02:00
naofireblade
92eabfe5cd Wording 2017-04-30 14:35:23 +02:00
naofireblade
67b930653f Wording 2017-04-30 14:17:21 +02:00
naofireblade
579bd32a93 Readme 2017-04-28 18:12:22 +02:00
naofireblade
a606d4c632 Version 0.2.0
* Added dock info
* Changed logging to debug library
2017-04-22 22:57:09 +02:00
naofireblade
3a4327f7e9 spelling 2017-04-18 02:59:07 +02:00
naofireblade
cbfbb26b14 spelling 2017-04-18 02:51:59 +02:00
9 changed files with 995 additions and 283 deletions

17
.gitattributes vendored
View File

@@ -1,17 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

106
.gitignore vendored
View File

@@ -1,47 +1,75 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Logs
logs
*.log
npm-debug.log*
# Folder config file
Desktop.ini
# Runtime data
pids
*.pid
*.seed
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Coverage directory used by tools like istanbul
coverage
# Windows shortcuts
*.lnk
# nyc test coverage
.nyc_output
# =========================
# Operating System Files
# =========================
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# OSX
# =========================
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/dist-server
/dist-e2e
/tmp
/out-tsc
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
*.iml
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
/.sass-cache
/connect.lock
/libpeerconnection.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
Thumbs.db
package-lock.json

View File

@@ -4,4 +4,106 @@
* Added return to base
* Added enable and disable schedule
* Added enable and disable eco mode
* Added battery info
* Added battery info
## 0.2.0
* Added dock info
* Improved logging to use a debug library
## 0.2.1
* Improved the go to dock command
## 0.3.0
* Added periodic refresh of robot state while cleaning
* Added optional periodic refresh of robot state while not cleaning
* Added error messages when cant login or get robot
* Improved go to dock switch to be enabled as soon as possible without manual refresh
* Improved switches to indicate the time an action needs to complete
* Improved eco mode to not be overwritten by robot state update
## 0.3.1
* 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
## 0.4.5
* Fixed compatibility with homebridge 0.4.23 (occupancy sensor not working)
## 0.4.6
* Added error log while refreshing robot state
* Fixed a rare bug where the robot stops after some seconds of cleaning
## 0.4.7
* Fixed an exception when no robot is associated with the account
## 0.5.0
* Added noGo lines button
* Added extra care navigation button
* Added syncing cleaning options from last run
* Added option to disable background state update completely
* Changed goto dock button is now always off
* Changed error handling
* Changed debug messages
* Updated node-botvac dependency to 0.1.6
* Removed extra care navigation option parameter (is now a button)
## 0.5.1
* Updated node-botvac dependency to 0.1.7
## 0.5.2
* Added schema file for use with homebridge-config-ui-x
## 0.6.0
* Added support for zone cleaning
## 0.6.1
* Fixed homebridge startup failed when robot does not support zone cleaning
## 0.6.2
* Fixed homebridge startup failed when robot does not support mapping
## 0.6.3
* Fixed homebridge crash when robot has a map without zones
* Fixed homebridge crash when homebridge has no internet connection or the neato servers are offline
* Fixed homebridge crash when 2 zones have the same name
## 0.7.0
* Fixed room switches not taking eco and extraCare mode into account
* Fixed room switches to support pause/resume of cleaning
* Added feature that enabling another room switch, returns to robot to dock and starts cleaning the new room automatically

View File

@@ -1,40 +1,85 @@
# homebridge-neato
[![npm](https://img.shields.io/npm/v/homebridge-neato.svg?style=flat-square)](https://www.npmjs.com/package/homebridge-neato)
[![npm](https://img.shields.io/npm/dt/homebridge-neato.svg?style=flat-square)](https://www.npmjs.com/package/homebridge-neato)
[![GitHub last commit](https://img.shields.io/github/last-commit/naofireblade/homebridge-neato.svg?style=flat-square)](https://github.com/naofireblade/homebridge-neato)
This is a plugin to control your [Neato](https://www.neatorobotics.com/) vacuum robot.
This is a plugin for [homebridge](https://github.com/nfarina/homebridge) to control your [Neato](https://www.neatorobotics.com/) vacuum robot. You can download it via [npm](https://www.npmjs.com/package/homebridge-neato).
# Installation
If you like this plugin, I would be very grateful for your support:
1. Install homebridge using: npm install -g homebridge
2. Install this plugin using: npm install -g homebridge-neato
3. Update your configuration file. See the sample below.
<a href="https://www.buymeacoffee.com/2D1nUuK36" target="_blank"><img width="140" src="https://bmc-cdn.nyc3.digitaloceanspaces.com/BMC-button-images/custom_images/orange_img.png" alt="Buy Me A Coffee"></a>
# Configuration
Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues).
Configuration sample:
## Features
Add the following information to your config file. Change the values for name, email and password.
- Start and pause cleaning
- Return to dock
- Scheduling
- Eco mode
- Extra care navigation
- Nogo lines
- Zone cleaning
- Get battery info
- Get dock info
- Periodic refresh of robot state
- Support for multiple robots
## 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
Add the following information to your config file. Change the values for email and password.
### Simple
```json
"accessories": [
{
"accessory": "NeatoVacuumRobot",
"name": "YourRobot",
"email": "YourEmail",
"password": "YourPassword"
}
]
"platforms": [
{
"platform": "NeatoVacuumRobot",
"email": "YourEmail",
"password": "YourPassword"
}
]
```
# Features
### Advanced
- Atart and pause cleaning
- Return to base
- Enable and disable schedule
- Enable and disable eco mode
- Get battery info
The following config contains advanced optional settings.
# Tested robots
The parameter **refresh** sets an interval in seconds that is used to update the robot state in the background. This is only required for automations based on the robot state. The default value is `auto` which means that the update is automatically enabled while cleaning and disabled while not cleaning. You can set a value in seconds e.g. `120` to enable background updates even when the robot is not cleaning. You can also disable background updates completely by setting the value `0`. This might be required if you experience timeouts in the app because you have other home automation apps that are connected to your robot.
- BotVac Connected Firmware 2.2.0
The parameter **disabled** accepts a list of switches/sensors that can be disabled in the neato homekit plugin (e.g. dock, dockstate, eco, schedule).
if you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues/new) your experience with this plugin.
```json
"platforms": [
{
"platform": "NeatoVacuumRobot",
"email": "YourEmail",
"password": "YourPassword",
"refresh": "120",
"disabled": ["dock", "dockstate", "eco", "nogolines", "extracare", "schedule"]
}
]
```
## Tested robots
- BotVac Connected (Firmware 2.2.0)
- BotVac D3 Connected
- BotVac D5 Connected (Firmware 4.0.0, 4.3.0)
- BotVac D7 Connected
The plugin should work with D4 and D6 as well. If you have connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) about your experience with this plugin.
## Contributors
Many thanks go to
- [ghulands](https://github.com/ghulands) for finding and fixing a bug when no robot is associated with the neato account
- [Berkay](https://github.com/btutal) for adding the schema file to use the plugin with homebridge-config-ui-x
- [Antoine de Maleprade](https://github.com/az0uz) for adding the zone cleaning feature
- [DJay](https://github.com/DJay-X) for testing new beta versions

1
_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -0,0 +1,570 @@
const debug = require('debug')('homebridge-neato');
let Service,
Characteristic;
module.exports = function (_Service, _Characteristic)
{
Service = _Service;
Characteristic = _Characteristic;
return NeatoVacuumRobotAccessory;
};
function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined)
{
this.platform = platform;
this.boundary = boundary;
this.log = platform.log;
this.refresh = platform.refresh;
this.hiddenServices = platform.hiddenServices;
this.robot = robot;
this.nextRoom = null;
if (typeof boundary === 'undefined')
{
this.name = robot.name;
}
else
{
// if boundary name already exists
if (platform.boundaryNames.includes(this.boundary.name))
{
let lastChar = this.boundary.name.slice(-1);
// boundary name already contains a count number
if (!isNaN(lastChar))
{
// Increment existing count number
this.boundary.name = this.boundary.name.slice(0, -1) + (parseInt(lastChar) + 1);
}
else
{
// Add a new count number
this.boundary.name = this.boundary.name + " 2";
}
}
platform.boundaryNames.push(this.boundary.name);
this.name = this.robot.name + ' - ' + this.boundary.name;
}
this.lastUpdate = null;
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
if (typeof boundary === 'undefined')
{
this.vacuumRobotCleanService = new Service.Switch("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.vacuumRobotNoGoLinesService = new Service.Switch(this.name + " NoGo Lines", "noGoLines");
this.vacuumRobotExtraCareService = new Service.Switch(this.name + " Extra Care", "extraCare");
this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule");
}
else
{
const splitName = boundary.name.split(' ');
let serviceName = "Clean the " + boundary.name;
if (splitName.length >= 2 && splitName[splitName.length - 2].match(/[']s$/g))
{
serviceName = "Clean " + boundary.name;
}
this.vacuumRobotCleanBoundaryService =
new Service.Switch(serviceName, "cleanBoundary:" + boundary.id);
this.log("Adding zone cleaning for: " + boundary.name);
}
this.updateRobotTimer();
}
NeatoVacuumRobotAccessory.prototype = {
identify: function (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;
});
},
getServices: function ()
{
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Neato Robotics")
.setCharacteristic(Characteristic.Model, "Coming soon")
.setCharacteristic(Characteristic.SerialNumber, this.robot._serial);
if (typeof this.boundary === "undefined")
{
this.informationService
.setCharacteristic(Characteristic.Name, this.robot.name)
}
else
{
this.informationService
.setCharacteristic(Characteristic.Name, this.robot.name + ' - ' + this.boundary.name)
}
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
this.services = [this.informationService, this.vacuumRobotBatteryService];
if (typeof this.boundary === "undefined")
{
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', (on, serviceCallback) =>
{
this.setClean(on, serviceCallback, this.boundary)
});
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', (serviceCallback) =>
{
this.getClean(serviceCallback, this.boundary);
});
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this));
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this));
this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this));
this.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).on('set', this.setNoGoLines.bind(this));
this.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).on('get', this.getNoGoLines.bind(this));
this.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).on('set', this.setExtraCare.bind(this));
this.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).on('get', this.getExtraCare.bind(this));
this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this));
this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this));
this.services.push(this.vacuumRobotCleanService);
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('nogolines') === -1)
this.services.push(this.vacuumRobotNoGoLinesService);
if (this.hiddenServices.indexOf('extracare') === -1)
this.services.push(this.vacuumRobotExtraCareService);
if (this.hiddenServices.indexOf('schedule') === -1)
this.services.push(this.vacuumRobotScheduleService);
}
else
{
this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).on('set', (on, serviceCallback) =>
{
this.setClean(on, serviceCallback, this.boundary)
});
this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).on('get', (serviceCallback) =>
{
this.getClean(serviceCallback, this.boundary);
});
this.services.push(this.vacuumRobotCleanBoundaryService);
}
return this.services;
},
getClean: function (callback, boundary)
{
this.updateRobot((error, result) =>
{
let cleaning;
if (typeof boundary === 'undefined')
{
cleaning = this.robot.canPause;
}
else
{
cleaning = this.robot.canPause && (this.robot.cleaningBoundaryId === boundary.id)
}
debug(this.name + ": Cleaning is " + (cleaning ? 'ON' : 'OFF'));
callback(false, cleaning);
});
},
setClean: function (on, callback, boundary)
{
this.updateRobot((error, result) =>
{
// Start
if (on)
{
debug(typeof boundary);
debug(boundary);
// No room given or same room
if (typeof boundary === 'undefined' || this.robot.cleaningBoundaryId === boundary.id)
{
// Resume cleaning
if (this.robot.canResume)
{
debug(this.name + ": Resume cleaning");
this.robot.resumeCleaning(callback);
}
// Start cleaning
else if (this.robot.canStart)
{
this.clean(callback, boundary);
}
// Cannot start
else
{
debug(this.name + ": Cannot start, maybe already cleaning");
callback();
}
}
// Different room given
else
{
// Return to dock
if (this.robot.canPause || this.robot.canResume)
{
debug(this.name + ": Returning to dock to start cleaning of new room");
this.setGoToDock(true, (error, result) =>
{
this.nextRoom = boundary;
setTimeout(() =>
{
this.clean(callback, boundary);
}, 1000);
});
}
// Start new cleaning of new room
else
{
this.clean(callback, boundary);
}
}
}
// Stop
else
{
if (this.robot.canPause)
{
debug(this.name + ": Pause cleaning");
this.robot.pauseCleaning(callback);
}
else
{
debug(this.name + ": Already paused");
callback();
}
}
});
},
clean: function (callback, boundary)
{
// Start automatic update while cleaning
if (this.refresh === 'auto')
{
setTimeout(() =>
{
clearTimeout(this.timer);
this.updateRobotTimer();
}, 60 * 1000);
}
let eco = this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).value;
let extraCare = this.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).value;
let nogoLines = this.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).value;
let room = (typeof boundary === 'undefined') ? '' : boundary.name;
debug(this.name + ": Start cleaning (" + room + " eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ")");
// Normal cleaning
if (typeof boundary === 'undefined')
{
this.robot.startCleaning(
eco,
extraCare ? 2 : 1,
nogoLines,
(error, result) =>
{
if (error)
{
this.log.error("Cannot start cleaning. " + error + ": " + JSON.stringify(result));
callback(true);
}
else
{
callback();
}
});
}
// Room cleaning
else
{
this.robot.startCleaningBoundary(eco, extraCare, boundary.id, (error, result) =>
{
if (error)
{
this.log.error("Cannot start room cleaning. " + error + ": " + JSON.stringify(result));
callback(true);
}
else
{
callback();
}
});
}
},
getGoToDock: function (callback)
{
callback(false, false);
},
setGoToDock: function (on, callback)
{
let that = this;
this.updateRobot(function (error, result)
{
if (on)
{
if (that.robot.canPause)
{
debug(that.name + ": Pause cleaning to go to dock");
that.robot.pauseCleaning(function (error, result)
{
setTimeout(function ()
{
debug(that.name + ": Go to dock");
that.robot.sendToBase(callback);
}, 1000);
});
}
else if (that.robot.canGoToBase)
{
debug(that.name + ": Go to dock");
that.robot.sendToBase(callback);
}
else
{
that.log.warn(that.name + ": Can't go to dock at the moment");
callback();
}
}
else
{
callback();
}
});
},
getEco: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Eco mode is " + (that.robot.eco ? 'ON' : 'OFF'));
callback(false, that.robot.eco);
});
},
setEco: function (on, callback)
{
this.robot.eco = on;
debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Eco mode ");
callback();
},
getNoGoLines: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Nogo Lines are " + (that.robot.eco ? 'ON' : 'OFF'));
callback(false, that.robot.noGoLines ? 1 : 0);
});
},
setNoGoLines: function (on, callback)
{
this.robot.noGoLines = on;
debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Nogo lines ");
callback();
},
getExtraCare: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Extra Care Navigation is " + (that.robot.navigationMode == 2 ? 'ON' : 'OFF'));
callback(false, that.robot.navigationMode == 2 ? 1 : 0);
});
},
setExtraCare: function (on, callback)
{
this.robot.navigationMode = on ? 2 : 1;
debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Extra Care Navigation ");
callback();
},
getSchedule: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Schedule is " + (that.robot.eco ? 'ON' : 'OFF'));
callback(false, that.robot.isScheduleEnabled);
});
},
setSchedule: function (on, callback)
{
let that = this;
this.updateRobot(function (error, result)
{
if (on)
{
debug(that.name + ": Enabled Schedule");
that.robot.enableSchedule(callback);
}
else
{
debug(that.name + ": Disabled Schedule");
that.robot.disableSchedule(callback);
}
});
},
getDock: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Is " + (that.robot.isDocked ? '' : 'not ') + "docked");
callback(false, that.robot.isDocked ? 1 : 0);
});
},
getBatteryLevel: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Battery is at " + that.robot.charge + "%");
callback(false, that.robot.charge);
});
},
getBatteryChargingState: function (callback)
{
let that = this;
this.updateRobot(function ()
{
debug(that.name + ": Is " + (that.robot.isCharging ? '' : 'not ') + "charging");
callback(false, that.robot.isCharging);
});
},
updateRobot: function (callback)
{
let that = this;
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000)
{
callback();
}
else
{
debug(this.name + ": Updating robot state");
this.robot.getState(function (error, result)
{
if (error)
{
that.log.error("Cannot update robot. Check if robot is online. " + error);
}
that.lastUpdate = new Date();
callback();
});
}
},
updateRobotTimer: function ()
{
this.updateRobot((error, result) =>
{
if (!this.boundary)
{
// only update these values if the state is different from the current one, otherwise we might accidentally start an action
if (this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).value !== this.robot.canPause)
{
this.vacuumRobotCleanService.setCharacteristic(Characteristic.On, this.robot.canPause);
}
// dock switch is on (dock not seen before) and dock has just been seen -> turn switch off
if (this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value == true && this.robot.dockHasBeenSeen)
{
this.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, false);
}
if (this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled)
{
this.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, this.robot.isScheduleEnabled);
}
// no commands here, values can be updated without problems
this.vacuumRobotDockStateService.setCharacteristic(Characteristic.OccupancyDetected, this.robot.isDocked ? 1 : 0);
this.vacuumRobotEcoService.setCharacteristic(Characteristic.On, this.robot.eco);
this.vacuumRobotNoGoLinesService.setCharacteristic(Characteristic.On, this.robot.noGoLines);
this.vacuumRobotExtraCareService.setCharacteristic(Characteristic.On, this.robot.navigationMode == 2 ? true : false);
}
else
{
if (this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).value !== this.robot.canPause)
{
this.vacuumRobotCleanBoundaryService.setCharacteristic(Characteristic.On, this.robot.canPause);
}
}
this.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, this.robot.charge);
this.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, this.robot.isCharging);
// Robot has a next room to clean in queue
if (this.nextRoom !== null && this.robot.isDocked)
{
this.clean((error, result) =>
{
this.nextRoom = null;
}, this.nextRoom);
}
// robot is currently cleaning, refresh is set to auto or specific interval -> continue updating
if (this.robot.canPause && this.refresh !== 0)
{
let refreshTime = this.refresh === 'auto' ? 60 : this.refresh;
debug(this.name + ": Updating state in background every " + refreshTime + " seconds while cleaning");
this.timer = setTimeout(this.updateRobotTimer.bind(this), refreshTime * 1000);
}
// robot is not cleaning, but a specific refresh interval is set -> continue updating
else if (this.refresh !== 'auto' && this.refresh !== 0)
{
debug(this.name + ": Updating state in background every " + this.refresh + " seconds (user setting)");
this.timer = setTimeout(this.updateRobotTimer.bind(this), this.refresh * 1000);
}
// robot is not cleaning, no specific refresh interval is set -> stop updating
else
{
debug(this.name + ": Disabled Background Updates");
}
});
},
};

23
config.schema.json Normal file
View File

@@ -0,0 +1,23 @@
{
"pluginAlias": "NeatoVacuumRobot",
"pluginType": "platform",
"headerDisplay": "For Advanced settings like Refresh time interval or Disabled switches/sensors. [Check Here](https://github.com/naofireblade/homebridge-neato#readme)",
"schema": {
"type": "object",
"properties": {
"email": {
"title": "email",
"type": "string",
"required": true,
"format": "email",
"description": "Your Email Address"
},
"password": {
"title": "password",
"type": "string",
"required": true,
"description": "Your Password"
}
}
}
}

333
index.js
View File

@@ -1,223 +1,164 @@
"use strict";
var inherits = require('util').inherits;
var botvac = require('node-botvac');
let inherits = require('util').inherits,
debug = require('debug')('homebridge-neato'),
botvac = require('node-botvac'),
var Service, Characteristic;
Service,
Characteristic;
module.exports = function (homebridge) {
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.lastUpdate = null;
this.robot = null;
if ('refresh' in config && config['refresh'] !== 'auto')
{
// parse config parameter
this.refresh = parseInt(config['refresh']);
// must be integer and positive
this.refresh = (typeof this.refresh !== 'number' || (this.refresh % 1) !== 0 || this.refresh < 0) ? 60 : this.refresh;
// minimum 60s to save some load on the neato servers
this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh;
}
// default auto
else
{
this.refresh = 'auto';
}
debug("Refresh is set to: " + this.refresh);
}
NeatoVacuumRobot.prototype = {
identify: function (callback) {
this.log("Identify requested");
callback();
},
getServices: function () {
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Neato Robotics")
.setCharacteristic(Characteristic.Model, this.name)
.setCharacteristic(Characteristic.SerialNumber, this.serial);
this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean");
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.clean.bind(this));
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
this.vacuumRobotDockService = new Service.Switch(this.name + " Dock", "dock");
this.vacuumRobotDockService.getCharacteristic(Characteristic.On).on('set', this.dock.bind(this));
this.vacuumRobotDockService.getCharacteristic(Characteristic.On).on('get', this.getDock.bind(this));
this.vacuumRobotEcoService = new Service.Switch(this.name + " Eco Mode", "eco");
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('set', this.eco.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.schedule.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.ChargingState).on('get', this.getBatteryChargingState.bind(this));
return [this.informationService, this.vacuumRobotCleanService, this.vacuumRobotDockService, this.vacuumRobotEcoService,
this.vacuumRobotScheduleService, this.vacuumRobotBatteryService];
},
clean: function (on, callback) {
let that = this;
if (on) {
this.getState(function (error, result) {
that.log(that.robot);
if (that.robot.canResume === true) {
that.log("Resume cleaning");
that.robot.resumeCleaning(function (error, result) {
that.log(result);
});
}
else {
that.log("Start cleaning");
that.robot.startCleaning(that.robot.eco, function (error, result) {
that.log(result);
});
}
});
}
else {
this.log("Pause cleaning");
this.robot.pauseCleaning(false, function (error, result) {
that.log(result);
});
}
callback();
},
dock: function (on, callback) {
let that = this;
that.log(that.robot);
if (on) {
that.log("Send to dock");
that.robot.sendToBase(false, function (error, result) {
that.log(result);
});
}
callback();
},
eco: function (on, callback) {
this.log(on ? "Enable eco mode" : "Disable eco mode");
this.robot.eco = on;
callback();
},
schedule: function (on, callback) {
if (on) {
this.log("Enable schedule");
this.robot.enableSchedule(false, function (error, result) {
onsole.log(result);
});
}
else {
this.log("Disable schedule");
this.robot.disableSchedule(false, function (error, result) {
onsole.log(result);
});
}
callback();
},
getClean: function(callback) {
let that = this;
this.getState(function (error, result) {
that.log("Is cleaning: " + that.robot.canPause);
callback(false, that.robot.canPause);
});
},
getDock: function(callback) {
let that = this;
this.getState(function (error, result) {
that.log("Can go to dock: " + that.robot.canGoToBase);
that.log("Is docked: " + that.robot.isDocked);
callback(false, that.robot.isDocked);
});
},
getEco: function(callback) {
let that = this;
this.getState(function (error, result) {
that.log("Eco mode: " + that.robot.eco);
callback(false, that.robot.eco);
});
},
getSchedule: function(callback) {
let that = this;
this.getState(function (error, result) {
that.log("Schedule: " + that.robot.isScheduleEnabled);
callback(false, that.robot.isScheduleEnabled);
});
},
getBatteryLevel: function(callback) {
let that = this;
this.getState(function (error, result) {
that.log("Battery: " + that.robot.charge);
callback(false, that.robot.charge);
});
},
getBatteryChargingState: function(callback) {
let that = this;
this.getState(function (error, result) {
that.log("Is charging: " + that.robot.isCharging);
callback(false, that.robot.isCharging);
});
},
getState: function(callback) {
let that = this;
if (this.robot === null)
NeatoVacuumRobotPlatform.prototype = {
accessories: function (callback)
{
let accessories = [];
let platform = this;
platform.boundaryNames = [];
this.getRobots(function ()
{
this.getRobot(function (error, result) {
that._getState(callback);
});
}
else {
that._getState(callback);
}
if (platform.robots)
{
platform.robots.forEach((robot, i) =>
{
platform.log("Found robot #" + (i + 1) + " named \"" + robot.name + "\" with serial \"" + robot._serial + "\"");
let NeatoVacuumRobotAccessory = require('./accessories/neatVacuumRobot')(Service, Characteristic);
let robotAccessory = new NeatoVacuumRobotAccessory(robot, platform);
accessories.push(robotAccessory);
if (robot.maps)
{
robot.maps.forEach((map) =>
{
if (map.boundaries)
{
map.boundaries.forEach((boundary) =>
{
if (boundary.type === "polygon")
{
accessories.push(new NeatoVacuumRobotAccessory(robot, platform, boundary))
}
})
}
})
}
})
}
callback(accessories);
});
},
_getState: function(callback) {
if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) {
//this.log("Get state (cached)");
callback();
}
else {
//this.log("Get state (new)");
let that = this;
this.robot.getState(function (error, result) {
that.lastUpdate = new Date();
callback();
});
}
},
getRobot: function(callback) {
//this.log("Get robot");
getRobots: function (callback)
{
debug("Loading your robots");
let client = new botvac.Client();
let that = this;
client.authorize(this.email, this.password, false, function (error) {
if (error) {
client.authorize(this.email, this.password, false, (error) =>
{
if (error)
{
that.log(error);
that.log.error("Can't log on to neato cloud. Please check your internet connection and your credentials. Try again later if the neato servers have issues.");
callback();
}
else {
client.getRobots(function (error, robots) {
if (error) {
else
{
client.getRobots((error, robots) =>
{
if (error)
{
that.log(error);
}
else {
that.robot = robots[0];
that.log("Found robot: " + that.robot.name);
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.");
that.robots = [];
callback();
}
else
{
debug("Found " + robots.length + " robots");
let updatedRobotCount = 0;
that.robots = robots;
that.robots.forEach((robot) =>
{
robot.getPersistentMaps((error, result) =>
{
if (error)
{
that.log("Error updating persistent maps: " + error + ": " + result);
callback();
return;
}
robot.maps = result;
let processedMapCount = 0;
if (robot.maps.length === 0)
{
callback();
}
robot.maps.forEach((map) =>
{
robot.getMapBoundaries(map.id, (error, result) =>
{
if (error)
{
this.log("error getting boundaries: " + error + ": " + result)
}
else
{
map.boundaries = result.boundaries;
}
processedMapCount++;
if (processedMapCount === robot.maps.length)
{
updatedRobotCount++;
if (updatedRobotCount === that.robots.length)
{
callback();
}
}
})
})
})
})
}
}
});
}
});
}
}
};

View File

@@ -1,23 +1,42 @@
{
"name": "homebridge-neato",
"version": "0.1.0",
"description": "control your neato vacuum robot",
"version": "0.7.0-beta.0",
"description": "A Neato vacuum robot plugin for homebridge.",
"license": "MIT",
"keywords": [
"homebridge-plugin"
"homebridge-plugin",
"neato",
"botvac"
],
"engines": {
"node": ">=0.12.0",
"homebridge": ">=0.2.0"
},
"author": {
"name": "Arne Blumentritt"
"name": "Arne Blumentritt",
"url2": "https://github.com/naofireblade"
},
"contributors": [
{
"name": "ghulands",
"url": "https://github.com/ghulands"
},
{
"name": "Berkay",
"url": "https://github.com/btutal"
},
{
"name": "Antoine de Maleprade",
"url": "https://github.com/az0uz"
}
],
"repository": {
"type": "git",
"url": "git://github.com/naofireblade/homebridge-neato.git"
},
"dependencies": {
"node-botvac": ">=0.1.4"
"debug": "^4.1.1",
"node-botvac": ">=0.3.0",
"uuid": "^3.3.2"
}
}