From d320c2e146dbb663ff5981034c497497b433a2b8 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 09:27:53 +0100 Subject: [PATCH 01/11] Add StatelessSwitch accessory. Set Homebdrige server name from service name. Set Homebridge server random mac, unique by plugin instance --- lib/HomebridgeConfig.js | 43 +++++++++++++-- lib/homebridge-config.json | 4 +- lib/plugins/StatelessSwitchFactory.js | 75 +++++++++++++++++++++++++++ lib/statics.js | 8 +-- npm-shrinkwrap.json | 24 +++++++-- package.json | 3 +- plugin/index.js | 4 ++ 7 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 lib/plugins/StatelessSwitchFactory.js diff --git a/lib/HomebridgeConfig.js b/lib/HomebridgeConfig.js index 9786b03..4dea3a6 100644 --- a/lib/HomebridgeConfig.js +++ b/lib/HomebridgeConfig.js @@ -5,6 +5,7 @@ const path = require('path') const fsExtra = require('fs-extra') const ip = require('ip') const { cloneDeep } = require('lodash') +const randomMac = require('random-mac') const homebridgeConfig = require('./homebridge-config.json') @@ -12,8 +13,10 @@ const { HOMEBRIDGE_PATH, WRITING_HOMEBRIDGE_CONFIG, HOMEBRIDGE_PORT, + HOMEBRIDGE_MAC_STORAGE_KEY, - ACCESORY_SWITCH_NAME + ACCESORY_SWITCH_NAME, + ACCESORY_STATELESS_SWITCH_NAME } = require('./statics') class Accesories { @@ -53,18 +56,52 @@ class Accesories { }) } + getStatelessSwitchs (abilities, pluginConnection) { + // Abilities with no defined data that have an action can be considered as Domapic "statelessSwitch", that will be mapped to homebridge "switches". Will return false as state always + const switchValidAbilities = abilities.filter(ability => ability.type === undefined && ability.action === true) + return switchValidAbilities.map(ability => { + return { + accessory: ACCESORY_STATELESS_SWITCH_NAME, + apiKey: pluginConnection.apiKey, + bridgeUrl: `${pluginConnection.url}${ability._id}`, + servicePackageName: ability.service.package, + serviceName: ability.service.name, + serviceProcessId: ability.service.processId, + abilityName: ability.name, + name: `${ability.service.name} ${ability.name}` + } + }) + } + async getAccesories (abilities) { const pluginConnection = await this.getPluginConnection() return [ - ...this.getSwitchs(abilities, pluginConnection) + ...this.getSwitchs(abilities, pluginConnection), + ...this.getStatelessSwitchs(abilities, pluginConnection) ] } + async getMac () { + let mac + try { + mac = await this.plugin.storage.get(HOMEBRIDGE_MAC_STORAGE_KEY) + } catch (err) { + mac = randomMac() + await this.plugin.storage.set(HOMEBRIDGE_MAC_STORAGE_KEY, mac) + } + return mac + } + async write (abilities) { await this.tracer.info(WRITING_HOMEBRIDGE_CONFIG) + const pluginConfig = await this.plugin.config.get() this.config = cloneDeep(homebridgeConfig) - this.config.bridge.port = await this.plugin.config.get(HOMEBRIDGE_PORT) + + this.config.bridge.name = pluginConfig.name + this.config.bridge.username = await this.getMac() + this.config.bridge.port = pluginConfig[HOMEBRIDGE_PORT] this.config.accessories = await this.getAccesories(abilities) + await fsExtra.writeJson(this.configPath, this.config, { spaces: 2 }) diff --git a/lib/homebridge-config.json b/lib/homebridge-config.json index 43b7549..4c9a93e 100644 --- a/lib/homebridge-config.json +++ b/lib/homebridge-config.json @@ -1,9 +1,7 @@ { "bridge": { - "name": "Homebridge", - "username": "CC:22:3D:E3:CE:30" }, - "description": "Homebridge server for Domapic Homekit integration", + "description": "Homebridge server for Domapic HomeKit integration", "accessories": [ ] } diff --git a/lib/plugins/StatelessSwitchFactory.js b/lib/plugins/StatelessSwitchFactory.js new file mode 100644 index 0000000..d34c1d9 --- /dev/null +++ b/lib/plugins/StatelessSwitchFactory.js @@ -0,0 +1,75 @@ +'use strict' + +const url = require('url') + +const requestPromise = require('request-promise') + +const { DOMAPIC, HOMEBRIDGE_ERROR, ACCESORY_STATELESS_SWITCH_NAME } = require('../statics') + +const StatelessSwitchFactory = function (Service, Characteristic) { + return class Switch { + constructor (log, config) { + this.config = config + this.log = log + this.bridgeUrl = url.parse(this.config.bridgeUrl) + this.getServices = this.getServices.bind(this) + this.getSwitchOnCharacteristic = this.getSwitchOnCharacteristic.bind(this) + this.setSwitchOnCharacteristic = this.setSwitchOnCharacteristic.bind(this) + + this.requestOptions = { + uri: this.bridgeUrl, + json: true, + headers: { + 'X-Api-Key': this.config.apiKey + }, + method: 'GET' + } + } + + static get name () { + return ACCESORY_STATELESS_SWITCH_NAME + } + + logError (error) { + this.log(`${HOMEBRIDGE_ERROR} ${error.message}`) + } + + getServices () { + const informationService = new Service.AccessoryInformation() + informationService + .setCharacteristic(Characteristic.Manufacturer, DOMAPIC) + .setCharacteristic(Characteristic.Model, this.config.servicePackageName) + .setCharacteristic(Characteristic.SerialNumber, this.config.serviceProcessId) + + const switchService = new Service.Switch(this.config.name) + switchService.getCharacteristic(Characteristic.On) + .on('get', this.getSwitchOnCharacteristic) + .on('set', this.setSwitchOnCharacteristic) + + this.informationService = informationService + this.switchService = switchService + return [informationService, switchService] + } + + getSwitchOnCharacteristic (next) { + this.log(`Getting state. Returning false`) + next(false) + } + + setSwitchOnCharacteristic (on, next) { + this.log(`Triggering`) + return requestPromise({ + ...this.requestOptions, + method: 'POST' + }).then(() => { + this.log(`Trigger success`) + next() + }).catch(error => { + this.logError(error) + next(error) + }) + } + } +} + +module.exports = StatelessSwitchFactory diff --git a/lib/statics.js b/lib/statics.js index 4f41f20..82c49b0 100644 --- a/lib/statics.js +++ b/lib/statics.js @@ -11,6 +11,7 @@ module.exports = { DOMAPIC, LOADING_ABILITIES: 'Loading abilities from Controller', WRITING_HOMEBRIDGE_CONFIG: 'Writing Homebridge configuration file', + HOMEBRIDGE_MAC_STORAGE_KEY: 'username_mac', HOMEBRIDGE_LOG: '[Homebrigde log]', HOMEBRIDGE_ERROR: 'ERROR:', HOMEBRIDGE_EXITED: 'Homebridge exited with code', @@ -20,8 +21,9 @@ module.exports = { PACKAGE_PATH, HOMEBRIDGE_PATH, - ACCESORY_SWITCH_NAME: `${DOMAPIC}Switch`, - SENDING_ABILITY_ACTION: `Sending action to ability`, - GETTING_ABILITY_STATE: `Getting state of ability` + GETTING_ABILITY_STATE: `Getting state of ability`, + + ACCESORY_SWITCH_NAME: `${DOMAPIC}Switch`, + ACCESORY_STATELESS_SWITCH_NAME: `${DOMAPIC}StatelessSwitch` } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 92fbcfc..8f13318 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1487,6 +1487,13 @@ "jsonschema": "1.2.4", "lodash": "4.17.10", "rand-token": "0.4.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + } } }, "dont-sniff-mimetype": { @@ -3982,9 +3989,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash.debounce": { "version": "4.0.8", @@ -4499,6 +4506,12 @@ "uglify-js": "^2.6" } }, + "lodash": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "dev": true + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -5387,6 +5400,11 @@ "resolved": "https://registry.npmjs.org/rand-token/-/rand-token-0.4.0.tgz", "integrity": "sha512-FLNNsir2R+XY8LKsZ+8u/w0qZ4sGit7cpNdznsI77cAVob6UlVPueDKRyjJ3W1Q6FJLgAVH98JvlqqpSaL7NEQ==" }, + "random-mac": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/random-mac/-/random-mac-0.0.4.tgz", + "integrity": "sha1-NlD3Bbb592XbQiM34fl0eKuSIVw=" + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", diff --git a/package.json b/package.json index 9235e11..5e1e487 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "fs-extra": "7.0.1", "homebridge": "0.4.45", "ip": "1.1.5", - "lodash": "4.17.10", + "lodash": "4.17.11", + "random-mac": "0.0.4", "request-promise": "4.2.2", "tree-kill": "1.2.1" }, diff --git a/plugin/index.js b/plugin/index.js index 1201b17..a514b75 100644 --- a/plugin/index.js +++ b/plugin/index.js @@ -2,10 +2,14 @@ const { DOMAPIC } = require('../lib/statics') const SwitchFactory = require('../lib/plugins/SwitchFactory') +const StatelessSwitchFactory = require('../lib/plugins/StatelessSwitchFactory') module.exports = function (homebridge) { const Switch = new SwitchFactory(homebridge.hap.Service, homebridge.hap.Characteristic) homebridge.registerAccessory(DOMAPIC, Switch.name, Switch) + const StatelessSwitch = new StatelessSwitchFactory(homebridge.hap.Service, homebridge.hap.Characteristic) + homebridge.registerAccessory(DOMAPIC, StatelessSwitch.name, StatelessSwitch) + // TODO, register accesories for all HomeKit accesory types } From ca2aef3eadba13afea4bb914e2d86d726acced28 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 09:52:22 +0100 Subject: [PATCH 02/11] Write homebridge config in domapic storage folder --- lib/Homebridge.js | 4 +++- lib/HomebridgeConfig.js | 7 +++++-- lib/statics.js | 3 +-- test/unit/Domapic.mocks.js | 3 ++- test/unit/FsExtra.mocks.js | 3 ++- test/unit/lib/Homebridge.specs.js | 2 +- test/unit/lib/HomebridgeConfig.specs.js | 17 +++++++++-------- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/Homebridge.js b/lib/Homebridge.js index 317b6fd..0437624 100644 --- a/lib/Homebridge.js +++ b/lib/Homebridge.js @@ -18,6 +18,7 @@ const { class HomeBridge { constructor (dpmcPlugin) { + this.plugin = dpmcPlugin this.tracer = dpmcPlugin.tracer this.process = null this.binPath = path.resolve(PACKAGE_PATH, 'node_modules', '.bin', 'homebridge') @@ -46,9 +47,10 @@ class HomeBridge { async start () { await this.tracer.info(HOMEBRIDGE_STARTING) await this.writePackageJson() + const homebridgePath = path.resolve(await this.plugin.storage.getPath(), HOMEBRIDGE_PATH) this.process = childProcess.spawn(this.binPath, [ '-U', - HOMEBRIDGE_PATH, + homebridgePath, '-P', this.pluginPath ], { diff --git a/lib/HomebridgeConfig.js b/lib/HomebridgeConfig.js index 4dea3a6..c44b67d 100644 --- a/lib/HomebridgeConfig.js +++ b/lib/HomebridgeConfig.js @@ -21,7 +21,6 @@ const { class Accesories { constructor (dpmcPlugin) { - this.configPath = path.resolve(HOMEBRIDGE_PATH, 'config.json') this.plugin = dpmcPlugin this.tracer = dpmcPlugin.tracer } @@ -95,6 +94,8 @@ class Accesories { async write (abilities) { await this.tracer.info(WRITING_HOMEBRIDGE_CONFIG) const pluginConfig = await this.plugin.config.get() + const homebridgePath = path.resolve(await this.plugin.storage.getPath(), HOMEBRIDGE_PATH) + this.config = cloneDeep(homebridgeConfig) this.config.bridge.name = pluginConfig.name @@ -102,7 +103,9 @@ class Accesories { this.config.bridge.port = pluginConfig[HOMEBRIDGE_PORT] this.config.accessories = await this.getAccesories(abilities) - await fsExtra.writeJson(this.configPath, this.config, { + fsExtra.ensureDirSync(homebridgePath) + + await fsExtra.writeJson(path.resolve(homebridgePath, 'config.json'), this.config, { spaces: 2 }) } diff --git a/lib/statics.js b/lib/statics.js index 82c49b0..69f06b3 100644 --- a/lib/statics.js +++ b/lib/statics.js @@ -3,7 +3,6 @@ const path = require('path') const PACKAGE_PATH = path.resolve(__dirname, '..') -const HOMEBRIDGE_PATH = path.resolve(PACKAGE_PATH, '.homebridge') const DOMAPIC = 'Domapic' @@ -17,9 +16,9 @@ module.exports = { HOMEBRIDGE_EXITED: 'Homebridge exited with code', HOMEBRIDGE_STOPPING: 'Stopping Homebridge server', HOMEBRIDGE_STARTING: 'Starting Homebridge server', + HOMEBRIDGE_PATH: 'homebridge', HOMEBRIDGE_PORT: 'homebridgePort', PACKAGE_PATH, - HOMEBRIDGE_PATH, SENDING_ABILITY_ACTION: `Sending action to ability`, GETTING_ABILITY_STATE: `Getting state of ability`, diff --git a/test/unit/Domapic.mocks.js b/test/unit/Domapic.mocks.js index a8fa602..7c933ff 100644 --- a/test/unit/Domapic.mocks.js +++ b/test/unit/Domapic.mocks.js @@ -22,7 +22,8 @@ const Mock = function () { }, storage: { get: sandbox.stub().resolves(), - set: sandbox.stub().resolves() + set: sandbox.stub().resolves(), + getPath: sandbox.stub().resolves('') }, tracer: { info: sandbox.stub().resolves(), diff --git a/test/unit/FsExtra.mocks.js b/test/unit/FsExtra.mocks.js index 4952cbe..3d5c0c7 100644 --- a/test/unit/FsExtra.mocks.js +++ b/test/unit/FsExtra.mocks.js @@ -9,7 +9,8 @@ const Mock = function () { const stubs = { copy: sandbox.stub().resolves(), - writeJson: sandbox.stub().resolves() + writeJson: sandbox.stub().resolves(), + ensureDirSync: sandbox.stub() } const restore = () => { diff --git a/test/unit/lib/Homebridge.specs.js b/test/unit/lib/Homebridge.specs.js index a3b3cd0..47f9dd4 100644 --- a/test/unit/lib/Homebridge.specs.js +++ b/test/unit/lib/Homebridge.specs.js @@ -36,7 +36,7 @@ test.describe('HomeBridge', () => { const packagePath = path.resolve(__dirname, '..', '..', '..') const pluginPath = path.resolve(packagePath, 'plugin') const binPath = path.resolve(packagePath, 'node_modules', '.bin', 'homebridge') - const homebridgePath = path.resolve(packagePath, '.homebridge') + const homebridgePath = path.resolve(packagePath, 'homebridge') test.it('should copy the plugin package.json file to the plugin folder', () => { const origin = path.resolve(packagePath, 'lib', 'plugin-package.json') diff --git a/test/unit/lib/HomebridgeConfig.specs.js b/test/unit/lib/HomebridgeConfig.specs.js index 93a2da1..2741200 100644 --- a/test/unit/lib/HomebridgeConfig.specs.js +++ b/test/unit/lib/HomebridgeConfig.specs.js @@ -7,6 +7,12 @@ const FsExtraMocks = require('../FsExtra.mocks') const IpMocks = require('../Ip.mocks') test.describe('Homebridge Config', () => { + const fooConfig = { + homebridgePort: 3422, + port: 'foo-port', + hostName: '', + name: 'foo-name' + } let HomebridgeConfig let homebridgeConfig let ip @@ -46,10 +52,7 @@ test.describe('Homebridge Config', () => { ip = new IpMocks() HomebridgeConfig = require('../../../lib/HomebridgeConfig') - domapic.stubs.plugin.config.get.resolves({ - port: 'foo-port', - hostName: '' - }) + domapic.stubs.plugin.config.get.resolves(fooConfig) ip.stubs.address.returns('foo-host') @@ -69,7 +72,7 @@ test.describe('Homebridge Config', () => { }) test.describe('write method', () => { - const homebridgeConfigPath = path.resolve(__dirname, '..', '..', '..', '.homebridge', 'config.json') + const homebridgeConfigPath = path.resolve(__dirname, '..', '..', '..', 'homebridge', 'config.json') test.it('should write abilities based configuration in homebridge folder', () => { return homebridgeConfig.write(fooAbilities) .then(() => { @@ -95,11 +98,9 @@ test.describe('Homebridge Config', () => { }) test.it('should set port based on homebridgePort plugin configuration', () => { - const fooPort = 3422 - domapic.stubs.plugin.config.get.withArgs('homebridgePort').resolves(fooPort) return homebridgeConfig.write(fooAbilities) .then(() => { - return test.expect(fsExtra.stubs.writeJson.getCall(0).args[1].bridge.port).to.equal(fooPort) + return test.expect(fsExtra.stubs.writeJson.getCall(0).args[1].bridge.port).to.equal(fooConfig.homebridgePort) }) }) }) From a2bd4998c0979db3eb8c8dac1e606249ca15a605 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 09:53:24 +0100 Subject: [PATCH 03/11] Upgrade domapic-service version --- npm-shrinkwrap.json | 25 ++++++++++--------------- package.json | 2 +- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8f13318..025a54a 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -31,9 +31,9 @@ } }, "@pm2/agent-node": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@pm2/agent-node/-/agent-node-1.0.12.tgz", - "integrity": "sha512-wbTc68qSEZ9nnAYFYQKN9xSrQAeukkPvzpktz140EKq7iNkpRnW1UFmUez3PgX8diIHzc0TUCE2D1F7KxAj72Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pm2/agent-node/-/agent-node-1.1.0.tgz", + "integrity": "sha512-mgs/WwI2y+4CCn4wdkEwrbIxhRtp4uokKt7q2SvanZwR6MxnrWH9qU2uZGKJg23PGlv48imYG6MZv0zUFfWPoQ==", "requires": { "debug": "^3.1.0", "eventemitter2": "^5.0.1", @@ -1423,9 +1423,9 @@ } }, "domapic-base": { - "version": "1.0.0-beta.17", - "resolved": "https://registry.npmjs.org/domapic-base/-/domapic-base-1.0.0-beta.17.tgz", - "integrity": "sha512-d3wFDdOc3bhI8iFeBTSGeNS62DbVFkQ6xgfaqYtUdHW0Qw2Yw2ENcVuokj5QKGBP9S0duu7tDHQpwCTfw+BowA==", + "version": "1.0.0-beta.18", + "resolved": "https://registry.npmjs.org/domapic-base/-/domapic-base-1.0.0-beta.18.tgz", + "integrity": "sha512-1YeH1PXQFTCPdViVNT9R7SZs59JuPGS8WMolez6Kn3oPV+06NNfb4GSeAoM2Z8O6689biruRoi2khPhnC1VTwg==", "requires": { "bluebird": "3.5.2", "body-parser": "1.18.3", @@ -1466,22 +1466,17 @@ "jsonfile": "^4.0.0", "universalify": "^0.1.0" } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" } } }, "domapic-service": { - "version": "1.0.0-alpha.3", - "resolved": "https://registry.npmjs.org/domapic-service/-/domapic-service-1.0.0-alpha.3.tgz", - "integrity": "sha512-3Lisnrkj4VfBGypLuFk3GWScXmiuiAQBAiCVi5TBwREM9g7xx2Rypon5xo7Q6g3T9Qu3ziGvlHsiujwN66G6Ig==", + "version": "1.0.0-alpha.4", + "resolved": "https://registry.npmjs.org/domapic-service/-/domapic-service-1.0.0-alpha.4.tgz", + "integrity": "sha512-AwLT1pKIwvkzWuE2KFsL12cvJddEWHBTf0WlzC9cAcTxqfYLWxiCuHinrvvfCOtPe9JYcvn7/8FsEKzLfcQdnQ==", "requires": { "bluebird": "3.5.1", "deep-equal": "1.0.1", - "domapic-base": "1.0.0-beta.17", + "domapic-base": "1.0.0-beta.18", "ip": "1.1.5", "is-promise": "2.1.0", "jsonschema": "1.2.4", diff --git a/package.json b/package.json index 5e1e487..aaa9537 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "domapic-homebridge": "./bin/domapic-homebridge" }, "dependencies": { - "domapic-service": "1.0.0-alpha.3", + "domapic-service": "1.0.0-alpha.4", "fs-extra": "7.0.1", "homebridge": "0.4.45", "ip": "1.1.5", From e50c26ba0a20917424f373d6d2d831c403e58a83 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 09:56:51 +0100 Subject: [PATCH 04/11] Upgrade version --- CHANGELOG.md | 12 ++++++++++++ npm-shrinkwrap.json | 2 +- package.json | 2 +- sonar-project.properties | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30201e0..ab744a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Fixed ### Removed +## [1.0.0-beta.2] - 2018-12-09 +### Added +- Add StatelessSwitch accessory + +### Changed +- Set Homebridge server name from service name +- Set Homebridge server random mac, unique by plugin instance +- Upgraded domapic-service dependency + +### Fixed +- Write homebridge config in domapic storage folder instead of a child of package folder + ## [1.0.0-beta.1] - 2018-12-07 ### Added - First prerelease diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 025a54a..7d309c0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "homebridge-domapic-plugin", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index aaa9537..f149855 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-domapic-plugin", - "version": "1.0.0-beta.1", + "version": "1.0.0-beta.2", "description": "Domapic plugin for controlling modules with Apple's HomeKit", "main": "server.js", "bin": { diff --git a/sonar-project.properties b/sonar-project.properties index 1d1f459..77943c1 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,6 +1,6 @@ sonar.organization=domapic sonar.projectKey=homebridge-domapic-plugin -sonar.projectVersion=1.0.0-beta.1 +sonar.projectVersion=1.0.0-beta.2 sonar.sources=. sonar.exclusions=node_modules/** From fd514c066fe77aed3fcc1722c97652c0e7ccf6ad Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 10:07:16 +0100 Subject: [PATCH 05/11] Convert mac to uppercase --- lib/HomebridgeConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/HomebridgeConfig.js b/lib/HomebridgeConfig.js index c44b67d..f380aeb 100644 --- a/lib/HomebridgeConfig.js +++ b/lib/HomebridgeConfig.js @@ -4,7 +4,7 @@ const path = require('path') const fsExtra = require('fs-extra') const ip = require('ip') -const { cloneDeep } = require('lodash') +const { cloneDeep, toUpper } = require('lodash') const randomMac = require('random-mac') const homebridgeConfig = require('./homebridge-config.json') @@ -85,7 +85,7 @@ class Accesories { try { mac = await this.plugin.storage.get(HOMEBRIDGE_MAC_STORAGE_KEY) } catch (err) { - mac = randomMac() + mac = toUpper(randomMac()) await this.plugin.storage.set(HOMEBRIDGE_MAC_STORAGE_KEY, mac) } return mac From d2d3952f466fcab6af99c84f7bb8314c210e2fb6 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 10:32:14 +0100 Subject: [PATCH 06/11] Add unit tests --- lib/HomebridgeConfig.js | 43 +++--- lib/plugins/StatelessSwitchFactory.js | 3 +- test/unit/lib/HomebridgeConfig.specs.js | 57 +++++++- .../plugins/StatelessSwitchFactory.mocks.js | 32 +++++ .../plugins/StatelessSwitchFactory.specs.js | 136 ++++++++++++++++++ test/unit/plugin/index.specs.js | 4 + 6 files changed, 246 insertions(+), 29 deletions(-) create mode 100644 test/unit/lib/plugins/StatelessSwitchFactory.mocks.js create mode 100644 test/unit/lib/plugins/StatelessSwitchFactory.specs.js diff --git a/lib/HomebridgeConfig.js b/lib/HomebridgeConfig.js index f380aeb..664fb1f 100644 --- a/lib/HomebridgeConfig.js +++ b/lib/HomebridgeConfig.js @@ -38,38 +38,29 @@ class Accesories { } } + getAccessoryConfig (accesoryName, ability, pluginConnection) { + return { + accessory: accesoryName, + apiKey: pluginConnection.apiKey, + bridgeUrl: `${pluginConnection.url}${ability._id}`, + servicePackageName: ability.service.package, + serviceName: ability.service.name, + serviceProcessId: ability.service.processId, + abilityName: ability.name, + name: `${ability.service.name} ${ability.name}` + } + } + getSwitchs (abilities, pluginConnection) { // Abilities with boolean data that have state and action can be considered as homebridge "switches" - const switchValidAbilities = abilities.filter(ability => ability.type === 'boolean' && ability.state === true && ability.action === true) - return switchValidAbilities.map(ability => { - return { - accessory: ACCESORY_SWITCH_NAME, - apiKey: pluginConnection.apiKey, - bridgeUrl: `${pluginConnection.url}${ability._id}`, - servicePackageName: ability.service.package, - serviceName: ability.service.name, - serviceProcessId: ability.service.processId, - abilityName: ability.name, - name: `${ability.service.name} ${ability.name}` - } - }) + const validAbilities = abilities.filter(ability => ability.type === 'boolean' && ability.state === true && ability.action === true) + return validAbilities.map(ability => this.getAccessoryConfig(ACCESORY_SWITCH_NAME, ability, pluginConnection)) } getStatelessSwitchs (abilities, pluginConnection) { // Abilities with no defined data that have an action can be considered as Domapic "statelessSwitch", that will be mapped to homebridge "switches". Will return false as state always - const switchValidAbilities = abilities.filter(ability => ability.type === undefined && ability.action === true) - return switchValidAbilities.map(ability => { - return { - accessory: ACCESORY_STATELESS_SWITCH_NAME, - apiKey: pluginConnection.apiKey, - bridgeUrl: `${pluginConnection.url}${ability._id}`, - servicePackageName: ability.service.package, - serviceName: ability.service.name, - serviceProcessId: ability.service.processId, - abilityName: ability.name, - name: `${ability.service.name} ${ability.name}` - } - }) + const validAbilities = abilities.filter(ability => ability.type === undefined && ability.action === true) + return validAbilities.map(ability => this.getAccessoryConfig(ACCESORY_STATELESS_SWITCH_NAME, ability, pluginConnection)) } async getAccesories (abilities) { diff --git a/lib/plugins/StatelessSwitchFactory.js b/lib/plugins/StatelessSwitchFactory.js index d34c1d9..82cc5b4 100644 --- a/lib/plugins/StatelessSwitchFactory.js +++ b/lib/plugins/StatelessSwitchFactory.js @@ -53,7 +53,8 @@ const StatelessSwitchFactory = function (Service, Characteristic) { getSwitchOnCharacteristic (next) { this.log(`Getting state. Returning false`) - next(false) + next(null, false) + return Promise.resolve() } setSwitchOnCharacteristic (on, next) { diff --git a/test/unit/lib/HomebridgeConfig.specs.js b/test/unit/lib/HomebridgeConfig.specs.js index 2741200..ad62580 100644 --- a/test/unit/lib/HomebridgeConfig.specs.js +++ b/test/unit/lib/HomebridgeConfig.specs.js @@ -45,6 +45,18 @@ test.describe('Homebridge Config', () => { service: { _id: 'foo-service-id-2' } + }, + { + _id: 'foo-id-3', + _service: 'foo-service-id-3', + name: 'foo-name-3', + action: true, + service: { + _id: 'foo-service-id-3', + name: 'foo-service-name-3', + package: 'foo-service-package-3', + processId: 'foo-service-processId-3' + } } ] domapic = new DomapicMocks() @@ -72,11 +84,12 @@ test.describe('Homebridge Config', () => { }) test.describe('write method', () => { - const homebridgeConfigPath = path.resolve(__dirname, '..', '..', '..', 'homebridge', 'config.json') + const homebridgeConfigPath = path.resolve(__dirname, '..', '..', '..', 'homebridge') + const homebridgeConfigFile = path.resolve(homebridgeConfigPath, 'config.json') test.it('should write abilities based configuration in homebridge folder', () => { return homebridgeConfig.write(fooAbilities) .then(() => { - return test.expect(fsExtra.stubs.writeJson.getCall(0).args[0]).to.equal(homebridgeConfigPath) + return test.expect(fsExtra.stubs.writeJson.getCall(0).args[0]).to.equal(homebridgeConfigFile) }) }) @@ -93,16 +106,56 @@ test.describe('Homebridge Config', () => { serviceName: 'foo-service-name', servicePackageName: 'foo-service-package', serviceProcessId: 'foo-service-processId' + }, { + abilityName: 'foo-name-3', + accessory: 'DomapicStatelessSwitch', + apiKey: 'foo-key', + bridgeUrl: 'http://foo-host:foo-port/api/controller/abilities/foo-id-3', + name: 'foo-service-name-3 foo-name-3', + serviceName: 'foo-service-name-3', + servicePackageName: 'foo-service-package-3', + serviceProcessId: 'foo-service-processId-3' }]) }) }) + test.it('should ensure that homebridge folder exists', () => { + return homebridgeConfig.write(fooAbilities) + .then(() => { + return test.expect(fsExtra.stubs.ensureDirSync.getCall(0).args[0]).to.equal(homebridgeConfigPath) + }) + }) + test.it('should set port based on homebridgePort plugin configuration', () => { return homebridgeConfig.write(fooAbilities) .then(() => { return test.expect(fsExtra.stubs.writeJson.getCall(0).args[1].bridge.port).to.equal(fooConfig.homebridgePort) }) }) + + test.it('should set name based on plugin name', () => { + return homebridgeConfig.write(fooAbilities) + .then(() => { + return test.expect(fsExtra.stubs.writeJson.getCall(0).args[1].bridge.name).to.equal(fooConfig.name) + }) + }) + + test.it('should set username based on previously stored mac', () => { + const fooMac = 'foo-mac' + domapic.stubs.plugin.storage.get.withArgs('username_mac').resolves(fooMac) + return homebridgeConfig.write(fooAbilities) + .then(() => { + return test.expect(fsExtra.stubs.writeJson.getCall(0).args[1].bridge.username).to.equal(fooMac) + }) + }) + + test.it('should save a new mac if there is no one in storage', () => { + domapic.stubs.plugin.storage.get.withArgs('username_mac').rejects(new Error()) + return homebridgeConfig.write(fooAbilities) + .then(() => { + return test.expect(domapic.stubs.plugin.storage.set.getCall(0).args[0]).to.equal('username_mac') + }) + }) }) test.describe('getSwitchs method', () => { diff --git a/test/unit/lib/plugins/StatelessSwitchFactory.mocks.js b/test/unit/lib/plugins/StatelessSwitchFactory.mocks.js new file mode 100644 index 0000000..1ef5323 --- /dev/null +++ b/test/unit/lib/plugins/StatelessSwitchFactory.mocks.js @@ -0,0 +1,32 @@ +const test = require('narval') + +const mockery = require('../../mockery') + +const MODULE = '../lib/plugins/StatelessSwitchFactory' + +const Mock = function () { + let sandbox = test.sinon.createSandbox() + + const instanceStubs = {} + + const stub = sandbox.stub().callsFake(function () { + return instanceStubs + }) + + const restore = () => { + sandbox.restore() + mockery.deregister(MODULE) + } + + mockery.register(MODULE, stub) + + return { + restore, + stubs: { + Constructor: stub, + instance: instanceStubs + } + } +} + +module.exports = Mock diff --git a/test/unit/lib/plugins/StatelessSwitchFactory.specs.js b/test/unit/lib/plugins/StatelessSwitchFactory.specs.js new file mode 100644 index 0000000..3857bf5 --- /dev/null +++ b/test/unit/lib/plugins/StatelessSwitchFactory.specs.js @@ -0,0 +1,136 @@ +const test = require('narval') + +const HomebridgeMocks = require('../../Homebridge.mocks') +const RequestPromiseMocks = require('../../RequestPromise.mocks') + +test.describe('Stateless Switch Plugin Factory', () => { + let homebridge + let requestPromise + let SwitchFactory + let Switch + let switchPlugin + let fooConfig + let sandbox + let log + + test.beforeEach(() => { + sandbox = test.sinon.createSandbox() + log = sandbox.stub() + fooConfig = { + abilityName: 'foo-name', + accessory: 'DomapicSwitch', + apiKey: 'foo-api-key', + bridgeUrl: 'foo-url/foo-id', + name: 'foo-service-name foo-name', + serviceName: 'foo-service-name', + servicePackageName: 'foo-service-package', + serviceProcessId: 'foo-service-processId' + } + homebridge = new HomebridgeMocks() + requestPromise = new RequestPromiseMocks() + + SwitchFactory = require('../../../../lib/plugins/StatelessSwitchFactory') + Switch = new SwitchFactory(homebridge.stubs.hap.Service, homebridge.stubs.hap.Characteristic) + switchPlugin = new Switch(log, fooConfig) + }) + + test.afterEach(() => { + sandbox.restore() + homebridge.restore() + requestPromise.restore() + }) + + test.describe('Switch static name getter', () => { + test.it('should return accessory name', () => { + test.expect(Switch.name).to.equal('DomapicStatelessSwitch') + }) + }) + + test.describe('Switch instance', () => { + test.describe('logError method', () => { + test.it('should log error message', () => { + const FOO_MESSAGE = 'Foo error message' + const error = new Error(FOO_MESSAGE) + switchPlugin.logError(error) + test.expect(log).to.have.been.calledWith(`ERROR: ${FOO_MESSAGE}`) + }) + }) + + test.describe('getServices method', () => { + test.it('should set accesory Manufacturer as Domapic', () => { + switchPlugin.getServices() + test.expect(homebridge.instances.accessoryInformation.setCharacteristic).to.have.been.calledWith( + homebridge.stubs.hap.Characteristic.Manufacturer, + 'Domapic' + ) + }) + + test.it('should set accesory Model with configuration servicePackageName', () => { + switchPlugin.getServices() + test.expect(homebridge.instances.accessoryInformation.setCharacteristic).to.have.been.calledWith( + homebridge.stubs.hap.Characteristic.Model, + 'foo-service-package' + ) + }) + + test.it('should set accesory SerialNumber with configuration serviceProcessId', () => { + switchPlugin.getServices() + test.expect(homebridge.instances.accessoryInformation.setCharacteristic).to.have.been.calledWith( + homebridge.stubs.hap.Characteristic.SerialNumber, + 'foo-service-processId' + ) + }) + + test.it('should have configured switch service to call "getSwitchOnCharacteristic" method on get event', () => { + switchPlugin.getServices() + test.expect(homebridge.instances.switch.on).to.have.been.calledWith( + 'get', + switchPlugin.getSwitchOnCharacteristic + ) + }) + + test.it('should have configured switch service to call "setSwitchOnCharacteristic" method on set event', () => { + switchPlugin.getServices() + test.expect(homebridge.instances.switch.on).to.have.been.calledWith( + 'set', + switchPlugin.setSwitchOnCharacteristic + ) + }) + }) + + test.describe('getSwitchOnCharacteristic method', () => { + test.it('should invoque callback with false', () => { + const cb = sandbox.stub() + return switchPlugin.getSwitchOnCharacteristic(cb) + .then(() => { + return test.expect(cb).to.have.been.calledWith(null, false) + }) + }) + }) + + test.describe('setSwitchOnCharacteristic method', () => { + test.it('should call to request plugin bridge api without passing data, and invoque callback with no data if request is success', () => { + const fooData = 'foo' + requestPromise.stub.resolves() + const cb = sandbox.stub() + return switchPlugin.setSwitchOnCharacteristic(fooData, cb) + .then(() => { + return Promise.all([ + test.expect(requestPromise.stub.getCall(0).args[0].body).to.be.undefined(), + test.expect(cb).to.have.been.called() + ]) + }) + }) + + test.it('should invoque callback with error if request fails', () => { + const fooError = new Error('foo') + requestPromise.stub.rejects(fooError) + const cb = sandbox.stub() + return switchPlugin.setSwitchOnCharacteristic('foo', cb) + .then(() => { + return test.expect(cb).to.have.been.calledWith(fooError) + }) + }) + }) + }) +}) diff --git a/test/unit/plugin/index.specs.js b/test/unit/plugin/index.specs.js index 82c6b39..928b343 100644 --- a/test/unit/plugin/index.specs.js +++ b/test/unit/plugin/index.specs.js @@ -1,11 +1,13 @@ const test = require('narval') const SwitchFactoryMocks = require('../lib/plugins/SwitchFactory.mocks') +const StatelessSwitchFactoryMocks = require('../lib/plugins/StatelessSwitchFactory.mocks') const HomebridgeMocks = require('../Homebridge.mocks') test.describe('plugin', () => { let sandbox let switchFactory + let statelessSwitchFactory let homebridge let plugin @@ -13,6 +15,7 @@ test.describe('plugin', () => { sandbox = test.sinon.createSandbox() homebridge = new HomebridgeMocks() switchFactory = new SwitchFactoryMocks() + statelessSwitchFactory = new StatelessSwitchFactoryMocks() plugin = require('../../../plugin/index') plugin(homebridge.stubs) }) @@ -20,6 +23,7 @@ test.describe('plugin', () => { test.after(() => { sandbox.restore() switchFactory.restore() + statelessSwitchFactory.restore() }) test.it('should create a new Switch accessory', () => { From 41868ac84f64efcf585ccdadc8d769aa21a35102 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 10:43:17 +0100 Subject: [PATCH 07/11] Rename StatelessSwitch accessory into Button --- lib/HomebridgeConfig.js | 8 ++++---- ...tatelessSwitchFactory.js => ButtonFactory.js} | 16 ++++++++-------- lib/statics.js | 2 +- plugin/index.js | 6 +++--- test/unit/lib/HomebridgeConfig.specs.js | 2 +- ...chFactory.mocks.js => ButtonFactory.mocks.js} | 2 +- ...chFactory.specs.js => ButtonFactory.specs.js} | 10 +++++----- test/unit/plugin/index.specs.js | 16 ++++++++++++---- 8 files changed, 35 insertions(+), 27 deletions(-) rename lib/plugins/{StatelessSwitchFactory.js => ButtonFactory.js} (82%) rename test/unit/lib/plugins/{StatelessSwitchFactory.mocks.js => ButtonFactory.mocks.js} (90%) rename test/unit/lib/plugins/{StatelessSwitchFactory.specs.js => ButtonFactory.specs.js} (93%) diff --git a/lib/HomebridgeConfig.js b/lib/HomebridgeConfig.js index 664fb1f..f9826af 100644 --- a/lib/HomebridgeConfig.js +++ b/lib/HomebridgeConfig.js @@ -16,7 +16,7 @@ const { HOMEBRIDGE_MAC_STORAGE_KEY, ACCESORY_SWITCH_NAME, - ACCESORY_STATELESS_SWITCH_NAME + ACCESORY_BUTTON_NAME } = require('./statics') class Accesories { @@ -57,17 +57,17 @@ class Accesories { return validAbilities.map(ability => this.getAccessoryConfig(ACCESORY_SWITCH_NAME, ability, pluginConnection)) } - getStatelessSwitchs (abilities, pluginConnection) { + getButtons (abilities, pluginConnection) { // Abilities with no defined data that have an action can be considered as Domapic "statelessSwitch", that will be mapped to homebridge "switches". Will return false as state always const validAbilities = abilities.filter(ability => ability.type === undefined && ability.action === true) - return validAbilities.map(ability => this.getAccessoryConfig(ACCESORY_STATELESS_SWITCH_NAME, ability, pluginConnection)) + return validAbilities.map(ability => this.getAccessoryConfig(ACCESORY_BUTTON_NAME, ability, pluginConnection)) } async getAccesories (abilities) { const pluginConnection = await this.getPluginConnection() return [ ...this.getSwitchs(abilities, pluginConnection), - ...this.getStatelessSwitchs(abilities, pluginConnection) + ...this.getButtons(abilities, pluginConnection) ] } diff --git a/lib/plugins/StatelessSwitchFactory.js b/lib/plugins/ButtonFactory.js similarity index 82% rename from lib/plugins/StatelessSwitchFactory.js rename to lib/plugins/ButtonFactory.js index 82cc5b4..5a53e86 100644 --- a/lib/plugins/StatelessSwitchFactory.js +++ b/lib/plugins/ButtonFactory.js @@ -4,10 +4,10 @@ const url = require('url') const requestPromise = require('request-promise') -const { DOMAPIC, HOMEBRIDGE_ERROR, ACCESORY_STATELESS_SWITCH_NAME } = require('../statics') +const { DOMAPIC, HOMEBRIDGE_ERROR, ACCESORY_BUTTON_NAME } = require('../statics') -const StatelessSwitchFactory = function (Service, Characteristic) { - return class Switch { +const ButtonFactory = function (Service, Characteristic) { + return class Button { constructor (log, config) { this.config = config this.log = log @@ -27,7 +27,7 @@ const StatelessSwitchFactory = function (Service, Characteristic) { } static get name () { - return ACCESORY_STATELESS_SWITCH_NAME + return ACCESORY_BUTTON_NAME } logError (error) { @@ -52,18 +52,18 @@ const StatelessSwitchFactory = function (Service, Characteristic) { } getSwitchOnCharacteristic (next) { - this.log(`Getting state. Returning false`) + this.log(`Getting button state. Returning false`) next(null, false) return Promise.resolve() } setSwitchOnCharacteristic (on, next) { - this.log(`Triggering`) + this.log(`Activating button`) return requestPromise({ ...this.requestOptions, method: 'POST' }).then(() => { - this.log(`Trigger success`) + this.log(`Button activation success`) next() }).catch(error => { this.logError(error) @@ -73,4 +73,4 @@ const StatelessSwitchFactory = function (Service, Characteristic) { } } -module.exports = StatelessSwitchFactory +module.exports = ButtonFactory diff --git a/lib/statics.js b/lib/statics.js index 69f06b3..dbfff6f 100644 --- a/lib/statics.js +++ b/lib/statics.js @@ -24,5 +24,5 @@ module.exports = { GETTING_ABILITY_STATE: `Getting state of ability`, ACCESORY_SWITCH_NAME: `${DOMAPIC}Switch`, - ACCESORY_STATELESS_SWITCH_NAME: `${DOMAPIC}StatelessSwitch` + ACCESORY_BUTTON_NAME: `${DOMAPIC}Button` } diff --git a/plugin/index.js b/plugin/index.js index a514b75..a12fac5 100644 --- a/plugin/index.js +++ b/plugin/index.js @@ -2,14 +2,14 @@ const { DOMAPIC } = require('../lib/statics') const SwitchFactory = require('../lib/plugins/SwitchFactory') -const StatelessSwitchFactory = require('../lib/plugins/StatelessSwitchFactory') +const ButtonFactory = require('../lib/plugins/ButtonFactory') module.exports = function (homebridge) { const Switch = new SwitchFactory(homebridge.hap.Service, homebridge.hap.Characteristic) homebridge.registerAccessory(DOMAPIC, Switch.name, Switch) - const StatelessSwitch = new StatelessSwitchFactory(homebridge.hap.Service, homebridge.hap.Characteristic) - homebridge.registerAccessory(DOMAPIC, StatelessSwitch.name, StatelessSwitch) + const Button = new ButtonFactory(homebridge.hap.Service, homebridge.hap.Characteristic) + homebridge.registerAccessory(DOMAPIC, Button.name, Button) // TODO, register accesories for all HomeKit accesory types } diff --git a/test/unit/lib/HomebridgeConfig.specs.js b/test/unit/lib/HomebridgeConfig.specs.js index ad62580..b98022b 100644 --- a/test/unit/lib/HomebridgeConfig.specs.js +++ b/test/unit/lib/HomebridgeConfig.specs.js @@ -108,7 +108,7 @@ test.describe('Homebridge Config', () => { serviceProcessId: 'foo-service-processId' }, { abilityName: 'foo-name-3', - accessory: 'DomapicStatelessSwitch', + accessory: 'DomapicButton', apiKey: 'foo-key', bridgeUrl: 'http://foo-host:foo-port/api/controller/abilities/foo-id-3', name: 'foo-service-name-3 foo-name-3', diff --git a/test/unit/lib/plugins/StatelessSwitchFactory.mocks.js b/test/unit/lib/plugins/ButtonFactory.mocks.js similarity index 90% rename from test/unit/lib/plugins/StatelessSwitchFactory.mocks.js rename to test/unit/lib/plugins/ButtonFactory.mocks.js index 1ef5323..8383cdc 100644 --- a/test/unit/lib/plugins/StatelessSwitchFactory.mocks.js +++ b/test/unit/lib/plugins/ButtonFactory.mocks.js @@ -2,7 +2,7 @@ const test = require('narval') const mockery = require('../../mockery') -const MODULE = '../lib/plugins/StatelessSwitchFactory' +const MODULE = '../lib/plugins/ButtonFactory' const Mock = function () { let sandbox = test.sinon.createSandbox() diff --git a/test/unit/lib/plugins/StatelessSwitchFactory.specs.js b/test/unit/lib/plugins/ButtonFactory.specs.js similarity index 93% rename from test/unit/lib/plugins/StatelessSwitchFactory.specs.js rename to test/unit/lib/plugins/ButtonFactory.specs.js index 3857bf5..7d0f706 100644 --- a/test/unit/lib/plugins/StatelessSwitchFactory.specs.js +++ b/test/unit/lib/plugins/ButtonFactory.specs.js @@ -3,7 +3,7 @@ const test = require('narval') const HomebridgeMocks = require('../../Homebridge.mocks') const RequestPromiseMocks = require('../../RequestPromise.mocks') -test.describe('Stateless Switch Plugin Factory', () => { +test.describe('Button Plugin Factory', () => { let homebridge let requestPromise let SwitchFactory @@ -29,7 +29,7 @@ test.describe('Stateless Switch Plugin Factory', () => { homebridge = new HomebridgeMocks() requestPromise = new RequestPromiseMocks() - SwitchFactory = require('../../../../lib/plugins/StatelessSwitchFactory') + SwitchFactory = require('../../../../lib/plugins/ButtonFactory') Switch = new SwitchFactory(homebridge.stubs.hap.Service, homebridge.stubs.hap.Characteristic) switchPlugin = new Switch(log, fooConfig) }) @@ -40,13 +40,13 @@ test.describe('Stateless Switch Plugin Factory', () => { requestPromise.restore() }) - test.describe('Switch static name getter', () => { + test.describe('Button static name getter', () => { test.it('should return accessory name', () => { - test.expect(Switch.name).to.equal('DomapicStatelessSwitch') + test.expect(Switch.name).to.equal('DomapicButton') }) }) - test.describe('Switch instance', () => { + test.describe('Button instance', () => { test.describe('logError method', () => { test.it('should log error message', () => { const FOO_MESSAGE = 'Foo error message' diff --git a/test/unit/plugin/index.specs.js b/test/unit/plugin/index.specs.js index 928b343..5d2f961 100644 --- a/test/unit/plugin/index.specs.js +++ b/test/unit/plugin/index.specs.js @@ -1,13 +1,13 @@ const test = require('narval') const SwitchFactoryMocks = require('../lib/plugins/SwitchFactory.mocks') -const StatelessSwitchFactoryMocks = require('../lib/plugins/StatelessSwitchFactory.mocks') +const ButtonFactoryMocks = require('../lib/plugins/ButtonFactory.mocks') const HomebridgeMocks = require('../Homebridge.mocks') test.describe('plugin', () => { let sandbox let switchFactory - let statelessSwitchFactory + let buttonFactory let homebridge let plugin @@ -15,7 +15,7 @@ test.describe('plugin', () => { sandbox = test.sinon.createSandbox() homebridge = new HomebridgeMocks() switchFactory = new SwitchFactoryMocks() - statelessSwitchFactory = new StatelessSwitchFactoryMocks() + buttonFactory = new ButtonFactoryMocks() plugin = require('../../../plugin/index') plugin(homebridge.stubs) }) @@ -23,7 +23,7 @@ test.describe('plugin', () => { test.after(() => { sandbox.restore() switchFactory.restore() - statelessSwitchFactory.restore() + buttonFactory.restore() }) test.it('should create a new Switch accessory', () => { @@ -33,4 +33,12 @@ test.describe('plugin', () => { test.it('should register switch accesory in homebridge', () => { test.expect(homebridge.stubs.registerAccessory.getCall(0).args[2]).to.equal(switchFactory.stubs.instance) }) + + test.it('should create a new Button accessory', () => { + test.expect(buttonFactory.stubs.Constructor).to.have.been.calledWith(homebridge.stubs.hap.Service, homebridge.stubs.hap.Characteristic) + }) + + test.it('should register button accesory in homebridge', () => { + test.expect(homebridge.stubs.registerAccessory.getCall(1).args[2]).to.equal(buttonFactory.stubs.instance) + }) }) From 820cfd89729bfcc5ff8dea6b1e48d9d1cb7f8eae Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 10:51:43 +0100 Subject: [PATCH 08/11] Add Button details to documentation --- CHANGELOG.md | 2 +- README.md | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab744a4..933dea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [1.0.0-beta.2] - 2018-12-09 ### Added -- Add StatelessSwitch accessory +- Add Button accessory. Register abilities with action and no data type defined as Buttons ### Changed - Set Homebridge server name from service name diff --git a/README.md b/README.md index 7484434..19f82e8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,10 @@ Since Siri supports devices added through HomeKit, this means that **with this p * _"Siri, open the garage door"_ * _"Siri, activate my awesome webhook"_ -> For now, only abilities which have "boolean" data type and have both `state` and `action` are being exposed as HomeKit `Switch` accessories. Soon will be added custom plugin configuration for abilities to [Domapic Controller][domapic-controller-url], and then the user will be able to decide which type of accessory should be each ability, as long as data type is compatible. +> For now, only certain types of abilities are being registered as accessories: + - Abilities that have "boolean" data type and have both `state` and `action` are being exposed as HomeKit `Switch` accesories. + - Abilities without data type that have an `action` defined are being exposed as Buttons. (HomeKit Switch returning always `false` as state). + Soon will be added custom plugin configuration for abilities to [Domapic Controller][domapic-controller-url], and then the user will be able to decide which type of accessory should be each ability, as long as data type is compatible. ## Prerequisites From da9a59dfc45f25872ac0ea7bd8049d75cf95f88c Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 10:55:02 +0100 Subject: [PATCH 09/11] Change comment --- lib/HomebridgeConfig.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/HomebridgeConfig.js b/lib/HomebridgeConfig.js index f9826af..60ea6b4 100644 --- a/lib/HomebridgeConfig.js +++ b/lib/HomebridgeConfig.js @@ -58,7 +58,7 @@ class Accesories { } getButtons (abilities, pluginConnection) { - // Abilities with no defined data that have an action can be considered as Domapic "statelessSwitch", that will be mapped to homebridge "switches". Will return false as state always + // Abilities with no defined data that have an action can be considered as Domapic "Buttons", that will be mapped to homebridge "switches". Will return false as state always const validAbilities = abilities.filter(ability => ability.type === undefined && ability.action === true) return validAbilities.map(ability => this.getAccessoryConfig(ACCESORY_BUTTON_NAME, ability, pluginConnection)) } From 2d3741200740d8253da49d08c66bd7c51718f250 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 10:59:24 +0100 Subject: [PATCH 10/11] Do not look for duplicated code in plugins folder. Technical debt --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 77943c1..dfcbd4c 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,6 +6,6 @@ sonar.sources=. sonar.exclusions=node_modules/** sonar.test.exclusions=test/**/* sonar.coverage.exclusions=test/**/* -sonar.cpd.exclusions=test/** +sonar.cpd.exclusions=test/** lib/plugins/** sonar.javascript.lcov.reportPaths=.coverage/lcov.info sonar.host.url=https://sonarcloud.io From 31d267f69f60bf087b4f3701a09796e919421113 Mon Sep 17 00:00:00 2001 From: Javier Brea Date: Sun, 9 Dec 2018 11:01:58 +0100 Subject: [PATCH 11/11] Fix sonar cpd exclussions --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index dfcbd4c..b5d4aaa 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -6,6 +6,6 @@ sonar.sources=. sonar.exclusions=node_modules/** sonar.test.exclusions=test/**/* sonar.coverage.exclusions=test/**/* -sonar.cpd.exclusions=test/** lib/plugins/** +sonar.cpd.exclusions=test/**,lib/plugins/** sonar.javascript.lcov.reportPaths=.coverage/lcov.info sonar.host.url=https://sonarcloud.io