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.
```
java -jar ha-bridge-4.2.1.jar
java -jar ha-bridge-4.3.0.jar
```
### Automation on Linux systems
To have this configured and running automatically there are a few resources to use. One is using Docker and a docker container has been built for this and can be gotten here: https://github.com/aptalca/docker-ha-bridge
Create the directory and make sure that ha-bridge-4.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:~ $ 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:~ $ 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)
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]
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]
WantedBy=multi-user.target
@@ -130,7 +130,7 @@ Then cut and past this, modify any locations that are not correct
```
cd /home/pi/habridge
rm /home/pi/habridge/habridge-log.txt
nohup java -jar -Dconfig.file=/home/pi/habridge/data/habridge.config /home/pi/habridge/ha-bridge-4.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
```
@@ -175,54 +175,6 @@ Added the following lines to my Apache config file “000-default”
</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
### lighthttpd Example
```
@@ -287,13 +239,9 @@ The server defaults to running on port 80. To override what the default is, spec
#### UPNP Response Port
The upnp response port that will be used. The default is 50000.
#### 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
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.
#### Harmony Username
deprecated
#### Harmony Password
deprecated
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}
#### 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.
@@ -311,7 +259,7 @@ The password for the user name of the home.nest.com account for the Nest user. T
#### 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.
#### 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
The password associated with the Somfy Tahoma username above
#### 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:
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 | 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
@@ -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.
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:
```
@@ -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.
```
[{"item":"C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"},
{"item":"notepad.exe","type":"cmdDevice"}]
OR
[{"item":"exec://notepad.exe","type":"cmdDevice"}]
[{"item":"exec://C:\\Users\\John\\Documents\\Applications\\putty.exe 192.168.1.1","type":"cmdDevice"},{"item":"exec://notepad.exe","type":"cmdDevice"}]
[{"item":"/home/pi/scripts/dothisscript.sh","type":"cmdDevice"}]
```
#### 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)}".
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.
```
[{"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>
<artifactId>ha-bridge</artifactId>
<version>4.2.1</version>
<version>4.3.0</version>
<packaging>jar</packaging>
<name>HA Bridge</name>

View File

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

View File

@@ -72,7 +72,7 @@ public class HABridge {
theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
theSettingResponder.setupServer();
// 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();
// wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization();

View File

@@ -62,6 +62,13 @@ public class SystemControl {
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
get (SYSTEM_CONTEXT + "/logmsgs", "application/json", (request, response) -> {
log.debug("Get logmsgs.");

View File

@@ -62,9 +62,18 @@ public class DeviceDescriptor{
@SerializedName("noState")
@Expose
private boolean noState;
@SerializedName("offState")
@Expose
private boolean offState;
@SerializedName("requesterAddress")
@Expose
private String requesterAddress;
@SerializedName("description")
@Expose
private String description;
@SerializedName("comments")
@Expose
private String comments;
private DeviceState deviceState;
@@ -222,6 +231,14 @@ public class DeviceDescriptor{
this.noState = noState;
}
public boolean isOffState() {
return offState;
}
public void setOffState(boolean offState) {
this.offState = offState;
}
public String getRequesterAddress() {
return requesterAddress;
}
@@ -230,6 +247,22 @@ public class DeviceDescriptor{
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) {
if(aType == null)
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_VALUE = "X";
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) {
if (targetBri != null) {
@@ -45,50 +48,79 @@ public class BrightnessDecode {
if (request == null) {
return null;
}
if (request.contains(INTENSITY_BYTE)) {
if (isHex) {
String hexValue = convertToHex(intensity);
request = request.replace(INTENSITY_BYTE, hexValue);
} else {
String intensityByte = String.valueOf(intensity);
request = request.replace(INTENSITY_BYTE, intensityByte);
}
} else if (request.contains(INTENSITY_PERCENT)) {
int percentBrightness = (int) Math.round(intensity / 255.0 * 100);
if (isHex) {
String hexValue = convertToHex(percentBrightness);
request = request.replace(INTENSITY_PERCENT, hexValue);
} else {
String intensityPercent = String.valueOf(percentBrightness);
request = request.replace(INTENSITY_PERCENT, intensityPercent);
}
} else if (request.contains(INTENSITY_DECIMAL_PERCENT)) {
float decimalBrightness = (float) (intensity / 255.0);
String intensityPercent = String.format("%1.2f", decimalBrightness);
request = request.replace(INTENSITY_DECIMAL_PERCENT, intensityPercent);
} else if (request.contains(INTENSITY_MATH)) {
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),
request.indexOf(INTENSITY_MATH_CLOSE));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
try {
boolean notDone = true;
String replaceValue = null;
String replaceTarget = null;
int percentBrightness = (int) Math.round(intensity / 255.0 * 100);
float decimalBrightness = (float) (intensity / 255.0);
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = null;
while(notDone) {
notDone = false;
if (request.contains(INTENSITY_BYTE)) {
if (isHex) {
replaceValue = convertToHex(intensity);
} else {
replaceValue = String.valueOf(intensity);
}
replaceTarget = INTENSITY_BYTE;
notDone = true;
} else if (request.contains(INTENSITY_BYTE_HEX)) {
replaceValue = convertToHex(intensity);
replaceTarget = INTENSITY_BYTE_HEX;
notDone = true;
} else if (request.contains(INTENSITY_PERCENT)) {
if (isHex) {
replaceValue = convertToHex(percentBrightness);
} else {
replaceValue = String.valueOf(percentBrightness);
}
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: "
+ String.valueOf(intensity));
Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables);
Integer endResult = Math.round(result.floatValue());
if (isHex) {
String hexValue = convertToHex(endResult);
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue);
} else {
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE,
endResult.toString());
Integer endResult = calculateMath(variables, mathDescriptor);
if(endResult != null) {
if (isHex) {
replaceValue = convertToHex(endResult);
} else {
replaceValue = endResult.toString();
}
replaceTarget = INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE;
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;
}
@@ -108,4 +140,17 @@ public class BrightnessDecode {
newBytes[1] = theBytes[0];
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;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.DeviceMapTypes;
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.HuePublicConfig;
import com.bwssystems.HABridge.api.hue.StateChangeBody;
import com.bwssystems.HABridge.api.hue.WhitelistEntry;
import com.bwssystems.HABridge.dao.*;
import com.bwssystems.HABridge.plugins.hue.HueHome;
import com.bwssystems.HABridge.util.JsonTransformer;
@@ -33,12 +33,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
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
@@ -52,13 +48,15 @@ public class HueMulator {
private HomeManager homeManager;
private HueHome myHueHome;
private BridgeSettingsDescriptor bridgeSettings;
private BridgeSettings bridgeSettingMaster;
private Gson aGsonHandler;
private DeviceMapTypes validMapTypes;
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HomeManager aHomeManager) {
public HueMulator(BridgeSettings bridgeMaster, DeviceRepository aDeviceRepository, HomeManager aHomeManager) {
repository = aDeviceRepository;
validMapTypes = new DeviceMapTypes();
bridgeSettings = theBridgeSettings;
bridgeSettingMaster = bridgeMaster;
bridgeSettings = bridgeSettingMaster.getBridgeSettingsDescriptor();
homeManager= aHomeManager;
myHueHome = (HueHome) homeManager.findHome(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]);
aGsonHandler = new GsonBuilder().create();
@@ -393,7 +391,7 @@ public class HueMulator {
}
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 = "[";
boolean notFirstChange = false;
@@ -408,6 +406,8 @@ public class HueMulator {
deviceState.setOn(stateChanges.isOn());
if(!deviceState.isOn() && deviceState.getBri() == 254)
deviceState.setBri(0);
if(!deviceState.isOn() && offState)
deviceState.setBri(0);
}
notFirstChange = true;
}
@@ -556,50 +556,6 @@ public class HueMulator {
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) {
if (requesterFilterList == null || requesterFilterList.length() == 0)
return true;
@@ -620,9 +576,13 @@ public class HueMulator {
private String basicListHandler(String type, String userId, String requestIp) {
log.debug("hue " + type + " list requested: " + userId + " from " + requestIp);
HueError[] theErrors = validateWhitelistUser(userId, false);
if (theErrors != null)
HueError[] theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors != null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
return aGsonHandler.toJson(theErrors);
}
return "{}";
}
@@ -630,8 +590,11 @@ public class HueMulator {
log.debug("hue group list requested: " + userId + " from " + requestIp);
HueError[] theErrors = null;
Map<String, GroupResponse> groupResponseMap = null;
theErrors = validateWhitelistUser(userId, false);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
groupResponseMap = new HashMap<String, GroupResponse>();
groupResponseMap.put("1", (GroupResponse) this.groupsIdHandler("1", userId, requestIp));
return groupResponseMap;
@@ -644,8 +607,11 @@ public class HueMulator {
private Object groupsIdHandler(String groupId, String userId, String requestIp) {
log.debug("hue group id: <" + groupId + "> requested: " + userId + " from " + requestIp);
HueError[] theErrors = null;
theErrors = validateWhitelistUser(userId, false);
theErrors = bridgeSettings.validateWhitelistUser(userId, null, false);
if (theErrors == null) {
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (groupId.equalsIgnoreCase("0")) {
GroupResponse theResponse = GroupResponse.createDefaultGroupResponse(repository.findActive());
return theResponse;
@@ -666,8 +632,11 @@ public class HueMulator {
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: 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(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
List<DeviceDescriptor> deviceList = repository.findAllByRequester(requestIp);
// List<DeviceDescriptor> deviceList = repository.findActive();
deviceResponseMap = new HashMap<String, DeviceResponse>();
@@ -724,12 +693,20 @@ public class HueMulator {
newUser = aNewUser.getUsername();
aDeviceType = aNewUser.getDevicetype();
}
if (newUser == null)
newUser = getNewUserID();
validateWhitelistUser(newUser, false);
if (aDeviceType == null)
aDeviceType = "<not given>";
if (newUser == null) {
newUser = bridgeSettings.createWhitelistUser(aDeviceType);
}
else {
bridgeSettings.validateWhitelistUser(newUser, aDeviceType, false);
}
if(bridgeSettings.isSettingsChanged())
bridgeSettingMaster.updateConfigFile();
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: "
+ newUser + (followingSlash ? " /api/ called" : ""));
@@ -743,7 +720,7 @@ public class HueMulator {
if (bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api/:userid/config 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");
HuePublicConfig apiResponse = HuePublicConfig.createConfig("Philips hue",
bridgeSettings.getUpnpConfigAddress(), bridgeSettings.getHubversion());
@@ -759,7 +736,7 @@ public class HueMulator {
@SuppressWarnings("unchecked")
private Object getFullState(String userId, String 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)
return theErrors;
@@ -773,7 +750,7 @@ public class HueMulator {
private Object getLight(String userId, String lightId, String 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)
return theErrors;
@@ -817,7 +794,7 @@ public class HueMulator {
Integer targetBri = null;
Integer targetBriInc = null;
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)
return aGsonHandler.toJson(theErrors);
try {
@@ -849,7 +826,7 @@ public class HueMulator {
if (state == null)
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);
return responseString;
@@ -867,7 +844,7 @@ public class HueMulator {
aMultiUtil.setDelayDefault(bridgeSettings.getButtonsleep());
aMultiUtil.setSetCount(1);
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)
return aGsonHandler.toJson(theErrors);
try {
@@ -989,11 +966,11 @@ public class HueMulator {
if (responseString == null || !responseString.contains("[{\"error\":")) {
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);
} else {
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;

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ import com.bwssystems.HABridge.IpList;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -161,6 +162,8 @@ public class HarmonyHome implements Home {
if (url.substring(0, 1).equalsIgnoreCase("{")) {
url = "[" + url + "]";
}
url = BrightnessDecode.calculateReplaceIntensityValue(url, intensity, targetBri, targetBriInc, false);
ButtonPress[] deviceButtons = aGsonHandler.fromJson(url, ButtonPress[].class);
Integer theCount = 1;
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.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.google.gson.Gson;
@@ -49,12 +50,14 @@ public class HTTPHome implements Home {
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrl,
intensity, targetBri, targetBriInc, false);
anUrl = DeviceDataDecode.replaceDeviceData(anUrl, device);
anUrl = TimeDecode.replaceTimeValue(anUrl);
String aBody = null;
if(anItem.getHttpBody()!= null && !anItem.getHttpBody().isEmpty()) {
aBody = BrightnessDecode.calculateReplaceIntensityValue(anItem.getHttpBody(),
intensity, targetBri, targetBriInc, false);
aBody = DeviceDataDecode.replaceDeviceData(aBody, device);
aBody = TimeDecode.replaceTimeValue(aBody);
}
// make call

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
.scrollableContainer {
max-height: 436px; /* sets max-height value for all standards-compliant browsers */
height: 310px;
position: relative;
padding-top: 35px;
overflow: hidden;
@@ -28,17 +28,17 @@
}
.scrollArea {
_height: expression( this.scrollHeight > 599 ? "600px" : "auto" ); /* sets max-height for IE6 */
max-height: 400px; /* sets max-height value for all standards-compliant browsers */
height: 100%;
overflow-x: auto;
overflow-y: auto;
border: 1px solid #d5d5d5;
/* the implementation of this is still quite buggy; specifically, it doesn't like the
absolutely positioned headers within
-webkit-overflow-scrolling: touch; */
-webkit-overflow-scrolling: auto;
}
.scrollArea table {
overflow-x: hidden;
overflow-x: auto;
overflow-y: auto;
margin-bottom: 0;
width: 100%;
@@ -48,7 +48,7 @@
.scrollArea table th {
padding: 0 !important;
border: none !important;
min-width: 60px;
min-width: 40px;
}
.scrollArea table .th-inner {
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) {
bridgeService.loadBridgeSettings();
bridgeService.getHABridgeVersion();
bridgeService.getTestUser();
bridgeService.viewMapTypes();
});
@@ -79,7 +80,7 @@ String.prototype.replaceAll = function (search, replace)
app.service ('bridgeService', function ($http, $window, ngToast) {
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) {
var toastContent = errorTitle;
@@ -151,7 +152,7 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
this.clearDevice = function () {
self.state.device = {};
self.state.olddevicename = "";
self.state.olddevicename = null;
};
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) {
return a.indexOf(b) >= 0;
}
@@ -538,6 +550,20 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
type = self.getMapType(s.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
@@ -799,7 +825,7 @@ app.service ('bridgeService', function ($http, $window, ngToast) {
this.testUrl = function (device, type, value) {
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\":";
if (type === "off") {
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) {
bridgeService.viewDevices();
@@ -1205,7 +1251,16 @@ app.controller('ViewingController', function ($scope, $location, $http, $window,
else
$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) {
$scope.slider = {
@@ -2622,20 +2677,25 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
if (copy) {
$scope.device.id = 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)
$scope.device.mapType = $scope.mapTypeSelected[0];
else
$scope.device.mapType = null;
if ($scope.onDevices !== null)
$scope.device.onUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.onDevices));
if ($scope.dimDevices !== null)
$scope.device.dimUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.dimDevices));
if ($scope.offDevices !== null)
$scope.device.offUrl = angular.toJson(bridgeService.updateCallObjectsType($scope.offDevices));
bridgeService.addDevice($scope.device).then(
function () {
bridgeService.state.queueDevId = $scope.device.id;
console.log("Device updated for Q Id <<" + bridgeService.state.queueDevId + ">>")
$scope.clearDevice();
$location.path('/');
},
@@ -2653,7 +2713,7 @@ app.controller('EditController', function ($scope, $location, $http, bridgeServi
if ($scope.onDevices === null)
$scope.onDevices = [];
$scope.onDevices.push(newitem);
$scope.newOnItem = [];
$scope.newOnItem = {};
};
$scope.removeItemOn = function (anItem) {
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)
$scope.dimDevices = [];
$scope.dimDevices.push(newitem);
$scope.newDimItem = [];
$scope.newDimItem = {};
};
$scope.removeItemDim = function (anItem) {
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)
$scope.offDevices = [];
$scope.offDevices.push(newitem);
$scope.newOffItem = [];
$scope.newOffItem = {};
};
$scope.removeItemOff = function (anItem) {
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 role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</ul>
<div postrender-action="goToRow()">
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">Current devices ({{bridge.devices.length}})</h1>
@@ -34,7 +34,6 @@
<p>
<button class="btn btn-primary" type="submit" ng-click="renumberDevices()">Renumber Devices</button>
</p>
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
@@ -42,6 +41,7 @@
<th>Row</th>
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="description">Description</th>
<th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th>
<th sortable-header col="inactive">Inactive</th>
@@ -49,10 +49,11 @@
<th>Actions</th>
</tr>
</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>{{device.id}}</td>
<td>{{device.name}}</td>
<td class="cr">{{device.description}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.inactive}}</td>
@@ -76,6 +77,7 @@
</scrollable-table>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h1 class="panel-title">

View File

@@ -65,6 +65,18 @@
<td><input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"></td>
</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>
<td><label>Inactive</label></td>
<td><input type="checkbox"
@@ -77,6 +89,12 @@
ng-model="device.noState" ng-true-value=true
ng-false-value=false> {{device.noState}}</td>
</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>
<td><label>Filter Address (comma separated list)</label></td>
<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.showHass" role="presentation"><a href="#!/hassdevices">HomeAssistant Devices</a></li>
<li ng-if="bridge.showDomoticz" role="presentation"><a href="#!/domoticzdevices">Domoticz Devices</a></li>
<li ng-if="bridge.showSomfy" role="presentation"><a href="#!/somfydevices">Somfy Devices</a></li>
<li role="presentation" class="active"><a href="#!/lifxdevices">LIFX Devices</a></li>
<li role="presentation"><a href="#!/editdevice">Add/Edit</a></li>
</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);
}
}