Skip to content

Commit

Permalink
Finished implementation of generic UDP handling. This will support the
Browse files Browse the repository at this point in the history
LimitlessLED Bridge. Updated Readme for UDP and more on configuration
setup. Fixed issue with HueMulator handliong due to devices without
mapTypes.

Fixes #6.
  • Loading branch information
bwssytems committed Dec 7, 2015
1 parent cf77233 commit 3c54ccd
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 30 deletions.
63 changes: 46 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ha-bridge
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge. Alternatively the Bridge supports custom calls as well. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol. You can provide multiple harmony hubs for control.
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol. This bridge can control most devices that have a distinct API. In the cases of systems that require authorization and/or have API's that cannot be handled in the current method, a module may need to be built. The Harmony Hub is such a module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge. Alternatively the Bridge supports custom calls as well.
## Build
To customize and build it yourself, build a new jar with maven:
```
Expand Down Expand Up @@ -32,8 +32,16 @@ The password for the user name of the MyHarmony.com account for the Harmony Hub.
Upnp has been very closed on this platform to try and respond as a hue and there is now a setting to control if it is more open or strict, Add -Dupnp.strict=`<true|false>` to your command line to have the emulator respond to what it thinks is an echo to a hue or any other device. The default is upnp.strict=true.
### -Dtrace.upnp=`<true|false>`
Turn on tracing for upnp discovery messages. The default is false.
## Web Config
Configure by going to the url for the host you are running on or localhost with port you have assigned: and use the helpers for the Vera or Harmony Hub to create devices that the Echo will find.
## HA Bridge Device Configuration
You must configure devices before you will have any thing for the Echo to receive. The easy way to get devices configures is with the web interface by going to the url for the host you are running on or localhost with port you have assigned: and use the helpers for the Vera or Harmony Hub to create devices that the Echo will find.

Another way to add a device is through the Manual Add Tab. This allows you to manually enter the name, the on and off URLs and select if there are custom handling with the type of call that can be made. This allows for control of anything that has a distinct request that can be executed so you are not limited to the Vera or the Harmony.

The format of these can be the default HTTP request which excecutes the URLs formtted as http:https://<your stuff here> as a GET. Other options to this are to select the HTTP Verb and add the data type and add a body that is passed with the request. Another option that is detected by the bridge is to use the udp:https://<ip_address>:<port>/<your stuff here to send a UDP request. If your data for the UDP request is formatted as "0x00F009B9" lexical hex format, the bridge will convert the UDP data into a binary stream to send. Sed the examples inthe Configuration REST API Usage Section to get an idea.

Also, you may want to use the REST API's listed below to configure your devices.

Web Configuration Access
```
http:https://<ip address>:<port>
```
Expand All @@ -50,7 +58,7 @@ Turn on / off your connected home device | "Turn on/off [connected home device n
Set the brightness of compatible lights | "Set brightness to [##]%." OR "Dim the lights to [##]%."

To view or remove devices that Alexa knows about, you can use the mobile app `Menu / Settings / Connected Home`.
## Configuration REST API usage
## Configuration REST API Usage
This section will describe the REST api available for configuration. The REST body examples are all formatted for easy reading, the actual body usage should be like this:
```
{"var1":"value1","var2":"value2","var3:"value3"}
Expand All @@ -62,7 +70,7 @@ Add a new device to the HA Bridge configuration. There is a basic examples and t
```
POST http:https://host:8080/api/devices
```
#### Body arguments
#### Body Arguments
Name | Type | Description | Required
-----|-------|--------------|------------
name | string | A name for the device. This is also the utterance value that the Echo will use. | Required
Expand All @@ -73,7 +81,7 @@ httpVerb | string | This is used for "custom" calls that the user would like to
contentType | string | This is an http type string such as "application/text" or "application/xml" or "application/json". | Optional
contentBody | string | This is the content body that you would like to send when executing an "on" request. | Optional
contentBodyOff | string | This is the content body that you would like to send when executing an "off" request. | Optional
#### Basic example
#### Basic Example
```
{
"name" : "bedroom light",
Expand All @@ -82,8 +90,8 @@ contentBodyOff | string | This is the content body that you would like to send w
"offUrl" : "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41"
}
```
#### Dimming and value passing control example
Dimming is also supported by using the expressions ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 or custom values using ${intensity.math(<your expression using "X" as the value to operate on>)} i.e. "${intensity.math(X/4)}".
#### Dimming Control Example
Dimming is also supported by using the expressions ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 for straight pass trhough of the value.
e.g.
```
{
Expand All @@ -95,13 +103,26 @@ e.g.
```
See the echo's documentation for the dimming phrase.

#### POST/PUT support example
#### Value Passing Control Example
You can control items that require special calculated values using ${intensity.math(<your expression using "X" as the value to operate on>)} i.e. "${intensity.math(X/4)}".
e.g.
```
{
"name": "Thermostat,
"deviceType": "custom",
"offUrl": "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=10",
"onUrl": "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=10&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.math(X/4)}"
}
```
See the echo's documentation for the dimming phrase.

#### POST/PUT Support Example
```
This will allow control of any other application that may need more then GET. You can also use the dimming and value control commands within the URLs as well.
e.g:
{
"name": "test device",
"deviceType": "switch",
"deviceType": "custom",
"offUrl": "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31",
"onUrl": "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=31&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.percent}",
"httpVerb":"POST",
Expand All @@ -115,12 +136,20 @@ Anything that takes an action as a result of an HTTP request will probably work
```
{
"name": "night mode",
"deviceType": "switch",
"deviceType": ""custom",
"offUrl": "http:https://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=1",
"onUrl": "http:https://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=3"
}
```
#### Response
Here is a UDP example that can send binary data.
```
{
"name": "UDPPacket",
"deviceType": "custom",
"offUrl": "udp:https://192.168.1.1:8899/0x460055",
"onUrl": "udp:https://192.168.1.1:8899/0x450055"
}
```#### Response
Name | Type | Description
-----|-------|-------------
id | number | This is the ID assigned to the device and used for lookup.
Expand All @@ -141,12 +170,12 @@ contentBodyOff | string | This is the content body that you would like to send w
"offUrl" : "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41"
}
```
### Update a device
### Update a Device
Update an existing device using it's ID that was given when the device was created and the update could contain any of the fields that are used and shown in the previous examples when adding a device.
```
POST http:https://host:8080/api/devices/<id>
```
#### Body arguments
#### Body Arguments
Name | Type | Description | Required
-----|-------|--------------|------------
id | number | This is the ID assigned to the device and used for lookup.
Expand Down Expand Up @@ -178,7 +207,7 @@ contentBodyOff | string | This is the content body that you would like to send w
"offUrl" : "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41"
}
```
### Get all devices
### Get All Devices
Get all devices saved in the HA bridge configuration.
```
GET http:https://host:8080/api/devices
Expand All @@ -201,7 +230,7 @@ Individual entries are the same as a single device but in json list format.
"offUrl" : "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41"
}]
```
### Get a specific device
### Get a Specific Device
Get a device by ID assigned from creation and saved in the HA bridge configuration.
```
GET http:https://host:8080/api/devices/<id>
Expand All @@ -217,7 +246,7 @@ The response is the same layout as defined in the add device response.
"offUrl" : "http:https://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41"
}
```
### Delete a specific device
### Delete a Specific Device
Delete a device by ID assigned from creation and saved in the HA bridge configuration.
```
DELETE http:https://host:8080/api/devices/<id>
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>1.2.1a</version>
<version>1.2.2</version>
<packaging>jar</packaging>

<name>HA Bridge</name>
Expand Down
37 changes: 25 additions & 12 deletions src/main/java/com/bwssystems/HABridge/hue/HueMulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,15 @@

import java.io.IOException;
import java.math.BigDecimal;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.DatatypeConverter;

/**
* Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server
*/
Expand All @@ -62,6 +66,7 @@ public class HueMulator {
private HttpClient httpClient;
private ObjectMapper mapper;
private BridgeSettings bridgeSettings;
private byte[] sendData;


public HueMulator(BridgeSettings theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome){
Expand Down Expand Up @@ -266,14 +271,14 @@ public void setupServer() {
try {
state = mapper.readValue(request.body(), DeviceState.class);
} catch (IOException e) {
log.error("Object mapper barfed on input of body.", e);
log.warn("Object mapper barfed on input of body.", e);
responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId + ",\"description\": \"Object mapper barfed on input of body.\"}}]";
return responseString;
}

DeviceDescriptor device = repository.findOne(lightId);
if (device == null) {
log.error("Could not find device: " + lightId + " for hue state change request: " + userId + " from " + request.ip() + " body: " + request.body());
log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " + request.ip() + " body: " + request.body());
responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + ",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]";
return responseString;
}
Expand Down Expand Up @@ -301,27 +306,27 @@ public void setupServer() {
else
responseString = responseString + "]";

if(device.getDeviceType().toLowerCase().contains("activity") || device.getMapType().equalsIgnoreCase("harmonyActivity"))
if(device.getDeviceType().toLowerCase().contains("activity") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyActivity")))
{
log.debug("executing activity to Harmony: " + url);
RunActivity anActivity = new Gson().fromJson(url, RunActivity.class);
HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice());
if(myHarmony == null)
{
log.error("Should not get here, no harmony hub available");
log.warn("Should not get here, no harmony hub available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else
myHarmony.startActivity(anActivity);
}
else if(device.getDeviceType().toLowerCase().contains("button") || device.getMapType().equalsIgnoreCase("harmonyButton"))
else if(device.getDeviceType().toLowerCase().contains("button") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyButton")))
{
log.debug("executing button press to Harmony: " + url);
ButtonPress aDeviceButton = new Gson().fromJson(url, ButtonPress.class);
HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice());
if(myHarmony == null)
{
log.error("Should not get here, no harmony hub available");
log.warn("Should not get here, no harmony hub available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else
Expand All @@ -330,14 +335,22 @@ else if(device.getDeviceType().toLowerCase().contains("button") || device.getMap
else if(url.startsWith("udp:https://"))
{
try {
DatagramSocket responseSocket = new DatagramSocket(10000);
String intermediate = url.substring(6);
String ipAddr = intermediate.substring(0, intermediate.indexOf(':'));
String port = intermediate.substring(intermediate.indexOf(':'), intermediate.indexOf('/'));
String port = intermediate.substring(intermediate.indexOf(':') + 1, intermediate.indexOf('/'));
String theBody = intermediate.substring(intermediate.indexOf('/')+1);
DatagramSocket responseSocket = new DatagramSocket(Integer.parseInt(port));
if(theBody.startsWith("0x")) {
sendData = DatatypeConverter.parseHexBinary(theBody.substring(2));
}
else
sendData = theBody.getBytes();
InetAddress IPAddress = InetAddress.getByName(ipAddr);
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, Integer.parseInt(port));
responseSocket.send(sendPacket);
responseSocket.close();
} catch (IOException e) {
log.error("Could not send UDP Datagram packet for request.", e);
log.warn("Could not send UDP Datagram packet for request.", e);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
}
Expand All @@ -353,7 +366,7 @@ else if(url.startsWith("udp:https://"))
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri());
// make call
if (!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)) {
log.error("Error on calling url to change device state: " + url);
log.warn("Error on calling url to change device state: " + url);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
}
Expand Down Expand Up @@ -393,7 +406,7 @@ protected String replaceIntensityValue(String request, int intensity){
Integer endResult = Math.round(result.floatValue());
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString());
} catch (Exception e) {
log.error("Could not execute Math: " + mathDescriptor, e);
log.warn("Could not execute Math: " + mathDescriptor, e);
} }
return request;
}
Expand Down Expand Up @@ -426,7 +439,7 @@ protected boolean doHttpRequest(String url, String httpVerb, String contentType,
return true;
}
} catch (IOException e) {
log.error("Error calling out to HA gateway", e);
log.warn("Error calling out to HA gateway", e);
}
return false;
}
Expand Down

0 comments on commit 3c54ccd

Please sign in to comment.