diff --git a/index.js b/index.js index 5ea07a4..95b63c4 100644 --- a/index.js +++ b/index.js @@ -36,16 +36,23 @@ function NeatoVacuumRobotPlatform(log, config) { NeatoVacuumRobotPlatform.prototype = { accessories: function (callback) { - this.accessories = []; + let 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); + 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(that.accessories); + callback(accessories); }); }, @@ -53,14 +60,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 +81,35 @@ 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) { + updatedRobotCount++ + if (updatedRobotCount === that.robots.length) { + callback(); + } + } + }) + }) + }) + }) } } }); @@ -84,24 +118,41 @@ NeatoVacuumRobotPlatform.prototype = { } } -function NeatoVacuumRobotAccessory(robot, platform) { +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; - this.name = robot.name; + if(!this.boundary) { + this.name = robot.name; + } else { + this.name = this.robot.name + ' - ' + this.boundary.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,18 +169,29 @@ NeatoVacuumRobotAccessory.prototype = { that.log(that.robot); that.robot._serial = _serial; that.robot._secret = _secret; - callback(); }); }, getServices: function () { - this.informationService = new Service.AccessoryInformation(); - this.informationService + 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) - .setCharacteristic(Characteristic.Manufacturer, "Neato Robotics") - .setCharacteristic(Characteristic.Model, "Coming soon") - .setCharacteristic(Characteristic.SerialNumber, this.robot._serial); + } 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)); @@ -150,79 +212,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); + } - return this.services; + 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) { + 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(); + } + } }); }, @@ -372,45 +478,53 @@ NeatoVacuumRobotAccessory.prototype = { }, 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"); + } + }); }, -} \ No newline at end of file +} diff --git a/package.json b/package.json index 12ca776..cffd2d9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "url": "git://github.com/naofireblade/homebridge-neato.git" }, "dependencies": { - "node-botvac": ">=0.1.7", - "debug": "^2.2.0" + "debug": "^2.2.0", + "node-botvac": ">=0.3.0", + "uuid": "^3.3.2" } -} \ No newline at end of file +}