-
Notifications
You must be signed in to change notification settings - Fork 24
/
Copy pathhue-cli.js
executable file
·395 lines (368 loc) · 13.4 KB
/
hue-cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
#!/usr/bin/env node
/**
* Hue Command Line Interface
*
* Author: Dave Eddy <[email protected]>
* Date: 3/14/13
* License: MIT
*/
var fs = require('fs');
var path = require('path');
var util = require('util');
var csscolors = require('css-color-names');
var deepmerge = require('deepmerge');
var getopt = require('posix-getopt');
var Hue = require('hue.js');
var sprintf = require('extsprintf').sprintf;
function printf() { console.log(sprintf.apply(this, arguments)); }
var package = require('./package.json');
var homedir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
var defaultconfigfile = path.join(homedir, '.hue.json');
var app = 'node-hue-cli';
/**
* return the usage statement
*/
function usage() {
return [
'Usage: hue [-c config] [-H host] [--json] [command]',
'',
'control philips hue over the command line',
'',
'examples',
' hue config # view the hue config',
' hue lights # get a list of lights',
' hue lights 5 # get information about light 5',
' hue lights 5,6,7 on # turn lights 5 6 and 7 on',
' hue lights on # turn all lights on',
' hue lights 1 ff0000 # turn light 1 red',
' hue lights 1 red # same as above',
' hue lights 1 +10 # increase the brightness by 10 (out of 254)',
' hue lights 1 -10 # decrease the brightness by 10 (out of 254)',
' hue lights 1 =100 # set the brightness to 100 (out of 254)',
' hue lights 1 +10% # increase the brightness by 10%',
' hue lights 1 -10% # decrease the brightness by 10',
' hue lights 1 =100% # set the brightness to 100%',
' hue lights 4,5 colorloop # enable the colorloop effect on lights 4 and 5',
' hue lights 4,5 alert # blink lights 4 and 5 for 30 seconds',
' hue lights 4,5 clear # clear any effects on lights 4 and 5',
' hue lights 1 state # set the state on light 1 as passed in as JSON over stdin',
' hue rename 1 light-name # set light 1\'s name to the given string',
' hue lights reset # reset all lamps to default (on, as if the bulb was just flipped on)',
' hue lights 1,2 reset # reset just bulbs 1 and 2',
' hue help # this message',
' hue register # register this app to hue',
' hue search # search for hue base stations',
' hue alias # shows all the defined aliases',
' hue alias bedroom 8,9,10 # creates an alias allowing `hue lights bedroom on` and so on',
'',
'commands',
' config, lights, help, register, search',
'',
'options',
' -c, --config <file> config file, defaults to ~/.hue.json',
' -h, --help print this message and exit',
' -H, --host the hostname or ip of the bridge to control',
' -j, --json force output to be in json',
' -u, --updates check for available updates',
' -v, --version print the version number and exit'
].join('\n');
}
// command line arguments
var options = [
'c:(config)',
'h(help)',
'H:(host)',
'j(json)',
'u(updates)',
'v(version)'
].join('');
var parser = new getopt.BasicParser(options, process.argv);
var option;
var config = {};
var configfile;
var json = false;
while ((option = parser.getopt()) !== undefined) {
switch (option.option) {
case 'c': configfile = option.optarg; break;
case 'h': console.log(usage()); process.exit(0);
case 'H': config.host = option.optarg; break;
case 'j': json = true; break;
case 'u': // check for updates
require('latest').checkupdate(package, function(ret, msg) {
console.log(msg);
process.exit(ret);
});
return;
case 'v': console.log(package.version); process.exit(0);
default: console.error(usage()); process.exit(1); break;
}
}
var args = process.argv.slice(parser.optind());
try {
var file = configfile || defaultconfigfile;
var readConfig = JSON.parse(fs.readFileSync(file, 'utf-8'));
} catch (e) {
if (configfile) {
console.error('failed to read config %s: %s', configfile, e.message);
process.exit(1);
}
}
configfile = file;
config = deepmerge(readConfig, config);
// load in config colors if present
if (config.colors) {
Object.keys(config.colors).forEach(function(name) {
csscolors[name] = config.colors[name];
});
}
// command switch
var client, lights;
switch (args[0]) {
case 'config': // get the config as json
client = getclient();
client.config(function(err, data) {
console.log(JSON.stringify(err || data, null, 2));
});
break;
case 'help': // print the help message
console.log(usage());
break;
case 'lights': case 'light': case 'list':// mess with the lights
client = getclient();
getlights(client, function(lights) {
// if there are no lights specified, return the list of lights
var keys = Object.keys(lights);
if (!args[1]) {
if (json) return console.log(JSON.stringify(lights, null, 2));
//printf('%4s %s', 'ID', 'NAME');
keys.forEach(function(key) {
printf('%4d %s', key, lights[key].name);
});
return;
}
// handle shortucts like `lights off`, `lights all on`
var l = args[1].split(',');
switch (l[0]) {
case 'all': l = keys; break;
case 'on': l = keys; args[2] = 'on'; break;
case 'off': l = keys; args[2] = 'off'; break;
case 'colorloop': l = keys; args[2] = 'colorloop'; break;
case 'alert': l = keys; args[2] = 'alert'; break;
case 'clear': l = keys; args[2] = 'clear'; break;
case 'reset': l = keys; args[2] = 'reset'; break;
case 'state': l = keys; args[2] = 'state'; break;
default:
if (config.alias && config.alias[l]) {
l = config.alias[l].split(',');
}
break;
}
// if there is no action specified, return info for all lights
if (!args[2]) {
//if (!json) printf('%4s %-5s %s', 'ID', 'STATE', 'NAME');
l.forEach(function(id) {
client.light(id, function(err, data) {
if (data) data.id = id;
if (json) return console.log(JSON.stringify(err || data, null, 2));
if (err) return printf('%4d %-5s %s (type %d)', id, 'error', err.description, err.type);
printf('%4d %-5s %-7d %s',
id,
data.state.on ? 'on' : 'off',
data.state.bri,
data.name);
});
});
return;
}
switch (args[2]) {
case 'off': l.forEach(function(id) { client.off(id, callback(id)); }); break;
case 'on': l.forEach(function(id) { client.on(id, callback(id)); }); break;
case 'colorloop': l.forEach(function(id) { client.state(id, {effect: 'colorloop'}, callback(id)); }); break;
case 'alert': l.forEach(function(id) { client.state(id, {alert: 'lselect'}, callback(id)); }); break;
case 'clear': l.forEach(function(id) { client.state(id, {effect: 'none', alert: 'none'}, callback(id)); }); break;
case 'reset': l.forEach(function(id) { client.state(id, {on: true, bri: 254, effect: 'none', alert: 'none', ct: 370}, callback(id)); }); break;
case 'state': // read state from stdin
var data = JSON.parse(fs.readFileSync('/dev/stdin', 'utf-8'));
l.forEach(function(id) {
client.state(id, data, callback(id));
});
break;
default: // hex, colors, or brightness
var s = args[2];
var match;
if ((match = s.match(/^([-+=])([0-9]+)(%?)$/))) {
var op = match[1];
var num = match[2];
var perc = match[3];
l.forEach(function(id) {
client.light(id, function(err, data) {
if (err) {
if (json)
return console.log(JSON.stringify(err || data, null, 2));
return printf('%4d %-5s %s (type %d)', id, 'error', err.description, err.type);
}
var bri = data.state.bri;
var oldbri = bri;
switch (op) {
case '=':
if (perc)
bri = Math.round(num * (254/100));
else
bri = num;
break;
case '+':
if (perc)
bri += Math.round(num * (254/100));
else
bri += num;
break;
case '-':
if (perc)
bri -= Math.round(num * (254/100));
else
bri -= num;
break;
}
bri = Math.min(254, Math.max(1, bri));
client.state(id, {bri: bri}, function(err, data) {
if (json) return console.log(JSON.stringify(err || data, null, 2));
if (err) return printf('%4d %-5s %s (type %d)', id, 'error', err.description, err.type);
console.log('light %d brightness %d -> %s', id, oldbri, bri);
});
});
});
return;
}
var hex = csscolors[s] || s;
var rgb = hex2rgb(hex);
l.forEach(function(id) {
client.rgb(id, rgb[0], rgb[1], rgb[2], callback(id));
});
break;
}
function callback(id) {
return function(err, data) {
if (json) return console.log(JSON.stringify(err || data, null, 2));
if (err) return console.error('light %d failed: %s', id, err.description);
console.log('light %d success', id);
}
}
});
break;
case 'register': // register this app
// Check for existing config
var existingconfig = statPath(configfile);
if (existingconfig && existingconfig.isFile()) {
console.log('A config file already exists at %s', configfile);
console.log('please remove it before attempting to register a new hub')
process.exit(1);
}
// Attempt to pair with hue hub
client = getclient();
console.log('please go and press the link button on your base station');
client.register(function(err, resp) {
if (err) {
console.error('failed to pair to Hue Base Station %s', config.host);
throw err;
}
console.log('Hue Base Station paired!')
console.log('username: ' + resp[0].success.username);
config.username = resp[0].success.username;
// writing config file
var s = JSON.stringify(config, null, 2);
fs.writeFileSync(configfile, s + '\n');
console.log('config file written to `%s`', configfile);
});
break;
case 'alias':
config.alias = config.alias || {};
if (args.length === 1) {
console.log(JSON.stringify(config.alias, null, 2));
} else if (args.length == 3) {
config.alias[args[1]] = args[2];
// writing config file
var s = JSON.stringify(config, null, 2);
fs.writeFileSync(configfile, s + '\n');
console.log('config in `%s` updated', configfile);
} else {
console.error('wrong usage of alias, run `hue help`');
process.exit(1);
}
break;
case 'search': // search for base stations
Hue.discover(function(stations) {
if (json) return console.log(JSON.stringify(stations, null, 2));
console.log('%d stations found\n', stations.length);
stations.forEach(function(name, i) { console.log('%d: %s', i+1, name); });
});
break;
case 'rename': // rename light
client = getclient();
client.rename(args[1], args[2], function(reply) {
if (reply) {
console.log('problem renaming light: ' + reply.description);
} else {
console.log('light %d renamed', args[1]);
}
});
break;
default: // uh oh
console.error('unknown command: run `hue help` for more information');
process.exit(1);
}
// wrapper around get client to error on failure
function getclient() {
if (!config.host) {
console.error([
'error: host not set',
'',
'search for hosts with `hue search`',
'then run with `-H <host>`',
].join('\n'));
process.exit(1);
}
// create the client
var client = Hue.createClient({
stationIp: config.host,
appName: app,
username: config.username
});
return client;
}
// wrapper around get lights to error on failure
function getlights(client, cb) {
// checking for lights will also help us ensure the app is registered
// from exmample here https://github.com/thatguydan/hue.js
client.lights(function(err, lights) {
if (err && err.type === 1) {
console.error('error: application not registered, run `hue register` first');
process.exit(1);
}
if (err) throw err;
cb(lights)
});
}
// convert a 3 or 6 character hex string to rgb
function hex2rgb(hex) {
if (hex[0] === '#') hex = hex.slice(1);
var r, g, b;
if (hex.length === 3) {
r = todec(hex[0], hex[0]);
g = todec(hex[1], hex[1]);
b = todec(hex[2], hex[2]);
} else {
r = todec(hex[0], hex[1]);
g = todec(hex[2], hex[3]);
b = todec(hex[4], hex[5]);
}
return [r, g, b];
function todec(h, i) {
return parseInt(h + '' + i, 16)
}
}
function statPath(path) {
try {
return fs.statSync(path);
} catch (ex) {}
return false;
}