From 313c532f87271d69f8dac0eb4d4e761bdca57615 Mon Sep 17 00:00:00 2001 From: Arne Blumentritt Date: Tue, 27 Apr 2021 19:35:12 +0200 Subject: [PATCH] WIP dynamic platform --- .eslintrc | 38 ++++ .github/ISSUE_TEMPLATE/bug-report.md | 44 +++++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature-request.md | 23 +++ .github/ISSUE_TEMPLATE/support-request.md | 38 ++++ .github/workflows/build.yml | 31 ++++ .gitignore | 119 ++++++++---- .npmignore | 135 ++++++++++++++ nodemon.json | 12 ++ package.json | 30 +++- src/accessories/neatoVacuumRobot.ts | 141 +++++++++++++++ src/homebridgeNeatoPlatform.ts | 210 ++++++++++++++++++++++ src/index.ts | 11 ++ src/settings.ts | 9 + tsconfig.json | 26 +++ 15 files changed, 831 insertions(+), 41 deletions(-) create mode 100644 .eslintrc create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/support-request.md create mode 100644 .github/workflows/build.yml create mode 100644 .npmignore create mode 100644 nodemon.json create mode 100644 src/accessories/neatoVacuumRobot.ts create mode 100644 src/homebridgeNeatoPlatform.ts create mode 100644 src/index.ts create mode 100644 src/settings.ts create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..ba9b8ef --- /dev/null +++ b/.eslintrc @@ -0,0 +1,38 @@ +{ + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" // uses the recommended rules from the @typescript-eslint/eslint-plugin + ], + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "ignorePatterns": [ + "dist" + ], + "rules": { + "quotes": ["warn", "single"], + "indent": ["warn", 2, { "SwitchCase": 1 }], + "semi": ["off"], + "comma-dangle": ["warn", "always-multiline"], + "dot-notation": "off", + "eqeqeq": "warn", + "curly": ["warn", "all"], + "brace-style": ["warn"], + "prefer-arrow-callback": ["warn"], + "max-len": ["warn", 140], + "no-console": ["warn"], // use the provided Homebridge log method instead + "no-non-null-assertion": ["off"], + "comma-spacing": ["error"], + "no-multi-spaces": ["warn", { "ignoreEOLComments": true }], + "no-trailing-spaces": ["warn"], + "lines-between-class-members": ["warn", "always", {"exceptAfterSingleLine": true}], + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/semi": ["warn"], + "@typescript-eslint/member-delimiter-style": ["warn"] + } +} diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..fd51059 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,44 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + + + +**Describe The Bug:** + + +**To Reproduce:** + + +**Expected behavior:** + + +**Logs:** + +``` +Show the Homebridge logs here, remove any sensitive information. +``` + +**Plugin Config:** + +```json +Show your Homebridge config.json here, remove any sensitive information. +``` + +**Screenshots:** + + +**Environment:** + +* **Plugin Version**: +* **Homebridge Version**: +* **Node.js Version**: +* **NPM Version**: +* **Operating System**: + + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..fe4e429 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +# blank_issues_enabled: false +# contact_links: +# - name: Homebridge Discord Community +# url: https://discord.gg/kqNCe2D +# about: Ask your questions in the #YOUR_CHANNEL_HERE channel \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..f974b3b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,23 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe:** + + +**Describe the solution you'd like:** + + +**Describe alternatives you've considered:** + + +**Additional context:** + + + + diff --git a/.github/ISSUE_TEMPLATE/support-request.md b/.github/ISSUE_TEMPLATE/support-request.md new file mode 100644 index 0000000..c64c5ff --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support-request.md @@ -0,0 +1,38 @@ +--- +name: Support Request +about: Need help? +title: '' +labels: question +assignees: '' + +--- + + + +**Describe Your Problem:** + + +**Logs:** + +``` +Show the Homebridge logs here, remove any sensitive information. +``` + +**Plugin Config:** + +```json +Show your Homebridge config.json here, remove any sensitive information. +``` + +**Screenshots:** + + +**Environment:** + +* **Plugin Version**: +* **Homebridge Version**: +* **Node.js Version**: +* **NPM Version**: +* **Operating System**: + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1ed1c13 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,31 @@ +name: Build and Lint + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + # the Node.js versions to build on + node-version: [10.x, 12.x, 13.x, 14.x, 15.x] + + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: npm install + + - name: Lint the project + run: npm run lint + + - name: Build the project + run: npm run build + env: + CI: true diff --git a/.gitignore b/.gitignore index 066a135..761da75 100644 --- a/.gitignore +++ b/.gitignore @@ -1,75 +1,120 @@ +# Ignore compiled code +dist + +# ------------- Defaults ------------- # + # Logs logs *.log npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed +*.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage +*.lcov # nyc test coverage .nyc_output -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt +# Bower dependency directory (https://bower.io/) +bower_components + # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) +# Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories -node_modules -jspm_packages +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo # Optional npm cache directory .npm +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + # 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 +# Output of 'npm pack' +*.tgz -# IDEs and editors -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace -*.iml +# Yarn Integrity file +.yarn-integrity -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json +# dotenv environment variables file +.env +.env.test -# misc -/.sass-cache -/connect.lock -/libpeerconnection.log -yarn-error.log -testem.log -/typings +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache -# System Files -.DS_Store -Thumbs.db -package-lock.json \ No newline at end of file +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.pnp.* \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..b97a6c1 --- /dev/null +++ b/.npmignore @@ -0,0 +1,135 @@ +# Ignore source code +src + +# ------------- Defaults ------------- # + +# gitHub actions +.github + +# eslint +.eslintrc + +# typescript +tsconfig.json + +# vscode +.vscode + +# nodemon +nodemon.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.pnp.* \ No newline at end of file diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..6dd7df6 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,12 @@ +{ + "watch": [ + "src" + ], + "ext": "ts", + "ignore": [], + "exec": "tsc && homebridge -I -D", + "signal": "SIGTERM", + "env": { + "NODE_OPTIONS": "--trace-warnings" + } +} \ No newline at end of file diff --git a/package.json b/package.json index 24d087f..c7750ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "homebridge-neato", - "version": "0.7.3", + "displayName": "Homebridge Neato", + "version": "1.0.0-beta.1", "description": "A Neato vacuum robot plugin for homebridge.", "license": "MIT", "keywords": [ @@ -9,8 +10,15 @@ "botvac" ], "engines": { - "node": ">=0.12.0", - "homebridge": ">=0.2.0" + "node": ">=10.17.0", + "homebridge": ">=1.3.0" + }, + "main": "dist/index.js", + "scripts": { + "lint": "eslint src/**.ts --max-warnings=0", + "watch": "npm run build && npm link && nodemon", + "build": "rimraf ./dist && tsc", + "prepublishOnly": "npm run lint && npm run build" }, "author": { "name": "Arne Blumentritt", @@ -34,10 +42,24 @@ "type": "git", "url": "git://github.com/naofireblade/homebridge-neato.git" }, + "bugs": { + "url": "https://github.com/naofireblade/homebridge-neato/issues" + }, "dependencies": { "colors": "^1.4.0", "debug": "^4.1.1", - "node-botvac": ">=0.4.0", + "node-botvac": "^0.4.0", "uuid": "^3.3.2" + }, + "devDependencies": { + "@types/node": "^14.14.31", + "@typescript-eslint/eslint-plugin": "^4.16.1", + "@typescript-eslint/parser": "^4.16.1", + "eslint": "^7.21.0", + "homebridge": "^1.3.1", + "nodemon": "^2.0.7", + "rimraf": "^3.0.2", + "ts-node": "^9.1.1", + "typescript": "^4.2.2" } } diff --git a/src/accessories/neatoVacuumRobot.ts b/src/accessories/neatoVacuumRobot.ts new file mode 100644 index 0000000..79022fe --- /dev/null +++ b/src/accessories/neatoVacuumRobot.ts @@ -0,0 +1,141 @@ +import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge'; + +import { HomebridgeNeatoPlatform } from '../homebridgeNeatoPlatform'; + +/** + * Platform Accessory + * An instance of this class is created for each accessory your platform registers + * Each accessory may expose multiple services of different service types. + */ +export class NeatoVacuumRobotAccessory { + private service: Service; + + /** + * These are just used to create a working example + * You should implement your own code to track the state of your accessory + */ + private exampleStates = { + On: false, + Brightness: 100, + }; + + constructor( + private readonly platform: HomebridgeNeatoPlatform, + private readonly accessory: PlatformAccessory, + ) { + + // set accessory information + this.accessory.getService(this.platform.Service.AccessoryInformation)! + .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Default-Manufacturer') + .setCharacteristic(this.platform.Characteristic.Model, 'Default-Model') + .setCharacteristic(this.platform.Characteristic.SerialNumber, 'Default-Serial'); + + // get the LightBulb service if it exists, otherwise create a new LightBulb service + // you can create multiple services for each accessory + this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb); + + // set the service name, this is what is displayed as the default name on the Home app + // in this example we are using the name we stored in the `accessory.context` in the `discoverDevices` method. + this.service.setCharacteristic(this.platform.Characteristic.Name, accessory.context.device.exampleDisplayName); + + // each service must implement at-minimum the "required characteristics" for the given service type + // see https://developers.homebridge.io/#/service/Lightbulb + + // register handlers for the On/Off Characteristic + this.service.getCharacteristic(this.platform.Characteristic.On) + .onSet(this.setOn.bind(this)) // SET - bind to the `setOn` method below + .onGet(this.getOn.bind(this)); // GET - bind to the `getOn` method below + + // register handlers for the Brightness Characteristic + this.service.getCharacteristic(this.platform.Characteristic.Brightness) + .onSet(this.setBrightness.bind(this)); // SET - bind to the 'setBrightness` method below + + /** + * Creating multiple services of the same type. + * + * To avoid "Cannot add a Service with the same UUID another Service without also defining a unique 'subtype' property." error, + * when creating multiple services of the same type, you need to use the following syntax to specify a name and subtype id: + * this.accessory.getService('NAME') || this.accessory.addService(this.platform.Service.Lightbulb, 'NAME', 'USER_DEFINED_SUBTYPE_ID'); + * + * The USER_DEFINED_SUBTYPE must be unique to the platform accessory (if you platform exposes multiple accessories, each accessory + * can use the same sub type id.) + */ + + // Example: add two "motion sensor" services to the accessory + const motionSensorOneService = this.accessory.getService('Motion Sensor One Name') || + this.accessory.addService(this.platform.Service.MotionSensor, 'Motion Sensor One Name', 'YourUniqueIdentifier-1'); + + const motionSensorTwoService = this.accessory.getService('Motion Sensor Two Name') || + this.accessory.addService(this.platform.Service.MotionSensor, 'Motion Sensor Two Name', 'YourUniqueIdentifier-2'); + + /** + * Updating characteristics values asynchronously. + * + * Example showing how to update the state of a Characteristic asynchronously instead + * of using the `on('get')` handlers. + * Here we change update the motion sensor trigger states on and off every 10 seconds + * the `updateCharacteristic` method. + * + */ + let motionDetected = false; + setInterval(() => { + // EXAMPLE - inverse the trigger + motionDetected = !motionDetected; + + // push the new value to HomeKit + motionSensorOneService.updateCharacteristic(this.platform.Characteristic.MotionDetected, motionDetected); + motionSensorTwoService.updateCharacteristic(this.platform.Characteristic.MotionDetected, !motionDetected); + + this.platform.log.debug('Triggering motionSensorOneService:', motionDetected); + this.platform.log.debug('Triggering motionSensorTwoService:', !motionDetected); + }, 10000); + } + + /** + * Handle "SET" requests from HomeKit + * These are sent when the user changes the state of an accessory, for example, turning on a Light bulb. + */ + async setOn(value: CharacteristicValue) { + // implement your own code to turn your device on/off + this.exampleStates.On = value as boolean; + + this.platform.log.debug('Set Characteristic On ->', value); + } + + /** + * Handle the "GET" requests from HomeKit + * These are sent when HomeKit wants to know the current state of the accessory, for example, checking if a Light bulb is on. + * + * GET requests should return as fast as possbile. A long delay here will result in + * HomeKit being unresponsive and a bad user experience in general. + * + * If your device takes time to respond you should update the status of your device + * asynchronously instead using the `updateCharacteristic` method instead. + + * @example + * this.service.updateCharacteristic(this.platform.Characteristic.On, true) + */ + async getOn(): Promise { + // implement your own code to check if the device is on + const isOn = this.exampleStates.On; + + this.platform.log.debug('Get Characteristic On ->', isOn); + + // if you need to return an error to show the device as "Not Responding" in the Home app: + // throw new this.platform.api.hap.HapStatusError(this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE); + + return isOn; + } + + /** + * Handle "SET" requests from HomeKit + * These are sent when the user changes the state of an accessory, for example, changing the Brightness + */ + async setBrightness(value: CharacteristicValue) { + // implement your own code to set the brightness + this.exampleStates.Brightness = value as number; + + this.platform.log.debug('Set Characteristic Brightness -> ', value); + } + +} diff --git a/src/homebridgeNeatoPlatform.ts b/src/homebridgeNeatoPlatform.ts new file mode 100644 index 0000000..18e595a --- /dev/null +++ b/src/homebridgeNeatoPlatform.ts @@ -0,0 +1,210 @@ +import {API, Characteristic, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service} from 'homebridge'; +import Debug from "debug"; +import NeatoApi from "node-botvac"; +import {PLATFORM_NAME, PLUGIN_NAME} from './settings'; +import {NeatoVacuumRobotAccessory} from './accessories/NeatoVacuumRobot'; + +const debug = Debug("homebridge-neato"); + +/** + * HomebridgePlatform + * This class is the main constructor for your plugin, this is where you should + * parse the user config and discover/register accessories with Homebridge. + */ +export class HomebridgeNeatoPlatform implements DynamicPlatformPlugin +{ + public readonly Service: typeof Service = this.api.hap.Service; + public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic; + + // this is used to track restored cached accessories + public readonly accessories: PlatformAccessory[] = []; + + constructor( + public readonly log: Logger, + public readonly config: PlatformConfig, + public readonly api: API) + { + this.log.debug('Finished initializing platform:', this.config.name); + + this.api.on('didFinishLaunching', () => { + log.debug('Executed didFinishLaunching callback'); + this.discoverRobots(); + }); + } + + /** + * This function is invoked when homebridge restores cached accessories from disk at startup. + * It should be used to setup event handlers for characteristics and update respective values. + */ + configureAccessory(accessory: PlatformAccessory) + { + this.log.info('Loading accessory from cache:', accessory.displayName); + + // add the restored accessory to the accessories cache so we can track if it has already been registered + this.accessories.push(accessory); + } + + discoverRobots() + { + debug("Discovering new robots"); + let client = new NeatoApi.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.robots = []; + 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(); + } + } + }) + }); + } + }); + } + }); + }); + } + }); + } + }); + + const exampleDevices = [ + { + exampleUniqueId: 'ABCD', + exampleDisplayName: 'Bedroom', + }, + { + exampleUniqueId: 'EFGH', + exampleDisplayName: 'Kitchen', + }, + ]; + + // loop over the discovered devices and register each one if it has not already been registered + for (const device of exampleDevices) + { + + // generate a unique id for the accessory this should be generated from + // something globally unique, but constant, for example, the device serial + // number or MAC address + const uuid = this.api.hap.uuid.generate(device.exampleUniqueId); + + // see if an accessory with the same uuid has already been registered and restored from + // the cached devices we stored in the `configureAccessory` method above + const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid); + + if (existingAccessory) + { + // the accessory already exists + this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); + + // if you need to update the accessory.context then you should run `api.updatePlatformAccessories`. eg.: + // existingAccessory.context.device = device; + // this.api.updatePlatformAccessories([existingAccessory]); + + // create the accessory handler for the restored accessory + // this is imported from `platformAccessory.ts` + new NeatoVacuumRobotAccessory(this, existingAccessory); + + // it is possible to remove platform accessories at any time using `api.unregisterPlatformAccessories`, eg.: + // remove platform accessories when no longer present + // this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [existingAccessory]); + // this.log.info('Removing existing accessory from cache:', existingAccessory.displayName); + } + else + { + // the accessory does not yet exist, so we need to create it + this.log.info('Adding new accessory:', device.exampleDisplayName); + + // create a new accessory + const accessory = new this.api.platformAccessory(device.exampleDisplayName, uuid); + + // store a copy of the device object in the `accessory.context` + // the `context` property can be used to store any data about the accessory you may need + accessory.context.device = device; + + // create the accessory handler for the newly create accessory + // this is imported from `platformAccessory.ts` + new NeatoVacuumRobotAccessory(this, accessory); + + // link the accessory to your platform + this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); + } + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..f7048eb --- /dev/null +++ b/src/index.ts @@ -0,0 +1,11 @@ +import { API } from 'homebridge'; + +import { PLATFORM_NAME } from './settings'; +import { HomebridgeNeatoPlatform } from './platform'; + +/** + * This method registers the platform with Homebridge + */ +export = (api: API) => { + api.registerPlatform(PLATFORM_NAME, HomebridgeNeatoPlatform); +}; diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..be8b746 --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,9 @@ +/** + * This is the name of the platform that users will use to register the plugin in the Homebridge config.json + */ +export const PLATFORM_NAME = 'NeatoVacuumRobot'; + +/** + * This must match the name of your plugin as defined the package.json + */ +export const PLUGIN_NAME = 'homebridge-neato'; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bf07099 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2018", // ~node10 + "module": "commonjs", + "lib": [ + "es2015", + "es2016", + "es2017", + "es2018" + ], + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "noImplicitAny": false + }, + "include": [ + "src/" + ], + "exclude": [ + "**/*.spec.ts" + ] +}