diff --git a/README.md b/README.md index 16c5eb6..f30b89a 100644 --- a/README.md +++ b/README.md @@ -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” ``` -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” - -``` - - 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) - - Order deny,allow - Allow from all - - -….. (the rest of the VirtualHost config section) ….. - -``` - 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://:/` to send a UDP request. TCP calls are handled the same way as `tcp://:/`. 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()} 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"}] diff --git a/pom.xml b/pom.xml index 638a919..c147bbf 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 4.2.1 + 4.3.0 jar HA Bridge diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java index 9631a2d..0097e3a 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java @@ -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 perms = new HashSet(); - // 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 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 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 perms = new HashSet(); + // 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 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 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(); + } +} diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java b/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java index 567c2b3..5cf4ffe 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java @@ -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 theUserIds = whitelist.keySet(); + Iterator 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); + } + } } diff --git a/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java b/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java index 56a06a4..f8fae87 100644 --- a/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java +++ b/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java @@ -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); diff --git a/src/main/java/com/bwssystems/HABridge/HABridge.java b/src/main/java/com/bwssystems/HABridge/HABridge.java index 81cb921..f0c86a5 100644 --- a/src/main/java/com/bwssystems/HABridge/HABridge.java +++ b/src/main/java/com/bwssystems/HABridge/HABridge.java @@ -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(); diff --git a/src/main/java/com/bwssystems/HABridge/SystemControl.java b/src/main/java/com/bwssystems/HABridge/SystemControl.java index c1d8934..eaa1db6 100644 --- a/src/main/java/com/bwssystems/HABridge/SystemControl.java +++ b/src/main/java/com/bwssystems/HABridge/SystemControl.java @@ -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."); diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java index 547b04e..c31b0d0 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java @@ -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; diff --git a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java index ff6c186..f715ef8 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java @@ -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 variables = new HashMap(); - 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 variables = new HashMap(); + 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 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; + } } \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java new file mode 100644 index 0000000..1585d45 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -0,0 +1,21 @@ +package com.bwssystems.HABridge.hue; + +import java.util.List; + +public class ColorDecode { + + public static String convertCIEtoRGB(List 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; + } +} diff --git a/src/main/java/com/bwssystems/HABridge/hue/DeviceDataDecode.java b/src/main/java/com/bwssystems/HABridge/hue/DeviceDataDecode.java new file mode 100644 index 0000000..7997d15 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/DeviceDataDecode.java @@ -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; + } + +} diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index dca0dfe..bab604f 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -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 theUserIds = bridgeSettings.getWhitelist().keySet(); - Iterator userIterator = theUserIds.iterator(); - while (userIterator.hasNext()) { - validUser = userIterator.next(); - if (validUser.equals(aUser)) - found = true; - } - } - - if (!found && !strict) { - if (bridgeSettings.getWhitelist() == null) { - Map 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 groupResponseMap = null; - theErrors = validateWhitelistUser(userId, false); + theErrors = bridgeSettings.validateWhitelistUser(userId, null, false); if (theErrors == null) { + if(bridgeSettings.isSettingsChanged()) + bridgeSettingMaster.updateConfigFile(); + groupResponseMap = new HashMap(); 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 deviceList = repository.findAllByRequester(requestIp); // List deviceList = repository.findActive(); deviceResponseMap = new HashMap(); @@ -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 = ""; + + 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; diff --git a/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java b/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java index 6d1c835..62343f7 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/TimeDecode.java @@ -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; diff --git a/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java b/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java index 0879188..2ddaf35 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/exec/CommandHome.java @@ -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) { diff --git a/src/main/java/com/bwssystems/HABridge/plugins/harmony/HarmonyHome.java b/src/main/java/com/bwssystems/HABridge/plugins/harmony/HarmonyHome.java index 5857f08..11d2c97 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/harmony/HarmonyHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/harmony/HarmonyHome.java @@ -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++) { diff --git a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java index 985fd7e..6e52459 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHome.java @@ -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 diff --git a/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java b/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java index 79212c1..0b490aa 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/mqtt/MQTTHome.java @@ -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 + "]"; diff --git a/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java b/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java index 53cb664..d359e96 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/tcp/TCPHome.java @@ -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(); } diff --git a/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java b/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java index 1244bff..3d49881 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/udp/UDPHome.java @@ -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(); } diff --git a/src/main/resources/public/css/scrollable-table.css b/src/main/resources/public/css/scrollable-table.css index 39364c0..492656d 100644 --- a/src/main/resources/public/css/scrollable-table.css +++ b/src/main/resources/public/css/scrollable-table.css @@ -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; diff --git a/src/main/resources/public/js/angular-scrollable-table.min.js b/src/main/resources/public/js/angular-scrollable-table.min.js index 513e1fb..93a1539 100644 --- a/src/main/resources/public/js/angular-scrollable-table.min.js +++ b/src/main/resources/public/js/angular-scrollable-table.min.js @@ -1 +1 @@ -(function(e){"use strict";function t(e){return parseInt(e.replace(/px|%/,""),10)}e.module("scrollable-table",[]).directive("scrollableTable",["$timeout","$q","$parse",function(n,r,i){return{transclude:true,restrict:"E",scope:{rows:"=watch",sortFn:"="},template:'
'+'
'+'
'+"
",controller:["$scope","$element","$attrs",function(s,o,u){function a(e,t){var n=s.sortExpr.match(/(.+)\s+as\s+(.+)/);var r={};r[n[1]]=e;var o=i(n[2])(r);r[n[1]]=t;var u=i(n[2])(r);if(o===u)return 0;return o>u?1:-1}function f(e){var t=o.find(".headerSpacer").height();var n=o.find(".scrollArea").scrollTop();o.find(".scrollArea").scrollTop(n+e.position().top-t)}function l(){function t(){if(o.find("table:visible").length===0){n(t,100)}else{e.resolve()}}var e=r.defer();n(t);return e.promise}function h(){if(!o.find("thead th .th-inner").length){o.find("thead th").wrapInner('
')}if(o.find("thead th .th-inner:not(:has(.box))").length){o.find("thead th .th-inner:not(:has(.box))").wrapInner('
')}o.find("table th .th-inner:visible").each(function(n,r){r=e.element(r);var i=r.parent().width(),s=o.find("table th:visible:last"),u=i;if(s.css("text-align")!=="center"){var a=o.find(".scrollArea").height()'+'
'+'
'+''+''+''+''+""+""+"
"+"",link:function(t,n,r,i){var s=r.on||"a as a."+r.col;t.element=e.element(n);t.isActive=function(){return i.getSortExpr()===s};t.toggleSort=function(e){if(t.isActive()){i.toggleSort()}else{i.setSortExpr(s)}i.doSort(t[r.comparatorFn]);e.preventDefault()};t.isAscending=function(){if(t.focused&&!t.isActive()){return true}else{return i.isAsc()}};t.enter=function(){t.focused=true};t.leave=function(){t.focused=false};t.isLastCol=function(){return n.parent().find("th:last-child").get(0)===n.get(0)}}}}]).directive("resizable",["$compile",function(n){return{restrict:"A",priority:0,require:"^scrollableTable",link:function(i,s,o,u){function a(){var t=s.find("table th .th-inner");if(t.find(".resize-rod").length==0){u.getTableElement().find(".scrollArea table").css("table-layout","auto");var r=e.element('
');t.append(n(r)(i))}}function f(){var n=u.getTableElement();var r=1;n.find("table th .th-inner:visible").each(function(n,i){i=e.element(i);var s=i.parent().width(),o=t(i.parent().css("min-width"));s=Math.max(o,s);i.css("left",r);r+=s})}function l(){var n=1,r=u.getTableElement();u.getTableElement().find("table th .th-inner:visible").each(function(i,s){s=e.element(s);var o=s.parent().width(),u=r.find("table th:visible:last"),a=t(s.parent().css("min-width"));o=Math.max(a,o);if(u[0]!=s.parent()[0]){s.parent().css("width",o)}s.css("left",n);n+=o})}function c(n){var r=u.getTableElement(),i=r.find("table th:visible").length,s=r.find("table th:visible:last");r.find("table th:visible").each(function(r,o){o=e.element(o);if(s.get(0)==o.get(0)){o.css("width","auto");return}var u=o.data("width");if(/\d+%$/.test(u)){u=Math.ceil(n*t(u)/100)}else{u=n/i}o.css("width",u+"px")});u.renderTalble().then(l())}u.appendTableResizingHandler(function(){a()});u.appendTableResizingHandler(function(){var t=u.getTableElement().find(".scrollArea table");if(t.css("table-layout")==="auto"){f()}else{c(t.parent().width())}});i.resizing=function(n){var r=u.getTableElement().find(".scrollArea").scrollLeft(),i=e.element(n.target).parent(),s=i.parent(),o=t(i.css("left"))+i.width()-r,a=n.pageX,f=e.element(document),c=e.element("body"),h=e.element(".scrollableContainer .resizing-cover"),p=e.element('
');c.addClass("scrollable-resizing");h.addClass("active");e.element(".scrollableContainer").append(p);p.css("left",o);f.bind("mousemove",function(e){var n=e.pageX-a,r=t(p.css("left"))-o,i=s.width(),u=t(s.css("min-width")),f=s.next().width(),l=t(s.next().css("min-width"));a=e.pageX;e.preventDefault();if(n>0&&f-r<=l||n<0&&i+r<=u){return}p.css("left",t(p.css("left"))+n)});f.bind("mouseup",function(n){n.preventDefault();p.remove();c.removeClass("scrollable-resizing");h.removeClass("active");f.unbind("mousemove");f.unbind("mouseup");var r=t(p.css("left"))-o,i=s.width(),a=t(s.css("min-width")),d=s.next().width(),v=t(s.next().css("min-width")),m=u.getTableElement().find(".scrollArea table");if(m.css("table-layout")==="auto"){m.find("th .th-inner").each(function(t,n){n=e.element(n);var r=n.parent().width();n.parent().css("width",r)})}m.css("table-layout","fixed");if(r>0&&d-r<=v){r=d-v}s.next().removeAttr("style");i+=r;s.css("width",Math.max(a,i));s.next().css("width",d-r);u.renderTalble().then(l())})}}}}])})(angular) \ No newline at end of file +!function(e){"use strict";function t(e){return parseInt(e.replace(/px|%/,""),10)}var n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1;e.module("scrollable-table",[]).directive("scrollableTable",["$timeout","$q","$parse",function(i,r,s){return{transclude:!0,restrict:"E",scope:{rows:"=watch",sortFn:"="},template:'
',controller:["$scope","$element","$attrs",function(a,l,o){function c(e,t){var n=a.sortExpr.match(/(.+)\s+as\s+(.+)/),i={};i[n[1]]=e;var r=s(n[2])(i);i[n[1]]=t;var l=s(n[2])(i);return r===l?0:r>l?1:-1}function d(e){var t=l.find(".headerSpacer").height(),n=l.find(".scrollArea").scrollTop();l.find(".scrollArea").scrollTop(n+e.position().top-t)}function h(){function e(){0===l.find("table:visible").length?i(e,100):t.resolve()}var t=r.defer();return i(e),t.promise}function f(){l.find("thead th .th-inner").length||l.find("thead th").wrapInner('
'),l.find("thead th .th-inner:not(:has(.box))").length&&l.find("thead th .th-inner:not(:has(.box))").wrapInner('
'),l.find("table th .th-inner:visible").each(function(n,i){i=e.element(i);var r=i.parent().width(),s=l.find("table th:visible:last"),a=r;if("center"!==s.css("text-align")){var o=l.find(".scrollArea").height()
',link:function(t,n,i,r){var s=i.on||"a as a."+i.col;t.element=e.element(n),t.isActive=function(){return r.getSortExpr()===s},t.toggleSort=function(e){t.isActive()?r.toggleSort():r.setSortExpr(s),r.doSort(t[i.comparatorFn]),e.preventDefault()},t.isAscending=function(){return!(!t.focused||t.isActive())||r.isAsc()},t.enter=function(){t.focused=!0},t.leave=function(){t.focused=!1},t.isLastCol=function(){return n.parent().find("th:last-child").get(0)===n.get(0)}}}}]).directive("resizable",["$compile",function(n){return{restrict:"A",priority:0,scope:!1,require:"scrollableTable",link:function(i,r,s,a){function l(){var t=r.find("table th:not(:last-child) .th-inner");if(0==t.find(".resize-rod").length){a.getTableElement().find(".scrollArea table").css("table-layout","auto");var s=e.element('
');t.append(n(s)(i))}}function o(){var n=a.getTableElement(),i=1;n.find("table th .th-inner:visible").each(function(n,r){r=e.element(r);var s=r.parent().width(),a=t(r.parent().css("min-width"));s=Math.max(a,s),r.css("left",i),i+=s})}function c(){var n=1,i=a.getTableElement();a.getTableElement().find("table th .th-inner:visible").each(function(r,s){s=e.element(s);var a=s.parent().width(),l=i.find("table th:visible:last"),o=t(s.parent().css("min-width"));a=Math.max(o,a),l[0]!=s.parent()[0]&&s.parent().css("width",a),s.css("left",n),n+=a})}function d(n){var i=a.getTableElement(),r=i.find("table th:visible").length,s=i.find("table th:visible:last");i.find("table th:visible").each(function(i,a){if(a=e.element(a),s.get(0)==a.get(0))return void a.css("width","auto");var l=a.data("width");l=/\d+%$/.test(l)?Math.ceil(n*t(l)/100):n/r,a.css("width",l+"px")}),a.renderTalble().then(c())}a.appendTableResizingHandler(function(){l()}),a.appendTableResizingHandler(function(){var e=a.getTableElement().find(".scrollArea table");"auto"===e.css("table-layout")?o():d(e.parent().width())}),i.resizing=function(n){var i=a.getTableElement().find(".scrollArea").scrollLeft(),r=e.element(n.target).parent(),s=r.parent(),l=t(r.css("left"))+r.width()-i,o=n.pageX,d=e.element(document),h=e.element("body"),f=e.element(".scrollableContainer .resizing-cover"),u=e.element('
');h.addClass("scrollable-resizing"),f.addClass("active"),e.element(".scrollableContainer").append(u),u.css("left",l),d.bind("mousemove",function(e){var n=e.pageX-o,i=t(u.css("left"))-l,r=s.width(),a=s.nextAll("th:visible").first(),c=t(s.css("min-width")),d=a.width(),h=t(a.css("min-width"));o=e.pageX,e.preventDefault(),n>0&&d-i<=h||n<0&&r+i<=c||u.css("left",t(u.css("left"))+n)}),d.bind("mouseup",function(n){n.preventDefault(),u.remove(),h.removeClass("scrollable-resizing"),f.removeClass("active"),d.unbind("mousemove"),d.unbind("mouseup");var i=t(u.css("left"))-l,r=s.width(),o=t(s.css("min-width")),v=s.nextAll("th:visible").first(),p=v.width(),b=t(v.css("min-width")),g=a.getTableElement().find(".scrollArea table");"auto"===g.css("table-layout")&&g.find("th .th-inner").each(function(t,n){n=e.element(n);var i=n.parent().width();n.parent().css("width",i)}),g.css("table-layout","fixed"),i>0&&p-i<=b&&(i=p-b),v.removeAttr("style"),r+=i,s.css("width",Math.max(o,r)),v.css("width",p-i),a.renderTalble().then(c())})}}}}])}(angular); \ No newline at end of file diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index e73742f..f01e6ee 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -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--) { diff --git a/src/main/resources/public/views/configuration.html b/src/main/resources/public/views/configuration.html index 14dc9b4..3cb3123 100644 --- a/src/main/resources/public/views/configuration.html +++ b/src/main/resources/public/views/configuration.html @@ -24,7 +24,7 @@
  • LIFX Devices
  • Add/Edit
  • - +

    Current devices ({{bridge.devices.length}})

    @@ -34,7 +34,6 @@

    - @@ -42,6 +41,7 @@ + @@ -49,10 +49,11 @@ - + + @@ -76,6 +77,7 @@ +

    diff --git a/src/main/resources/public/views/editdevice.html b/src/main/resources/public/views/editdevice.html index 759b10b..ae1439d 100644 --- a/src/main/resources/public/views/editdevice.html +++ b/src/main/resources/public/views/editdevice.html @@ -65,6 +65,18 @@

    + + + + + + + + + + + + + +
    Row ID NameDescription Type Target InactiveActions
    {{$index+1}} {{device.id}} {{device.name}}{{device.description}} {{device.deviceType}} {{device.targetDevice}} {{device.inactive}}
    {{device.noState}}
    {{device.offState}}
    MQTT Messages
  • HomeAssistant Devices
  • Domoticz Devices
  • +
  • Somfy Devices
  • Add/Edit
  • diff --git a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java new file mode 100644 index 0000000..4d148be --- /dev/null +++ b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java @@ -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 xy = new ArrayList(Arrays.asList(new Double(0.3972), new Double(0.4564))); + + String colorDecode = ColorDecode.convertCIEtoRGB(xy); + Assert.assertEquals(colorDecode, null); + } + +}