Skip to content

Commit

Permalink
Merge pull request #5 from domapic/release-1.0.0-beta.1
Browse files Browse the repository at this point in the history
Release 1.0.0 beta.1
  • Loading branch information
javierbrea committed Dec 7, 2018
2 parents 02f3c31 + 9b54d4c commit 1b2913c
Show file tree
Hide file tree
Showing 42 changed files with 2,157 additions and 21 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
.scannerwork
node_modules
npm-debug.log
.homebridge
.test
/.narval
/plugin/package.json
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
### Removed

## [1.0.0-alpha.1] - 2018-12-04
## [1.0.0-beta.1] - 2018-12-07
### Added
- First prerelease
- Register boolean abilities as Switch accessories
96 changes: 94 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,94 @@
# homebridge-domapic-plugin
Domapic plugin for controlling modules with Apple's HomeKit.
# Homebridge Domapic Plugin

> Domapic Plugin that automatically register Domapic abilities as Apple's HomeKit accessories (using the awesome [Homebridge][homebridge-url])
[![Build status][travisci-image]][travisci-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Quality Gate][quality-gate-image]][quality-gate-url] [![js-standard-style][standard-image]][standard-url]

[![NPM dependencies][npm-dependencies-image]][npm-dependencies-url] [![Last commit][last-commit-image]][last-commit-url] <!--[![Last release][release-image]][release-url] -->

[![NPM downloads][npm-downloads-image]][npm-downloads-url] [![License][license-image]][license-url]

## Intro

The Homebridge Domapic Plugin retrieves the information about modules and abilities registered in your Domapic Controller, and automatically configures and starts an underlay Homebridge server that will act as a bridge between Apple's HomeKit and the Domapic abilities.

Since Siri supports devices added through HomeKit, this means that **with this plugin you can ask Siri to control your own-made Domapic modules:**

* _"Siri, turn on the living room light"_
* _"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.
## Prerequisites

[Domapic Controller][domapic-controller-url] has to be installed and running in your LAN.
At least one Domapic module, such as [relay-domapic-module][relay-domapic-module-url] has to be installed and connected to the Controller.
Also read the system requisites of [Homebridge][homebridge-url], which is used underlay to connect with HomeKit.

## Installation

```bash
npm i homebridge-domapic-plugin -g
```

## Start the plugin

Start the plugin providing the Controller url and connection token:

```bash
domapic-homebridge start --controller=http://192.168.1.100:3000 --controllerApiKey=foo-api-key
```

The plugin will be started in background using [pm2][pm2-url]

To display logs, type:

```bash
domapic-homebridge logs --lines=200
```

Once the Plugin has connected with the Controller, the Homebridge server will be started, and a QR code will be displayed in logs:

![HomeKit connection QR][homekit-connection-qr-image]

**Scan this code with your HomeKit app on your iOS device to add your accessories.**

From now, all modules added to your Controller will automatically be added to your HomeKit app too, and **will be available through Siri**.

## Options

The plugin, apart of all common [domapic services options][domapic-service-options-url], provides custom options for configuring it:

* `homebridgePort` - Number defining the port that Homebridge server will use.

```bash
domapic-homebridge start --homebridgePort=3200
```

[coveralls-image]: https://coveralls.io/repos/github/domapic/homebridge-domapic-plugin/badge.svg?branch=master
[coveralls-url]: https://coveralls.io/github/domapic/homebridge-domapic-plugin
[travisci-image]: https://travis-ci.org/domapic/homebridge-domapic-plugin.svg?branch=master
[travisci-url]: https://travis-ci.org/domapic/homebridge-domapic-plugin
[last-commit-image]: https://img.shields.io/github/last-commit/domapic/homebridge-domapic-plugin.svg
[last-commit-url]: https://github.com/domapic/homebridge-domapic-plugin/commits
[license-image]: https://img.shields.io/npm/l/homebridge-domapic-plugin.svg
[license-url]: https://github.com/domapic/homebridge-domapic-plugin/blob/master/LICENSE
[npm-downloads-image]: https://img.shields.io/npm/dm/homebridge-domapic-plugin.svg
[npm-downloads-url]: https://www.npmjs.com/package/homebridge-domapic-plugin
[npm-dependencies-image]: https://img.shields.io/david/domapic/homebridge-domapic-plugin.svg
[npm-dependencies-url]: https://david-dm.org/domapic/homebridge-domapic-plugin
[quality-gate-image]: https://sonarcloud.io/api/project_badges/measure?project=homebridge-domapic-plugin&metric=alert_status
[quality-gate-url]: https://sonarcloud.io/dashboard?id=homebridge-domapic-plugin
[release-image]: https://img.shields.io/github/release-date/domapic/homebridge-domapic-plugin.svg
[release-url]: https://github.com/domapic/homebridge-domapic-plugin/releases
[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg
[standard-url]: http://standardjs.com/

[pm2-url]: http://pm2.keymetrics.io/
[homebridge-url]: https://www.npmjs.com/package/homebridge
[domapic-controller-url]: https://www.npmjs.com/package/domapic-controller
[relay-domapic-module-url]: https://www.npmjs.com/package/relay-domapic-module
[domapic-service-options-url]: https://github.com/domapic/domapic-service#options

[homekit-connection-qr-image]: http://domapic.com/assets/homebridge_qr_screenshot.jpg
Binary file added assets/homebridge_qr_screenshot.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions bin/domapic-homebridge
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../lib/cli')
25 changes: 25 additions & 0 deletions lib/Abilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

const {
LOADING_ABILITIES
} = require('./statics')

class Abilities {
constructor (dpmcPlugin) {
this.plugin = dpmcPlugin
}

async get () {
const abilities = await this.plugin.controller.abilities.get()
const services = await this.plugin.controller.services.get()
await this.plugin.tracer.info(LOADING_ABILITIES)
return abilities.map(ability => {
return {
...ability,
service: {...services.find(service => service._id === ability._service)}
}
})
}
}

module.exports = Abilities
96 changes: 96 additions & 0 deletions lib/Homebridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
'use strict'

const path = require('path')
const childProcess = require('child_process')

const { trim } = require('lodash')
const kill = require('tree-kill')
const fsExtra = require('fs-extra')

const {
HOMEBRIDGE_LOG,
HOMEBRIDGE_EXITED,
HOMEBRIDGE_STOPPING,
HOMEBRIDGE_STARTING,
PACKAGE_PATH,
HOMEBRIDGE_PATH
} = require('./statics')

class HomeBridge {
constructor (dpmcPlugin) {
this.tracer = dpmcPlugin.tracer
this.process = null
this.binPath = path.resolve(PACKAGE_PATH, 'node_modules', '.bin', 'homebridge')
this.libPath = path.resolve(__dirname)
this.pluginPath = path.resolve(PACKAGE_PATH, 'plugin')
this.pluginPackageJsonOrigin = path.resolve(this.libPath, 'plugin-package.json')
this.pluginPackageJsonDest = path.resolve(this.pluginPath, 'package.json')

this.start = this.start.bind(this)
}

log (data, method) {
let cleanData = trim(data.toString())
if (cleanData.length) {
if (cleanData.split('\n').length > 1) {
cleanData = `\n${cleanData}`
}
this.tracer[method](`${HOMEBRIDGE_LOG} ${cleanData}`)
}
}

async writePackageJson () {
await fsExtra.copy(this.pluginPackageJsonOrigin, this.pluginPackageJsonDest)
}

async start () {
await this.tracer.info(HOMEBRIDGE_STARTING)
await this.writePackageJson()
this.process = childProcess.spawn(this.binPath, [
'-U',
HOMEBRIDGE_PATH,
'-P',
this.pluginPath
], {
cwd: PACKAGE_PATH,
encoding: 'utf8'
})

this.process.stdout.on('data', data => {
this.log(data, 'info')
})

this.process.stderr.on('data', data => {
this.log(data, 'error')
})

this.process.on('close', code => {
this.log(`${HOMEBRIDGE_EXITED} ${code}`, 'info')
this.process = null
})
}

async stop () {
if (this.process) {
await this.tracer.info(HOMEBRIDGE_STOPPING)
return new Promise((resolve, reject) => {
kill(this.process.pid, error => {
if (error) {
reject(error)
} else {
this.process = null
resolve()
}
})
})
}
return Promise.resolve()
}

async restart () {
await this.stop()
return this.start()
}
}

module.exports = HomeBridge
74 changes: 74 additions & 0 deletions lib/HomebridgeConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict'

const path = require('path')

const fsExtra = require('fs-extra')
const ip = require('ip')
const { cloneDeep } = require('lodash')

const homebridgeConfig = require('./homebridge-config.json')

const {
HOMEBRIDGE_PATH,
WRITING_HOMEBRIDGE_CONFIG,
HOMEBRIDGE_PORT,

ACCESORY_SWITCH_NAME
} = require('./statics')

class Accesories {
constructor (dpmcPlugin) {
this.configPath = path.resolve(HOMEBRIDGE_PATH, 'config.json')
this.plugin = dpmcPlugin
this.tracer = dpmcPlugin.tracer
}

async getPluginConnection () {
const pluginConfig = await this.plugin.config.get()
const pluginApiKeys = await this.plugin.storage.get('apiKeys')
const host = pluginConfig.hostName.length ? pluginConfig.hostName : ip.address()
const secureProtocolSuffix = pluginConfig.sslCert ? 's' : ''
const port = pluginConfig.port

return {
url: `http${secureProtocolSuffix}://${host}:${port}/api/controller/abilities/`,
apiKey: pluginApiKeys.find(apiKey => apiKey.role === 'admin').key
}
}

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}`
}
})
}

async getAccesories (abilities) {
const pluginConnection = await this.getPluginConnection()
return [
...this.getSwitchs(abilities, pluginConnection)
]
}

async write (abilities) {
await this.tracer.info(WRITING_HOMEBRIDGE_CONFIG)
this.config = cloneDeep(homebridgeConfig)
this.config.bridge.port = await this.plugin.config.get(HOMEBRIDGE_PORT)
this.config.accessories = await this.getAccesories(abilities)
await fsExtra.writeJson(this.configPath, this.config, {
spaces: 2
})
}
}

module.exports = Accesories
35 changes: 35 additions & 0 deletions lib/api/AbilitiesBridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict'

const { SENDING_ABILITY_ACTION, GETTING_ABILITY_STATE } = require('../statics')

class AbilitiesBridge {
constructor (dpmcPlugin) {
this.plugin = dpmcPlugin

this.actionOperationHandler = this.actionOperationHandler.bind(this)
this.stateOperationHandler = this.stateOperationHandler.bind(this)
}

actionOperationHandler (params, body) {
this.plugin.tracer.debug(`${SENDING_ABILITY_ACTION} ${params.path.id}`, body)
return this.plugin.controller.abilities.action(params.path.id, body)
}

stateOperationHandler (params) {
this.plugin.tracer.debug(`${GETTING_ABILITY_STATE} ${params.path.id}`)
return this.plugin.controller.abilities.state(params.path.id)
}

operations () {
return {
abilityActionHandler: {
handler: this.actionOperationHandler
},
abilityStateHandler: {
handler: this.stateOperationHandler
}
}
}
}

module.exports = AbilitiesBridge

0 comments on commit 1b2913c

Please sign in to comment.