Merge pull request #557 from bwssytems/NewConnectors3.1

New connectors3.1

Fixes #454 Somfy Tahoma plugin enhancement
Fixes #430 hex value needed for dimming enhancement question
Fixes #423 Naming & Notes Enhancement Suggestions enhancement
Fixes #512 Dim handling in V4 question
Fixes #467 Available syntaxes besides ${intensity.percent} ? enhancement
Fixes #416 Use ${intensity.percent} with Harmony Hub enhancement question
Fixes #548 Removing Delay or Repeat in Edit-Device results in bad Config bug
Fixes #537 Minor documentation error bug
Fixes #237 Upgrade resulted in duplicates - Need to keep ha-bridge created user for device enhancement question
Fixes #324 Added a optional webhook for harmony-activity changes enhancement
Fixes #492 Use relative path in URLs enhancement
This commit is contained in:
BWS Systems
2017-03-17 16:55:23 -05:00
committed by GitHub
26 changed files with 831 additions and 510 deletions

View File

@@ -59,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. ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below.
``` ```
java -jar ha-bridge-4.2.1.jar java -jar ha-bridge-4.3.0.jar
``` ```
### Automation on Linux systems ### 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 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.2.1.jar is in your /home/pi/habridge directory. Create the directory and make sure that ha-bridge-4.3.0.jar is in your /home/pi/habridge directory.
``` ```
pi@raspberrypi:~ $ mkdir habridge pi@raspberrypi:~ $ mkdir habridge
pi@raspberrypi:~ $ cd habridge pi@raspberrypi:~ $ cd habridge
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.2.1/ha-bridge-4.2.1.jar pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.3.0/ha-bridge-4.3.0.jar
``` ```
Create the directory and make sure that ha-bridge-4.2.1.jar is in your /home/pi/habridge directory. Create the directory and make sure that ha-bridge-4.3.0.jar is in your /home/pi/habridge directory.
``` ```
pi@raspberrypi:~ $ mkdir habridge pi@raspberrypi:~ $ mkdir habridge
pi@raspberrypi:~ $ cd habridge pi@raspberrypi:~ $ cd habridge
pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.2.1/ha-bridge-4.2.1.jar pi@raspberrypi:~/habridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v4.3.0/ha-bridge-4.3.0.jar
``` ```
#### System Control Setup on a pi (preferred) #### 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 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
@@ -95,7 +95,7 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.2.1.jar ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.3.0.jar
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -130,7 +130,7 @@ Then cut and past this, modify any locations that are not correct
``` ```
cd /home/pi/habridge cd /home/pi/habridge
rm /home/pi/habridge/habridge-log.txt rm /home/pi/habridge/habridge-log.txt
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 & nohup java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.3.0.jar > /home/pi/habridge/habridge-log.txt 2>&1 &
chmod 777 /home/pi/habridge/habridge-log.txt chmod 777 /home/pi/habridge/habridge-log.txt
``` ```
@@ -175,54 +175,6 @@ Added the following lines to my Apache config file “000-default”
</VirtualHost> </VirtualHost>
``` ```
service apache2 restart
### lighthttpd Example
```
server.modules += ( "mod_proxy" )
proxy.server = (
"/api" =>
(
( "host" => "127.0.0.1",
"port" => "8080"
)
)
)
```
### nginx Example
```
location /api/ {
proxy_pass http://127.0.0.1:8080/api;
}
```
## Run ha-bridge alongside web server already on port 80
These examples will help you proxy your current webserver requests to the ha-bridge running on a different port, such as 8080.
### Apache Example
Reverse proxy with Apache on Ubuntu linux:
a2enmod proxy
a2enmod proxy_http
a2enmod headers
Added the following lines to my Apache config file “000-default”
```
<VirtualHost *:80>
ProxyPass /api http://localhost:8080/api nocanon
ProxyPassReverse /api http://localhost:8080/api
ProxyRequests Off
AllowEncodedSlashes NoDecode
# Local reverse proxy authorization override
# Most unix distribution deny proxy by default (ie /etc/apache2/mods-enabled/proxy.conf in Ubuntu)
<Proxy http://localhost:8080/api*>
Order deny,allow
Allow from all
</Proxy>
….. (the rest of the VirtualHost config section) …..
</VirtualHost>
```
service apache2 restart service apache2 restart
### lighthttpd Example ### lighthttpd Example
``` ```
@@ -287,13 +239,9 @@ The server defaults to running on port 80. To override what the default is, spec
#### UPNP Response Port #### UPNP Response Port
The upnp response port that will be used. The default is 50000. The upnp response port that will be used. The default is 50000.
#### Vera Names and IP Addresses #### Vera Names and IP Addresses
Provide IP Addresses of your Veras that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Vera and device/scene you configure. Note you have to 'turn on' a window to open it, and 'turn off' to close. Provide IP Addresses of your Veras that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the devices or scenes by the call it receives and send it to the target Vera and device/scene you configure.
#### Harmony Names and IP Addresses #### Harmony Names and IP Addresses
Provide IP Addresses of your Harmony Hubs that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the activity or buttons by the call it receives and send it to the target Harmony Hub and activity/button you configure. Provide IP Addresses of your Harmony Hubs that you want to utilize with the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will be able to control the activity or buttons by the call it receives and send it to the target Harmony Hub and activity/button you configure. Also, an option of webhook can be called when the activity changes on the harmony hub that will send an HTTP GET call to the the address of your choosing. This can contain the replacement variables of ${activity.id} and/or ${activity.label}. Example : http://192.168.0.1/activity/${activity.id}/${activity.label} OR http://hook?a=${activity.label}
#### Harmony Username
deprecated
#### Harmony Password
deprecated
#### Hue Names and IP Addresses #### Hue Names and IP Addresses
Provide IP Addresses of your Hue Bridges that you want to proxy through the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will passthru the call it receives to the target Hue and device you configure. Provide IP Addresses of your Hue Bridges that you want to proxy through the bridge. Also, give a meaningful name to each one so it is easy to decipher in the helper tab. When these names and IP's are given, the bridge will passthru the call it receives to the target Hue and device you configure.
@@ -311,7 +259,7 @@ The password for the user name of the home.nest.com account for the Nest user. T
#### Nest Temp Fahrenheit #### Nest Temp Fahrenheit
This setting allows the value being sent into the bridge to be interpreted as Fahrenheit or Celsius. The default is to have Fahrenheit. This setting allows the value being sent into the bridge to be interpreted as Fahrenheit or Celsius. The default is to have Fahrenheit.
#### Somfy Tahoma Username #### Somfy Tahoma Username
The user name used to login to www.tahomalink.com. This needs to be provided if you're using the Somfy Tahoma features (for connecting to IO Homecontrol used by Velux among others). There is no need to give any IP address or host information as this contacts your cloud account The user name used to login to www.tahomalink.com. This needs to be provided if you're using the Somfy Tahoma features (for connecting to IO Homecontrol used by Velux among others). There is no need to give any IP address or host information as this contacts your cloud account. *Note:* you have to 'turn on' a window to open it, and 'turn off' to close.
#### Somfy Tahoma Password #### Somfy Tahoma Password
The password associated with the Somfy Tahoma username above The password associated with the Somfy Tahoma username above
#### Button Press/Call Item Loop Sleep Interval (ms) #### Button Press/Call Item Loop Sleep Interval (ms)
@@ -344,7 +292,7 @@ There is a new format for the on/dim/off URL areas. The new editor handles the i
Here are the fields that can be put into the call item: Here are the fields that can be put into the call item:
Json Type | field name | What | Use Json Type | field name | What | Use
----------|------------|------|----- ----------|------------|------|-----
String or JsonElement | item| This is the payload that will be called for devices | Required String or JsonElement | item | This is the payload that will be called for devices | Required
Integer | count | This is how many times this items will be executed | Optional Integer | count | This is how many times this items will be executed | Optional
Integer | delay | This is how long we will wait until the next call after | Optional Integer | delay | This is how long we will wait until the next call after | Optional
String | type | This is the type of device we are executing | Required String | type | This is the type of device we are executing | Required
@@ -384,7 +332,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. 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.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. 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} or ${intensity.percent.hex} for 0-100 or ${intensity.decimal_percent} for 0.00-1.00 or ${intensity.byte} or ${intensity.byte.hex} 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)}" or "${intensity.math(X/4).hex}". See Value Passing Controls Below.
Examples: Examples:
``` ```
@@ -429,18 +377,18 @@ To configure this type of manual add, you will need to select the Device type of
In the URL areas, the format of the execution is just providing what command line you would like to run, or using the multiple call item construct described above. In the URL areas, the format of the execution is just providing what command line you would like to run, or using the multiple call item construct described above.
``` ```
[{"item":"C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"}, [{"item":"exec://C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"},{"item":"exec://notepad.exe","type":"cmdDevice"}]
{"item":"notepad.exe","type":"cmdDevice"}]
OR
[{"item":"exec://notepad.exe","type":"cmdDevice"}]
[{"item":"/home/pi/scripts/dothisscript.sh","type":"cmdDevice"}]
``` ```
#### Value Passing Controls #### 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.decimal_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.percent.hex}", "${intensity.decimal_percent}", "${intensity.byte}", "${intensity.byte.hex}", "${intensity.math(using X in your calc)}" and "${intensity.math(using X in your calc).hex}".
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)}". 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 For the items that want to have a date time put into the message, utilize ${time.format(yyyy-MM-ddTHH:mm:ssXXX)} where "yyyy-MM-ddTHH:mm:ssXXX" can be any format from the Java SimpleDateFormat documented here: https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
Also, device data can be inserted into your payloads by the use of "${device.name}", "${device.id}", "${device.uniqueid}", "${device.targetDevice}", "${device.mapId}", "${device.mapType}" and "${device.deviceType}". These work just like the dimming value replacements.
e.g. e.g.
``` ```
[{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=10&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.math(X/4)}","type":"httpDevice"}] [{"item":"http://192.168.1.201:3480/data_request?id=action&output_format=json&DeviceNum=10&serviceId=urn:upnp-org:serviceId:Dimming1&action=SetLoadLevelTarget&newLoadlevelTarget=${intensity.math(X/4)}","type":"httpDevice"}]

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId> <groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId> <artifactId>ha-bridge</artifactId>
<version>4.2.1</version> <version>4.3.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HA Bridge</name> <name>HA Bridge</name>

View File

@@ -1,301 +1,318 @@
package com.bwssystems.HABridge; package com.bwssystems.HABridge;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.net.SocketException; import java.net.SocketException;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermission;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.apache.http.conn.util.InetAddressUtils; import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.util.BackupHandler; import com.bwssystems.HABridge.util.BackupHandler;
import com.bwssystems.HABridge.util.JsonTransformer; import com.bwssystems.HABridge.util.JsonTransformer;
import com.google.gson.Gson; import com.google.gson.Gson;
public class BridgeSettings extends BackupHandler { public class BridgeSettings extends BackupHandler {
private static final Logger log = LoggerFactory.getLogger(BridgeSettings.class); private static final Logger log = LoggerFactory.getLogger(BridgeSettings.class);
private BridgeSettingsDescriptor theBridgeSettings; private BridgeSettingsDescriptor theBridgeSettings;
private BridgeControlDescriptor bridgeControl; private BridgeControlDescriptor bridgeControl;
public BridgeSettings() { public BridgeSettings() {
super(); super();
bridgeControl = new BridgeControlDescriptor(); bridgeControl = new BridgeControlDescriptor();
theBridgeSettings = new BridgeSettingsDescriptor(); theBridgeSettings = new BridgeSettingsDescriptor();
String ipV6Stack = System.getProperty("ipV6Stack"); String ipV6Stack = System.getProperty("ipV6Stack");
if(ipV6Stack == null || !ipV6Stack.equalsIgnoreCase("true")) { if(ipV6Stack == null || !ipV6Stack.equalsIgnoreCase("true")) {
System.setProperty("java.net.preferIPv4Stack" , "true"); System.setProperty("java.net.preferIPv4Stack" , "true");
} }
} }
public BridgeControlDescriptor getBridgeControl() { public BridgeControlDescriptor getBridgeControl() {
return bridgeControl; return bridgeControl;
} }
public BridgeSettingsDescriptor getBridgeSettingsDescriptor() { public BridgeSettingsDescriptor getBridgeSettingsDescriptor() {
return theBridgeSettings; return theBridgeSettings;
} }
public void buildSettings() { public void buildSettings() {
String addressString = null; String addressString = null;
String theVeraAddress = null; String theVeraAddress = null;
String theSomfyAddress = null; String theSomfyAddress = null;
String theHarmonyAddress = null; String theHarmonyAddress = null;
String configFileProperty = System.getProperty("config.file"); String configFileProperty = System.getProperty("config.file");
if(configFileProperty == null) { if(configFileProperty == null) {
Path filePath = Paths.get(Configuration.CONFIG_FILE); Path filePath = Paths.get(Configuration.CONFIG_FILE);
if(Files.exists(filePath) && Files.isReadable(filePath)) if(Files.exists(filePath) && Files.isReadable(filePath))
configFileProperty = Configuration.CONFIG_FILE; configFileProperty = Configuration.CONFIG_FILE;
} }
String serverPortOverride = System.getProperty("server.port"); String serverPortOverride = System.getProperty("server.port");
String serverIpOverride = System.getProperty("server.ip"); String serverIpOverride = System.getProperty("server.ip");
if(configFileProperty != null) if(configFileProperty != null)
{ {
log.info("reading from config file: " + configFileProperty); log.info("reading from config file: " + configFileProperty);
theBridgeSettings.setConfigfile(configFileProperty); theBridgeSettings.setConfigfile(configFileProperty);
_loadConfig(); _loadConfig();
} }
else else
{ {
log.info("reading from system properties"); log.info("reading from system properties");
theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES); theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES);
theBridgeSettings.setFarenheit(true); theBridgeSettings.setFarenheit(true);
theBridgeSettings.setConfigfile(Configuration.CONFIG_FILE); theBridgeSettings.setConfigfile(Configuration.CONFIG_FILE);
theBridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DEFAULT_WEB_PORT)); theBridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DEFAULT_WEB_PORT));
theBridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address")); theBridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address"));
theBridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db")); theBridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db"));
theBridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT)); theBridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
theVeraAddress = System.getProperty("vera.address"); theVeraAddress = System.getProperty("vera.address");
IpList theVeraList = null; IpList theVeraList = null;
if(theVeraAddress != null) { if(theVeraAddress != null) {
try { try {
theVeraList = new Gson().fromJson(theVeraAddress, IpList.class); theVeraList = new Gson().fromJson(theVeraAddress, IpList.class);
} catch (Exception e) { } catch (Exception e) {
try { try {
theVeraList = new Gson().fromJson("{devices:[{name:default,ip:" + theVeraAddress + "}]}", IpList.class); theVeraList = new Gson().fromJson("{devices:[{name:default,ip:" + theVeraAddress + "}]}", IpList.class);
} catch (Exception et) { } catch (Exception et) {
log.error("Cannot parse vera.address, not set with message: " + e.getMessage(), e); log.error("Cannot parse vera.address, not set with message: " + e.getMessage(), e);
theVeraList = null; theVeraList = null;
} }
} }
} }
theBridgeSettings.setVeraAddress(theVeraList); theBridgeSettings.setVeraAddress(theVeraList);
theHarmonyAddress = System.getProperty("harmony.address"); theHarmonyAddress = System.getProperty("harmony.address");
IpList theHarmonyList = null; IpList theHarmonyList = null;
if(theHarmonyAddress != null) { if(theHarmonyAddress != null) {
try { try {
theHarmonyList = new Gson().fromJson(theHarmonyAddress, IpList.class); theHarmonyList = new Gson().fromJson(theHarmonyAddress, IpList.class);
} catch (Exception e) { } catch (Exception e) {
try { try {
theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + theHarmonyAddress + "}]}", IpList.class); theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + theHarmonyAddress + "}]}", IpList.class);
} catch (Exception et) { } catch (Exception et) {
log.error("Cannot parse harmony.address, not set with message: " + e.getMessage(), e); log.error("Cannot parse harmony.address, not set with message: " + e.getMessage(), e);
theHarmonyList = null; theHarmonyList = null;
} }
} }
} }
theBridgeSettings.setHarmonyAddress(theHarmonyList); theBridgeSettings.setHarmonyAddress(theHarmonyList);
theSomfyAddress = System.getProperty("somfy.address"); theSomfyAddress = System.getProperty("somfy.address");
IpList theSomfyList = null; IpList theSomfyList = null;
if(theSomfyAddress != null) { if(theSomfyAddress != null) {
try { try {
theSomfyList = new Gson().fromJson(theSomfyAddress, IpList.class); theSomfyList = new Gson().fromJson(theSomfyAddress, IpList.class);
} catch (Exception e) { } catch (Exception e) {
try { try {
theSomfyList = new Gson().fromJson("{devices:[{name:default,ip:" + theSomfyAddress + "}]}", IpList.class); theSomfyList = new Gson().fromJson("{devices:[{name:default,ip:" + theSomfyAddress + "}]}", IpList.class);
} catch (Exception et) { } catch (Exception et) {
log.error("Cannot parse somfy.address, not set with message: " + e.getMessage(), e); log.error("Cannot parse somfy.address, not set with message: " + e.getMessage(), e);
theSomfyList = null; theSomfyList = null;
} }
} }
} }
theBridgeSettings.setSomfyAddress(theSomfyList); theBridgeSettings.setSomfyAddress(theSomfyList);
theBridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true"))); theBridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
theBridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false"))); theBridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
theBridgeSettings.setButtonsleep(Integer.parseInt(System.getProperty("button.sleep", Configuration.DEFAULT_BUTTON_SLEEP))); theBridgeSettings.setButtonsleep(Integer.parseInt(System.getProperty("button.sleep", Configuration.DEFAULT_BUTTON_SLEEP)));
theBridgeSettings.setNestuser(System.getProperty("nest.user")); theBridgeSettings.setNestuser(System.getProperty("nest.user"));
theBridgeSettings.setNestpwd(System.getProperty("nest.pwd")); theBridgeSettings.setNestpwd(System.getProperty("nest.pwd"));
} }
if(theBridgeSettings.getUpnpConfigAddress() == null || theBridgeSettings.getUpnpConfigAddress().equals("")) { if(theBridgeSettings.getUpnpConfigAddress() == null || theBridgeSettings.getUpnpConfigAddress().equals("")) {
addressString = checkIpAddress(null, true); addressString = checkIpAddress(null, true);
if(addressString != null) { if(addressString != null) {
theBridgeSettings.setUpnpConfigAddress(addressString); theBridgeSettings.setUpnpConfigAddress(addressString);
log.info("Adding " + addressString + " as our default upnp config address."); log.info("Adding " + addressString + " as our default upnp config address.");
} }
else else
log.error("Cannot get ip address of this host."); log.error("Cannot get ip address of this host.");
} }
else { else {
addressString = checkIpAddress(theBridgeSettings.getUpnpConfigAddress(), false); addressString = checkIpAddress(theBridgeSettings.getUpnpConfigAddress(), false);
if(addressString == null) if(addressString == null)
log.warn("The upnp config address, " + theBridgeSettings.getUpnpConfigAddress() + ", does not match any known IP's on this host."); log.warn("The upnp config address, " + theBridgeSettings.getUpnpConfigAddress() + ", does not match any known IP's on this host.");
} }
if(theBridgeSettings.getUpnpResponsePort() == null) if(theBridgeSettings.getUpnpResponsePort() == null)
theBridgeSettings.setUpnpResponsePort(Configuration.UPNP_RESPONSE_PORT); theBridgeSettings.setUpnpResponsePort(Configuration.UPNP_RESPONSE_PORT);
if(theBridgeSettings.getServerPort() == null) if(theBridgeSettings.getServerPort() == null)
theBridgeSettings.setServerPort(Configuration.DEFAULT_WEB_PORT); theBridgeSettings.setServerPort(Configuration.DEFAULT_WEB_PORT);
if(theBridgeSettings.getUpnpDeviceDb() == null) if(theBridgeSettings.getUpnpDeviceDb() == null)
theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY); theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY);
if(theBridgeSettings.getNumberoflogmessages() == null || theBridgeSettings.getNumberoflogmessages() <= 0) if(theBridgeSettings.getNumberoflogmessages() == null || theBridgeSettings.getNumberoflogmessages() <= 0)
theBridgeSettings.setNumberoflogmessages(new Integer(Configuration.NUMBER_OF_LOG_MESSAGES)); theBridgeSettings.setNumberoflogmessages(new Integer(Configuration.NUMBER_OF_LOG_MESSAGES));
if(theBridgeSettings.getButtonsleep() == null || theBridgeSettings.getButtonsleep() < 0) if(theBridgeSettings.getButtonsleep() == null || theBridgeSettings.getButtonsleep() < 0)
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP)); theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera()); theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera());
theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony()); theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony());
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest()); theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue()); theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
theBridgeSettings.setHalconfigured(theBridgeSettings.isValidHal()); theBridgeSettings.setHalconfigured(theBridgeSettings.isValidHal());
theBridgeSettings.setMqttconfigured(theBridgeSettings.isValidMQTT()); theBridgeSettings.setMqttconfigured(theBridgeSettings.isValidMQTT());
theBridgeSettings.setHassconfigured(theBridgeSettings.isValidHass()); theBridgeSettings.setHassconfigured(theBridgeSettings.isValidHass());
theBridgeSettings.setDomoticzconfigured(theBridgeSettings.isValidDomoticz()); theBridgeSettings.setDomoticzconfigured(theBridgeSettings.isValidDomoticz());
theBridgeSettings.setSomfyconfigured(theBridgeSettings.isValidSomfy()); theBridgeSettings.setSomfyconfigured(theBridgeSettings.isValidSomfy());
// Lifx is either configured or not, so it does not need an update. // Lifx is either configured or not, so it does not need an update.
if(serverPortOverride != null) if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride); theBridgeSettings.setServerPort(serverPortOverride);
if(serverIpOverride != null) if(serverIpOverride != null)
theBridgeSettings.setWebaddress(serverIpOverride); theBridgeSettings.setWebaddress(serverIpOverride);
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-"); setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
}
setupInternalTestUser();
public void loadConfig() { }
if(theBridgeSettings.getConfigfile() != null)
_loadConfig(); public void loadConfig() {
} if(theBridgeSettings.getConfigfile() != null)
private void _loadConfig() { _loadConfig();
Path configPath = Paths.get(theBridgeSettings.getConfigfile()); }
_loadConfig(configPath); private void _loadConfig() {
} Path configPath = Paths.get(theBridgeSettings.getConfigfile());
_loadConfig(configPath);
private void _loadConfig(Path aPath) { }
String jsonContent = configReader(aPath);
if(jsonContent == null) private void _loadConfig(Path aPath) {
return; String jsonContent = configReader(aPath);
try { if(jsonContent == null)
theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class); return;
} catch (Exception e) { try {
log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed."); theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class);
theBridgeSettings = new BridgeSettingsDescriptor(); } catch (Exception e) {
theBridgeSettings.setConfigfile(aPath.toString()); log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed.");
} theBridgeSettings = new BridgeSettingsDescriptor();
} theBridgeSettings.setConfigfile(aPath.toString());
}
public void save(BridgeSettingsDescriptor newBridgeSettings) { }
log.debug("Save HA Bridge settings.");
Path configPath = Paths.get(theBridgeSettings.getConfigfile()); public void save(BridgeSettingsDescriptor newBridgeSettings) {
JsonTransformer aRenderer = new JsonTransformer(); log.debug("Save HA Bridge settings.");
String jsonValue = aRenderer.render(newBridgeSettings); Path configPath = Paths.get(theBridgeSettings.getConfigfile());
configWriter(jsonValue, configPath); JsonTransformer aRenderer = new JsonTransformer();
_loadConfig(configPath); String jsonValue = aRenderer.render(newBridgeSettings);
} configWriter(jsonValue, configPath);
_loadConfig(configPath);
}
private void configWriter(String content, Path filePath) {
if(Files.exists(filePath) && !Files.isWritable(filePath)){
log.error("Error file is not writable: " + filePath); public void updateConfigFile() {
return; log.debug("Save HA Bridge settings.");
} Path configPath = Paths.get(theBridgeSettings.getConfigfile());
JsonTransformer aRenderer = new JsonTransformer();
if(Files.notExists(filePath.getParent())) { String jsonValue = aRenderer.render(theBridgeSettings);
try { configWriter(jsonValue, configPath);
Files.createDirectories(filePath.getParent()); _loadConfig(configPath);
} catch (IOException e) { }
log.error("Error creating the directory: " + filePath + " message: " + e.getMessage(), e);
}
} private void configWriter(String content, Path filePath) {
if(Files.exists(filePath) && !Files.isWritable(filePath)){
try { log.error("Error file is not writable: " + filePath);
Path target = null; return;
if(Files.exists(filePath)) { }
target = FileSystems.getDefault().getPath(filePath.getParent().toString(), "habridge.config.old");
Files.move(filePath, target); if(Files.notExists(filePath.getParent())) {
} try {
Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE); Files.createDirectories(filePath.getParent());
} catch (IOException e) {
// set attributes to be for user only log.error("Error creating the directory: " + filePath + " message: " + e.getMessage(), e);
// using PosixFilePermission to set file permissions }
Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>(); }
// add owners permission
perms.add(PosixFilePermission.OWNER_READ); try {
perms.add(PosixFilePermission.OWNER_WRITE); Path target = null;
if(Files.exists(filePath)) {
try { target = FileSystems.getDefault().getPath(filePath.getParent().toString(), "habridge.config.old");
Files.setPosixFilePermissions(filePath, perms); Files.move(filePath, target);
} catch(UnsupportedOperationException e) { }
log.info("Cannot set permissions for config file on this system as it is not supported. Continuing"); Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE);
}
if(target != null) // set attributes to be for user only
Files.delete(target); // using PosixFilePermission to set file permissions
} catch (IOException e) { Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
log.error("Error writing the file: " + filePath + " message: " + e.getMessage(), e); // add owners permission
} perms.add(PosixFilePermission.OWNER_READ);
} perms.add(PosixFilePermission.OWNER_WRITE);
private String configReader(Path filePath) { try {
String content = null; Files.setPosixFilePermissions(filePath, perms);
if(Files.notExists(filePath) || !Files.isReadable(filePath)){ } catch(UnsupportedOperationException e) {
log.warn("Error reading the file: " + filePath + " - Does not exist or is not readable. continuing..."); log.info("Cannot set permissions for config file on this system as it is not supported. Continuing");
return null; }
} if(target != null)
Files.delete(target);
try { } catch (IOException e) {
content = new String(Files.readAllBytes(filePath)); log.error("Error writing the file: " + filePath + " message: " + e.getMessage(), e);
} catch (IOException e) { }
log.error("Error reading the file: " + filePath + " message: " + e.getMessage(), e); }
}
private String configReader(Path filePath) {
return content; String content = null;
} if(Files.notExists(filePath) || !Files.isReadable(filePath)){
log.warn("Error reading the file: " + filePath + " - Does not exist or is not readable. continuing...");
private String checkIpAddress(String ipAddress, boolean checkForLocalhost) { return null;
Enumeration<NetworkInterface> ifs = null; }
try {
ifs = NetworkInterface.getNetworkInterfaces(); try {
} catch(SocketException e) { content = new String(Files.readAllBytes(filePath));
log.error("checkIpAddress cannot get ip address of this host, Exiting with message: " + e.getMessage(), e); } catch (IOException e) {
return null; log.error("Error reading the file: " + filePath + " message: " + e.getMessage(), e);
} }
String addressString = null;
InetAddress address = null; return content;
while (ifs.hasMoreElements() && addressString == null) { }
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses(); private String checkIpAddress(String ipAddress, boolean checkForLocalhost) {
String name = xface.getName(); Enumeration<NetworkInterface> ifs = null;
int IPsPerNic = 0; try {
ifs = NetworkInterface.getNetworkInterfaces();
while (addrs.hasMoreElements() && IPsPerNic == 0) { } catch(SocketException e) {
address = addrs.nextElement(); log.error("checkIpAddress cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
if (InetAddressUtils.isIPv4Address(address.getHostAddress())) { return null;
log.debug(name + " ... has IPV4 addr " + address); }
if(checkForLocalhost && (!name.equalsIgnoreCase(Configuration.LOOP_BACK_INTERFACE) || !address.getHostAddress().equalsIgnoreCase(Configuration.LOOP_BACK_ADDRESS))) { String addressString = null;
IPsPerNic++; InetAddress address = null;
addressString = address.getHostAddress(); while (ifs.hasMoreElements() && addressString == null) {
log.debug("checkIpAddress found " + addressString + " from interface " + name); NetworkInterface xface = ifs.nextElement();
} Enumeration<InetAddress> addrs = xface.getInetAddresses();
else if(ipAddress != null && ipAddress.equalsIgnoreCase(address.getHostAddress())){ String name = xface.getName();
addressString = ipAddress; int IPsPerNic = 0;
IPsPerNic++;
} while (addrs.hasMoreElements() && IPsPerNic == 0) {
} address = addrs.nextElement();
} if (InetAddressUtils.isIPv4Address(address.getHostAddress())) {
} log.debug(name + " ... has IPV4 addr " + address);
return addressString; if(checkForLocalhost && (!name.equalsIgnoreCase(Configuration.LOOP_BACK_INTERFACE) || !address.getHostAddress().equalsIgnoreCase(Configuration.LOOP_BACK_ADDRESS))) {
} IPsPerNic++;
} addressString = address.getHostAddress();
log.debug("checkIpAddress found " + addressString + " from interface " + name);
}
else if(ipAddress != null && ipAddress.equalsIgnoreCase(address.getHostAddress())){
addressString = ipAddress;
IPsPerNic++;
}
}
}
}
return addressString;
}
private void setupInternalTestUser() {
theBridgeSettings.setupInternalTestUser();
if(theBridgeSettings.isSettingsChanged())
this.updateConfigFile();
}
}

View File

@@ -1,12 +1,21 @@
package com.bwssystems.HABridge; package com.bwssystems.HABridge;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import com.bwssystems.HABridge.api.hue.HueConstants; import com.bwssystems.HABridge.api.hue.HueConstants;
import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.api.hue.WhitelistEntry; import com.bwssystems.HABridge.api.hue.WhitelistEntry;
public class BridgeSettingsDescriptor { public class BridgeSettingsDescriptor {
private static final String DEFAULT_INTERNAL_USER = "thehabridgeuser";
private static final String DEFAULT_USER_DESCRIPTION = "default_test_user";
private String upnpconfigaddress; private String upnpconfigaddress;
private Integer serverport; private Integer serverport;
private Integer upnpresponseport; private Integer upnpresponseport;
@@ -362,4 +371,77 @@ public class BridgeSettingsDescriptor {
public Boolean isValidLifx() { public Boolean isValidLifx() {
return this.isLifxconfigured(); return this.isLifxconfigured();
} }
public HueError[] validateWhitelistUser(String aUser, String userDescription, boolean strict) {
String validUser = null;
boolean found = false;
if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null")
&& !aUser.equalsIgnoreCase("")) {
if (whitelist != null) {
Set<String> theUserIds = whitelist.keySet();
Iterator<String> userIterator = theUserIds.iterator();
while (userIterator.hasNext()) {
validUser = userIterator.next();
if (validUser.equals(aUser))
found = true;
}
}
}
if(!found && !strict) {
newWhitelistUser(aUser, userDescription);
found = true;
}
if (!found) {
return HueErrorResponse.createResponse("1", "/api/" + aUser, "unauthorized user", null, null, null).getTheErrors();
}
return null;
}
public void newWhitelistUser(String aUser, String userDescription) {
if (whitelist == null) {
whitelist = new HashMap<>();
}
if(userDescription == null)
userDescription = "auto insert user";
whitelist.put(aUser, WhitelistEntry.createEntry(userDescription));
setSettingsChanged(true);
}
public String createWhitelistUser(String userDescription) {
String aUser = getNewUserID();
newWhitelistUser(aUser, userDescription);
return aUser;
}
private String getNewUserID() {
UUID uid = UUID.randomUUID();
StringTokenizer st = new StringTokenizer(uid.toString(), "-");
String newUser = "";
while (st.hasMoreTokens()) {
newUser = newUser + st.nextToken();
}
return newUser;
}
public String getInternalTestUser() {
return DEFAULT_INTERNAL_USER;
}
public void setupInternalTestUser() {
boolean found = false;
for (String key : whitelist.keySet()) {
if(key.equals(DEFAULT_INTERNAL_USER))
found = true;
}
if(!found) {
newWhitelistUser(DEFAULT_INTERNAL_USER, DEFAULT_USER_DESCRIPTION);
}
}
} }

View File

@@ -52,6 +52,7 @@ public class DeviceMapTypes {
deviceMapTypes.add(MQTT_MESSAGE); deviceMapTypes.add(MQTT_MESSAGE);
deviceMapTypes.add(NEST_HOMEAWAY); deviceMapTypes.add(NEST_HOMEAWAY);
deviceMapTypes.add(NEST_THERMO_SET); deviceMapTypes.add(NEST_THERMO_SET);
deviceMapTypes.add(SOMFY_DEVICE);
deviceMapTypes.add(TCP_DEVICE); deviceMapTypes.add(TCP_DEVICE);
deviceMapTypes.add(UDP_DEVICE); deviceMapTypes.add(UDP_DEVICE);
deviceMapTypes.add(VERA_DEVICE); deviceMapTypes.add(VERA_DEVICE);

View File

@@ -72,7 +72,7 @@ public class HABridge {
theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor()); theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
theSettingResponder.setupServer(); theSettingResponder.setupServer();
// setup the class to handle the hue emulator rest api // setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), homeManager); theHueMulator = new HueMulator(bridgeSettings, theResources.getDeviceRepository(), homeManager);
theHueMulator.setupServer(); theHueMulator.setupServer();
// wait for the sparkjava initialization of the rest api classes to be complete // wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization(); awaitInitialization();

View File

@@ -62,6 +62,13 @@ public class SystemControl {
return "{\"version\":\"" + version.getVersion() + "\"}"; return "{\"version\":\"" + version.getVersion() + "\"}";
}); });
// http://ip_address:port/system/habridge/testuser gets the valid test user for calling the api
get (SYSTEM_CONTEXT + "/habridge/testuser", "application/json", (request, response) -> {
log.debug("Get HA Bridge testuser: " + bridgeSettings.getBridgeSettingsDescriptor().getInternalTestUser());
response.status(HttpStatus.SC_OK);
return "{\"user\":\"" + bridgeSettings.getBridgeSettingsDescriptor().getInternalTestUser() + "\"}";
});
// http://ip_address:port/system/logmsgs gets the log messages for the bridge // http://ip_address:port/system/logmsgs gets the log messages for the bridge
get (SYSTEM_CONTEXT + "/logmsgs", "application/json", (request, response) -> { get (SYSTEM_CONTEXT + "/logmsgs", "application/json", (request, response) -> {
log.debug("Get logmsgs."); log.debug("Get logmsgs.");

View File

@@ -62,9 +62,18 @@ public class DeviceDescriptor{
@SerializedName("noState") @SerializedName("noState")
@Expose @Expose
private boolean noState; private boolean noState;
@SerializedName("offState")
@Expose
private boolean offState;
@SerializedName("requesterAddress") @SerializedName("requesterAddress")
@Expose @Expose
private String requesterAddress; private String requesterAddress;
@SerializedName("description")
@Expose
private String description;
@SerializedName("comments")
@Expose
private String comments;
private DeviceState deviceState; private DeviceState deviceState;
@@ -222,6 +231,14 @@ public class DeviceDescriptor{
this.noState = noState; this.noState = noState;
} }
public boolean isOffState() {
return offState;
}
public void setOffState(boolean offState) {
this.offState = offState;
}
public String getRequesterAddress() { public String getRequesterAddress() {
return requesterAddress; return requesterAddress;
} }
@@ -230,6 +247,22 @@ public class DeviceDescriptor{
this.requesterAddress = requesterAddress; this.requesterAddress = requesterAddress;
} }
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getComments() {
return comments;
}
public void setComments(String comments) {
this.comments = comments;
}
public boolean containsType(String aType) { public boolean containsType(String aType) {
if(aType == null) if(aType == null)
return false; return false;

View File

@@ -17,6 +17,9 @@ public class BrightnessDecode {
private static final String INTENSITY_MATH = "${intensity.math("; private static final String INTENSITY_MATH = "${intensity.math(";
private static final String INTENSITY_MATH_VALUE = "X"; private static final String INTENSITY_MATH_VALUE = "X";
private static final String INTENSITY_MATH_CLOSE = ")}"; private static final String INTENSITY_MATH_CLOSE = ")}";
private static final String INTENSITY_MATH_CLOSE_HEX = ").hex}";
private static final String INTENSITY_PERCENT_HEX = "${intensity.percent.hex}";
private static final String INTENSITY_BYTE_HEX = "${intensity.byte.hex}";
public static int calculateIntensity(int setIntensity, Integer targetBri, Integer targetBriInc) { public static int calculateIntensity(int setIntensity, Integer targetBri, Integer targetBriInc) {
if (targetBri != null) { if (targetBri != null) {
@@ -45,50 +48,79 @@ public class BrightnessDecode {
if (request == null) { if (request == null) {
return null; return null;
} }
if (request.contains(INTENSITY_BYTE)) { boolean notDone = true;
if (isHex) { String replaceValue = null;
String hexValue = convertToHex(intensity); String replaceTarget = null;
request = request.replace(INTENSITY_BYTE, hexValue); int percentBrightness = (int) Math.round(intensity / 255.0 * 100);
} else { float decimalBrightness = (float) (intensity / 255.0);
String intensityByte = String.valueOf(intensity); Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
request = request.replace(INTENSITY_BYTE, intensityByte); String mathDescriptor = null;
}
} else if (request.contains(INTENSITY_PERCENT)) { while(notDone) {
int percentBrightness = (int) Math.round(intensity / 255.0 * 100); notDone = false;
if (isHex) { if (request.contains(INTENSITY_BYTE)) {
String hexValue = convertToHex(percentBrightness); if (isHex) {
request = request.replace(INTENSITY_PERCENT, hexValue); replaceValue = convertToHex(intensity);
} else { } else {
String intensityPercent = String.valueOf(percentBrightness); replaceValue = String.valueOf(intensity);
request = request.replace(INTENSITY_PERCENT, intensityPercent); }
} replaceTarget = INTENSITY_BYTE;
} else if (request.contains(INTENSITY_DECIMAL_PERCENT)) { notDone = true;
float decimalBrightness = (float) (intensity / 255.0); } else if (request.contains(INTENSITY_BYTE_HEX)) {
replaceValue = convertToHex(intensity);
String intensityPercent = String.format("%1.2f", decimalBrightness); replaceTarget = INTENSITY_BYTE_HEX;
request = request.replace(INTENSITY_DECIMAL_PERCENT, intensityPercent); notDone = true;
} else if (request.contains(INTENSITY_MATH)) { } else if (request.contains(INTENSITY_PERCENT)) {
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>(); if (isHex) {
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(), replaceValue = convertToHex(percentBrightness);
request.indexOf(INTENSITY_MATH_CLOSE)); } else {
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity)); replaceValue = String.valueOf(percentBrightness);
}
try { replaceTarget = INTENSITY_PERCENT;
notDone = true;
} else if (request.contains(INTENSITY_PERCENT_HEX)) {
replaceValue = convertToHex(percentBrightness);
replaceTarget = INTENSITY_PERCENT_HEX;
notDone = true;
} else if (request.contains(INTENSITY_DECIMAL_PERCENT)) {
replaceValue = String.format("%1.2f", decimalBrightness);
replaceTarget = INTENSITY_DECIMAL_PERCENT;
notDone = true;
} else if (request.contains(INTENSITY_MATH_CLOSE)) {
mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
request.indexOf(INTENSITY_MATH_CLOSE));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
log.debug("Math eval is: " + mathDescriptor + ", Where " + INTENSITY_MATH_VALUE + " is: " log.debug("Math eval is: " + mathDescriptor + ", Where " + INTENSITY_MATH_VALUE + " is: "
+ String.valueOf(intensity)); + String.valueOf(intensity));
Expression exp = new Expression(mathDescriptor); Integer endResult = calculateMath(variables, mathDescriptor);
BigDecimal result = exp.eval(variables); if(endResult != null) {
Integer endResult = Math.round(result.floatValue()); if (isHex) {
if (isHex) { replaceValue = convertToHex(endResult);
String hexValue = convertToHex(endResult); } else {
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue); replaceValue = endResult.toString();
} else { }
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, replaceTarget = INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE;
endResult.toString()); notDone = true;
}
} else if (request.contains(INTENSITY_MATH_CLOSE_HEX)) {
mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
request.indexOf(INTENSITY_MATH_CLOSE_HEX));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
Integer endResult = calculateMath(variables, mathDescriptor);
if(endResult != null) {
if (isHex) {
replaceValue = convertToHex(endResult);
} else {
replaceValue = endResult.toString();
}
replaceTarget = INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE_HEX;
notDone = true;
} }
} catch (Exception e) {
log.warn("Could not execute Math: " + mathDescriptor, e);
} }
if(notDone)
request = request.replace(replaceTarget, replaceValue);
} }
return request; return request;
} }
@@ -108,4 +140,17 @@ public class BrightnessDecode {
newBytes[1] = theBytes[0]; newBytes[1] = theBytes[0];
return new String(newBytes); return new String(newBytes);
} }
private static Integer calculateMath(Map<String, BigDecimal> variables, String mathDescriptor) {
Integer endResult = null;
try {
Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables);
endResult = Math.round(result.floatValue());
} catch (Exception e) {
log.warn("Could not execute Math: " + mathDescriptor, e);
endResult = null;
}
return endResult;
}
} }

View File

@@ -0,0 +1,21 @@
package com.bwssystems.HABridge.hue;
import java.util.List;
public class ColorDecode {
public static String convertCIEtoRGB(List<Double> xy) {
double x;
double y;
double Y;
x = xy.get(0) * 100;
y = xy.get(1) * 100;
Y= y;
double R = 3.240479*((x*Y)/y) + -1.537150*Y + -0.498535*(((1-x-y)*Y)/y);
double G = -0.969256*((x*Y)/y) + 1.875992*Y + 0.041556*(((1-x-y)*Y)/y);
double B = 0.055648*((x*Y)/y) + -0.204043*Y + 1.057311*(((1-x-y)*Y)/y);
return null;
}
}

View File

@@ -0,0 +1,66 @@
package com.bwssystems.HABridge.hue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
public class DeviceDataDecode {
private static final Logger log = LoggerFactory.getLogger(DeviceDataDecode.class);
private static final String DEVICE_ID = "${device.id}";
private static final String DEVICE_UNIQUEID = "${device.uniqueid}";
private static final String DEVICE_NAME = "${device.name}";
private static final String DEVICE_MAPID = "${device.mapId}";
private static final String DEVICE_MAPTYPE = "${device.mapType}";
private static final String DEVICE_DEVICETYPE = "${device.deviceType}";
private static final String DEVICE_TARGETDEVICE = "${device.targetDevice}";
public static String replaceDeviceData(String request, DeviceDescriptor device) {
if (request == null) {
return null;
}
boolean notDone = true;
while(notDone) {
notDone = false;
if (request.contains(DEVICE_ID)) {
request = request.replace(DEVICE_ID, device.getId());
notDone = true;
}
if (request.contains(DEVICE_UNIQUEID)) {
request = request.replace(DEVICE_UNIQUEID, device.getUniqueid());
notDone = true;
}
if (request.contains(DEVICE_NAME)) {
request = request.replace(DEVICE_NAME, device.getName());
notDone = true;
}
if (request.contains(DEVICE_MAPID)) {
request = request.replace(DEVICE_MAPID, device.getMapId());
notDone = true;
}
if (request.contains(DEVICE_MAPTYPE)) {
request = request.replace(DEVICE_MAPTYPE, device.getMapType());
notDone = true;
}
if (request.contains(DEVICE_DEVICETYPE)) {
request = request.replace(DEVICE_DEVICETYPE, device.getDeviceType());
notDone = true;
}
if (request.contains(DEVICE_TARGETDEVICE)) {
request = request.replace(DEVICE_TARGETDEVICE, device.getTargetDevice());
notDone = true;
}
log.debug("Request <<" + request + ">>, not done: " + notDone);
}
return request;
}
}

View File

@@ -1,5 +1,6 @@
package com.bwssystems.HABridge.hue; package com.bwssystems.HABridge.hue;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor; import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.DeviceMapTypes; import com.bwssystems.HABridge.DeviceMapTypes;
import com.bwssystems.HABridge.HomeManager; import com.bwssystems.HABridge.HomeManager;
@@ -14,7 +15,6 @@ import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse; import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.api.hue.HuePublicConfig; import com.bwssystems.HABridge.api.hue.HuePublicConfig;
import com.bwssystems.HABridge.api.hue.StateChangeBody; import com.bwssystems.HABridge.api.hue.StateChangeBody;
import com.bwssystems.HABridge.api.hue.WhitelistEntry;
import com.bwssystems.HABridge.dao.*; import com.bwssystems.HABridge.dao.*;
import com.bwssystems.HABridge.plugins.hue.HueHome; import com.bwssystems.HABridge.plugins.hue.HueHome;
import com.bwssystems.HABridge.util.JsonTransformer; import com.bwssystems.HABridge.util.JsonTransformer;
@@ -33,12 +33,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
/** /**
* Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server * Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server
@@ -52,13 +48,15 @@ public class HueMulator {
private HomeManager homeManager; private HomeManager homeManager;
private HueHome myHueHome; private HueHome myHueHome;
private BridgeSettingsDescriptor bridgeSettings; private BridgeSettingsDescriptor bridgeSettings;
private BridgeSettings bridgeSettingMaster;
private Gson aGsonHandler; private Gson aGsonHandler;
private DeviceMapTypes validMapTypes; private DeviceMapTypes validMapTypes;
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HomeManager aHomeManager) { public HueMulator(BridgeSettings bridgeMaster, DeviceRepository aDeviceRepository, HomeManager aHomeManager) {
repository = aDeviceRepository; repository = aDeviceRepository;
validMapTypes = new DeviceMapTypes(); validMapTypes = new DeviceMapTypes();
bridgeSettings = theBridgeSettings; bridgeSettingMaster = bridgeMaster;
bridgeSettings = bridgeSettingMaster.getBridgeSettingsDescriptor();
homeManager= aHomeManager; homeManager= aHomeManager;
myHueHome = (HueHome) homeManager.findHome(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]); myHueHome = (HueHome) homeManager.findHome(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]);
aGsonHandler = new GsonBuilder().create(); aGsonHandler = new GsonBuilder().create();
@@ -393,7 +391,7 @@ public class HueMulator {
} }
private String formatSuccessHueResponse(StateChangeBody stateChanges, String body, String lightId, private String formatSuccessHueResponse(StateChangeBody stateChanges, String body, String lightId,
DeviceState deviceState, Integer targetBri, Integer targetBriInc) { DeviceState deviceState, Integer targetBri, Integer targetBriInc, boolean offState) {
String responseString = "["; String responseString = "[";
boolean notFirstChange = false; boolean notFirstChange = false;
@@ -408,6 +406,8 @@ public class HueMulator {
deviceState.setOn(stateChanges.isOn()); deviceState.setOn(stateChanges.isOn());
if(!deviceState.isOn() && deviceState.getBri() == 254) if(!deviceState.isOn() && deviceState.getBri() == 254)
deviceState.setBri(0); deviceState.setBri(0);
if(!deviceState.isOn() && offState)
deviceState.setBri(0);
} }
notFirstChange = true; notFirstChange = true;
} }
@@ -556,50 +556,6 @@ public class HueMulator {
return responseString; return responseString;
} }
private String getNewUserID() {
UUID uid = UUID.randomUUID();
StringTokenizer st = new StringTokenizer(uid.toString(), "-");
String newUser = "";
while (st.hasMoreTokens()) {
newUser = newUser + st.nextToken();
}
return newUser;
}
private HueError[] validateWhitelistUser(String aUser, boolean strict) {
String validUser = null;
boolean found = false;
if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null")
&& !aUser.equalsIgnoreCase("")) {
if (bridgeSettings.getWhitelist() != null) {
Set<String> theUserIds = bridgeSettings.getWhitelist().keySet();
Iterator<String> userIterator = theUserIds.iterator();
while (userIterator.hasNext()) {
validUser = userIterator.next();
if (validUser.equals(aUser))
found = true;
}
}
if (!found && !strict) {
if (bridgeSettings.getWhitelist() == null) {
Map<String, WhitelistEntry> awhitelist = new HashMap<>();
bridgeSettings.setWhitelist(awhitelist);
}
bridgeSettings.getWhitelist().put(aUser, WhitelistEntry.createEntry("auto insert user"));
bridgeSettings.setSettingsChanged(true);
found = true;
}
}
if (!found) {
log.debug("Validate user, No User supplied");
return HueErrorResponse.createResponse("1", "/api/" + aUser, "unauthorized user", null, null, null).getTheErrors();
}
return null;
}
private Boolean filterByRequester(String requesterFilterList, String anAddress) { private Boolean filterByRequester(String requesterFilterList, String anAddress) {
if (requesterFilterList == null || requesterFilterList.length() == 0) if (requesterFilterList == null || requesterFilterList.length() == 0)
return true; return true;
@@ -620,9 +576,13 @@ public class HueMulator {
private String basicListHandler(String type, String userId, String requestIp) { private String basicListHandler(String type, String userId, String requestIp) {
log.debug("hue " + type + " list requested: " + userId + " from " + requestIp); log.debug("hue " + type + " list requested: " + userId + " from " + requestIp);
HueError[] theErrors = validateWhitelistUser(userId, false); HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors != null) if (theErrors != null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
return aGsonHandler.toJson(theErrors); return aGsonHandler.toJson(theErrors);
}
return "{}"; return "{}";
} }
@@ -630,8 +590,11 @@ public class HueMulator {
log.debug("hue group list requested: " + userId + " from " + requestIp); log.debug("hue group list requested: " + userId + " from " + requestIp);
HueError[] theErrors = null; HueError[] theErrors = null;
Map<String, GroupResponse> groupResponseMap = null; Map<String, GroupResponse> groupResponseMap = null;
theErrors = validateWhitelistUser(userId, false); theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors == null) { if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
groupResponseMap = new HashMap<String, GroupResponse>(); groupResponseMap = new HashMap<String, GroupResponse>();
groupResponseMap.put("1", (GroupResponse) this.groupsIdHandler("1", userId, requestIp)); groupResponseMap.put("1", (GroupResponse) this.groupsIdHandler("1", userId, requestIp));
return groupResponseMap; return groupResponseMap;
@@ -644,8 +607,11 @@ public class HueMulator {
private Object groupsIdHandler(String groupId, String userId, String requestIp) { private Object groupsIdHandler(String groupId, String userId, String requestIp) {
log.debug("hue group id: <" + groupId + "> requested: " + userId + " from " + requestIp); log.debug("hue group id: <" + groupId + "> requested: " + userId + " from " + requestIp);
HueError[] theErrors = null; HueError[] theErrors = null;
theErrors = validateWhitelistUser(userId, false); theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors == null) { if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (groupId.equalsIgnoreCase("0")) { if (groupId.equalsIgnoreCase("0")) {
GroupResponse theResponse = GroupResponse.createDefaultGroupResponse(repository.findActive()); GroupResponse theResponse = GroupResponse.createDefaultGroupResponse(repository.findActive());
return theResponse; return theResponse;
@@ -666,8 +632,11 @@ public class HueMulator {
if (bridgeSettings.isTraceupnp()) if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue lights list requested: " + userId + " from " + requestIp); log.info("Traceupnp: hue lights list requested: " + userId + " from " + requestIp);
log.debug("hue lights list requested: " + userId + " from " + requestIp); log.debug("hue lights list requested: " + userId + " from " + requestIp);
theErrors = validateWhitelistUser(userId, false); theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors == null) { if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
List<DeviceDescriptor> deviceList = repository.findAllByRequester(requestIp); List<DeviceDescriptor> deviceList = repository.findAllByRequester(requestIp);
// List<DeviceDescriptor> deviceList = repository.findActive(); // List<DeviceDescriptor> deviceList = repository.findActive();
deviceResponseMap = new HashMap<String, DeviceResponse>(); deviceResponseMap = new HashMap<String, DeviceResponse>();
@@ -724,12 +693,20 @@ public class HueMulator {
newUser = aNewUser.getUsername(); newUser = aNewUser.getUsername();
aDeviceType = aNewUser.getDevicetype(); aDeviceType = aNewUser.getDevicetype();
} }
if (newUser == null)
newUser = getNewUserID();
validateWhitelistUser(newUser, false);
if (aDeviceType == null) if (aDeviceType == null)
aDeviceType = "<not given>"; aDeviceType = "<not given>";
if (newUser == null) {
newUser = bridgeSettings.createWhitelistUser(aDeviceType);
}
else {
bridgeSettings.validateWhitelistUser(newUser, aDeviceType, false);
}
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (bridgeSettings.isTraceupnp()) if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: " log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: "
+ newUser + (followingSlash ? " /api/ called" : "")); + newUser + (followingSlash ? " /api/ called" : ""));
@@ -743,7 +720,7 @@ public class HueMulator {
if (bridgeSettings.isTraceupnp()) if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api/:userid/config config requested: " + userId + " from " + ipAddress); log.info("Traceupnp: hue api/:userid/config config requested: " + userId + " from " + ipAddress);
log.debug("hue api config requested: " + userId + " from " + ipAddress); log.debug("hue api config requested: " + userId + " from " + ipAddress);
if (validateWhitelistUser(userId, true) != null) { if (bridgeSettings.validateWhitelistUser(userId, null, true) != null) {
log.debug("hue api config requested, No User supplied, returning public config"); log.debug("hue api config requested, No User supplied, returning public config");
HuePublicConfig apiResponse = HuePublicConfig.createConfig("Philips hue", HuePublicConfig apiResponse = HuePublicConfig.createConfig("Philips hue",
bridgeSettings.getUpnpConfigAddress(), bridgeSettings.getHubversion()); bridgeSettings.getUpnpConfigAddress(), bridgeSettings.getHubversion());
@@ -759,7 +736,7 @@ public class HueMulator {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Object getFullState(String userId, String ipAddress) { private Object getFullState(String userId, String ipAddress) {
log.debug("hue api full state requested: " + userId + " from " + ipAddress); log.debug("hue api full state requested: " + userId + " from " + ipAddress);
HueError[] theErrors = validateWhitelistUser(userId, false); HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, true);
if (theErrors != null) if (theErrors != null)
return theErrors; return theErrors;
@@ -773,7 +750,7 @@ public class HueMulator {
private Object getLight(String userId, String lightId, String ipAddress) { private Object getLight(String userId, String lightId, String ipAddress) {
log.debug("hue light requested: " + lightId + " for user: " + userId + " from " + ipAddress); log.debug("hue light requested: " + lightId + " for user: " + userId + " from " + ipAddress);
HueError[] theErrors = validateWhitelistUser(userId, false); HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, true);
if (theErrors != null) if (theErrors != null)
return theErrors; return theErrors;
@@ -817,7 +794,7 @@ public class HueMulator {
Integer targetBri = null; Integer targetBri = null;
Integer targetBriInc = null; Integer targetBriInc = null;
log.debug("Update state requested: " + userId + " from " + ipAddress + " body: " + body); log.debug("Update state requested: " + userId + " from " + ipAddress + " body: " + body);
HueError[] theErrors = validateWhitelistUser(userId, false); HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, true);
if (theErrors != null) if (theErrors != null)
return aGsonHandler.toJson(theErrors); return aGsonHandler.toJson(theErrors);
try { try {
@@ -849,7 +826,7 @@ public class HueMulator {
if (state == null) if (state == null)
state = DeviceState.createDeviceState(); state = DeviceState.createDeviceState();
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc); responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc, device.isOffState());
device.setDeviceState(state); device.setDeviceState(state);
return responseString; return responseString;
@@ -867,7 +844,7 @@ public class HueMulator {
aMultiUtil.setDelayDefault(bridgeSettings.getButtonsleep()); aMultiUtil.setDelayDefault(bridgeSettings.getButtonsleep());
aMultiUtil.setSetCount(1); aMultiUtil.setSetCount(1);
log.debug("hue state change requested: " + userId + " from " + ipAddress + " body: " + body); log.debug("hue state change requested: " + userId + " from " + ipAddress + " body: " + body);
HueError[] theErrors = validateWhitelistUser(userId, false); HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, true);
if (theErrors != null) if (theErrors != null)
return aGsonHandler.toJson(theErrors); return aGsonHandler.toJson(theErrors);
try { try {
@@ -989,11 +966,11 @@ public class HueMulator {
if (responseString == null || !responseString.contains("[{\"error\":")) { if (responseString == null || !responseString.contains("[{\"error\":")) {
if(!device.isNoState()) { if(!device.isNoState()) {
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc); responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state, targetBri, targetBriInc, device.isOffState());
device.setDeviceState(state); device.setDeviceState(state);
} else { } else {
DeviceState dummyState = DeviceState.createDeviceState(); DeviceState dummyState = DeviceState.createDeviceState();
responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, dummyState, targetBri, targetBriInc); responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, dummyState, targetBri, targetBriInc, device.isOffState());
} }
} }
return responseString; return responseString;

View File

@@ -21,16 +21,22 @@ public class TimeDecode {
if (request == null) { if (request == null) {
return null; return null;
} }
if (request.contains(TIME_FORMAT)) { boolean notDone = true;
String timeFormatDescriptor = request.substring(request.indexOf(TIME_FORMAT) + TIME_FORMAT.length(),
request.indexOf(TIME_FORMAT_CLOSE)); while(notDone) {
notDone = false;
try { if (request.contains(TIME_FORMAT)) {
log.debug("Time eval is: " + timeFormatDescriptor); String timeFormatDescriptor = request.substring(request.indexOf(TIME_FORMAT) + TIME_FORMAT.length(),
SimpleDateFormat dateFormat = new SimpleDateFormat(timeFormatDescriptor); request.indexOf(TIME_FORMAT_CLOSE));
request = request.replace(TIME_FORMAT + timeFormatDescriptor + TIME_FORMAT_CLOSE, dateFormat.format(new Date()));
} catch (Exception e) { try {
log.warn("Could not format current time: " + timeFormatDescriptor, e); log.debug("Time eval is: " + timeFormatDescriptor);
SimpleDateFormat dateFormat = new SimpleDateFormat(timeFormatDescriptor);
request = request.replace(TIME_FORMAT + timeFormatDescriptor + TIME_FORMAT_CLOSE, dateFormat.format(new Date()));
notDone = true;
} catch (Exception e) {
log.warn("Could not format current time: " + timeFormatDescriptor, e);
}
} }
} }
return request; return request;

View File

@@ -10,6 +10,7 @@ import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil; import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.hue.TimeDecode;
@@ -31,6 +32,7 @@ public class CommandHome implements Home {
else else
intermediate = anItem.getItem().getAsString(); intermediate = anItem.getItem().getAsString();
intermediate = BrightnessDecode.calculateReplaceIntensityValue(intermediate, itensity, targetBri, targetBriInc, false); intermediate = BrightnessDecode.calculateReplaceIntensityValue(intermediate, itensity, targetBri, targetBriInc, false);
intermediate = DeviceDataDecode.replaceDeviceData(intermediate, device);
intermediate = TimeDecode.replaceTimeValue(intermediate); intermediate = TimeDecode.replaceTimeValue(intermediate);
String anError = doExecRequest(intermediate, lightId); String anError = doExecRequest(intermediate, lightId);
if (anError != null) { if (anError != null) {

View File

@@ -17,6 +17,7 @@ import com.bwssystems.HABridge.IpList;
import com.bwssystems.HABridge.NamedIP; import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil; import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@@ -161,6 +162,8 @@ public class HarmonyHome implements Home {
if (url.substring(0, 1).equalsIgnoreCase("{")) { if (url.substring(0, 1).equalsIgnoreCase("{")) {
url = "[" + url + "]"; url = "[" + url + "]";
} }
url = BrightnessDecode.calculateReplaceIntensityValue(url, intensity, targetBri, targetBriInc, false);
ButtonPress[] deviceButtons = aGsonHandler.fromJson(url, ButtonPress[].class); ButtonPress[] deviceButtons = aGsonHandler.fromJson(url, ButtonPress[].class);
Integer theCount = 1; Integer theCount = 1;
for(int z = 0; z < deviceButtons.length; z++) { for(int z = 0; z < deviceButtons.length; z++) {

View File

@@ -11,6 +11,7 @@ import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse; import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil; import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -49,12 +50,14 @@ public class HTTPHome implements Home {
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrl, String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrl,
intensity, targetBri, targetBriInc, false); intensity, targetBri, targetBriInc, false);
anUrl = DeviceDataDecode.replaceDeviceData(anUrl, device);
anUrl = TimeDecode.replaceTimeValue(anUrl); anUrl = TimeDecode.replaceTimeValue(anUrl);
String aBody = null; String aBody = null;
if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) { if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) {
aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(), aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(),
intensity, targetBri, targetBriInc, false); intensity, targetBri, targetBriInc, false);
aBody = DeviceDataDecode.replaceDeviceData(aBody, device);
aBody = TimeDecode.replaceTimeValue(aBody); aBody = TimeDecode.replaceTimeValue(aBody);
} }
// make call // make call

View File

@@ -14,6 +14,7 @@ import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil; import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -89,6 +90,7 @@ public class MQTTHome implements Home {
mqttObject =anItem.getItem().getAsString(); mqttObject =anItem.getItem().getAsString();
mqttObject = BrightnessDecode.calculateReplaceIntensityValue(mqttObject, mqttObject = BrightnessDecode.calculateReplaceIntensityValue(mqttObject,
intensity, targetBri, targetBriInc, false); intensity, targetBri, targetBriInc, false);
mqttObject = DeviceDataDecode.replaceDeviceData(mqttObject, device);
mqttObject = TimeDecode.replaceTimeValue(mqttObject); mqttObject = TimeDecode.replaceTimeValue(mqttObject);
if (mqttObject.substring(0, 1).equalsIgnoreCase("{")) if (mqttObject.substring(0, 1).equalsIgnoreCase("{"))
mqttObject = "[" + mqttObject + "]"; mqttObject = "[" + mqttObject + "]";

View File

@@ -20,6 +20,7 @@ import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil; import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.hue.TimeDecode;
@@ -71,9 +72,11 @@ public class TCPHome implements Home {
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody); theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) { if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true); theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2)); sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else { } else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false); theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody); theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes(); sendData = theUrlBody.getBytes();
} }

View File

@@ -15,6 +15,7 @@ import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.api.CallItem; import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode; import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil; import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode; import com.bwssystems.HABridge.hue.TimeDecode;
import com.bwssystems.HABridge.util.UDPDatagramSender; import com.bwssystems.HABridge.util.UDPDatagramSender;
@@ -57,9 +58,11 @@ public class UDPHome implements Home {
theUrlBody = TimeDecode.replaceTimeValue(theUrlBody); theUrlBody = TimeDecode.replaceTimeValue(theUrlBody);
if (theUrlBody.startsWith("0x")) { if (theUrlBody.startsWith("0x")) {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true); theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, true);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2)); sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
} else { } else {
theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false); theUrlBody = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody, intensity, targetBri, targetBriInc, false);
theUrlBody = DeviceDataDecode.replaceDeviceData(theUrlBody, device);
theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody); theUrlBody = StringEscapeUtils.unescapeJava(theUrlBody);
sendData = theUrlBody.getBytes(); sendData = theUrlBody.getBytes();
} }

View File

@@ -1,5 +1,5 @@
.scrollableContainer { .scrollableContainer {
max-height: 436px; /* sets max-height value for all standards-compliant browsers */ height: 310px;
position: relative; position: relative;
padding-top: 35px; padding-top: 35px;
overflow: hidden; overflow: hidden;
@@ -28,17 +28,17 @@
} }
.scrollArea { .scrollArea {
_height: expression( this.scrollHeight > 599 ? "600px" : "auto" ); /* sets max-height for IE6 */ height: 100%;
max-height: 400px; /* sets max-height value for all standards-compliant browsers */
overflow-x: auto; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
border: 1px solid #d5d5d5; border: 1px solid #d5d5d5;
/* the implementation of this is still quite buggy; specifically, it doesn't like the /* the implementation of this is still quite buggy; specifically, it doesn't like the
absolutely positioned headers within absolutely positioned headers within
-webkit-overflow-scrolling: touch; */ -webkit-overflow-scrolling: touch; */
-webkit-overflow-scrolling: auto;
} }
.scrollArea table { .scrollArea table {
overflow-x: hidden; overflow-x: auto;
overflow-y: auto; overflow-y: auto;
margin-bottom: 0; margin-bottom: 0;
width: 100%; width: 100%;
@@ -48,7 +48,7 @@
.scrollArea table th { .scrollArea table th {
padding: 0 !important; padding: 0 !important;
border: none !important; border: none !important;
min-width: 60px; min-width: 40px;
} }
.scrollArea table .th-inner { .scrollArea table .th-inner {
overflow: hidden; overflow: hidden;

File diff suppressed because one or more lines are too long

View File

@@ -62,6 +62,7 @@ app.config (function ($locationProvider, $routeProvider) {
app.run( function (bridgeService) { app.run( function (bridgeService) {
bridgeService.loadBridgeSettings(); bridgeService.loadBridgeSettings();
bridgeService.getHABridgeVersion(); bridgeService.getHABridgeVersion();
bridgeService.getTestUser();
bridgeService.viewMapTypes(); bridgeService.viewMapTypes();
}); });
@@ -79,7 +80,7 @@ String.prototype.replaceAll = function (search, replace)
app.service ('bridgeService', function ($http, $window, ngToast) { app.service ('bridgeService', function ($http, $window, ngToast) {
var self = this; var self = this;
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.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: "", viewDevId: "", queueDevId: ""};
this.displayWarn = function(errorTitle, error) { this.displayWarn = function(errorTitle, error) {
var toastContent = errorTitle; var toastContent = errorTitle;
@@ -151,7 +152,7 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
this.clearDevice = function () { this.clearDevice = function () {
self.state.device = {}; self.state.device = {};
self.state.olddevicename = ""; self.state.olddevicename = null;
}; };
this.getHABridgeVersion = function () { this.getHABridgeVersion = function () {
@@ -165,6 +166,17 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
); );
}; };
this.getTestUser = function () {
return $http.get(this.state.systemsbase + "/habridge/testuser").then(
function (response) {
self.state.testuser = response.data.user;
},
function (error) {
self.displayWarn("Cannot get testuser: ", error);
}
);
};
this.aContainsB = function (a, b) { this.aContainsB = function (a, b) {
return a.indexOf(b) >= 0; return a.indexOf(b) >= 0;
} }
@@ -538,6 +550,20 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
type = self.getMapType(s.type[0]) type = self.getMapType(s.type[0])
s.type = type[0] s.type = type[0]
} }
if(s.delay === "" || s.delay === null)
delete s.delay;
if(s.count === "" || s.count === null)
delete s.count;
if(s.filterIPs === "" || s.filterIPs === null)
delete s.filterIPs;
if(s.httpVerb === "" || s.httpVerb === null)
delete s.httpVerb;
if(s.httpBody === "" || s.httpBody === null)
delete s.httpBody;
if(s.httpHeaders === "" || s.httpHeaders === null)
delete s.httpHeaders;
if(s.contentType === "" || s.contentType === null)
delete s.contentType;
} }
} }
return theDevices return theDevices
@@ -799,7 +825,7 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
this.testUrl = function (device, type, value) { this.testUrl = function (device, type, value) {
var msgDescription = "unknown"; var msgDescription = "unknown";
var testUrl = this.state.huebase + "/test/lights/" + device.id + "/state"; var testUrl = this.state.huebase + "/" + this.state.testuser + "/lights/" + device.id + "/state";
var testBody = "{\"on\":"; var testBody = "{\"on\":";
if (type === "off") { if (type === "off") {
testBody = testBody + "false"; testBody = testBody + "false";
@@ -1140,6 +1166,26 @@ app.controller('LogsController', function ($scope, $location, $http, $window, br
}; };
}); });
app.directive('postrenderAction', postrenderAction);
/* @ngInject */
function postrenderAction($timeout) {
// ### Directive Interface
// Defines base properties for the directive.
var directive = {
restrict: 'A',
priority: 101,
link: link
};
return directive;
// ### Link Function
// Provides functionality for the directive during the DOM building/data binding stage.
function link(scope, element, attrs) {
$timeout(function() {
scope.$evalAsync(attrs.postrenderAction);
}, 0);
}
}
app.controller('ViewingController', function ($scope, $location, $http, $window, bridgeService, ngDialog) { app.controller('ViewingController', function ($scope, $location, $http, $window, bridgeService, ngDialog) {
bridgeService.viewDevices(); bridgeService.viewDevices();
@@ -1205,7 +1251,16 @@ app.controller('ViewingController', function ($scope, $location, $http, $window,
else else
$scope.imgBkUrl = "glyphicon glyphicon-plus"; $scope.imgBkUrl = "glyphicon glyphicon-plus";
}; };
});
$scope.goToRow = function() {
if (bridgeService.state.queueDevId !== null && bridgeService.state.queueDevId !== "") {
bridgeService.state.viewDevId = bridgeService.state.queueDevId;
$scope.$broadcast("rowSelected", bridgeService.state.viewDevId);
console.log("Go to Row selected Id <<" + bridgeService.state.viewDevId + ">>")
bridgeService.state.queueDevId = null;
}
};
});
app.controller('ValueDialogCtrl', function ($scope, bridgeService, ngDialog) { app.controller('ValueDialogCtrl', function ($scope, bridgeService, ngDialog) {
$scope.slider = { $scope.slider = {
@@ -2622,20 +2677,25 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
if (copy) { if (copy) {
$scope.device.id = null; $scope.device.id = null;
$scope.device.uniqueid = null; $scope.device.uniqueid = null;
$scope.device.mapId = $scope.device.mapId + "-copy"; if($scope.bridge.olddevicename !== null && $scope.bridge.olddevicename !== "")
$scope.device.mapId = $scope.device.mapId + "-copy";
} }
if($scope.mapTypeSelected !== undefined && $scope.mapTypeSelected !== null) if($scope.mapTypeSelected !== undefined && $scope.mapTypeSelected !== null)
$scope.device.mapType = $scope.mapTypeSelected[0]; $scope.device.mapType = $scope.mapTypeSelected[0];
else else
$scope.device.mapType = null; $scope.device.mapType = null;
if ($scope.onDevices !== null) if ($scope.onDevices !== null)
$scope.device.onUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.onDevices)); $scope.device.onUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.onDevices));
if ($scope.dimDevices !== null) if ($scope.dimDevices !== null)
$scope.device.dimUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.dimDevices)); $scope.device.dimUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.dimDevices));
if ($scope.offDevices !== null) if ($scope.offDevices !== null)
$scope.device.offUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.offDevices)); $scope.device.offUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.offDevices));
bridgeService.addDevice($scope.device).then( bridgeService.addDevice($scope.device).then(
function () { function () {
bridgeService.state.queueDevId = $scope.device.id;
console.log("Device updated for Q Id <<" + bridgeService.state.queueDevId + ">>")
$scope.clearDevice(); $scope.clearDevice();
$location.path('/'); $location.path('/');
}, },
@@ -2653,7 +2713,7 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
if ($scope.onDevices === null) if ($scope.onDevices === null)
$scope.onDevices = []; $scope.onDevices = [];
$scope.onDevices.push(newitem); $scope.onDevices.push(newitem);
$scope.newOnItem = []; $scope.newOnItem = {};
}; };
$scope.removeItemOn = function (anItem) { $scope.removeItemOn = function (anItem) {
for(var i = $scope.onDevices.length - 1; i >= 0; i--) { for(var i = $scope.onDevices.length - 1; i >= 0; i--) {
@@ -2670,7 +2730,7 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
if ($scope.dimDevices === null) if ($scope.dimDevices === null)
$scope.dimDevices = []; $scope.dimDevices = [];
$scope.dimDevices.push(newitem); $scope.dimDevices.push(newitem);
$scope.newDimItem = []; $scope.newDimItem = {};
}; };
$scope.removeItemDim = function (anItem) { $scope.removeItemDim = function (anItem) {
for(var i = $scope.dimDevices.length - 1; i >= 0; i--) { for(var i = $scope.dimDevices.length - 1; i >= 0; i--) {
@@ -2687,7 +2747,7 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
if ($scope.offDevices === null) if ($scope.offDevices === null)
$scope.offDevices = []; $scope.offDevices = [];
$scope.offDevices.push(newitem); $scope.offDevices.push(newitem);
$scope.newOffItem = []; $scope.newOffItem = {};
}; };
$scope.removeItemOff = function (anItem) { $scope.removeItemOff = function (anItem) {
for(var i = $scope.offDevices.length - 1; i >= 0; i--) { for(var i = $scope.offDevices.length - 1; i >= 0; i--) {

View File

@@ -24,7 +24,7 @@
<li ng-if="bridge.showLifx" role="presentation"><a href="#!/lifxdevices">LIFX 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> <li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul> </ul>
<div postrender-action="goToRow()">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h1 class="panel-title">Current devices ({{bridge.devices.length}})</h1> <h1 class="panel-title">Current devices ({{bridge.devices.length}})</h1>
@@ -34,7 +34,6 @@
<p> <p>
<button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button> <button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button>
</p> </p>
<scrollable-table watch="bridge.devices"> <scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
@@ -42,6 +41,7 @@
<th>Row</th> <th>Row</th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th> <th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
<th sortable-header col="name">Name</th> <th sortable-header col="name">Name</th>
<th sortable-header col="description">Description</th>
<th sortable-header col="deviceType">Type</th> <th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th> <th sortable-header col="targetDevice">Target</th>
<th sortable-header col="inactive">Inactive</th> <th sortable-header col="inactive">Inactive</th>
@@ -49,10 +49,11 @@
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="device in bridge.devices"> <tr ng-repeat="device in bridge.devices" row-id="{{device.id}}" ng-class="{info: bridge.viewDevId == device.id}" >
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{device.id}}</td> <td>{{device.id}}</td>
<td>{{device.name}}</td> <td>{{device.name}}</td>
<td class="cr">{{device.description}}</td>
<td>{{device.deviceType}}</td> <td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td> <td>{{device.targetDevice}}</td>
<td>{{device.inactive}}</td> <td>{{device.inactive}}</td>
@@ -76,6 +77,7 @@
</scrollable-table> </scrollable-table>
</div> </div>
</div> </div>
</div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h1 class="panel-title"> <h1 class="panel-title">

View File

@@ -65,6 +65,18 @@
<td><input type="text" class="form-control" id="device-name" <td><input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"></td> ng-model="device.name" placeholder="Device Name"></td>
</tr> </tr>
<tr>
<td><label>Description</label></td>
<td><input type="text" class="form-control" id="device-description"
ng-model="device.description" placeholder="Device Description"></td>
</tr>
<tr>
<td><label>Comments</label></td>
<td><input type="text" class="form-control" id="device-comments"
ng-model="device.comments" placeholder="Device Comments"></td>
</tr>
<tr> <tr>
<td><label>Inactive</label></td> <td><label>Inactive</label></td>
<td><input type="checkbox" <td><input type="checkbox"
@@ -77,6 +89,12 @@
ng-model="device.noState" ng-true-value=true ng-model="device.noState" ng-true-value=true
ng-false-value=false> {{device.noState}}</td> ng-false-value=false> {{device.noState}}</td>
</tr> </tr>
<tr>
<td><label>Off State Resets Bri</label></td>
<td><input type="checkbox"
ng-model="device.offState" ng-true-value=true
ng-false-value=false> {{device.offState}}</td>
</tr>
<tr> <tr>
<td><label>Filter Address (comma separated list)</label></td> <td><label>Filter Address (comma separated list)</label></td>
<td><input type="text" class="form-control" id="device-requester-addr" <td><input type="text" class="form-control" id="device-requester-addr"

View File

@@ -12,6 +12,7 @@
<li ng-if="bridge.showMqtt" role="presentation"><a href="#!/mqttmessages">MQTT Messages</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.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.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 role="presentation" class="active"><a href="#!/lifxdevices">LIFX 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> <li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul> </ul>

View File

@@ -0,0 +1,21 @@
package com.bwssystems.color.test;
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import com.bwssystems.HABridge.hue.ColorDecode;
public class ConvertCIEColorTestCase {
@Test
public void testColorConversion() {
ArrayList<Double> xy = new ArrayList<Double>(Arrays.asList(new Double(0.3972), new Double(0.4564)));
String colorDecode = ColorDecode.convertCIEtoRGB(xy);
Assert.assertEquals(colorDecode, null);
}
}