From 296a81010adee4bd45174ecd1ba49e5f475575cd Mon Sep 17 00:00:00 2001 From: Arne Blumentritt Date: Tue, 4 May 2021 09:11:50 +0200 Subject: [PATCH] WIP dynamic platform --- accessories/neatoVacuumRobot.js | 614 ---------------------------- characteristics/spotHeight.js | 21 - characteristics/spotRepeat.js | 17 - characteristics/spotWidth.js | 21 - config.schema.json | 104 ++++- old_index.js | 304 -------------- src/accessories/neatoVacuumRobot.ts | 135 +++--- src/homebridgeNeatoPlatform.ts | 6 +- 8 files changed, 172 insertions(+), 1050 deletions(-) delete mode 100644 accessories/neatoVacuumRobot.js delete mode 100644 characteristics/spotHeight.js delete mode 100644 characteristics/spotRepeat.js delete mode 100644 characteristics/spotWidth.js delete mode 100644 old_index.js diff --git a/accessories/neatoVacuumRobot.js b/accessories/neatoVacuumRobot.js deleted file mode 100644 index 2c3908a..0000000 --- a/accessories/neatoVacuumRobot.js +++ /dev/null @@ -1,614 +0,0 @@ -const debug = require('debug')('homebridge-neato'); -const colors = require('colors'); - -const CustomUUID = { - SpotCleanWidth: 'A7889A9A-2F27-4293-BEF8-3FE805B36F4E', - SpotCleanHeight: 'CA282DB2-62BF-4325-A1BE-F8BB5478781A', - SpotCleanRepeat: '1E79C603-63B8-4E6A-9CE1-D31D67981831' -}; - -let Service, - Characteristic, - SpotWidthCharacteristic, - SpotHeightCharacteristic, - SpotRepeatCharacteristic; - -module.exports = function (_Service, _Characteristic) -{ - Service = _Service; - Characteristic = _Characteristic; - SpotWidthCharacteristic = require('../characteristics/spotWidth')(Characteristic, CustomUUID); - SpotHeightCharacteristic = require('../characteristics/spotHeight')(Characteristic, CustomUUID); - SpotRepeatCharacteristic = require('../characteristics/spotRepeat')(Characteristic, CustomUUID); - - return NeatoVacuumRobotAccessory; -}; - -function NeatoVacuumRobotAccessory(platform, robotObject) -{ - this.platform = platform; - this.log = platform.log; - this.refresh = platform.refresh; - this.hiddenServices = platform.hiddenServices; - this.nextRoom = platform.nextRoom; - - this.robotObject = robotObject; - this.robot = robotObject.device; - this.meta = robotObject.meta; - this.spotPlusFeatures = ((typeof robotObject.availableServices.spotCleaning !== 'undefined') && robotObject.availableServices.spotCleaning.includes("basic")); - this.boundary = (typeof robotObject.boundary === 'undefined') ? null : robotObject.boundary; - - if (this.boundary == null) - { - this.name = this.robot.name; - } - else - { - // if boundary name already exists - if (platform.boundaryNames.includes(this.boundary.name)) - { - let lastChar = this.boundary.name.slice(-1); - // boundary name already contains a count number - if (!isNaN(lastChar)) - { - // Increment existing count number - this.boundary.name = this.boundary.name.slice(0, -1) + (parseInt(lastChar) + 1); - } - else - { - // Add a new count number - this.boundary.name = this.boundary.name + " 2"; - } - } - platform.boundaryNames.push(this.boundary.name); - this.name = this.robot.name + ' - ' + this.boundary.name; - } - - this.batteryService = new Service.BatteryService("Battery", "battery"); - - if (this.boundary == null) - { - this.cleanService = new Service.Switch(this.name + " Clean", "clean"); - this.goToDockService = new Service.Switch(this.name + " Go to Dock", "goToDock"); - this.dockStateService = new Service.OccupancySensor(this.name + " Dock", "dockState"); - this.ecoService = new Service.Switch(this.name + " Eco Mode", "eco"); - this.noGoLinesService = new Service.Switch(this.name + " NoGo Lines", "noGoLines"); - this.extraCareService = new Service.Switch(this.name + " Extra Care", "extraCare"); - this.scheduleService = new Service.Switch(this.name + " Schedule", "schedule"); - this.findMeService = new Service.Switch(this.name + " Find Me", "findMe"); - - this.spotCleanService = new Service.Switch(this.name + " Clean Spot", "cleanSpot"); - this.spotCleanService.addCharacteristic(SpotRepeatCharacteristic); - if (this.spotPlusFeatures) - { - this.spotCleanService.addCharacteristic(SpotWidthCharacteristic); - this.spotCleanService.addCharacteristic(SpotHeightCharacteristic); - } - } - else - { - const splitName = this.boundary.name.split(' '); - let serviceName = "Clean the " + this.boundary.name; - if (splitName.length >= 2 && splitName[splitName.length - 2].match(/[']s$/g)) - { - serviceName = "Clean " + this.boundary.name; - } - this.cleanService = new Service.Switch(serviceName, "cleanBoundary:" + this.boundary.id); - } - - this.log("Added cleaning device named: " + this.name); -} - -NeatoVacuumRobotAccessory.prototype = { - identify: function (callback) - { - this.robot.getState((error, result) => - { - if (error) - { - this.log.error("Error getting robot information: " + error + ": " + result); - } - else - { - this.log("### Robot information ###"); - this.log(result); - } - callback(); - }); - }, - - getServices: function () - { - this.informationService = new Service.AccessoryInformation(); - this.informationService - .setCharacteristic(Characteristic.Manufacturer, "Neato Robotics") - .setCharacteristic(Characteristic.Model, this.meta.modelName) - .setCharacteristic(Characteristic.SerialNumber, this.robot._serial) - .setCharacteristic(Characteristic.FirmwareRevision, this.meta.firmware) - .setCharacteristic(Characteristic.Name, this.robot.name + (this.boundary == null ? '' : ' - ' + this.boundary.name)); - - this.cleanService.getCharacteristic(Characteristic.On).on('set', this.setClean.bind(this)); - this.cleanService.getCharacteristic(Characteristic.On).on('get', this.getClean.bind(this)); - - this.services = [this.informationService, this.cleanService]; - - if (this.boundary == null) - { - this.batteryService.getCharacteristic(Characteristic.BatteryLevel).on('get', this.getBatteryLevel.bind(this)); - this.batteryService.getCharacteristic(Characteristic.ChargingState).on('get', this.getBatteryChargingState.bind(this)); - this.services.push(this.batteryService); - - this.goToDockService.getCharacteristic(Characteristic.On).on('set', this.setGoToDock.bind(this)); - this.goToDockService.getCharacteristic(Characteristic.On).on('get', this.getGoToDock.bind(this)); - - this.dockStateService.getCharacteristic(Characteristic.OccupancyDetected).on('get', this.getDock.bind(this)); - - this.ecoService.getCharacteristic(Characteristic.On).on('set', this.setEco.bind(this)); - this.ecoService.getCharacteristic(Characteristic.On).on('get', this.getEco.bind(this)); - - this.noGoLinesService.getCharacteristic(Characteristic.On).on('set', this.setNoGoLines.bind(this)); - this.noGoLinesService.getCharacteristic(Characteristic.On).on('get', this.getNoGoLines.bind(this)); - - this.extraCareService.getCharacteristic(Characteristic.On).on('set', this.setExtraCare.bind(this)); - this.extraCareService.getCharacteristic(Characteristic.On).on('get', this.getExtraCare.bind(this)); - - this.scheduleService.getCharacteristic(Characteristic.On).on('set', this.setSchedule.bind(this)); - this.scheduleService.getCharacteristic(Characteristic.On).on('get', this.getSchedule.bind(this)); - - this.findMeService.getCharacteristic(Characteristic.On).on('set', this.setFindMe.bind(this)); - this.findMeService.getCharacteristic(Characteristic.On).on('get', this.getFindMe.bind(this)); - - this.spotCleanService.getCharacteristic(Characteristic.On).on('set', this.setSpotClean.bind(this)); - this.spotCleanService.getCharacteristic(Characteristic.On).on('get', this.getSpotClean.bind(this)); - this.spotCleanService.getCharacteristic(SpotRepeatCharacteristic).on('set', this.setSpotRepeat.bind(this)); - this.spotCleanService.getCharacteristic(SpotRepeatCharacteristic).on('get', this.getSpotRepeat.bind(this)); - - if (this.spotPlusFeatures) - { - this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).on('set', this.setSpotWidth.bind(this)); - this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).on('get', this.getSpotWidth.bind(this)); - this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).on('set', this.setSpotHeight.bind(this)); - this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).on('get', this.getSpotHeight.bind(this)); - } - - if (this.hiddenServices.indexOf('spot') === -1) - { - this.services.push(this.spotCleanService); - } - - // Add optional services - if (this.hiddenServices.indexOf('dock') === -1) - this.services.push(this.goToDockService); - if (this.hiddenServices.indexOf('dockstate') === -1) - this.services.push(this.dockStateService); - if (this.hiddenServices.indexOf('eco') === -1) - this.services.push(this.ecoService); - if (this.hiddenServices.indexOf('nogolines') === -1) - this.services.push(this.noGoLinesService); - if (this.hiddenServices.indexOf('extracare') === -1) - this.services.push(this.extraCareService); - if (this.hiddenServices.indexOf('schedule') === -1) - this.services.push(this.scheduleService); - if (this.hiddenServices.indexOf('find') === -1) - this.services.push(this.findMeService); - } - - return this.services; - }, - - - getClean: function (callback) - { - this.platform.updateRobot(this.robot._serial, (error, result) => - { - let cleaning; - if (this.boundary == null) - { - cleaning = this.robot.canPause; - } - else - { - cleaning = this.robot.canPause && (this.robot.cleaningBoundaryId === this.boundary.id) - } - - debug(this.name + ": Cleaning is " + (cleaning ? 'ON'.brightGreen : 'OFF'.red)); - callback(false, cleaning); - }); - }, - - setClean: function (on, callback) - { - - }, - - clean: function (callback, spot) - { - // Start automatic update while cleaning - if (this.refresh === 'auto') - { - setTimeout(() => - { - this.platform.updateRobotTimer(this.robot._serial); - }, 60 * 1000); - } - - let eco = this.robotObject.mainAccessory.ecoService.getCharacteristic(Characteristic.On).value; - let extraCare = this.robotObject.mainAccessory.extraCareService.getCharacteristic(Characteristic.On).value; - let nogoLines = this.robotObject.mainAccessory.noGoLinesService.getCharacteristic(Characteristic.On).value; - let room = (this.boundary == null) ? '' : this.boundary.name; - debug(this.name + ": ## Start cleaning (" + (room !== '' ? room + " " : '') + "eco: " + eco + ", extraCare: " + extraCare + ", nogoLines: " + nogoLines + ", spot: " + JSON.stringify(spot) + ")"); - - // Normal cleaning - if (this.boundary == null && (typeof spot === 'undefined')) - { - this.robot.startCleaning(eco, extraCare ? 2 : 1, nogoLines, (error, result) => - { - if (error) - { - this.log.error("Cannot start cleaning. " + error + ": " + JSON.stringify(result)); - } - callback(error); - }); - } - // Room cleaning - else if (room !== '') - { - this.robot.startCleaningBoundary(eco, extraCare, this.boundary.id, (error, result) => - { - if (error) - { - this.log.error("Cannot start room cleaning. " + error + ": " + JSON.stringify(result)); - } - callback(error); - }); - } - // Spot cleaning - else - { - this.robot.startSpotCleaning(eco, spot.width, spot.height, spot.repeat, extraCare ? 2 : 1, (error, result) => - { - if (error) - { - this.log.error("Cannot start spot cleaning. " + error + ": " + JSON.stringify(result)); - } - callback(error); - }); - } - }, - - getGoToDock: function (callback) - { - callback(false, false); - }, - - setGoToDock: function (on, callback) - { - this.platform.updateRobot(this.robot._serial, (error, result) => - { - if (on) - { - if (this.robot.canPause) - { - debug(this.name + ": ## Pause cleaning to go to dock"); - this.robot.pauseCleaning((error, result) => - { - setTimeout(() => - { - debug(this.name + ": ## Go to dock"); - this.robot.sendToBase(() => - { - callback(); - }); - }, 1000); - }); - } - else if (this.robot.canGoToBase) - { - debug(this.name + ": ## Go to dock"); - this.robot.sendToBase(() => - { - callback(); - }); - } - else - { - this.log.warn(this.name + ": Can't go to dock at the moment"); - callback(); - } - } - else - { - callback(); - } - }); - }, - - getEco: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Eco Mode is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red)); - callback(false, this.robot.eco); - }); - }, - - setEco: function (on, callback) - { - this.robot.eco = on; - debug(this.name + ": " + (on ? "Enabled ".red : "Disabled".red) + " Eco Mode "); - callback(); - }, - - getNoGoLines: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": NoGoLine is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red)); - callback(false, this.robot.noGoLines ? 1 : 0); - }); - }, - - setNoGoLines: function (on, callback) - { - this.robot.noGoLines = on; - debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " NoGoLine "); - callback(); - }, - - getExtraCare: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Care Nav is " + (this.robot.navigationMode === 2 ? 'ON'.brightGreen : 'OFF'.red)); - callback(false, this.robot.navigationMode === 2 ? 1 : 0); - }); - }, - - setExtraCare: function (on, callback) - { - this.robot.navigationMode = on ? 2 : 1; - debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Care Nav "); - callback(); - }, - - getSchedule: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Schedule is " + (this.robot.eco ? 'ON'.brightGreen : 'OFF'.red)); - callback(false, this.robot.isScheduleEnabled); - }); - }, - - setSchedule: function (on, callback) - { - this.platform.updateRobot(this.robot._serial, (error, result) => - { - if (on) - { - debug(this.name + ": " + "Enabled".brightGreen + " Schedule"); - this.robot.enableSchedule((error) => - { - callback(error); - }); - } - else - { - debug(this.name + ": " + "Disabled".red + " Schedule"); - this.robot.disableSchedule((error) => - { - callback(error); - }); - } - }); - }, - - getFindMe: function (callback) - { - callback(false, false); - }, - - setFindMe: function (on, callback) - { - if (on) - { - debug(this.name + ": ## Find me"); - setTimeout(() => - { - this.findMeService.setCharacteristic(Characteristic.On, false); - }, 1000); - - this.robot.findMe((error) => - { - callback(error); - }); - } - }, - - getSpotClean: function (callback) - { - callback(false, this.spotCleanService.getCharacteristic(Characteristic.On).value); - }, - - setSpotClean: function (on, callback) - { - let spot = { - width: this.spotPlusFeatures ? this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).value : null, - height: this.spotPlusFeatures ? this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).value : null, - repeat: this.spotCleanService.getCharacteristic(SpotRepeatCharacteristic).value - }; - - this.platform.updateRobot(this.robot._serial, (error, result) => - { - // Start - if (on) - { - // Resume cleaning - if (this.robot.canResume) - { - debug(this.name + ": ## Resume (spot) cleaning"); - this.robot.resumeCleaning(callback); - } - // Start cleaning - else if (this.robot.canStart) - { - this.clean(callback, spot); - } - // Cannot start - else - { - debug(this.name + ": Cannot start spot cleaning, maybe already cleaning"); - callback(); - } - } - // Stop - else - { - if (this.robot.canPause) - { - debug(this.name + ": ## Pause cleaning"); - this.robot.pauseCleaning((error) => - { - callback(error); - }); - } - else - { - debug(this.name + ": Already paused"); - callback(); - } - } - }); - }, - - getSpotWidth: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Spot width is " + this.robot.spotWidth + "cm"); - callback(false, this.robot.spotWidth); - }); - }, - - setSpotWidth: function (width, callback) - { - this.robot.spotWidth = width; - debug(this.name + ": Set spot width to " + width + "cm"); - callback(); - }, - - getSpotHeight: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Spot height is " + this.robot.spotHeight + "cm"); - callback(false, this.robot.spotHeight); - }); - }, - - setSpotHeight: function (height, callback) - { - this.robot.spotHeight = height; - debug(this.name + ": Set spot height to " + height + "cm"); - callback(); - }, - - getSpotRepeat: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Spot repeat is " + (this.robot.spotRepeat ? 'ON'.brightGreen : 'OFF'.red)); - callback(false, this.robot.spotRepeat); - }); - }, - - setSpotRepeat: function (on, callback) - { - this.robot.spotRepeat = on; - debug(this.name + ": " + (on ? "Enabled ".brightGreen : "Disabled".red) + " Spot repeat"); - callback(); - }, - - getDock: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": The Dock is " + (this.robot.isDocked ? "OCCUPIED".brightGreen : "NOT OCCUPIED".red)); - callback(false, this.robot.isDocked ? 1 : 0); - }); - }, - - getBatteryLevel: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Battery is " + this.robot.charge + "%"); - callback(false, this.robot.charge); - }); - }, - - getBatteryChargingState: function (callback) - { - this.platform.updateRobot(this.robot._serial, () => - { - debug(this.name + ": Battery is " + (this.robot.isCharging ? "CHARGING".brightGreen : "NOT CHARGING".red)); - callback(false, this.robot.isCharging); - }); - }, - - updated: function () - { - if (this.boundary == null) - { - // only update these values if the state is different from the current one, otherwise we might accidentally start an action - if (this.cleanService.getCharacteristic(Characteristic.On).value !== this.robot.canPause) - { - this.cleanService.setCharacteristic(Characteristic.On, this.robot.canPause); - } - - // dock switch is on (dock not seen before) and dock has just been seen -> turn switch off - if (this.goToDockService.getCharacteristic(Characteristic.On).value == true && this.robot.dockHasBeenSeen) - { - this.goToDockService.setCharacteristic(Characteristic.On, false); - } - - if (this.scheduleService.getCharacteristic(Characteristic.On).value !== this.robot.isScheduleEnabled) - { - this.scheduleService.setCharacteristic(Characteristic.On, this.robot.isScheduleEnabled); - } - - // no commands here, values can be updated without problems - this.dockStateService.setCharacteristic(Characteristic.OccupancyDetected, this.robot.isDocked ? 1 : 0); - - this.ecoService.setCharacteristic(Characteristic.On, this.robot.eco); - this.noGoLinesService.setCharacteristic(Characteristic.On, this.robot.noGoLines); - this.extraCareService.setCharacteristic(Characteristic.On, this.robot.navigationMode == 2 ? true : false); - - this.spotCleanService.setCharacteristic(SpotRepeatCharacteristic, this.robot.spotRepeat); - - if (this.spotPlusFeatures) - { - let widthProps = this.spotCleanService.getCharacteristic(SpotWidthCharacteristic).props; - let heightProps = this.spotCleanService.getCharacteristic(SpotHeightCharacteristic).props; - - this.spotCleanService.setCharacteristic(SpotWidthCharacteristic, - this.robot.spotWidth >= widthProps.minValue && this.robot.spotWidth <= widthProps.maxValue ? this.robot.spotWidth : widthProps.minValue); - this.spotCleanService.setCharacteristic(SpotHeightCharacteristic, - this.robot.spotHeight >= heightProps.minValue && this.robot.spotHeight <= heightProps.maxValue ? this.robot.spotHeight : heightProps.minValue); - } - } - - this.batteryService.setCharacteristic(Characteristic.BatteryLevel, this.robot.charge); - this.batteryService.setCharacteristic(Characteristic.ChargingState, this.robot.isCharging); - - // Robot has a next room to clean in queue - if (this.nextRoom != null && this.robot.isDocked) - { - this.clean((error, result) => - { - this.nextRoom = null; - debug("## Starting cleaning of next room"); - }); - } - } -}; \ No newline at end of file diff --git a/characteristics/spotHeight.js b/characteristics/spotHeight.js deleted file mode 100644 index e609595..0000000 --- a/characteristics/spotHeight.js +++ /dev/null @@ -1,21 +0,0 @@ -const inherits = require('util').inherits; - -module.exports = function (Characteristic, CustomUUID) -{ - let SpotHeight = function () - { - Characteristic.call(this, 'Spot ↕', CustomUUID.SpotCleanHeight); - this.setProps({ - format: Characteristic.Formats.INT, - unit: 'cm', - maxValue: 400, - minValue: 100, - minStep: 50, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); - }; - inherits(SpotHeight, Characteristic); - - return SpotHeight; -}; \ No newline at end of file diff --git a/characteristics/spotRepeat.js b/characteristics/spotRepeat.js deleted file mode 100644 index 8f7d72d..0000000 --- a/characteristics/spotRepeat.js +++ /dev/null @@ -1,17 +0,0 @@ -const inherits = require('util').inherits; - -module.exports = function (Characteristic, CustomUUID) -{ - let SpotRepeat = function () - { - Characteristic.call(this, 'Spot 2x', CustomUUID.SpotCleanRepeat); - this.setProps({ - format: Characteristic.Formats.BOOL, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); - }; - inherits(SpotRepeat, Characteristic); - - return SpotRepeat; -}; \ No newline at end of file diff --git a/characteristics/spotWidth.js b/characteristics/spotWidth.js deleted file mode 100644 index e3ed9cf..0000000 --- a/characteristics/spotWidth.js +++ /dev/null @@ -1,21 +0,0 @@ -const inherits = require('util').inherits; - -module.exports = function (Characteristic, CustomUUID) -{ - let SpotWidth = function () - { - Characteristic.call(this, 'Spot ↔', CustomUUID.SpotCleanWidth); - this.setProps({ - format: Characteristic.Formats.INT, - unit: 'cm', - maxValue: 400, - minValue: 100, - minStep: 50, - perms: [Characteristic.Perms.READ, Characteristic.Perms.WRITE] - }); - this.value = this.getDefaultValue(); - }; - inherits(SpotWidth, Characteristic); - - return SpotWidth; -}; \ No newline at end of file diff --git a/config.schema.json b/config.schema.json index 290f674..15cb50e 100644 --- a/config.schema.json +++ b/config.schema.json @@ -1,23 +1,93 @@ { - "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", + "pluginAlias": "NeatoVacuumRobot", + "pluginType": "platform", + "singular": true, + "headerDisplay": "", + "schema": { + "type": "object", + "properties": { + "email": { + "title": "E-Mail", + "type": "string", + "required": true, + "format": "email" + }, + "password": { + "title": "Password", + "type": "string", + "required": true + }, + "prefix": { + "title": "Prefix robot name", + "description": "Display the name of the robot in front of every service.", + "type": "boolean", + "default": false + }, + "backgroundUpdate": { + "title": "Background update", + "description": "Interval for background updates while the robot is not cleaning (in minutes). During cleaning, the robot will automatically update at a faster rate.", + "type": "integer", + "minimum": 1, + "default": 30 + }, + "services": { + "type": "array", + "items": { "type": "string", - "required": true, - "format": "email", - "description": "Your Email Address" + "enum": [ + "Clean house", + "Clean spot", + "Go to dock", + "Find me", + "Schedule", + "Eco", + "Nogo lines", + "Extra care", + "Docked sensor", + "Bin full sensor" + ] }, - "password": { - "title": "password", - "type": "string", - "required": true, - "description": "Your Password" - } + "uniqueItems": true } } + }, + "layout": [ + { + "type": "help", + "helpvalue": "

Login

" + }, + { + "type": "help", + "helpvalue": "Enter the credentials of your Neato app. If you don't have a neato account yet, register here" + }, + { + "type": "flex", + "flex-flow": "row wrap", + "items": [ + "email", + { + "key": "password", + "type": "password" + } + ] + }, + { + "type": "help", + "helpvalue": "

Options

" + }, + "backgroundUpdate", + "prefix", + { + "type": "fieldset", + "title": "Services", + "description": "Services to be displayed in Homekit", + "expandable": true, + "items": [ + { + "key": "services", + "notitle": true + } + ] + } + ] } \ No newline at end of file diff --git a/old_index.js b/old_index.js deleted file mode 100644 index c20f2bb..0000000 --- a/old_index.js +++ /dev/null @@ -1,304 +0,0 @@ -"use strict"; -let inherits = require('util').inherits, - debug = require('debug')('homebridge-neato'), - botvac = require('node-botvac'), - - Service, - Characteristic, - NeatoVacuumRobotAccessory; - -module.exports = function (homebridge) -{ - Service = homebridge.hap.Service; - Characteristic = homebridge.hap.Characteristic; - NeatoVacuumRobotAccessory = require('./accessories/neatoVacuumRobot')(Service, 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 = ''; - this.hiddenServices = ('disabled' in config ? config['disabled'] : this.hiddenServices); - this.hiddenServices = ('hidden' in config ? config['hidden'] : this.hiddenServices); - - // Array of real robots and associated robot accessories (incl rooms) - this.robotAccessories = []; - this.nextRoom = null; - - if ('refresh' in config && config['refresh'] !== 'auto') - { - // parse config parameter - this.refresh = parseInt(config['refresh']); - // must be integer and positive - this.refresh = (typeof this.refresh !== 'number' || (this.refresh % 1) !== 0 || this.refresh < 0) ? 60 : this.refresh; - // minimum 60s to save some load on the neato servers - if (this.refresh > 0 && this.refresh < 60) - { - this.log.warn("Minimum refresh time is 60 seconds to not overload the neato servers"); - this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh; - } - } - // default auto - else - { - this.refresh = 'auto'; - } - this.log("Refresh is set to: " + this.refresh + (this.refresh !== 'auto' ? ' seconds' : '')); -} - -NeatoVacuumRobotPlatform.prototype = { - accessories: function (callback) - { - debug("Get robots"); - let accessories = []; - this.boundaryNames = []; - - this.getRobots(() => - { - // // MOCK MULTIPLE ROBOTS START - // let client = new botvac.Client(); - // client.authorize(this.email, this.password, false, (error) => - // { - // client.getRobots((error, robs) => - // { - // let testRobot = robs[0]; - // testRobot.getState((error, result) => - // { - // testRobot.name = "Testrobot"; - // this.robots.push({device: testRobot, meta: result.meta, availableServices: result.availableServices}); - // // MOCK MULTIPLE ROBOTS END - - this.robots.forEach((robot, i) => - { - this.log("Found robot #" + (i + 1) + " named \"" + robot.device.name + "\" with serial \"" + robot.device._serial.substring(0, 9) + "XXXXXXXXXXXX\""); - - let mainAccessory = new NeatoVacuumRobotAccessory(this, robot); - accessories.push(mainAccessory); - - robot.mainAccessory = mainAccessory; - robot.roomAccessories = []; - - // Start Update Intervall - this.updateRobotTimer(robot.device._serial); - - // // MOCK ZONE CLEANING START - // robot.boundary = {name: "Testroom", id: "1"}; - // let roomAccessory = new NeatoVacuumRobotAccessory(this, robot); - // accessories.push(roomAccessory); - // robot.roomAccessories.push(roomAccessory); - // // MOCK ZONE CLEANING END - - if (robot.device.maps) - { - robot.device.maps.forEach((map) => - { - if (map.boundaries) - { - map.boundaries.forEach((boundary) => - { - if (boundary.type === "polygon") - { - robot.boundary = boundary; - let roomAccessory = new NeatoVacuumRobotAccessory(this, robot); - accessories.push(roomAccessory); - - robot.roomAccessories.push(roomAccessory); - } - }) - } - }) - } - }); - callback(accessories); - - // // MOCK MULTIPLE ROBOTS START - // }); - // }); - // }); - // // MOCK MULTIPLE ROBOTS END - }); - }, - - getRobots: function (callback) - { - debug("Loading your robots"); - let client = new botvac.Client(); - - // Login - client.authorize(this.email, this.password, false, (error) => - { - if (error) - { - this.log.error("Can't log on to neato cloud. Please check your internet connection and your credentials. Try again later if the neato servers have issues: " + error); - callback(); - } - else - { - // Get all robots - client.getRobots((error, robots) => - { - if (error) - { - this.log.error("Successful login but can't connect to your neato robot: " + error); - callback(); - } - else if (robots.length === 0) - { - this.log.error("Successful login but no robots associated with your account."); - this.robotAccessories = []; - callback(); - } - else - { - debug("Found " + robots.length + " robots"); - let loadedRobots = 0; - - robots.forEach((robot) => - { - // Get additional information for the robot - robot.getState((error, state) => - { - if (error) - { - this.log.error("Error getting robot meta information: " + error + ": " + state); - callback(); - } - else - { - // Get all maps for each robot - robot.getPersistentMaps((error, maps) => - { - if (error) - { - this.log.error("Error updating persistent maps: " + error + ": " + maps); - callback(); - } - // Robot has no maps - else if (maps.length === 0) - { - robot.maps = []; - this.robots.push({device: robot, meta: state.meta, availableServices: state.availableServices}); - loadedRobots++; - if (loadedRobots === robots.length) - { - callback(); - } - } - // Robot has maps - else - { - robot.maps = maps; - let loadedMaps = 0; - robot.maps.forEach((map) => - { - // Save zones in each map - robot.getMapBoundaries(map.id, (error, result) => - { - if (error) - { - this.log.error("Error getting boundaries: " + error + ": " + result) - } - else - { - map.boundaries = result.boundaries; - } - loadedMaps++; - // Robot is completely requested if zones for all maps are loaded - if (loadedMaps === robot.maps.length) - { - this.robots.push({device: robot, meta: state.meta, availableServices: state.availableServices}); - loadedRobots++; - if (loadedRobots === 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.lastUpdate = new Date(); - robot.device.getState((error, result) => - { - if (error) - { - this.log.error("Cannot update robot. Check if robot is online. " + error); - } - 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/src/accessories/neatoVacuumRobot.ts b/src/accessories/neatoVacuumRobot.ts index b5ab949..0b6e3a4 100644 --- a/src/accessories/neatoVacuumRobot.ts +++ b/src/accessories/neatoVacuumRobot.ts @@ -9,13 +9,8 @@ import {Options} from '../models/options'; */ export class NeatoVacuumRobotAccessory { - private robot: any; + // Homebridge private log: Logger; - private readonly refresh: any; - private isSpotCleaning: boolean; - private readonly options: Options; - private timer: any; - private batteryService: Service; private cleanService: Service; private findMeService: Service; @@ -28,6 +23,19 @@ export class NeatoVacuumRobotAccessory private scheduleService: Service; private spotCleanService: Service; + // Context + private robot: any; + private readonly options: Options; + + // Config + private readonly backgroundUpdateInterval: number; + private readonly prefix: boolean; + private readonly availableServices: string[]; + + // Transient + private isSpotCleaning: boolean; + private timer: any; + /** * These are just used to create a working example * You should implement your own code to track the state of your accessory @@ -36,35 +44,21 @@ export class NeatoVacuumRobotAccessory constructor( private readonly platform: HomebridgeNeatoPlatform, private readonly accessory: PlatformAccessory, - private readonly isNew: Boolean, private readonly config: PlatformConfig) { this.log = platform.log; + this.robot = accessory.context.robot; this.options = accessory.context.options || new Options(); - if ('refresh' in this.config && this.config['refresh'] !== 'auto') - { - // parse config parameter - this.refresh = parseInt(this.config['refresh']); - // must be integer and positive - this.refresh = (typeof this.refresh !== 'number' || (this.refresh % 1) !== 0 || this.refresh < 0) ? 60 : this.refresh; - // minimum 60s to save some load on the neato servers - if (this.refresh > 0 && this.refresh < 60) - { - this.log.warn("Minimum refresh time is 60 seconds to not overload the neato servers"); - this.refresh = (this.refresh > 0 && this.refresh < 60) ? 60 : this.refresh; - } - } - else - { - this.refresh = 'auto'; - } - this.debug(DebugType.STATUS, "Background update interval is: " + this.refresh); + this.backgroundUpdateInterval = NeatoVacuumRobotAccessory.parseBackgroundUpdateInterval(this.config['backgroundUpdate']); + this.prefix = this.config['prefix'] || PREFIX; + this.availableServices = this.config['services'] || SERVICES; + this.log.debug(JSON.stringify(this.availableServices)); this.isSpotCleaning = false; - // set accessory information + // Information this.accessory.getService(this.platform.Service.AccessoryInformation)! .setCharacteristic(this.platform.Characteristic.Manufacturer, "Neato Robotics") .setCharacteristic(this.platform.Characteristic.Model, this.robot.meta.modelName) @@ -72,9 +66,10 @@ export class NeatoVacuumRobotAccessory .setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.robot.meta.firmware) .setCharacteristic(this.platform.Characteristic.Name, this.robot.name); + // Identify this.accessory.on(PlatformAccessoryEvent.IDENTIFY, () => { this.robot.getState((error, result) => { - this.log.info(this.robot.name + " Identified"); + this.log.info("[" + this.robot.name + "] Identified"); if (error) { this.debug(DebugType.INFO, JSON.stringify("Error: " + error)); @@ -83,19 +78,19 @@ export class NeatoVacuumRobotAccessory }); }); + // Services + this.cleanService = this.getSwitchService("Clean House"); + this.spotCleanService = this.getSwitchService("Clean Spot"); + this.goToDockService = this.getSwitchService("Go to Dock"); + this.dockStateService = this.getOccupancyService("Docked") + this.binFullService = this.getOccupancyService("Bin Full") + this.findMeService = this.getSwitchService("Find Me"); + this.scheduleService = this.getSwitchService("Schedule"); + this.ecoService = this.getSwitchService("Option: Eco Mode"); + this.noGoLinesService = this.getSwitchService("Option: NoGo Lines"); + this.extraCareService = this.getSwitchService("Option: Extra Care"); this.batteryService = this.accessory.getService(this.platform.Service.Battery) || this.accessory.addService(this.platform.Service.Battery) - this.cleanService = this.getSwitchService(this.robot.name + " Clean House"); - this.spotCleanService = this.getSwitchService(this.robot.name + " Clean Spot"); - this.goToDockService = this.getSwitchService(this.robot.name + " Go to Dock"); - this.dockStateService = this.getOccupancyService(this.robot.name + " Docked") - this.binFullService = this.getOccupancyService(this.robot.name + " Bin Full") - this.findMeService = this.getSwitchService(this.robot.name + " Find Me"); - this.scheduleService = this.getSwitchService(this.robot.name + " Option: Schedule"); - this.ecoService = this.getSwitchService(this.robot.name + " Option: Eco Mode"); - this.noGoLinesService = this.getSwitchService(this.robot.name + " Option: NoGo Lines"); - this.extraCareService = this.getSwitchService(this.robot.name + " Option: Extra Care"); - this.cleanService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setCleanHouse.bind(this)) .onGet(this.getCleanHouse.bind(this)); @@ -107,7 +102,7 @@ export class NeatoVacuumRobotAccessory .onGet(this.getGoToDock.bind(this)); this.dockStateService.getCharacteristic(this.platform.Characteristic.OccupancyDetected) .onGet(this.getDocked.bind(this)); - this.dockStateService.getCharacteristic(this.platform.Characteristic.OccupancyDetected) + this.binFullService.getCharacteristic(this.platform.Characteristic.OccupancyDetected) .onGet(this.getBinFull.bind(this)); this.findMeService.getCharacteristic(this.platform.Characteristic.On) .onSet(this.setFindMe.bind(this)) @@ -125,6 +120,7 @@ export class NeatoVacuumRobotAccessory .onSet(this.setExtraCare.bind(this)) .onGet(this.getExtraCare.bind(this)); + // Start background update this.updateRobotPeriodically().then(() => { if (!accessory.context.options) { @@ -141,14 +137,27 @@ export class NeatoVacuumRobotAccessory }); } - private getSwitchService(servicename: string) + private getSwitchService(serviceName: string) { - return this.accessory.getService(servicename) || this.accessory.addService(this.platform.Service.Switch, servicename, servicename) + let displayName = this.prefix ? this.robot.name + serviceName : serviceName; + return this.accessory.getService(displayName) || this.accessory.addService(this.platform.Service.Switch, displayName, serviceName) } - private getOccupancyService(servicename: string) + private getOccupancyService(serviceName: string) { - return this.accessory.getService(servicename) || this.accessory.addService(this.platform.Service.OccupancySensor, servicename, servicename) + let displayName = this.prefix ? this.robot.name + serviceName : serviceName; + return this.accessory.getService(displayName) || this.accessory.addService(this.platform.Service.OccupancySensor, displayName, serviceName) + } + + private static parseBackgroundUpdateInterval(configValue: any) + { + // Parse as number + let backgroundUpdateInterval = parseInt(configValue) || BACKGROUND_INTERVAL; + + // must be integer and positive + backgroundUpdateInterval = ((backgroundUpdateInterval % 1) !== 0 || backgroundUpdateInterval < 0) ? BACKGROUND_INTERVAL : backgroundUpdateInterval; + + return backgroundUpdateInterval; } async getCleanHouse(): Promise @@ -184,7 +193,7 @@ export class NeatoVacuumRobotAccessory // Start cleaning else if (this.robot.canStart) { - await this.clean(CleanType.HOUSE) + await this.clean(CleanType.ALL) } // Cannot start else @@ -422,13 +431,15 @@ export class NeatoVacuumRobotAccessory this.updateRobotPeriodically(); }, 60 * 1000); - this.log.info("[" + this.robot.name + "] > Start cleaning with options type: " + CleanType[cleanType] + ", eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " + this.options.extraCare); + this.log.info( + "[" + this.robot.name + "] > Start cleaning with options type: " + CleanType[cleanType] + ", eco: " + this.options.eco + ", noGoLines: " + this.options.noGoLines + ", extraCare: " + + this.options.extraCare); try { switch (cleanType) { - case CleanType.HOUSE: + case CleanType.ALL: await this.robot.startCleaning(this.options.eco, this.options.extraCare ? 2 : 1, this.options.noGoLines); break; case CleanType.SPOT: @@ -490,10 +501,10 @@ export class NeatoVacuumRobotAccessory } else { - interval = this.refresh == "auto" ? 30 : this.refresh; + interval = this.backgroundUpdateInterval; } - - this.debug(DebugType.INFO, "Background update done. Next update in " + interval + " minute(s)" + ((this.robot.canPause) ? ", robot is currently cleaning" : "")); + + this.debug(DebugType.INFO, "Background update done. Next update in " + interval + " minute" + (interval == 1 ? "" : "s") + ((this.robot.canPause) ? ", robot is currently cleaning." : ".")); this.timer = setTimeout(this.updateRobotPeriodically.bind(this), interval * 60 * 1000); } @@ -511,7 +522,7 @@ export class NeatoVacuumRobotAccessory // Docked this.dockStateService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, await this.getDocked()); - + // Bin full this.binFullService.updateCharacteristic(this.platform.Characteristic.OccupancyDetected, await this.getBinFull()); @@ -528,7 +539,7 @@ export class NeatoVacuumRobotAccessory this.noGoLinesService.updateCharacteristic(this.platform.Characteristic.On, await this.getNoGoLines()); } - debug(debugType: DebugType, message: String) + private debug(debugType: DebugType, message: String) { switch (debugType) { @@ -547,7 +558,7 @@ export class NeatoVacuumRobotAccessory enum CleanType { - HOUSE, + ALL, SPOT } @@ -556,4 +567,22 @@ enum DebugType ACTION, STATUS, INFO -} \ No newline at end of file +} + +enum Action +{ + CLEAN_HOUSE = "CLEAN_HOUSE", + CLEAN_SPOT = "CLEAN_SPOT", + GO_TO_DOCK = "GO_TO_DOCK", + DOCKED = "DOCKED", + BIN_FULL = "BIN_FULL", + FIND_ME = "FIND_ME", + SCHEDULE = "SCHEDULE", + ECO = "ECO", + NOGO_LINES = "NOGO_LINES", + EXTRA_CARE = "EXTRA_CARE" +} + +const BACKGROUND_INTERVAL = 30; +const PREFIX = false; +const SERVICES = Object.values(Action); \ No newline at end of file diff --git a/src/homebridgeNeatoPlatform.ts b/src/homebridgeNeatoPlatform.ts index 605f090..823a4c0 100644 --- a/src/homebridgeNeatoPlatform.ts +++ b/src/homebridgeNeatoPlatform.ts @@ -67,7 +67,7 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin return; } - this.log.debug("Neato account has " + robots.length + " robot " + (robots.length == 1 ? "" : "s")); + this.log.info("Neato account has " + robots.length + " robot " + (robots.length == 1 ? "" : "s")); for (let robot of robots) { @@ -95,7 +95,7 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin existingAccessory.context.robot = robot; this.api.updatePlatformAccessories([existingAccessory]); - new NeatoVacuumRobotAccessory(this, existingAccessory, false, this.config); + new NeatoVacuumRobotAccessory(this, existingAccessory, this.config); } else { @@ -103,7 +103,7 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin const accessory = new this.api.platformAccessory(robot.name, uuid); accessory.context.robot = robot; - new NeatoVacuumRobotAccessory(this, accessory, true, this.config); + new NeatoVacuumRobotAccessory(this, accessory, this.config); // link the accessory to your platform this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);