Compare commits

...

9 commits

Author SHA1 Message Date
Todd Treece
fb7303eacb switch to swagger-client promises 2016-01-28 16:40:10 -05:00
Todd Treece
62cc18a3cd working test version of light accessory 2016-01-28 16:01:11 -05:00
Todd Treece
bb4c4272d5 adds node-persist init to homekit cli 2016-01-28 16:00:03 -05:00
Todd Treece
7500a11bbe adds homekit to gulp syntax check 2016-01-28 15:56:01 -05:00
Todd Treece
f9a07a6abb adds accessory index 2016-01-27 18:53:35 -05:00
Todd Treece
17ef2c2487 adds homekit to main cli 2016-01-27 18:50:57 -05:00
Todd Treece
b826823041 adds homekit light accessory 2016-01-27 18:49:01 -05:00
Todd Treece
efb8bd0286 adds homekit cli 2016-01-27 18:48:49 -05:00
Todd Treece
b0ce189ff0 adds hap-node dependency 2016-01-26 14:29:02 -05:00
7 changed files with 277 additions and 7 deletions

132
cli/homekit.js Normal file
View file

@ -0,0 +1,132 @@
'use strict';
const Client = require('../client'),
CLI = require('./index'),
Yargs = require('yargs'),
uuid = require('hap-nodejs').uuid,
storage = require('node-persist'),
Bridge = require('hap-nodejs').Bridge,
Accessory = require('hap-nodejs').Accessory,
inquirer = require('inquirer'),
Accessories = require('../homekit'),
crypto = require('crypto');
storage.initSync();
class HomekitCLI extends CLI {
constructor() {
super('homekit');
this.completions = [
'help',
'light'
];
this.yargs = Yargs(process.argv.slice(3));
this.client = false;
}
init() {
if(! process.env.AIO_CLIENT_USER || ! process.env.AIO_CLIENT_KEY)
return this.requireAuth(this.yargs);
const options = {
success: this.clientReady.bind(this, this.yargs),
failure: this.error.bind(this)
};
if(process.env.AIO_CLIENT_HOST)
options.host = process.env.AIO_CLIENT_HOST;
if(process.env.AIO_CLIENT_PORT)
options.port = process.env.AIO_CLIENT_PORT;
this.client = new Client(process.env.AIO_CLIENT_USER, process.env.AIO_CLIENT_KEY, options);
}
clientReady(yargs) {
const argv = yargs
.command('light', 'Light')
.command('help', 'Show help')
.demand(1, 'You must supply a valid homekit command')
.argv;
if(! argv)
return;
const command = argv._[0];
if(command === 'help')
return yargs.showHelp();
if(! this[command])
return yargs.showHelp();
this[command](Yargs(process.argv.slice(4)));
}
light(yargs) {
yargs.usage(`Usage: adafruit-io homekit light [options]`)
.command('help', 'Show help')
.alias('n', 'name').demand('name')
.nargs('n', 1).describe('n', 'the name of the light');
const argv = yargs.argv,
command = argv._[0];
if(command === 'help')
return yargs.showHelp();
const name = argv.name || 'light',
bridge = new Bridge('Adafruit IO', uuid.generate('Adafruit IO'));
bridge.on('identify', (paired, cb) => cb());
const light = new Accessories.light(name, this.client);
bridge.addBridgedAccessory(light);
this.logo();
this.info('advertising homekit light accessory...');
this.info('PIN: 100-11-100');
bridge.publish({
username: 'AD:A0:AD:A0:AD:A0',
port: 60000,
pincode: '100-11-100',
category: Accessory.Categories.BRIDGE
});
}
requireAuth(yargs) {
const argv = yargs
.usage('Usage: adafruit-io homekit config [options]')
.alias('h', 'host').nargs('h', 1).default('h', process.env.AIO_CLIENT_HOST || 'io.adafruit.com').describe('h', 'Server hostname')
.alias('p', 'port').nargs('p', 1).default('p', process.env.AIO_CLIENT_PORT || '80').describe('p', 'Server port')
.alias('u', 'username').demand('username').nargs('u', 1).describe('u', 'Adafruit IO Username')
.alias('k', 'key').demand('key').nargs('k', 1).describe('k', 'Adafruit IO Key')
.command('help', 'Show help')
.argv;
process.env.AIO_CLIENT_HOST = argv.host;
process.env.AIO_CLIENT_PORT = argv.port;
process.env.AIO_CLIENT_USER = argv.username;
process.env.AIO_CLIENT_KEY = argv.key;
this.saveEnv();
}
}
exports = module.exports = HomekitCLI;

View file

@ -16,6 +16,7 @@ class CLI {
this.type = type || 'cli'; this.type = type || 'cli';
this.completions = [ this.completions = [
'client', 'client',
'homekit',
'server', 'server',
'tunnel', 'tunnel',
'help', 'help',
@ -24,6 +25,7 @@ class CLI {
this.sub = { this.sub = {
client: require('./client'), client: require('./client'),
homekit: require('./homekit'),
server: require('./server'), server: require('./server'),
tunnel: require('./tunnel') tunnel: require('./tunnel')
}; };
@ -50,6 +52,7 @@ class CLI {
this.yargs this.yargs
.usage('Usage: adafruit-io <command>') .usage('Usage: adafruit-io <command>')
.command('server', 'Adafruit IO local server') .command('server', 'Adafruit IO local server')
.command('homekit', 'Adafruit IO homekit bridge')
.command('client', 'Adafruit IO client') .command('client', 'Adafruit IO client')
.command('tunnel', 'TLS tunnel to io.adafruit.com') .command('tunnel', 'TLS tunnel to io.adafruit.com')
.command('help', 'Show help') .command('help', 'Show help')
@ -163,7 +166,7 @@ class CLI {
if(Object.keys(children).indexOf(commands[0]) < 0) if(Object.keys(children).indexOf(commands[0]) < 0)
return done([]); return done([]);
if(commands[0] === 'server' || commands[0] === 'tunnel') { if(commands[0] === 'server' || commands[0] === 'tunnel' || commands[0] === 'homekit') {
const child = new children[commands[0]](); const child = new children[commands[0]]();

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
const Swagger = require('swagger-client-promises'), const Swagger = require('swagger-client'),
Stream = require('./lib/stream'); Stream = require('./lib/stream');
class Client { class Client {
@ -13,7 +13,6 @@ class Client {
this.key = key || false; this.key = key || false;
this.swagger_path = '/api/docs/api.json'; this.swagger_path = '/api/docs/api.json';
this.success = function() {}; this.success = function() {};
this.failure = function(err) { throw err; };
Object.assign(this, options); Object.assign(this, options);
@ -23,13 +22,15 @@ class Client {
if(! this.key) if(! this.key)
throw new Error('client key is required'); throw new Error('client key is required');
this.swagger = new Swagger({ new Swagger({
url: `http://${this.host}:${this.port}${this.swagger_path}`, url: `http://${this.host}:${this.port}${this.swagger_path}`,
success: this._defineGetters.bind(this), usePromise: true,
failure: this.failure,
authorizations: { authorizations: {
HeaderKey: new Swagger.ApiKeyAuthorization('X-AIO-Key', this.key, 'header') HeaderKey: new Swagger.ApiKeyAuthorization('X-AIO-Key', this.key, 'header')
} }
}).then((client) => {
this.swagger = client;
this._defineGetters();
}); });
} }

View file

@ -23,6 +23,7 @@ gulp.task('lint', function() {
return gulp.src([ return gulp.src([
'index.js', 'index.js',
'cli/*.js', 'cli/*.js',
'homekit/*.js',
'client/**/*.js', 'client/**/*.js',
'server/index.js', 'server/index.js',
'server/lib/*.js', 'server/lib/*.js',

3
homekit/index.js Normal file
View file

@ -0,0 +1,3 @@
exports = module.exports = {
light: require('./light')
};

129
homekit/light.js Normal file
View file

@ -0,0 +1,129 @@
'use strict';
const Hap = require('hap-nodejs'),
Accessory = Hap.Accessory,
Service = Hap.Service,
Characteristic = Hap.Characteristic,
uuid = Hap.uuid;
class Light extends Accessory {
constructor(name, io) {
super(name, uuid.generate(`adafruit:accessories:light-${name}`));
this.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Manufacturer, 'Adafruit Industries')
.setCharacteristic(Characteristic.Model, 'Adafruit IO Light')
.setCharacteristic(Characteristic.SerialNumber, 'AIOLIGHT01');
// just call cb on ident
this.on('identify', (paired, cb) => cb());
this._name = name;
this._io = io;
this._state = {
power: 0,
brightness: 100,
hue: 255,
saturation: 100
};
this._stream = this._io.Groups.writable(this._name);
let timer = setTimeout(() => {
this._writeState();
}, 1000);
this._stream.on('data', (group) => {
if(timer) {
clearTimeout(timer);
timer = false;
}
group = JSON.parse(group.toString());
Object.keys(group.feeds).forEach((key) => {
if(/^brightness/.test(key))
this._state.brightness = parseInt(group.feeds[key]);
if(/^power/.test(key))
this._state.power = parseInt(group.feeds[key]);
if(/^hue/.test(key))
this._state.hue = parseInt(group.feeds[key]);
if(/^saturation/.test(key))
this._state.saturation = parseInt(group.feeds[key]);
});
});
const service = this.addService(Service.Lightbulb, `Adafruit Light`);
service.getCharacteristic(Characteristic.On)
.on('set', this._lightOnSet.bind(this))
.on('get', this._lightOnGet.bind(this));
service.getCharacteristic(Characteristic.Brightness)
.on('set', this._lightBrightnessSet.bind(this))
.on('get', this._lightBrightnessGet.bind(this));
service.getCharacteristic(Characteristic.Hue)
.on('set', this._lightHueSet.bind(this))
.on('get', this._lightHueGet.bind(this));
service.getCharacteristic(Characteristic.Saturation)
.on('set', this._lightSaturationSet.bind(this))
.on('get', this._lightSaturationGet.bind(this));
}
_lightOnGet(cb) {
cb(null, this._state.power);
}
_lightOnSet(value, cb) {
this._state.power = value ? 1 : 0;
this._writeState();
cb();
}
_lightBrightnessGet(cb) {
cb(null, this._state.brightness);
}
_lightBrightnessSet(value, cb) {
this._state.brightness = parseInt(value);
this._writeState();
cb();
}
_lightSaturationGet(cb) {
cb(null, this._state.saturation);
}
_lightSaturationSet(value, cb) {
this._state.saturation = parseInt(value);
this._writeState();
cb();
}
_lightHueGet(cb) {
cb(null, this._state.hue);
}
_lightHueSet(value, cb) {
this._state.hue = parseInt(value);
this._writeState();
cb();
}
_writeState() {
Object.keys(this._state).forEach((key) => {
this._state[key] = this._state[key] || 0;
});
this._stream.write(JSON.stringify({ feeds: this._state }));
}
}
exports = module.exports = Light;

View file

@ -45,12 +45,13 @@
"dotenv": "^1.2.0", "dotenv": "^1.2.0",
"express": "^4.13.3", "express": "^4.13.3",
"express-csv": "^0.6.0", "express-csv": "^0.6.0",
"hap-nodejs": "^0.1.1",
"inquirer": "^0.10.0", "inquirer": "^0.10.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mqtt": "^1.4.1", "mqtt": "^1.4.1",
"nedb": "^1.1.3", "nedb": "^1.1.3",
"restler": "^3.2.2", "restler": "^3.2.2",
"swagger-client-promises": "^1.0.2", "swagger-client": "swagger-api/swagger-js#d7f8bc26306fbfcb8e7be1daff94a07945562808",
"swagger-tools": "^0.9.5", "swagger-tools": "^0.9.5",
"winston": "^1.0.1", "winston": "^1.0.1",
"xml": "^1.0.0", "xml": "^1.0.0",