├── .gitignore ├── README.md ├── hue-cli.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hue(1) 2 | ====== 3 | 4 | A command line interface to [philips hue](http://meethue.com) 5 | 6 | **NOTE:** A more complete and updated implementation of a Hue Management CLI 7 | can be found here https://github.com/bahamas10/hueadm 8 | 9 | Installation 10 | ------------ 11 | 12 | First, install [Node.js](http://nodejs.org), then: 13 | 14 | npm install -g hue-cli 15 | 16 | ...and the executable will be installed globally as `hue` 17 | 18 | Usage 19 | ----- 20 | 21 | Usage: hue [-c config] [-H host] [--json] [command] 22 | 23 | control philips hue over the command line 24 | 25 | examples 26 | hue config # view the hue config 27 | hue lights # get a list of lights 28 | hue lights 5 # get information about light 5 29 | hue lights 5,6,7 on # turn lights 5 6 and 7 on 30 | hue lights on # turn all lights on 31 | hue lights 1 ff0000 # turn light 1 red 32 | hue lights 1 red # same as above 33 | hue lights 1 +10 # increase the brightness by 10 (out of 254) 34 | hue lights 1 -10 # decrease the brightness by 10 (out of 254) 35 | hue lights 1 =100 # set the brightness to 100 (out of 254) 36 | hue lights 1 +10% # increase the brightness by 10% 37 | hue lights 1 -10% # decrease the brightness by 10% 38 | hue lights 1 =100% # set the brightness to 100% 39 | hue lights 4,5 colorloop # enable the colorloop effect on lights 4 and 5 40 | hue lights 4,5 alert # blink lights 4 and 5 for 30 seconds 41 | hue lights 4,5 clear # clear any effects on lights 4 and 5 42 | hue lights 1 state # set the state on light 1 as passed in as JSON over stdin 43 | hue rename 1 light-name # set light 1's name to the given string 44 | hue lights reset # reset all lamps to default (on, as if the bulb was just flipped on) 45 | hue lights 1,2 reset # reset just bulbs 1 and 2 46 | hue help # this message 47 | hue register # register this app to hue 48 | hue search # search for hue base stations 49 | 50 | commands 51 | config, lights, help, register, search 52 | 53 | options 54 | -c, --config config file, defaults to ~/.hue.json 55 | -h, --help print this message and exit 56 | -H, --host the hostname or ip of the bridge to control 57 | -j, --json force output to be in json 58 | -u, --updates check for available updates 59 | -v, --version print the version number and exit 60 | 61 | Example 62 | ------- 63 | 64 | ### starting off 65 | 66 | First, let's search for nearby base stations 67 | 68 | $ hue search 69 | 1 stations found 70 | 71 | 1: 10.0.1.218 72 | 73 | Pass in `-j` for json if you'd like 74 | 75 | $ hue -j search 76 | [ 77 | "10.0.1.218" 78 | ] 79 | 80 | Next, let's try to list the lights on that base station 81 | 82 | $ hue -H 10.0.1.218 lights 83 | error: application not registered, run `hue register` first 84 | 85 | This app isn't registered yet, let's go ahead and do that 86 | 87 | $ hue -H 10.0.1.218 register 88 | please go and press the link button on your base station 89 | Hue Base Station paired! 90 | 91 | ### listing lights 92 | 93 | All you had to do was press the button on your base station to register, cool 94 | right? Let's re-run the lights command 95 | 96 | $ hue -H 10.0.1.218 lights 97 | 1 Mike 1 98 | 2 Mike 2 99 | 3 Dave closet 100 | 4 Hallway 2 101 | 5 Hallway 1 102 | 6 Front hallway 103 | 7 Dave Ledge Left 104 | 8 Dave Ledge Right 105 | 9 Dave's Piano 106 | 10 Dave's Lamp 107 | 11 Balcony Mike 108 | 12 Balcony Dave 109 | 13 Balcony Living Room 110 | 14 Mike 3 111 | 15 Living room 3 112 | 16 Living room 1 113 | 114 | Again, `-j` if you'd like json output. 115 | 116 | Running with the command `lights` will give us a list of all the lights 117 | connected to the base station. 118 | 119 | From here, we can get information about a single light like: 120 | 121 | $ hue lights 1 122 | 1 on 141 Mike 1 123 | 124 | 141 in the above example is the brightness. 125 | 126 | And `-j` for json 127 | 128 | $ hue -j lights 1 129 | { 130 | "state": { 131 | "on": true, 132 | "bri": 141, 133 | "hue": 13122, 134 | "sat": 211, 135 | "xy": [ 136 | 0.5119, 137 | 0.4147 138 | ], 139 | "ct": 467, 140 | "alert": "none", 141 | "effect": "none", 142 | "colormode": "ct", 143 | "reachable": true 144 | }, 145 | "type": "Extended color light", 146 | ... 147 | } 148 | 149 | ### controlling the lights 150 | 151 | Let's actually mess with the lights now. Let's turn on the light in my closet. 152 | 153 | $ hue lights 3 on 154 | light 3 success 155 | 156 | What about both lights in the hallway? 157 | 158 | $ hue lights 4,5 on 159 | light 4 success 160 | light 5 success 161 | 162 | What if we try to turn on a non-existent light? 163 | 164 | $ hue lights 99 on 165 | light 99 failed: resource, /lights/99/state, not available 166 | 167 | Cool, errors handled properly. Let's see some more examples 168 | 169 | $ hue lights off 170 | light 1 success 171 | light 2 success 172 | light 3 success 173 | ... 174 | 175 | This is shorthand for 176 | 177 | $ hue lights all off 178 | 179 | Where `all` is a recognized keyword for all lights in the system. You can also: 180 | 181 | $ hue lights off 182 | 183 | To quickly turn off all lights on the system 184 | 185 | ### controlling colors 186 | 187 | > We can turn the lights on and off, that's great... what about colors? 188 | 189 | How about hex 190 | 191 | $ hue lights 4 ffffff 192 | light 4 success 193 | 194 | We just set the light in the hallway to pure white, hex `ffffff`. Let's go crazy 195 | and turn all of the lights in the house red (this is where we need the `all` keyword) 196 | 197 | $ hue lights all ff0000 198 | light 1 success 199 | light 2 success 200 | ... 201 | 202 | It's worth noting here that, because this tool is written in Node, all requests to the 203 | lights are done concurrently. This means we don't have to wait for light 1 to finish 204 | before we instruct light 2 to change, nor wait for light 2 to finish before we instruct 205 | light 3 to change, and so on. 206 | 207 | Shorthand hex is also supported 208 | 209 | $ hue lights 3,4 0f0 210 | light 3 success 211 | light 4 success 212 | 213 | Now lights 3 and 4 are green 214 | 215 | Last but not least, any CSS name is supported for colors 216 | 217 | $ hue lights 1 yellow 218 | light 1 success 219 | 220 | Light 1 is now yellow. The full list of colors is available here 221 | http://xahlee.info/js/css_color_names.html 222 | 223 | ### brightness 224 | 225 | Brightness can also be changed using the `=`, `+` and `-` operators 226 | 227 | $ hue lights 1 +20 228 | light 1 brightness 200 -> 220 229 | $ hue lights 1 -30 230 | light 1 brightness 220 -> 190 231 | $ hue lights 1 =150 232 | light 1 brightness 150 233 | 234 | ### effects 235 | 236 | You can enable the colorloop effect on lamps by running 237 | 238 | $ hue lights 4,5,6 colorloop 239 | light 4 success 240 | light 5 success 241 | light 6 success 242 | 243 | and clear all effects with 244 | 245 | $ hue lights 4,5,6 clear 246 | light 4 success 247 | light 5 success 248 | light 6 success 249 | 250 | ### debugging 251 | 252 | Last but not least, you can pass the state as JSON over stdin. The possible 253 | values are found at [http://developers.meethue.com/1_lightsapi.html](http://developers.meethue.com/1_lightsapi.html) 254 | in section 1.6. 255 | 256 | $ echo '{"bri": 240, "hue": 25500}' | hue lights 7 state 257 | 258 | The `state` keyword tells `hue` to read from stdin 259 | 260 | Config 261 | ------ 262 | 263 | A config file will be created at `~/.hue.json` upon registration that looks like... 264 | 265 | ``` json 266 | { 267 | "host": "1.2.3.4", 268 | "colors": { 269 | "myred": "fe0000", 270 | "myblue": "0000fe" 271 | } 272 | } 273 | ``` 274 | 275 | * `host`: the host to connect to (normally passed in as `-H`) 276 | * `colors`: a key-value pair of color aliases to their hex mapping, you can use these 277 | when changing the colors of a light 278 | 279 | Credits 280 | ------- 281 | 282 | * [Philips hue](http://meethue.com): I assume you know what this is by now 283 | * [hue.js](https://github.com/thatguydan/hue.js): Node.js hue client 284 | * [css-color-names](https://github.com/bahamas10/css-color-names): color aliases provided by this module 285 | 286 | License 287 | ------- 288 | 289 | MIT 290 | -------------------------------------------------------------------------------- /hue-cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Hue Command Line Interface 4 | * 5 | * Author: Dave Eddy 6 | * Date: 3/14/13 7 | * License: MIT 8 | */ 9 | var fs = require('fs'); 10 | var path = require('path'); 11 | var util = require('util'); 12 | 13 | var csscolors = require('css-color-names'); 14 | var deepmerge = require('deepmerge'); 15 | var getopt = require('posix-getopt'); 16 | var Hue = require('hue.js'); 17 | var sprintf = require('extsprintf').sprintf; 18 | function printf() { console.log(sprintf.apply(this, arguments)); } 19 | 20 | var package = require('./package.json'); 21 | 22 | var homedir = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 23 | var defaultconfigfile = path.join(homedir, '.hue.json'); 24 | var app = 'node-hue-cli'; 25 | 26 | /** 27 | * return the usage statement 28 | */ 29 | function usage() { 30 | return [ 31 | 'Usage: hue [-c config] [-H host] [--json] [command]', 32 | '', 33 | 'control philips hue over the command line', 34 | '', 35 | 'examples', 36 | ' hue config # view the hue config', 37 | ' hue lights # get a list of lights', 38 | ' hue lights 5 # get information about light 5', 39 | ' hue lights 5,6,7 on # turn lights 5 6 and 7 on', 40 | ' hue lights on # turn all lights on', 41 | ' hue lights 1 ff0000 # turn light 1 red', 42 | ' hue lights 1 red # same as above', 43 | ' hue lights 1 +10 # increase the brightness by 10 (out of 254)', 44 | ' hue lights 1 -10 # decrease the brightness by 10 (out of 254)', 45 | ' hue lights 1 =100 # set the brightness to 100 (out of 254)', 46 | ' hue lights 1 +10% # increase the brightness by 10%', 47 | ' hue lights 1 -10% # decrease the brightness by 10', 48 | ' hue lights 1 =100% # set the brightness to 100%', 49 | ' hue lights 4,5 colorloop # enable the colorloop effect on lights 4 and 5', 50 | ' hue lights 4,5 alert # blink lights 4 and 5 for 30 seconds', 51 | ' hue lights 4,5 clear # clear any effects on lights 4 and 5', 52 | ' hue lights 1 state # set the state on light 1 as passed in as JSON over stdin', 53 | ' hue rename 1 light-name # set light 1\'s name to the given string', 54 | ' hue lights reset # reset all lamps to default (on, as if the bulb was just flipped on)', 55 | ' hue lights 1,2 reset # reset just bulbs 1 and 2', 56 | ' hue help # this message', 57 | ' hue register # register this app to hue', 58 | ' hue search # search for hue base stations', 59 | ' hue alias # shows all the defined aliases', 60 | ' hue alias bedroom 8,9,10 # creates an alias allowing `hue lights bedroom on` and so on', 61 | '', 62 | 'commands', 63 | ' config, lights, help, register, search', 64 | '', 65 | 'options', 66 | ' -c, --config config file, defaults to ~/.hue.json', 67 | ' -h, --help print this message and exit', 68 | ' -H, --host the hostname or ip of the bridge to control', 69 | ' -j, --json force output to be in json', 70 | ' -u, --updates check for available updates', 71 | ' -v, --version print the version number and exit' 72 | ].join('\n'); 73 | } 74 | 75 | // command line arguments 76 | var options = [ 77 | 'c:(config)', 78 | 'h(help)', 79 | 'H:(host)', 80 | 'j(json)', 81 | 'u(updates)', 82 | 'v(version)' 83 | ].join(''); 84 | var parser = new getopt.BasicParser(options, process.argv); 85 | 86 | var option; 87 | var config = {}; 88 | var configfile; 89 | var json = false; 90 | while ((option = parser.getopt()) !== undefined) { 91 | switch (option.option) { 92 | case 'c': configfile = option.optarg; break; 93 | case 'h': console.log(usage()); process.exit(0); 94 | case 'H': config.host = option.optarg; break; 95 | case 'j': json = true; break; 96 | case 'u': // check for updates 97 | require('latest').checkupdate(package, function(ret, msg) { 98 | console.log(msg); 99 | process.exit(ret); 100 | }); 101 | return; 102 | case 'v': console.log(package.version); process.exit(0); 103 | default: console.error(usage()); process.exit(1); break; 104 | } 105 | } 106 | var args = process.argv.slice(parser.optind()); 107 | 108 | try { 109 | var file = configfile || defaultconfigfile; 110 | var readConfig = JSON.parse(fs.readFileSync(file, 'utf-8')); 111 | } catch (e) { 112 | if (configfile) { 113 | console.error('failed to read config %s: %s', configfile, e.message); 114 | process.exit(1); 115 | } 116 | } 117 | configfile = file; 118 | config = deepmerge(readConfig, config); 119 | 120 | // load in config colors if present 121 | if (config.colors) { 122 | Object.keys(config.colors).forEach(function(name) { 123 | csscolors[name] = config.colors[name]; 124 | }); 125 | } 126 | 127 | // command switch 128 | var client, lights; 129 | switch (args[0]) { 130 | case 'config': // get the config as json 131 | client = getclient(); 132 | client.config(function(err, data) { 133 | console.log(JSON.stringify(err || data, null, 2)); 134 | }); 135 | break; 136 | case 'help': // print the help message 137 | console.log(usage()); 138 | break; 139 | case 'lights': case 'light': case 'list':// mess with the lights 140 | client = getclient(); 141 | getlights(client, function(lights) { 142 | // if there are no lights specified, return the list of lights 143 | var keys = Object.keys(lights); 144 | if (!args[1]) { 145 | if (json) return console.log(JSON.stringify(lights, null, 2)); 146 | //printf('%4s %s', 'ID', 'NAME'); 147 | keys.forEach(function(key) { 148 | printf('%4d %s', key, lights[key].name); 149 | }); 150 | return; 151 | } 152 | 153 | // handle shortucts like `lights off`, `lights all on` 154 | var l = args[1].split(','); 155 | switch (l[0]) { 156 | case 'all': l = keys; break; 157 | case 'on': l = keys; args[2] = 'on'; break; 158 | case 'off': l = keys; args[2] = 'off'; break; 159 | case 'colorloop': l = keys; args[2] = 'colorloop'; break; 160 | case 'alert': l = keys; args[2] = 'alert'; break; 161 | case 'clear': l = keys; args[2] = 'clear'; break; 162 | case 'reset': l = keys; args[2] = 'reset'; break; 163 | case 'state': l = keys; args[2] = 'state'; break; 164 | default: 165 | if (config.alias && config.alias[l]) { 166 | l = config.alias[l].split(','); 167 | } 168 | break; 169 | } 170 | // if there is no action specified, return info for all lights 171 | if (!args[2]) { 172 | //if (!json) printf('%4s %-5s %s', 'ID', 'STATE', 'NAME'); 173 | l.forEach(function(id) { 174 | client.light(id, function(err, data) { 175 | if (data) data.id = id; 176 | if (json) return console.log(JSON.stringify(err || data, null, 2)); 177 | if (err) return printf('%4d %-5s %s (type %d)', id, 'error', err.description, err.type); 178 | 179 | printf('%4d %-5s %-7d %s', 180 | id, 181 | data.state.on ? 'on' : 'off', 182 | data.state.bri, 183 | data.name); 184 | }); 185 | }); 186 | return; 187 | } 188 | 189 | switch (args[2]) { 190 | case 'off': l.forEach(function(id) { client.off(id, callback(id)); }); break; 191 | case 'on': l.forEach(function(id) { client.on(id, callback(id)); }); break; 192 | case 'colorloop': l.forEach(function(id) { client.state(id, {effect: 'colorloop'}, callback(id)); }); break; 193 | case 'alert': l.forEach(function(id) { client.state(id, {alert: 'lselect'}, callback(id)); }); break; 194 | case 'clear': l.forEach(function(id) { client.state(id, {effect: 'none', alert: 'none'}, callback(id)); }); break; 195 | case 'reset': l.forEach(function(id) { client.state(id, {on: true, bri: 254, effect: 'none', alert: 'none', ct: 370}, callback(id)); }); break; 196 | case 'state': // read state from stdin 197 | var data = JSON.parse(fs.readFileSync('/dev/stdin', 'utf-8')); 198 | l.forEach(function(id) { 199 | client.state(id, data, callback(id)); 200 | }); 201 | break; 202 | default: // hex, colors, or brightness 203 | var s = args[2]; 204 | var match; 205 | 206 | if ((match = s.match(/^([-+=])([0-9]+)(%?)$/))) { 207 | var op = match[1]; 208 | var num = match[2]; 209 | var perc = match[3]; 210 | l.forEach(function(id) { 211 | client.light(id, function(err, data) { 212 | if (err) { 213 | if (json) 214 | return console.log(JSON.stringify(err || data, null, 2)); 215 | return printf('%4d %-5s %s (type %d)', id, 'error', err.description, err.type); 216 | } 217 | var bri = data.state.bri; 218 | var oldbri = bri; 219 | switch (op) { 220 | case '=': 221 | if (perc) 222 | bri = Math.round(num * (254/100)); 223 | else 224 | bri = num; 225 | break; 226 | case '+': 227 | if (perc) 228 | bri += Math.round(num * (254/100)); 229 | else 230 | bri += num; 231 | break; 232 | case '-': 233 | if (perc) 234 | bri -= Math.round(num * (254/100)); 235 | else 236 | bri -= num; 237 | break; 238 | } 239 | bri = Math.min(254, Math.max(1, bri)); 240 | client.state(id, {bri: bri}, function(err, data) { 241 | if (json) return console.log(JSON.stringify(err || data, null, 2)); 242 | if (err) return printf('%4d %-5s %s (type %d)', id, 'error', err.description, err.type); 243 | console.log('light %d brightness %d -> %s', id, oldbri, bri); 244 | 245 | }); 246 | }); 247 | }); 248 | return; 249 | } 250 | 251 | var hex = csscolors[s] || s; 252 | var rgb = hex2rgb(hex); 253 | 254 | l.forEach(function(id) { 255 | client.rgb(id, rgb[0], rgb[1], rgb[2], callback(id)); 256 | }); 257 | break; 258 | } 259 | 260 | function callback(id) { 261 | return function(err, data) { 262 | if (json) return console.log(JSON.stringify(err || data, null, 2)); 263 | if (err) return console.error('light %d failed: %s', id, err.description); 264 | console.log('light %d success', id); 265 | } 266 | } 267 | }); 268 | break; 269 | case 'register': // register this app 270 | // Check for existing config 271 | var existingconfig = statPath(configfile); 272 | if (existingconfig && existingconfig.isFile()) { 273 | console.log('A config file already exists at %s', configfile); 274 | console.log('please remove it before attempting to register a new hub') 275 | process.exit(1); 276 | } 277 | // Attempt to pair with hue hub 278 | client = getclient(); 279 | console.log('please go and press the link button on your base station'); 280 | client.register(function(err, resp) { 281 | if (err) { 282 | console.error('failed to pair to Hue Base Station %s', config.host); 283 | throw err; 284 | } 285 | 286 | console.log('Hue Base Station paired!') 287 | console.log('username: ' + resp[0].success.username); 288 | config.username = resp[0].success.username; 289 | 290 | // writing config file 291 | var s = JSON.stringify(config, null, 2); 292 | fs.writeFileSync(configfile, s + '\n'); 293 | console.log('config file written to `%s`', configfile); 294 | }); 295 | break; 296 | case 'alias': 297 | config.alias = config.alias || {}; 298 | if (args.length === 1) { 299 | console.log(JSON.stringify(config.alias, null, 2)); 300 | } else if (args.length == 3) { 301 | config.alias[args[1]] = args[2]; 302 | // writing config file 303 | var s = JSON.stringify(config, null, 2); 304 | fs.writeFileSync(configfile, s + '\n'); 305 | console.log('config in `%s` updated', configfile); 306 | } else { 307 | console.error('wrong usage of alias, run `hue help`'); 308 | process.exit(1); 309 | } 310 | break; 311 | case 'search': // search for base stations 312 | Hue.discover(function(stations) { 313 | if (json) return console.log(JSON.stringify(stations, null, 2)); 314 | console.log('%d stations found\n', stations.length); 315 | stations.forEach(function(name, i) { console.log('%d: %s', i+1, name); }); 316 | }); 317 | break; 318 | case 'rename': // rename light 319 | client = getclient(); 320 | client.rename(args[1], args[2], function(reply) { 321 | if (reply) { 322 | console.log('problem renaming light: ' + reply.description); 323 | } else { 324 | console.log('light %d renamed', args[1]); 325 | } 326 | }); 327 | break; 328 | default: // uh oh 329 | console.error('unknown command: run `hue help` for more information'); 330 | process.exit(1); 331 | } 332 | 333 | // wrapper around get client to error on failure 334 | function getclient() { 335 | if (!config.host) { 336 | console.error([ 337 | 'error: host not set', 338 | '', 339 | 'search for hosts with `hue search`', 340 | 'then run with `-H `', 341 | ].join('\n')); 342 | process.exit(1); 343 | } 344 | 345 | // create the client 346 | var client = Hue.createClient({ 347 | stationIp: config.host, 348 | appName: app, 349 | username: config.username 350 | }); 351 | return client; 352 | } 353 | 354 | // wrapper around get lights to error on failure 355 | function getlights(client, cb) { 356 | // checking for lights will also help us ensure the app is registered 357 | // from exmample here https://github.com/thatguydan/hue.js 358 | client.lights(function(err, lights) { 359 | if (err && err.type === 1) { 360 | console.error('error: application not registered, run `hue register` first'); 361 | process.exit(1); 362 | } 363 | if (err) throw err; 364 | cb(lights) 365 | }); 366 | } 367 | 368 | // convert a 3 or 6 character hex string to rgb 369 | function hex2rgb(hex) { 370 | if (hex[0] === '#') hex = hex.slice(1); 371 | var r, g, b; 372 | 373 | if (hex.length === 3) { 374 | r = todec(hex[0], hex[0]); 375 | g = todec(hex[1], hex[1]); 376 | b = todec(hex[2], hex[2]); 377 | } else { 378 | r = todec(hex[0], hex[1]); 379 | g = todec(hex[2], hex[3]); 380 | b = todec(hex[4], hex[5]); 381 | } 382 | 383 | return [r, g, b]; 384 | 385 | function todec(h, i) { 386 | return parseInt(h + '' + i, 16) 387 | } 388 | } 389 | 390 | function statPath(path) { 391 | try { 392 | return fs.statSync(path); 393 | } catch (ex) {} 394 | return false; 395 | } 396 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hue-cli", 3 | "description": "A command line interface to philips hue", 4 | "author": "Dave Eddy (http://www.daveeddy.com)", 5 | "version": "0.4.0", 6 | "repository": { 7 | "url": "https://github.com/bahamas10/hue-cli", 8 | "type": "git" 9 | }, 10 | "scripts": { 11 | "test": "for f in examples/*.js; do echo \"$f\"; node \"$f\" || exit 1; echo; done; echo 'Passed'" 12 | }, 13 | "preferGlobal": true, 14 | "bin": { 15 | "hue": "./hue-cli.js" 16 | }, 17 | "dependencies": { 18 | "css-color-names": "0.0.0", 19 | "deepmerge": "^1.5.1", 20 | "extsprintf": "~1.0.2", 21 | "hue.js": "https://github.com/bahamas10/hue.js/tarball/master", 22 | "latest": "~0.1.1", 23 | "posix-getopt": "~1.0.0" 24 | }, 25 | "devDependencies": {}, 26 | "optionalDependencies": {}, 27 | "engines": { 28 | "node": "*" 29 | }, 30 | "keywords": [ 31 | "hue", 32 | "philips", 33 | "automation" 34 | ] 35 | } 36 | --------------------------------------------------------------------------------