Post latest merge from master

This commit is contained in:
Monica Goward
2017-03-16 11:55:02 +00:00
41 changed files with 1395 additions and 576 deletions

View File

@@ -1,6 +1,32 @@
# ha-bridge
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.
Here are some diagrams to put this software in perspective.
The Echo Path looks like this:
```
+------------------------+ +------------------------+
+-------------+ | H A +------------------| | A +------------------+ |
| Amazon Echo |----->| U P | ha-bridge core |--->| P | Device to control| |
+-------------+ | E I +------------------| | I +------------------+ |
+------------------------+ +------------------------+
```
The Google Home Path looks like this:
```
+------------------------+ +------------------------+
+-------------+ | H A +------------------| | A +------------------+ |
| Google Home |----->| U P | ha-bridge core |--->| P | Device to control| |
+-------------+ | E I +------------------| | I +------------------+ |
+------------------------+ +------------------------+
```
THe Harmony Hub Path looks like this:
```
+------------------------+ +------------------------+
+-------------+ | H A +------------------| | A +------------------+ |
| Harmony Hub |----->| U P | ha-bridge core |--->| P | Device to control| |
+-------------+ | E I +------------------| | I +------------------+ |
+------------------------+ +------------------------+
```
**SECURITY RISK: If you are unsure on how this software operates and what it exposes to your network, please make sure you understand that it can allow root access to your system. It is best practice to not open this to the Internet through your router as there are no security protocols in place to protect the system. The License agreement states specifically that you use this at your own risk.**
**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!**
@@ -33,23 +59,23 @@ 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.1.4.jar
java -jar ha-bridge-4.2.1.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.1.4.jar is in your /home/pi/habridge directory.
Create the directory and make sure that ha-bridge-4.2.1.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.1.4/ha-bridge-4.1.4.jar
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.2.1/ha-bridge-4.2.1.jar
```
Create the directory and make sure that ha-bridge-4.1.4.jar is in your /home/pi/habridge directory.
Create the directory and make sure that ha-bridge-4.2.1.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.1.4/ha-bridge-4.1.4.jar
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.2.1/ha-bridge-4.2.1.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
@@ -69,7 +95,7 @@ After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.1.4.jar
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.2.1.jar
[Install]
WantedBy=multi-user.target
@@ -104,7 +130,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.1.4.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.2.1.jar > /home/pi/habridge/habridge-log.txt 2>&1 &
chmod 777 /home/pi/habridge/habridge-log.txt
```
@@ -358,7 +384,7 @@ Headers can be added as well using a Json construct [{"name":"header type name",
Another option that is detected by the bridge is to use UDP or TCP direct calls such as `udp://<ip_address>:<port>/<your stuff here>` to send a UDP request. TCP calls are handled the same way as `tcp://<ip_address>:<port>/<your stuff here>`. If your data for the UDP or TCP request is formatted as "0x00F009B9" lexical hex format, the bridge will convert the data into a binary stream to send.
You can also use the value replacement constructs within these statements. Such as using the expressions "${time.format(Java time format string)}" for inserting a date/time stamp, ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 for straight pass through of the value or items that require special calculated values using ${intensity.math()} i.e. "${intensity.math(X/4)}". See Value Passing Controls Below.
You can also use the value replacement constructs within these statements. Such as using the expressions "${time.format(Java time format string)}" for inserting a date/time stamp, ${intensity.percent} for 0-100 or ${intensity.decimal_percent} for 0.00-1.00 or ${intensity.byte} for 0-255 for straight pass through of the value or items that require special calculated values using ${intensity.math()} i.e. "${intensity.math(X/4)}". See Value Passing Controls Below.
Examples:
```
@@ -412,7 +438,7 @@ OR
```
#### Value Passing Controls
There are multiple replacement constructs available to be put into any of the calls except Harmony items, Net Items and HAL items. These constructs are: "${time.format(Java time format string)}", "${intensity.percent}", "${intensity.byte}" and "${intensity.math(using X in your calc)}".
There are multiple replacement constructs available to be put into any of the calls except Harmony items, Net Items and HAL items. These constructs are: "${time.format(Java time format string)}", "${intensity.percent}", "${intensity.decimal_percent}", "${intensity.byte}" and "${intensity.math(using X in your calc)}".
You can control items that require special calculated values using ${intensity.math(<your expression using "X" as the value to operate on>)} i.e. "${intensity.math(X/4)}".
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
e.g.
@@ -522,7 +548,7 @@ contentBodyOff | string | This is the content body that you would like to send w
}
```
#### Dimming Control Example
Dimming is also supported by using the expressions ${intensity.percent} for 0-100 or ${intensity.byte} for 0-255 for straight pass through of the value.
Dimming is also supported by using the expressions ${intensity.percent} for 0-100 or ${intensity.decimal_percent} for 0.00-1.00 or ${intensity.byte} for 0-255 for straight pass through of the value.
e.g.
```
{

View File

@@ -1,144 +0,0 @@
# 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 doesnt include **“volume”** as it will cause conflicts with the Echos built in volume controls. A device name of **“cody sound”** 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 dont 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 dont 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}
```
### Test the three URLS
You should now be able to control the volume of Kodi using the structured URL you built above in a browser.
If you cant get it to work in a browser then you wont be able to get it to work in HA Bridge.
### Manually adding the device
Add a new manual device and give it a name e.g. “Cody Sound”
Set `Device type` to `Custom`
Use the same URL for all three (ON, OFF, DIM)
```
http://KODI_USERNAME:KODI_PASSWORD@192.168.1.123:8080/jsonrpc?request=
```
* `HTTP Verb` to `POST`
* `Content type` to `application/json`
**Content body On**
```json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":100},"id":1}
```
**Content body Dim**
```json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":${intensity.percent}},"id":1}
```
**Content body Off**
```json
{"jsonrpc":"2.0","method":"Application.SetVolume","params":{"volume":0},"id":1}
```
### HA Bridge
Save and test the button in the Bridge Devices tab and hopefully it should turn the volume up in Kodi.
### Controlling the Device
You can use the commands as listed in the [README](https://github.com/bwssytems/ha-bridge#ask-alexa)
“Set Cody Sound to 50 percent”
“Cody Sound to 70 percent”
Remembering that “Turn on Cody Sound” will set the volume to 100%, and “Turn off Cody Sound” will mute.

14
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>4.1.4</version>
<version>4.2.1</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
@@ -48,7 +48,7 @@
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>nest-controller</artifactId>
<version>1.0.13</version>
<version>1.0.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
@@ -121,6 +121,16 @@
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>lifx-sdk-java</artifactId>
<version>2.1.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
<build>

View File

@@ -167,6 +167,7 @@ public class BridgeSettings extends BackupHandler {
theBridgeSettings.setHassconfigured(theBridgeSettings.isValidHass());
theBridgeSettings.setDomoticzconfigured(theBridgeSettings.isValidDomoticz());
theBridgeSettings.setSomfyconfigured(theBridgeSettings.isValidSomfy());
// Lifx is either configured or not, so it does not need an update.
if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride);
if(serverIpOverride != null)

View File

@@ -43,6 +43,8 @@ public class BridgeSettingsDescriptor {
private IpList somfyaddress;
private boolean somfyconfigured;
private boolean lifxconfigured;
public BridgeSettingsDescriptor() {
super();
this.upnpstrict = true;
@@ -278,6 +280,12 @@ public class BridgeSettingsDescriptor {
public void setDomoticzconfigured(boolean domoticzconfigured) {
this.domoticzconfigured = domoticzconfigured;
}
public boolean isLifxconfigured() {
return lifxconfigured;
}
public void setLifxconfigured(boolean lifxconfigured) {
this.lifxconfigured = lifxconfigured;
}
public Boolean isValidVera() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;
@@ -351,4 +359,7 @@ public class BridgeSettingsDescriptor {
return false;
return true;
}
public Boolean isValidLifx() {
return this.isLifxconfigured();
}
}

View File

@@ -27,6 +27,7 @@ public class DeviceMapTypes {
public final static String[] HTTP_DEVICE = { "httpDevice", "HTTP Device"};
public final static String[] DOMOTICZ_DEVICE = { "domoticzDevice", "Domoticz Device"};
public final static String[] SOMFY_DEVICE = { "somfyDevice", "Somfy Device"};
public final static String[] LIFX_DEVICE = { "lifxDevice", "LIFX Device"};
public final static int typeIndex = 0;
public final static int displayIndex = 1;
@@ -47,6 +48,7 @@ public class DeviceMapTypes {
deviceMapTypes.add(HASS_DEVICE);
deviceMapTypes.add(HTTP_DEVICE);
deviceMapTypes.add(HUE_DEVICE);
deviceMapTypes.add(LIFX_DEVICE);
deviceMapTypes.add(MQTT_MESSAGE);
deviceMapTypes.add(NEST_HOMEAWAY);
deviceMapTypes.add(NEST_THERMO_SET);

View File

@@ -13,6 +13,7 @@ import com.bwssystems.HABridge.plugins.harmony.HarmonyHome;
import com.bwssystems.HABridge.plugins.hass.HassHome;
import com.bwssystems.HABridge.plugins.http.HTTPHome;
import com.bwssystems.HABridge.plugins.hue.HueHome;
import com.bwssystems.HABridge.plugins.lifx.LifxHome;
import com.bwssystems.HABridge.plugins.mqtt.MQTTHome;
import com.bwssystems.HABridge.plugins.somfy.SomfyHome;
import com.bwssystems.HABridge.plugins.tcp.TCPHome;
@@ -72,7 +73,6 @@ public class HomeManager {
homeList.put(DeviceMapTypes.CUSTOM_DEVICE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.VERA_DEVICE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.VERA_SCENE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex], aHome);
//setup the tcp handler Home
aHome = new TCPHome(bridgeSettings);
homeList.put(DeviceMapTypes.TCP_DEVICE[DeviceMapTypes.typeIndex], aHome);
@@ -81,17 +81,22 @@ public class HomeManager {
aHome = new UDPHome(bridgeSettings, aUdpDatagramSender);
homeList.put(DeviceMapTypes.UDP_DEVICE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.UDP_DEVICE_COMPAT[DeviceMapTypes.typeIndex], aHome);
// Setup Vera Home if available
aHome = new VeraHome(bridgeSettings);
resourceList.put(DeviceMapTypes.VERA_DEVICE[DeviceMapTypes.typeIndex], aHome);
resourceList.put(DeviceMapTypes.VERA_SCENE[DeviceMapTypes.typeIndex], aHome);
//setup the HomeAssistant configuration if available
//setup the Domoticz configuration if available
aHome = new DomoticzHome(bridgeSettings);
homeList.put(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex], aHome);
resourceList.put(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex], aHome);
//setup the Somfy configuration if available
aHome = new SomfyHome(bridgeSettings);
homeList.put(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex], aHome);
resourceList.put(DeviceMapTypes.SOMFY_DEVICE[DeviceMapTypes.typeIndex], aHome);
//setup the Lifx configuration if available
aHome = new LifxHome(bridgeSettings);
resourceList.put(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex], aHome);
homeList.put(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex], aHome);
}
public Home findHome(String type) {

View File

@@ -2,7 +2,8 @@ package com.bwssystems.HABridge;
public class NamedIP {
private String name;
private String ip;
private String ip;
private String webhook;
private String port;
private String username;
private String password;
@@ -20,7 +21,13 @@ public class NamedIP {
public void setIp(String ip) {
this.ip = ip;
}
public String getPort() {
public String getWebhook() {
return webhook;
}
public void setWebhook(final String webhook) {
this.webhook = webhook;
}
public String getPort() {
return port;
}
public void setPort(String port) {

View File

@@ -62,6 +62,9 @@ public class DeviceDescriptor{
@SerializedName("noState")
@Expose
private boolean noState;
@SerializedName("requesterAddress")
@Expose
private String requesterAddress;
private DeviceState deviceState;
@@ -219,6 +222,14 @@ public class DeviceDescriptor{
this.noState = noState;
}
public String getRequesterAddress() {
return requesterAddress;
}
public void setRequesterAddress(String requesterAddress) {
this.requesterAddress = requesterAddress;
}
public boolean containsType(String aType) {
if(aType == null)
return false;

View File

@@ -83,6 +83,33 @@ public class DeviceRepository extends BackupHandler {
return list;
}
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);
}

View File

@@ -18,12 +18,15 @@ import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.HomeManager;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.BackupFilename;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.DeviceRepository;
import com.bwssystems.HABridge.dao.ErrorMessage;
import com.bwssystems.HABridge.util.JsonTransformer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
spark core server for bridge configuration
@@ -33,11 +36,13 @@ public class DeviceResource {
private static final Logger log = LoggerFactory.getLogger(DeviceResource.class);
private DeviceRepository deviceRepository;
private HomeManager homeManager;
private Gson aGsonHandler;
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
public DeviceResource(BridgeSettingsDescriptor theSettings, HomeManager aHomeManager) {
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
homeManager = aHomeManager;
aGsonHandler = new GsonBuilder().create();
setupEndpoints();
}
@@ -65,14 +70,44 @@ public class DeviceResource {
else {
devices = new Gson().fromJson("[" + request.body() + "]", DeviceDescriptor[].class);
}
CallItem[] callItems = null;
String errorMessage = null;
for(int i = 0; i < devices.length; i++) {
if(devices[i].getContentBody() != null ) {
if (devices[i].getContentType() == null || devices[i].getHttpVerb() == null || !supportedVerbs.contains(devices[i].getHttpVerb().toLowerCase())) {
response.status(HttpStatus.SC_BAD_REQUEST);
log.debug("Bad http verb in create a Device(s): " + request.body());
return new ErrorMessage("Bad http verb in create a Device(s): " + request.body() + " ");
errorMessage = "Bad http verb in create device(s) for name: " + devices[i].getName() + " with verb: " + devices[i].getHttpVerb();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
}
try {
if(devices[i].getOnUrl() != null && !devices[i].getOnUrl().isEmpty())
callItems = aGsonHandler.fromJson(devices[i].getOnUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "Bad on URL JSON in create device(s) for name: " + devices[i].getName() + " with on URL: " + devices[i].getOnUrl();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
try {
if(devices[i].getDimUrl() != null && !devices[i].getDimUrl().isEmpty())
callItems = aGsonHandler.fromJson(devices[i].getDimUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "Bad dim URL JSON in create device(s) for name: " + devices[i].getName() + " with dim URL: " + devices[i].getDimUrl();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
try {
if(devices[i].getOffUrl() != null && !devices[i].getOffUrl().isEmpty())
callItems = aGsonHandler.fromJson(devices[i].getOffUrl(), CallItem[].class);
} catch(JsonSyntaxException e) {
response.status(HttpStatus.SC_BAD_REQUEST);
errorMessage = "Bad off URL JSON in create device(s) for name: " + devices[i].getName() + " with off URL: " + devices[i].getOffUrl();
log.debug(errorMessage);
return new ErrorMessage(errorMessage);
}
}
deviceRepository.save(devices);
@@ -219,6 +254,12 @@ public class DeviceResource {
return homeManager.findResource(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex]).getItems(DeviceMapTypes.DOMOTICZ_DEVICE[DeviceMapTypes.typeIndex]);
}, new JsonTransformer());
get (API_CONTEXT + "/lifx/devices", "application/json", (request, response) -> {
log.debug("Get LIFX devices");
response.status(HttpStatus.SC_OK);
return homeManager.findResource(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex]).getItems(DeviceMapTypes.LIFX_DEVICE[DeviceMapTypes.typeIndex]);
}, new JsonTransformer());
get (API_CONTEXT + "/somfy/devices", "application/json", (request, response) -> {
log.debug("Get somfy devices");
response.status(HttpStatus.SC_OK);

View File

@@ -1,20 +1,18 @@
package com.bwssystems.HABridge.hue;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.Conversion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.java.dev.eval.Expression;
public class BrightnessDecode {
private static final Logger log = LoggerFactory.getLogger(BrightnessDecode.class);
private static final String INTENSITY_PERCENT = "${intensity.percent}";
private static final String INTENSITY_DECIMAL_PERCENT = "${intensity.decimal_percent}";
private static final String INTENSITY_BYTE = "${intensity.byte}";
private static final String INTENSITY_MATH = "${intensity.math(";
private static final String INTENSITY_MATH_VALUE = "X";
@@ -49,9 +47,7 @@ public class BrightnessDecode {
}
if (request.contains(INTENSITY_BYTE)) {
if (isHex) {
BigInteger bigInt = BigInteger.valueOf(intensity);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
String hexValue = convertToHex(intensity);
request = request.replace(INTENSITY_BYTE, hexValue);
} else {
String intensityByte = String.valueOf(intensity);
@@ -60,14 +56,17 @@ public class BrightnessDecode {
} else if (request.contains(INTENSITY_PERCENT)) {
int percentBrightness = (int) Math.round(intensity / 255.0 * 100);
if (isHex) {
BigInteger bigInt = BigInteger.valueOf(percentBrightness);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
String hexValue = convertToHex(percentBrightness);
request = request.replace(INTENSITY_PERCENT, hexValue);
} else {
String intensityPercent = String.valueOf(percentBrightness);
request = request.replace(INTENSITY_PERCENT, intensityPercent);
}
} else if (request.contains(INTENSITY_DECIMAL_PERCENT)) {
float decimalBrightness = (float) (intensity / 255.0);
String intensityPercent = String.format("%1.2f", decimalBrightness);
request = request.replace(INTENSITY_DECIMAL_PERCENT, intensityPercent);
} else if (request.contains(INTENSITY_MATH)) {
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
@@ -81,9 +80,7 @@ public class BrightnessDecode {
BigDecimal result = exp.eval(variables);
Integer endResult = Math.round(result.floatValue());
if (isHex) {
BigInteger bigInt = BigInteger.valueOf(endResult);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
String hexValue = convertToHex(endResult);
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue);
} else {
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE,
@@ -100,4 +97,15 @@ public class BrightnessDecode {
public static String calculateReplaceIntensityValue(String request, int theIntensity, Integer targetBri, Integer targetBriInc, boolean isHex) {
return replaceIntensityValue(request, calculateIntensity(theIntensity, targetBri, targetBriInc), isHex);
}
}
// Apache Commons Conversion utils likes little endian too much
private static String convertToHex(int theValue) {
String destHex = "00";
String hexValue = Conversion.intToHex(theValue, 0, destHex, 0, 2);
byte[] theBytes = hexValue.getBytes();
byte[] newBytes = new byte[2];
newBytes[0] = theBytes[1];
newBytes[1] = theBytes[0];
return new String(newBytes);
}
}

View File

@@ -16,7 +16,6 @@ import com.bwssystems.HABridge.api.hue.HuePublicConfig;
import com.bwssystems.HABridge.api.hue.StateChangeBody;
import com.bwssystems.HABridge.api.hue.WhitelistEntry;
import com.bwssystems.HABridge.dao.*;
import com.bwssystems.HABridge.plugins.hue.HueDeviceIdentifier;
import com.bwssystems.HABridge.plugins.hue.HueHome;
import com.bwssystems.HABridge.util.JsonTransformer;
import com.google.gson.Gson;
@@ -669,7 +668,8 @@ public class HueMulator {
log.debug("hue lights list requested: " + userId + " from " + requestIp);
theErrors = validateWhitelistUser(userId, false);
if (theErrors == null) {
List<DeviceDescriptor> deviceList = repository.findActive();
List<DeviceDescriptor> deviceList = repository.findAllByRequester(requestIp);
// List<DeviceDescriptor> deviceList = repository.findActive();
deviceResponseMap = new HashMap<String, DeviceResponse>();
for (DeviceDescriptor device : deviceList) {
DeviceResponse deviceResponse = null;
@@ -945,8 +945,8 @@ public class HueMulator {
}
for (int i = 0; callItems != null && i < callItems.length; i++) {
if(!filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
log.debug("filter for requester address not present in list: " + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress);
if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) {
log.warn("filter for requester address not present in: (device)" + device.getRequesterAddress() + " OR then (item)" + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress);
continue;
}
if (callItems[i].getCount() != null && callItems[i].getCount() > 0)

View File

@@ -3,11 +3,12 @@ package com.bwssystems.HABridge.plugins.domoticz;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.NameValue;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.google.gson.Gson;
@@ -16,32 +17,33 @@ public class DomoticzHandler {
private static final String GET_REQUEST = "/json.htm?type=";
private static final String DEVICES_TYPE = "devices";
private static final String SCENES_TYPE = "scenes";
private static final String FILTER_USED = "&used=";
private HTTPHandler httpClient;
private static final String FILTER_USED = "&used=true";
private NamedIP domoticzAddress;
public DomoticzHandler(NamedIP addressName) {
super();
httpClient = new HTTPHandler();
domoticzAddress = addressName;
}
public List<DomoticzDevice> getDevices() {
return getDomoticzDevices(GET_REQUEST, DEVICES_TYPE, FILTER_USED);
public List<DomoticzDevice> getDevices(HTTPHandler httpClient) {
return getDomoticzDevices(GET_REQUEST, DEVICES_TYPE, FILTER_USED, httpClient);
}
public List<DomoticzDevice> getScenes() {
return getDomoticzDevices(GET_REQUEST, SCENES_TYPE, null);
public List<DomoticzDevice> getScenes(HTTPHandler httpClient) {
return getDomoticzDevices(GET_REQUEST, SCENES_TYPE, null, httpClient);
}
private List<DomoticzDevice> getDomoticzDevices(String rootRequest, String type, String postpend) {
private List<DomoticzDevice> getDomoticzDevices(String rootRequest, String type, String postpend, HTTPHandler httpClient) {
Devices theDomoticzApiResponse = null;
List<DomoticzDevice> deviceList = null;
String theUrl = null;
String theData;
theUrl = "http://" + domoticzAddress.getIp() + ":" + domoticzAddress.getPort() + rootRequest + type;
theData = httpClient.doHttpRequest(theUrl, null, null, null, null);
if(postpend != null && !postpend.isEmpty())
theUrl = buildUrl(rootRequest + type + postpend);
else
theUrl = buildUrl(rootRequest + type);
theData = httpClient.doHttpRequest(theUrl, null, null, null, buildHeaders());
if(theData != null) {
log.debug("GET " + type + " DomoticzApiResponse - data: " + theData);
theDomoticzApiResponse = new Gson().fromJson(theData, Devices.class);
@@ -70,6 +72,44 @@ public class DomoticzHandler {
return deviceList;
}
public String buildUrl(String thePayload) {
String newUrl = null;
if(thePayload != null && !thePayload.isEmpty()) {
if(domoticzAddress.getSecure() != null && domoticzAddress.getSecure())
newUrl = "https://";
else
newUrl = "http://";
newUrl = newUrl + domoticzAddress.getIp();
if(domoticzAddress.getPort() != null && !domoticzAddress.getPort().isEmpty())
newUrl = newUrl + ":" + domoticzAddress.getPort();
if(thePayload.startsWith("/"))
newUrl = newUrl + thePayload;
else
newUrl = newUrl + "/" + thePayload;
}
return newUrl;
}
public NameValue[] buildHeaders() {
NameValue[] headers = null;
if(domoticzAddress.getUsername() != null && !domoticzAddress.getUsername().isEmpty()
&& domoticzAddress.getPassword() != null && !domoticzAddress.getPassword().isEmpty()) {
NameValue theAuth = new NameValue();
theAuth.setName("Authorization");
String encoding = Base64.getEncoder().encodeToString((domoticzAddress.getUsername() + ":" + domoticzAddress.getPassword()).getBytes());
theAuth.setValue("Basic " + encoding);
headers = new NameValue[1];
headers[0] = theAuth;
}
return headers;
}
public NamedIP getDomoticzAddress() {
return domoticzAddress;
}

View File

@@ -13,13 +13,19 @@ import com.bwssystems.HABridge.BridgeSettingsDescriptor;
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.MultiCommandUtil;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.google.gson.Gson;
public class DomoticzHome implements Home {
private static final Logger log = LoggerFactory.getLogger(DomoticzHome.class);
private Map<String, DomoticzHandler> domoticzs;
private Boolean validDomoticz;
private HTTPHandler httpClient;
public DomoticzHome(BridgeSettingsDescriptor bridgeSettings) {
super();
@@ -36,14 +42,14 @@ public class DomoticzHome implements Home {
List<DomoticzDevice> deviceList = new ArrayList<DomoticzDevice>();
while(keys.hasNext()) {
String key = keys.next();
theResponse = domoticzs.get(key).getDevices();
theResponse = domoticzs.get(key).getDevices(httpClient);
if(theResponse != null)
addDomoticzDevices(deviceList, theResponse, key);
else {
log.warn("Cannot get lights for Domoticz with name: " + key + ", skipping this Domoticz.");
continue;
}
theResponse = domoticzs.get(key).getScenes();
theResponse = domoticzs.get(key).getScenes(httpClient);
if(theResponse != null)
addDomoticzDevices(deviceList, theResponse, key);
else
@@ -66,8 +72,54 @@ public class DomoticzHome 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;
Devices theDomoticzApiResponse = null;
String responseString = null;
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && (theUrl.startsWith("http://") || theUrl.startsWith("https://"))) {
String intermediate = theUrl.substring(theUrl.indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
} else
hostAddr = hostPortion;
DomoticzHandler theHandler = findHandlerByAddress(hostAddr);
if(theHandler != null){
String theData;
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody,
intensity, targetBri, targetBriInc, false);
theData = httpClient.doHttpRequest(theHandler.buildUrl(anUrl), null, null, null, theHandler.buildHeaders());
try {
theDomoticzApiResponse = new Gson().fromJson(theData, Devices.class);
if(theDomoticzApiResponse.getStatus().equals("OK"))
responseString = null;
else {
log.warn("Call failed for Domoticz " + theHandler.getDomoticzAddress().getName() + " with status " + theDomoticzApiResponse.getStatus() + " for item " + theDomoticzApiResponse.getTitle());
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);
}
} catch (Exception e) {
log.warn("Cannot interrpret result from call for Domoticz " + theHandler.getDomoticzAddress().getName() + " as response is not parsable.");
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);
}
} else {
log.warn("Domoticz Call could not complete, no address found: " + theUrl);
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);
}
} else {
log.warn("Domoticz Call to be presented as http(s)://<ip_address>(:<port>)/payload, format of request unknown: " + theUrl);
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);
}
return responseString;
}
@Override
@@ -76,6 +128,7 @@ public class DomoticzHome implements Home {
log.info("Domoticz Home created." + (validDomoticz ? "" : " No Domoticz devices configured."));
if(!validDomoticz)
return null;
httpClient = new HTTPHandler();
domoticzs = new HashMap<String, DomoticzHandler>();
Iterator<NamedIP> theList = bridgeSettings.getDomoticzaddress().getDevices().iterator();
while(theList.hasNext()) {
@@ -90,9 +143,26 @@ public class DomoticzHome implements Home {
return this;
}
private DomoticzHandler findHandlerByAddress(String hostAddress) {
DomoticzHandler aHandler = null;
boolean found = false;
Iterator<String> keys = domoticzs.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
aHandler = domoticzs.get(key);
if(aHandler != null && aHandler.getDomoticzAddress().getIp().equals(hostAddress)) {
found = true;
break;
}
}
if(!found)
aHandler = null;
return aHandler;
}
@Override
public void closeHome() {
// noop
if(httpClient != null)
httpClient.closeHandler();
}
}

View File

@@ -4,6 +4,8 @@ import static java.lang.String.format;
import javax.inject.Inject;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import org.apache.http.client.methods.HttpGet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,69 +20,98 @@ import net.whistlingfish.harmony.HarmonyClientModule;
import net.whistlingfish.harmony.config.Activity;
import net.whistlingfish.harmony.protocol.OAReplyProvider;
import java.net.URLEncoder;
public class HarmonyServer {
private static final String ACTIVIY_ID = "${activity.id}";
private static final String ACTIVIY_LABEL = "${activity.label}";
@Inject
private HarmonyClient harmonyClient;
private HarmonyHandler myHarmony;
private DevModeResponse devResponse;
private OAReplyProvider dummyProvider;
private NamedIP myNameAndIP;
private Boolean isDevMode;
private HTTPHandler httpClient;
private Logger log = LoggerFactory.getLogger(HarmonyServer.class);
public HarmonyServer(NamedIP theHarmonyAddress) {
super();
myHarmony = null;
dummyProvider = null;
myNameAndIP = theHarmonyAddress;
isDevMode = false;
}
public HarmonyServer(NamedIP theHarmonyAddress) {
super();
myHarmony = null;
dummyProvider = null;
myNameAndIP = theHarmonyAddress;
isDevMode = false;
httpClient = new HTTPHandler();
}
public static HarmonyServer setup(BridgeSettingsDescriptor bridgeSettings, Boolean harmonyDevMode, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony() && harmonyDevMode) {
return new HarmonyServer(theHarmonyAddress);
}
Injector injector = null;
if(!harmonyDevMode)
injector = Guice.createInjector(new HarmonyClientModule());
HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if(!harmonyDevMode)
injector.injectMembers(mainObject);
mainObject.execute(bridgeSettings, harmonyDevMode);
return mainObject;
}
private void execute(BridgeSettingsDescriptor mySettings, Boolean harmonyDevMode) throws Exception {
Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false"));
isDevMode = harmonyDevMode;
String modeString = "";
if(dummyProvider != null)
log.debug("something is very wrong as dummyProvider is not null...");
if(isDevMode)
modeString = " (development mode)";
else if(noopCalls)
modeString = " (no op calls to harmony)";
log.info("setup initiated " + modeString + "....");
if(isDevMode)
{
harmonyClient = null;
devResponse = new DevModeResponse();
public static HarmonyServer setup(
BridgeSettingsDescriptor bridgeSettings,
Boolean harmonyDevMode,
NamedIP theHarmonyAddress
) throws Exception {
if (!bridgeSettings.isValidHarmony() && harmonyDevMode) {
return new HarmonyServer(theHarmonyAddress);
}
else {
devResponse = null;
harmonyClient.addListener(new ActivityChangeListener() {
@Override
public void activityStarted(Activity activity) {
log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel()));
}
});
harmonyClient.connect(myNameAndIP.getIp());
Injector injector = null;
if (!harmonyDevMode) {
injector = Guice.createInjector(new HarmonyClientModule());
}
HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if (!harmonyDevMode) {
injector.injectMembers(mainObject);
}
mainObject.execute(bridgeSettings, harmonyDevMode);
return mainObject;
}
private void execute(BridgeSettingsDescriptor mySettings, Boolean harmonyDevMode) throws Exception {
Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false"));
isDevMode = harmonyDevMode;
String modeString = "";
if (dummyProvider != null) {
log.debug("something is very wrong as dummyProvider is not null...");
}
if (isDevMode) {
modeString = " (development mode)";
} else if (noopCalls) {
modeString = " (no op calls to harmony)";
}
log.info("setup initiated " + modeString + "....");
if (isDevMode) {
harmonyClient = null;
devResponse = new DevModeResponse();
} else {
devResponse = null;
harmonyClient.addListener(new ActivityChangeListener() {
@Override
public void activityStarted(Activity activity) {
String webhook = myNameAndIP.getWebhook();
if(webhook != null) {
try {
// Replacing variables
webhook = webhook.replace(ACTIVIY_ID, activity.getId().toString());
webhook = webhook.replace(ACTIVIY_LABEL, URLEncoder.encode(activity.getLabel(), "UTF-8"));
log.info(format("calling webhook: %s", webhook));
// Calling webhook
httpClient.doHttpRequest(webhook, HttpGet.METHOD_NAME, null, null, null);
} catch (Exception e) {
log.warn("could not call webhook: " + webhook, e);
}
}
log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel()));
}
});
harmonyClient.connect(myNameAndIP.getIp());
}
myHarmony = new HarmonyHandler(harmonyClient, noopCalls, devResponse);
}
}
public HarmonyHandler getMyHarmony() {
return myHarmony;
}
public HarmonyHandler getMyHarmony() {
return myHarmony;
}
}

View File

@@ -41,7 +41,12 @@ public class HomeAssistant {
aUrl = "https";
else
aUrl = "http";
aUrl = aUrl + "://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/services/" + aCommand.getEntityId().substring(0, aCommand.getEntityId().indexOf("."));
String domain = aCommand.getEntityId().substring(0, aCommand.getEntityId().indexOf("."));
aUrl = aUrl + "://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/services/";
if(domain.equals("group"))
aUrl = aUrl + "homeassistant";
else
aUrl = aUrl + domain;
String aBody = "{\"entity_id\":\"" + aCommand.getEntityId() + "\"";
NameValue[] headers = null;
if(hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
@@ -62,6 +67,7 @@ public class HomeAssistant {
aUrl = aUrl + "/turn_off";
aBody = aBody + "}";
}
log.debug("Calling HomeAssistant with url: " + aUrl);
String theData = anHttpHandler.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", aBody, headers);
log.debug("call Command return is: <" + theData + ">");
return true;

View File

@@ -4,27 +4,18 @@ import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,23 +24,13 @@ import com.bwssystems.HABridge.api.NameValue;
public class HTTPHandler {
private static final Logger log = LoggerFactory.getLogger(HTTPHandler.class);
private HttpClient httpClient;
// private CloseableHttpClient httpclientSSL;
// private SSLContext sslcontext;
// private SSLConnectionSocketFactory sslsf;
// private RequestConfig globalConfig;
private CloseableHttpClient httpClient;
private RequestConfig globalConfig;
public HTTPHandler() {
httpClient = HttpClients.createDefault();
// Removed Specific SSL as Apache HttpClient automatically uses SSL if the URI starts with https://
// Trust own CA and all self-signed certs
// sslcontext = SSLContexts.createDefault();
// Allow TLSv1 protocol only
// sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLS" }, null,
// SSLConnectionSocketFactory.getDefaultHostnameVerifier());
// globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
// httpclientSSL = HttpClients.custom().setSSLSocketFactory(sslsf).setDefaultRequestConfig(globalConfig).build();
globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
httpClient = HttpClients.custom().setDefaultRequestConfig(globalConfig).build();
}
@@ -100,33 +81,56 @@ public class HTTPHandler {
request.setHeader(headers[i].getName(), headers[i].getValue());
}
}
HttpResponse response;
try {
HttpResponse response;
// Removed Specific SSL as Apache HttpClient automatically uses SSL if the URI starts with https://
// if (url.startsWith("xyzhttps"))
// response = httpclientSSL.execute(request);
// else
for(int retryCount = 0; retryCount < 2; retryCount++) {
response = httpClient.execute(request);
log.debug((httpVerb == null ? "GET" : httpVerb) + " execute 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((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 + ">>>");
}
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 (theContent == null)
theContent = "";
}
} catch (IOException e) {
log.warn("Error calling out to HA gateway: IOException in log", e);
@@ -134,23 +138,22 @@ public class HTTPHandler {
return theContent;
}
public HttpClient getHttpClient() {
// public HttpClient getHttpClient() {
// return httpClient;
// }
public CloseableHttpClient getHttpClient() {
return httpClient;
}
// public CloseableHttpClient getHttpclientSSL() {
// return httpclientSSL;
// }
public void closeHandler() {
try {
httpClient.close();
} catch (IOException e) {
// noop
}
httpClient = null;
// try {
// httpclientSSL.close();
// } catch (IOException e) {
// // noop
// }
// httpclientSSL = null;
}
}

View File

@@ -29,23 +29,25 @@ public class HTTPHome implements Home {
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
String responseString = null;
//Backwards Compatibility Items
if(anItem.getHttpVerb() == null || anItem.getHttpVerb().isEmpty())
{
if(device.getHttpVerb() != null && !device.getHttpVerb().isEmpty())
anItem.setHttpVerb(device.getHttpVerb());
}
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && (theUrl.startsWith("http://") || theUrl.startsWith("https://"))) {
//Backwards Compatibility Items
if(anItem.getHttpVerb() == null || anItem.getHttpVerb().isEmpty())
{
if(device.getHttpVerb() != null && !device.getHttpVerb().isEmpty())
anItem.setHttpVerb(device.getHttpVerb());
}
if(anItem.getHttpHeaders() == null || anItem.getHttpHeaders().isEmpty()) {
if(device.getHeaders() != null && !device.getHeaders().isEmpty() )
anItem.setHttpHeaders(device.getHeaders());
}
log.debug("executing HUE api request to Http "
+ (anItem.getHttpVerb() == null ? "GET" : anItem.getHttpVerb()) + ": "
+ anItem.getItem().getAsString());
if(anItem.getHttpHeaders() == null || anItem.getHttpHeaders().isEmpty()) {
if(device.getHeaders() != null && !device.getHeaders().isEmpty() )
anItem.setHttpHeaders(device.getHeaders());
}
log.debug("executing HUE api request to Http "
+ (anItem.getHttpVerb() == null ? "GET" : anItem.getHttpVerb()) + ": "
+ anItem.getItem().getAsString());
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(anItem.getItem().getAsString(),
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrl,
intensity, targetBri, targetBriInc, false);
anUrl = TimeDecode.replaceTimeValue(anUrl);
@@ -63,6 +65,13 @@ public class HTTPHome implements Home {
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} else {
log.warn("HTTP Call to be presented as http(s)://<ip_address>(:<port>)/payload, format of request unknown: " + theUrl);
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);
}
return responseString;
}

View File

@@ -0,0 +1,49 @@
package com.bwssystems.HABridge.plugins.lifx;
import com.github.besherman.lifx.LFXGroup;
import com.github.besherman.lifx.LFXLight;
public class LifxDevice {
private Object lifxObject;
private String type;
public final static String LIGHT_TYPE = "Light";
public final static String GROUP_TYPE = "Group";
public LifxDevice(Object lifxObject, String type) {
super();
this.lifxObject = lifxObject;
this.type = type;
}
public LifxEntry toEntry() {
LifxEntry anEntry = null;
if(type.equals(LIGHT_TYPE)) {
anEntry = new LifxEntry();
anEntry.setId(((LFXLight)lifxObject).getID());
anEntry.setName(((LFXLight)lifxObject).getLabel());
anEntry.setType(LIGHT_TYPE);
}
if(type.equals(GROUP_TYPE)) {
anEntry = new LifxEntry();
anEntry.setId("na");
anEntry.setName(((LFXGroup)lifxObject).getLabel());
anEntry.setType(GROUP_TYPE);
}
return anEntry;
}
public Object getLifxObject() {
return lifxObject;
}
public void setLifxObject(Object lifxObject) {
this.lifxObject = lifxObject;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,25 @@
package com.bwssystems.HABridge.plugins.lifx;
public class LifxEntry {
private String name;
private String id;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}

View File

@@ -0,0 +1,257 @@
package com.bwssystems.HABridge.plugins.lifx;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
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.Home;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.github.besherman.lifx.LFXClient;
import com.github.besherman.lifx.LFXGroup;
import com.github.besherman.lifx.LFXGroupCollection;
import com.github.besherman.lifx.LFXGroupCollectionListener;
import com.github.besherman.lifx.LFXLight;
import com.github.besherman.lifx.LFXLightCollection;
import com.github.besherman.lifx.LFXLightCollectionListener;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class LifxHome implements Home {
private static final Logger log = LoggerFactory.getLogger(LifxHome.class);
private static final float DIM_DIVISOR = (float)254.00;
private Map<String, LifxDevice> lifxMap;
private LFXClient client;
private Boolean validLifx;
private Gson aGsonHandler;
public LifxHome(BridgeSettingsDescriptor bridgeSettings) {
super();
createHome(bridgeSettings);
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
lifxMap = null;
aGsonHandler = null;
validLifx = bridgeSettings.isValidLifx();
log.info("LifxDevice Home created." + (validLifx ? "" : " No LifxDevices configured."));
if(validLifx) {
try {
log.info("Open Lifx client....");
InetAddress configuredAddress = InetAddress.getByName(bridgeSettings.getUpnpConfigAddress());
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(configuredAddress);
InetAddress bcastInetAddr = null;
if (networkInterface != null) {
for (InterfaceAddress ifaceAddr : networkInterface.getInterfaceAddresses()) {
InetAddress addr = ifaceAddr.getAddress();
if (addr instanceof Inet4Address) {
bcastInetAddr = ifaceAddr.getBroadcast();
break;
}
}
}
if(bcastInetAddr != null) {
lifxMap = new HashMap<String, LifxDevice>();
log.info("Opening LFX Client with broadcast address: " + bcastInetAddr.getHostAddress());
client = new LFXClient(bcastInetAddr.getHostAddress());
client.getLights().addLightCollectionListener(new MyLightListener(lifxMap));
client.getGroups().addGroupCollectionListener(new MyGroupListener(lifxMap));
client.open(false);
aGsonHandler =
new GsonBuilder()
.create();
} else {
log.warn("Could not open LIFX, no bcast addr available, check your upnp config address.");
client = null;
validLifx = false;
return this;
}
} catch (IOException e) {
log.warn("Could not open LIFX, with IO Exception", e);
client = null;
validLifx = false;
return this;
} catch (InterruptedException e) {
log.warn("Could not open LIFX, with Interruprted Exception", e);
client = null;
validLifx = false;
return this;
}
}
return this;
}
public LifxDevice getLifxDevice(String aName) {
if(!validLifx)
return null;
LifxDevice aLifxDevice = null;
if(aName == null || aName.equals("")) {
log.debug("Cannot get LifxDevice for name as it is empty.");
}
else {
aLifxDevice = lifxMap.get(aName);
log.debug("Retrieved a LifxDevice for name: " + aName);
}
return aLifxDevice;
}
@Override
public Object getItems(String type) {
log.debug("consolidating devices for lifx");
if(!validLifx)
return null;
LifxEntry theResponse = null;
Iterator<String> keys = lifxMap.keySet().iterator();
List<LifxEntry> deviceList = new ArrayList<LifxEntry>();
while(keys.hasNext()) {
String key = keys.next();
theResponse = lifxMap.get(key).toEntry();
if(theResponse != null)
deviceList.add(theResponse);
else {
log.warn("Cannot get LifxDevice with name: " + key + ", skipping this Lifx.");
continue;
}
}
return deviceList;
}
private Boolean addLifxLights(LFXLightCollection theDeviceList) {
if(!validLifx)
return false;
Iterator<LFXLight> devices = theDeviceList.iterator();;
while(devices.hasNext()) {
LFXLight theDevice = devices.next();
LifxDevice aNewLifxDevice = new LifxDevice(theDevice, LifxDevice.LIGHT_TYPE);
lifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
return true;
}
private Boolean addLifxGroups(LFXGroupCollection theDeviceList) {
if(!validLifx)
return false;
Iterator<LFXGroup> devices = theDeviceList.iterator();;
while(devices.hasNext()) {
LFXGroup theDevice = devices.next();
LifxDevice aNewLifxDevice = new LifxDevice(theDevice, LifxDevice.GROUP_TYPE);
lifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
return true;
}
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri, Integer targetBriInc, DeviceDescriptor device, String body) {
String theReturn = null;
float aBriValue;
float theValue;
log.debug("executing HUE api request to send message to LifxDevice: " + anItem.getItem().toString());
if(!validLifx) {
log.warn("Should not get here, no LifxDevice clients configured");
theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Should not get here, no LifxDevices configured\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else {
LifxEntry lifxCommand = null;
if(anItem.getItem().isJsonObject())
lifxCommand = aGsonHandler.fromJson(anItem.getItem(), LifxEntry.class);
else
lifxCommand = aGsonHandler.fromJson(anItem.getItem().getAsString(), LifxEntry.class);
LifxDevice theDevice = getLifxDevice(lifxCommand.getName());
if (theDevice == null) {
log.warn("Should not get here, no LifxDevices available");
theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId
+ "\",\"description\": \"Should not get here, no Lifx clients available\", \"parameter\": \"/lights/"
+ lightId + "state\"}}]";
} else {
log.debug("calling LifxDevice: " + lifxCommand.getName());
if(theDevice.getType().equals(LifxDevice.LIGHT_TYPE)) {
LFXLight theLight = (LFXLight)theDevice.getLifxObject();
if(body.contains("true"))
theLight.setPower(true);
if(body.contains("false"))
theLight.setPower(false);
if(targetBri != null || targetBriInc != null) {
aBriValue = (float)BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc);
theValue = aBriValue/DIM_DIVISOR;
if(theValue > (float)1.0)
theValue = (float)0.99;
theLight.setBrightness(theValue);
}
} else if (theDevice.getType().equals(LifxDevice.GROUP_TYPE)) {
LFXGroup theGroup = (LFXGroup)theDevice.getLifxObject();
if(body.contains("true"))
theGroup.setPower(true);
if(body.contains("false"))
theGroup.setPower(false);
}
}
}
return theReturn;
}
@Override
public void closeHome() {
if(!validLifx)
return;
client.close();
}
private static class MyLightListener implements LFXLightCollectionListener {
private static final Logger log = LoggerFactory.getLogger(MyLightListener.class);
private Map<String, LifxDevice> aLifxMap;
public MyLightListener(Map<String, LifxDevice> theMap) {
aLifxMap = theMap;
}
@Override
public void lightAdded(LFXLight light) {
log.debug("Light added, label: " + light.getLabel() + " and id: " + light.getID());
LifxDevice aNewLifxDevice = new LifxDevice(light, LifxDevice.LIGHT_TYPE);
aLifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
@Override
public void lightRemoved(LFXLight light) {
log.debug("Light removed, label: " + light.getLabel() + " and id: " + light.getID());
aLifxMap.remove(light.getLabel());
}
}
private static class MyGroupListener implements LFXGroupCollectionListener {
private static final Logger log = LoggerFactory.getLogger(MyLightListener.class);
private Map<String, LifxDevice> aLifxMap;
public MyGroupListener(Map<String, LifxDevice> theMap) {
aLifxMap = theMap;
}
@Override
public void groupAdded(LFXGroup group) {
log.debug("Group: " + group.getLabel() + " added: " + group.size());
LifxDevice aNewLifxDevice = new LifxDevice(group, LifxDevice.GROUP_TYPE);
aLifxMap.put(aNewLifxDevice.toEntry().getName(), aNewLifxDevice);
}
@Override
public void groupRemoved(LFXGroup group) {
log.debug("Group: " + group.getLabel() + " removed");
aLifxMap.remove(group.getLabel());
}
}
}

View File

@@ -1,5 +1,6 @@
package com.bwssystems.HABridge.plugins.mqtt;
import org.apache.commons.lang3.StringEscapeUtils;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
@@ -47,7 +48,7 @@ public class MQTTHandler {
}
public void publishMessage(String topic, String content) {
MqttMessage message = new MqttMessage(content.getBytes());
MqttMessage message = new MqttMessage(StringEscapeUtils.unescapeJava(content).getBytes());
message.setQos(qos);
try {
myClient.publish(topic, message);

View File

@@ -1,12 +1,17 @@
package com.bwssystems.HABridge.plugins.tcp;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,6 +26,7 @@ import com.bwssystems.HABridge.hue.TimeDecode;
public class TCPHome implements Home {
private static final Logger log = LoggerFactory.getLogger(TCPHome.class);
private byte[] sendData;
private Map<String, Socket> theSockets;
public TCPHome(BridgeSettingsDescriptor bridgeSettings) {
@@ -31,48 +37,68 @@ public class TCPHome implements Home {
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
Socket dataSendSocket = null;
log.debug("executing HUE api request to TCP: " + anItem.getItem().getAsString());
String intermediate = anItem.getItem().getAsString().substring(anItem.getItem().getAsString().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
String port = null;
InetAddress IPAddress = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && theUrl.startsWith("tcp://")) {
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;
InetAddress IPAddress = null;
dataSendSocket = theSockets.get(hostPortion);
if(dataSendSocket == null) {
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
// noop
}
try {
dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
theSockets.put(hostPortion, dataSendSocket);
} catch (Exception e) {
// noop
}
}
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes();
}
try {
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
} catch (Exception e) {
log.warn("Could not send data to TCP socket <<<" + e.getMessage() + ">>>, closing socket: " + theUrl);
try {
dataSendSocket.close();
} catch (IOException e1) {
// noop
}
theSockets.remove(hostPortion);
}
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
// noop
}
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
sendData = theUrlBody.getBytes();
}
try {
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
dataSendSocket.close();
} catch (Exception e) {
// noop
}
log.warn("Tcp Call to be presented as tcp://<ip_address>:<port>/payload, format of request unknown: " + theUrl);
return null;
}
@Override
public Home createHome(BridgeSettingsDescriptor bridgeSettings) {
log.info("TCP Home created.");
theSockets = new HashMap<String, Socket>();
return this;
}
@@ -84,8 +110,18 @@ public class TCPHome implements Home {
@Override
public void closeHome() {
// noop
log.debug("Shutting down TCP sockets.");
if(theSockets != null && !theSockets.isEmpty()) {
Iterator<String> keys = theSockets.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
try {
theSockets.get(key).close();
} catch (IOException e) {
// noop
}
}
}
}
}

View File

@@ -6,6 +6,7 @@ import java.net.UnknownHostException;
import javax.xml.bind.DatatypeConverter;
import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,39 +34,45 @@ public class UDPHome implements Home {
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri,Integer targetBriInc, DeviceDescriptor device, String body) {
log.debug("executing HUE api request to UDP: " + anItem.getItem().getAsString());
String intermediate = anItem.getItem().getAsString().substring(anItem.getItem().getAsString().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
String port = null;
InetAddress IPAddress = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
String theUrl = anItem.getItem().getAsString();
if(theUrl != null && !theUrl.isEmpty () && theUrl.startsWith("udp://")) {
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;
InetAddress IPAddress = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
log.warn("Udp Call, unknown host, continuing...");
return null;
}
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes();
}
try {
theUDPDatagramSender.sendUDPResponse(sendData, IPAddress, Integer.parseInt(port));
} catch (NumberFormatException e) {
log.warn("Udp Call, Number format exception on port, continuing...");
} catch (IOException e) {
log.warn("IO exception on udp call, continuing...");
}
} else
hostAddr = hostPortion;
try {
IPAddress = InetAddress.getByName(hostAddr);
} catch (UnknownHostException e) {
log.warn("Udp Call, unknown host, continuing...");
return null;
}
log.warn("Udp Call to be presented as udp://<ip_address>:<port>/payload, format of request unknown: " + theUrl);
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
sendData = theUrlBody.getBytes();
}
try {
theUDPDatagramSender.sendUDPResponse(sendData, IPAddress, Integer.parseInt(port));
} catch (NumberFormatException e) {
log.warn("Udp Call, Number format exception on port, continuing...");
} catch (IOException e) {
log.warn("IO exception on udp call, continuing...");
}
return null;
}

View File

@@ -49,6 +49,11 @@ app.config (function ($locationProvider, $routeProvider) {
templateUrl: 'views/somfydevice.html',
controller: 'SomfyController'
}).otherwise ({
controller: 'DomoticzController'
}).when ('/lifxdevices', {
templateUrl: 'views/lifxdevice.html',
controller: 'LifxController'
}).otherwise ({
templateUrl: 'views/configuration.html',
controller: 'ViewingController'
})
@@ -74,7 +79,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: [], mapTypes: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, showMqtt: false, showHass: false, showDomoticz: false, showSomfy: false, habridgeversion: ""};
this.state = {base: "./api/devices", bridgelocation: ".", systemsbase: "./system", huebase: "./api", configs: [], backups: [], devices: [], device: {}, mapandid: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], mapTypes: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, showHal: false, showMqtt: false, showHass: false, showDomoticz: false, showSomfy: false, showLifx: false, habridgeversion: ""};
this.displayWarn = function(errorTitle, error) {
var toastContent = errorTitle;
@@ -265,6 +270,11 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
return;
}
this.updateShowLifx = function () {
this.state.showLifx = self.state.settings.lifxconfigured;
return;
}
this.loadBridgeSettings = function () {
return $http.get(this.state.systemsbase + "/settings").then(
function (response) {
@@ -278,6 +288,7 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
self.updateShowHass();
self.updateShowDomoticz();
self.updateShowSomfy();
self.updateShowLifx();
},
function (error) {
self.displayWarn("Load Bridge Settings Error: ", error);
@@ -473,6 +484,19 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
};
this.viewLifxDevices = function () {
if (!this.state.showLifx)
return;
return $http.get(this.state.base + "/lifx/devices").then(
function (response) {
self.state.lifxdevices = response.data;
},
function (error) {
self.displayWarn("Get Lifx Devices Error: ", error);
}
);
};
this.formatCallItem = function (currentItem) {
if(!currentItem.startsWith("{\"item") && !currentItem.startsWith("[{\"item")) {
if (currentItem.startsWith("[") || currentItem.startsWith("{"))
@@ -1247,7 +1271,7 @@ app.controller('VeraController', function ($scope, $location, $http, bridgeServi
$scope.device = bridgeService.state.device;
};
$scope.buildDeviceUrls = function (veradevice, dim_control) {
$scope.buildDeviceUrls = function (veradevice, dim_control, buildonly) {
if(dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0) {
dimpayload = "http://" + veradevice.veraaddress + ":" + $scope.vera.port
+ "/data_request?id=action&output_format=json&DeviceNum="
@@ -1268,8 +1292,10 @@ app.controller('VeraController', function ($scope, $location, $http, bridgeServi
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, veradevice.id, veradevice.name, veradevice.veraname, "switch", "veraDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildSceneUrls = function (verascene) {
@@ -1288,10 +1314,11 @@ app.controller('VeraController', function ($scope, $location, $http, bridgeServi
$scope.bulkAddDevices = function(dim_control) {
var devicesList = [];
$scope.clearDevice();
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.veradevices.length; x++) {
if(bridgeService.state.veradevices[x].id === $scope.bulk.devices[i]) {
$scope.buildDeviceUrls(bridgeService.state.veradevices[x],dim_control);
$scope.buildDeviceUrls(bridgeService.state.veradevices[x],dim_control,true);
devicesList[i] = {
name: $scope.device.name,
mapId: $scope.device.mapId,
@@ -1308,6 +1335,7 @@ app.controller('VeraController', function ($scope, $location, $http, bridgeServi
contentBodyDim: $scope.device.contentBodyDim,
contentBodyOff: $scope.device.contentBodyOff
};
$scope.clearDevice();
}
}
}
@@ -1351,7 +1379,7 @@ app.controller('VeraController', function ($scope, $location, $http, bridgeServi
else {
$scope.selectAll = true;
for(var x = 0; x < bridgeService.state.veradevices.length; x++) {
if($scope.bulk.devices.indexOf(bridgeService.state.veradevices[x]) < 0 && !bridgeService.findDeviceByMapId(bridgeService.state.veradevices[x].id, bridgeService.state.veradevices[x].veraname, "veraDevice"))
if($scope.bulk.devices.indexOf(bridgeService.state.veradevices[x]) < 0)
$scope.bulk.devices.push(bridgeService.state.veradevices[x].id);
}
}
@@ -1561,16 +1589,19 @@ app.controller('HueController', function ($scope, $location, $http, bridgeServic
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);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.bulkAddDevices = function() {
var devicesList = [];
$scope.clearDevice();
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.huedevices.length; x++) {
if(bridgeService.state.huedevices[x].device.uniqueid === $scope.bulk.devices[i]) {
$scope.buildDeviceUrls(bridgeService.state.huedevices[x]);
$scope.buildDeviceUrls(bridgeService.state.huedevices[x],true);
devicesList[i] = {
name: $scope.device.name,
mapId: $scope.device.mapId,
@@ -1587,6 +1618,7 @@ app.controller('HueController', function ($scope, $location, $http, bridgeServic
contentBodyDim: $scope.device.contentBodyDim,
contentBodyOff: $scope.device.contentBodyOff
};
$scope.clearDevice();
}
}
}
@@ -1674,7 +1706,7 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
$scope.device = bridgeService.state.device;
};
$scope.buildDeviceUrls = function (haldevice, dim_control) {
$scope.buildDeviceUrls = function (haldevice, dim_control, buildonly) {
var preOnCmd = "";
var preDimCmd = "";
var preOffCmd = "";
@@ -1729,11 +1761,13 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
+ postCmd;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname, haldevice.haldevicename, haldevice.halname, aDeviceType, "halDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildButtonUrls = function (haldevice, onbutton, offbutton) {
$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;
@@ -1741,20 +1775,24 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-" + actionOn.DeviceName, haldevice.haldevicename, haldevice.halname, "button", "halButton", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildHALHomeUrls = function (haldevice) {
$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);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildHALHeatUrls = function (haldevice) {
$scope.buildHALHeatUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
@@ -1772,11 +1810,13 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
+ $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetHeat", haldevice.haldevicename + " Heat", haldevice.halname, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildHALCoolUrls = function (haldevice) {
$scope.buildHALCoolUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
@@ -1794,11 +1834,13 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
+ $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetCool", haldevice.haldevicename + " Cool", haldevice.halname, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildHALAutoUrls = function (haldevice) {
$scope.buildHALAutoUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
@@ -1810,11 +1852,13 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
+ "!HVACMode=Off?Token="
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetAuto", haldevice.haldevicename + " Auto", haldevice.halname, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildHALOffUrls = function (haldevice) {
$scope.buildHALOffUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
@@ -1827,11 +1871,13 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
$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);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.buildHALFanUrls = function (haldevice) {
$scope.buildHALFanUrls = function (haldevice, buildonly) {
onpayload = "http://" + haldevice.haladdress
+ "/HVACService!HVACCmd=Set!HVACName="
+ haldevice.haldevicename.replaceAll(" ", "%20")
@@ -1844,21 +1890,24 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
+ $scope.bridge.settings.haltoken;
bridgeService.buildUrls(onpayload, null, offpayload, false, haldevice.haldevicename + "-" + haldevice.halname + "-SetFan", haldevice.haldevicename + " Fan", haldevice.halname, "thermo", "halThermoSet", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.bulkAddDevices = function(dim_control) {
var devicesList = [];
$scope.clearDevice();
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.haldevices.length; x++) {
if(bridgeService.state.haldevices[x].haldevicename === $scope.bulk.devices[i]) {
if(bridgeService.state.haldevices[x].haldevicetype === "HVAC")
$scope.buildHALAutoUrls(bridgeService.state.haldevices[x]);
$scope.buildHALAutoUrls(bridgeService.state.haldevices[x], true);
else if(bridgeService.state.haldevices[x].haldevicetype === "HOME")
$scope.buildHALHomeUrls(bridgeService.state.haldevices[x]);
$scope.buildHALHomeUrls(bridgeService.state.haldevices[x], true);
else
$scope.buildDeviceUrls(bridgeService.state.haldevices[x],dim_control);
$scope.buildDeviceUrls(bridgeService.state.haldevices[x],dim_control, true);
devicesList[i] = {
name: $scope.device.name,
mapId: $scope.device.mapId,
@@ -1875,6 +1924,7 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
contentBodyDim: $scope.device.contentBodyDim,
contentBodyOff: $scope.device.contentBodyOff
};
$scope.clearDevice();
}
}
}
@@ -1917,7 +1967,7 @@ app.controller('HalController', function ($scope, $location, $http, bridgeServic
else {
$scope.selectAll = true;
for(var x = 0; x < bridgeService.state.haldevices.length; x++) {
if($scope.bulk.devices.indexOf(bridgeService.state.haldevices[x]) < 0 && !bridgeService.findDeviceByMapId(bridgeService.state.haldevices[x].haldevicename + "-" + bridgeService.state.haldevices[x].halname, bridgeService.state.haldevices[x].halname, "halDevice"))
if($scope.bulk.devices.indexOf(bridgeService.state.haldevices[x]) < 0)
$scope.bulk.devices.push(bridgeService.state.haldevices[x].haldevicename);
}
}
@@ -2006,7 +2056,7 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
$scope.device = bridgeService.state.device;
};
$scope.buildDeviceUrls = function (hassdevice, dim_control) {
$scope.buildDeviceUrls = function (hassdevice, dim_control, buildonly) {
onpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}";
@@ -2016,89 +2066,19 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
};
$scope.buildHassHeatUrls = function (hassdevice) {
onpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}";
else
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
offpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"}";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
};
$scope.buildHassCoolUrls = function (hassdevice) {
onpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}";
else
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
offpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"}";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
};
$scope.buildHassAutoUrls = function (hassdevice) {
onpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}";
else
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
offpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"}";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
};
$scope.buildHassOffUrls = function (hassdevice) {
onpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}";
else
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
offpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"}";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
};
$scope.buildHassFanUrls = function (hassdevice) {
onpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
if((dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0))
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\",\"bri\":\"" + dim_control + "\"}";
else
dimpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"on\"}";
offpayload = "{\"entityId\":\"" + hassdevice.deviceState.entity_id + "\",\"hassName\":\"" + hassdevice.hassname + "\",\"state\":\"off\"}";
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, hassdevice.hassname + "-" + hassdevice.deviceState.entity_id, hassdevice.deviceState.entity_id, hassdevice.hassname, hassdevice.domain, "hassDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.bulkAddDevices = function(dim_control) {
var devicesList = [];
$scope.clearDevice();
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.hassdevices.length; x++) {
if(bridgeService.state.hassdevices[x].deviceName === $scope.bulk.devices[i]) {
if(bridgeService.state.hassdevices[x].domain === "climate")
$scope.buildHassAutoUrls(bridgeService.state.hassdevices[x]);
else
$scope.buildDeviceUrls(bridgeService.state.hassdevices[x],dim_control);
if(bridgeService.state.hassdevices[x].deviceState.entity_id === $scope.bulk.devices[i] && bridgeService.state.hassdevices[x].domain !== "sensor" && bridgeService.state.hassdevices[x].domain !== "sun") {
$scope.buildDeviceUrls(bridgeService.state.hassdevices[x],dim_control,true);
devicesList[i] = {
name: $scope.device.name,
mapId: $scope.device.mapId,
@@ -2115,6 +2095,7 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
contentBodyDim: $scope.device.contentBodyDim,
contentBodyOff: $scope.device.contentBodyOff
};
$scope.clearDevice();
}
}
}
@@ -2122,7 +2103,7 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
function () {
$scope.clearDevice();
bridgeService.viewDevices();
bridgeService.viewhassdevices();
bridgeService.viewHassDevices();
},
function (error) {
bridgeService.displayWarn("Error adding Hass devices in bulk.", error)
@@ -2157,8 +2138,8 @@ app.controller('HassController', function ($scope, $location, $http, bridgeServi
else {
$scope.selectAll = true;
for(var x = 0; x < bridgeService.state.hassdevices.length; x++) {
if($scope.bulk.devices.indexOf(bridgeService.state.hassdevices[x]) < 0 && !bridgeService.findDeviceByMapId(bridgeService.state.hassdevices[x].hassdevicename + "-" + bridgeService.state.hassdevices[x].halname, bridgeService.state.hassdevices[x].halname, "hassdevice"))
$scope.bulk.devices.push(bridgeService.state.hassdevices[x].hassdevicename);
if($scope.bulk.devices.indexOf(bridgeService.state.hassdevices[x].deviceState.entity_id) < 0 && bridgeService.state.hassdevices[x].domain !== "sensor" && bridgeService.state.hassdevices[x].domain !== "sun")
$scope.bulk.devices.push(bridgeService.state.hassdevices[x].deviceState.entity_id);
}
}
};
@@ -2201,7 +2182,7 @@ app.controller('DomoticzController', function ($scope, $location, $http, bridgeS
$scope.device = bridgeService.state.device;
};
$scope.buildDeviceUrls = function (domoticzdevice, dim_control) {
$scope.buildDeviceUrls = function (domoticzdevice, dim_control, buildonly) {
var preCmd = "";
var postOnCmd = "";
var postDimCmd = "";
@@ -2240,16 +2221,19 @@ app.controller('DomoticzController', function ($scope, $location, $http, bridgeS
+ postOffCmd;
bridgeService.buildUrls(onpayload, dimpayload, offpayload, false, domoticzdevice.devicename + "-" + domoticzdevice.domoticzname, domoticzdevice.devicename, domoticzdevice.domoticzname, aDeviceType, "domoticzDevice", null, null);
$scope.device = bridgeService.state.device;
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.bulkAddDevices = function(dim_control) {
var devicesList = [];
$scope.clearDevice();
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.domoticzdevices.length; x++) {
if(bridgeService.state.domoticzdevices[x].devicename === $scope.bulk.devices[i]) {
$scope.buildDeviceUrls(bridgeService.state.domoticzdevices[x],dim_control);
$scope.buildDeviceUrls(bridgeService.state.domoticzdevices[x],dim_control,true);
devicesList[i] = {
name: $scope.device.name,
mapId: $scope.device.mapId,
@@ -2266,6 +2250,7 @@ app.controller('DomoticzController', function ($scope, $location, $http, bridgeS
contentBodyDim: $scope.device.contentBodyDim,
contentBodyOff: $scope.device.contentBodyOff
};
$scope.clearDevice();
}
}
}
@@ -2276,7 +2261,7 @@ app.controller('DomoticzController', function ($scope, $location, $http, bridgeS
bridgeService.viewHalDevices();
},
function (error) {
bridgeService.displayWarn("Error adding HAL devices in bulk.", error)
bridgeService.displayWarn("Error adding Domoticz devices in bulk.", error)
}
);
$scope.bulk = { devices: [] };
@@ -2308,8 +2293,130 @@ app.controller('DomoticzController', function ($scope, $location, $http, bridgeS
else {
$scope.selectAll = true;
for(var x = 0; x < bridgeService.state.haldevices.length; x++) {
if($scope.bulk.devices.indexOf(bridgeService.state.haldevices[x]) < 0 && !bridgeService.findDeviceByMapId(bridgeService.state.haldevices[x].haldevicename + "-" + bridgeService.state.haldevices[x].halname, bridgeService.state.haldevices[x].halname, "halDevice"))
$scope.bulk.devices.push(bridgeService.state.haldevices[x].haldevicename);
if($scope.bulk.devices.indexOf(bridgeService.state.domoticzdevices[x]) < 0)
$scope.bulk.devices.push(bridgeService.state.domoticzdevices[x].devicename);
}
}
};
$scope.toggleButtons = function () {
$scope.buttonsVisible = !$scope.buttonsVisible;
if($scope.buttonsVisible)
$scope.imgButtonsUrl = "glyphicon glyphicon-minus";
else
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
};
$scope.deleteDevice = function (device) {
$scope.bridge.device = device;
ngDialog.open({
template: 'deleteDialog',
controller: 'DeleteDialogCtrl',
className: 'ngdialog-theme-default'
});
};
$scope.editDevice = function (device) {
bridgeService.editDevice(device);
$location.path('/editdevice');
};
});
app.controller('LifxController', function ($scope, $location, $http, bridgeService, ngDialog) {
$scope.bridge = bridgeService.state;
$scope.device = bridgeService.state.device;
$scope.device_dim_control = "";
$scope.bulk = { devices: [] };
$scope.selectAll = false;
bridgeService.viewLifxDevices();
$scope.imgButtonsUrl = "glyphicon glyphicon-plus";
$scope.buttonsVisible = false;
$scope.clearDevice = function () {
bridgeService.clearDevice();
$scope.device = bridgeService.state.device;
};
$scope.buildDeviceUrls = function (lifxdevice, dim_control, buildonly) {
dimpayload = angular.toJson(lifxdevice);
onpayload = angular.toJson(lifxdevice);
offpayload = angular.toJson(lifxdevice);
bridgeService.buildUrls(onpayload, dimpayload, offpayload, true, lifxdevice.name, lifxdevice.name, lifxdevice.name, null, "lifxDevice", null, null);
$scope.device = bridgeService.state.device;
if (!buildonly) {
bridgeService.editNewDevice($scope.device);
$location.path('/editdevice');
}
};
$scope.bulkAddDevices = function(dim_control) {
var devicesList = [];
$scope.clearDevice();
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.lifxdevices.length; x++) {
if(bridgeService.state.lifxdevices[x].devicename === $scope.bulk.devices[i]) {
$scope.buildDeviceUrls(bridgeService.state.lifxdevices[x],dim_control,true);
devicesList[i] = {
name: $scope.device.name,
mapId: $scope.device.mapId,
mapType: $scope.device.mapType,
deviceType: $scope.device.deviceType,
targetDevice: $scope.device.targetDevice,
onUrl: $scope.device.onUrl,
dimUrl: $scope.device.dimUrl,
offUrl: $scope.device.offUrl,
headers: $scope.device.headers,
httpVerb: $scope.device.httpVerb,
contentType: $scope.device.contentType,
contentBody: $scope.device.contentBody,
contentBodyDim: $scope.device.contentBodyDim,
contentBodyOff: $scope.device.contentBodyOff
};
$scope.clearDevice();
}
}
}
bridgeService.bulkAddDevice(devicesList).then(
function () {
$scope.clearDevice();
bridgeService.viewDevices();
bridgeService.viewHalDevices();
},
function (error) {
bridgeService.displayWarn("Error adding LIFX devices in bulk.", error)
}
);
$scope.bulk = { devices: [] };
$scope.selectAll = false;
};
$scope.toggleSelection = function toggleSelection(deviceId) {
var idx = $scope.bulk.devices.indexOf(deviceId);
// is currently selected
if (idx > -1) {
$scope.bulk.devices.splice(idx, 1);
if($scope.bulk.devices.length === 0 && $scope.selectAll)
$scope.selectAll = false;
}
// is newly selected
else {
$scope.bulk.devices.push(deviceId);
$scope.selectAll = true;
}
};
$scope.toggleSelectAll = function toggleSelectAll() {
if($scope.selectAll) {
$scope.selectAll = false;
$scope.bulk = { devices: [] };
}
else {
$scope.selectAll = true;
for(var x = 0; x < bridgeService.state.haldevices.length; x++) {
if($scope.bulk.devices.indexOf(bridgeService.state.lifxdevices[x]) < 0)
$scope.bulk.devices.push(bridgeService.state.lifxdevices[x].devicename);
}
}
};
@@ -2509,8 +2616,11 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
bridgeService.displayWarn("Error adding device. Name has not been changed from original.", null);
return;
}
if (copy)
if (copy) {
$scope.device.id = null;
$scope.device.uniqueid = null;
$scope.device.mapId = $scope.device.mapId + "-copy";
}
if($scope.mapTypeSelected !== undefined && $scope.mapTypeSelected !== null)
$scope.device.mapType = $scope.mapTypeSelected[0];
else
@@ -2733,6 +2843,20 @@ app.filter('configuredDomoticzItems', function (bridgeService) {
}
});
app.filter('configuredLifxItems', function (bridgeService) {
return function(input) {
var out = [];
if(input === undefined || input === null || input.length === undefined)
return out;
for (var i = 0; i < input.length; i++) {
if (bridgeService.deviceContainsType(input[i], "lifx")) {
out.push(input[i]);
}
}
return out;
}
});
app.filter('configuredSomfyDevices', function (bridgeService) {
return function(input) {
var out = [];
@@ -2752,4 +2876,4 @@ app.filter('configuredSomfyDevices', function (bridgeService) {
app.controller('VersionController', function ($scope, bridgeService) {
$scope.bridge = bridgeService.state;
});
});

View File

@@ -21,6 +21,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>

View File

@@ -19,6 +19,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li role="presentation" class="active"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -36,17 +37,18 @@
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Domoticz Devices' list below will show what
is already setup for your Domoticz.</p>
<p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the Domoticz.</p>
@@ -76,7 +78,7 @@
<td>{{domoticzdevice.domoticzname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(domoticzdevice, device_dim_control)">Build Item</button>
ng-click="buildDeviceUrls(domoticzdevice, device_dim_control,false)">Build Item</button>
</td>
</tr>
</table>

View File

@@ -21,6 +21,7 @@
href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation" class="active"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -33,7 +34,7 @@
fields the bridge uses. Please use care when updating these fields as
you may break the settings used by the bridge to call a specific end
point device.</p>
<p class="text-muted">This area allows you to create any http or
<p class="text-muted">This area allows you to create any http, tcp or
udp call to an endpoint. You can use the default GET or select the
http verb type below and configure a payload for either on, dim or
off methods. For Execution of a
@@ -41,10 +42,10 @@
can use Json notation of array with [{&quot;item&quot;:&quot;the
payload&quot;},{&quot;item&quot;:&quot;another payload&quot;}] to
execute multiple entries. Adding the value replacements
(${intensity..byte},${intensity.percent},${intensity.math(X*1)}) will
(${intensity.byte},${intensity.percent},${intensity.decimal_percent},${intensity.math(X*1)}) will
also work. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command.</p>
<p>When copying, update the name and select the "Add Bridge
<p class="text-muted">When copying, update the name and select the "Add Bridge
Device" Button.</p>
<form name="form">
@@ -65,17 +66,22 @@
ng-model="device.name" placeholder="Device Name"></td>
</tr>
<tr>
<td>Inactive</td>
<td><label>Inactive</label></td>
<td><input type="checkbox"
ng-model="device.inactive" ng-true-value=true
ng-false-value=false> {{device.inactive}}</td>
</tr>
<tr>
<td>No State (Do not update state for device)</td>
<td><label>No State (Do not update state for device)</label></td>
<td><input type="checkbox"
ng-model="device.noState" ng-true-value=true
ng-false-value=false> {{device.noState}}</td>
</tr>
<tr>
<td><label>Filter Address (comma separated list)</label></td>
<td><input type="text" class="form-control" id="device-requester-addr"
ng-model="device.requesterAddress" placeholder="Only use if you want to restrict this device to a specific caller(s)"></td>
</tr>
<tr>
<td><label>Target</label></td>
@@ -130,6 +136,7 @@
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<div class="col-xs-12 col-md-4">
<th>Type</th>
<th>Target Item</th>
<th>Delay</th>
@@ -140,19 +147,21 @@
<th>Http Headers</th>
<th>Content Type</th>
<th>Manage</th>
</div>
</tr>
</thead>
<tr ng-repeat="onItem in onDevices">
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="onItem.type"></select></td>
<td><textarea rows="1" cols="20" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-target" ng-model="onItem.item" placeholder="The Call"></textarea></td>
<td><textarea rows="1" cols="4" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-delay" ng-model="onItem.delay" placeholder="millis"></textarea></td>
<td><textarea rows="1" cols="2" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-count" ng-model="onItem.count" placeholder="number"></textarea></td>
<td><textarea rows="1" cols="16" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-filterIPs" ng-model="onItem.filterIPs"
placeholder="restrict IPs"></textarea></td>
<td><select name="item-http-verb" id="item-http-verb"
@@ -163,7 +172,7 @@
<option value="PUT">PUT</option>
<option value="POST">POST</option>
</select></td>
<td><textarea rows="1" cols="16" class="form-control"
<td><textarea rows="1" class="form-control"
id="item-httpBody" ng-model="onItem.httpBody"
placeholder="body args"></textarea></td>
<td><textarea rows="1" cols="16" class="form-control"
@@ -189,8 +198,10 @@
</select></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeItemOn(onItem)">Del</button></td>
</div>
</tr>
<tr>
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="newOnItem.type"></select></td>
@@ -240,6 +251,7 @@
</select></td>
<td><button class="btn btn-success" type="submit"
ng-click="addItemOn(newOnItem)">Add</button></td>
</div>
</tr>
</table>
</scrollable-table></td>
@@ -251,6 +263,7 @@
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<div class="col-xs-12 col-md-4">
<th>Type</th>
<th>Target Item</th>
<th>Delay</th>
@@ -261,9 +274,11 @@
<th>Http Headers</th>
<th>Content Type</th>
<th>Manage</th>
</div>
</tr>
</thead>
<tr ng-repeat="dimItem in dimDevices">
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="dimItem.type"></select></td>
@@ -311,8 +326,10 @@
</select></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeItemDim(dimItem)">Del</button></td>
</div>
</tr>
<tr>
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="newDimItem.type"></select></td>
@@ -362,6 +379,7 @@
</select></td>
<td><button class="btn btn-success" type="submit"
ng-click="addItemDim(newDimItem)">Add</button></td>
</div>
</tr>
</table>
</scrollable-table></td>
@@ -373,6 +391,7 @@
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<div class="col-xs-12 col-md-4">
<th>Type</th>
<th>Target Item</th>
<th>Delay</th>
@@ -383,9 +402,11 @@
<th>Http Headers</th>
<th>Content Type</th>
<th>Manage</th>
</div>
</tr>
</thead>
<tr ng-repeat="offItem in offDevices">
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="offItem.type"></select></td>
@@ -433,8 +454,10 @@
</select></td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeItemOff(offItem)">Del</button></td>
</div>
</tr>
<tr>
<div class="col-xs-12 col-md-4">
<td><select
ng-options="mapType as mapType[1] for mapType in bridge.mapTypes track by mapType[0]"
ng-model="newOffItem.type"></select></td>
@@ -484,6 +507,7 @@
</select></td>
<td><button class="btn btn-success" type="submit"
ng-click="addItemOff(newOffItem)">Add</button></td>
</div>
</tr>
</table>
</scrollable-table></td>

View File

@@ -18,6 +18,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -35,17 +36,18 @@
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured HAL Devices' list below will show what
is already setup for your HAL.</p>
<p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the HAL.</p>
@@ -89,27 +91,27 @@
</td>
<td>
<button ng-if="haldevice.haldevicetype != 'Home' && haldevice.haldevicetype != 'HVAC' && haldevice.haldevicetype != 'IrData'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(haldevice, device_dim_control)">Build Item</button>
ng-click="buildDeviceUrls(haldevice, device_dim_control, false)">Build Item</button>
<button ng-if="haldevice.haldevicetype == 'Home'" class="btn btn-success" type="submit"
ng-click="buildHALHomeUrls(haldevice)">Build Home/Away</button>
ng-click="buildHALHomeUrls(haldevice, false)">Build Home/Away</button>
<button ng-if="haldevice.haldevicetype == 'IrData'" class="btn btn-success" type="submit"
ng-click="buildButtonUrls(haldevice, button_on, button_off)">Build
ng-click="buildButtonUrls(haldevice, button_on, button_off, false)">Build
A Button</button>
<ul ng-if="haldevice.haldevicetype == 'HVAC'" class="list-group">
<li class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildHALHeatUrls(haldevice)">Heat</button>
ng-click="buildHALHeatUrls(haldevice, false)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALCoolUrls(haldevice)">Cool</button>
ng-click="buildHALCoolUrls(haldevice, false)">Cool</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALAutoUrls(haldevice)">Auto</button>
ng-click="buildHALAutoUrls(haldevice, false)">Auto</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildHALOffUrls(haldevice)">Off</button>
ng-click="buildHALOffUrls(haldevice, false)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildHALFanUrls(haldevice)">Fan</button>
ng-click="buildHALFanUrls(haldevice, false)">Fan</button>
</p>
</li>
</ul>

View File

@@ -19,6 +19,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>

View File

@@ -19,6 +19,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>

View File

@@ -13,12 +13,13 @@
<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" class="active"><a href="#!/haldevices">HAL
<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="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -36,17 +37,18 @@
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured HomeAssistant Devices' list below will show what
is already setup for your HomeAssitant.</p>
<p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated, BUT for Home Assistant, the selection should be Pass-thru: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the HomeAssitant.</p>
@@ -73,26 +75,8 @@
{{hassdevice.deviceState.entity_id}}</td>
<td>{{hassdevice.hassname}}</td>
<td>
<button ng-if="hassdevice.domain != 'climate' && hassdevice.domain != 'sensor' && hassdevice.domain != 'sun'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(hassdevice, device_dim_control)">Build Item</button>
<ul ng-if="hassdevice.domain == 'climate'" class="list-group">
<li class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildhassHeatUrls(hassdevice)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildhassCoolUrls(hassdevice)">Cool</button>
<button class="btn btn-success" type="submit"
ng-click="buildhassAutoUrls(hassdevice)">Auto</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildhassOffUrls(hassdevice)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildhassFanUrls(hassdevice)">Fan</button>
</p>
</li>
</ul>
<button ng-if="hassdevice.domain != 'sensor' && hassdevice.domain != 'sun'" class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(hassdevice, device_dim_control, false)">Build Item</button>
</td>
</tr>
</table>

View File

@@ -19,6 +19,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -36,9 +37,9 @@
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Hue Devices' list below will show what
is already setup for your Hue.</p>
<p>Use the check boxes by the names to use the bulk addition
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items, then click bulk add below. Your items
will be added with on and off or dim and off if selected with the
will be added with on, off and dim with the
name of the device from the Hue.</p>
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
@@ -65,7 +66,7 @@
<td>{{huedevice.huename}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(huedevice)">Build Item</button>
ng-click="buildDeviceUrls(huedevice, false)">Build Item</button>
</td>
</tr>
</table>

View File

@@ -0,0 +1,122 @@
<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 ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</a></li>
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li role="presentation" class="active"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">LIFX Devices</h2>
</div>
<div class="panel-body">
<p class="text-muted">For any LIFX Device, use the build action buttons
to generate the item addition information into the ha-bridge device and this will put you into the edit screen. Then
you can modify the name to anything you want that will be the keyword
for the Echo or Google Home. Also, you can go back to any helper tab and click a build
action button to add another item for a multi-command. After you are
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured LIFX Devices' list below will show what
is already setup for your LIFX.</p>
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on, off and dim
with the name of the device from the LIFX device.</p>
<scrollable-table watch="bridge.lifxdevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">
<span><input type="checkbox" name="selectAll"
value="{{selectAll}}"
ng-checked="selectAll"
ng-click="toggleSelectAll()"> Name</span></th>
<th sortable-header col="id">ID</th>
<th sortable-header col="type">Type</th>
<th>Build Actions</th>
</tr>
</thead>
<tr ng-repeat="lifxdev in bridge.lifxdevices">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]"
value="{{lifxdev.name}}"
ng-checked="bulk.devices.indexOf(lifxdev.name) > -1"
ng-click="toggleSelection(lifxdev.name)">{{lifxdev.name}}</td>
<td>{{lifxdev.id}}</td>
<td>{{lifxdev.type}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(lifxdev, false)">Build
Item</button>
</td>
</tr>
</table>
</scrollable-table>
<div class="panel-footer">
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add
({{bulk.devices.length}})</button>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
Already Configured LIFX Devices <a ng-click="toggleButtons()"><span
class={{imgButtonsUrl}} aria-hidden="true"></span></a>
</h2>
</div>
<div ng-if="buttonsVisible" class="panel-body">
<scrollable-table watch="bridge.lifxdevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="targetDevice">Light Name</th>
<th>Map Id</th>
<th>Actions</th>
</tr>
</thead>
<tr
ng-repeat="device in bridge.devices | configuredLifxItems | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td>
<td>
<p>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</scrollable-table>
</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

@@ -19,6 +19,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>

View File

@@ -13,6 +13,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>

View File

@@ -19,6 +19,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>

View File

@@ -20,6 +20,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -141,25 +142,30 @@
<thead>
<tr>
<th>Name</th>
<th>IP</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.ip}}</td>
<td>{{harmony.webhook}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip)">Del</button></td>
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip, harmony.webhook)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-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-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><button class="btn btn-success" type="submit"
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip)">Add</button></td>
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip, newharmonywebhook)">Add</button></td>
</tr>
</table></td>
</tr>
@@ -416,6 +422,12 @@
ng-model="bridge.settings.farenheit" ng-true-value=true
ng-false-value=false> {{bridge.settings.farenheit}}</td>
</tr>
<tr>
<td>LIFX Support</td>
<td><input type="checkbox"
ng-model="bridge.settings.lifxconfigured" ng-true-value=true
ng-false-value=false> {{bridge.settings.lifxconfigured}}</td>
</tr>
<tr>
<td>Emulate Hue Hub Version</td>
<td><input id="bridge-settings-hubversion" class="form-control"

View File

@@ -18,6 +18,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
@@ -36,17 +37,18 @@
done in the edit tab, click the 'Add Bridge Device' to finish that selection
setup. The 'Already Configured Vera Devices' list below will show
what is already setup for your Vera.</p>
<p>
<p class="text-muted">
Also, use this select menu for which type of dim control you would
like to be generated: <select name="device-dim-control"
id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.decimal_percent}">Decimal Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
</p>
<p>Use the check boxes by the names to use the bulk addition
<p class="text-muted">Use the check boxes by the names to use the bulk addition
feature. Select your items and dim control type if wanted, then click
bulk add below. Your items will be added with on and off or dim and
off if selected with the name of the device from the Vera.</p>
@@ -80,7 +82,7 @@
<td>{{veradevice.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(veradevice, device_dim_control)">Build Item</button>
ng-click="buildDeviceUrls(veradevice, device_dim_control, false)">Build Item</button>
</td>
</tr>
</table>

View File

@@ -18,6 +18,7 @@
<li ng-if="bridge.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>