Compare commits

...

15 Commits

Author SHA1 Message Date
BWS Systems
e14482e232 Merge pull request #248 from bwssytems/postV3.2.2NewConnectors
Post v3.2.2 new connectors
2016-11-18 15:17:36 -06:00
Admin
08166c0ebf Finished MQTT support and some testing. Updated harmony-java-client to
v1.1.1 to fix authentication error.
2016-11-18 15:14:58 -06:00
BWS Systems
373515e1ed Clarify Software Alexa Statement
Thanks to digiltd for helping write the clarification.

Fixes #245
2016-11-17 09:52:50 -06:00
Admin
68f38e1d95 MQTT publish implementation, still without password. 2016-11-16 16:58:11 -06:00
Admin
90f2bce282 Fixed requester filter for multiple echos. Started MQTT MEssage screen. 2016-11-14 16:43:33 -06:00
Admin
3eba9b9245 Basic MQTT configuration implementation. Still needs username/pwd and
MQTT screen helper.
2016-11-11 16:42:20 -06:00
Admin
7d39b79e05 Added Requester logic filtering. Added logic for count and delay in
items and buttons.
2016-11-11 14:38:37 -06:00
BWS Systems
407b0e0bd5 Add declaration that software Alexa does not work with local hue bridges 2016-11-09 16:13:18 -06:00
BWS Systems
9baff8d403 Another update 2016-11-08 15:55:50 -06:00
BWS Systems
23a6c7059c Make directory in docs consistent
Fixes #226
2016-11-08 15:54:20 -06:00
Admin
84fb79f9d9 Fixed issue with "Test Dim" button not presenting the value selector
when there was a body with a dim command. Added ability to specifically
set the web server address port.

Fixes #225
Fixes #217
2016-11-08 11:27:43 -06:00
BWS Systems
3702de8efd Merge pull request #222 from TheOriginalAndrobot/master
Update documentation with Google Home info
2016-11-05 11:33:44 -05:00
Androbot
f0ab9afd66 Update documentation with Google Home info 2016-11-05 08:48:35 -07:00
Admin
e5c4e09543 Update readme for version. 2016-11-02 16:33:57 -05:00
Admin
425ac9fb91 Updated upnp response handling to warn on failure and not stop. Added
configuration of echo url for those overseas. Fixed overwriting device
issue when adding a manual device add or after a restart. Added default
IpV4 handling with override for ha-bridge. Fixed Dim URL http body
handling on PUT and POST. Added documentation for Kodi Volume handling.

Fixes #207
Fixes #202
Fixes #201
Fixes #198
Fixes #194
Fixes #193
2016-11-02 16:32:57 -05:00
35 changed files with 1189 additions and 179 deletions

107
README.md
View File

@@ -1,5 +1,7 @@
# ha-bridge
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo. The Bridge handles basic commands such as "On", "Off" and "brightness" commands of the hue protocol. This bridge can control most devices that have a distinct API.
Emulates Philips Hue api to other home automation gateways such as an Amazon Echo or Google Home. 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.
**ATTENTION: This requires a physical Amazon Echo, Dot or Tap and does not work with prototype devices built using the Alexa Voice Service e.g. Amazon's Alexa AVS Sample App and Sam Machin's AlexaPi. The AVS version does not have any capability for Hue Bridge discovery!**
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 and the ability to proxy all of your real Hue bridges behind this bridge.
@@ -21,15 +23,19 @@ Then locate the jar and start the server with:
ATTENTION: This requires JDK 1.8 to run
```
java -jar ha-bridge-3.2.0.jar
java -jar ha-bridge-3.2.2.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-3.2.2.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/v3.2.2/ha-bridge-3.2.2.jar
```
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
*NOTE ON RC.LOCAL*: Due to the way network subsystem is brought up on the pi, it uses the new systemctl to start services. The old style runlevel setup, which rc.local is part of does not get the benefit of knowing if the network has been fully realized. Starting ha-bridge from rc.local on next gen systems will cause unexpected results and issues with discovering registered devices.
```
[Unit]
Description=HA Bridge
@@ -38,20 +44,15 @@ After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/amazon-echo/data/habridge.config /home/pi/amazon-echo/ha-bridge-3.2.0.jar
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-3.2.2.jar
[Install]
WantedBy=multi-user.target
```
Basic script setup to run the bridge on a pi.
Create the directory and make sure that ha-bridge-3.2.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/v3.2.0/ha-bridge-3.2.0.jar
```
*NOTE ON RC.LOCAL*: Due to the way network subsystem is brought up on the pi, it uses the new systemctl to start services. The old style runlevel setup, which rc.local is part of does not get the benefit of knowing if the network has been fully realized. Starting ha-bridge from rc.local on next gen systems will cause unexpected results and issues with discovering registered devices.
Edit the shell script for starting:
```
pi@raspberrypi:~/habridge $ nano starthabridge.sh
@@ -60,7 +61,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 /home/pi/habridge/ha-bridge-3.2.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-3.2.2.jar > /home/pi/habridge/habridge-log.txt 2>&1 &
chmod 777 /home/pi/habridge/habridge-log.txt
```
Exit and save the file with ctrl-X and follow the prompts and then execute on the command line:
@@ -87,6 +88,7 @@ The default port number for the bridge is 80. To override what the default or wh
```
java -jar -Dserver.port=80 ha-bridge-W.X.Y.jar
```
Note: if using with a Google Home device, port 80 *must* be used.
## HA Bridge Usage and Configuration
This section will cover the basics of configuration and where this configuration can be done. This requires that you have started your bridge process and then have pointed your
@@ -107,6 +109,8 @@ The default location for the configuration file to contain the settings for the
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.
#### 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
The server defaults to running on port 80. To override what the default is, specify a different number. ATTENTION: If you want to use any of the apps made for the Hue to control this bridge, you should keep this port set to 80.
#### UPNP Response Port
@@ -272,6 +276,39 @@ DIM Commands |
To see what Alexa thinks you said, you can check in the home page for your Alexa.
To view or remove devices that Alexa knows about, you can use the mobile app `Menu / Settings / Connected Home` or go to http://echo.amazon.com/#cards.
## Google Assistant
Google Home is supported as of v3.2.0 and forward, but only if the bridge is running on port 80.
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).
Click on `Philips Hue` under the `Add new` section. If ha-bridge is on the same network as the
phone as well as the Home device, then the app should quickly pass through the pairing step and
populate with all of the devices. If instead it takes you to a Philips Hue login page, this means
that the bridge was not properly discovered.
Then you can say "OK Google, Turn on the office light" or whatever name you have given your configured devices.
The Google Assistant can also group lights into rooms as described in the main [help article](https://support.google.com/googlehome/answer/7072090?hl=en&ref_topic=7029100).
Here is the table of items to use to tell Google what you want to do. Note that either "OK Google"
or "Hey Google" can be used as a trigger.
To do this: | Say "Hey Google", then...
------------|--------------------------
To turn on/off a light | "Turn on <light name>"
Dim a light | "Dim the <light name>"
Brighten a light | "Brighten the <light name>"
Set a light brightness to a certain percentage | "Set <light name> to 50%"
Dim/Brighten lights by a certain percentage | "Dim/Brighten <light name> by 50%"
Turn on/off all lights in room | “Turn on/off lights in <room name>"
Turn on/off all lights | “Turn on/off all of the lightsâ€<C3A2>
To see what Home thinks you said, you can ask "Hey Google, What did I say?" or check the history in the app.
New or removed devices are picked up automatically as soon as they are added/removed from ha-bridge.
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:
```
@@ -932,17 +969,45 @@ ST: upnp:rootdevice\r\n
ST: ssdp:all\r\n
```
If this criteria is met, the following response is provided to the calling application:
If this criteria is met, the following three responses are provided to the calling application:
```
HTTP/1.1 200 OK\r\n
CACHE-CONTROL: max-age=86400\r\n
EXT:\r\n
LOCATION: http://192.168.1.1:80/description.xml\r\n
SERVER: FreeRTOS/7.4.2 UPnP/1.0 IpBridge/1.10.0\r\n
ST: urn:schemas-upnp-org:device:basic:1\r\n
"USN: uuid:2f402f80-da50-11e1-9b23-001788102201::urn:schemas-upnp-org:device:basic:1\r\n\r\n
HTTP/1.1 200 OK
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
EXT:
LOCATION: http://192.168.1.1:80/description.xml
SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.15.0
hue-bridgeid: 001E06FFFE123456
ST: upnp:rootdevice
USN: uuid:2f402f80-da50-11e1-9b23-001e06123456::upnp:rootdevice
```
```
HTTP/1.1 200 OK
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
EXT:
LOCATION: http://192.168.1.1:80/description.xml
SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.15.0
hue-bridgeid: 001E06FFFE123456
ST: uuid:2f402f80-da50-11e1-9b23-001e06123456
USN: uuid:2f402f80-da50-11e1-9b23-001e06123456
```
```
HTTP/1.1 200 OK
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age=100
EXT:
LOCATION: http://192.168.1.1:80/description.xml
SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.15.0
hue-bridgeid: 001E06FFFE123456
ST: urn:schemas-upnp-org:device:basic:1
USN: uuid:2f402f80-da50-11e1-9b23-001e06123456
```
Note that `192.168.1.1` and `12345` are replaced with the actual IP address and last 6 digits of the MAC address, respectively.
### UPNP description service
The bridge provides the description service which is used by the calling app to interogate access details after it has decided the upnp multicast response is the correct device.
#### Get Description

160
kodivolume.md Normal file
View File

@@ -0,0 +1,160 @@
# Kodi volume control using dim commands & JSON-RPC
You can use HA Bridge to adjust the Kodi software volume output. This allows you to use dim commands and set the volume level with percentage values.
### What is JSON-RPC?
The short answer is JSON-RPC an interface to communicate with Kodi. [See the official Kodi Wiki for more info](http://kodi.wiki/view/JSON-RPC_API)
### Setup Kodi to allow control through JSON-RPC
In Kodi navigate to Settings/Services/Control ([screenshot](http://kodi.wiki/view/Settings/Services/Control))
Turn **ON** the following:
- Allow control of Kodi via HTTP
- Allow remote control from applications on this system
- Allow remote control from applications on other systems
Change the **username** to something unique and set a strong **password**.
Make a note of the **PORT**
### Adding the device to HA Bridge
Access the HA Bridge Configuration in your browser and open the **Manual Add** tab.
#### Name
Give the device a unique name that doesn<73>t include **<EFBFBD>volume<EFBFBD>** as it will cause conflicts with the Echo<68>s built in volume controls. A device name of **<EFBFBD>cody sound<6E>** works well.
#### Device type
Select **TCP** in the dropdown
### URLs
This section might seem a little long winded and if you know what you are doing then feel free to jump ahead.
#### Building the URL
We need to log into the Kodi web server without having to fill in the popup each time. You can do this by putting the username and password in the URL. It is not a good idea to do this on other websites as it does put your password in clear view, but for your local network it is fine.
Use the example below replacing the relevant sections with the details that you defined in the Kodi settings screen in the first step. Replacing the IP with the address of the machine that Kodi is running on.
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc
```
##### Testing the URL in a browser
Before you continue, open your custom URL in a browser (making sure Kodi is running). If all is working as it should you will see a big page of JSON that starts with:
``` json
{
"description": "JSON-RPC API of XBMC",
"id": "http://xbmc.org/jsonrpc/ServiceDescription.json",
```
If you don<6F>t see something that looks like the code above, then go back and double check your settings.
#### The JSON request
The URL is what connects you to Kodi, JSON is what is used to communicate with it. The JSON that is used to set the volume level is:
``` json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
##### Joining the URL and JSON
Join the two together by adding `?request=` to the end of the URL and then add the JSON to the end of the request. You will end up with something like:
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
##### Testing the request in a browser
Go ahead and test the combined URL/JSON in a browser changing **100** to whatever level you want to set. Kodi should adjust the volume accordingly, try a few different levels to be sure it is working correctly.
The browser will reformat the URL each time you press return so don<6F>t build the URL in the browser bar without making a copy first.
Ideally build the URL in a text document which you can easily edit and then copy/paste each time.
#### Prepare the three URLs
You want to end up with three full URLs in your text file, one for each of the commands.
**ON**
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
**DIM**
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":45},"id":1}
```
**OFF**
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":0},"id":1}
```
#### Encoding the JSON part
You should now be able to control the volume of Kodi using the structured URL you built above in a browser. If you can<61>t get it to work in a browser then you won<6F>t be able to get it to work in HA Bridge.
In order for HA Bridge to send the JSON request you need to encode the URL. This means we take this:
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request={"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
And turn it into this:
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Application.SetVolume%22%2C%22params%22%3A%7B%22volume%22%3A100%7D%2C%22id%22%3A1%7D
```
Note that we are only encoding the JSON, the URL part we leave as is.
Using the online tool [www.url-encode-decode.com](http://www.url-encode-decode.com/), copy the JSON part (shown in bold below) and paste it into the left hand field.
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=**{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}**
Click the **Encode url** button.
Copy the output from the right hand box and paste it on a new line in your text file.
```
%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Application.SetVolume%22%2C%22params%22%3A%7B%22volume%22%3A100%7D%2C%22id%22%3A1%7D
```
#### Join the URL and JSON
Finally you want to insert the URL part before the request (shown in bold below)
**http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=**%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Application.SetVolume%22%2C%22params%22%3A%7B%22volume%22%3A100%7D%2C%22id%22%3A1%7D
### HA Bridge
Paste the final URL in **On URL** field. Just do the one for now, add the device and in the Bridge Control tab click Save.
Test the button in the Bridge Devices tab and hopefully it should turn the volume up in Kodi.
Go ahead and repeat the steps for the **Off URL**. All the same steps but change 100% to 0%. If you want to use the previous URL you can, just find the 100% in the encoded URL.
`<EFBFBD>%22%3A%7B%22volume%22%3A`**100**`%7D%2C%22id%22%3A1%7D`
and change it to 0
`<EFBFBD>%22%3A%7B%22volume%22%3A`**0**`%7D%2C%22id%22%3A1%7D`
#### Dim JSON
The Dim URL uses the `${intensity.percent}` to take the given number from your voice command and pass it to Kodi.
Here is the JSON to <20>Dim<69> the volume
``` json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":"${intensity.percent}""},"id":1}
```
You don<6F>t need to encode the `${intensity.percent}` part. This means you can simply replace the number value (100/0) with `${intensity.percent}` as shown below.
`<EFBFBD>%22%3A%7B%22volume%22%3A`**${intensity.percent}**`%7D%2C%22id%22%3A1%7D`
### Controlling the Device
You can use the commands as listed in the [README](https://github.com/bwssytems/ha-bridge#ask-alexa)
<EFBFBD>Set Cody Sound to 50 percent<6E>
<EFBFBD>Cody Sound to 70 percent<6E>
Remembering that <20>Turn on Cody Sound<6E> will set the volume to 100%, and <20>Turn off Cody Sound<6E> will mute.

13
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>3.2.0</version>
<version>3.5.0</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
@@ -22,13 +22,17 @@
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>Eclipse Paho Repo</id>
<url>https://repo.eclipse.org/content/repositories/paho-releases/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>harmony-java-client</artifactId>
<version>1.0.8</version>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -106,6 +110,11 @@
<artifactId>smack-core</artifactId>
<version>4.0.7</version>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.1.0</version>
</dependency>
</dependencies>
<build>

View File

@@ -31,6 +31,11 @@ public class BridgeSettings extends BackupHandler {
super();
bridgeControl = new BridgeControlDescriptor();
theBridgeSettings = new BridgeSettingsDescriptor();
String ipV6Stack = System.getProperty("ipV6Stack");
if(ipV6Stack == null || !ipV6Stack.equalsIgnoreCase("true")) {
System.setProperty("java.net.preferIPv4Stack" , "true");
}
}
public BridgeControlDescriptor getBridgeControl() {
return bridgeControl;
@@ -42,7 +47,6 @@ public class BridgeSettings extends BackupHandler {
String addressString = null;
String theVeraAddress = null;
String theHarmonyAddress = null;
String configFileProperty = System.getProperty("config.file");
if(configFileProperty == null) {
Path filePath = Paths.get(Configuration.CONFIG_FILE);
@@ -98,8 +102,6 @@ public class BridgeSettings extends BackupHandler {
}
}
theBridgeSettings.setHarmonyAddress(theHarmonyList);
theBridgeSettings.setHarmonyUser(System.getProperty("harmony.user"));
theBridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd"));
theBridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
theBridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
theBridgeSettings.setButtonsleep(Integer.parseInt(System.getProperty("button.sleep", Configuration.DEFAULT_BUTTON_SLEEP)));
@@ -148,6 +150,7 @@ public class BridgeSettings extends BackupHandler {
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
theBridgeSettings.setHalconfigured(theBridgeSettings.isValidHal());
theBridgeSettings.setMqttconfigured(theBridgeSettings.isValidMQTT());
if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride);
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");

View File

@@ -12,8 +12,6 @@ public class BridgeSettingsDescriptor {
private String upnpdevicedb;
private IpList veraaddress;
private IpList harmonyaddress;
private String harmonyuser;
private String harmonypwd;
private Integer buttonsleep;
private boolean upnpstrict;
private boolean traceupnp;
@@ -32,6 +30,10 @@ public class BridgeSettingsDescriptor {
private boolean halconfigured;
private Map<String, WhitelistEntry> whitelist;
private boolean settingsChanged;
private String myechourl;
private String webaddress;
private IpList mqttaddress;
private boolean mqttconfigured;
public BridgeSettingsDescriptor() {
super();
@@ -42,9 +44,12 @@ public class BridgeSettingsDescriptor {
this.harmonyconfigured = false;
this.hueconfigured = false;
this.halconfigured = false;
this.mqttconfigured = false;
this.farenheit = true;
this.whitelist = null;
this.settingsChanged = false;
this.myechourl = "echo.amazon.com/#cards";
this.webaddress = "0.0.0.0";
}
public String getUpnpConfigAddress() {
return upnpconfigaddress;
@@ -88,18 +93,6 @@ public class BridgeSettingsDescriptor {
public void setHarmonyAddress(IpList harmonyaddress) {
this.harmonyaddress = harmonyaddress;
}
public String getHarmonyUser() {
return harmonyuser;
}
public void setHarmonyUser(String harmonyuser) {
this.harmonyuser = harmonyuser;
}
public String getHarmonyPwd() {
return harmonypwd;
}
public void setHarmonyPwd(String harmonypwd) {
this.harmonypwd = harmonypwd;
}
public boolean isUpnpStrict() {
return upnpstrict;
}
@@ -208,6 +201,30 @@ public class BridgeSettingsDescriptor {
public void setSettingsChanged(boolean settingsChanged) {
this.settingsChanged = settingsChanged;
}
public String getMyechourl() {
return myechourl;
}
public void setMyechourl(String myechourl) {
this.myechourl = myechourl;
}
public String getWebaddress() {
return webaddress;
}
public void setWebaddress(String webaddress) {
this.webaddress = webaddress;
}
public IpList getMqttaddress() {
return mqttaddress;
}
public void setMqttaddress(IpList mqttaddress) {
this.mqttaddress = mqttaddress;
}
public boolean isMqttconfigured() {
return mqttconfigured;
}
public void setMqttconfigured(boolean mqttconfigured) {
this.mqttconfigured = mqttconfigured;
}
public Boolean isValidVera() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;
@@ -222,10 +239,6 @@ public class BridgeSettingsDescriptor {
List<NamedIP> devicesList = this.getHarmonyAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
if(this.getHarmonyPwd() == null || this.getHarmonyPwd().equals(""))
return false;
if(this.getHarmonyUser() == null || this.getHarmonyUser().equals(""))
return false;
return true;
}
public Boolean isValidNest() {
@@ -253,4 +266,12 @@ public class BridgeSettingsDescriptor {
return false;
return true;
}
public Boolean isValidMQTT() {
if(this.getMqttaddress() == null || this.getMqttaddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getMqttaddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
}

View File

@@ -13,6 +13,7 @@ import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.hal.HalHome;
import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.hue.HueHome;
import com.bwssystems.mqtt.MQTTHome;
import com.bwssystems.util.UDPDatagramSender;
public class HABridge {
@@ -39,6 +40,7 @@ public class HABridge {
NestHome nestHome;
HueHome hueHome;
HalHome halHome;
MQTTHome mqttHome;
HueMulator theHueMulator;
UDPDatagramSender udpSender;
UpnpSettingsResource theSettingResponder;
@@ -54,9 +56,9 @@ public class HABridge {
bridgeSettings = new BridgeSettings();
while(!bridgeSettings.getBridgeControl().isStop()) {
bridgeSettings.buildSettings();
log.info("HA Bridge (v" + theVersion.getVersion() + ") initializing....");
log.info("HA Bridge initializing....");
// sparkjava config directive to set ip address for the web server to listen on
// ipAddress("0.0.0.0"); // not used
ipAddress(bridgeSettings.getBridgeSettingsDescriptor().getWebaddress());
// sparkjava config directive to set port for the web server to listen on
port(bridgeSettings.getBridgeSettingsDescriptor().getServerPort());
// sparkjava config directive to set html static file location for Jetty
@@ -72,8 +74,10 @@ public class HABridge {
hueHome = new HueHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the hal configuration if available
halHome = new HalHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the mqtt handlers if available
mqttHome = new MQTTHome(bridgeSettings.getBridgeSettingsDescriptor());
// setup the class to handle the resource setup rest api
theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), harmonyHome, nestHome, hueHome, halHome);
theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), harmonyHome, nestHome, hueHome, halHome, mqttHome);
// setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
theSettingResponder.setupServer();
@@ -84,7 +88,7 @@ public class HABridge {
}
else {
// setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome, udpSender);
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome, mqttHome, udpSender);
theHueMulator.setupServer();
// wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization();
@@ -104,6 +108,8 @@ public class HABridge {
nestHome = null;
harmonyHome.shutdownHarmonyHubs();
harmonyHome = null;
mqttHome.shutdownMQTTClients();
mqttHome = null;
udpSender.closeResponseSocket();
}
log.info("HA Bridge (v" + theVersion.getVersion() + ") exiting....");

View File

@@ -4,6 +4,9 @@ public class NamedIP {
private String name;
private String ip;
private String port;
private String username;
private String password;
public String getName() {
return name;
}
@@ -22,4 +25,16 @@ public class NamedIP {
public void setPort(String port) {
this.port = port;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -2,6 +2,8 @@ package com.bwssystems.HABridge.api;
public class CallItem {
private String item;
private Integer count;
private Integer delay;
public String getItem() {
return item;
@@ -10,4 +12,20 @@ public class CallItem {
public void setItem(String anitem) {
item = anitem;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getDelay() {
return delay;
}
public void setDelay(Integer delay) {
this.delay = delay;
}
}

View File

@@ -3,7 +3,6 @@ package com.bwssystems.HABridge.api.hue;
import java.util.List;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.DeviceRepository;
public class GroupResponse {
private DeviceState action;

View File

@@ -56,6 +56,9 @@ public class DeviceDescriptor{
@SerializedName("contentBodyDim")
@Expose
private String contentBodyDim;
@SerializedName("requesterAddress")
@Expose
private String requesterAddress;
private DeviceState deviceState;
@@ -187,6 +190,14 @@ public class DeviceDescriptor{
this.contentBodyDim = contentBodyDim;
}
public String getRequesterAddress() {
return requesterAddress;
}
public void setRequesterAddress(String requesterAddress) {
this.requesterAddress = requesterAddress;
}
public DeviceState getDeviceState() {
if(deviceState == null)
deviceState = DeviceState.createDeviceState();

View File

@@ -33,7 +33,7 @@ public class DeviceRepository extends BackupHandler {
private Map<String, DeviceDescriptor> devices;
private Path repositoryPath;
private Gson gson;
private Integer maxId;
private Integer nextId;
private Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public DeviceRepository(String deviceDb) {
@@ -45,7 +45,7 @@ public class DeviceRepository extends BackupHandler {
repositoryPath = null;
repositoryPath = Paths.get(deviceDb);
setupParams(repositoryPath, ".bk", "device.db-");
maxId = 1;
nextId = 0;
_loadRepository(repositoryPath);
}
@@ -62,8 +62,8 @@ public class DeviceRepository extends BackupHandler {
DeviceDescriptor list[] = gson.fromJson(jsonContent, DeviceDescriptor[].class);
for(int i = 0; i < list.length; i++) {
put(list[i].getId(), list[i]);
if(Integer.decode(list[i].getId()) > maxId) {
maxId = Integer.decode(list[i].getId());
if(Integer.decode(list[i].getId()) > nextId) {
nextId = Integer.decode(list[i].getId());
}
}
}
@@ -73,12 +73,48 @@ public class DeviceRepository extends BackupHandler {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
return list;
}
public List<DeviceDescriptor> findByDeviceType(String aType) {
/*
public List<DeviceDescriptor> findAllByRequester(String anAddress) {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
return list;
List<DeviceDescriptor> theReturnList = new ArrayList<DeviceDescriptor>();
Iterator<DeviceDescriptor> anIterator = list.iterator();
DeviceDescriptor theDevice;
String theRequesterAddress;
while(anIterator.hasNext()) {
theDevice = anIterator.next();
theRequesterAddress = theDevice.getRequesterAddress();
if(theRequesterAddress == null || theRequesterAddress.length() == 0 || theRequesterAddress.contains(anAddress))
theReturnList.add(theDevice);
}
return theReturnList;
}
*/
public List<DeviceDescriptor> findAllByRequester(String anAddress) {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
List<DeviceDescriptor> theReturnList = new ArrayList<DeviceDescriptor>();
Iterator<DeviceDescriptor> anIterator = list.iterator();
DeviceDescriptor theDevice;
String theRequesterAddress;
HashMap<String,String > addressMap;
while (anIterator.hasNext()) {
theDevice = anIterator.next();
theRequesterAddress = theDevice.getRequesterAddress();
addressMap = new HashMap<String, String>();
if(theRequesterAddress != null) {
if (theRequesterAddress.contains(",")) {
String[] theArray = theRequesterAddress.split(",");
for (String v : theArray) {
addressMap.put(v.trim(), v.trim());
}
} else
addressMap.put(theRequesterAddress, theRequesterAddress);
}
if (theRequesterAddress == null || theRequesterAddress.length() == 0 || addressMap.containsKey(anAddress))
theReturnList.add(theDevice);
}
return theReturnList;
}
public DeviceDescriptor findOne(String id) {
return devices.get(id);
}
@@ -93,8 +129,8 @@ public class DeviceRepository extends BackupHandler {
if(descriptors[i].getId() != null && descriptors[i].getId().length() > 0)
devices.remove(descriptors[i].getId());
else {
descriptors[i].setId(String.valueOf(maxId));
maxId++;
nextId++;
descriptors[i].setId(String.valueOf(nextId));
}
if(descriptors[i].getUniqueid() == null || descriptors[i].getUniqueid().length() == 0) {
BigInteger bigInt = BigInteger.valueOf(Integer.decode(descriptors[i].getId()));
@@ -115,18 +151,18 @@ public class DeviceRepository extends BackupHandler {
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
Iterator<DeviceDescriptor> deviceIterator = list.iterator();
Map<String, DeviceDescriptor> newdevices = new HashMap<String, DeviceDescriptor>();;
maxId = 1;
nextId = 0;
log.debug("Renumber devices.");
while(deviceIterator.hasNext()) {
nextId++;
DeviceDescriptor theDevice = deviceIterator.next();
theDevice.setId(String.valueOf(maxId));
BigInteger bigInt = BigInteger.valueOf(maxId);
theDevice.setId(String.valueOf(nextId));
BigInteger bigInt = BigInteger.valueOf(nextId);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
theDevice.setUniqueid("00:17:88:5E:D3:" + hexValue + "-" + hexValue);
newdevices.put(theDevice.getId(), theDevice);
maxId++;
}
devices = newdevices;
String jsonValue = gson.toJson(findAll());

View File

@@ -8,7 +8,6 @@ import static spark.Spark.delete;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -27,6 +26,7 @@ import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.hue.HueHome;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene;
import com.bwssystems.mqtt.MQTTHome;
import com.bwssystems.util.JsonTransformer;
import com.bwssystems.vera.VeraHome;
import com.google.gson.Gson;
@@ -43,9 +43,10 @@ public class DeviceResource {
private NestHome nestHome;
private HueHome hueHome;
private HalHome halHome;
private MQTTHome mqttHome;
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, HalHome aHalHome) {
public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, HalHome aHalHome, MQTTHome aMqttHome) {
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
if(theSettings.isValidVera())
@@ -73,6 +74,11 @@ public class DeviceResource {
else
this.halHome = null;
if(theSettings.isValidMQTT())
this.mqttHome = aMqttHome;
else
this.mqttHome = null;
setupEndpoints();
}
@@ -281,6 +287,16 @@ public class DeviceResource {
return halHome.getDevices();
}, new JsonTransformer());
get (API_CONTEXT + "/mqtt/devices", "application/json", (request, response) -> {
log.debug("Get MQTT brokers");
if(mqttHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("A MQTT config is not available.");
}
response.status(HttpStatus.SC_OK);
return mqttHome.getBrokers();
}, new JsonTransformer());
// http://ip_address:port/api/devices/exec/renumber CORS request
options(API_CONTEXT + "/exec/renumber", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);

View File

@@ -25,6 +25,9 @@ import com.bwssystems.hue.HueDeviceIdentifier;
import com.bwssystems.hue.HueErrorStringSet;
import com.bwssystems.hue.HueHome;
import com.bwssystems.hue.HueUtil;
import com.bwssystems.mqtt.MQTTHandler;
import com.bwssystems.mqtt.MQTTHome;
import com.bwssystems.mqtt.MQTTMessage;
import com.bwssystems.nest.controller.Nest;
import com.bwssystems.util.JsonTransformer;
import com.bwssystems.util.UDPDatagramSender;
@@ -92,6 +95,7 @@ public class HueMulator implements HueErrorStringSet {
private HarmonyHome myHarmonyHome;
private Nest theNest;
private HueHome myHueHome;
private MQTTHome mqttHome;
private HttpClient httpClient;
private CloseableHttpClient httpclientSSL;
private SSLContext sslcontext;
@@ -104,7 +108,7 @@ public class HueMulator implements HueErrorStringSet {
private String errorString;
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, UDPDatagramSender aUdpDatagramSender) {
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome, MQTTHome aMqttHome, UDPDatagramSender aUdpDatagramSender) {
httpClient = HttpClients.createDefault();
// Trust own CA and all self-signed certs
sslcontext = SSLContexts.createDefault();
@@ -135,6 +139,10 @@ public class HueMulator implements HueErrorStringSet {
this.myHueHome = aHueHome;
else
this.myHueHome = null;
if(theBridgeSettings.isValidMQTT())
this.mqttHome = aMqttHome;
else
this.mqttHome = null;
bridgeSettings = theBridgeSettings;
theUDPDatagramSender = aUdpDatagramSender;
hueUser = null;
@@ -174,7 +182,7 @@ public class HueMulator implements HueErrorStringSet {
}
if(groupId.equalsIgnoreCase("0")) {
GroupResponse theResponse = GroupResponse.createGroupResponse(repository.findAll());
GroupResponse theResponse = GroupResponse.createGroupResponse(repository.findAllByRequester(request.ip()));
return new Gson().toJson(theResponse, GroupResponse.class);
}
@@ -271,7 +279,7 @@ public class HueMulator implements HueErrorStringSet {
return theErrorResp.getTheErrors();
}
List<DeviceDescriptor> deviceList = repository.findAll();
List<DeviceDescriptor> deviceList = repository.findAllByRequester(request.ip());
Map<String, DeviceResponse> deviceResponseMap = new HashMap<>();
for (DeviceDescriptor device : deviceList) {
DeviceResponse deviceResponse = null;
@@ -437,7 +445,7 @@ public class HueMulator implements HueErrorStringSet {
return theErrorResp.getTheErrors();
}
List<DeviceDescriptor> descriptorList = repository.findAll();
List<DeviceDescriptor> descriptorList = repository.findAllByRequester(request.ip());
HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(), bridgeSettings.getWhitelist());
Map<String, DeviceResponse> deviceList = new HashMap<>();
if (descriptorList != null) {
@@ -623,6 +631,7 @@ public class HueMulator implements HueErrorStringSet {
DeviceState state = null;
boolean stateHasBri = false;
boolean stateHasBriInc = false;
Integer theDelay = bridgeSettings.getButtonsleep();
log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body());
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json");
@@ -661,7 +670,6 @@ public class HueMulator implements HueErrorStringSet {
state = device.getDeviceState();
if(state == null)
state = DeviceState.createDeviceState();
state.fillIn();
theHeaders = new Gson().fromJson(device.getHeaders(), NameValue[].class);
@@ -782,11 +790,23 @@ public class HueMulator implements HueErrorStringSet {
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else {
Integer setCount = 1;
for(int i = 0; i < deviceButtons.length; i++) {
if( i > 0)
Thread.sleep(bridgeSettings.getButtonsleep());
log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i));
myHarmony.pressButton(deviceButtons[i]);
if(deviceButtons[i].getCount() != null && deviceButtons[i].getCount() > 0)
setCount = deviceButtons[i].getCount();
else
setCount = 1;
for(int x = 0; x < setCount; x++) {
if( x > 0 || i > 0) {
Thread.sleep(theDelay);
}
if(deviceButtons[i].getDelay() != null && deviceButtons[i].getDelay() > 0)
theDelay = deviceButtons[i].getDelay();
else
theDelay = bridgeSettings.getButtonsleep();
log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i) + " - count: " + String.valueOf(x));
myHarmony.pressButton(deviceButtons[i]);
}
}
}
}
@@ -843,6 +863,46 @@ public class HueMulator implements HueErrorStringSet {
}
}
}
else if((device.getMapType() != null && device.getMapType().equalsIgnoreCase("mqttMessage")))
{
log.debug("executing HUE api request to send message to MQTT broker: " + url);
if(mqttHome != null)
{
if(url.substring(0, 1).equalsIgnoreCase("{")) {
url = "[" + url +"]";
}
MQTTMessage[] mqttMessages = new Gson().fromJson(url, MQTTMessage[].class);
Integer setCount = 1;
for(int i = 0; i < mqttMessages.length; i++) {
MQTTHandler mqttHandler = mqttHome.getMQTTHandler(mqttMessages[i].getClientId());
if(mqttHandler == null)
{
log.warn("Should not get here, no mqtt hanlder available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no mqtt handler available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
if(mqttMessages[i].getCount() != null && mqttMessages[i].getCount() > 0)
setCount = mqttMessages[i].getCount();
else
setCount = 1;
for(int x = 0; x < setCount; x++) {
if( x > 0 || i > 0) {
Thread.sleep(theDelay);
}
if(mqttMessages[i].getDelay() != null &&mqttMessages[i].getDelay() > 0)
theDelay = mqttMessages[i].getDelay();
else
theDelay = bridgeSettings.getButtonsleep();
log.debug("publishing message: " + mqttMessages[i].getClientId() + " - " + mqttMessages[i].getTopic() + " - " + mqttMessages[i].getMessage() + " - iteration: " + String.valueOf(i) + " - count: " + String.valueOf(x));
mqttHandler.publishMessage(mqttMessages[i].getTopic(), mqttMessages[i].getMessage());
}
}
}
else {
log.warn("Should not get here, no mqtt brokers configured");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no mqtt brokers configured\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
}
else if(device.getDeviceType().startsWith("exec")) {
log.debug("Exec Request called with url: " + url);
if(!url.startsWith("[")) {
@@ -852,20 +912,31 @@ public class HueMulator implements HueErrorStringSet {
url = "[{\"item\":\"" + url +"\"}]";
}
CallItem[] callItems = new Gson().fromJson(url, CallItem[].class);
Integer setCount = 1;
for(int i = 0; i < callItems.length; i++) {
if( i > 0) {
Thread.sleep(bridgeSettings.getButtonsleep());
}
String intermediate;
if(callItems[i].getItem().contains("exec://"))
intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
else
intermediate = callItems[i].getItem();
String anError = doExecRequest(intermediate, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), lightId);
if(anError != null) {
responseString = anError;
i = callItems.length+1;
}
if(callItems[i].getCount() != null && callItems[i].getCount() > 0)
setCount = callItems[i].getCount();
else
setCount = 1;
for(int x = 0; x < setCount; x++) {
if( x > 0 || i > 0) {
Thread.sleep(theDelay);
}
if(callItems[i].getDelay() != null && callItems[i].getDelay() > 0)
theDelay = callItems[i].getDelay();
else
theDelay = bridgeSettings.getButtonsleep();
String intermediate;
if(callItems[i].getItem().contains("exec://"))
intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
else
intermediate = callItems[i].getItem();
String anError = doExecRequest(intermediate, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), lightId);
if(anError != null) {
responseString = anError;
i = callItems.length+1;
}
}
}
}
else // This section allows the usage of http/tcp/udp/exec calls in a given set of items
@@ -878,75 +949,88 @@ public class HueMulator implements HueErrorStringSet {
url = "[{\"item\":\"" + url +"\"}]";
}
CallItem[] callItems = new Gson().fromJson(url, CallItem[].class);
Integer setCount = 1;
for(int i = 0; i < callItems.length; i++) {
if( i > 0) {
Thread.sleep(bridgeSettings.getButtonsleep());
}
try {
if(callItems[i].getItem().contains("udp://") || callItems[i].getItem().contains("tcp://")) {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().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);
if(callItems[i].getCount() != null && callItems[i].getCount() > 0)
setCount = callItems[i].getCount();
else
setCount = 1;
for(int x = 0; x < setCount; x++) {
if( x > 0 || i > 0) {
Thread.sleep(theDelay);
}
if(callItems[i].getDelay() != null && callItems[i].getDelay() > 0)
theDelay = callItems[i].getDelay();
else
theDelay = bridgeSettings.getButtonsleep();
try {
if(callItems[i].getItem().contains("udp://") || callItems[i].getItem().contains("tcp://")) {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().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;
InetAddress IPAddress = InetAddress.getByName(hostAddr);;
if(theUrlBody.startsWith("0x")) {
theUrlBody = replaceIntensityValue(theUrlBody, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
}
else {
theUrlBody = replaceIntensityValue(theUrlBody, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
sendData = theUrlBody.getBytes();
}
if(callItems[i].getItem().contains("udp://")) {
log.debug("executing HUE api request to UDP: " + callItems[i].getItem());
theUDPDatagramSender.sendUDPResponse(new String(sendData), IPAddress, Integer.parseInt(port));
}
else if(callItems[i].getItem().contains("tcp://"))
{
log.debug("executing HUE api request to TCP: " + callItems[i].getItem());
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
dataSendSocket.close();
}
}
else
hostAddr = hostPortion;
InetAddress IPAddress = InetAddress.getByName(hostAddr);;
if(theUrlBody.startsWith("0x")) {
theUrlBody = replaceIntensityValue(theUrlBody, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
else if(callItems[i].getItem().contains("exec://")) {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
String anError = doExecRequest(intermediate, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), lightId);
if(anError != null) {
responseString = anError;
i = callItems.length+1;
}
}
else {
theUrlBody = replaceIntensityValue(theUrlBody, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
sendData = theUrlBody.getBytes();
log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + callItems[i].getItem());
String anUrl = replaceIntensityValue(callItems[i].getItem(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
String body;
if(stateHasBri || stateHasBriInc)
body = replaceIntensityValue(device.getContentBodyDim(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
else if (state.isOn())
body = replaceIntensityValue(device.getContentBody(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
else
body = replaceIntensityValue(device.getContentBodyOff(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
// make call
if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), body, theHeaders) == null) {
log.warn("Error on calling url to change device state: " + anUrl);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
}
if(callItems[i].getItem().contains("udp://")) {
log.debug("executing HUE api request to UDP: " + callItems[i].getItem());
theUDPDatagramSender.sendUDPResponse(new String(sendData), IPAddress, Integer.parseInt(port));
}
else if(callItems[i].getItem().contains("tcp://"))
{
log.debug("executing HUE api request to TCP: " + callItems[i].getItem());
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
dataSendSocket.close();
}
}
else if(callItems[i].getItem().contains("exec://")) {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
String anError = doExecRequest(intermediate, calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), lightId);
if(anError != null) {
responseString = anError;
i = callItems.length+1;
}
}
else {
log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + callItems[i].getItem());
String anUrl = replaceIntensityValue(callItems[i].getItem(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
String body;
if (state.isOn())
body = replaceIntensityValue(device.getContentBody(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
else
body = replaceIntensityValue(device.getContentBodyOff(), calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false);
// make call
if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), body, theHeaders) == null) {
log.warn("Error on calling url to change device state: " + anUrl);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
}
} catch (Exception e) {
log.warn("Change device state, Could not send data for network request: " + callItems[i].getItem() + " with Message: " + e.getMessage());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
} catch (Exception e) {
log.warn("Change device state, Could not send data for network request: " + callItems[i].getItem() + " with Message: " + e.getMessage());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
}
}
}

View File

@@ -126,12 +126,8 @@ public class UpnpListener {
try {
sendUpnpResponse(packet.getAddress(), packet.getPort());
} catch (IOException e) {
if(e.getMessage().equalsIgnoreCase("Host is down"))
log.warn("UpnpListener encountered an error sending upnp response packet as requesting host is now not available. IP: " + packet.getAddress().getHostAddress());
else {
log.error("UpnpListener encountered an error sending upnp response packet. Shutting down", e);
error = true;
}
log.warn("UpnpListener encountered an error sending upnp response packet. IP: " + packet.getAddress().getHostAddress() + " with message: " + e.getMessage());
log.debug("UpnpListener send upnp exception: ", e);
}
}
} catch (IOException e) {

View File

@@ -3,6 +3,8 @@ package com.bwssystems.harmony;
public class ButtonPress {
private String device;
private String button;
private Integer delay;
private Integer count;
public String getDevice() {
return device;
}
@@ -15,6 +17,18 @@ public class ButtonPress {
public void setButton(String button) {
this.button = button;
}
public Integer getDelay() {
return delay;
}
public void setDelay(Integer delay) {
this.delay = delay;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Boolean isValid() {
if (device != null && !device.isEmpty()){
if (button != null && !button.isEmpty())

View File

@@ -75,7 +75,7 @@ public class HarmonyServer {
log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel()));
}
});
harmonyClient.connect(myNameAndIP.getIp(), mySettings.getHarmonyUser(), mySettings.getHarmonyPwd());
harmonyClient.connect(myNameAndIP.getIp());
}
myHarmony = new HarmonyHandler(harmonyClient, noopCalls, devResponse);
}

View File

@@ -0,0 +1,30 @@
package com.bwssystems.mqtt;
import com.bwssystems.HABridge.NamedIP;
public class MQTTBroker {
private String clientId;
private String ip;
public MQTTBroker(NamedIP brokerConfig) {
super();
this.setIp(brokerConfig.getIp());
this.setClientId(brokerConfig.getName());
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
}

View File

@@ -0,0 +1,71 @@
package com.bwssystems.mqtt;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
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();
log.info("Setting up handler for name: " + aConfig.getName());
MemoryPersistence persistence = new MemoryPersistence();
myConfig = aConfig;
try {
myClient = new MqttClient("tcp://" + myConfig.getIp(), myConfig.getName(), persistence);
} catch (MqttException e) {
log.error("Could not create MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp() + " with message: " + e.getMessage());
}
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
if(aConfig.getUsername() != null && aConfig.getUsername().trim().length() > 0) {
if(aConfig.getPassword() != null && aConfig.getPassword().trim().length() > 0) {
connOpts.setUserName(aConfig.getUsername().trim());
connOpts.setPassword(aConfig.getPassword().trim().toCharArray());
}
}
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) {
MqttMessage message = new MqttMessage(content.getBytes());
message.setQos(qos);
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());
}
}
public NamedIP getMyConfig() {
return myConfig;
}
public void shutdown() {
try {
myClient.disconnect();
} catch (MqttException e) {
log.warn("Could not disconnect MQTT client for name: " + myConfig.getName() + " and ip: " + myConfig.getIp());
}
}
}

View File

@@ -0,0 +1,77 @@
package com.bwssystems.mqtt;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
public class MQTTHome {
private static final Logger log = LoggerFactory.getLogger(MQTTHome.class);
private Map<String, MQTTHandler> handlers;
private Boolean validMqtt;
public MQTTHome(BridgeSettingsDescriptor bridgeSettings) {
super();
validMqtt = bridgeSettings.isValidMQTT();
if(!validMqtt)
return;
handlers = new HashMap<String, MQTTHandler>();
Iterator<NamedIP> theList = bridgeSettings.getMqttaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aClientConfig = theList.next();
MQTTHandler aHandler = new MQTTHandler(aClientConfig);
if(aHandler != null)
handlers.put(aClientConfig.getName(), aHandler);
}
}
public void shutdownMQTTClients() {
if(!validMqtt)
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();
handlers.get(key).shutdown();
}
}
}
public MQTTHandler getMQTTHandler(String aName) {
if(!validMqtt)
return null;
MQTTHandler aHandler;
if(aName == null || aName.equals("")) {
aHandler = null;
log.debug("Cannot get MQTT handler for name as it is empty.");
}
else {
aHandler = handlers.get(aName);
log.debug("Retrieved a MQTT hanlder for name: " + aName);
}
return aHandler;
}
public List<MQTTBroker> getBrokers() {
if(!validMqtt)
return null;
Iterator<String> keys = handlers.keySet().iterator();
ArrayList<MQTTBroker> deviceList = new ArrayList<MQTTBroker>();
while(keys.hasNext()) {
String key = keys.next();
MQTTHandler aHandler = handlers.get(key);
MQTTBroker aDevice = new MQTTBroker(aHandler.getMyConfig());
deviceList.add(aDevice);
}
return deviceList;
}
}

View File

@@ -0,0 +1,39 @@
package com.bwssystems.mqtt;
public class MQTTMessage {
private String clientId;
private String topic;
private String message;
private Integer delay;
private Integer count;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Integer getDelay() {
return delay;
}
public void setDelay(Integer delay) {
this.delay = delay;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}

View File

@@ -36,7 +36,7 @@
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="http://echo.amazon.com/#cards" target="_blank">My Echo</a></li>
<li><a href="http://{{bridge.settings.myechourl}}" target="_blank">My Echo</a></li>
<li><a href="https://github.com/bwssytems/ha-bridge/blob/master/README.md" target="_blank">Help</a></li>
<li class="dropdown">
<a id="dLabel" href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">About <span class="caret"></span></a>

View File

@@ -37,6 +37,9 @@ app.config(function ($routeProvider) {
}).when('/haldevices', {
templateUrl: 'views/haldevice.html',
controller: 'HalController'
}).when('/mqttmessages', {
templateUrl: 'views/mqttpublish.html',
controller: 'MQTTController'
}).otherwise({
templateUrl: 'views/configuration.html',
controller: 'ViewingController'
@@ -62,7 +65,7 @@ String.prototype.replaceAll = function(search, replace)
app.service('bridgeService', function ($http, $window, ngToast) {
var self = this;
this.state = {base: window.location.origin + "/api/devices", bridgelocation: window.location.origin, systemsbase: window.location.origin + "/system", huebase: window.location.origin + "/api", configs: [], backups: [], devices: [], device: [], mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, habridgeversion: ""};
this.state = {base: window.location.origin + "/api/devices", bridgelocation: window.location.origin, systemsbase: window.location.origin + "/system", huebase: window.location.origin + "/api", configs: [], backups: [], devices: [], device: [], mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, showMqtt: false, habridgeversion: ""};
this.displayWarn = function(errorTitle, error) {
var toastContent = errorTitle;
@@ -145,6 +148,7 @@ app.service('bridgeService', function ($http, $window, ngToast) {
self.state.device.contentBody = null;
self.state.device.contentBodyDim = null;
self.state.device.contentBodyOff = null;
self.state.device.requesterAddress = null;
self.state.olddevicename = "";
};
@@ -188,6 +192,11 @@ app.service('bridgeService', function ($http, $window, ngToast) {
return;
}
this.updateShowMqtt = function () {
this.state.showMqtt = self.state.settings.mqttconfigured;
return;
}
this.loadBridgeSettings = function () {
return $http.get(this.state.systemsbase + "/settings").then(
function (response) {
@@ -197,6 +206,7 @@ app.service('bridgeService', function ($http, $window, ngToast) {
self.updateShowNest();
self.updateShowHue();
self.updateShowHal();
self.updateShowMqtt();
},
function (error) {
self.displayWarn("Load Bridge Settings Error: ", error);
@@ -339,6 +349,19 @@ app.service('bridgeService', function ($http, $window, ngToast) {
);
};
this.viewMQTTDevices = function () {
if(!this.state.showMqtt)
return;
return $http.get(this.state.base + "/mqtt/devices").then(
function (response) {
self.state.mqttbrokers = response.data;
},
function (error) {
self.displayWarn("Get MQTT Devices Error: ", error);
}
);
};
this.updateLogLevels = function(logComponents) {
return $http.put(this.state.systemsbase + "/logmgmt/update", logComponents ).then(
function (response) {
@@ -679,6 +702,24 @@ app.controller('SystemController', function ($scope, $location, $http, $window,
}
}
};
$scope.addMQTTtoSettings = function (newmqttname, newmqttip, newmqttusername, newmqttpassword) {
if($scope.bridge.settings.mqttaddress == null) {
$scope.bridge.settings.mqttaddress = { devices: [] };
}
var newmqtt = {name: newmqttname, ip: newmqttip, username: newmqttusername, password: newmqttpassword }
$scope.bridge.settings.mqttaddress.devices.push(newmqtt);
$scope.newmqttname = null;
$scope.newmqttip = null;
$scope.newmqttusername = null;
$scope.newmqttpassword = null;
};
$scope.removeMQTTtoSettings = function (mqttname, mqttip) {
for(var i = $scope.bridge.settings.mqttaddress.devices.length - 1; i >= 0; i--) {
if($scope.bridge.settings.mqttaddress.devices[i].name === mqttname && $scope.bridge.settings.mqttaddress.devices[i].ip === mqttip) {
$scope.bridge.settings.mqttaddress.devices.splice(i, 1);
}
}
};
$scope.bridgeReinit = function () {
bridgeService.reinit();
};
@@ -769,11 +810,7 @@ app.controller('ViewingController', function ($scope, $location, $http, $window,
(type == "off" && (bridgeService.aContainsB(device.offUrl, "${intensity.byte}") ||
bridgeService.aContainsB(device.offUrl, "${intensity.percent}") ||
bridgeService.aContainsB(device.offUrl, "${intensity.math("))) ||
(type == "dim" && (bridgeService.aContainsB(device.dimUrl, "${intensity.byte}") ||
bridgeService.aContainsB(device.dimUrl, "${intensity.percent}") ||
bridgeService.aContainsB(device.dimUrl, "${intensity.math(") ||
bridgeService.aContainsB(device.deviceType, "passthru") ||
bridgeService.aContainsB(device.mapType, "hueDevice"))))) {
(type == "dim"))) {
$scope.bridge.device = device;
$scope.bridge.type = type;
ngDialog.open({
@@ -1715,6 +1752,71 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
});
app.controller('MQTTController', function ($scope, $location, $http, bridgeService, ngDialog) {
$scope.bridge = bridgeService.state;
$scope.device = $scope.bridge.device;
bridgeService.viewMQTTDevices();
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
$scope.buttonsVisible = false;
$scope.clearDevice = function () {
bridgeService.clearDevice();
};
$scope.buildMQTTPublish = function (mqttbroker, mqtttopic, mqttmessage) {
var currentOn = $scope.device.onUrl;
var currentOff = $scope.device.offUrl;
if( $scope.device.mapType == "mqttMessage") {
$scope.device.mapId = $scope.device.mapId + "-" + mqtttopic;
$scope.device.onUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
$scope.device.offUrl = currentOff.substr(0, currentOff.indexOf("]")) + ",{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
}
else if ($scope.device.mapType == null || $scope.device.mapType == "") {
bridgeService.clearDevice();
$scope.device.deviceType = "mqtt";
$scope.device.targetDevice = mqttbroker.clientId;
$scope.device.name = mqttbroker.clientId + mqtttopic;
$scope.device.mapType = "mqttMessage";
$scope.device.mapId = mqttbroker.clientId + "-" + mqtttopic;
$scope.device.onUrl = "[{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
$scope.device.offUrl = "[{\"clientId\":\"" + mqttbroker.clientId + "\",\"topic\":\"" + mqtttopic + "\",\"message\":\"" + mqttmessage + "\"}]";
}
};
$scope.addDevice = function () {
if($scope.device.name == "" && $scope.device.onUrl == "")
return;
bridgeService.addDevice($scope.device).then(
function () {
$scope.clearDevice();
bridgeService.viewDevices();
bridgeService.viewMQTTDevices();
},
function (error) {
}
);
};
$scope.toggleButtons = function () {
$scope.buttonsVisible = !$scope.buttonsVisible;
if($scope.buttonsVisible)
$scope.imgButtonsUrl = "glyphicon glyphicon-minus";
else
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
};
$scope.deleteDeviceByMapId = function (id, mapType) {
$scope.bridge.mapandid = { id, mapType };
ngDialog.open({
template: 'deleteMapandIdDialog',
controller: 'DeleteMapandIdDialogCtrl',
className: 'ngdialog-theme-default'
});
};
});
app.controller('EditController', function ($scope, $location, $http, bridgeService) {
$scope.bridge = bridgeService.state;
$scope.device = $scope.bridge.device;
@@ -1989,6 +2091,20 @@ app.filter('configuredButtons', function() {
}
});
app.filter('configuredMqttMsgs', function() {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(input[i].mapType == "mqttMessage"){
out.push(input[i]);
}
}
return out;
}
});
app.controller('VersionController', function ($scope, bridgeService) {
$scope.bridge = bridgeService.state;
});

View File

@@ -17,6 +17,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
@@ -39,6 +40,7 @@
<th sortable-header col="name">Name</th>
<th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th>
<th sortable-header col="requesterAddress">Requester Address</th>
<th>Actions</th>
</tr>
</thead>
@@ -48,6 +50,7 @@
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.requesterAddress}}</td>
<td>
<p>
<button class="btn btn-info" type="submit"

View File

@@ -13,9 +13,10 @@
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a
href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
<li role="presentation" class="active"><a href="#/editdevice">Edit
Device</a></li>
</ul>
@@ -115,6 +116,14 @@
ng-model="device.uniqueid" placeholder="AA:BB:CC:DD:EE:FF-XX" readonly>
</div>
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-requester-addr">Requester Address (comma separated list) </label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="evice-requester-addr"
ng-model="device.requesterAddress" placeholder="Only use if you want to restrict this device to a specific caller(s)">
</div>
</div>
<div ng-if="device.mapType" class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-map-id">Map
ID </label>

View File

@@ -15,6 +15,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation" class="active"><a href="#/editor">Manual
Add</a></li>
</ul>
@@ -129,6 +130,16 @@
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-requester-addr">Requester Address (comma separated list) </label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="evice-requester-addr"
ng-model="device.requesterAddress" placeholder="Only use if you want to restrict this device to a specific caller(s)">
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On

View File

@@ -15,6 +15,7 @@
href="#/huedevices">Hue Devices</a></li>
<li role="presentation" class="active"><a href="#/haldevices">HAL
Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -15,6 +15,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -15,6 +15,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -15,6 +15,7 @@
Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -15,6 +15,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -0,0 +1,151 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a href="#/haldevices">HAL Devices</a></li>
<li role="presentation" class="active"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">MQTT Messages</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any MQTT Broker, use the
build button to generate the configuration for the publish generation.
You can add topic and content in the text areas provided
then selecting the publish generation. Then you can modify the name
to anything you want that will be the keyword for Alexa. Click the
'Add Bridge Device' to finish that selection setup. The 'Already
Configured MQTT Publish messages' list below will show what is already
setup for your MQTT Brokers.</p>
</div>
<scrollable-table watch="bridge.mqttbrokers">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">ClientID</th>
<th sortable-header col="ip">IP</th>
<th>Topic</th>
<th>Content</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="mqttbroker in bridge.mqttbrokers">
<td>{{$index+1}}</td>
<td>{{mqttbroker.clientId}}</td>
<td>{{mqttbroker.ip}}</td>
<td>
<textarea rows="2" class="form-control" id="mqtt-topic"
ng-model="mqtttopic" placeholder="The MQTT Topic"></textarea>
</td>
<td>
<textarea rows="2" class="form-control" id="mqtt-content"
ng-model="mqttcontent" placeholder="The MQTT Message Content"></textarea>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildMQTTPublish(mqttbroker, mqtttopic, mqttcontent)">Build
publish Message</button>
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured MQTT Messages <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<scrollable-table ng-if="buttonsVisible" watch="bridge.mqttbrokers">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="nameclientid">Name</th>
<th sortable-header col="ip">ClientID</th>
<th>MQTT Message ID</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredMqttMsgs | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId, device.mapType)">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for MQTT Messages</h2>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"
ng-click="addDevice()">Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
</form>
</div>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -15,6 +15,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -16,6 +16,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
@@ -82,6 +83,13 @@
ng-model="bridge.settings.upnpconfigaddress"
placeholder="192.168.1.1"></td>
</tr>
<tr>
<td>Web Server IP Address</td>
<td><input id="bridge-settings-webaddress"
class="form-control" type="text"
ng-model="bridge.settings.webaddress"
placeholder="0.0.0.0"></td>
</tr>
<tr>
<td>Web Server Port</td>
<td><input id="bridge-settings-serverport"
@@ -152,19 +160,6 @@
</tr>
</table></td>
</tr>
<tr>
<td>Harmony Username</td>
<td><input id="bridge-settings-harmonyuser"
class="form-control" type="text"
ng-model="bridge.settings.harmonyuser"
placeholder="someone@gmail.com"></td>
</tr>
<tr>
<td>Harmony Password</td>
<td><input id="bridge-settings-harmonypwd"
class="form-control" type="password"
ng-model="bridge.settings.harmonypwd" placeholder="thepassword"></td>
</tr>
<tr>
<td>Hue Names and IP Addresses</td>
<td><table
@@ -229,6 +224,47 @@
type="password" ng-model="bridge.settings.haltoken"
placeholder="thetoken"></td>
</tr>
<tr>
<td>MQTT Client IDs and IP Addresses</td>
<td><table
class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Client ID</th>
<th>IP</th>
<th>User (opt)</th>
<th>Password (opt)</th>
<th>Manage</th>
</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><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"
class="form-control" type="text" ng-model="newmqttname"
placeholder="A MQTT Client ID"></td>
<td><input id="bridge-settings-next-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"
class="form-control" type="text" ng-model="newmqttusername"
placeholder="MQTT Broker username (optional)"></td>
<td><input id="bridge-settings-next-mqtt-password"
class="form-control" type="password" ng-model="newmqttpassword"
placeholder="MQTT Broker password (opt)"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addMQTTtoSettings(newmqttname, newmqttip, newmqttusername, newmqttpassword)">Add</button></td>
</tr>
</table></td>
</tr>
<tr>
<td>Nest Username</td>
<td><input id="bridge-settings-nestuser" class="form-control"
@@ -272,6 +308,12 @@
ng-model="bridge.settings.traceupnp" ng-true-value=true
ng-false-value=false> {{bridge.settings.traceupnp}}</td>
</tr>
<tr>
<td>My Echo URL</td>
<td><input id="bridge-settings-myechourl" class="form-control"
type="text" ng-model="bridge.settings.myechourl"
placeholder="echo.amazon.com/#cards"></td>
</tr>
</table>
</form>
</div>

View File

@@ -14,6 +14,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>

View File

@@ -14,6 +14,7 @@
href="#/huedevices">Hue Devices</a></li>
<li ng-if="bridge.showHal" role="presentation"><a
href="#/haldevices">HAL Devices</a></li>
<li ng-if="bridge.showMqtt" role="presentation"><a href="#/mqttmessages">MQTT Messages</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>