37 Commits

Author SHA1 Message Date
Arne
9af5fe8a26 Bumped version to v0.7.0 2019-10-12 11:29:10 +02:00
Arne
61b66444ac Fixed missing callback when sending to another room 2019-10-12 11:28:09 +02:00
Arne
7197d78f33 Fixed a bug in spot cleaning 2019-10-11 12:17:24 +02:00
Arne
fa25b0d6a5 Bumped version to v0.7.0-beta.10 2019-09-26 09:08:35 +02:00
Arne
b7c2f82173 Bumped version to v0.7.8-beta.9 2019-09-26 08:59:35 +02:00
Arne
09e19d38cf Refactored zone cleaning and improved debugging 2019-09-26 08:59:17 +02:00
Arne
500ae64e0f Renamed hidden config option (backwards compatible) 2019-09-25 09:57:23 +02:00
Arne
12dd6d4676 Renamed spot repeat button 2019-09-24 20:32:09 +02:00
Arne
5402123340 Bumped version to v0.7.0-beta.8 and enabled find me 2019-09-24 20:09:16 +02:00
Arne
e75686e665 Bumped version to v0.7.0-beta.7 2019-09-24 20:06:20 +02:00
Arne
bef37d88c8 Fixed a bug that crashed the plugin on devices with zone cleaning
Fixed a bug in the update cache timing
2019-09-24 20:06:04 +02:00
Arne
af54046927 Fixed a bug that caused the cleaning of all rooms at once 2019-09-23 12:12:18 +02:00
Arne
1a4308ac40 Bumped version to v0.7.0-beta.6 2019-09-23 11:48:50 +02:00
Arne
46ba5e5f30 Added spot cleaning function with individual spot size (D7) 2019-09-23 11:48:35 +02:00
Arne
b8fa1db8ae Merge pull request #23 from nicoh88/patch-1
Added support for spot cleaning, added repeat and 4x4 mode for spot cleaning
2019-09-23 09:31:33 +02:00
Arne
0e92f09079 Merge branch 'master' into patch-1 2019-09-23 09:12:25 +02:00
Arne
73bc399d64 Bumped version to v0.7.0-beta.5 2019-09-22 22:30:29 +02:00
Arne
e49b4af85a Bumped version to v0.7.0-beta.4 2019-09-22 22:16:58 +02:00
Arne
139f415a42 Prepared find me function and fixed room cleaning exception 2019-09-22 22:16:43 +02:00
Arne
c789598019 Bumped version to v0.7.0-beta.3 2019-09-22 16:08:02 +02:00
Arne
c6def8c0fc Fixed some issues and added more device info 2019-09-22 16:07:35 +02:00
Arne
114ff70d60 Moved update logic to platform to reduce server requests for multiple rooms 2019-09-21 23:43:44 +02:00
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
Nico Hartung
769712405a Update README.md for new spot cleaning 2019-01-03 23:25:38 +01:00
Nico Hartung
7b70d8f076 Added support for spot cleaning, added repeat and 4x4 mode for spot cleaning
Repeat and 4x4 mode are not persistent - node-botvac api not supported. After a reboot of homebridge set it to off/false - use it for spot cleaning in compination with homekit scenes or automations.
2019-01-03 23:23:14 +01:00
8 changed files with 1051 additions and 525 deletions

View File

@@ -94,4 +94,20 @@
## 0.6.2
* Fixed homebridge startup failed when robot does not support mapping
* 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
* Added find me function
* Added spot cleaning function with individual spot size and repeat option
* Added model and firmware information to homekit
* Added logic to be able to change the currently cleaned room
* Improved number of requests when having multiple rooms
* Fixed room switches not taking eco and extraCare mode into account
* Fixed room switches to support pause/resume

View File

@@ -1,31 +1,44 @@
# 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)
[![npm](https://img.shields.io/npm/v/homebridge-neato)](https://www.npmjs.com/package/homebridge-neato)
[![npm](https://img.shields.io/npm/dt/homebridge-neato)](https://www.npmjs.com/package/homebridge-neato?activeTab=versions)
[![GitHub last commit](https://img.shields.io/github/last-commit/naofireblade/homebridge-neato)](https://github.com/naofireblade/homebridge-neato)
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).
If you like this plugin and find it useful, I would be forever grateful for your support:
<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>
Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues).
## Features
- Start and pause cleaning
- House Cleaning
- Eco mode
- Extra care navigation
- Nogo lines
- Zone cleaning* (only D7)
- Spot cleaning
- Individual spot size (only D7)
- Clean twice
- 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
- Find the robot
- Schedule (de)activation
- Robot information
- Battery level
- Charging state
- Dock occupancy
- Model and firmware version
- Automatic or periodic refresh of robot state
- Multiple robots
>*You can also send the robot from one room to another. He will return to the base, wait there some seconds and then starts cleaning the other room.
## 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/).
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
@@ -48,9 +61,9 @@ Add the following information to your config file. Change the values for email a
The following config contains advanced optional settings.
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.
The parameter **refresh** is default set to `auto` and updates the robot state when a cleaning was started via homekit so that you can activate automations based on a successful cleaning. If you want to get robot state updates after starting the cleaning from outside of homekit as well (neato app or schedule), you have to set refresh to a static value in seconds e.g. `120`. You can disable background updates completely by setting this to `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).
The parameter **hidden** accepts a list of switches/sensors that can be hidden from homekit (e.g. `dock`, `dockstate`, `eco`, `nogolines`, `extracare`, `schedule`, `find`, `spot`).
```json
"platforms": [
@@ -59,22 +72,18 @@ The parameter **disabled** accepts a list of switches/sensors that can be disabl
"email": "YourEmail",
"password": "YourPassword",
"refresh": "120",
"disabled": ["dock", "dockstate", "eco", "nogolines", "extracare", "schedule"]
"hidden": ["dock", "dockstate", "eco", "nogolines", "extracare", "schedule", "find", "spot"]
}
]
```
## 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.
The plugin is successfully tested with all Neato Connected Robots.
## 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
- [Antoine de Maleprade](https://github.com/az0uz) for adding the zone cleaning feature
- [DJay](https://github.com/DJay-X) for testing out tons of new beta versions

View File

@@ -0,0 +1,686 @@
const debug = require('debug')('homebridge-neato');
const colors = require('colors');
const CustomUUID = {
SpotCleanWidth: 'A7889A9A-2F27-4293-BEF8-3FE805B36F4E',
SpotCleanHeight: 'CA282DB2-62BF-4325-A1BE-F8BB5478781A',
SpotCleanRepeat: '1E79C603-63B8-4E6A-9CE1-D31D67981831'
};
let Service,
Characteristic,
SpotWidthCharacteristic,
SpotHeightCharacteristic,
SpotRepeatCharacteristic;
module.exports = function (_Service, _Characteristic)
{
Service = _Service;
Characteristic = _Characteristic;
return NeatoVacuumRobotAccessory;
};
function NeatoVacuumRobotAccessory(platform, robotObject)
{
this.platform = platform;
this.log = platform.log;
this.refresh = platform.refresh;
this.hiddenServices = platform.hiddenServices;
this.nextRoom = platform.nextRoom;
this.robotObject = robotObject;
this.robot = robotObject.device;
this.meta = robotObject.meta;
this.availableServices = robotObject.availableServices;
this.boundary = (typeof robotObject.boundary === 'undefined') ? null : robotObject.boundary;
if (this.boundary == null)
{
this.name = this.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.batteryService = new Service.BatteryService("Battery", "battery");
if (this.boundary == null)
{
this.cleanService = new Service.Switch(this.name + " Clean", "clean");
this.goToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock");
this.dockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState");
this.ecoService = new Service.Switch(this.name + " Eco Mode", "eco");
this.noGoLinesService = new Service.Switch(this.name + " NoGo Lines", "noGoLines");
this.extraCareService = new Service.Switch(this.name + " Extra Care", "extraCare");
this.scheduleService = new Service.Switch(this.name + " Schedule", "schedule");
this.findMeService = new Service.Switch(this.name + " Find Me", "findMe");
SpotWidthCharacteristic = require('../characteristics/spotWidth')(Characteristic, CustomUUID);
SpotHeightCharacteristic = require('../characteristics/spotHeight')(Characteristic, CustomUUID);
SpotRepeatCharacteristic = require('../characteristics/spotRepeat')(Characteristic, CustomUUID);
// Spot cleaning with advanced options
if ((typeof this.availableServices.spotCleaning !== 'undefined') && this.availableServices.spotCleaning.includes("basic"))
{
this.spotCleanAdvancedService = new Service.Switch(this.name + " Clean Spot", "cleanSpot");
this.spotCleanAdvancedService.addCharacteristic(SpotRepeatCharacteristic);
this.spotCleanAdvancedService.addCharacteristic(SpotWidthCharacteristic);
this.spotCleanAdvancedService.addCharacteristic(SpotHeightCharacteristic);
}
// Spot cleaning without advanced options
else
{
this.spotCleanSimpleService = new Service.Switch(this.name + " Clean Spot", "cleanSpot");
this.spotCleanSimpleService.addCharacteristic(SpotRepeatCharacteristic);
}
}
else
{
const splitName = this.boundary.name.split(' ');
let serviceName = "Clean the " + this.boundary.name;
if (splitName.length >= 2 && splitName[splitName.length - 2].match(/[']s$/g))
{
serviceName = "Clean " + this.boundary.name;
}
this.cleanService = new Service.Switch(serviceName, "cleanBoundary:" + this.boundary.id);
}
this.log("Added cleaning device named: " + this.name);
}
NeatoVacuumRobotAccessory.prototype = {
identify: function (callback)
{
this.robot.getState((error, result) =>
{
if (error)
{
this.log.error("Error getting robot information: " + error + ": " + result);
}
else
{
this.log("### Robot information ###");
this.log(result);
}
callback();
});
},
getServices: function ()
{
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Neato Robotics")
.setCharacteristic(Characteristic.Model, this.meta.modelName)
.setCharacteristic(Characteristic.SerialNumber, this.robot._serial)
.setCharacteristic(Characteristic.FirmwareRevision, this.meta.firmware)
.setCharacteristic(Characteristic.Name, this.robot.name + (this.boundary == null ? '' : ' - ' + this.boundary.name));
this.cleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this));
this.cleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
this.services = [this.informationService, this.cleanService];
if (this.boundary == null)
{
this.batteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.batteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
this.services.push(this.batteryService);
this.goToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this));
this.goToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this));
this.dockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this));
this.ecoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this));
this.ecoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this));
this.noGoLinesService.getCharacteristic(Characteristic.On).on('set', this.setNoGoLines.bind(this));
this.noGoLinesService.getCharacteristic(Characteristic.On).on('get', this.getNoGoLines.bind(this));
this.extraCareService.getCharacteristic(Characteristic.On).on('set', this.setExtraCare.bind(this));
this.extraCareService.getCharacteristic(Characteristic.On).on('get', this.getExtraCare.bind(this));
this.scheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this));
this.scheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this));
this.findMeService.getCharacteristic(Characteristic.On).on('set', this.setFindMe.bind(this));
this.findMeService.getCharacteristic(Characteristic.On).on('get', this.getFindMe.bind(this));
if (typeof this.spotCleanAdvancedService !== 'undefined')
{
this.spotCleanAdvancedService.getCharacteristic(Characteristic.On).on('set', this.setSpotClean.bind(this));
this.spotCleanAdvancedService.getCharacteristic(Characteristic.On).on('get', this.getSpotClean.bind(this));
this.spotCleanAdvancedService.getCharacteristic(SpotRepeatCharacteristic).on('set', this.setSpotRepeat.bind(this));
this.spotCleanAdvancedService.getCharacteristic(SpotRepeatCharacteristic).on('get', this.getSpotRepeat.bind(this));
this.spotCleanAdvancedService.getCharacteristic(SpotWidthCharacteristic).on('set', this.setSpotWidth.bind(this));
this.spotCleanAdvancedService.getCharacteristic(SpotWidthCharacteristic).on('get', this.getSpotWidth.bind(this));
this.spotCleanAdvancedService.getCharacteristic(SpotHeightCharacteristic).on('set', this.setSpotHeight.bind(this));
this.spotCleanAdvancedService.getCharacteristic(SpotHeightCharacteristic).on('get', this.getSpotHeight.bind(this));
if (this.hiddenServices.indexOf('spot') === -1)
this.services.push(this.spotCleanAdvancedService);
}
else
{
this.spotCleanSimpleService.getCharacteristic(Characteristic.On).on('set', this.setSpotClean.bind(this));
this.spotCleanSimpleService.getCharacteristic(Characteristic.On).on('get', this.getSpotClean.bind(this));
this.spotCleanSimpleService.getCharacteristic(SpotRepeatCharacteristic).on('set', this.setSpotRepeat.bind(this));
this.spotCleanSimpleService.getCharacteristic(SpotRepeatCharacteristic).on('get', this.getSpotRepeat.bind(this));
if (this.hiddenServices.indexOf('spot') === -1)
this.services.push(this.spotCleanSimpleService);
}
// Add optional services
if (this.hiddenServices.indexOf('dock') === -1)
this.services.push(this.goToDockService);
if (this.hiddenServices.indexOf('dockstate') === -1)
this.services.push(this.dockStateService);
if (this.hiddenServices.indexOf('eco') === -1)
this.services.push(this.ecoService);
if (this.hiddenServices.indexOf('nogolines') === -1)
this.services.push(this.noGoLinesService);
if (this.hiddenServices.indexOf('extracare') === -1)
this.services.push(this.extraCareService);
if (this.hiddenServices.indexOf('schedule') === -1)
this.services.push(this.scheduleService);
if (this.hiddenServices.indexOf('find') === -1)
this.services.push(this.findMeService);
}
return this.services;
},
getClean: function (callback)
{
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
let cleaning;
if (this.boundary == null)
{
cleaning = this.robot.canPause;
}
else
{
cleaning = this.robot.canPause && (this.robot.cleaningBoundaryId === this.boundary.id)
}
debug(this.name + ": Cleaning is " + (cleaning ? 'ON'.brightGreen : 'OFF'.red));
callback(false, cleaning);
});
},
setClean: function (on, callback)
{
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Clean " + (this.boundary ? JSON.stringify(this.boundary) : ''));
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
// Start
if (on)
{
// No room given or same room
if (this.boundary == null || this.robot.cleaningBoundaryId === this.boundary.id)
{
// Resume cleaning
if (this.robot.canResume)
{
debug(this.name + ": ## Resume cleaning");
this.robot.resumeCleaning(callback);
}
// Start cleaning
else if (this.robot.canStart)
{
debug(this.name + ": ## Start cleaning");
this.clean(callback, this.boundary);
}
// Cannot start
else
{
debug(this.name + ": Cannot start, maybe already cleaning (expected)");
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 = this.boundary.id;
callback();
});
}
// Start new cleaning of new room
else
{
debug(this.name + ": ## Start cleaning of new room");
this.clean(callback, this.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, spot)
{
// Start automatic update while cleaning
if (this.refresh === 'auto')
{
setTimeout(() =>
{
this.platform.updateRobotTimer(this.robot._serial);
}, 60 * 1000);
}
let eco = this.robotObject.mainAccessory.ecoService.getCharacteristic(Characteristic.On).value;
let extraCare = this.robotObject.mainAccessory.extraCareService.getCharacteristic(Characteristic.On).value;
let nogoLines = this.robotObject.mainAccessory.noGoLinesService.getCharacteristic(Characteristic.On).value;
let room = (this.boundary == null) ? '' : this.boundary.name;
debug(this.name + ": ## Start cleaning (" + (room !== '' ? room + " " : '') + "eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ", spot: " + JSON.stringify(spot) + ")");
// Normal cleaning
if (this.boundary == null && (typeof spot === '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 if (room !== '')
{
this.robot.startCleaningBoundary(eco, extraCare, this.boundary.id, (error, result) =>
{
if (error)
{
this.log.error("Cannot start room cleaning. " + error + ": " + JSON.stringify(result));
callback(true);
}
else
{
callback();
}
});
}
// Spot cleaning
else
{
this.robot.startSpotCleaning(eco, spot.width, spot.height, spot.repeat, extraCare ? 2 : 1, (error, result) =>
{
if (error)
{
this.log.error("Cannot start spot cleaning. " + error + ": " + JSON.stringify(result));
callback(true);
}
else
{
callback();
}
});
}
},
getGoToDock: function (callback)
{
callback(false, false);
},
setGoToDock: function (on, callback)
{
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
if (on)
{
if (this.robot.canPause)
{
debug(this.name + ": ## Pause cleaning to go to dock");
this.robot.pauseCleaning((error, result) =>
{
setTimeout(() =>
{
debug(this.name + ": ## Go to dock");
this.robot.sendToBase(callback);
}, 1000);
});
}
else if (this.robot.canGoToBase)
{
debug(this.name + ": ## Go to dock");
this.robot.sendToBase(callback);
}
else
{
this.log.warn(this.name + ": Can't go to dock at the moment");
callback();
}
}
else
{
callback();
}
});
},
getEco: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Eco Mode is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.eco);
});
},
setEco: function (on, callback)
{
this.robot.eco = on;
debug(this.name + ": " + (on ? "Enabled ".red : "Disabled".red) + " Eco Mode ");
callback();
},
getNoGoLines: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": NoGoLine is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.noGoLines ? 1 : 0);
});
},
setNoGoLines: function (on, callback)
{
this.robot.noGoLines = on;
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " NoGoLine ");
callback();
},
getExtraCare: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Care Nav is " + (this.robot.navigationMode === 2 ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.navigationMode === 2 ? 1 : 0);
});
},
setExtraCare: function (on, callback)
{
this.robot.navigationMode = on ? 2 : 1;
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Care Nav ");
callback();
},
getSchedule: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Schedule is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.isScheduleEnabled);
});
},
setSchedule: function (on, callback)
{
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
if (on)
{
debug(this.name + ": " + "Enabled".brightGreen + " Schedule");
this.robot.enableSchedule(callback);
}
else
{
debug(this.name + ": " + "Disabled".red + " Schedule");
this.robot.disableSchedule(callback);
}
});
},
getFindMe: function (callback)
{
callback(false, false);
},
setFindMe: function (on, callback)
{
if (on)
{
debug(this.name + ": ## Find me");
setTimeout(() =>
{
this.findMeService.setCharacteristic(Characteristic.On, false);
}, 1000);
this.robot.findMe(callback);
}
},
getSpotClean: function (callback)
{
callback();
},
setSpotClean: function (on, callback)
{
let spotCleanService = (typeof this.spotCleanAdvancedService !== 'undefined') ? this.spotCleanAdvancedService : this.spotCleanSimpleService;
let spot = {
width: spotCleanService.getCharacteristic(SpotWidthCharacteristic).value,
height: spotCleanService.getCharacteristic(SpotHeightCharacteristic).value,
repeat: spotCleanService.getCharacteristic(SpotRepeatCharacteristic).value
};
this.platform.updateRobot(this.robot._serial, (error, result) =>
{
// Start
if (on)
{
// Resume cleaning
if (this.robot.canResume)
{
debug(this.name + ": ## Resume (spot) cleaning");
this.robot.resumeCleaning(callback);
}
// Start cleaning
else if (this.robot.canStart)
{
this.clean(callback, spot);
}
// Cannot start
else
{
debug(this.name + ": Cannot start spot cleaning, maybe already cleaning");
callback();
}
}
// Stop
else
{
if (this.robot.canPause)
{
debug(this.name + ": ## Pause cleaning");
this.robot.pauseCleaning(callback);
}
else
{
debug(this.name + ": Already paused");
callback();
}
}
});
},
getSpotWidth: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Spot width is " + this.robot.spotWidth + "cm");
callback(false, this.robot.spotWidth);
});
},
setSpotWidth: function (width, callback)
{
this.robot.spotWidth = width;
debug(this.name + ": Set spot width to " + width + "cm");
callback();
},
getSpotHeight: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Spot height is " + this.robot.spotHeight + "cm");
callback(false, this.robot.spotHeight);
});
},
setSpotHeight: function (height, callback)
{
this.robot.spotHeight = height;
debug(this.name + ": Set spot height to " + height + "cm");
callback();
},
getSpotRepeat: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Spot repeat is " + (this.robot.spotRepeat ? 'ON'.brightGreen : 'OFF'.red));
callback(false, this.robot.spotRepeat);
});
},
setSpotRepeat: function (on, callback)
{
this.robot.spotRepeat = on;
debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Spot repeat");
callback();
},
getDock: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": The Dock is " + (this.robot.isDocked ? "OCCUPIED".brightGreen : "NOT OCCUPIED".red));
callback(false, this.robot.isDocked ? 1 : 0);
});
},
getBatteryLevel: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Battery is " + this.robot.charge + "%");
callback(false, this.robot.charge);
});
},
getBatteryChargingState: function (callback)
{
this.platform.updateRobot(this.robot._serial, () =>
{
debug(this.name + ": Battery is " + (this.robot.isCharging ? "CHARGING".brightGreen : "NOT CHARGING".red));
callback(false, this.robot.isCharging);
});
},
updated: function ()
{
if (this.boundary == null)
{
// only update these values if the state is different from the current one, otherwise we might accidentally start an action
if (this.cleanService.getCharacteristic(Characteristic.On).value !== this.robot.canPause)
{
this.cleanService.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.goToDockService.getCharacteristic(Characteristic.On).value == true && this.robot.dockHasBeenSeen)
{
this.goToDockService.setCharacteristic(Characteristic.On, false);
}
if (this.scheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled)
{
this.scheduleService.setCharacteristic(Characteristic.On, this.robot.isScheduleEnabled);
}
// no commands here, values can be updated without problems
this.dockStateService.setCharacteristic(Characteristic.OccupancyDetected, this.robot.isDocked ? 1 : 0);
this.ecoService.setCharacteristic(Characteristic.On, this.robot.eco);
this.noGoLinesService.setCharacteristic(Characteristic.On, this.robot.noGoLines);
this.extraCareService.setCharacteristic(Characteristic.On, this.robot.navigationMode == 2 ? true : false);
if (typeof this.spotCleanAdvancedService !== 'undefined')
{
this.spotCleanAdvancedService.setCharacteristic(SpotWidthCharacteristic, this.robot.spotWidth);
this.spotCleanAdvancedService.setCharacteristic(SpotHeightCharacteristic, this.robot.spotHeight);
this.spotCleanAdvancedService.setCharacteristic(SpotRepeatCharacteristic, this.robot.spotRepeat);
}
else
{
this.spotCleanSimpleService.setCharacteristic(SpotRepeatCharacteristic, this.robot.spotRepeat);
}
}
this.batteryService.setCharacteristic(Characteristic.BatteryLevel, this.robot.charge);
this.batteryService.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;
debug("## Starting cleaning of next room");
});
}
}
};

View File

@@ -0,0 +1,21 @@
const inherits = require('util').inherits;
module.exports = function (Characteristic, CustomUUID)
{
let SpotHeight = function ()
{
Characteristic.call(this, 'Spot ↕', CustomUUID.SpotCleanHeight);
this.setProps({
format: Characteristic.Formats.INT,
unit: 'cm',
maxValue: 400,
minValue: 100,
minStep: 50,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
inherits(SpotHeight, Characteristic);
return SpotHeight;
};

View File

@@ -0,0 +1,17 @@
const inherits = require('util').inherits;
module.exports = function (Characteristic, CustomUUID)
{
let SpotRepeat = function ()
{
Characteristic.call(this, 'Spot 2x', CustomUUID.SpotCleanRepeat);
this.setProps({
format: Characteristic.Formats.BOOL,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
inherits(SpotRepeat, Characteristic);
return SpotRepeat;
};

View File

@@ -0,0 +1,21 @@
const inherits = require('util').inherits;
module.exports = function (Characteristic, CustomUUID)
{
let SpotWidth = function ()
{
Characteristic.call(this, 'Spot ↔', CustomUUID.SpotCleanWidth);
this.setProps({
format: Characteristic.Formats.INT,
unit: 'cm',
maxValue: 400,
minValue: 100,
minStep: 50,
perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
inherits(SpotWidth, Characteristic);
return SpotWidth;
};

749
index.js
View File

@@ -1,523 +1,278 @@
"use strict";
var inherits = require('util').inherits,
debug = require('debug')('homebridge-neato'),
botvac = require('node-botvac'),
let inherits = require('util').inherits,
debug = require('debug')('homebridge-neato'),
botvac = require('node-botvac'),
Service,
Characteristic;
Service,
Characteristic;
module.exports = function (homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerPlatform("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobotPlatform);
module.exports = function (homebridge)
{
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
homebridge.registerPlatform("homebridge-neato", "NeatoVacuumRobot", NeatoVacuumRobotPlatform);
};
function NeatoVacuumRobotPlatform(log, config) {
this.log = log;
this.serial = "1-3-3-7";
this.email = config['email'];
this.password = config['password'];
this.hiddenServices = ('disabled' in config ? config['disabled'] : '');
function NeatoVacuumRobotPlatform(log, config)
{
this.log = log;
this.serial = "1-3-3-7";
this.email = config['email'];
this.password = config['password'];
this.hiddenServices = '';
this.hiddenServices = ('disabled' in config ? config['disabled'] : this.hiddenServices);
this.hiddenServices = ('hidden' in config ? config['hidden'] : this.hiddenServices);
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);
// Array of real robots and associated robot accessories (incl rooms)
this.robots = [];
this.nextRoom = 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
if (this.refresh > 0 && this.refresh < 60)
{
this.log.warn("Minimum refresh time is 60 seconds to not overload the neato servers");
this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh;
}
}
// default auto
else
{
this.refresh = 'auto';
}
this.log("Refresh is set to: " + this.refresh + (this.refresh !== 'auto' ? ' seconds' : ''));
}
NeatoVacuumRobotPlatform.prototype = {
accessories: function (callback) {
let accessories = [];
accessories: function (callback)
{
debug("Get robots");
let accessories = [];
this.boundaryNames = [];
this.getRobots(() =>
{
this.robots.forEach((robot, i) =>
{
this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial.substring(0,9) + "XXXXXXXXXXXX\"");
let that = this;
this.getRobots(function () {
for (let i = 0; i < that.robots.length; i++) {
that.log("Found robot #" + (i + 1) + " named \"" + that.robots[i].name + "\" with serial \"" + that.robots[i]._serial + "\"");
let robotAccessory = new NeatoVacuumRobotAccessory(that.robots[i], that);
accessories.push(robotAccessory);
if (that.robots[i].maps && that.robots[i].maps.length > 0)
{
that.robots[i].maps.forEach((map) => {
map.boundaries.forEach((boundary) => {
if (boundary.type === "polygon") {
accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary))
}
})
})
}
}
callback(accessories);
});
},
// Start Update Intervall
this.updateRobotTimer(robot.device._serial);
getRobots: function (callback) {
debug("Loading your robots");
let client = new botvac.Client();
let that = this;
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 credentials.");
callback();
} else {
client.getRobots((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.");
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();
}
}
})
})
})
})
}
}
});
}
});
}
};
let NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, Characteristic);
let mainAccessory = new NeatoVacuumRobotAccessory(this, robot);
accessories.push(mainAccessory);
function NeatoVacuumRobotAccessory(robot, platform, boundary) {
this.platform = platform;
this.boundary = boundary;
this.log = platform.log;
this.refresh = platform.refresh;
this.hiddenServices = platform.hiddenServices;
this.robot = robot;
if (!this.boundary) {
this.name = robot.name;
} else {
this.name = this.robot.name + ' - ' + this.boundary.name;
}
this.lastUpdate = null;
robot.mainAccessory = mainAccessory;
robot.roomAccessories = [];
this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery");
// For testing purposes only
// robot.boundary = {name: "Testroom", id: "1"};
// let roomAccessory = new NeatoVacuumRobotAccessory(this, robot);
// accessories.push(roomAccessory);
// robot.roomAccessories.push(roomAccessory);
if (!this.boundary) {
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);
}
if (robot.device.maps)
{
robot.device.maps.forEach((map) =>
{
if (map.boundaries)
{
map.boundaries.forEach((boundary) =>
{
if (boundary.type === "polygon")
{
robot.boundary = boundary;
let roomAccessory = new NeatoVacuumRobotAccessory(this, robot);
accessories.push(roomAccessory);
this.updateRobotTimer();
}
robot.roomAccessories.push(roomAccessory);
}
})
}
})
}
});
callback(accessories);
});
},
getRobots: function (callback)
{
debug("Loading your robots");
let client = new botvac.Client();
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;
});
},
// Login
client.authorize(this.email, this.password, false, (error) =>
{
if (error)
{
this.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: " + error);
callback();
}
else
{
// Get robots
client.getRobots((error, robots) =>
{
if (error)
{
this.log.error("Successful login but can't connect to your neato robot: " + error);
callback();
}
else if (robots.length === 0)
{
this.log.error("Successful login but no robots associated with your account.");
this.robots = [];
callback();
}
else
{
debug("Found " + robots.length + " robots");
let requestedRobot = 0;
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 (!this.boundary) {
this.informationService
.setCharacteristic(Characteristic.Name, this.robot.name)
} else {
this.informationService
.setCharacteristic(Characteristic.Name, this.robot.name + ' - ' + this.boundary.name)
}
robots.forEach((robot) =>
{
// Get Maps for each robot
robot.getPersistentMaps((error, result) =>
{
if (error)
{
this.log.error("Error updating persistent maps: " + error + ": " + result);
callback();
}
else if (result.length === 0)
{
robot.maps = [];
callback();
}
else
{
robot.maps = result;
let requestedMap = 0;
robot.maps.forEach((map) =>
{
// Get Map Boundary Lines
robot.getMapBoundaries(map.id, (error, result) =>
{
if (error)
{
this.log.error("Error getting boundaries: " + error + ": " + result)
}
else
{
map.boundaries = result.boundaries;
}
requestedMap++;
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this));
this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this));
// Robot is completely requested if all maps are requested
if (requestedMap === robot.maps.length)
{
// Get additional information
robot.getState((error, result) =>
{
if (error)
{
this.log.error("Error getting robot meta information: " + error + ": " + result);
callback();
}
else
{
this.robots.push({device: robot, meta: result.meta, availableServices: result.availableServices});
requestedRobot++;
this.services = [this.informationService, this.vacuumRobotBatteryService];
// Initial request is complete if all robots are requested.
if (requestedRobot === robots.length)
{
callback();
}
}
});
}
})
});
}
});
});
}
});
}
});
},
if (!this.boundary) {
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this));
this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this));
updateRobot: function (serial, callback)
{
let robot = this.getRobot(serial);
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this));
this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this));
// Data is up to date
if (typeof (robot.lastUpdate) !== 'undefined' && new Date() - robot.lastUpdate < 2000)
{
callback();
}
else
{
debug(robot.device.name + ": ++ Updating robot state");
robot.lastUpdate = new Date();
robot.device.getState((error, result) =>
{
if (error)
{
this.log.error("Cannot update robot. Check if robot is online. " + error);
}
callback();
});
}
},
this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this));
getRobot(serial)
{
let result;
this.robots.forEach(function (robot)
{
if (robot.device._serial === serial)
{
result = robot;
}
});
return result;
},
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this));
this.vacuumRobotEcoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this));
updateRobotTimer: function (serial)
{
this.updateRobot(serial, () =>
{
let robot = this.getRobot(serial);
// Clear any other overlapping timers for this robot
clearTimeout(robot.timer);
this.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).on('set', this.setNoGoLines.bind(this));
this.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).on('get', this.getNoGoLines.bind(this));
// Tell all accessories of this robot (mainAccessory and roomAccessories) that updated robot data is available
robot.mainAccessory.updated();
robot.roomAccessories.forEach(accessory =>
{
accessory.updated();
});
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);
}
if (this.boundary) {
this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).on('set', (on, serviceCallback) => {
this.setCleanBoundary(this.boundary.id, on, serviceCallback)
});
this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).on('get', (serviceCallback) => {
this.getCleanBoundary(this.boundary.id, serviceCallback);
});
this.services.push(this.vacuumRobotCleanBoundaryService);
}
return this.services;
},
setCleanBoundary: function (boundaryId, on, callback) {
this.updateRobot((error, result) => {
if (on) {
if (this.robot.canStart) {
this.log("Starting to clean: " + boundaryId);
this.robot.startCleaningBoundary(this.eco, this.extraCare, boundaryId, (error, result) => {
if (error) {
this.log(error + ": " + JSON.stringify(result));
}
callback();
});
return
} else {
this.log("Error, robot is already cleaning");
}
} else {
if (this.robot.canPause) {
debug(this.name + ": Pause cleaning");
this.robot.pauseCleaning(callback);
return;
} else {
debug(this.name + ": Already stopped");
}
}
callback();
});
},
getCleanBoundary: function (boundaryId, callback) {
this.updateRobot((error, result) => {
callback(false, this.robot.canPause && (this.robot.cleaningBoundaryId == boundaryId));
});
},
setClean: function (on, callback) {
let that = this;
this.updateRobot(function (error, result) {
if (on) {
if (that.robot.canResume || that.robot.canStart) {
// start extra update robot timer if refresh is set to "auto"
if (that.refresh === 'auto') {
setTimeout(function () {
clearTimeout(that.timer);
that.updateRobotTimer();
}, 60 * 1000);
}
if (that.robot.canResume) {
debug(that.name + ": Resume cleaning");
that.robot.resumeCleaning(callback);
} else {
let eco = that.vacuumRobotEcoService.getCharacteristic(Characteristic.On).value;
let extraCare = that.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).value;
let nogoLines = that.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).value;
debug(that.name + ": Start cleaning (eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ")");
that.robot.startCleaning(
eco,
extraCare ? 2 : 1,
nogoLines,
function (error, result) {
if (error) {
that.log.error(error + ": " + result);
callback(true);
} else {
callback();
}
});
}
} else {
debug(that.name + ": Cant start, maybe already cleaning");
callback();
}
} else {
if (that.robot.canPause) {
debug(that.name + ": Pause cleaning");
that.robot.pauseCleaning(callback);
} else {
debug(that.name + ": Already stopped");
callback();
}
}
});
},
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("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();
}
});
},
setEco: function (on, callback) {
debug(this.name + ": " + (on ? "Enable eco mode" : "Disable eco mode"));
this.robot.eco = on;
callback();
},
setNoGoLines: function (on, callback) {
debug(this.name + ": " + (on ? "Enable nogo lines" : "Disable nogo lines"));
this.robot.noGoLines = on;
callback();
},
setExtraCare: function (on, callback) {
debug(this.name + ": " + (on ? "Enable extra care navigation" : "Disable extra care navigation"));
this.robot.navigationMode = on ? 2 : 1;
callback();
},
setSchedule: function (on, callback) {
let that = this;
this.updateRobot(function (error, result) {
if (on) {
debug(that.name + ": Enable schedule");
that.robot.enableSchedule(callback);
} else {
debug(that.name + ": Disable schedule");
that.robot.disableSchedule(callback);
}
});
},
getClean: function (callback) {
let that = this;
this.updateRobot(function (error, result) {
debug(that.name + ": Is cleaning: " + that.robot.canPause);
callback(false, that.robot.canPause);
});
},
getGoToDock: function (callback) {
callback(false, false);
},
getDock: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Is docked: " + that.robot.isDocked);
callback(false, that.robot.isDocked ? 1 : 0);
});
},
getEco: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Is eco: " + that.robot.eco);
callback(false, that.robot.eco);
});
},
getNoGoLines: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Is nogo lines: " + that.robot.noGoLines);
callback(false, that.robot.noGoLines ? 1 : 0);
});
},
getExtraCare: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Is extra care navigation: " + (that.robot.navigationMode == 2 ? true : false));
callback(false, that.robot.navigationMode == 2 ? 1 : 0);
});
},
getSchedule: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Is schedule: " + that.robot.isScheduleEnabled);
callback(false, that.robot.isScheduleEnabled);
});
},
getBatteryLevel: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Battery: " + that.robot.charge + "%");
callback(false, that.robot.charge);
});
},
getBatteryChargingState: function (callback) {
let that = this;
this.updateRobot(function () {
debug(that.name + ": Is charging: " + that.robot.isCharging);
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(error + ": " + result);
}
that.lastUpdate = new Date();
callback();
});
}
},
updateRobotTimer: function () {
let that = this;
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 (that.vacuumRobotCleanService.getCharacteristic(Characteristic.On).value !== that.robot.canPause) {
that.vacuumRobotCleanService.setCharacteristic(Characteristic.On, that.robot.canPause);
}
// dock switch is on (dock not seen before) and dock has just been seen -> turn switch off
if (that.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value == true && that.robot.dockHasBeenSeen) {
that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, false);
}
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 ? 1 : 0);
that.vacuumRobotEcoService.setCharacteristic(Characteristic.On, that.robot.eco);
that.vacuumRobotNoGoLinesService.setCharacteristic(Characteristic.On, that.robot.noGoLines);
that.vacuumRobotExtraCareService.setCharacteristic(Characteristic.On, that.robot.navigationMode == 2 ? true : false);
} else {
if (this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).value !== that.robot.canPause) {
this.vacuumRobotCleanBoundaryService.setCharacteristic(Characteristic.On, that.robot.canPause);
}
}
that.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, that.robot.charge);
that.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, that.robot.isCharging);
// robot is currently cleaning, update if refresh is set to auto or a specific interval
if (that.robot.canPause && that.refresh !== 0) {
let refreshTime = that.refresh === 'auto' ? 60 : that.refresh;
debug("Updating state in background every " + refreshTime + " seconds while cleaning");
that.timer = setTimeout(that.updateRobotTimer.bind(that), refreshTime * 1000);
}
// robot is not cleaning, but a specific refresh interval is set
else if (that.refresh !== 'auto' && that.refresh !== 0) {
debug("Updating state in background every " + that.refresh + " seconds (user setting)");
that.timer = setTimeout(that.updateRobotTimer.bind(that), that.refresh * 1000);
} else {
debug("Updating state in background disabled");
}
});
},
};
// Periodic refresh interval set in config
if (this.refresh !== 'auto' && this.refresh !== 0)
{
debug(robot.device.name + ": ++ Next background update in " + this.refresh + " seconds");
robot.timer = setTimeout(this.updateRobotTimer.bind(this), this.refresh * 1000, serial);
}
// Auto refresh set in config
else if (this.refresh === 'auto' && robot.device.canPause)
{
debug(robot.device.name + ": ++ Next background update in 60 seconds while cleaning (auto mode)");
robot.timer = setTimeout(this.updateRobotTimer.bind(this), 60 * 1000, serial);
}
// No refresh
else
{
debug(robot.device.name + ": ++ Stopped background updates");
}
});
},
};

View File

@@ -1,6 +1,6 @@
{
"name": "homebridge-neato",
"version": "0.6.2",
"version": "0.7.0",
"description": "A Neato vacuum robot plugin for homebridge.",
"license": "MIT",
"keywords": [
@@ -35,8 +35,9 @@
"url": "git://github.com/naofireblade/homebridge-neato.git"
},
"dependencies": {
"debug": "^2.2.0",
"node-botvac": ">=0.3.0",
"colors": "^1.4.0",
"debug": "^4.1.1",
"node-botvac": ">=0.4.0",
"uuid": "^3.3.2"
}
}