From 7c72aabb25fbc87ce2b671867de7f824e07a1462 Mon Sep 17 00:00:00 2001 From: Berkay Date: Mon, 8 Apr 2019 22:26:30 +0200 Subject: [PATCH 01/31] Add Schema for Config Add schema file for homebridge-config-ui-x and similar plugins --- config.schema.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 config.schema.json diff --git a/config.schema.json b/config.schema.json new file mode 100644 index 0000000..290f674 --- /dev/null +++ b/config.schema.json @@ -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" + } + } + } +} \ No newline at end of file From 442b91c34799644cc2337396e159c24b0d786c4f Mon Sep 17 00:00:00 2001 From: Arne Date: Tue, 9 Apr 2019 12:46:08 +0200 Subject: [PATCH 02/31] Versionbump to v0.5.2 --- CHANGELOG.md | 6 +++++- README.md | 1 + package.json | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4273426..f1802d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,4 +78,8 @@ ## 0.5.1 -* Updated node-botvac dependency to 0.1.7 \ No newline at end of file +* Updated node-botvac dependency to 0.1.7 + +## 0.5.2 + +* Added schema file for use with homebridge-config-ui-x \ No newline at end of file diff --git a/README.md b/README.md index 0024ba0..4a616a4 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,4 @@ The plugin should work with D4 and D6 as well. If you have connected neato robot ## 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 diff --git a/package.json b/package.json index a0a8ad5..12ca776 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.5.1", + "version": "0.5.2", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ @@ -23,4 +23,4 @@ "node-botvac": ">=0.1.7", "debug": "^2.2.0" } -} +} \ No newline at end of file From afe7d690ef43dca52551a8a75df3073c4724e408 Mon Sep 17 00:00:00 2001 From: Antoine de Maleprade Date: Sat, 27 Apr 2019 22:06:07 -0700 Subject: [PATCH 03/31] Added zone cleaning --- index.js | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 5ea07a4..a8c75d9 100644 --- a/index.js +++ b/index.js @@ -102,6 +102,33 @@ function NeatoVacuumRobotAccessory(robot, platform) { this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule"); this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); + this.updatePersistentMaps(() => { + this.vacuumRobotCleanZoneServices = {}; + this.maps.forEach((map) => { + map.boundaries.forEach((boundary) => { + if (boundary.type === "polygone") { + this.vacuumRobotCleanZoneServices[boundary.id] = new Service.Switch(this.name + " Clean the " + boundary.name, "clean"); + this.vacuumRobotCleanZoneServices[boundary.id].getCharacteristic(Characteristic.On).on('set', (on, callback) => { + if(on){ + if(that.robot.canStart) { + this.robot.startCleaningBoundary(this.eco, this.extraCare, boundary.id, (error, result) => { + if (error){ + debug(error+": "+JSON.stringify(result)); + return; + } + callback(); + }) + } else { + debug("Error, robot is already cleaning"); + callback(); + } + } + }) + } + }) + }) + }); + this.updateRobotTimer(); } @@ -371,6 +398,30 @@ NeatoVacuumRobotAccessory.prototype = { } }, + updatePersistentMaps: function(callback) { + this.robot.getPersistentMaps((error, maps) => { + if (error) { + this.log.error(error + ": " + result); + return; + } + this.maps = maps; + let processedMapsCounter = 0 + maps.forEach((map) => { + this.getMapBoundaries(map.id, (error, result) => { + if(error) { + this.log.error(error + ": " + result); + } else { + map.boundaries = result; + } + processedMapsCounter++; + if(processedMapsCounter == this.maps.length) { + callback(); + } + }) + }) + }) + }, + updateRobotTimer: function () { let that = this; this.updateRobot(function (error, result) { @@ -413,4 +464,4 @@ NeatoVacuumRobotAccessory.prototype = { } }); }, -} \ No newline at end of file +} From b87e49e12f2568d85aa7f46fe978d4f290f4d654 Mon Sep 17 00:00:00 2001 From: Antoine de Maleprade Date: Sat, 27 Apr 2019 22:52:49 -0700 Subject: [PATCH 04/31] fix: zone services discovery --- index.js | 59 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/index.js b/index.js index a8c75d9..d16abb3 100644 --- a/index.js +++ b/index.js @@ -102,33 +102,6 @@ function NeatoVacuumRobotAccessory(robot, platform) { this.vacuumRobotScheduleService = new Service.Switch(this.name + " Schedule", "schedule"); this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); - this.updatePersistentMaps(() => { - this.vacuumRobotCleanZoneServices = {}; - this.maps.forEach((map) => { - map.boundaries.forEach((boundary) => { - if (boundary.type === "polygone") { - this.vacuumRobotCleanZoneServices[boundary.id] = new Service.Switch(this.name + " Clean the " + boundary.name, "clean"); - this.vacuumRobotCleanZoneServices[boundary.id].getCharacteristic(Characteristic.On).on('set', (on, callback) => { - if(on){ - if(that.robot.canStart) { - this.robot.startCleaningBoundary(this.eco, this.extraCare, boundary.id, (error, result) => { - if (error){ - debug(error+": "+JSON.stringify(result)); - return; - } - callback(); - }) - } else { - debug("Error, robot is already cleaning"); - callback(); - } - } - }) - } - }) - }) - }); - this.updateRobotTimer(); } @@ -145,7 +118,34 @@ NeatoVacuumRobotAccessory.prototype = { that.log(that.robot); that.robot._serial = _serial; that.robot._secret = _secret; - callback(); + + this.updatePersistentMaps(() => { + this.vacuumRobotCleanZoneServices = {}; + this.maps.forEach((map) => { + map.boundaries.forEach((boundary) => { + if (boundary.type === "polygone") { + this.vacuumRobotCleanZoneServices[boundary.id] = new Service.Switch(this.name + " Clean the " + boundary.name, "clean"); + this.vacuumRobotCleanZoneServices[boundary.id].getCharacteristic (Characteristic.On).on('set', (on, serviceCallback) => { + if(on){ + if(that.robot.canStart) { + this.robot.startCleaningBoundary(this.eco, this.extraCare, boundary.id, (error, result) => { + if (error){ + debug(error+": "+JSON.stringify(result)); + return; + } + serviceCallback(); + }) + } else { + debug("Error, robot is already cleaning"); + serviceCallback(); + } + } + }) + } + }) + }) + callback(); + }); }); }, @@ -194,6 +194,9 @@ NeatoVacuumRobotAccessory.prototype = { if (this.hiddenServices.indexOf('schedule') === -1) this.services.push(this.vacuumRobotScheduleService); + this.vacuumRobotCleanZoneServices.forEach((service) => { + services.push(service); + }) return this.services; }, From 7d824ee0b953f6cd4e328f11c62988bd0e762318 Mon Sep 17 00:00:00 2001 From: Antoine de Maleprade Date: Sun, 28 Apr 2019 17:20:48 -0700 Subject: [PATCH 05/31] Working zone cleaning, implemented multiple accesories to have a room per switch in homekit --- index.js | 374 +++++++++++++++++++++++++++++---------------------- package.json | 5 +- 2 files changed, 217 insertions(+), 162 deletions(-) diff --git a/index.js b/index.js index d16abb3..dca033f 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,8 @@ var inherits = require('util').inherits, Service, Characteristic +const uuidv4 = require('uuid/v4'); + module.exports = function (homebridge) { Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; @@ -39,11 +41,18 @@ NeatoVacuumRobotPlatform.prototype = { this.accessories = []; let that = this; - this.robots = this.getRobots(function () { + this.getRobots(function () { for (var i = 0; i < that.robots.length; i++) { that.log("Found robot #" + (i + 1) + " named \"" + that.robots[i].name + "\" with serial \"" + that.robots[i]._serial + "\""); var robotAccessory = new NeatoVacuumRobotAccessory(that.robots[i], that); that.accessories.push(robotAccessory); + that.robots[i].maps.forEach((map) => { + map.boundaries.forEach((boundary) => { + if (boundary.type === "polygon") { + that.accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) + } + }) + }) } callback(that.accessories); }); @@ -53,14 +62,14 @@ NeatoVacuumRobotPlatform.prototype = { debug("Loading your robots"); let client = new botvac.Client(); let that = this; - client.authorize(this.email, this.password, false, function (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 credentials."); callback(); } else { - client.getRobots(function (error, robots) { + client.getRobots((error, robots) => { if (error) { that.log(error); that.log.error("Successful login but can't connect to your neato robot."); @@ -74,8 +83,36 @@ NeatoVacuumRobotPlatform.prototype = { } else { debug("Found " + robots.length + " robots"); - that.robots = robots; - callback(); + 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 + 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) { + this.log("Discovered Maps: " + JSON.stringify(robot.maps)); + updatedRobotCount++ + if (updatedRobotCount === that.robots.length) { + callback(); + } + } + }) + }) + }) + }) } } }); @@ -84,8 +121,10 @@ NeatoVacuumRobotPlatform.prototype = { } } -function NeatoVacuumRobotAccessory(robot, platform) { +function NeatoVacuumRobotAccessory(robot, platform, boundary) { + this.uuid_base = uuidv4(); this.platform = platform; + this.boundary = boundary; this.log = platform.log; this.refresh = platform.refresh; this.hiddenServices = platform.hiddenServices; @@ -93,15 +132,27 @@ function NeatoVacuumRobotAccessory(robot, platform) { this.name = robot.name; this.lastUpdate = null; - this.vacuumRobotCleanService = new Service.Switch(this.name + " Clean", "clean"); - this.vacuumRobotGoToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock"); - 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"); this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); + 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); + } + this.updateRobotTimer(); } @@ -118,45 +169,23 @@ NeatoVacuumRobotAccessory.prototype = { that.log(that.robot); that.robot._serial = _serial; that.robot._secret = _secret; - - this.updatePersistentMaps(() => { - this.vacuumRobotCleanZoneServices = {}; - this.maps.forEach((map) => { - map.boundaries.forEach((boundary) => { - if (boundary.type === "polygone") { - this.vacuumRobotCleanZoneServices[boundary.id] = new Service.Switch(this.name + " Clean the " + boundary.name, "clean"); - this.vacuumRobotCleanZoneServices[boundary.id].getCharacteristic (Characteristic.On).on('set', (on, serviceCallback) => { - if(on){ - if(that.robot.canStart) { - this.robot.startCleaningBoundary(this.eco, this.extraCare, boundary.id, (error, result) => { - if (error){ - debug(error+": "+JSON.stringify(result)); - return; - } - serviceCallback(); - }) - } else { - debug("Error, robot is already cleaning"); - serviceCallback(); - } - } - }) - } - }) - }) - callback(); - }); }); }, getServices: function () { - this.informationService = new Service.AccessoryInformation(); - this.informationService - .setCharacteristic(Characteristic.Name, this.robot.name) - .setCharacteristic(Characteristic.Manufacturer, "Neato Robotics") - .setCharacteristic(Characteristic.Model, "Coming soon") - .setCharacteristic(Characteristic.SerialNumber, this.robot._serial); + this.informationService = new Service.AccessoryInformation(); + this.informationService + .setCharacteristic(Characteristic.Name, this.robot.name) + .setCharacteristic(Characteristic.Manufacturer, "Neato Robotics") + .setCharacteristic(Characteristic.Model, "Coming soon") + .setCharacteristic(Characteristic.SerialNumber, this.robot._serial); + 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(!this.boundary) { this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this)); this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this)); @@ -177,82 +206,123 @@ NeatoVacuumRobotAccessory.prototype = { this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this)); this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this)); - this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); - this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); + this.services.push(this.vacuumRobotCleanService); - this.services = [this.informationService, this.vacuumRobotCleanService, this.vacuumRobotBatteryService]; if (this.hiddenServices.indexOf('dock') === -1) - this.services.push(this.vacuumRobotGoToDockService); + this.services.push(this.vacuumRobotGoToDockService); if (this.hiddenServices.indexOf('dockstate') === -1) - this.services.push(this.vacuumRobotDockStateService); + this.services.push(this.vacuumRobotDockStateService); if (this.hiddenServices.indexOf('eco') === -1) - this.services.push(this.vacuumRobotEcoService); + this.services.push(this.vacuumRobotEcoService); if (this.hiddenServices.indexOf('nogolines') === -1) - this.services.push(this.vacuumRobotNoGoLinesService); + this.services.push(this.vacuumRobotNoGoLinesService); if (this.hiddenServices.indexOf('extracare') === -1) - this.services.push(this.vacuumRobotExtraCareService); + this.services.push(this.vacuumRobotExtraCareService); if (this.hiddenServices.indexOf('schedule') === -1) - this.services.push(this.vacuumRobotScheduleService); + this.services.push(this.vacuumRobotScheduleService); + } - this.vacuumRobotCleanZoneServices.forEach((service) => { - services.push(service); + 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 this.services; + 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) { + 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); - } + // 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); + 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 { - 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(); - } - }); + 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(); - } + 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(); + } + } }); }, @@ -401,70 +471,54 @@ NeatoVacuumRobotAccessory.prototype = { } }, - updatePersistentMaps: function(callback) { - this.robot.getPersistentMaps((error, maps) => { - if (error) { - this.log.error(error + ": " + result); - return; - } - this.maps = maps; - let processedMapsCounter = 0 - maps.forEach((map) => { - this.getMapBoundaries(map.id, (error, result) => { - if(error) { - this.log.error(error + ": " + result); - } else { - map.boundaries = result; - } - processedMapsCounter++; - if(processedMapsCounter == this.maps.length) { - callback(); - } - }) - }) - }) - }, - updateRobotTimer: function () { - let that = this; - this.updateRobot(function (error, result) { + let that = this; + this.updateRobot((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 (!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); - } + // 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); - } + 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); - that.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, that.robot.charge); - that.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, that.robot.isCharging); + // 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); - // 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"); - } - }); + } 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"); + } + }); }, } diff --git a/package.json b/package.json index 12ca776..903d8b5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "url": "git://github.com/naofireblade/homebridge-neato.git" }, "dependencies": { + "debug": "^2.2.0", "node-botvac": ">=0.1.7", - "debug": "^2.2.0" + "uuid": "^3.3.2" } -} \ No newline at end of file +} From 979dc40ccfba90b335c89199285197ed924be791 Mon Sep 17 00:00:00 2001 From: Antoine de Maleprade Date: Mon, 6 May 2019 20:11:04 -0700 Subject: [PATCH 06/31] fixed uuid reset when restarting homebridge, removed logs --- index.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index dca033f..95b63c4 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,6 @@ var inherits = require('util').inherits, Service, Characteristic -const uuidv4 = require('uuid/v4'); - module.exports = function (homebridge) { Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; @@ -38,23 +36,23 @@ function NeatoVacuumRobotPlatform(log, config) { NeatoVacuumRobotPlatform.prototype = { accessories: function (callback) { - this.accessories = []; + let accessories = []; let that = this; this.getRobots(function () { for (var i = 0; i < that.robots.length; i++) { that.log("Found robot #" + (i + 1) + " named \"" + that.robots[i].name + "\" with serial \"" + that.robots[i]._serial + "\""); var robotAccessory = new NeatoVacuumRobotAccessory(that.robots[i], that); - that.accessories.push(robotAccessory); + accessories.push(robotAccessory); that.robots[i].maps.forEach((map) => { map.boundaries.forEach((boundary) => { if (boundary.type === "polygon") { - that.accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) + accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) } }) }) } - callback(that.accessories); + callback(accessories); }); }, @@ -103,7 +101,6 @@ NeatoVacuumRobotPlatform.prototype = { } processedMapCount++; if(processedMapCount == robot.maps.length) { - this.log("Discovered Maps: " + JSON.stringify(robot.maps)); updatedRobotCount++ if (updatedRobotCount === that.robots.length) { callback(); @@ -122,14 +119,17 @@ NeatoVacuumRobotPlatform.prototype = { } function NeatoVacuumRobotAccessory(robot, platform, boundary) { - this.uuid_base = uuidv4(); this.platform = platform; this.boundary = boundary; this.log = platform.log; this.refresh = platform.refresh; this.hiddenServices = platform.hiddenServices; this.robot = robot; - this.name = robot.name; + if(!this.boundary) { + this.name = robot.name; + } else { + this.name = this.robot.name + ' - ' + this.boundary.name; + } this.lastUpdate = null; this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); @@ -175,10 +175,16 @@ NeatoVacuumRobotAccessory.prototype = { getServices: function () { this.informationService = new Service.AccessoryInformation(); this.informationService - .setCharacteristic(Characteristic.Name, this.robot.name) .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) + } this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); From 8ef24c9d4034d2eb8c49e2267285db7f16b6289b Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 10 Jul 2019 10:27:11 +0200 Subject: [PATCH 07/31] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 903d8b5..cffd2d9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "debug": "^2.2.0", - "node-botvac": ">=0.1.7", + "node-botvac": ">=0.3.0", "uuid": "^3.3.2" } } From 4a9748740041b662fa5757e54050d72e9f52a260 Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 14 Jul 2019 23:35:09 +0200 Subject: [PATCH 08/31] Added contributor and bumped version to v0.6.0 --- README.md | 10 ++++++---- package.json | 19 +++++++++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4a616a4..c47ddcf 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ Feel free to leave any feedback [here](https://github.com/naofireblade/homebridg - Start and pause cleaning - Return to dock -- Toggle schedule -- Toggle eco mode -- Toggle extra care navigation -- Toggle nogo lines +- Scheduling +- Eco mode +- Extra care navigation +- Nogo lines +- Zone cleaning - Get battery info - Get dock info - Periodic refresh of robot state @@ -76,3 +77,4 @@ The plugin should work with D4 and D6 as well. If you have connected neato robot 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 \ No newline at end of file diff --git a/package.json b/package.json index cffd2d9..59450fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.5.2", + "version": "0.6.0", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ @@ -13,8 +13,23 @@ "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" From 3cfde323a83a8b6adfd2429f7e22260003fb528d Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 20 Jul 2019 22:20:31 +0200 Subject: [PATCH 09/31] Added gitignore --- .gitignore | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..066a135 --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# 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 +Thumbs.db +package-lock.json \ No newline at end of file From f795781d2ad044e3157b7a0d5fc34472ea9d05c9 Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 20 Jul 2019 22:27:16 +0200 Subject: [PATCH 10/31] Code cleanup --- index.js | 916 +++++++++++++++++++++++++++---------------------------- 1 file changed, 451 insertions(+), 465 deletions(-) diff --git a/index.js b/index.js index 95b63c4..9770c29 100644 --- a/index.js +++ b/index.js @@ -1,530 +1,516 @@ "use strict"; var inherits = require('util').inherits, - debug = require('debug')('homebridge-neato'), - botvac = require('node-botvac'), + 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); -} + 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'] : ''); + this.log = log; + this.serial = "1-3-3-7"; + this.email = config['email']; + this.password = config['password']; + this.hiddenServices = ('disabled' in config ? config['disabled'] : ''); - 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); + 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); } NeatoVacuumRobotPlatform.prototype = { - accessories: function (callback) { - let accessories = []; + accessories: function (callback) { + let accessories = []; - let that = this; - this.getRobots(function () { - for (var i = 0; i < that.robots.length; i++) { - that.log("Found robot #" + (i + 1) + " named \"" + that.robots[i].name + "\" with serial \"" + that.robots[i]._serial + "\""); - var robotAccessory = new NeatoVacuumRobotAccessory(that.robots[i], that); - accessories.push(robotAccessory); - 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); - }); - }, + 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); + 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); + }); + }, - 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 - 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(); - } - } - }) - }) - }) - }) - } - } - }); - } - }); - } -} + 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; + 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(); + } + } + }) + }) + }) + }) + } + } + }); + } + }); + } +}; 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; + 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; - this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); + this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); - 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 (!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); + } - this.updateRobotTimer(); + 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; - }); - }, + 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(!this.boundary) { - this.informationService - .setCharacteristic(Characteristic.Name, this.robot.name) - } else { - this.informationService - .setCharacteristic(Characteristic.Name, this.robot.name + ' - ' + this.boundary.name) - } + 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) + } - this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); - this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); - this.services = [this.informationService, this.vacuumRobotBatteryService]; + this.services = [this.informationService, this.vacuumRobotBatteryService]; - 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)); + 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)); - this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this)); - this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.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.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.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.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.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.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.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); + 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.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); - } + 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; - }, + 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(); - }); - }, + 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)); - }); - }, + 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) { + 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); - } + // 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(); - } - } - }); - }, + 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(); - } - }); - }, + 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(); - }, + 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(); - }, + 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(); - }, + 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); - } - }); - }, + 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); - }); - }, + 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); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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(); - }); - } - }, + 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) => { + 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); - } + 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); - } + // 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); - } + 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); + // 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); - } - } + } 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); + 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"); - } - }); - }, -} + // 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"); + } + }); + }, +}; From db8305bbee0f2e90307efa8758580cbd5e2f6df2 Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 20 Jul 2019 22:37:27 +0200 Subject: [PATCH 11/31] Fixed homebridge startup failed when robot does not support zone cleaning --- CHANGELOG.md | 10 +++++++++- index.js | 4 ++++ package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1802d1..8675c43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,4 +82,12 @@ ## 0.5.2 -* Added schema file for use with homebridge-config-ui-x \ No newline at end of file +* 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 \ No newline at end of file diff --git a/index.js b/index.js index 9770c29..81f241c 100644 --- a/index.js +++ b/index.js @@ -89,6 +89,10 @@ NeatoVacuumRobotPlatform.prototype = { } robot.maps = result; let processedMapCount = 0; + if (robot.maps.length === 0) + { + callback(); + } robot.maps.forEach((map) => { robot.getMapBoundaries(map.id, (error, result) => { if (error) { diff --git a/package.json b/package.json index 59450fc..340dc7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.6.0", + "version": "0.6.1", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From 1436e4b342af97deaf0c29d9a3c8f95102481b79 Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 31 Jul 2019 10:05:59 +0200 Subject: [PATCH 12/31] Fixed homebridge startup failed when robot does not support mapping --- CHANGELOG.md | 6 +++++- index.js | 15 +++++++++------ package.json | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8675c43..a40fbe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,4 +90,8 @@ ## 0.6.1 -* Fixed homebridge startup failed when robot does not support zone cleaning \ No newline at end of file +* Fixed homebridge startup failed when robot does not support zone cleaning + +## 0.6.2 + +* Fixed homebridge startup failed when robot does not support mapping \ No newline at end of file diff --git a/index.js b/index.js index 81f241c..aa5cf41 100644 --- a/index.js +++ b/index.js @@ -44,13 +44,16 @@ NeatoVacuumRobotPlatform.prototype = { 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); - that.robots[i].maps.forEach((map) => { - map.boundaries.forEach((boundary) => { - if (boundary.type === "polygon") { - accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) - } + 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); }); diff --git a/package.json b/package.json index 340dc7c..53608a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.6.1", + "version": "0.6.2", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From 20c54a02e8aa3c5920a3afd341b52b82afd1a25d Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 14 Aug 2019 22:48:23 +0200 Subject: [PATCH 13/31] Fixed homebridge crash when robot has a map without zones --- index.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index aa5cf41..bb7ea5c 100644 --- a/index.js +++ b/index.js @@ -44,14 +44,15 @@ NeatoVacuumRobotPlatform.prototype = { 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) - { + if (that.robots[i].maps) { that.robots[i].maps.forEach((map) => { - map.boundaries.forEach((boundary) => { - if (boundary.type === "polygon") { - accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) - } - }) + if (map.boundaries) { + map.boundaries.forEach((boundary) => { + if (boundary.type === "polygon") { + accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) + } + }) + } }) } } @@ -307,7 +308,7 @@ NeatoVacuumRobotAccessory.prototype = { nogoLines, function (error, result) { if (error) { - that.log.error(error + ": " + result); + that.log.error("Cannot start cleaning. " + error + ": " + result); callback(true); } else { callback(); @@ -315,7 +316,7 @@ NeatoVacuumRobotAccessory.prototype = { }); } } else { - debug(that.name + ": Cant start, maybe already cleaning"); + debug(that.name + ": Cannot start, maybe already cleaning"); callback(); } } else { @@ -463,7 +464,7 @@ NeatoVacuumRobotAccessory.prototype = { debug(this.name + ": Updating robot state"); this.robot.getState(function (error, result) { if (error) { - that.log.error(error + ": " + result); + that.log.error("Cannot update robot. Check if robot is online. " + error); } that.lastUpdate = new Date(); callback(); From c43a666378689a1857424c836d5cc846c7053601 Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 14 Aug 2019 23:06:03 +0200 Subject: [PATCH 14/31] Fixed homebridge crash when homebridge has no internet connection or the neato servers are offline --- index.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index bb7ea5c..55fac75 100644 --- a/index.js +++ b/index.js @@ -40,21 +40,23 @@ NeatoVacuumRobotPlatform.prototype = { 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.forEach((map) => { - if (map.boundaries) { - map.boundaries.forEach((boundary) => { - if (boundary.type === "polygon") { - accessories.push(new NeatoVacuumRobotAccessory(that.robots[i], that, boundary)) - } - }) - } - }) - } + if (that.robots) { + that.robots.forEach((robot, i) => { + that.log("Found robot #" + (i + 1) + " named \"" + robot.name + "\" with serial \"" + robot._serial + "\""); + let robotAccessory = new NeatoVacuumRobotAccessory(robot, that); + 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, that, boundary)) + } + }) + } + }) + } + }) } callback(accessories); }); @@ -67,7 +69,7 @@ NeatoVacuumRobotPlatform.prototype = { 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."); + 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((error, robots) => { From 54f303394e6690ab0ccbba193f0b03544d931bc0 Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 14 Aug 2019 23:43:03 +0200 Subject: [PATCH 15/31] Fixed homebridge crash when 2 zones have the same name --- index.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 55fac75..1e15b4f 100644 --- a/index.js +++ b/index.js @@ -37,8 +37,8 @@ function NeatoVacuumRobotPlatform(log, config) { NeatoVacuumRobotPlatform.prototype = { accessories: function (callback) { let accessories = []; - let that = this; + that.boundaryNames = []; this.getRobots(function () { if (that.robots) { that.robots.forEach((robot, i) => { @@ -135,6 +135,19 @@ function NeatoVacuumRobotAccessory(robot, platform, boundary) { if (!this.boundary) { 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; From 0338580c0afc631f1879bd1ededb78b7f29a4ab0 Mon Sep 17 00:00:00 2001 From: Arne Date: Wed, 14 Aug 2019 23:45:19 +0200 Subject: [PATCH 16/31] Version 0.6.3 --- CHANGELOG.md | 8 +++++++- package.json | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a40fbe1..2faf85b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,4 +94,10 @@ ## 0.6.2 -* Fixed homebridge startup failed when robot does not support mapping \ No newline at end of file +* 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 \ No newline at end of file diff --git a/package.json b/package.json index 53608a4..11d96de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.6.2", + "version": "0.6.3", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From a3b64b7c53865eed5af6f963e594d5a061c86e03 Mon Sep 17 00:00:00 2001 From: Arne Date: Tue, 17 Sep 2019 22:27:06 +0200 Subject: [PATCH 17/31] Added option to buy me a coffee --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c47ddcf..ec01763 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ 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, I would be very grateful for any support to stay awake while coding: +Buy Me A Coffee + Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues). ## Features From 5139929bce5c037fbd2ac1d3a947393344b2d67a Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 19 Sep 2019 09:37:16 +0200 Subject: [PATCH 18/31] Formatting only --- index.js | 1098 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 623 insertions(+), 475 deletions(-) diff --git a/index.js b/index.js index 1e15b4f..3740344 100644 --- a/index.js +++ b/index.js @@ -1,539 +1,687 @@ "use strict"; var inherits = require('util').inherits, - debug = require('debug')('homebridge-neato'), - botvac = require('node-botvac'), + 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 = ('disabled' in config ? config['disabled'] : ''); - 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); + 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); } NeatoVacuumRobotPlatform.prototype = { - accessories: function (callback) { - let accessories = []; - let that = this; - that.boundaryNames = []; - this.getRobots(function () { - if (that.robots) { - that.robots.forEach((robot, i) => { - that.log("Found robot #" + (i + 1) + " named \"" + robot.name + "\" with serial \"" + robot._serial + "\""); - let robotAccessory = new NeatoVacuumRobotAccessory(robot, that); - 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, that, boundary)) - } - }) - } - }) - } - }) - } - callback(accessories); - }); - }, + accessories: function (callback) + { + let accessories = []; + let that = this; + that.boundaryNames = []; + this.getRobots(function () + { + if (that.robots) + { + that.robots.forEach((robot, i) => + { + that.log("Found robot #" + (i + 1) + " named \"" + robot.name + "\" with serial \"" + robot._serial + "\""); + let robotAccessory = new NeatoVacuumRobotAccessory(robot, that); + 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, that, boundary)) + } + }) + } + }) + } + }) + } + callback(accessories); + }); + }, - 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 internet connection and your credentials. Try again later if the neato servers have issues."); - 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(); - } - } - }) - }) - }) - }) - } - } - }); - } - }); - } + 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 internet connection and your credentials. Try again later if the neato servers have issues."); + 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(); + } + } + }) + }) + }) + }) + } + } + }); + } + }); + } }; -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 { - // 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; +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 + { + // 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"); + this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); - 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 (!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); + } - this.updateRobotTimer(); + 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; - }); - }, + 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 (!this.boundary) { - this.informationService - .setCharacteristic(Characteristic.Name, this.robot.name) - } else { - this.informationService - .setCharacteristic(Characteristic.Name, this.robot.name + ' - ' + this.boundary.name) - } + 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) + } - this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); - this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); - this.services = [this.informationService, this.vacuumRobotBatteryService]; + this.services = [this.informationService, this.vacuumRobotBatteryService]; - 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)); + 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)); - this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this)); - this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.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.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.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.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.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.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.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); + 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.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); - } + 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; - }, + 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(); - }); - }, + 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)); - }); - }, + 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) { + 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); - } + // 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("Cannot start cleaning. " + error + ": " + result); - callback(true); - } else { - callback(); - } - }); - } - } else { - debug(that.name + ": Cannot 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(); - } - } - }); - }, + 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("Cannot start cleaning. " + error + ": " + result); + callback(true); + } + else + { + callback(); + } + }); + } + } + else + { + debug(that.name + ": Cannot 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(); - } - }); - }, + 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(); - }, + 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(); - }, + 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(); - }, + 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); - } - }); - }, + 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); - }); - }, + 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); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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); - }); - }, + 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("Cannot update robot. Check if robot is online. " + error); - } - that.lastUpdate = new Date(); - callback(); - }); - } - }, + 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 () { - let that = this; - this.updateRobot((error, result) => { + 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); - } + 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); - } + // 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); - } + 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); + // 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); - } - } + } + 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); + 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"); - } - }); - }, + // 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"); + } + }); + }, }; From 6f19a189b23c679a7f17b254ab8a8749560e1140 Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 19 Sep 2019 11:25:36 +0200 Subject: [PATCH 19/31] Reworked room cleaning (WIP) --- accessories/neatVacuumRobot.js | 552 +++++++++++++++++++++++++++++++++ index.js | 545 +------------------------------- 2 files changed, 563 insertions(+), 534 deletions(-) create mode 100644 accessories/neatVacuumRobot.js diff --git a/accessories/neatVacuumRobot.js b/accessories/neatVacuumRobot.js new file mode 100644 index 0000000..ff5e649 --- /dev/null +++ b/accessories/neatVacuumRobot.js @@ -0,0 +1,552 @@ +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; + + 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 (!this.boundary) + { + 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 (!this.boundary) + { + this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this)); + this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.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.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); + } + + if (this.boundary) + { + 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 + ": Is cleaning: " + cleaning); + callback(false, cleaning); + }); + }, + + setClean: function (on, callback, boundary) + { + this.updateRobot((error, result) => + { + // Start + if (on) + { + // 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 + { + // Stop current (running or paused) cleaning of old room + if (this.robot.canPause || this.robot.canResume) + { + debug(this.name + ": Stop cleaning to start cleaning of new room"); + this.robot.stopCleaning((error, result) => + { + 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 stopped"); + callback(); + } + } + }); + }, + + clean: function (callback, boundaryId) + { + // 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(that.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 () + { + 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, refresh is set to auto or specific interval -> continue updating + if (that.robot.canPause && that.refresh !== 0) + { + let refreshTime = that.refresh === 'auto' ? 60 : that.refresh; + debug(that.name + ": 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 -> continue updating + else if (that.refresh !== 'auto' && that.refresh !== 0) + { + debug(that.name + ": Updating state in background every " + that.refresh + " seconds (user setting)"); + that.timer = setTimeout(that.updateRobotTimer.bind(that), that.refresh * 1000); + } + // robot is not cleaning, no specific refresh interval is set -> stop updating + else + { + debug(that.name + ": Disabled background updates"); + } + }); + }, +}; \ No newline at end of file diff --git a/index.js b/index.js index 3740344..bc5a3a9 100644 --- a/index.js +++ b/index.js @@ -42,17 +42,20 @@ NeatoVacuumRobotPlatform.prototype = { accessories: function (callback) { let accessories = []; - let that = this; - that.boundaryNames = []; + let platform = this; + platform.boundaryNames = []; this.getRobots(function () { - if (that.robots) + if (platform.robots) { - that.robots.forEach((robot, i) => + platform.robots.forEach((robot, i) => { - that.log("Found robot #" + (i + 1) + " named \"" + robot.name + "\" with serial \"" + robot._serial + "\""); - let robotAccessory = new NeatoVacuumRobotAccessory(robot, that); + 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) => @@ -63,7 +66,7 @@ NeatoVacuumRobotPlatform.prototype = { { if (boundary.type === "polygon") { - accessories.push(new NeatoVacuumRobotAccessory(robot, that, boundary)) + accessories.push(new NeatoVacuumRobotAccessory(robot, platform, boundary)) } }) } @@ -158,530 +161,4 @@ NeatoVacuumRobotPlatform.prototype = { } }); } -}; - -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 - { - // 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 (!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); - } - - 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 (!this.boundary) - { - 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 (!this.boundary) - { - this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this)); - this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.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.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); - } - - 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("Cannot start cleaning. " + error + ": " + result); - callback(true); - } - else - { - callback(); - } - }); - } - } - else - { - debug(that.name + ": Cannot 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("Cannot update robot. Check if robot is online. " + error); - } - 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"); - } - }); - }, -}; +}; \ No newline at end of file From ca2af3968cbe39dae3a625710e1ae5951d08837e Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 19 Sep 2019 11:26:27 +0200 Subject: [PATCH 20/31] Updated debug lib --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 11d96de..19dfce7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.6.3", + "version": "0.6.4", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ @@ -35,7 +35,7 @@ "url": "git://github.com/naofireblade/homebridge-neato.git" }, "dependencies": { - "debug": "^2.2.0", + "debug": "^4.1.1", "node-botvac": ">=0.3.0", "uuid": "^3.3.2" } From 150b8973ee2f463d3ec3211b634fa15a950771fe Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 19 Sep 2019 11:26:42 +0200 Subject: [PATCH 21/31] Changed coffee button --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec01763..3f7ac55 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ 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, I would be very grateful for any support to stay awake while coding: -Buy Me A Coffee +If you like this plugin, I would be very grateful for any support. +Buy Me A Coffee Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues). From 8822670f9b49aabae45e877e2db5d416ce6feeba Mon Sep 17 00:00:00 2001 From: Arne Date: Thu, 19 Sep 2019 12:10:17 +0200 Subject: [PATCH 22/31] Fixed boundary cleaning --- accessories/neatVacuumRobot.js | 2 +- index.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/accessories/neatVacuumRobot.js b/accessories/neatVacuumRobot.js index ff5e649..882a1e6 100644 --- a/accessories/neatVacuumRobot.js +++ b/accessories/neatVacuumRobot.js @@ -256,7 +256,7 @@ NeatoVacuumRobotAccessory.prototype = { }); }, - clean: function (callback, boundaryId) + clean: function (callback, boundary) { // Start automatic update while cleaning if (this.refresh === 'auto') diff --git a/index.js b/index.js index bc5a3a9..2c47494 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,5 @@ "use strict"; -var inherits = require('util').inherits, +let inherits = require('util').inherits, debug = require('debug')('homebridge-neato'), botvac = require('node-botvac'), @@ -143,7 +143,7 @@ NeatoVacuumRobotPlatform.prototype = { map.boundaries = result.boundaries; } processedMapCount++; - if (processedMapCount == robot.maps.length) + if (processedMapCount === robot.maps.length) { updatedRobotCount++; if (updatedRobotCount === that.robots.length) From 6a57eaa57ee4fba4893c21a93532a74b4bcacc03 Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 21 Sep 2019 14:25:49 +0200 Subject: [PATCH 23/31] Bumped version to 0.7.0-beta.0 --- CHANGELOG.md | 8 +++++++- README.md | 8 +++++--- package.json | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2faf85b..1f1175b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,4 +100,10 @@ * 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 \ No newline at end of file +* 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 diff --git a/README.md b/README.md index 3f7ac55..4524f9a 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ 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, I would be very grateful for any support. -Buy Me A Coffee +If you like this plugin, I would be very grateful for your support: + +Buy Me A Coffee Feel free to leave any feedback [here](https://github.com/naofireblade/homebridge-neato/issues). @@ -80,4 +81,5 @@ The plugin should work with D4 and D6 as well. If you have connected neato robot 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 \ No newline at end of file +- [Antoine de Maleprade](https://github.com/az0uz) for adding the zone cleaning feature +- [DJay](https://github.com/DJay-X) for testing new beta versions \ No newline at end of file diff --git a/package.json b/package.json index 19dfce7..8dc485c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.6.4", + "version": "0.7.0-beta.0", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From 0ac882414a21808c7dcddb8f400deece6fdbed2a Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 21 Sep 2019 14:28:31 +0200 Subject: [PATCH 24/31] Added feature to start cleaning of a new room. --- accessories/neatVacuumRobot.js | 65 +++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/accessories/neatVacuumRobot.js b/accessories/neatVacuumRobot.js index 882a1e6..11f7a3f 100644 --- a/accessories/neatVacuumRobot.js +++ b/accessories/neatVacuumRobot.js @@ -19,6 +19,7 @@ function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined) this.refresh = platform.refresh; this.hiddenServices = platform.hiddenServices; this.robot = robot; + this.nextRoom = null; if (typeof boundary === 'undefined') { @@ -220,12 +221,13 @@ NeatoVacuumRobotAccessory.prototype = { // Different room given else { - // Stop current (running or paused) cleaning of old room + // Return to dock if (this.robot.canPause || this.robot.canResume) { - debug(this.name + ": Stop cleaning to start cleaning of new room"); - this.robot.stopCleaning((error, result) => - { + 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); @@ -249,7 +251,7 @@ NeatoVacuumRobotAccessory.prototype = { } else { - debug(this.name + ": Already stopped"); + debug(this.name + ": Already paused"); callback(); } } @@ -488,64 +490,71 @@ NeatoVacuumRobotAccessory.prototype = { 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) + if (this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).value !== this.robot.canPause) { - that.vacuumRobotCleanService.setCharacteristic(Characteristic.On, that.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 (that.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value == true && that.robot.dockHasBeenSeen) + if (this.vacuumRobotGoToDockService.getCharacteristic(Characteristic.On).value == true && this.robot.dockHasBeenSeen) { - that.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, false); + this.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, false); } - if (that.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== that.robot.isScheduleEnabled) + if (this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled) { - that.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, that.robot.isScheduleEnabled); + this.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, this.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); + 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 !== that.robot.canPause) + if (this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).value !== this.robot.canPause) { - this.vacuumRobotCleanBoundaryService.setCharacteristic(Characteristic.On, that.robot.canPause); + this.vacuumRobotCleanBoundaryService.setCharacteristic(Characteristic.On, this.robot.canPause); } } - that.vacuumRobotBatteryService.setCharacteristic(Characteristic.BatteryLevel, that.robot.charge); - that.vacuumRobotBatteryService.setCharacteristic(Characteristic.ChargingState, that.robot.isCharging); + 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 (that.robot.canPause && that.refresh !== 0) + if (this.robot.canPause && this.refresh !== 0) { - let refreshTime = that.refresh === 'auto' ? 60 : that.refresh; - debug(that.name + ": Updating state in background every " + refreshTime + " seconds while cleaning"); - that.timer = setTimeout(that.updateRobotTimer.bind(that), refreshTime * 1000); + 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 (that.refresh !== 'auto' && that.refresh !== 0) + else if (this.refresh !== 'auto' && this.refresh !== 0) { - debug(that.name + ": Updating state in background every " + that.refresh + " seconds (user setting)"); - that.timer = setTimeout(that.updateRobotTimer.bind(that), that.refresh * 1000); + 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(that.name + ": Disabled background updates"); + debug(this.name + ": Disabled background updates"); } }); }, From 7594843bb3f6380c48950a2d418d4bcb90ab496e Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 21 Sep 2019 14:47:41 +0200 Subject: [PATCH 25/31] Fixed boundary issues --- accessories/neatVacuumRobot.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/accessories/neatVacuumRobot.js b/accessories/neatVacuumRobot.js index 11f7a3f..fbdaab2 100644 --- a/accessories/neatVacuumRobot.js +++ b/accessories/neatVacuumRobot.js @@ -100,7 +100,7 @@ NeatoVacuumRobotAccessory.prototype = { .setCharacteristic(Characteristic.Manufacturer, "Neato Robotics") .setCharacteristic(Characteristic.Model, "Coming soon") .setCharacteristic(Characteristic.SerialNumber, this.robot._serial); - if (!this.boundary) + if (typeof this.boundary === "undefined") { this.informationService .setCharacteristic(Characteristic.Name, this.robot.name) @@ -116,10 +116,16 @@ NeatoVacuumRobotAccessory.prototype = { this.services = [this.informationService, this.vacuumRobotBatteryService]; - if (!this.boundary) + if (typeof this.boundary === "undefined") { - this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this)); - this.vacuumRobotCleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this)); + 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)); @@ -153,8 +159,7 @@ NeatoVacuumRobotAccessory.prototype = { if (this.hiddenServices.indexOf('schedule') === -1) this.services.push(this.vacuumRobotScheduleService); } - - if (this.boundary) + else { this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).on('set', (on, serviceCallback) => { @@ -185,7 +190,7 @@ NeatoVacuumRobotAccessory.prototype = { cleaning = this.robot.canPause && (this.robot.cleaningBoundaryId === boundary.id) } - debug(this.name + ": Is cleaning: " + cleaning); + debug(this.name + ": Cleaning is " + (cleaning ? 'ON' : 'OFF')); callback(false, cleaning); }); }, @@ -197,6 +202,8 @@ NeatoVacuumRobotAccessory.prototype = { // Start if (on) { + debug(typeof boundary); + debug(boundary); // No room given or same room if (typeof boundary === 'undefined' || this.robot.cleaningBoundaryId === boundary.id) { @@ -225,7 +232,8 @@ NeatoVacuumRobotAccessory.prototype = { 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.setGoToDock(true, (error, result) => + { this.nextRoom = boundary; setTimeout(() => @@ -274,8 +282,8 @@ NeatoVacuumRobotAccessory.prototype = { 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(that.name + ": Start cleaning (" + room + " eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ")"); + 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') @@ -533,7 +541,8 @@ NeatoVacuumRobotAccessory.prototype = { // Robot has a next room to clean in queue if (this.nextRoom !== null && this.robot.isDocked) { - this.clean((error, result) => { + this.clean((error, result) => + { this.nextRoom = null; }, this.nextRoom); } @@ -554,7 +563,7 @@ NeatoVacuumRobotAccessory.prototype = { // robot is not cleaning, no specific refresh interval is set -> stop updating else { - debug(this.name + ": Disabled background updates"); + debug(this.name + ": Disabled Background Updates"); } }); }, From 114ff70d6035d83458f151b8ac281fc19c40c806 Mon Sep 17 00:00:00 2001 From: Arne Date: Sat, 21 Sep 2019 23:43:44 +0200 Subject: [PATCH 26/31] Moved update logic to platform to reduce server requests for multiple rooms --- ...neatVacuumRobot.js => neatoVacuumRobot.js} | 259 +++++++----------- index.js | 222 ++++++++++----- package.json | 2 +- 3 files changed, 257 insertions(+), 226 deletions(-) rename accessories/{neatVacuumRobot.js => neatoVacuumRobot.js} (59%) diff --git a/accessories/neatVacuumRobot.js b/accessories/neatoVacuumRobot.js similarity index 59% rename from accessories/neatVacuumRobot.js rename to accessories/neatoVacuumRobot.js index fbdaab2..ca7261d 100644 --- a/accessories/neatVacuumRobot.js +++ b/accessories/neatoVacuumRobot.js @@ -11,19 +11,19 @@ module.exports = function (_Service, _Characteristic) return NeatoVacuumRobotAccessory; }; -function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined) +function NeatoVacuumRobotAccessory(robotObject, 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.robot = robotObject.device; this.nextRoom = null; if (typeof boundary === 'undefined') { - this.name = robot.name; + this.name = this.robot.name; } else { @@ -46,7 +46,6 @@ function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined) platform.boundaryNames.push(this.boundary.name); this.name = this.robot.name + ' - ' + this.boundary.name; } - this.lastUpdate = null; this.vacuumRobotBatteryService = new Service.BatteryService("Battery", "battery"); @@ -72,24 +71,22 @@ function NeatoVacuumRobotAccessory(robot, platform, boundary = undefined) 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 () + this.platform.updateRobot(this.robot._serial, () => { // 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; + let _serial = this.robot._serial; + let _secret = this.robot._secret; + this.robot._serial = "*****"; + this.robot._secret = "*****"; + this.log(this.robot); + this.robot._serial = _serial; + this.robot._secret = _secret; + callback(); }); }, @@ -111,10 +108,7 @@ NeatoVacuumRobotAccessory.prototype = { .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]; + this.services = [this.informationService]; if (typeof this.boundary === "undefined") { @@ -144,7 +138,11 @@ NeatoVacuumRobotAccessory.prototype = { this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this)); this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); + this.services.push(this.vacuumRobotCleanService); + this.services.push(this.vacuumRobotBatteryService); if (this.hiddenServices.indexOf('dock') === -1) this.services.push(this.vacuumRobotGoToDockService); @@ -178,7 +176,7 @@ NeatoVacuumRobotAccessory.prototype = { getClean: function (callback, boundary) { - this.updateRobot((error, result) => + this.platform.updateRobot(this.robot._serial, (error, result) => { let cleaning; if (typeof boundary === 'undefined') @@ -197,7 +195,7 @@ NeatoVacuumRobotAccessory.prototype = { setClean: function (on, callback, boundary) { - this.updateRobot((error, result) => + this.platform.updateRobot(this.robot._serial, (error, result) => { // Start if (on) @@ -235,11 +233,6 @@ NeatoVacuumRobotAccessory.prototype = { this.setGoToDock(true, (error, result) => { this.nextRoom = boundary; - - setTimeout(() => - { - this.clean(callback, boundary); - }, 1000); }); } // Start new cleaning of new room @@ -273,8 +266,7 @@ NeatoVacuumRobotAccessory.prototype = { { setTimeout(() => { - clearTimeout(this.timer); - this.updateRobotTimer(); + this.platform.updateRobotTimer(this.robot._serial); }, 60 * 1000); } @@ -330,31 +322,30 @@ NeatoVacuumRobotAccessory.prototype = { setGoToDock: function (on, callback) { - let that = this; - this.updateRobot(function (error, result) + this.platform.updateRobot(this.robot._serial, (error, result) => { if (on) { - if (that.robot.canPause) + if (this.robot.canPause) { - debug(that.name + ": Pause cleaning to go to dock"); - that.robot.pauseCleaning(function (error, result) + debug(this.name + ": Pause cleaning to go to dock"); + this.robot.pauseCleaning((error, result) => { - setTimeout(function () + setTimeout(() => { - debug(that.name + ": Go to dock"); - that.robot.sendToBase(callback); + debug(this.name + ": Go to dock"); + this.robot.sendToBase(callback); }, 1000); }); } - else if (that.robot.canGoToBase) + else if (this.robot.canGoToBase) { - debug(that.name + ": Go to dock"); - that.robot.sendToBase(callback); + debug(this.name + ": Go to dock"); + this.robot.sendToBase(callback); } else { - that.log.warn(that.name + ": Can't go to dock at the moment"); + this.log.warn(this.name + ": Can't go to dock at the moment"); callback(); } } @@ -367,204 +358,152 @@ NeatoVacuumRobotAccessory.prototype = { getEco: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial, () => { - debug(that.name + ": Eco mode is " + (that.robot.eco ? 'ON' : 'OFF')); - callback(false, that.robot.eco); + debug(this.name + ": Eco Mode is " + (this.robot.eco ? 'ON' : 'OFF')); + callback(false, this.robot.eco); }); }, setEco: function (on, callback) { this.robot.eco = on; - debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Eco mode "); + debug(this.name + ": " + (on ? "Enabled " : "Disabled") + " Eco Mode "); callback(); }, getNoGoLines: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial, () => { - debug(that.name + ": Nogo Lines are " + (that.robot.eco ? 'ON' : 'OFF')); - callback(false, that.robot.noGoLines ? 1 : 0); + debug(this.name + ": NoGoLine is " + (this.robot.eco ? 'ON' : 'OFF')); + callback(false, this.robot.noGoLines ? 1 : 0); }); }, setNoGoLines: function (on, callback) { this.robot.noGoLines = on; - debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Nogo lines "); + debug(this.name + ": " + (on ? "Enabled " : "Disabled") + " NoGoLine "); callback(); }, getExtraCare: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial, () => { - debug(that.name + ": Extra Care Navigation is " + (that.robot.navigationMode == 2 ? 'ON' : 'OFF')); - callback(false, that.robot.navigationMode == 2 ? 1 : 0); + debug(this.name + ": Care Nav is " + (this.robot.navigationMode === 2 ? 'ON' : 'OFF')); + callback(false, this.robot.navigationMode === 2 ? 1 : 0); }); }, setExtraCare: function (on, callback) { this.robot.navigationMode = on ? 2 : 1; - debug(this.name + ": " + (on ? "Enabled" : "Disabled") + " Extra Care Navigation "); + debug(this.name + ": " + (on ? "Enabled " : "Disabled") + " Care Nav "); callback(); }, getSchedule: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial,() => { - debug(that.name + ": Schedule is " + (that.robot.eco ? 'ON' : 'OFF')); - callback(false, that.robot.isScheduleEnabled); + debug(this.name + ": Schedule is " + (this.robot.eco ? 'ON' : 'OFF')); + callback(false, this.robot.isScheduleEnabled ); }); }, setSchedule: function (on, callback) { - let that = this; - this.updateRobot(function (error, result) + this.platform.updateRobot(this.robot._serial, (error, result) => { if (on) { - debug(that.name + ": Enabled Schedule"); - that.robot.enableSchedule(callback); + debug(this.name + ": Enabled Schedule"); + this.robot.enableSchedule(callback); } else { - debug(that.name + ": Disabled Schedule"); - that.robot.disableSchedule(callback); + debug(this.name + ": Disabled Schedule"); + this.robot.disableSchedule(callback); } }); }, getDock: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial, () => { - debug(that.name + ": Is " + (that.robot.isDocked ? '' : 'not ') + "docked"); - callback(false, that.robot.isDocked ? 1 : 0); + debug(this.name + ": The Dock is " + (this.robot.isDocked ? '' : 'un ') + "occupied"); + callback(false, this.robot.isDocked ? 1 : 0); }); }, getBatteryLevel: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial, () => { - debug(that.name + ": Battery is at " + that.robot.charge + "%"); - callback(false, that.robot.charge); + debug(this.name + ": Battery is " + this.robot.charge + "%"); + callback(false, this.robot.charge); }); }, getBatteryChargingState: function (callback) { - let that = this; - this.updateRobot(function () + this.platform.updateRobot(this.robot._serial, () => { - debug(that.name + ": Is " + (that.robot.isCharging ? '' : 'not ') + "charging"); - callback(false, that.robot.isCharging); + debug(this.name + ": Battery is " + (this.robot.isCharging ? '' : 'not ') + "charging"); + callback(false, this.robot.isCharging); }); }, - updateRobot: function (callback) + updated: function () { - let that = this; - if (this.lastUpdate !== null && new Date() - this.lastUpdate < 2000) + if (!this.boundary) { - callback(); + // 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 { - debug(this.name + ": Updating robot state"); - this.robot.getState(function (error, result) + if (this.vacuumRobotCleanBoundaryService.getCharacteristic(Characteristic.On).value !== this.robot.canPause) { - if (error) - { - that.log.error("Cannot update robot. Check if robot is online. " + error); - } - that.lastUpdate = new Date(); - callback(); - }); + this.vacuumRobotCleanBoundaryService.setCharacteristic(Characteristic.On, this.robot.canPause); + } } - }, - updateRobotTimer: function () - { - this.updateRobot((error, result) => + 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) { - - if (!this.boundary) + this.clean((error, result) => { - // 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"); - } - }); - }, + this.nextRoom = null; + debug("Starting cleaning of next room"); + }, this.nextRoom); + } + } }; \ No newline at end of file diff --git a/index.js b/index.js index 2c47494..6168954 100644 --- a/index.js +++ b/index.js @@ -21,6 +21,9 @@ function NeatoVacuumRobotPlatform(log, config) this.password = config['password']; this.hiddenServices = ('disabled' in config ? config['disabled'] : ''); + // Array of real robots and associated robot accessories (incl rooms) + this.robots = []; + if ('refresh' in config && config['refresh'] !== 'auto') { // parse config parameter @@ -28,52 +31,62 @@ function NeatoVacuumRobotPlatform(log, config) // 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; + 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'; } - debug("Refresh is set to: " + this.refresh); + this.log("Refresh is set to: " + this.refresh + (this.refresh !== 'auto' ? ' seconds' : '')); } NeatoVacuumRobotPlatform.prototype = { accessories: function (callback) { + debug("##############################################"); + debug("################# GET ROBOTS #################"); + debug("##############################################"); let accessories = []; - let platform = this; - platform.boundaryNames = []; - this.getRobots(function () + this.boundaryNames = []; + this.getRobots(() => { - if (platform.robots) + this.robots.forEach((robot, i) => { - platform.robots.forEach((robot, i) => + this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial + "\""); + this.updateRobotTimer(robot.device._serial); + + let NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, Characteristic); + let mainAccessory = new NeatoVacuumRobotAccessory(robot, this); + accessories.push(mainAccessory); + + robot.mainAccessory = mainAccessory; + robot.roomAccessories = []; + + if (robot.device.maps) { - 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.device.maps.forEach((map) => { - robot.maps.forEach((map) => + if (map.boundaries) { - if (map.boundaries) + map.boundaries.forEach((boundary) => { - map.boundaries.forEach((boundary) => + if (boundary.type === "polygon") { - if (boundary.type === "polygon") - { - accessories.push(new NeatoVacuumRobotAccessory(robot, platform, boundary)) - } - }) - } - }) - } - }) - } + let roomAccessory = new NeatoVacuumRobotAccessory(robot, this, boundary); + accessories.push(roomAccessory); + + robot.roomAccessories.push(roomAccessory); + } + }) + } + }) + } + }); callback(accessories); }); }, @@ -82,83 +95,162 @@ NeatoVacuumRobotPlatform.prototype = { { debug("Loading your robots"); let client = new botvac.Client(); - let that = this; + + // Login 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."); + 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) { - that.log(error); - that.log.error("Successful login but can't connect to your neato robot."); + 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 { - if (robots.length === 0) + debug("Found " + robots.length + " robots"); + let requestedRobot = 0; + + robots.forEach((robot) => { - 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) => + // Get Maps for each robot + robot.getPersistentMaps((error, result) => { - 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 { - if (error) - { - that.log("Error updating persistent maps: " + error + ": " + result); - callback(); - return; - } robot.maps = result; - let processedMapCount = 0; - if (robot.maps.length === 0) - { - callback(); - } + let requestedMap = 0; robot.maps.forEach((map) => { + // Get Map Boundary Lines robot.getMapBoundaries(map.id, (error, result) => { if (error) { - this.log("error getting boundaries: " + error + ": " + result) + this.log.error("Error getting boundaries: " + error + ": " + result) } else { map.boundaries = result.boundaries; } - processedMapCount++; - if (processedMapCount === robot.maps.length) + requestedMap++; + + // Robot is completely requested if all maps are requested + if (requestedMap === robot.maps.length) { - updatedRobotCount++; - if (updatedRobotCount === that.robots.length) + this.robots.push({device: robot}); + requestedRobot++; + + // Initial request is complete if all robots are requested. + if (requestedRobot === robots.length) { callback(); } } }) - }) - }) - }) - } + }); + } + }); + }); } }); } }); - } + }, + + updateRobot: function (serial, callback) + { + let robot = this.getRobot(serial); + + // 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.device.getState(function (error, result) + { + if (error) + { + this.log.error("Cannot update robot. Check if robot is online. " + error); + } + robot.lastUpdate = new Date(); + callback(); + }); + } + }, + + getRobot(serial) + { + let result; + this.robots.forEach(function (robot) + { + if (robot.device._serial === serial) + { + result = robot; + } + }); + return result; + }, + + updateRobotTimer: function (serial) + { + this.updateRobot(serial, () => + { + let robot = this.getRobot(serial); + // Clear any other overlapping timers for this robot + clearTimeout(robot.timer); + + // Tell all accessories of this robot (mainAccessory and roomAccessories) that updated robot data is available + robot.mainAccessory.updated(); + robot.roomAccessories.forEach(accessory => + { + accessory.updated(); + }); + + // 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"); + } + }); + }, }; \ No newline at end of file diff --git a/package.json b/package.json index 8dc485c..0d97a98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.7.0-beta.0", + "version": "0.7.0-beta.1", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From c6def8c0fcebd40464be56a01f539fa287b98414 Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Sep 2019 16:07:35 +0200 Subject: [PATCH 27/31] Fixed some issues and added more device info --- CHANGELOG.md | 2 ++ accessories/neatoVacuumRobot.js | 26 +++++++++++++------------- index.js | 30 ++++++++++++++++++++++-------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f1175b..c6fe131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,3 +107,5 @@ * 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 +* Improved requests for multiple rooms (TODO) +* Added model and firmware information to homekit \ No newline at end of file diff --git a/accessories/neatoVacuumRobot.js b/accessories/neatoVacuumRobot.js index ca7261d..5c55ebf 100644 --- a/accessories/neatoVacuumRobot.js +++ b/accessories/neatoVacuumRobot.js @@ -20,6 +20,7 @@ function NeatoVacuumRobotAccessory(robotObject, platform, boundary = undefined) this.hiddenServices = platform.hiddenServices; this.robot = robotObject.device; this.nextRoom = null; + this.meta = robotObject.meta; if (typeof boundary === 'undefined') { @@ -51,7 +52,7 @@ function NeatoVacuumRobotAccessory(robotObject, platform, boundary = undefined) if (typeof boundary === 'undefined') { - this.vacuumRobotCleanService = new Service.Switch("Clean", "clean"); + 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"); @@ -95,8 +96,9 @@ NeatoVacuumRobotAccessory.prototype = { this.informationService = new Service.AccessoryInformation(); this.informationService .setCharacteristic(Characteristic.Manufacturer, "Neato Robotics") - .setCharacteristic(Characteristic.Model, "Coming soon") - .setCharacteristic(Characteristic.SerialNumber, this.robot._serial); + .setCharacteristic(Characteristic.Model, this.meta.modelName) + .setCharacteristic(Characteristic.SerialNumber, this.robot._serial) + .setCharacteristic(Characteristic.FirmwareRevision, this.meta.firmware); if (typeof this.boundary === "undefined") { this.informationService @@ -200,15 +202,13 @@ NeatoVacuumRobotAccessory.prototype = { // 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"); + debug(this.name + ": ## Resume cleaning"); this.robot.resumeCleaning(callback); } // Start cleaning @@ -229,7 +229,7 @@ NeatoVacuumRobotAccessory.prototype = { // Return to dock if (this.robot.canPause || this.robot.canResume) { - debug(this.name + ": Returning to dock to start cleaning of new room"); + debug(this.name + ": ## Returning to dock to start cleaning of new room"); this.setGoToDock(true, (error, result) => { this.nextRoom = boundary; @@ -247,7 +247,7 @@ NeatoVacuumRobotAccessory.prototype = { { if (this.robot.canPause) { - debug(this.name + ": Pause cleaning"); + debug(this.name + ": ## Pause cleaning"); this.robot.pauseCleaning(callback); } else @@ -275,7 +275,7 @@ NeatoVacuumRobotAccessory.prototype = { 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 + ")"); + debug(this.name + ": ## Start cleaning (" + room + " eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ")"); // Normal cleaning if (typeof boundary === 'undefined') @@ -328,19 +328,19 @@ NeatoVacuumRobotAccessory.prototype = { { if (this.robot.canPause) { - debug(this.name + ": Pause cleaning to go to dock"); + debug(this.name + ": ## Pause cleaning to go to dock"); this.robot.pauseCleaning((error, result) => { setTimeout(() => { - debug(this.name + ": Go to dock"); + debug(this.name + ": ## Go to dock"); this.robot.sendToBase(callback); }, 1000); }); } else if (this.robot.canGoToBase) { - debug(this.name + ": Go to dock"); + debug(this.name + ": ## Go to dock"); this.robot.sendToBase(callback); } else @@ -502,7 +502,7 @@ NeatoVacuumRobotAccessory.prototype = { this.clean((error, result) => { this.nextRoom = null; - debug("Starting cleaning of next room"); + debug("## Starting cleaning of next room"); }, this.nextRoom); } } diff --git a/index.js b/index.js index 6168954..597cc9d 100644 --- a/index.js +++ b/index.js @@ -58,6 +58,8 @@ NeatoVacuumRobotPlatform.prototype = { this.robots.forEach((robot, i) => { this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial + "\""); + + // Start Update Intervall this.updateRobotTimer(robot.device._serial); let NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, Characteristic); @@ -162,14 +164,26 @@ NeatoVacuumRobotPlatform.prototype = { // Robot is completely requested if all maps are requested if (requestedMap === robot.maps.length) { - this.robots.push({device: robot}); - requestedRobot++; - - // Initial request is complete if all robots are requested. - if (requestedRobot === robots.length) + // Get additional information + robot.getState((error, result) => { - callback(); - } + if (error) + { + this.log.error("Error getting robot meta information: " + error + ": " + result); + callback(); + } + else + { + this.robots.push({device: robot, meta: result.meta}); + requestedRobot++; + + // Initial request is complete if all robots are requested. + if (requestedRobot === robots.length) + { + callback(); + } + } + }); } }) }); @@ -194,7 +208,7 @@ NeatoVacuumRobotPlatform.prototype = { else { debug(robot.device.name + ": ++ Updating robot state"); - robot.device.getState(function (error, result) + robot.device.getState((error, result) => { if (error) { From c789598019bfbc0e73cf6f36d84d97e0bda0d051 Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Sep 2019 16:08:02 +0200 Subject: [PATCH 28/31] Bumped version to v0.7.0-beta.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d97a98..ce239c9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.7.0-beta.1", + "version": "0.7.0-beta.3", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From 139f415a4208186bffe121bb21f27f3205f58806 Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Sep 2019 22:16:43 +0200 Subject: [PATCH 29/31] Prepared find me function and fixed room cleaning exception --- accessories/neatoVacuumRobot.js | 41 ++++++++++++++++++++++++++------- index.js | 4 +--- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/accessories/neatoVacuumRobot.js b/accessories/neatoVacuumRobot.js index 5c55ebf..0cb258c 100644 --- a/accessories/neatoVacuumRobot.js +++ b/accessories/neatoVacuumRobot.js @@ -19,6 +19,7 @@ function NeatoVacuumRobotAccessory(robotObject, platform, boundary = undefined) this.refresh = platform.refresh; this.hiddenServices = platform.hiddenServices; this.robot = robotObject.device; + this.mainAccessory = robotObject.mainAccessory; this.nextRoom = null; this.meta = robotObject.meta; @@ -59,6 +60,7 @@ function NeatoVacuumRobotAccessory(robotObject, platform, boundary = undefined) 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"); + this.vacuumRobotFindMeService = new Service.Switch(this.name + " Find Me", "findMe"); } else { @@ -140,6 +142,9 @@ NeatoVacuumRobotAccessory.prototype = { this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this)); this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this)); + this.vacuumRobotFindMeService.getCharacteristic(Characteristic.On).on('set', this.setFindMe.bind(this)); + this.vacuumRobotFindMeService.getCharacteristic(Characteristic.On).on('get', this.getFindMe.bind(this)); + this.vacuumRobotBatteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); this.vacuumRobotBatteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); @@ -158,6 +163,8 @@ NeatoVacuumRobotAccessory.prototype = { this.services.push(this.vacuumRobotExtraCareService); if (this.hiddenServices.indexOf('schedule') === -1) this.services.push(this.vacuumRobotScheduleService); + // if (this.hiddenServices.indexOf('find') === -1) + // this.services.push(this.vacuumRobotFindMeService); } else { @@ -270,10 +277,9 @@ NeatoVacuumRobotAccessory.prototype = { }, 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 eco = this.mainAccessory.vacuumRobotEcoService.getCharacteristic(Characteristic.On).value; + let extraCare = this.mainAccessory.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).value; + let nogoLines = this.mainAccessory.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).value; let room = (typeof boundary === 'undefined') ? '' : boundary.name; debug(this.name + ": ## Start cleaning (" + room + " eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ")"); @@ -406,10 +412,10 @@ NeatoVacuumRobotAccessory.prototype = { getSchedule: function (callback) { - this.platform.updateRobot(this.robot._serial,() => + this.platform.updateRobot(this.robot._serial, () => { debug(this.name + ": Schedule is " + (this.robot.eco ? 'ON' : 'OFF')); - callback(false, this.robot.isScheduleEnabled ); + callback(false, this.robot.isScheduleEnabled); }); }, @@ -430,6 +436,25 @@ NeatoVacuumRobotAccessory.prototype = { }); }, + getFindMe: function (callback) + { + callback(false, false); + }, + + setFindMe: function (on, callback) + { + if (on) + { + debug(this.name + ": ## Find me"); + setTimeout(() => + { + this.vacuumRobotFindMeService.setCharacteristic(Characteristic.On, false); + }, 1000); + + this.robot.findMe(callback); + } + }, + getDock: function (callback) { this.platform.updateRobot(this.robot._serial, () => @@ -473,9 +498,9 @@ NeatoVacuumRobotAccessory.prototype = { this.vacuumRobotGoToDockService.setCharacteristic(Characteristic.On, false); } - if (this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled ) + if (this.vacuumRobotScheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled) { - this.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, this.robot.isScheduleEnabled ); + this.vacuumRobotScheduleService.setCharacteristic(Characteristic.On, this.robot.isScheduleEnabled); } // no commands here, values can be updated without problems diff --git a/index.js b/index.js index 597cc9d..192e3fe 100644 --- a/index.js +++ b/index.js @@ -48,9 +48,7 @@ function NeatoVacuumRobotPlatform(log, config) NeatoVacuumRobotPlatform.prototype = { accessories: function (callback) { - debug("##############################################"); - debug("################# GET ROBOTS #################"); - debug("##############################################"); + debug("Get robots"); let accessories = []; this.boundaryNames = []; this.getRobots(() => From e49b4af85a1f23b7247555e49aaf1c4c6eb9339a Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Sep 2019 22:16:58 +0200 Subject: [PATCH 30/31] Bumped version to v0.7.0-beta.4 --- README.md | 36 ++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4524f9a..098458c 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,20 @@ Feel free to leave any feedback [here](https://github.com/naofireblade/homebridg ## Features - Start and pause cleaning + - Eco mode + - Extra care navigation + - Nogo lines + - Zone 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 +- Find the robot +- Enable/Disable the schedule +- Robot information + - battery level + - charging state + - dock occupancy + - model and firmware version +- Automatic and periodic refresh for notifications +- Multiple robots ## Installation @@ -52,9 +56,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 the cleaning was started via homekit so that you can activate automations after the cleaning is done. If you want to get robot state updates after starting the cleaning from the neato app or a 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 **disabled** accepts a list of switches/sensors that can be disabled in the neato homekit plugin (e.g. `dock`, `dockstate`, `eco`, `schedule`, `find`). ```json "platforms": [ @@ -70,16 +74,16 @@ The parameter **disabled** accepts a list of switches/sensors that can be disabl ## Tested robots -- BotVac Connected (Firmware 2.2.0) +- BotVac Connected - BotVac D3 Connected -- BotVac D5 Connected (Firmware 4.0.0, 4.3.0) +- BotVac D4 Connected +- BotVac D5 Connected +- BotVac D6 Connected - 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 \ No newline at end of file +- [DJay](https://github.com/DJay-X) for testing out new beta versions \ No newline at end of file diff --git a/package.json b/package.json index ce239c9..efd57b6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.7.0-beta.3", + "version": "0.7.0-beta.4", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ From 73bc399d64739aa2bc96aa8ffc228cf4a7e57dac Mon Sep 17 00:00:00 2001 From: Arne Date: Sun, 22 Sep 2019 22:30:29 +0200 Subject: [PATCH 31/31] Bumped version to v0.7.0-beta.5 --- accessories/neatoVacuumRobot.js | 16 +++++++++------- index.js | 4 ++-- package.json | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/accessories/neatoVacuumRobot.js b/accessories/neatoVacuumRobot.js index 0cb258c..b16699e 100644 --- a/accessories/neatoVacuumRobot.js +++ b/accessories/neatoVacuumRobot.js @@ -11,18 +11,20 @@ module.exports = function (_Service, _Characteristic) return NeatoVacuumRobotAccessory; }; -function NeatoVacuumRobotAccessory(robotObject, platform, boundary = undefined) +function NeatoVacuumRobotAccessory(platform, robotObject, boundary = undefined) { this.platform = platform; - this.boundary = boundary; this.log = platform.log; this.refresh = platform.refresh; this.hiddenServices = platform.hiddenServices; + + this.robotObject = robotObject; this.robot = robotObject.device; - this.mainAccessory = robotObject.mainAccessory; - this.nextRoom = null; this.meta = robotObject.meta; + this.boundary = boundary; + this.nextRoom = null; + if (typeof boundary === 'undefined') { this.name = this.robot.name; @@ -277,9 +279,9 @@ NeatoVacuumRobotAccessory.prototype = { }, 60 * 1000); } - let eco = this.mainAccessory.vacuumRobotEcoService.getCharacteristic(Characteristic.On).value; - let extraCare = this.mainAccessory.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).value; - let nogoLines = this.mainAccessory.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).value; + let eco = this.robotObject.mainAccessory.vacuumRobotEcoService.getCharacteristic(Characteristic.On).value; + let extraCare = this.robotObject.mainAccessory.vacuumRobotExtraCareService.getCharacteristic(Characteristic.On).value; + let nogoLines = this.robotObject.mainAccessory.vacuumRobotNoGoLinesService.getCharacteristic(Characteristic.On).value; let room = (typeof boundary === 'undefined') ? '' : boundary.name; debug(this.name + ": ## Start cleaning (" + room + " eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ")"); diff --git a/index.js b/index.js index 192e3fe..d6005c7 100644 --- a/index.js +++ b/index.js @@ -61,7 +61,7 @@ NeatoVacuumRobotPlatform.prototype = { this.updateRobotTimer(robot.device._serial); let NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, Characteristic); - let mainAccessory = new NeatoVacuumRobotAccessory(robot, this); + let mainAccessory = new NeatoVacuumRobotAccessory(this, robot); accessories.push(mainAccessory); robot.mainAccessory = mainAccessory; @@ -77,7 +77,7 @@ NeatoVacuumRobotPlatform.prototype = { { if (boundary.type === "polygon") { - let roomAccessory = new NeatoVacuumRobotAccessory(robot, this, boundary); + let roomAccessory = new NeatoVacuumRobotAccessory(this, robot, boundary); accessories.push(roomAccessory); robot.roomAccessories.push(roomAccessory); diff --git a/package.json b/package.json index efd57b6..ee0673b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-neato", - "version": "0.7.0-beta.4", + "version": "0.7.0-beta.5", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [