Compare commits

..

No commits in common. "api_v2" and "master" have entirely different histories.

11 changed files with 49 additions and 447 deletions

View file

@ -22,23 +22,21 @@ class ClientCLI extends CLI {
init() {
let options = {};
if(! process.env.AIO_CLIENT_USER || ! process.env.AIO_CLIENT_KEY)
return this.requireAuth(this.yargs);
const options = {
success: this.setupAPI.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;
new Client(process.env.AIO_CLIENT_USER, process.env.AIO_CLIENT_KEY, options)
.then((client) => {
this.client = client;
this.setupAPI(this.yargs);
})
.catch((err) => this.error(JSON.stringify(err)));
this.client = new Client(process.env.AIO_CLIENT_USER, process.env.AIO_CLIENT_KEY, options);
}
@ -70,12 +68,10 @@ class ClientCLI extends CLI {
};
new Client(process.env.AIO_CLIENT_USER, process.env.AIO_CLIENT_KEY)
.then((client) => {
this.client = client;
parse();
})
.catch(this.error.bind(this));
this.client = new Client(process.env.AIO_CLIENT_USER, process.env.AIO_CLIENT_KEY, {
success: parse.bind(this),
failure: this.error.bind(this)
});
}
@ -200,14 +196,7 @@ class ClientCLI extends CLI {
this.info('Success');
console.log(res.obj);
})
.catch(res => {
if(argv.json)
return console.log(res.statusText);
this.error(JSON.parse(res.statusText).error);
});
.catch(res => this.error(res.obj.toString().replace('Error: ', '')));
}
const questions = Object.keys(body.schema.properties).map(name => {
@ -230,14 +219,7 @@ class ClientCLI extends CLI {
this.info('Success');
console.log(res.obj);
})
.catch(res => {
if(argv.json)
return console.log(res.statusText);
this.error(JSON.parse(res.statusText).error);
});
.catch(res => this.error(res.obj.toString().replace('Error: ', '')));
});
}

View file

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

View file

@ -1,8 +1,7 @@
'use strict';
const Swagger = require('./lib/client'),
const Swagger = require('swagger-client-promises'),
Stream = require('./lib/stream'),
Signature = require('./lib/signature'),
pkg = require('../package.json');
class HeaderKey {
@ -27,12 +26,11 @@ class Client {
this.port = 80;
this.username = username || false;
this.key = key || false;
this.swagger_path = '/api/docs/v2.json';
this.authorizations = {
HeaderKey: new HeaderKey(this.key)
};
this.swagger_path = '/api/docs/v1.json';
this.success = function() {};
this.failure = function(err) { throw err; };
Object.assign(this, options || {});
Object.assign(this, options);
if(! this.username)
throw new Error('client username is required');
@ -40,14 +38,14 @@ class Client {
if(! this.key)
throw new Error('client key is required');
return new Swagger({
this.swagger = new Swagger({
url: `http://${this.host}:${this.port}${this.swagger_path}`,
usePromise: true,
authorizations: this.authorizations
}).then((client) => {
this.swagger = client;
this._defineGetters();
return this;
debug: true,
success: this._defineGetters.bind(this),
failure: this.failure,
authorizations: {
HeaderKey: new HeaderKey(this.key)
}
});
}
@ -70,7 +68,6 @@ class Client {
this.swagger[api].readable = (id) => { stream.connect(id); return stream; };
this.swagger[api].writable = (id) => { stream.connect(id); return stream; };
// add dynamic getter to this class for the API
Object.defineProperty(this, api, {
get: () => {
return this.swagger[api];
@ -79,14 +76,8 @@ class Client {
});
}
this.success();
static get Signature() {
return Signature;
}
static get Stream() {
return Stream;
}
}

View file

@ -1,12 +0,0 @@
const Swagger = require('swagger-client');
class Client extends Swagger {
idFromOp(path, httpMethod, op) {
op.operationId = op['x-swagger-router-action'];
return super.idFromOp(path, httpMethod, op);
}
}
exports = module.exports = Client;

View file

@ -1,99 +0,0 @@
'use strict';
const crypto = require('crypto'),
url = require('url');
class Signature {
constructor(options) {
this.username = false;
this.key = false;
this.host = false;
this.method = 'GET';
this.params = false;
this.path = false;
this.parsed = false;
Object.assign(this, options || {});
if(! this.username)
throw new Error('Username is required');
if(! this.key)
throw new Error('AIO Key is required');
if(! this.path)
throw new Error('path is required');
}
toString() {
const step1 = this.hmac(this.key, this.date()),
step2 = this.hmac(step1, this.host),
step3 = this.hmac(step2, this.method),
step4 = this.hmac(step3, this.params);
const signing_key = this.hmac(step4, this.version),
canonical_request = this.hash(this.request),
to_sign = `${this.algorithm}\n${this.date}\n${canonical_request}`;
return this.hmac(signing_key, to_sign);
}
get request() {
return `${this.method}\n${this.path}?${this.params}
host: ${this.host}
x-aio-date: ${this.date}`;
}
get version() {
return 'aio-signature-v1';
}
get credential() {
return `${this.username}/${this.version}`;
}
get date() {
return (new Date()).toISOString();
}
get algorithm() {
return 'sha512';
}
get algorithmName() {
return `aio-hmac-${this.algorithm()}`;
}
get path() {
return this.path;
}
set path(path) {
this.parsed = url.parse(path, true);
this.params = Object.keys(this.parsed.query).sort().map(q => q.toLowerCase()).join('&');
this.host = this.parsed.host;
this.path = this.parsed.href.split('?')[0];
}
hash(data) {
const hash = crypto.createHash(this.algorithm());
return hmac.update(data).digest('hex');
}
hmac(key, data) {
const hmac = crypto.createHmac(this.algorithm(), key);
return hmac.update(data).digest('hex');
}
}
exports = module.exports = Signature;

View file

@ -1,27 +1,34 @@
'use strict';
const gulp = require('gulp'),
eslint = require('gulp-eslint');
jshint = require('gulp-jshint');
gulp.task('lint', function() {
const config = {
ecmaFeatures: {
templateStrings: true
},
env: ['node', 'es6']
};
const lint = jshint({
"esnext": true,
"curly": false,
"eqeqeq": true,
"immed": true,
"newcap": false,
"noarg": true,
"sub": true,
"unused": "var",
"boss": true,
"eqnull": true,
"node": true,
"-W086": true
});
return gulp.src([
'index.js',
'cli/*.js',
'homekit/*.js',
'client/**/*.js',
'server/index.js',
'server/lib/*.js',
'tunnel/index.js',
'tunnel/lib/*.js'
]).pipe(eslint(config)).pipe(eslint.format()).pipe(eslint.failAfterError());
]).pipe(lint).pipe(jshint.reporter('jshint-stylish'));
});

View file

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

View file

@ -1,129 +0,0 @@
'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

@ -1,3 +1,3 @@
exports = {
Client: require('./client')
};
exports = module.exports = require('./client');
module.exports.CLI = require('./cli/index');

View file

@ -19,8 +19,9 @@
"homepage": "https://github.com/adafruit/adafruit-io-node#readme",
"devDependencies": {
"gulp": "^3.9.0",
"gulp-eslint": "^1.0.0",
"gulp-mocha": "^2.1.3"
"gulp-jshint": "^1.11.2",
"gulp-mocha": "^2.1.3",
"jshint-stylish": "^2.0.1"
},
"keywords": [
"adafruit",
@ -44,13 +45,12 @@
"dotenv": "^1.2.0",
"express": "^4.13.3",
"express-csv": "^0.6.0",
"hap-nodejs": "^0.3.2",
"inquirer": "^0.10.0",
"mkdirp": "^0.5.1",
"mqtt": "^1.4.1",
"nedb": "^1.1.3",
"restler": "^3.2.2",
"swagger-client": "^2.1.14",
"swagger-client-promises": "^1.0.2",
"swagger-tools": "^0.9.5",
"winston": "^1.0.1",
"xml": "^1.0.0",