Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
jsiegenthaler committed Jan 6, 2024
2 parents a4dab0c + 61464cc commit 28891d3
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 40 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ See the [Readme file](https://github.com/jsiegenthaler/hueget/blob/master/README

# Bug Fixes and Improvements

## 0.7.0 (2024-01-06)
* Added toggle capability for lights and groups
* Bumped dependencies: "axios": "^1.6.5"

## 0.6.6 (2023-11-27)
* Added some more README.md improvements

Expand Down
24 changes: 19 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ Flash your lights in the entire house when the doorbell rings. I have a Shelly1
## Control Hue Lights directly from Shelly Motion Sensors
Anything that can call a url when triggered - such as a Shelly Motion Sensor - can be used to turn the lights on and off again. Make sure the motion sensor calls a url to turn lights on, and a url to turn lights off. The Shelly Motion Sensor is ideal for this, as you can activate call urls for different motion triggers.

## Toggle lights from a Shelly Button 1
Toggle a light or group of lights from a button that sends a non-changing URL. The ```toggle``` command is perfect for any pushbutton controller that does not know (or can not know) the current light state, and only sends a non-changing static URL, such as a Shelly Button 1.

## Be Home Soon Alert
Flash lights in a room or in any group (zone, room) when someone comes home. The ```alert=lselect``` command is perfect to generate a 15 second long flash without any extra programming. Just call the URL from Apple HomeKit automations when a person arrives in your geofence.

Expand Down Expand Up @@ -144,19 +147,22 @@ Examples:
* Turn light 31 on at 50% brightness: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/31/state?on=true&bri=50
* Turn light 31 on at 100% brightness: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/31/state?on=true&bri=100
* Turn light 31 on at 100% brightness, 0.5,0.6 xy: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/31/state?on=true&bri=100&xy=[0.5%2c0.6]
* Toggle light 31: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/31/toggle
* Identify light 31 with a single blink: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/31/state?alert=select
* Identify light 31 with 15 seconds of blinking: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/31/state?alert=lselect

## Groups
### Group 0 (a special group for all lights in your home)
* Turn group 0 on: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/0/action?on=true
* Turn group 0 off: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/0/action?on=false
* Toggle group 0: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/0/toggle
* Identify group 0 with 15 seconds of blinking: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/0/action?alert=lselect


### Group 2 (example)
* Turn group 2 on: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/action?on=true
* Turn group 2 off: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/action?on=false
* Toggle group 2: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/toggle
* Turn group 2 on at 50% brightness: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/action?on=true&bri=50
* Turn group 2 on at 100% brightness: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/action?on=true&bri=100
* Turn group 2 on at 100% brightness, 0.5,0.6 xy: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/state?on=true&bri=100&xy=[0.5%2c0.6]
Expand All @@ -165,7 +171,15 @@ Examples:

Groups are collections of lights, and are used for Rooms and Zones in the Hue app.

# Supported Keywords
## Special Commands
The hueget server supports a special toggle command, which does not exist natively in the Philips Hue bridge. This toggles (changes the state) of a specified light or a group, allowing you to toggle the light/group state with a single URL.

Syntax:
* Toggle light 1: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/lights/1/toggle
* Toggle group 2: http://192.168.0.101:3000/api/yourPhilipsHueBridgeUsername/groups/2/toggle


## Supported Keywords
The API is transparent to all Philips Hue keywords. It expects all name=value pairs to be separated by a comma. If any comma is required inside a value, eg: for the xy command which expects a value array, then you must url encode the comma to %2c.

The full JSON response for a light looks like this:
Expand All @@ -178,12 +192,12 @@ The full JSON response for a group looks like this:
{"name":"Lounge","lights":["9","1","2"],"sensors":[],"type":"Room","state":{"all_on":false,"any_on":false},"recycle":false,"class":"Lounge","action":{"on":false,"bri":0,"hue":7800,"sat":138,"effect":"none","xy":[0.5302,0.392],"ct":153,"alert":"select","colormode":"xy"}}
```
The most common action keywords for state or group are:
on, bri, hue, sat, effect, xy, ct, alert, colormode, mode (lights only)
on, bri, hue, sat, effect, xy, ct, alert, colormode, mode (lights only).
More keywords exist, see the [API documentation](#api-documentation).

## on (get and set)
Turn a light on or off. On=true, Off=false.
Valid for light or group.
Valid for light or group. A group also supports all_on and any_on.

## bri (get and set)
The brightness value to set the light to. Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum).
Expand Down Expand Up @@ -247,7 +261,7 @@ See the [API documentation](#api-documentation).

## API Documentation
For full details of the control capabilities, please see the [official Philips Hue API reference](https://developers.meethue.com/develop/hue-api/).
An [alternative unoffical reference](http://www.burgestrand.se/hue-api/), somewhat outdated also exists.
An [alternative unoffical reference](http://www.burgestrand.se/hue-api/), somewhat outdated, also exists.


# Finding your Light or Group ids
Expand All @@ -265,6 +279,6 @@ Go backwards in the text until you find the keyword **state**, this is at the st
... ,"31":{"state":{"on":true,"bri":100,"hue":65396 ...
```

Use the same method for groups to find the group id of the room you wish to control. Note that group id 0 is a special group containing all lights in the system, and is not returned by the ‘get all groups’ command. Group 0 is not visible, and cannot be created, modified or deleted using the API.
Use the same method for groups to find the group id of the room you wish to control. Note that group id 0 is a special group containing all lights in the system, and is not returned by the ‘get all groups’ command. Group 0 is not visible, and cannot be created, modified or deleted using the API, but group 0 can be controlled by the API.


85 changes: 69 additions & 16 deletions hueget.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,12 @@ app.use('/api/' + options.username, (req, res) => {
break;
}
//console.log('expectedCommand', expectedCommand );
if (!command.startsWith(expectedCommand)) { throw errPrefix + 'unknown command "' + command + '", expecting "' + expectedCommand + '": "' + req.url + '"'; }
if (!command.includes(expectedCommand + '?')) { throw errPrefix + 'query character "?" missing in "' + command + '", expecting "' + expectedCommand + '?<query>": "' + req.url + '"'; }
if (command.endsWith(expectedCommand + '?')) { throw errPrefix + 'query missing in "' + command + '", expecting "' + expectedCommand + '?<query>": "' + req.url + '"'; }
// toggle is a special case, raise error for anything else that does not fit the syntax
if (command != 'toggle') {
if (!command.startsWith(expectedCommand)) { throw errPrefix + 'unknown command "' + command + '", expecting "' + expectedCommand + '": "' + req.url + '"'; }
if (!command.includes(expectedCommand + '?')) { throw errPrefix + 'query character "?" missing in "' + command + '", expecting "' + expectedCommand + '?<query>": "' + req.url + '"'; }
if (command.endsWith(expectedCommand + '?')) { throw errPrefix + 'query missing in "' + command + '", expecting "' + expectedCommand + '?<query>": "' + req.url + '"'; }
}
}


Expand Down Expand Up @@ -137,41 +140,91 @@ app.use('/api/' + options.username, (req, res) => {
result = result + ',"' + pair[0] + '":' + pairValue;
});
result = result.replace('{,','{') + '}'; // clean up, add brackets
//console.log('result', result );
console.log('result', result );
dataObj = JSON.parse(result);
}




// if a dataObj exists, send PUT; otherwise, send a GET
// GET http://192.168.0.101/api/<username>/lights/31
// PUT http://192.168.0.101/api/<username>/lights/31/state --data "{""on"":true}"
var url = 'http://' + options.ip + '/api/' + options.username + '/' + resource;
if (id) { url = url + '/' + id; } // add id if supplied
if (dataObj){
console.log('sending PUT: %s %s', url + '/' + expectedCommand, dataObj || '');
axios.put(url + '/' + expectedCommand, dataObj)


// special handling for toggle command, this toggles a light or group state
if (command == 'toggle') {
console.log('toggling current state')
// Get actual state
console.log('sending GET: %s', url);
axios.get(url)
.then(response => {
console.log('PUT response:', response.status, response.statusText, JSON.stringify(response.data) );
res.json(response.data);
})
// for lights /lights/<id> state = on true/false
// for groups, /groups/<id> state = all_on true/false
switch(resource) {
case 'lights':
console.log('GET response:', response.status, response.statusText, "state:on="+response.data["state"]["on"] );
state = !response.data["state"]["on"] // get the current on state , as a boolean, and invert it
expectedCommand = 'state'
break;
case 'groups':
console.log('GET response:', response.status, response.statusText, "state:all_on="+response.data["state"]["all_on"] );
state = !response.data["state"]["all_on"] // get the current all_on state, as a boolean, and invert it
expectedCommand = 'action'
break;
}
// toggle light or group state
// lights: http://localhost:3000/api/<username>/lights/31/state?on=true
// groups: http://localhost:3000/api/<username>/groups/0/action?on=true
console.log('sending PUT: %s%s', url + '/' + "state?on=", state.toString() || '');
axios.put(url + '/' + expectedCommand,'{"on":' + state.toString() + '}')
.then(response => {
console.log('PUT response:', response.status, response.statusText, JSON.stringify(response.data) );
res.json(response.data);
})
.catch(error => {
const errText = error.syscall + ' ' + error.code + ' ' + error.address + ':' + error.port;
console.log('PUT error:', errText);
res.json({ error: errText });
});
})
.catch(error => {
const errText = error.syscall + ' ' + error.code + ' ' + error.address + ':' + error.port;
console.log('PUT error:', errText);
console.log('GET error:', errText);
res.json({ error: errText });
});
} else {
console.log('sending GET: %s', url);
axios.get(url)


// normal handling for non-toggle commands
} else {
if (dataObj){
console.log('sending PUT: %s %s', url + '/' + expectedCommand, dataObj || '');
axios.put(url + '/' + expectedCommand, dataObj)
.then(response => {
console.log('GET response:', response.status, response.statusText, JSON.stringify(response.data) );
console.log('PUT response:', response.status, response.statusText, JSON.stringify(response.data) );
res.json(response.data);
})
.catch(error => {
const errText = error.syscall + ' ' + error.code + ' ' + error.address + ':' + error.port;
console.log('GET error:', errText);
console.log('PUT error:', errText);
res.json({ error: errText });
});
} else {
console.log('sending GET: %s', url);
axios.get(url)
.then(response => {
console.log('GET response:', response.status, response.statusText, JSON.stringify(response.data) );
res.json(response.data);
})
.catch(error => {
const errText = error.syscall + ' ' + error.code + ' ' + error.address + ':' + error.port;
console.log('GET error:', errText);
res.json({ error: errText });
});
}
}
return;


Expand Down
34 changes: 17 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hueget",
"version": "0.6.6",
"version": "0.7.0",
"description": "A simple API that converts the Philips Hue API PUT to GET requests, allowing control of Philips Hue lights via http GET requests",
"main": "hueget.js",
"scripts": {
Expand All @@ -25,7 +25,7 @@
"author": "Jochen Siegenthaler (https://github.com/jsiegenthaler)",
"license": "ISC",
"dependencies": {
"axios": "^1.6.2",
"axios": "^1.6.5",
"express": "^4.18.2",
"stdio": "^2.1.1"
},
Expand Down

0 comments on commit 28891d3

Please sign in to comment.