Compare commits

...

62 commits

Author SHA1 Message Date
Todd Treece
3623b7335e export client 2016-06-14 17:20:11 -04:00
Todd Treece
2920e220e3 allow authorizations to be passed in via config to client 2016-06-14 14:54:37 -04:00
Todd Treece
b7946a9e4d Merge branch 'api_v2' of github.com:adafruit/io-client-node into api_v2 2016-06-14 11:42:43 -04:00
Todd Treece
f894887452 fix merge error 2016-06-14 11:39:47 -04:00
Todd Treece
7ed74a8804 add swagger client operationId override 2016-06-14 11:39:00 -04:00
Todd Treece
a8cf50c7b4 4.4.0 2016-06-14 11:39:00 -04:00
Todd Treece
874b7ade9f 4.3.0 2016-06-14 11:39:00 -04:00
Todd Treece
aaa4035e7e add user agent to client headers 2016-06-14 11:39:00 -04:00
Todd Treece
700d602701 4.2.7 2016-06-14 11:37:59 -04:00
Todd Treece
d4092eb6ef add latest docs 2016-06-14 11:37:59 -04:00
Todd Treece
12ee3ef61b 4.2.6 2016-06-14 11:37:59 -04:00
Todd Treece
bfd7c20426 update api docs path 2016-06-14 11:37:58 -04:00
Todd Treece
2a40b1444c switch to swagger-client promises 2016-06-14 11:37:58 -04:00
Todd Treece
9d1a189c92 working test version of light accessory 2016-06-14 11:36:33 -04:00
Todd Treece
83e14d02b7 adds node-persist init to homekit cli 2016-06-14 11:36:33 -04:00
Todd Treece
a1fd20c23c adds homekit to gulp syntax check 2016-06-14 11:36:33 -04:00
Todd Treece
536df6bd1e adds accessory index 2016-06-14 11:36:33 -04:00
Todd Treece
0176c93acf adds homekit to main cli 2016-06-14 11:36:33 -04:00
Todd Treece
a92e04d218 adds homekit light accessory 2016-06-14 11:36:33 -04:00
Todd Treece
c9ecab57be adds homekit cli 2016-06-14 11:36:33 -04:00
Todd Treece
d10cabb6e3 adds hap-node dependency 2016-06-14 11:36:33 -04:00
Todd Treece
58430f29f1 4.2.5 2016-06-14 11:36:33 -04:00
Todd Treece
18399e27a8 fix client write stream errors on dropped connection 2016-06-14 11:36:33 -04:00
Todd Treece
8eeb1d0c7a 4.2.4 2016-06-14 11:36:33 -04:00
Todd Treece
b1319313c7 watch for client mqtt reconnect events 2016-06-14 11:36:33 -04:00
Todd Treece
a7ec84f7c4 4.2.3 2016-06-14 11:36:33 -04:00
Todd Treece
c0812e1144 fix yargs issue 2016-06-14 11:36:32 -04:00
Todd Treece
a21c9a206f 4.2.2 2016-06-14 11:36:32 -04:00
Todd Treece
50a5af1213 expose yargs to cli sub modules 2016-06-14 11:36:32 -04:00
Todd Treece
e8d3dd6954 4.2.1 2016-06-14 11:36:32 -04:00
Todd Treece
ed4e97f0a3 fix cli sub module scope issue 2016-06-14 11:36:32 -04:00
Todd Treece
1f52fe01f9 4.2.0 2016-06-14 11:36:32 -04:00
Todd Treece
d562b34890 allow sub commands to be set externally in cli 2016-06-14 11:36:32 -04:00
Todd Treece
14022293b4 4.1.4 2016-06-14 11:36:32 -04:00
Todd Treece
6bde7147a9 adds cli to exported modules 2016-06-14 11:36:32 -04:00
Todd Treece
2da9904981 4.1.3 2016-06-14 11:36:32 -04:00
Todd Treece
fbfa2201db 4.1.2 2016-06-14 11:36:32 -04:00
Todd Treece
c7272a64ed removes console.log 2016-06-14 11:36:32 -04:00
Todd Treece
9b6e8d9787 4.1.1 2016-06-14 11:36:32 -04:00
Todd Treece
e7b47c384e bump highwatermark to 100k for client streams 2016-06-14 11:36:32 -04:00
Todd Treece
bad04cef59 4.1.0 2016-06-14 11:36:32 -04:00
Todd Treece
1134c5985b adds client stdin stream to cli 2016-06-14 11:36:31 -04:00
Todd Treece
f0551dff3b 4.0.6 2016-06-14 11:36:31 -04:00
Todd Treece
abb1e80a43 check to make sure cli tunnel command exists 2016-06-14 11:36:31 -04:00
Todd Treece
9b0997b8fc check to make sure cli server command exists 2016-06-14 11:36:31 -04:00
Todd Treece
3c2f1c9217 fixes cli module export 2016-06-14 11:36:31 -04:00
Todd Treece
e18d38e7d6 fixes nodejs.org link in readme 2016-06-14 11:36:31 -04:00
Todd Treece
0e13b8aef1 add swagger client operationId override 2016-06-14 11:31:38 -04:00
Todd Treece
bd8745a7be Merge branch 'homekit' of github.com:adafruit/io-client-node into api_v2 2016-06-13 16:07:23 -04:00
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
Todd Treece
e1f26c365a adds static getters for Client.Stream & Client.Signature 2015-09-25 12:52:52 -04:00
Todd Treece
89074c9848 finished client signature class 2015-09-25 12:50:38 -04:00
Todd Treece
187e7c5fe4 use eslint instead of jshint 2015-09-25 12:50:02 -04:00
Todd Treece
ec6eb8a631 adds signature module 2015-09-24 18:36:49 -04:00
11 changed files with 447 additions and 49 deletions

View file

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

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.completions = [
'client',
'homekit',
'server',
'tunnel',
'help',
@ -24,6 +25,7 @@ class CLI {
this.sub = {
client: require('./client'),
homekit: require('./homekit'),
server: require('./server'),
tunnel: require('./tunnel')
};
@ -50,6 +52,7 @@ 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')
@ -163,7 +166,7 @@ class CLI {
if(Object.keys(children).indexOf(commands[0]) < 0)
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]]();

View file

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

12
client/lib/client.js Normal file
View file

@ -0,0 +1,12 @@
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;

99
client/lib/signature.js Normal file
View file

@ -0,0 +1,99 @@
'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,34 +1,27 @@
'use strict';
const gulp = require('gulp'),
jshint = require('gulp-jshint');
eslint = require('gulp-eslint');
gulp.task('lint', function() {
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
});
const config = {
ecmaFeatures: {
templateStrings: true
},
env: ['node', 'es6']
};
return gulp.src([
'index.js',
'cli/*.js',
'homekit/*.js',
'client/**/*.js',
'server/index.js',
'server/lib/*.js',
'tunnel/index.js',
'tunnel/lib/*.js'
]).pipe(lint).pipe(jshint.reporter('jshint-stylish'));
]).pipe(eslint(config)).pipe(eslint.format()).pipe(eslint.failAfterError());
});

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

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

View file

@ -19,9 +19,8 @@
"homepage": "https://github.com/adafruit/adafruit-io-node#readme",
"devDependencies": {
"gulp": "^3.9.0",
"gulp-jshint": "^1.11.2",
"gulp-mocha": "^2.1.3",
"jshint-stylish": "^2.0.1"
"gulp-eslint": "^1.0.0",
"gulp-mocha": "^2.1.3"
},
"keywords": [
"adafruit",
@ -45,12 +44,13 @@
"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-promises": "^1.0.2",
"swagger-client": "^2.1.14",
"swagger-tools": "^0.9.5",
"winston": "^1.0.1",
"xml": "^1.0.0",