diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c2fb34..7b26d6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,4 +13,11 @@ ## 0.2.1 -* Improved the go to dock command \ No newline at end of file +* Improved the go to dock command + +## 0.3.0 + +* Added periodic refresh of robot state +* Improved go to dock switch to be enabled as soon as possible without manual refresh +* Improved switches to indicate the time an action lasts +* Improved eco mode to not be overwritten by robot state \ No newline at end of file diff --git a/README.md b/README.md index b8211f2..04e4125 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,16 @@ Feel free to leave any feedback [here](https://github.com/naofireblade/homebridg Add the following information to your config file. Change the values for name, email and password. +The parameter **refresh** is optional (default 120 seconds) and adjusts in what interval changes in the robot state will be pushed to homekit (e.g. when starting the robot with the neato app). The minimum is 60 seconds. You can disable this by entering 0. + ```json "accessories": [ { "accessory": "NeatoVacuumRobot", "name": "YourRobot", "email": "YourEmail", - "password": "YourPassword" + "password": "YourPassword", + "refresh": "120" } ] ``` @@ -43,4 +46,4 @@ Add the following information to your config file. Change the values for name, e - BotVac Connected (Firmware 2.2.0) -If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) your experience with this plugin. \ No newline at end of file +If you have another connected neato robot, please [tell me](https://github.com/naofireblade/homebridge-neato/issues) about your experience with this plugin. \ No newline at end of file diff --git a/index.js b/index.js index f262597..5aaa761 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,18 @@ "use strict"; -var inherits = require('util').inherits; -var debug = require('debug')('homebridge-neato'); -var botvac = require('node-botvac'); +var inherits = require('util').inherits, + debug = require('debug')('homebridge-neato'), + botvac = require('node-botvac'), -var Service, Characteristic; + Service, + Characteristic, + vacuumRobotCleanService, + vacuumRobotGoToDockService, + vacuumRobotDockStateService, + vacuumRobotEcoService, + vacuumRobotScheduleService, + vacuumRobotBatteryService, + refresh, + timer module.exports = function (homebridge) { Service = homebridge.hap.Service; @@ -18,8 +27,28 @@ function NeatoVacuumRobot(log, config) { this.email = config['email']; this.password = config['password']; + // load refresh time + // default 120s + this.refresh = ('refresh' in config ? parseInt(config['refresh']) : 120); + // must be integer and positive + if (typeof this.refresh !=='number' || (this.refresh%1)!==0 || this.refresh < 0) { + this.refresh = 0; + } + // minimum 60s + if (this.refresh != 0 && this.refresh < 60) { + this.refresh = 60; + } + + this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean"); + this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock"); + this.vacuumRobotDockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState"); + this.vacuumRobotEcoService = new Service.Switch(this.name + " Eco Mode", "eco"); + this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule"); + this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); + this.lastUpdate = null; this.robot = null; + this.getStateTimer(); } NeatoVacuumRobot.prototype = { @@ -35,26 +64,20 @@ NeatoVacuumRobot.prototype = { .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('set', this.setClean.bind(this)); this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this)); - this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock"); - this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.dock.bind(this)); - this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getCanGoToDock.bind(this)); + this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this)); + this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this)); - this.vacuumRobotDockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState"); - this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDockState.bind(this)); + this.vacuumRobotDockStateService.getCharacteristic(Characteristic.OccupancyDetected).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('set', this.setEco.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('set', this.setSchedule.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)); @@ -62,92 +85,115 @@ NeatoVacuumRobot.prototype = { this.vacuumRobotScheduleService, this.vacuumRobotBatteryService]; }, - clean: function (on, callback) { + setClean: function (on, callback) { let that = this; - if (on) { - this.getState(function (error, result) { - if (that.robot.canResume === true) { - debug("Resume cleaning"); - that.robot.resumeCleaning(function (error, result) { - that.log(result); - }); + this.getStateAndRobot(function (error, result) { + if (on) { + if (that.robot.canResume || that.robot.canStart) { + // wait for robot to start and then disable the old timer and enable it again (with a shorter interval) + setTimeout(function() { + clearTimeout(that.timer); + that.getStateTimer(); + }, 10000); + + if (that.robot.canResume) { + debug("Resume cleaning"); + that.robot.resumeCleaning(callback); + } + else { + debug("Start cleaning"); + that.robot.startCleaning(that.robot.eco, callback); + } } else { - debug("Start cleaning"); - that.robot.startCleaning(that.robot.eco, function (error, result) { - that.log(result); + debug("Already cleaning"); + callback(); + } + } + else { + if (that.robot.canPause) { + debug("Pause cleaning"); + that.robot.pauseCleaning(callback); + } + else { + debug("Already stopped"); + callback(); + } + } + }); + }, + + setGoToDock: function (on, callback) { + let that = this; + this.getStateAndRobot(function (error, result) { + if (on) { + if (that.robot.canPause) { + debug("Pause cleaning to go to dock"); + that.robot.pauseCleaning(function (error, result) { + setTimeout(function() { + debug("Go to dock"); + that.robot.sendToBase(callback); + }, 1000); }); } - }); - } - else { - debug("Pause cleaning"); - this.robot.pauseCleaning(false, function (error, result) { - that.log(result); - }); - } - callback(); + else if (that.robot.canGoToBase) + { + debug("Go to dock"); + that.robot.sendToBase(callback); + } + else { + debug("Can't go to dock at the moment"); + callback(); + } + } else { + // dont allow manual setting the switch to off + setTimeout(function() { + that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, true); + callback(); + },1000); + } + }); }, - dock: function (on, callback) { - let that = this; - if (on) { - debug("Pause cleaning to go to dock"); - this.robot.pauseCleaning(false, function (error, result) { - that.log(result); - }); - setTimeout(function() { - debug("Go to dock"); - that.robot.sendToBase(false, function (error, result) { - that.log(result); - }); - }, 3000); - } - callback(); - }, - - eco: function (on, callback) { + setEco: function (on, callback) { debug(on ? "Enable eco mode" : "Disable eco mode"); this.robot.eco = on; callback(); }, - schedule: function (on, callback) { + setSchedule: function (on, callback) { let that = this; - if (on) { - debug("Enable schedule"); - this.robot.enableSchedule(false, function (error, result) { - that.log(result); - }); - } - else { - debug("Disable schedule"); - this.robot.disableSchedule(false, function (error, result) { - that.log(result); - }); - } - callback(); + this.getStateAndRobot(function (error, result) { + if (on) { + debug("Enable schedule"); + that.robot.enableSchedule(callback); + } + else { + debug("Disable schedule"); + that.robot.disableSchedule(callback); + } + }); }, getClean: function(callback) { let that = this; - this.getState(function (error, result) { + this.getStateAndRobot(function (error, result) { debug("Is cleaning: " + that.robot.canPause); callback(false, that.robot.canPause); }); }, - getCanGoToDock: function(callback) { + getGoToDock: function(callback) { let that = this; - this.getState(function (error, result) { + this.getStateAndRobot(function (error, result) { debug("Can go to dock: " + that.robot.dockHasBeenSeen); callback(false, !that.robot.dockHasBeenSeen); }); }, - getDockState: function(callback) { + getDock: function(callback) { let that = this; - this.getState(function (error, result) { + this.getStateAndRobot(function (error, result) { debug("Is docked: " + that.robot.isDocked); debug(that.robot); callback(false, that.robot.isDocked); @@ -155,16 +201,13 @@ NeatoVacuumRobot.prototype = { }, getEco: function(callback) { - let that = this; - this.getState(function (error, result) { - debug("Eco mode: " + that.robot.eco); - callback(false, that.robot.eco); - }); + // dont load eco here, because we cant save the eco state on the robot + callback(false, this.robot.eco); }, getSchedule: function(callback) { let that = this; - this.getState(function (error, result) { + this.getStateAndRobot(function (error, result) { debug("Schedule: " + that.robot.isScheduleEnabled); callback(false, that.robot.isScheduleEnabled); }); @@ -173,7 +216,7 @@ NeatoVacuumRobot.prototype = { getBatteryLevel: function(callback) { let that = this; - this.getState(function (error, result) { + this.getStateAndRobot(function (error, result) { debug("Battery: " + that.robot.charge); callback(false, that.robot.charge); }); @@ -181,32 +224,32 @@ NeatoVacuumRobot.prototype = { getBatteryChargingState: function(callback) { let that = this; - this.getState(function (error, result) { + this.getStateAndRobot(function (error, result) { debug("Is charging: " + that.robot.isCharging); callback(false, that.robot.isCharging); }); }, - getState: function(callback) { + getStateAndRobot: function(callback) { let that = this; if (this.robot === null) { this.getRobot(function (error, result) { - that._getState(callback); + that.getState(callback); }); } else { - that._getState(callback); - } + that.getState(callback); + } }, - _getState: function(callback) { + getState: function(callback) { if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) { - debug("Get info (cached)"); + debug("Get state (cached)"); callback(); } else { - debug("Get info (new)"); + debug("Get state (new)"); let that = this; this.robot.getState(function (error, result) { that.lastUpdate = new Date(); @@ -215,6 +258,45 @@ NeatoVacuumRobot.prototype = { } }, + getStateTimer: function() { + debug("Timer called"); + let that = this; + this.getStateAndRobot(function (error, result) { + + // only update these values if the state is different from the current one, otherwise we might accidentally start an action + if (that.vacuumRobotCleanService.getCharacteristic(Characteristic.On).value !== that.robot.canPause) { + that.vacuumRobotCleanService.setCharacteristic(Characteristic.On, that.robot.canPause); + } + + if (that.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value !== !that.robot.dockHasBeenSeen) { + that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, !that.robot.dockHasBeenSeen); + } + + if (that.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== that.robot.isScheduleEnabled) { + that.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, that.robot.isScheduleEnabled); + } + + // no commands here, values can be updated without problems + that.vacuumRobotDockStateService.setCharacteristic(Characteristic.OccupancyDetected, that.robot.isDocked); + that.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, that.robot.charge); + that.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, that.robot.isCharging); + + // dont update eco, because we cant write that value onto the robot and dont want it to be overwritten in our plugin + + if (that.robot.canPause) { + debug("Short timer set: 10s"); + that.timer = setTimeout(that.getStateTimer.bind(that), 10 * 1000); + } + else if (that.refresh != 0) { + debug("Long timer set: " + that.refresh + "s"); + that.timer = setTimeout(that.getStateTimer.bind(that), that.refresh * 1000); + } + else { + debug("Disabled timer"); + } + }); + }, + getRobot: function(callback) { debug("Get robot"); let client = new botvac.Client(); diff --git a/package.json b/package.json index ddf7b3a..1c122a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.2.1", + "version": "0.3.0", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [