Merge branch 'master' into master

This commit is contained in:
diamond5170
2017-09-24 21:45:56 +03:00
committed by GitHub
37 changed files with 601 additions and 423 deletions

View File

@@ -1,5 +1,5 @@
# ha-bridge
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo or other systmes that support Philips Hue. 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.
Emulates Philips Hue API to other home automation gateways such as an Amazon Echo or other systems that support Philips Hue. 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.
Here are some diagrams to put this software in perspective.
@@ -35,11 +35,11 @@ THe Harmony Hub Path looks like this:
**NOTE: This software does not control Philips Hue devices directly. A physical Philips Hue Hub is required for that, by which the ha-bridge can then proxy all of your real Hue bridges behind this bridge.**
**ISSUE: Google Home now seems to not support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local APi, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.**
**ISSUE: Google Home now seems to not support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.**
**FAQ: Please look here for the current FAQs! https://github.com/bwssytems/ha-bridge/wiki/HA-Bridge-FAQs**
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 and so is the Nest module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge, Nest, Somfy Tahoma and the ability to proxy all of your real Hue bridges behind this bridge.
In the cases of systems that require authorization and/or have APIs that cannot be handled in the current method, a module may need to be built. The Harmony Hub is such a module and so is the Nest module. The Bridge has helpers to build devices for the gateway for the Logitech Harmony Hub, Vera, Vera Lite or Vera Edge, Nest, Somfy Tahoma and the ability to proxy all of your real Hue bridges behind this bridge.
Alternatively the Bridge supports custom calls as well using http/https/udp and tcp such as the LimitlessLED/MiLight bulbs using the UDP protocol. Binary data is supported with UDP/TCP.
@@ -61,24 +61,19 @@ ATTENTION: This requires JDK 1.8 to run
ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below.
```
java -jar ha-bridge-4.5.0.jar
java -jar ha-bridge-4.5.6.jar
```
### Automation on Linux systems
To have this configured and running automatically there are a few resources to use. One is using Docker and a docker container has been built for this and can be gotten here: https://github.com/aptalca/docker-ha-bridge
Create the directory and make sure that ha-bridge-4.5.0.jar is in your /home/pi/habridge directory.
Create the directory and make sure that ha-bridge-4.5.6.jar is in your /home/pi/habridge directory.
```
pi@raspberrypi:~ $ mkdir habridge
pi@raspberrypi:~ $ cd habridge
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.5.0/ha-bridge-4.5.0.jar
```
Create the directory and make sure that ha-bridge-4.5.0.jar is in your /home/pi/habridge directory.
```
pi@raspberrypi:~ $ mkdir habridge
pi@raspberrypi:~ $ cd habridge
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.5.0/ha-bridge-4.5.0.jar
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.5.6/ha-bridge-4.5.6.jar
```
#### System Control Setup on a pi (preferred)
For next gen Linux systems (this includes the Raspberry Pi), here is a systemctl unit file that you can install. Here is a link on how to do this: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
@@ -97,7 +92,7 @@ After=network.target
[Service]
Type=simple
WorkingDirectory=/home/pi/habridge
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.5.0.jar
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.5.6.jar
[Install]
WantedBy=multi-user.target
@@ -132,7 +127,7 @@ Then cut and past this, modify any locations that are not correct
```
cd /home/pi/habridge
rm /home/pi/habridge/habridge-log.txt
nohup java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.5.0.jar > /home/pi/habridge/habridge-log.txt 2>&1 &
nohup java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.5.6.jar > /home/pi/habridge/habridge-log.txt 2>&1 &
chmod 777 /home/pi/habridge/habridge-log.txt
```
@@ -150,12 +145,14 @@ pi@raspberrypi:~/habridge $ tail -f habridge-log.txt
```
## Run ha-bridge alongside web server already on port 80
These examples will help you proxy your current webserver requests to the ha-bridge running on a different port, such as 8080.
### Apache Example
Reverse proxy with Apache on Ubuntu linux:
a2enmod proxy
a2enmod proxy_http
a2enmod headers
Enable the required Apache modules:
`a2enmod proxy proxy_http headers`
Added the following lines to my Apache config file “000-default”
@@ -177,7 +174,10 @@ Added the following lines to my Apache config file “000-default”
</VirtualHost>
```
service apache2 restart
Restart apache for the changes to take effect.
`service apache2 restart`
### lighthttpd Example
```
server.modules += ( "mod_proxy" )
@@ -198,6 +198,11 @@ location /api/ {
```
## Available Arguments
Arguments are now deprecated. The ha-bridge will use the old -D arguments and populate the configuration screen, Bridge Control Tab, which can now be saved to a file and will not be needed. There is only one optional argument that overrides and that is the location of the configuration file. The default is the relative path "data/habridge.config".
### -Djava.net.preferIPv4Stack=true
This will guarantee that the ha-bridge will not use an IPV6 address. This cannot be automatically set inside the code.
```
java -jar -Djava.net.preferIPv4Stack=true ha-bridge-W.X.Y.jar
```
### -Dconfig.file=`<filepath>`
The default location for the configuration file to contain the settings for the bridge is the relative path from where the bridge is started in "data/habridge.config". If you would like a different filename or directory, specify -Dconfig.file=`<directory>/<filename>` explicitly. The command line example:
```
@@ -221,7 +226,7 @@ This option is very important to set if you will be using username/passwords to
java -jar -Dsecurity.key=Xfawer354WertSdf321234asd ha-bridge-W.X.Y.jar
```
### -Dexec.garden=`<The path to your scripts and program directory>`
This sets a directory of your choosing to have a walled area for what can be executed by the Exec Command type. This is a good feature to use if you use the capabilities of executing a script or program from the ha-bridge. The default is not set which allows any program or script to be called and anyone with access to the your system could create an exec command call and execute it from the api. This is will prevent any issues if your system gets hacked. To override the default, specify -Dexec.garden=`<The path to your scripts and program directory>` explicitly on the command line. The command line example:
This sets a directory of your choosing to have a walled area for what can be executed by the Exec Command type. This is a good feature to use if you use the capabilities of executing a script or program from the ha-bridge. The default is not set which allows any program or script to be called and anyone with access to the your system could create an exec command call and execute it from the API. This is will prevent any issues if your system gets hacked. To override the default, specify -Dexec.garden=`<The path to your scripts and program directory>` explicitly on the command line. The command line example:
```
java -jar -Dexec.garden=C:\Users\John\bin
```
@@ -243,13 +248,15 @@ This field is used to test the bridge server with the UPNP IP Address and to mak
#### Bridge Control Buttons
These buttons are for managing the bridge. The Save button is enabled when there is a change to the configuration. The Bridge Reinitialize button will recycle the internal running of the bridge in the java process. The Stop button will stop the java process. The Refresh button will refresh the page and settings.
#### The Security Dialog
This is where you can set the different security settings for the ha-bridge. There are two settings, one for enabling Hue like operation to secure the Hue api with the internally generated user for the calls that are done after the link button. The other is to secure the hue api with a username/password that is created as well. The other fields are to add and delete users and to set and change passwords for those users. If there are no users in the system, the system will not require a username/password to operate.
This is where you can set the different security settings for the ha-bridge. There are two settings, one for enabling Hue like operation to secure the Hue API with the internally generated user for the calls that are done after the link button. The other is to secure the hue API with a username/password that is created as well. The other fields are to add and delete users and to set and change passwords for those users. If there are no users in the system, the system will not require a username/password to operate.
#### Configuration Path and File
The default location for the configuration file to contain the settings for the bridge is the relative path from where the bridge is started in "data/habridge.config". If you would like a different filename or directory, specify `<directory>/<filename>` explicitly.
#### Device DB Path and File
The default location for the db to contain the devices as they are added is "data/devices.db". If you would like a different filename or directory, specify `<directory>/<filename> explicitly.
#### UPNP IP Address
The server defaults to the first available address on the host if this is not given. This default may NOT be the correct IP that is your public IP for your host on the network. It is best to set this parameter to not have discovery issues. Replace this value with the server ipv4 address you would like to use as the address that any upnp device will call after discovery.
#### Use UPNP Address Interface
The server tries to bind to all interfaces to respond to UPNP request. Setting this to `true` will make the binding to the interface that has the `UPNP IP Address`. The default is to be all interfaces which is set as false.
#### Web Server IP Address
The server defaults to all interfaces on the machine (0.0.0.0). Replace this value with the server ipv4 address you would like to use as the address that will bind to a specific ip address on an interface if you would like. This is only necessary if you want to isolate how access is handled to the web UI.
#### Web Server Port
@@ -408,7 +415,7 @@ You can control items that require special calculated values using ${intensity.m
For the items that want to have a date time put into the message, utilize ${time.format(yyyy-MM-ddTHH:mm:ssXXX)} where "yyyy-MM-ddTHH:mm:ssXXX" can be any format from the Java SimpleDateFormat documented here: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
Also, device data can be inserted into your payloads by the use of "${device.name}", "${device.id}", "${device.uniqueid}", "${device.targetDevice}", "${device.mapId}", "${device.mapType}" and "${device.deviceType}". These work just like the dimming value replacements.
Also, device data can be inserted into your payloads by the use of "${device.name}", "${device.id}", "${device.uniqueid}", "${device.targetDevice}", "${device.mapId}", "${device.mapType}", "${device.deviceType}", "${device.requesterAddress}", "${device.description}" and "${device.comments}". These work just like the dimming value replacements.
e.g.
```
[{"item":"http://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)}","type":"httpDevice"}]
@@ -421,7 +428,7 @@ e.g.
```
Also, you may want to use the REST API's listed below to configure your devices.
Also, you may want to use the REST APIs listed below to configure your devices.
## Ask Alexa
After this Tell Alexa: "Alexa, discover my devices". If there is an issue you can go to the `Menu / Settings / Connected Home` for the echo on the mobile app or your browser and have Alexa forget all devices and then do the discovery again.
@@ -446,7 +453,7 @@ To view or remove devices that Alexa knows about, you can use the mobile app `Me
## Google Assistant
Google Home is supported as of v3.2.0 and forward, but only if the bridge is running on port 80.
**ISSUE: Google Home now seems to not support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local APi, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.**
**ISSUE: Google Home now seems to not support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.**
Use the Google Home app on a phone to add new "home control" devices by going into `Settings / Home Control / +`
as described [here](https://support.google.com/googlehome/answer/7124115?hl=en&ref_topic=7125624#homecontrol).
@@ -478,7 +485,7 @@ New or removed devices are picked up automatically as soon as they are added/rem
No re-discovery step is necessary.
## 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:
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"}
```
@@ -510,8 +517,8 @@ contentBodyOff | string | This is the content body that you would like to send w
{
"name" : "bedroom light",
"deviceType" : "switch",
"onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}],
"offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}]
"onUrl" : "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41\",\"type\":\"veraDevice\"}]",
"offUrl" : "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41\",\"type\":\"veraDevice\"}]"
}
```
#### Dimming Control Example
@@ -521,8 +528,8 @@ e.g.
{
"name": "entry light",
"deviceType": "switch",
"offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31","type":"veraDevice"}],
"onUrl": [{"item":"http://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}","type":"veraDevice"}]
"offUrl": "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31\",\"type\":\"veraDevice\"}]",
"onUrl": "[{\"item\":\"http://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}\",\"type\":\"veraDevice\"}]"
}
```
See the echo's documentation for the dimming phrase.
@@ -534,8 +541,8 @@ e.g.
{
"name": "Thermostat,
"deviceType": "custom",
"offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=10","type":"veraDevice"}],
"onUrl": [{"item":"http://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)}","type":"veraDevice"}]
"offUrl": "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=10\",\"type\":\"veraDevice\"}]",
"onUrl": "[{\"item\":\"http://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)}\",\"type\":\"veraDevice\"}]"
}
```
See the echo's documentation for the dimming phrase.
@@ -547,8 +554,8 @@ e.g:
{
"name": "test device",
"deviceType": "custom",
"offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31","httpVerb":"POST","contentType" : "application/json","httpBody" : "{\"fooBar\":\"baz_off\"}],
"onUrl": [{"item":"http://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}","type":"httpDevice","httpVerb":"POST","contentType" : "application/json","httpBody" : "{\"fooBar\":\"baz_on\"}]
"offUrl": "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=31\",\"httpVerb\":\"POST\",\"contentType\" : \"application/json\",\"httpBody\" : \"{\"fooBar\":\"baz_off\"}]",
"onUrl": "[{\"item\":\"http://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}\",\"type\":\"httpDevice\",\"httpVerb\":\"POST\",\"contentType\" : \"application/json\",\"httpBody\" : \"{\"fooBar\":\"baz_on\"}]"
}
```
#### Custom Usage URLs Example
@@ -557,8 +564,8 @@ Anything that takes an action as a result of an HTTP request will probably work
{
"name": "night mode",
"deviceType": ""custom",
"offUrl": [{"item":"http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=1","type":"httpDevice"}],
"onUrl": [{"item":"http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=3","type":"httpDevice"}]
"offUrl": "[{\"item\":\"http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=1\",\"type\":\"httpDevice\"}]",
"onUrl": "[{\"item\":\"http://192.168.1.201:3480/data_request?id=lu_action&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=SetHouseMode&Mode=3\",\"type\":\"httpDevice\"}]"
}
```
Here is a UDP example that can send binary data.
@@ -566,8 +573,8 @@ Here is a UDP example that can send binary data.
{
"name": "UDPPacket",
"deviceType": "custom",
"offUrl": [{"item":"udp://192.168.1.1:8899/0x460055","type":"udpDevice"}],
"onUrl": [{"item":"udp://192.168.1.1:8899/0x450055","type":"udpDevice"}]
"offUrl": "[{\"item\":\"udp://192.168.1.1:8899/0x460055\",\"type\":\"udpDevice\"}]",
"onUrl": "[{\"item\":\"udp://192.168.1.1:8899/0x450055\",\"type\":\"udpDevice\"}]"
}
```
#### Response
@@ -626,8 +633,8 @@ contentBodyOff | string | This is the content body that you would like to send w
"id" : "6789",
"name" : "table light",
"deviceType" : "switch",
"onUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41","type":"veraDevice"}],
"offUrl" : [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41","type":"veraDevice"}]
"onUrl" : "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=41\",\"type\":\"veraDevice\"}]",
"offUrl" : "[{\"item\":\"http://192.168.1.201:3480/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=0&DeviceNum=41\",\"type\":\"veraDevice\"}]"
}
```
#### Response
@@ -880,7 +887,7 @@ The example below is representative of some HUE device responses.
Name | Type | Description
-----|-------|-------------
device | HUE lights object | The HUE light detail descriptor, see api for lights response below.
device | HUE lights object | The HUE light detail descriptor, see API for lights response below.
huedeviceid | string | The id of the actual passthru HUE light id.
hueaddress | string | The address of the target HUE bridge.
huename | string | A name given to the target HUE bridge.
@@ -889,7 +896,7 @@ huename | string | A name given to the target HUE bridge.
[{"device":{"state":{"on":true,"bri":254,"hue":4444,"sat":254,"effect":"none","ct":0,"alert":"none","colormode":"hs","reachable":true,"xy":[0.0,0.0]},"type":"Extended color light","name":"Hue Lamp 1","modelid":"LCT001","uniqueid":"00:17:88:01:00:d4:12:08-0a","swversion":"65003148"},"huedeviceid":"1","hueaddress":"192.168.0.118:8000","huename":"HueEmul"},{"device":{"state":{"on":true,"bri":254,"hue":23536,"sat":144,"effect":"none","ct":201,"alert":"none","colormode":"hs","reachable":true,"xy":[0.346,0.3568]},"type":"Extended color light","name":"Hue Lamp 2","modelid":"LCT001","uniqueid":"00:17:88:01:00:d4:12:08-0b","swversion":"65003148"},"huedeviceid":"2","hueaddress":"192.168.0.118:8000","huename":"HueEmul"},{"device":{"state":{"on":true,"bri":254,"hue":65136,"sat":254,"effect":"none","ct":201,"alert":"none","colormode":"hs","reachable":true,"xy":[0.346,0.3568]},"type":"Extended color light","name":"Hue Lamp 3","modelid":"LCT001","uniqueid":"00:17:88:01:00:d4:12:08-0c","swversion":"65003148"},"huedeviceid":"3","hueaddress":"192.168.0.118:8000","huename":"HueEmul"}]
```
## HUE REST API usage
This section will describe the REST api available for controlling the bridge based off of the HUE API. This Bridge does not support the full HUE API, only the calls that are supported with the HA Bridge are shown. The REST body examples are all formatted for easy reading, the actual body usage should be like this:
This section will describe the REST API available for controlling the bridge based off of the HUE API. This Bridge does not support the full HUE API, only the calls that are supported with the HA Bridge are shown. The REST body examples are all formatted for easy reading, the actual body usage should be like this:
```
{"var1":"value1","var2":"value2","var3:"value3"}
```
@@ -998,7 +1005,7 @@ A response to a successful PUT request contains confirmation of the arguments pa
]
```
### Update bridge internal light state
Allows the user to set the internal state of the light on and off, modify the brightness. This is not a HUE API call and is special to the bridge as it keeps track of the state changes to the light from the api. It is intended to allow you to sync the bridge state with your HA system state.
Allows the user to set the internal state of the light on and off, modify the brightness. This is not a HUE API call and is special to the bridge as it keeps track of the state changes to the light from the API. It is intended to allow you to sync the bridge state with your HA system state.
```
PUT http://host:port/api/<username>/lights/<id>/bridgeupdatestate
```

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>4.5.0</version>
<version>4.5.6</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
@@ -33,7 +33,7 @@
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>harmony-java-client</artifactId>
<version>1.1.1</version>
<version>1.1.5</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>

View File

@@ -201,43 +201,44 @@ public class BridgeSecurity {
public void setSettingsChanged(boolean settingsChanged) {
this.settingsChanged = settingsChanged;
}
public Map<String, WhitelistEntry> getWhitelist() {
return securityDescriptor.getWhitelist();
}
public HueError[] validateWhitelistUser(String aUser, String userDescription, boolean strict) {
String validUser = null;
boolean found = false;
if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null")
&& !aUser.equalsIgnoreCase("")) {
&& !aUser.equalsIgnoreCase("") && !aUser.equals(DEPRACATED_INTERNAL_USER)) {
if (securityDescriptor.getWhitelist() != null) {
Set<String> theUserIds = securityDescriptor.getWhitelist().keySet();
Iterator<String> userIterator = theUserIds.iterator();
while (userIterator.hasNext()) {
validUser = userIterator.next();
if (validUser.equals(aUser))
if (validUser.equals(aUser)) {
found = true;
log.debug("validateWhitelistUser: found a user <" + aUser + ">");
}
}
}
}
if(!found && !strict) {
newWhitelistUser(aUser, userDescription);
found = true;
if(!found && !strict) {
log.debug("validateWhitelistUser: a user was not found and it is not strict rules <" + aUser + "> being created");
newWhitelistUser(aUser, userDescription);
found = true;
}
}
if (!found) {
return HueErrorResponse.createResponse("1", "/api/" + aUser, "unauthorized user", null, null, null).getTheErrors();
log.debug("validateWhitelistUser: a user was not found and strict rules is set to: " + strict + "for user <" + aUser + ">");
return HueErrorResponse.createResponse("1", "/api/" + aUser == null ? "" : aUser, "unauthorized user", null, null, null).getTheErrors();
}
Object anUser = securityDescriptor.getWhitelist().remove(DEPRACATED_INTERNAL_USER);
if(anUser != null)
setSettingsChanged(true);
return null;
}
public void newWhitelistUser(String aUser, String userDescription) {
if(aUser.equals(DEPRACATED_INTERNAL_USER))
return;
private void newWhitelistUser(String aUser, String userDescription) {
if (securityDescriptor.getWhitelist() == null) {
securityDescriptor.setWhitelist(new HashMap<>());
}
@@ -278,9 +279,8 @@ public class BridgeSecurity {
Iterator<Entry<String, WhitelistEntry>> it = securityDescriptor.getWhitelist().entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, WhitelistEntry> pair = it.next();
it.remove(); // avoids a ConcurrentModificationException
if(pair.getValue().getName().equals(TEST_USER_TYPE)) {
securityDescriptor.getWhitelist().remove(pair.getKey());
it.remove(); // avoids a ConcurrentModificationException
setSettingsChanged(true);
}
}

View File

@@ -12,6 +12,9 @@ public class BridgeSettingsDescriptor {
@SerializedName("upnpconfigaddress")
@Expose
private String upnpconfigaddress;
@SerializedName("useupnpiface")
@Expose
private boolean useupnpiface;
@SerializedName("serverport")
@Expose
private Integer serverport;
@@ -108,6 +111,7 @@ public class BridgeSettingsDescriptor {
public BridgeSettingsDescriptor() {
super();
this.upnpstrict = true;
this.useupnpiface = false;
this.traceupnp = false;
this.nestconfigured = false;
this.veraconfigured = false;
@@ -118,8 +122,11 @@ public class BridgeSettingsDescriptor {
this.halconfigured = false;
this.mqttconfigured = false;
this.hassconfigured = false;
this.domoticzconfigured = false;
this.somfyconfigured = false;
this.lifxconfigured = false;
this.farenheit = true;
this.whitelist = null;
this.securityData = null;
this.settingsChanged = false;
this.myechourl = "echo.amazon.com/#cards";
this.webaddress = "0.0.0.0";
@@ -131,6 +138,12 @@ public class BridgeSettingsDescriptor {
public void setUpnpConfigAddress(String upnpConfigAddress) {
this.upnpconfigaddress = upnpConfigAddress;
}
public boolean isUseupnpiface() {
return useupnpiface;
}
public void setUseupnpiface(boolean useupnpiface) {
this.useupnpiface = useupnpiface;
}
public Integer getServerPort() {
return serverport;
}
@@ -410,8 +423,10 @@ public class BridgeSettingsDescriptor {
List<NamedIP> devicesList = this.getHaladdress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
if(this.getHaltoken() == null || this.getHaltoken().equals(""))
return false;
if(devicesList.get(0).getPassword() == null || devicesList.get(0).getPassword().trim().isEmpty()) {
if(this.getHaltoken() == null || this.getHaltoken().equals(""))
return false;
}
return true;
}
public Boolean isValidMQTT() {
@@ -449,4 +464,15 @@ public class BridgeSettingsDescriptor {
public Boolean isValidLifx() {
return this.isLifxconfigured();
}
public void updateHue(NamedIP aHue) {
int indexHue = -1;
for( int i = 0; i < hueaddress.getDevices().size(); i++) {
if(hueaddress.getDevices().get(i).getName().equals(aHue.getName()))
indexHue = i;
}
if(indexHue >= 0) {
hueaddress.getDevices().set(indexHue, aHue);
this.setSettingsChanged(true);
}
}
}

View File

@@ -49,6 +49,7 @@ public class HABridge {
staticFileLocation("/public");
while(!bridgeSettings.getBridgeControl().isStop()) {
bridgeSettings.buildSettings();
bridgeSettings.getBridgeSecurity().removeTestUsers();
log.info("HA Bridge initializing....");
// sparkjava config directive to set ip address for the web server to listen on
ipAddress(bridgeSettings.getBridgeSettingsDescriptor().getWebaddress());

View File

@@ -18,9 +18,9 @@ public class HueApiResponse {
private Map<String, JsonObject> rules;
private HueConfig config;
public HueApiResponse(String name, String ipaddress, Map<String, WhitelistEntry> awhitelist, String emulateHubVersion) {
public HueApiResponse(String name, String ipaddress, Map<String, WhitelistEntry> awhitelist, String emulateHubVersion, boolean isLinkButtonPressed) {
super();
this.setConfig(HueConfig.createConfig(name, ipaddress, awhitelist, emulateHubVersion));
this.setConfig(HueConfig.createConfig(name, ipaddress, awhitelist, emulateHubVersion, isLinkButtonPressed));
this.setRules(new HashMap<>());
this.setSensors(new HashMap<>());
this.setSchedules(new HashMap<>());

View File

@@ -34,7 +34,7 @@ public class HueConfig
private String replacesbridgeid;
private Map<String, WhitelistEntry> whitelist;
public static HueConfig createConfig(String name, String ipaddress, Map<String, WhitelistEntry> awhitelist, String emulateHubVersion) {
public static HueConfig createConfig(String name, String ipaddress, Map<String, WhitelistEntry> awhitelist, String emulateHubVersion, boolean isLinkButtonPressed) {
HueConfig aConfig = new HueConfig();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
SimpleDateFormat dateFormatGmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
@@ -44,7 +44,7 @@ public class HueConfig
aConfig.setPortalservices(false);
aConfig.setGateway(ipaddress);
aConfig.setSwversion(emulateHubVersion);
aConfig.setLinkbutton(true);
aConfig.setLinkbutton(isLinkButtonPressed);
aConfig.setIpaddress(ipaddress);
aConfig.setProxyport(0);
aConfig.setSwupdate(Swupdate.createSwupdate());

View File

@@ -1,8 +1,8 @@
package com.bwssystems.HABridge.api.hue;
public class HueConstants {
public final static String HUB_VERSION = "01036659";
public final static String API_VERSION = "1.15.0";
public final static String HUB_VERSION = "1705121051";
public final static String API_VERSION = "1.19.0";
public final static String MODEL_ID = "BSB002";
public final static String UUID_PREFIX = "2f402f80-da50-11e1-9b23-";
}

View File

@@ -74,7 +74,8 @@ public class DeviceDescriptor{
@SerializedName("comments")
@Expose
private String comments;
@SerializedName("deviceState")
@Expose
private DeviceState deviceState;
public String getName() {

View File

@@ -61,6 +61,7 @@ public class DeviceRepository extends BackupHandler {
{
DeviceDescriptor list[] = gson.fromJson(jsonContent, DeviceDescriptor[].class);
for(int i = 0; i < list.length; i++) {
list[i].setDeviceState(null);
put(list[i].getId(), list[i]);
if(Integer.decode(list[i].getId()) > nextId) {
nextId = Integer.decode(list[i].getId());

View File

@@ -2,6 +2,7 @@ package com.bwssystems.HABridge.hue;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.Conversion;
@@ -83,7 +84,7 @@ public class BrightnessDecode {
replaceTarget = INTENSITY_PERCENT_HEX;
notDone = true;
} else if (request.contains(INTENSITY_DECIMAL_PERCENT)) {
replaceValue = String.format("%1.2f", decimalBrightness);
replaceValue = String.format(Locale.ROOT, "%1.2f", decimalBrightness);
replaceTarget = INTENSITY_DECIMAL_PERCENT;
notDone = true;
} else if (request.contains(INTENSITY_MATH_CLOSE)) {

View File

@@ -14,6 +14,9 @@ public class DeviceDataDecode {
private static final String DEVICE_MAPTYPE = "${device.mapType}";
private static final String DEVICE_DEVICETYPE = "${device.deviceType}";
private static final String DEVICE_TARGETDEVICE = "${device.targetDevice}";
private static final String DEVICE_REQUESTERADDRESS = "${device.requesterAddress}";
private static final String DEVICE_DESCRIPTION = "${device.description}";
private static final String DEVICE_COMMENTS = "${device.comments}";
public static String replaceDeviceData(String request, DeviceDescriptor device) {
if (request == null) {
@@ -58,6 +61,21 @@ public class DeviceDataDecode {
notDone = true;
}
if (request.contains(DEVICE_REQUESTERADDRESS)) {
request = request.replace(DEVICE_REQUESTERADDRESS, device.getRequesterAddress());
notDone = true;
}
if (request.contains(DEVICE_DESCRIPTION)) {
request = request.replace(DEVICE_DESCRIPTION, device.getDescription());
notDone = true;
}
if (request.contains(DEVICE_COMMENTS)) {
request = request.replace(DEVICE_COMMENTS, device.getComments());
notDone = true;
}
log.debug("Request <<" + request + ">>, not done: " + notDone);
}
return request;

View File

@@ -60,6 +60,7 @@ public class HueMulator {
validMapTypes = new DeviceMapTypes();
bridgeSettingMaster = bridgeMaster;
bridgeSettings = bridgeSettingMaster.getBridgeSettingsDescriptor();
homeManager= aHomeManager;
myHueHome = (HueHome) homeManager.findHome(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]);
aGsonHandler = new GsonBuilder().create();
@@ -69,6 +70,7 @@ public class HueMulator {
public void setupServer() {
log.info("Hue emulator service started....");
before(HUE_CONTEXT + "/*", (request, response) -> {
log.debug("HueMulator " + request.requestMethod() + " called on api/* with request <<<" + request.pathInfo() + ">>>, and body <<<" + request.body() + ">>>");
if(bridgeSettingMaster.getBridgeSecurity().isSecure()) {
String pathInfo = request.pathInfo();
if(pathInfo != null && pathInfo.contains(HUE_CONTEXT + "/devices")) {
@@ -118,6 +120,17 @@ public class HueMulator {
log.debug("group add requested from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"success\":{\"id\":\"1\"}}]";
});
// http://ip_address:port/api/:userid/groups/<groupid>/action
// Dummy handler
// Error forces Logitech Pop to fall back to individual light control
// instead of scene-based control.
put(HUE_CONTEXT + "/:userid/groups/:groupid/action", "application/json", (request, response) -> {
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
response.status(HttpStatus.SC_OK);
log.debug("put action to groups API from " + request.ip() + " user " + request.params(":userid") + " with body " + request.body());
return "[{\"error\":{\"address\": \"/groups/0/action/scene\", \"type\":7, \"description\": \"invalid value, dummy for parameter, scene\"}}]";
});
// http://ip_address:port/api/{userId}/scenes returns json objects of
// all scenes configured
get(HUE_CONTEXT + "/:userid/scenes", "application/json", (request, response) -> {
@@ -742,7 +755,9 @@ public class HueMulator {
log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser + (followingSlash ? " /api/ called" : ""));
return "[{\"success\":{\"username\":\"" + newUser + "\"}}]";
}
return aGsonHandler.toJson(HueErrorResponse.createResponse("1", "/api/", "unauthorized user", null, null, null).getTheErrors());
else
log.debug("user add toContinue was false, returning not authorized");
return aGsonHandler.toJson(HueErrorResponse.createResponse("101", "/api/", "link button not pressed", null, null, null).getTheErrors());
}
private Object getConfig(String userId, String ipAddress) {
@@ -750,15 +765,15 @@ public class HueMulator {
log.info("Traceupnp: hue api/:userid/config config requested: " + userId + " from " + ipAddress);
log.debug("hue api config requested: " + userId + " from " + ipAddress);
if (bridgeSettingMaster.getBridgeSecurity().validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton()) != null) {
log.debug("hue api config requested, No User supplied, returning public config");
log.debug("hue api config requested, User invalid, returning public config");
HuePublicConfig apiResponse = HuePublicConfig.createConfig("Philips hue",
bridgeSettings.getUpnpConfigAddress(), bridgeSettings.getHubversion());
return apiResponse;
}
HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(),
bridgeSettings.getWhitelist(), bridgeSettings.getHubversion());
bridgeSettingMaster.getBridgeSecurity().getWhitelist(), bridgeSettings.getHubversion(), bridgeSettingMaster.getBridgeControl().isLinkButton());
log.debug("api response config <<<" + aGsonHandler.toJson(apiResponse.getConfig()) + ">>>");
return apiResponse.getConfig();
}
@@ -766,11 +781,13 @@ public class HueMulator {
private Object getFullState(String userId, String ipAddress) {
log.debug("hue api full state requested: " + userId + " from " + ipAddress);
HueError[] theErrors = bridgeSettingMaster.getBridgeSecurity().validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton());
if (theErrors != null)
if (theErrors != null) {
log.debug("full state error occurred <<<" + aGsonHandler.toJson(theErrors) + ">>>");
return theErrors;
}
HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(),
bridgeSettings.getWhitelist(), bridgeSettings.getHubversion());
bridgeSettingMaster.getBridgeSecurity().getWhitelist(), bridgeSettings.getHubversion(), bridgeSettingMaster.getBridgeControl().isLinkButton());
apiResponse.setLights((Map<String, DeviceResponse>) this.lightsListHandler(userId, ipAddress));
apiResponse.setGroups((Map<String, GroupResponse>) this.groupsListHandler(userId, ipAddress));

View File

@@ -1,10 +1,11 @@
package com.bwssystems.HABridge.plugins.hal;
import com.bwssystems.HABridge.NamedIP;
public class HalDevice {
private String haldevicetype;
private String haldevicename;
private String haladdress;
private String halname;
private NamedIP haladdress;
private DeviceElements buttons;
public String getHaldevicetype() {
return haldevicetype;
@@ -18,18 +19,12 @@ public class HalDevice {
public void setHaldevicename(String haldevicename) {
this.haldevicename = haldevicename;
}
public String getHaladdress() {
public NamedIP getHaladdress() {
return haladdress;
}
public void setHaladdress(String haladdress) {
public void setHaladdress(NamedIP haladdress) {
this.haladdress = haladdress;
}
public String getHalname() {
return halname;
}
public void setHalname(String halname) {
this.halname = halname;
}
public DeviceElements getButtons() {
return buttons;
}

View File

@@ -13,8 +13,14 @@ import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson;
public class HalHome implements Home {
private static final Logger log = LoggerFactory.getLogger(HalHome.class);
@@ -30,7 +36,7 @@ public class HalHome implements Home {
public Object getItems(String type) {
if(!validHal)
return null;
log.debug("consolidating devices for hues");
log.debug("consolidating devices for HALs");
List<HalDevice> theResponse = null;
Iterator<String> keys = hals.keySet().iterator();
List<HalDevice> deviceList = new ArrayList<HalDevice>();
@@ -106,8 +112,63 @@ public class HalHome implements Home {
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
// Not a device handler
return null;
boolean halFound = false;
String responseString = null;
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && theUrl.contains("http")) {
String intermediate = theUrl.substring(theUrl.indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
// String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
// String hostAddr = null;
// String port = null;
// if (hostPortion.contains(":")) {
// hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
// port = hostPortion.substring(intermediate.indexOf(':') + 1);
// } else
// hostAddr = hostPortion;
log.debug("executing HUE api request to Http "
+ (anItem.getHttpVerb() == null ? "GET" : anItem.getHttpVerb()) + ": "
+ anItem.getItem().getAsString());
String anUrl = null;
anUrl = BrightnessDecode.calculateReplaceIntensityValue(intermediate, intensity, targetBri, targetBriInc, false);
anUrl = DeviceDataDecode.replaceDeviceData(anUrl, device);
anUrl = TimeDecode.replaceTimeValue(anUrl);
for (Map.Entry<String, HalInfo> entry : hals.entrySet())
{
if(entry.getValue().getHalAddress().getIp().equals(hostPortion)) {
halFound = true;
if(entry.getValue().getHalAddress().getSecure()!= null && entry.getValue().getHalAddress().getSecure())
anUrl = "https://" + anUrl;
else
anUrl = "http://" + anUrl;
if(!anUrl.contains("?Token="))
anUrl = anUrl + "?Token=" + entry.getValue().getHalAddress().getPassword();
log.debug("executing HUE api request to Http "
+ (anItem.getHttpVerb() == null ? "GET" : anItem.getHttpVerb()) + ": "
+ anUrl);
if (entry.getValue().deviceCommand(anUrl) == null) {
log.warn("Error on calling hal to change device state: " + anUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
}
}
}
if(!halFound) {
log.warn("No HAL found to call: " + theUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"No HAL found.", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
return responseString;
}
@Override

View File

@@ -35,13 +35,13 @@ public class HalInfo {
private static final String IRDATA_TYPE = "IrData";
private HTTPHandler httpClient;
private NamedIP halAddress;
private String theToken;
public HalInfo(NamedIP addressName, String aGivenToken) {
super();
httpClient = new HTTPHandler();
halAddress = addressName;
theToken = aGivenToken;
if(halAddress.getPassword() == null || halAddress.getPassword().trim().isEmpty())
halAddress.setPassword(aGivenToken);
}
public List<HalDevice> getLights() {
@@ -98,12 +98,16 @@ public class HalInfo {
String theUrl = null;
String theData;
theUrl = "http://" + halAddress.getIp() + apiType + theToken;
if(halAddress.getSecure()!= null && halAddress.getSecure())
theUrl = "https://";
else
theUrl = "http://";
theUrl = theUrl + halAddress.getIp() + apiType + halAddress.getPassword();
theData = httpClient.doHttpRequest(theUrl, null, null, null, null);
if(theData != null) {
log.debug("GET " + deviceType + " HalApiResponse - data: " + theData);
theHalApiResponse = new Gson().fromJson(theData, DeviceElements.class);
if(theHalApiResponse.getDeviceElements() == null) {
if(theHalApiResponse == null || theHalApiResponse.getDeviceElements() == null) {
StatusDescription theStatus = new Gson().fromJson(theData, StatusDescription.class);
if(theStatus.getStatus() == null) {
log.warn("Cannot get an devices for type " + deviceType + " for hal " + halAddress.getName() + " as response is not parsable.");
@@ -121,8 +125,10 @@ public class HalInfo {
HalDevice aNewHalDevice = new HalDevice();
aNewHalDevice.setHaldevicetype(deviceType);
aNewHalDevice.setHaldevicename(theDevice.getDeviceName());
aNewHalDevice.setHaladdress(halAddress.getIp());
aNewHalDevice.setHalname(halAddress.getName());
NamedIP theaddress = new NamedIP();
theaddress.setIp(halAddress.getIp());
theaddress.setName(halAddress.getName());
aNewHalDevice.setHaladdress(theaddress);
deviceList.add(aNewHalDevice);
}
@@ -145,7 +151,11 @@ public class HalInfo {
deviceList = new ArrayList<HalDevice>();
while (theHalDevices.hasNext()) {
HalDevice theHalDevice = theHalDevices.next();
theUrl = "http://" + halAddress.getIp() + IRBUTTON_REQUEST + TextStringFormatter.forQuerySpaceUrl(theHalDevice.getHaldevicename()) + TOKEN_REQUEST + theToken;
if(halAddress.getSecure()!= null && halAddress.getSecure())
theUrl = "https://";
else
theUrl = "http://";
theUrl = theUrl + halAddress.getIp() + IRBUTTON_REQUEST + TextStringFormatter.forQuerySpaceUrl(theHalDevice.getHaldevicename()) + TOKEN_REQUEST + halAddress.getPassword();
theData = httpClient.doHttpRequest(theUrl, null, null, null, null);
if (theData != null) {
log.debug("GET IrData for IR Device " + theHalDevice.getHaldevicename() + " HalApiResponse - data: " + theData);
@@ -177,6 +187,12 @@ public class HalInfo {
return deviceList;
}
public String deviceCommand(String theUrl) {
String theData = null;
theData = httpClient.doHttpRequest(theUrl, null, null, null, null);
return theData;
}
public NamedIP getHalAddress() {
return halAddress;
}
@@ -190,6 +206,5 @@ public class HalInfo {
httpClient.closeHandler();
httpClient = null;
halAddress = null;
theToken = null;
}
}

View File

@@ -6,6 +6,7 @@ public class ButtonPress {
private Integer delay;
private Integer count;
private String hub;
private Integer pressTime;
public String getDevice() {
return device;
}
@@ -43,4 +44,10 @@ public class ButtonPress {
public void setHub(String hub) {
this.hub = hub;
}
public Integer getPressTime() {
return pressTime;
}
public void setPressTime(Integer pressTime) {
this.pressTime = pressTime;
}
}

View File

@@ -62,7 +62,7 @@ public class HarmonyHandler {
}
public Boolean startActivity(RunActivity anActivity) {
log.debug("Harmony api start activity requested for: " + anActivity.getName() + " noop mode: " + noopCalls);
log.debug("Harmony api start activity requested for: " + anActivity.getName() + " for a hub: " + anActivity.getHub() + " noop mode: " + noopCalls);
if (anActivity.isValid()) {
try {
if (noopCalls || devMode) {
@@ -72,7 +72,7 @@ public class HarmonyHandler {
devResponse.setCurrentActivity(devResponse.getConfig().getActivityByName(anActivity.getName()));
}
log.info("noop mode: Harmony api start activity requested for: " + anActivity.getName());
log.info("noop mode: Harmony api start activity requested for: " + anActivity.getName() + " for a hub: " + anActivity.getHub());
}
else
harmonyClient.startActivity(Integer.parseInt(anActivity.getName()));
@@ -81,12 +81,12 @@ public class HarmonyHandler {
if (!noopCalls)
harmonyClient.startActivityByName(anActivity.getName());
} catch (IllegalArgumentException ei) {
log.error("Error in finding activity: " + anActivity.getName());
log.error("Error in finding activity: " + anActivity.getName() + " for a hub: " + anActivity.getHub());
return false;
}
}
} else {
log.error("Error in finding activity: " + anActivity.getName());
log.error("Error in finding activity: " + anActivity.getName() + " for a hub: " + anActivity.getHub());
return false;
}
@@ -94,26 +94,30 @@ public class HarmonyHandler {
}
public Boolean pressButton(ButtonPress aDeviceButton) {
log.debug("Harmony api press a button requested for device: " + aDeviceButton.getDevice() + " and a for button: " + aDeviceButton.getButton() + " noop mode: " + noopCalls);
log.debug("Harmony api press a button requested for device: " + aDeviceButton.getDevice() + " and a for button: " + aDeviceButton.getButton() + " with pressTime of: " + aDeviceButton.getPressTime() + " for a hub: " + aDeviceButton.getHub() + " noop mode: " + noopCalls);
if (aDeviceButton.isValid()) {
try {
if (noopCalls || devMode) {
log.info("noop mode: Harmony api press a button requested for device: " + aDeviceButton.getDevice() + " and a for button: " + aDeviceButton.getButton());
log.info("noop mode: Harmony api press a button requested for device: " + aDeviceButton.getDevice() + " and a for button: " + aDeviceButton.getButton() +
" with a pressTime of: " + aDeviceButton.getPressTime() + " for a hub: " + aDeviceButton.getHub());
}
else
harmonyClient.pressButton(Integer.parseInt(aDeviceButton.getDevice()), aDeviceButton.getButton());
else {
if(aDeviceButton.getPressTime() != null && aDeviceButton.getPressTime() > 0)
harmonyClient.pressButton(Integer.parseInt(aDeviceButton.getDevice()), aDeviceButton.getButton(), aDeviceButton.getPressTime());
else
harmonyClient.pressButton(Integer.parseInt(aDeviceButton.getDevice()), aDeviceButton.getButton());
}
} catch (IllegalArgumentException e) {
try {
if (!noopCalls)
harmonyClient.pressButton(aDeviceButton.getDevice(), aDeviceButton.getButton());
} catch (IllegalArgumentException ei) {
log.error("Error in finding device: " + aDeviceButton.getDevice() +" and a button: " + aDeviceButton.getButton());
log.error("Error in finding device: " + aDeviceButton.getDevice() +" and a button: " + aDeviceButton.getButton() + " for a hub: " + aDeviceButton.getHub());
return false;
}
}
} else {
log.error("Error in finding device: " + aDeviceButton.getDevice() +" and a button: " + aDeviceButton.getButton());
log.error("Error in finding device: " + aDeviceButton.getDevice() +" and a button: " + aDeviceButton.getButton() + " for a hub: " + aDeviceButton.getHub());
return false;
}

View File

@@ -181,7 +181,7 @@ public class HarmonyHome implements Home {
aMultiUtil.setTheDelay(anItem.getDelay());
else
aMultiUtil.setTheDelay(aMultiUtil.getDelayDefault());
log.debug("pressing button: " + deviceButtons[z].getDevice() + " - " + deviceButtons[z].getButton() + " - iteration: " + String.valueOf(z) + " - count: " + String.valueOf(y));
log.debug("pressing button: " + deviceButtons[z].getDevice() + " - " + deviceButtons[z].getButton() + " with pressTime of: " + deviceButtons[z].getPressTime() + " - iteration: " + String.valueOf(z) + " - count: " + String.valueOf(y));
if(deviceButtons[z].getHub() == null || deviceButtons[z].getHub().isEmpty())
deviceButtons[z].setHub(device.getTargetDevice());
HarmonyHandler myHarmony = getHarmonyHandler(deviceButtons[z].getHub());

View File

@@ -6,6 +6,7 @@ import java.net.URISyntaxException;
import java.nio.charset.Charset;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
@@ -67,73 +68,67 @@ public class HTTPHandler {
if (requestBody != null)
putRequest.setEntity(requestBody);
request = putRequest;
}
else
} else
request = new HttpGet(theURI);
} catch (IllegalArgumentException e) {
log.warn("Error creating outbound http request: IllegalArgumentException in log", e);
return null;
}
log.debug("Making outbound call in doHttpRequest: " + request);
log.debug("Making outbound call in doHttpRequest: <<<" + request.toString() + ">>>");
if (headers != null && headers.length > 0) {
for (int i = 0; i < headers.length; i++) {
request.setHeader(headers[i].getName(), headers[i].getValue());
}
}
HttpResponse response;
try {
for(int retryCount = 0; retryCount < 2; retryCount++) {
HttpResponse response = null;
for (int retryCount = 0; retryCount < 2; retryCount++) {
try {
response = httpClient.execute(request);
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute (" + retryCount + ") on URL responded: "
+ response.getStatusLine().getStatusCode());
if (response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
if (response.getEntity() != null) {
try {
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
// content
// for
// data
EntityUtils.consume(response.getEntity()); // close out
// inputstream
// ignore
// content
} catch (Exception e) {
log.debug("Error ocurred in handling response entity after successful call, still responding success. "
+ e.getMessage(), e);
}
log.debug("Successfull response - The http response is <<<" + theContent + ">>>");
}
} catch (ClientProtocolException e) {
log.warn("Client Protocol Exception received, retyring....");
} catch (IOException e) {
log.warn("Error calling out to HA gateway: IOException in log", e);
retryCount = 2;
}
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute (" + retryCount + ") on URL responded: "
+ response.getStatusLine().getStatusCode());
if (response != null && response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300) {
log.debug("Successfull response - The http response is <<<" + theContent + ">>>");
retryCount = 2;
} else if (response != null) {
log.warn("HTTP response code was not an expected successful response of between 200 - 299, the code was: "
+ response.getStatusLine());
if (response.getStatusLine().getStatusCode() == 504) {
log.warn("HTTP response code was 504, retrying...");
} else
retryCount = 2;
} else {
log.warn("HTTP response code was not an expected successful response of between 200 - 299, the code was: " + response.getStatusLine());
try {
String someContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
// content
// for
// data
EntityUtils.consume(response.getEntity()); // close out
// inputstream
// ignore
// content
log.debug("Unsuccessfull response - The http response is <<<" + someContent + ">>>");
} catch (Exception e) {
//noop
}
if (response.getStatusLine().getStatusCode() == 504) {
log.warn("HTTP response code was 504, retrying...");
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// noop
}
}
else
retryCount = 2;
}
if (response != null && response.getEntity() != null) {
try {
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); // read
// content
// for
// data
EntityUtils.consume(response.getEntity()); // close out
// inputstream
// ignore
// content
} catch (Exception e) {
log.debug("Error ocurred in handling response entity after successful call, still responding success. "
+ e.getMessage(), e);
}
}
if(retryCount < 2) {
theContent = null;
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// noop
}
}
} catch (IOException e) {
log.warn("Error calling out to HA gateway: IOException in log", e);
}
return theContent;
}

View File

@@ -24,9 +24,11 @@ public class HueHome implements Home {
private Map<String, HueInfo> hues;
private Boolean validHue;
private Gson aGsonHandler;
private BridgeSettings theBridgeSettings;
public HueHome(BridgeSettings bridgeSettings) {
super();
theBridgeSettings = bridgeSettings;
createHome(bridgeSettings);
}
@@ -113,12 +115,19 @@ public class HueHome implements Home {
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getHueaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHue = theList.next();
hues.put(aHue.getName(), new HueInfo(aHue));
hues.put(aHue.getName(), new HueInfo(aHue, this));
}
aGsonHandler = new GsonBuilder().create();
}
return this;
}
protected void updateHue(NamedIP aHue) {
theBridgeSettings.getBridgeSettingsDescriptor().updateHue(aHue);
if(theBridgeSettings.getBridgeSettingsDescriptor().isSettingsChanged()) {
theBridgeSettings.updateConfigFile();
}
}
@Override
public void closeHome() {

View File

@@ -27,12 +27,14 @@ public class HueInfo {
private static final Logger log = LoggerFactory.getLogger(HueInfo.class);
private HTTPHandler httpClient;
private NamedIP hueAddress;
private HueHome myHome;
public static final String HUE_REQUEST = "/api";
public HueInfo(NamedIP addressName) {
public HueInfo(NamedIP addressName, HueHome theHome) {
super();
httpClient = new HTTPHandler();
hueAddress = addressName;
myHome = theHome;
}
public HueApiResponse getHueApiResponse() {
@@ -62,7 +64,7 @@ public class HueInfo {
// ignore
}
}
theUrl = "http://" + hueAddress.getIp() + HueUtil.HUE_REQUEST + "/" + hueAddress.getUsername();
theUrl = "http://" + hueAddress.getIp() + HUE_REQUEST + "/" + hueAddress.getUsername();
theData = httpClient.doHttpRequest(theUrl, null, null, null, null);
if(theData != null) {
log.debug("GET HueApiResponse - data: " + theData);
@@ -118,6 +120,7 @@ public class HueInfo {
else {
SuccessUserResponse[] theResponses = new Gson().fromJson(theBody, SuccessUserResponse[].class); //read content for data, SuccessUserResponse[].class);
hueAddress.setUsername(theResponses[0].getSuccess().getUsername());
myHome.updateHue(hueAddress);
}
}
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content

View File

@@ -1,56 +0,0 @@
package com.bwssystems.HABridge.plugins.hue;
import java.io.IOException;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.api.SuccessUserResponse;
import com.bwssystems.HABridge.api.UserCreateRequest;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.google.gson.Gson;
public class HueUtil {
private static final Logger log = LoggerFactory.getLogger(HueUtil.class);
public static final String HUE_REQUEST = "/api";
public static final String registerWithHue(HTTPHandler anHttpHandler, String ipAddress, String aName, String theUser) {
UserCreateRequest theLogin = new UserCreateRequest();
theLogin.setDevicetype("HABridge#MyMachine");
HttpPost postRequest = new HttpPost("http://" + ipAddress + HUE_REQUEST);
ContentType parsedContentType = ContentType.parse("application/json");
StringEntity requestBody = new StringEntity(new Gson().toJson(theLogin), parsedContentType);
HttpResponse response = null;
postRequest.setEntity(requestBody);
HttpClient anHttpClient = anHttpHandler.getHttpClient();
try {
response = anHttpClient.execute(postRequest);
log.debug("POST execute on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
String theBody = EntityUtils.toString(response.getEntity());
log.debug("registerWithHue response data: " + theBody);
if(theBody.contains("[{\"error\":")) {
if(theBody.contains("link button not")) {
log.warn("registerWithHue needs link button pressed on HUE bridge: " + aName);
}
else
log.warn("registerWithHue returned an unexpected error: " + theBody);
}
else {
SuccessUserResponse[] theResponses = new Gson().fromJson(theBody, SuccessUserResponse[].class); //read content for data, SuccessUserResponse[].class);
theUser = theResponses[0].getSuccess().getUsername();
}
}
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
} catch (IOException e) {
log.warn("Error logging into HUE: IOException in log", e);
}
return theUser;
}
}

View File

@@ -13,11 +13,12 @@ import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import java.util.Optional;
public class MQTTHandler {
private static final Logger log = LoggerFactory.getLogger(MQTTHandler.class);
private NamedIP myConfig;
private MqttClient myClient;
private int qos = 1;
public MQTTHandler(NamedIP aConfig) {
super();
@@ -40,20 +41,19 @@ public class MQTTHandler {
}
try {
myClient.connect(connOpts);
} catch (MqttSecurityException e) {
log.error("Could not connect MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp() + " with message: " + e.getMessage());
} catch (MqttException e) {
log.error("Could not connect MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp() + " with message: " + e.getMessage());
}
}
public void publishMessage(String topic, String content) {
public void publishMessage(String topic, String content, Integer qos, Boolean retain) {
MqttMessage message = new MqttMessage(StringEscapeUtils.unescapeJava(content).getBytes());
message.setQos(qos);
message.setQos(Optional.ofNullable(qos).orElse(1));
message.setRetained(Optional.ofNullable(retain).orElse(false));
try {
myClient.publish(topic, message);
} catch (MqttPersistenceException e) {
log.error("Could not publish to MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp() + " with message: " + e.getMessage());
} catch (MqttException e) {
log.error("Could not publish to MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp() + " with message: " + e.getMessage());
}

View File

@@ -37,9 +37,7 @@ public class MQTTHome implements Home {
return;
log.debug("Shutting down MQTT handlers.");
if(handlers != null && !handlers.isEmpty()) {
Iterator<String> keys = handlers.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
for (String key : handlers.keySet()) {
handlers.get(key).shutdown();
}
}
@@ -108,7 +106,7 @@ public class MQTTHome implements Home {
if (mqttHandler == null) {
log.warn("Should not get here, no mqtt hanlder available");
} else {
mqttHandler.publishMessage(mqttMessages[y].getTopic(), mqttMessages[y].getMessage());
mqttHandler.publishMessage(mqttMessages[y].getTopic(), mqttMessages[y].getMessage(), mqttMessages[y].getQos(), mqttMessages[y].getRetain());
}
}
}
@@ -130,13 +128,10 @@ public class MQTTHome implements Home {
aGsonHandler =
new GsonBuilder()
.create();
handlers = new HashMap<String, MQTTHandler>();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getMqttaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aClientConfig = theList.next();
handlers = new HashMap<>();
for (NamedIP aClientConfig : bridgeSettings.getBridgeSettingsDescriptor().getMqttaddress().getDevices()) {
MQTTHandler aHandler = new MQTTHandler(aClientConfig);
if(aHandler != null)
handlers.put(aClientConfig.getName(), aHandler);
handlers.put(aClientConfig.getName(), aHandler);
}
}
return this;

View File

@@ -6,6 +6,8 @@ public class MQTTMessage {
private String message;
private Integer delay;
private Integer count;
private Integer qos;
private Boolean retain;
public String getClientId() {
return clientId;
}
@@ -36,4 +38,19 @@ public class MQTTMessage {
public void setCount(Integer count) {
this.count = count;
}
public Integer getQos() {
return qos;
}
public void setQos(Integer qos) {
this.qos = qos;
}
public Boolean getRetain() {
return retain;
}
public void setRetain(Boolean retain) {
this.retain = retain;
}
}

View File

@@ -25,6 +25,7 @@ public class UpnpListener {
private String responseAddress;
private boolean strict;
private boolean traceupnp;
private boolean useUpnpIface;
private BridgeControlDescriptor bridgeControl;
private String bridgeId;
private String bridgeSNUUID;
@@ -74,6 +75,7 @@ public class UpnpListener {
responseAddress = theSettings.getUpnpConfigAddress();
strict = theSettings.isUpnpStrict();
traceupnp = theSettings.isTraceupnp();
useUpnpIface = theSettings.isUseupnpiface();
bridgeControl = theControl;
aHueConfig = HuePublicConfig.createConfig("temp", responseAddress, HueConstants.HUB_VERSION);
bridgeId = aHueConfig.getBridgeid();
@@ -114,7 +116,10 @@ public class UpnpListener {
else
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
if(!useUpnpIface)
IPsPerNic++;
else if(addr.getHostAddress().equals(responseAddress))
IPsPerNic++;
}
}
log.debug("Checking " + name + " to our interface set");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -85,7 +85,7 @@ app.config (function ($locationProvider, $routeProvider) {
})
});
app.run( async function ($rootScope, $location, Auth, bridgeService) {
app.run(function ($rootScope, $location, Auth, bridgeService) {
bridgeService.getHABridgeVersion();
$rootScope.$on('securitySetupReceived', function(event, data) {
@@ -256,7 +256,7 @@ app.service ('bridgeService', function ($rootScope, $http, $base64, $location, n
if (error.status === 401)
$rootScope.$broadcast('securityReinit', 'done');
else
self.displayWarn("Cannot get testuser: ", error);
self.displayWarn("Cannot get testuser: ", error);
}
);
}
@@ -271,7 +271,7 @@ app.service ('bridgeService', function ($rootScope, $http, $base64, $location, n
if (error.status === 401)
$rootScope.$broadcast('securityReinit', 'done');
else
self.displayWarn("Cannot get a user: ", error);
self.displayWarn("Cannot get a user: ", error);
}
);
};
@@ -1180,6 +1180,7 @@ app.service ('bridgeService', function ($rootScope, $http, $base64, $location, n
this.testUrl = function (device, type, value) {
var msgDescription = "unknown";
self.getTestUser();
var testUrl = this.state.huebase + "/" + this.state.testuser + "/lights/" + device.id + "/state";
var testBody = "{\"on\":";
if (type === "off") {
@@ -1197,8 +1198,13 @@ app.service ('bridgeService', function ($rootScope, $http, $base64, $location, n
msgDescription = "success " + angular.toJson(response.data);
}
if (typeof(response.data[0].error) !== 'undefined') {
msgDescription = "error " + angular.toJson(response.data[0].error);
self.displayErrorMessage("Request Error, Pleae look in your habridge log: ", msgDescription);
if(reponse.data[0].error.indexOf("unauthorized") > -1) {
self.displayWarn("Authorization error, please retry...", null);
}
else {
msgDescription = "error " + angular.toJson(response.data[0].error);
self.displayErrorMessage("Request Error, Please look in your habridge log: ", msgDescription);
}
return;
}
@@ -1349,11 +1355,11 @@ app.controller ('SystemController', function ($scope, $location, bridgeService,
}
}
};
$scope.addHarmonytoSettings = function (newharmonyname, newharmonyip) {
$scope.addHarmonytoSettings = function (newharmonyname, newharmonyip, newharmonywebhook) {
if($scope.bridge.settings.harmonyaddress === undefined || $scope.bridge.settings.harmonyaddress === null) {
$scope.bridge.settings.harmonyaddress = { devices: [] };
}
var newharmony = {name: newharmonyname, ip: newharmonyip }
var newharmony = {name: newharmonyname, ip: newharmonyip, webhook: newharmonywebhook}
$scope.bridge.settings.harmonyaddress.devices.push(newharmony);
$scope.newharmonyname = null;
$scope.newharmonyip = null;
@@ -1381,11 +1387,11 @@ app.controller ('SystemController', function ($scope, $location, bridgeService,
}
}
};
$scope.addHaltoSettings = function (newhalname, newhalip) {
$scope.addHaltoSettings = function (newhalname, newhalip, newhalsecure, newhaltoken) {
if($scope.bridge.settings.haladdress === undefined || $scope.bridge.settings.haladdress === null) {
$scope.bridge.settings.haladdress = { devices: [] };
}
var newhal = {name: newhalname, ip: newhalip }
var newhal = {name: newhalname, ip: newhalip, secure: newhalsecure, password: newhaltoken }
$scope.bridge.settings.haladdress.devices.push(newhal);
$scope.newhalname = null;
$scope.newhalip = null;
@@ -2130,11 +2136,18 @@ app.controller('HarmonyController', function ($scope, $location, bridgeService,
$location.path('/editdevice');
};
$scope.buildButtonUrls = function (harmonydevice, onbutton, offbutton) {
$scope.buildButtonUrls = function (harmonydevice, onbutton, offbutton, onpresstime, offpresstime) {
var actionOn = angular.fromJson(onbutton);
var actionOff = angular.fromJson(offbutton);
onpayload = "{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOn.command + "\",\"hub\":\"" + harmonydevice.hub + "\"}";
offpayload = "{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOff.command + "\",\"hub\":\"" + harmonydevice.hub + "\"}";
var postCmd = "\"}";
if(onpresstime !== undefined && onpresstime !== "0")
postCmd = "\",\"pressTime\":" + onpresstime + "}";
onpayload = "{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOn.command + "\",\"hub\":\"" + harmonydevice.hub + postCmd;
if(offpresstime !== undefined && offpresstime !== "0")
postCmd = "\",\"pressTime\":" + offpresstime + "}";
else
postCmd = "\"}";
offpayload = "{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOff.command + "\",\"hub\":\"" + harmonydevice.hub + postCmd;
bridgeService.buildUrls(onpayload, null, offpayload, true, actionOn.command, harmonydevice.device.label, harmonydevice.hub, "button", "harmonyButton", null, null);
$scope.device = bridgeService.state.device;
@@ -2280,7 +2293,7 @@ app.controller('HueController', function ($scope, $location, bridgeService, ngDi
$scope.device = bridgeService.state.device;
};
$scope.buildDeviceUrls = function (huedevice) {
$scope.buildDeviceUrls = function (huedevice, buildonly) {
onpayload = "{\"ipAddress\":\"" + huedevice.hueaddress + "\",\"deviceId\":\"" + huedevice.huedeviceid +"\",\"hueName\":\"" + huedevice.huename + "\"}";
offpayload = "{\"ipAddress\":\"" + huedevice.hueaddress + "\",\"deviceId\":\"" + huedevice.huedeviceid +"\",\"hueName\":\"" + huedevice.huename + "\"}";
bridgeService.buildUrls(onpayload, null, offpayload, true, huedevice.device.uniqueid, huedevice.device.name, huedevice.huename, "passthru", "hueDevice", null, null);
@@ -2408,7 +2421,6 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
var preOffCmd = "";
var nameCmd = "";
var aDeviceType;
var postCmd = "?Token=" + $scope.bridge.settings.haltoken;
if(haldevice.haldevicetype === "Group") {
aDeviceType = "group";
preOnCmd = "/GroupService!GroupCmd=On";
@@ -2433,29 +2445,25 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
nameCmd = "!DeviceName=";
}
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0) && aDeviceType === "switch")
dimpayload = "http://" + haldevice.haladdress
dimpayload = "http://" + haldevice.haladdress.ip
+ preDimCmd
+ dim_control
+ nameCmd
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ postCmd;
+ haldevice.haldevicename.replaceAll(" ", "%20");
else
dimpayload = "http://" + haldevice.haladdress
dimpayload = "http://" + haldevice.haladdress.ip
+ preOnCmd
+ nameCmd
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ postCmd;
onpayload = "http://" + haldevice.haladdress
+ haldevice.haldevicename.replaceAll(" ", "%20");
onpayload = "http://" + haldevice.haladdress.ip
+ preOnCmd
+ nameCmd
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ postCmd;
offpayload = "http://" + haldevice.haladdress
+ haldevice.haldevicename.replaceAll(" ", "%20");
offpayload = "http://" + haldevice.haladdress.ip
+ preOffCmd
+ nameCmd
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ postCmd;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname, haldevice.haldevicename, haldevice.halname, aDeviceType, "halDevice", null, null);
+ haldevice.haldevicename.replaceAll(" ", "%20");
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name, haldevice.haldevicename, haldevice.haladdress.name, aDeviceType, "halDevice", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2466,10 +2474,10 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
$scope.buildButtonUrls = function (haldevice, onbutton, offbutton, buildonly) {
var actionOn = angular.fromJson(onbutton);
var actionOff = angular.fromJson(offbutton);
onpayload = "http://" + haldevice.haladdress + "/IrService!IrCmd=Set!IrDevice=" + haldevice.haldevicename.replaceAll(" ", "%20") + "!IrButton=" + actionOn.DeviceName.replaceAll(" ", "%20") + "?Token=" + $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress + "/IrService!IrCmd=Set!IrDevice=" + haldevice.haldevicename.replaceAll(" ", "%20") + "!IrButton=" + actionOff.DeviceName.replaceAll(" ", "%20") + "?Token=" + $scope.bridge.settings.haltoken;
onpayload = "http://" + haldevice.haladdress.ip + "/IrService!IrCmd=Set!IrDevice=" + haldevice.haldevicename.replaceAll(" ", "%20") + "!IrButton=" + actionOn.DeviceName.replaceAll(" ", "%20");
offpayload = "http://" + haldevice.haladdress.ip + "/IrService!IrCmd=Set!IrDevice=" + haldevice.haldevicename.replaceAll(" ", "%20") + "!IrButton=" + actionOff.DeviceName.replaceAll(" ", "%20");
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-" + actionOn.DeviceName, haldevice.haldevicename, haldevice.halname, "button", "halButton", null, null);
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-" + actionOn.DeviceName, haldevice.haldevicename, haldevice.haladdress.name, "button", "halButton", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2478,9 +2486,9 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
};
$scope.buildHALHomeUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress + "/ModeService!ModeCmd=Set!ModeName=Home?Token=" + $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress + "/ModeService!ModeCmd=Set!ModeName=Away?Token=" + $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-HomeAway", haldevice.haldevicename, haldevice.halname, "home", "halHome", null, null);
onpayload = "http://" + haldevice.haladdress.ip + "/ModeService!ModeCmd=Set!ModeName=Home";
offpayload = "http://" + haldevice.haladdress.ip + "/ModeService!ModeCmd=Set!ModeName=Away";
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-HomeAway", haldevice.haldevicename, haldevice.haladdress.name, "home", "halHome", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2489,22 +2497,19 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
};
$scope.buildHALHeatUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
onpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Heat?Token="
+ $scope.bridge.settings.haltoken;
dimpayload = "http://" + haldevice.haladdress
+ "!HVACMode=Heat";
dimpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Heat!HeatSpValue=${intensity.percent}?Token="
+ $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress
+ "!HVACMode=Heat!HeatSpValue=${intensity.percent}";
offpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Off?Token="
+ $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetHeat", haldevice.haldevicename + " Heat", haldevice.halname, "thermo", "halThermoSet", null, null);
+ "!HVACMode=Off";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-SetHeat", haldevice.haldevicename + " Heat", haldevice.haladdress.name, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2513,22 +2518,19 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
};
$scope.buildHALCoolUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
onpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Cool?Token="
+ $scope.bridge.settings.haltoken;
dimpayload = "http://" + haldevice.haladdress
+ "!HVACMode=Cool";
dimpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Cool!CoolSpValue=${intensity.percent}?Token="
+ $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress
+ "!HVACMode=Cool!CoolSpValue=${intensity.percent}";
offpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Off?Token="
+ $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetCool", haldevice.haldevicename + " Cool", haldevice.halname, "thermo", "halThermoSet", null, null);
+ "!HVACMode=Off";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-SetCool", haldevice.haldevicename + " Cool", haldevice.haladdress.name, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2537,16 +2539,15 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
};
$scope.buildHALAutoUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
onpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Auto?Token="
+ $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress
+ "!HVACMode=Auto";
offpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Off?Token="
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetAuto", haldevice.haldevicename + " Auto", haldevice.halname, "thermo", "halThermoSet", null, null);
+ "!HVACMode=Off";
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-SetAuto", haldevice.haldevicename + " Auto", haldevice.haladdress.name, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2555,17 +2556,15 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
};
$scope.buildHALOffUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
onpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Auto?Token="
+ $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress
+ "!HVACMode=Auto";
offpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!HVACMode=Off?Token="
$scope.device.offUrl = "http://" + haldevice.haladdress
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-TurnOff", haldevice.haldevicename + " Thermostat", haldevice.halname, "thermo", "halThermoSet", null, null);
+ "!HVACMode=Off";
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-TurnOff", haldevice.haldevicename + " Thermostat", haldevice.haladdress.name, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2574,17 +2573,15 @@ app.controller('HalController', function ($scope, $location, bridgeService, ngDi
};
$scope.buildHALFanUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
onpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!FanMode=On?Token="
+ $scope.bridge.settings.haltoken;
offpayload = "http://" + haldevice.haladdress
+ "!FanMode=On";
offpayload = "http://" + haldevice.haladdress.ip
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
+ "!FanMode=Auto?Token="
+ $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetFan", haldevice.haldevicename + " Fan", haldevice.halname, "thermo", "halThermoSet", null, null);
+ "!FanMode=Auto";
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.haladdress.name + "-SetFan", haldevice.haldevicename + " Fan", haldevice.haladdress.name, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
@@ -2704,9 +2701,9 @@ app.controller('MQTTController', function ($scope, $location, bridgeService, ngD
$scope.device = bridgeService.state.device;
};
$scope.buildMQTTPublish = function (mqttbroker, mqtttopic, mqttmessage) {
onpayload = "{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}";
offpayload = "{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}";
$scope.buildMQTTPublish = function (mqttbroker, mqtttopic, mqttmessage, mqttqos, mqttretain) {
onpayload = "{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\",\"qos\":\"" + mqttqos + "\",\"retain\":\"" + mqttretain + "\"}";
offpayload = "{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\",\"qos\":\"" + mqttqos + "\",\"retain\":\"" + mqttretain + "\"}";
bridgeService.buildUrls(onpayload, null, offpayload, true, mqttbroker.clientId + "-" + mqtttopic, mqttbroker.clientId + mqtttopic, mqttbroker.clientId, "mqtt", "mqttMessage", null, null);
$scope.device = bridgeService.state.device;
@@ -3617,7 +3614,6 @@ app.controller('LoginController', function ($scope, $location, Auth) {
$scope.logout = function() {
Auth.logout();
$scope.loggedIn = Auth.isLoggedIn();
bridgeService.displaySuccess("User Logged Out");
$location.path("/login");
};
});

View File

@@ -47,6 +47,7 @@
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="description">Description</th>
<th sortable-header col="devicestate">Device State</th>
<th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th>
<th sortable-header col="inactive">Inactive</th>
@@ -59,6 +60,7 @@
<td>{{device.id}}</td>
<td>{{device.name}}</td>
<td class="cr">{{device.description}}</td>
<td class="cr">on={{device.deviceState.on}},bri={{device.deviceState.on}},hue={{device.deviceState.hue}},sat={{device.deviceState.sat}},effect={{device.deviceState.effect}},ct={{device.deviceState.ct}},alert={{device.deviceState.alert}},colormode={{device.deviceState.colormode}},reachable={{device.deviceState.reachable}},XYList={{device.deviceState.xy}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.inactive}}</td>

View File

@@ -80,7 +80,7 @@
ng-click="toggleSelection(haldevice.haldevicename)">
{{haldevice.haldevicename}}</td>
<td>{{haldevice.haldevicetype}}</td>
<td>{{haldevice.halname}}</td>
<td>{{haldevice.haladdress.name}}</td>
<td>
<select name="button-on" id="button-on" ng-model="button_on">
<option ng-repeat="aButtonOn in haldevice.buttons.DeviceElements"

View File

@@ -41,6 +41,7 @@
'Add Bridge Device' to finish that selection setup. The 'Already
Configured Harmony Buttons' list below will show what is already
setup for your Harmony Hubs.</p>
<p>Note: The press time areas are for adding a press time in milliseconds to handle various long press scenarios. If left alone it will default to the original 200ms.</p>
<scrollable-table watch="bridge.harmonydevices">
<table class="table table-bordered table-striped table-hover">
@@ -51,7 +52,9 @@
<th sortable-header col="id" comparator-fn="comparatorNumber">Id</th>
<th sortable-header col="hub" comparator-fn="comparatorHub">Hub</th>
<th>On Button</th>
<th>On Press Time</th>
<th>Off Button</th>
<th>Off Press Time</th>
<th>Build Actions</th>
</tr>
</thead>
@@ -68,6 +71,10 @@
value="{{funcon.action}}">{{funcon.label}}</option>
</optgroup>
</select></td>
<td>
<textarea class="form-control" id="onpresstime"
ng-model="onpresstime" placeholder="On Press Time in ms"></textarea>
</td>
<td><select name="device-ctrloff" id="device-ctrloff"
ng-model="devicectrloff">
<optgroup ng-repeat="ctrloff in harmonydevice.device.controlGroup"
@@ -76,9 +83,13 @@
value="{{funcoff.action}}">{{funcoff.label}}</option>
</optgroup>
</select></td>
<td>
<textarea class="form-control" id="offpresstime"
ng-model="offpresstime" placeholder="Off Press Time in ms"></textarea>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Build
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff, onpresstime, offpresstime)">Build
A Button</button>
</td>
</tr>

View File

@@ -45,6 +45,8 @@
<th sortable-header col="ip">IP</th>
<th>Topic</th>
<th>Content</th>
<th>QoS</th>
<th>Retain</th>
<th>Build Actions</th>
</tr>
</thead>
@@ -60,9 +62,17 @@
<textarea rows="2" class="form-control" id="mqtt-content"
ng-model="mqttcontent" placeholder="The MQTT Message Content"></textarea>
</td>
<td>
<input type="number" rows="1" class="form-control" id="mqtt-qos"
ng-model="mqttqos" placeholder="1"/>
</td>
<td>
<input type="checkbox" rows="1" class="form-control" id="mqtt-retain"
ng-model="mqttretain"/>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildMQTTPublish(mqttbroker, mqtttopic, mqttcontent)">Build
ng-click="buildMQTTPublish(mqttbroker, mqtttopic, mqttcontent, mqttqos, mqttretain)">Build
publish Message</button>
</td>
</tr>

View File

@@ -86,6 +86,12 @@
ng-model="bridge.settings.upnpconfigaddress"
placeholder="192.168.1.1"></td>
</tr>
<tr>
<td>Use UPNP Address Interface Only</td>
<td><input type="checkbox"
ng-model="bridge.settings.useupnpiface" ng-true-value=true
ng-false-value=false> {{bridge.settings.useupnpiface}}</td>
</tr>
<tr>
<td>Web Server IP Address</td>
<td><input id="bridge-settings-webaddress"
@@ -117,16 +123,20 @@
</tr>
</thead>
<tr ng-repeat="vera in bridge.settings.veraaddress.devices">
<td>{{vera.name}}</td>
<td>{{vera.ip}}</td>
<td><input id="bridge-settings-next-vera-name"
class="form-control" type="text" ng-model="vera.name"
placeholder="A Vera"></td>
<td><input id="bridge-settings-next-vera-ip"
class="form-control" type="text" ng-model="vera.ip"
placeholder="192.168.1.2"></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeVeratoSettings(vera.name, vera.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-vera-name"
<td><input id="bridge-settings-new-vera-name"
class="form-control" type="text" ng-model="newveraname"
placeholder="A Vera"></td>
<td><input id="bridge-settings-next-vera-ip"
<td><input id="bridge-settings-new-vera-ip"
class="form-control" type="text" ng-model="newveraip"
placeholder="192.168.1.2"></td>
<td><button class="btn btn-success" type="submit"
@@ -181,30 +191,36 @@
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Webhook</th>
<th>IP</th>
<th>Webhook</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="harmony in bridge.settings.harmonyaddress.devices">
<td>{{harmony.name}}</td>
<td>{{harmony.ip}}</td>
<td>{{harmony.webhook}}</td>
<td><input id="bridge-settings-next-harmony-name"
class="form-control" type="text" ng-model="harmony.name"
placeholder="A Harmony"></td>
<td><input id="bridge-settings-next-harmony-ip"
class="form-control" type="text" ng-model="harmony.ip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-harmony-webhook"
class="form-control" type="text" ng-model="harmony.webhook"
placeholder="http://hook?a=${activity.label}"></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip, harmony.webhook)">Del</button></td>
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-harmony-name"
<td><input id="bridge-settings-new-harmony-name"
class="form-control" type="text" ng-model="newharmonyname"
placeholder="A Harmony"></td>
<td><input id="bridge-settings-next-harmony-ip"
class="form-control" type="text" ng-model="newharmonyip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-harmony-webhook"
class="form-control" type="text" ng-model="newharmonywebhook"
placeholder="http://hook?a=${activity.label}"></td>
<td><input id="bridge-settings-new-harmony-ip"
class="form-control" type="text" ng-model="newharmonyip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-new-harmony-webhook"
class="form-control" type="text" ng-model="newharmonywebhook"
placeholder="http://hook?a=${activity.label}"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip, newharmonywebhook)">Add</button></td>
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip, newharmonywebhook)">Add</button></td>
</tr>
</table></td>
</tr>
@@ -220,20 +236,24 @@
</tr>
</thead>
<tr ng-repeat="hue in bridge.settings.hueaddress.devices">
<td>{{hue.name}}</td>
<td>{{hue.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHuetoSettings(hue.name, hue.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-hue-name"
class="form-control" type="text" ng-model="newhuename"
class="form-control" type="text" ng-model="hue.name"
placeholder="A Hue"></td>
<td><input id="bridge-settings-next-hue-ip"
class="form-control" type="text" ng-model="hue.ip"
placeholder="192.168.1.3"></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHuetoSettings(hue.name, hue.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-new-hue-name"
class="form-control" type="text" ng-model="newhuename"
placeholder="A Hue"></td>
<td><input id="bridge-settings-new-hue-ip"
class="form-control" type="text" ng-model="newhueip"
placeholder="192.168.1.3"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHuetoSettings(newhuename, newhueip)">Add</button></td>
ng-click="addHuetoSettings(newhuename, newhueip)">Add</button></td>
</tr>
</table></td>
</tr>
@@ -245,29 +265,47 @@
<tr>
<th>Name</th>
<th>IP</th>
<th>Token</th>
<th>Use SSL</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="hal in bridge.settings.haladdress.devices">
<td>{{hal.name}}</td>
<td>{{hal.ip}}</td>
<td><input id="bridge-settings-next-hal-name"
class="form-control" type="text" ng-model="hal.name"
placeholder="A Hal"></td>
<td><input id="bridge-settings-next-hal-ip"
class="form-control" type="text" ng-model="hal.ip"
placeholder="192.168.1.3:82"></td>
<td><input id="bridge-settings-next-haltoken" class="form-control"
type="password" ng-model="hal.password"
placeholder="thetoken"></td>
<td><input type="checkbox"
ng-model="hal.secure" ng-true-value=true
ng-false-value=false></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHaltoSettings(hal.name, hal.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-hal-name"
<td><input id="bridge-settings-new-hal-name"
class="form-control" type="text" ng-model="newhalname"
placeholder="A Hal"></td>
<td><input id="bridge-settings-next-hal-ip"
<td><input id="bridge-settings-new-hal-ip"
class="form-control" type="text" ng-model="newhalip"
placeholder="192.168.1.3:82"></td>
<td><input id="bridge-settings-new-haltoken" class="form-control"
type="password" ng-model="newhaltoken"
placeholder="thetoken"></td>
<td><input type="checkbox"
ng-model="newhalsecure" ng-true-value=true
ng-false-value=false></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHaltoSettings(newhalname, newhalip)">Add</button></td>
ng-click="addHaltoSettings(newhalname, newhalip, newhalsecure, newhaltoken)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>HAL Token</td>
<td>HAL Token (please use token on individual HAL entry)</td>
<td><input id="bridge-settings-haltoken" class="form-control"
type="password" ng-model="bridge.settings.haltoken"
placeholder="thetoken"></td>
@@ -286,26 +324,32 @@
</tr>
</thead>
<tr ng-repeat="mqtt in bridge.settings.mqttaddress.devices">
<td>{{mqtt.name}}</td>
<td>{{mqtt.ip}}</td>
<td>{{mqtt.username}}</td>
<td ng-if="mqtt.password">*******</td>
<td ng-if="!mqtt.password"> </td>
<td><input id="bridge-settings-next-mqtt-name"
class="form-control" type="text" ng-model="mqtt.name"
placeholder="A MQTT Client ID"></td>
<td><input id="bridge-settings-next-mqtt-ip"
class="form-control" type="text" ng-model="mqtt.ip"
placeholder="MQTT Broker IP and port"></td>
<td><input id="bridge-settings-next-mqtt-username"
class="form-control" type="text" ng-model="mqtt.username"
placeholder="MQTT Broker username (optional)"></td>
<td><input id="bridge-settings-next-mqtt-password"
class="form-control" type="password" ng-model="mqtt.password"
placeholder="MQTT Broker password (opt)"></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeMQTTtoSettings(mqtt.name, mqtt.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-mqtt-name"
<td><input id="bridge-settings-new-mqtt-name"
class="form-control" type="text" ng-model="newmqttname"
placeholder="A MQTT Client ID"></td>
<td><input id="bridge-settings-next-mqtt-ip"
<td><input id="bridge-settings-new-mqtt-ip"
class="form-control" type="text" ng-model="newmqttip"
placeholder="MQTT Broker IP and port"></td>
<td><input id="bridge-settings-next-mqtt-username"
<td><input id="bridge-settings-new-mqtt-username"
class="form-control" type="text" ng-model="newmqttusername"
placeholder="MQTT Broker username (optional)"></td>
<td><input id="bridge-settings-next-mqtt-password"
<td><input id="bridge-settings-new-mqtt-password"
class="form-control" type="password" ng-model="newmqttpassword"
placeholder="MQTT Broker password (opt)"></td>
<td><button class="btn btn-success" type="submit"
@@ -328,26 +372,35 @@
</tr>
</thead>
<tr ng-repeat="hass in bridge.settings.hassaddress.devices">
<td>{{hass.name}}</td>
<td>{{hass.ip}}</td>
<td>{{hass.port}}</td>
<td ng-if="hass.password">*******</td>
<td ng-if="!hass.password"> </td>
<td>{{hass.secure}}</td>
<td><input id="bridge-settings-next-hass-name"
class="form-control" type="text" ng-model="hass.name"
placeholder="A HomeAssistant"></td>
<td><input id="bridge-settings-next-hass-ip"
class="form-control" type="text" ng-model="hass.ip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-hass-port"
class="form-control" type="text" ng-model="hass.port"
placeholder="8123"></td>
<td><input id="bridge-settings-next-hass-password"
class="form-control" type="password" ng-model="hass.password"
placeholder="Home Assistant password (opt)"></td>
<td><input type="checkbox"
ng-model="hass.secure" ng-true-value=true
ng-false-value=false></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHasstoSettings(hass.name, hass.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-hass-name"
<td><input id="bridge-settings-new-hass-name"
class="form-control" type="text" ng-model="newhassname"
placeholder="A HomeAssistant"></td>
<td><input id="bridge-settings-next-hass-ip"
<td><input id="bridge-settings-new-hass-ip"
class="form-control" type="text" ng-model="newhassip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-hass-port"
<td><input id="bridge-settings-new-hass-port"
class="form-control" type="text" ng-model="newhassport"
placeholder="8123"></td>
<td><input id="bridge-settings-next-hass-password"
<td><input id="bridge-settings-new-hass-password"
class="form-control" type="password" ng-model="newhasspassword"
placeholder="Home Assistant password (opt)"></td>
<td><input type="checkbox"
@@ -373,29 +426,38 @@
</tr>
</thead>
<tr ng-repeat="domoticz in bridge.settings.domoticzaddress.devices">
<td>{{domoticz.name}}</td>
<td>{{domoticz.ip}}</td>
<td>{{domoticz.port}}</td>
<td>{{domoticz.username}}</td>
<td ng-if="domoticz.password">*******</td>
<td ng-if="!domoticz.password"> </td>
<td><input id="bridge-settings-next-domoticz-name"
class="form-control" type="text" ng-model="domoticz.name"
placeholder="A Domoticz"></td>
<td><input id="bridge-settings-next-domoticz-ip"
class="form-control" type="text" ng-model="domoticz.ip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-domoticz-port"
class="form-control" type="text" ng-model="domoticz.port"
placeholder="8080"></td>
<td><input id="bridge-settings-next-domoticz-username"
class="form-control" type="text" ng-model="domoticz.username"
placeholder="Domoticz username"></td>
<td><input id="bridge-settings-next-domoticz-password"
class="form-control" type="password" ng-model="domoticz.password"
placeholder="Domoticz password (opt)"></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeDomoticztoSettings(domoticz.name, domoticz.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-domoticz-name"
<td><input id="bridge-settings-new-domoticz-name"
class="form-control" type="text" ng-model="newdomoticzname"
placeholder="A Domoticz"></td>
<td><input id="bridge-settings-next-domoticz-ip"
<td><input id="bridge-settings-new-domoticz-ip"
class="form-control" type="text" ng-model="newdomoticzip"
placeholder="192.168.1.3"></td>
<td><input id="bridge-settings-next-domoticz-port"
<td><input id="bridge-settings-new-domoticz-port"
class="form-control" type="text" ng-model="newdomoticzport"
placeholder="8080"></td>
<td><input id="bridge-settings-next-domoticz-username"
<td><input id="bridge-settings-new-domoticz-username"
class="form-control" type="text" ng-model="newdomoticzusername"
placeholder="Domoticz username"></td>
<td><input id="bridge-settings-next-domoticz-password"
<td><input id="bridge-settings-new-domoticz-password"
class="form-control" type="password" ng-model="newdomoticzpassword"
placeholder="Domoticz password (opt)"></td>
<td><button class="btn btn-success" type="submit"
@@ -417,25 +479,32 @@
</tr>
</thead>
<tr ng-repeat="somfy in bridge.settings.somfyaddress.devices">
<td>{{somfy.name}}</td>
<td>{{somfy.ip}}</td>
<td>{{somfy.username}}</td>
<td ng-if="somfy.password">*******</td>
<td ng-if="!somfy.password"> </td>
<td><input id="bridge-settings-next-somfy-name"
class="form-control" type="text" ng-model="somfy.name"
placeholder="A Somfy"></td>
<td><input id="bridge-settings-next-somfy-ip"
class="form-control" type="text" ng-model="somfy.ip"
placeholder="https://www.tahomalink.com"></td>
<td><input id="bridge-settings-next-somfy-username"
class="form-control" type="text" ng-model="somfy.username"
placeholder="Somfy username"></td>
<td><input id="bridge-settings-next-somfy-password"
class="form-control" type="password" ng-model="somfy.password"
placeholder="Somfy password"></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeSomfytoSettings(somfy.name, somfy.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-somfy-name"
<td><input id="bridge-settings-new-somfy-name"
class="form-control" type="text" ng-model="newsomfyname"
placeholder="A Somfy"></td>
<td><input id="bridge-settings-next-somfy-ip"
<td><input id="bridge-settings-new-somfy-ip"
class="form-control" type="text" ng-model="newsomfyip"
placeholder="https://www.tahomalink.com"></td>
<td><input id="bridge-settings-next-somfy-username"
<td><input id="bridge-settings-new-somfy-username"
class="form-control" type="text" ng-model="newsomfyusername"
placeholder="Somfy username"></td>
<td><input id="bridge-settings-next-somfy-password"
<td><input id="bridge-settings-new-somfy-password"
class="form-control" type="password" ng-model="newsomfypassword"
placeholder="Somfy password"></td>
<td><button class="btn btn-success" type="submit"