diff --git a/package.json b/package.json index d94278d..70354bf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "homebridge-neato", "displayName": "Homebridge Neato", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 0000000..eae19b8 --- /dev/null +++ b/src/api.ts @@ -0,0 +1,45 @@ +var axios = require('axios'); + +function request(url, payload, method, headers, callback) { + if (!url || url === '') { + if (typeof callback === 'function') callback('no url specified'); + return; + } + + var options = { + data: null, + method: method === 'GET' ? 'GET' : 'POST', + url: url, + headers: { + 'Accept': 'application/vnd.neato.nucleo.v1' + } + }; + + if (options.method === 'POST') { + options.data = payload; + } + + if (typeof headers === 'object') { + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + options.headers[header] = headers[header]; + } + } + } + + let res, err; + + axios(options) + .then(function (response) { + res = response.data; + }) + .catch(function (error) { + err = error; + }) + .finally(function () { + // Callback needs to be called in finally block, see: https://github.com/Pmant/node-botvac/issues/15 + if (typeof callback === 'function') callback(err, res); + }); +} + +exports.request = request; diff --git a/src/homebridgeNeatoPlatform.ts b/src/homebridgeNeatoPlatform.ts index b6d52ee..551a22e 100644 --- a/src/homebridgeNeatoPlatform.ts +++ b/src/homebridgeNeatoPlatform.ts @@ -3,6 +3,8 @@ import NeatoApi from "node-botvac"; import {PLATFORM_NAME, PLUGIN_NAME} from "./settings"; import {NeatoVacuumRobotAccessory} from "./accessories/NeatoVacuumRobot"; +const api = require("./api"); + /** * HomebridgePlatform * This class is the main constructor for your plugin, this is where you should @@ -14,7 +16,7 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; // this is used to track restored cached accessories - public readonly robotAccessories: PlatformAccessory[] = []; + public readonly cachedRobotAccessories: PlatformAccessory[] = []; constructor( public readonly log: Logger, @@ -33,12 +35,13 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin configureAccessory(accessory: PlatformAccessory) { // add the restored accessory to the accessories cache so we can track if it has already been registered - this.robotAccessories.push(accessory); + this.cachedRobotAccessories.push(accessory); } discoverRobots() { const client = new NeatoApi.Client(); + try { @@ -52,30 +55,77 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin return; } + // Debug robot request TODO: remove after beta + let that = this; + api.request(client._baseUrl + '/users/me/robots', null, 'GET', {Authorization: client._tokenType + client._token}, (function (error, result) { + result.forEach(r => { + r.serial = "xxx" + r.serial.length; + r.secret_key = "xxx" + r.secret_key.length; + r.mac_address = "xxx" + r.mac_address.length; + that.log.debug("Robot Request Result: " + JSON.stringify(r)); + }); + + if (error) + { + that.log.debug("Robot Request Error: " + JSON.stringify(error)); + } + })); + // Get all robots from account client.getRobots((error, robots) => { if (error) { - this.log.error("Successful login but can't connect to your neato robot: " + error); - // TODO retry after x min - return; - } - else if (robots.length === 0) - { - this.log.error("Successful login but no robots associated with your account."); + this.log.error("Successful login but can't list your neato robots. Error: " + error); // TODO retry after x min return; } - this.log.info("Neato account has " + robots.length + " robot " + (robots.length === 1 ? "" : "s")); - - for (const robot of robots) + // Neato robots in account + if (robots.length === 0) { - // Get additional information for the robot + this.log.error("Neato account has no robots. Did you add your robot here: https://neatorobotics.com/my-neato/ ?"); + } + else + { + this.log.info("Neato account has " + robots.length + " robot" + (robots.length === 1 ? "" : "s")); + } + + // Neato robots in cache + this.log.debug("Plugin Cache has " + this.cachedRobotAccessories.length + " robot" + (this.cachedRobotAccessories.length === 1 ? "" : "s")); + for (let cachedRobot of this.cachedRobotAccessories) + { + let accountRobot = robots.find(robot => this.api.hap.uuid.generate(robot._serial) === cachedRobot.UUID); + if (accountRobot) + { + this.log.debug("[" + cachedRobot.displayName + "] Cached robot found in Neato account."); + } + else + { + this.log.error("[" + cachedRobot.displayName + "] Cached robot not found in Neato account. Robot will now be removed from homebridge."); + this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [cachedRobot]); + } + } + + // Add / Update homebridge accessories with robot information from neato. This must be done for new and existing robots to reflect changes in the name, firmware, pluginconfig etc. + for (let robot of robots) + { + // Check if robot already exists as an accessory + const uuid = this.api.hap.uuid.generate(robot._serial); + const cachedRobot = this.cachedRobotAccessories.find(accessory => accessory.UUID === uuid); + + if (cachedRobot) + { + this.log.debug("[" + robot.name + "] Updating meta information for robot in cache."); + } + else + { + this.log.debug("[" + robot.name + "] Getting meta information for new robot."); + } + robot.getState((error, state) => { if (error) { - this.log.error("Error getting robot meta information: " + error + ": " + state); + this.log.error("[" + robot.name + "] Error getting meta information. Is the robot connected to the internet? Error: " + error + ". State: " + state); } else { @@ -83,40 +133,33 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin { robot.meta = state.meta; - const uuid = this.api.hap.uuid.generate(robot._serial); - const existingAccessory = this.robotAccessories.find(accessory => accessory.UUID === uuid); - - // the accessory already exists - if (existingAccessory) + // Update existing robot accessor + if (cachedRobot) { - this.log.info("[" + robot.name + "] Robot loaded from cache"); // TODO update maps - existingAccessory.context.robot = robot; - this.api.updatePlatformAccessories([existingAccessory]); - - new NeatoVacuumRobotAccessory(this, existingAccessory, this.config); + cachedRobot.context.robot = robot; + this.api.updatePlatformAccessories([cachedRobot]); + new NeatoVacuumRobotAccessory(this, cachedRobot, this.config); + this.log.info("[" + robot.name + "] Successfully loaded from cache"); } + // Create new robot accessory else { - this.log.info("[" + robot.name + "] Robot created"); - const accessory = new this.api.platformAccessory(robot.name, uuid); - - accessory.context.robot = robot; - new NeatoVacuumRobotAccessory(this, accessory, this.config); - - // link the accessory to your platform - this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); // TODO get maps + + const newRobot = new this.api.platformAccessory(robot.name, uuid); + newRobot.context.robot = robot; + new NeatoVacuumRobotAccessory(this, newRobot, this.config); + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [newRobot]); + this.log.info("[" + robot.name + "] Successfully created as new robot"); } } catch (error) { - this.log.error("Error creating robot accessory: " + robot.name); - this.log.error(error); + this.log.error("[" + robot.name + "] Creating accessory failed. Error: " + error); throw new this.api.hap.HapStatusError(this.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); } - } // // Get all maps for each robot @@ -176,7 +219,7 @@ export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin } catch (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); + 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: " + error); } } }