From 51c6ffc48a2e8e8c6ac2f824bf0e1805290dd9c6 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Tue, 24 Sep 2019 08:40:16 -0500 Subject: [PATCH 01/19] Auto stash before rebase of "origin/dev_branch_5.3.x" --- .../HABridge/upnp/UpnpListener.java | 28 +++------- .../HABridge/upnp/UpnpSettingsResource.java | 32 +++-------- .../bwssystems/HABridge/util/AddressUtil.java | 56 +++++++++++++++++++ 3 files changed, 72 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/bwssystems/HABridge/util/AddressUtil.java diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java index 0bcf81bf..57710b96 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java @@ -9,6 +9,7 @@ import com.bwssystems.HABridge.api.hue.HueConstants; import com.bwssystems.HABridge.api.hue.HuePublicConfig; import com.bwssystems.HABridge.util.UDPDatagramSender; +import com.bwssystems.HABridge.util.AddressUtil; import java.io.IOException; import java.net.*; @@ -331,8 +332,14 @@ protected void sendUpnpResponse(DatagramPacket aPacket) throws IOException { InetAddress requester = aPacket.getAddress(); int sourcePort = aPacket.getPort(); String discoveryResponse = null; - // refactored suggestion by https://github.com/pvint - String httpLocationAddress = getOutboundAddress(requesterAddress).getHostAddress(); + String httpLocationAddress = null; + if(useUpnpIface) { + httpLocationAddress = upnpConfigIP; + } else { + // refactored suggestion by https://github.com/pvint + httpLocationAddress = AddressUtil.getOutboundAddress(requesterAddress).getHostAddress(); + } + try { Thread.sleep(theUpnpSendDelay); } catch (InterruptedException e) { @@ -437,21 +444,4 @@ protected void sendUpnpNotify(InetAddress aSocketAddress) throws IOException { log.debug("sendUpnpNotify notifyTemplate3 is <<<{}>>>", notifyData); sendUDPResponse(notifyData.getBytes(), aSocketAddress, Configuration.UPNP_DISCOVERY_PORT); } - - // added by https://github.com/pvint - // Ruthlessly stolen from - // https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface - // Try to get a source IP that makes sense for the requestor to contact for use - // in the LOCATION header in replies - private InetAddress getOutboundAddress(SocketAddress remoteAddress) throws SocketException { - DatagramSocket sock = new DatagramSocket(); - // connect is needed to bind the socket and retrieve the local address - // later (it would return 0.0.0.0 otherwise) - sock.connect(remoteAddress); - final InetAddress localAddress = sock.getLocalAddress(); - sock.disconnect(); - sock.close(); - sock = null; - return localAddress; - } } diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java index b62d84e3..33d75349 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java @@ -7,6 +7,7 @@ import com.bwssystems.HABridge.BridgeSettingsDescriptor; import com.bwssystems.HABridge.api.hue.HueConstants; import com.bwssystems.HABridge.api.hue.HuePublicConfig; +import com.bwssystems.HABridge.util.AddressUtil; import com.bwssystems.HABridge.util.ParseRoute; import static spark.Spark.get; @@ -98,7 +99,12 @@ public void setupServer() { httpLocationAddr = theSettings.getUpnpConfigAddress(); hueTemplate = hueTemplate_pre + hueTemplate_mid_orig + hueTemplate_post; } else { - httpLocationAddr = getOutboundAddress(request.ip(), request.port()).getHostAddress(); + + if(theSettings.isUseupnpiface()) { + httpLocationAddr = theSettings.getUpnpConfigAddress(); + } else { + httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress(); + } hueTemplate = hueTemplate_pre + hueTemplate_post; } @@ -136,28 +142,4 @@ public void setupServer() { return ""; } ); } - // added by https://github.com/pvint - // Ruthlessly stolen from https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface - // Try to get a source IP that makes sense for the requester to contact for use in the LOCATION header in replies - private InetAddress getOutboundAddress(String remoteAddress, int remotePort) { - InetAddress localAddress = null; - try { - DatagramSocket sock = new DatagramSocket(); - // connect is needed to bind the socket and retrieve the local address - // later (it would return 0.0.0.0 otherwise) - sock.connect(new InetSocketAddress(remoteAddress, remotePort)); - localAddress = sock.getLocalAddress(); - sock.disconnect(); - sock.close(); - sock = null; - } catch(Exception e) { - ParseRoute theRoute = ParseRoute.getInstance(); - try { - localAddress = InetAddress.getByName(theRoute.getLocalIPAddress()); - } catch(Exception e1) {} - log.warn("Error <" + e.getMessage() + "> on determining interface to reply for <" + remoteAddress + ">. Using default route IP Address of " + localAddress.getHostAddress()); - } - log.debug("getOutbountAddress returning IP Address of " + localAddress.getHostAddress()); - return localAddress; - } } diff --git a/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java b/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java new file mode 100644 index 00000000..6eb4aa20 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java @@ -0,0 +1,56 @@ +package com.bwssystems.HABridge.util; + +import java.net.*; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AddressUtil { + final static Logger log = LoggerFactory.getLogger(AddressUtil.class); + + // added by https://github.com/pvint + // Ruthlessly stolen from + // https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface + // Try to get a source IP that makes sense for the requester to contact for use + // in the LOCATION header in replies + public static InetAddress getOutboundAddress(String remoteAddress, int remotePort) { + InetAddress localAddress = null; + try { + DatagramSocket sock = new DatagramSocket(); + // connect is needed to bind the socket and retrieve the local address + // later (it would return 0.0.0.0 otherwise) + sock.connect(new InetSocketAddress(remoteAddress, remotePort)); + localAddress = sock.getLocalAddress(); + sock.disconnect(); + sock.close(); + sock = null; + } catch (Exception e) { + ParseRoute theRoute = ParseRoute.getInstance(); + try { + localAddress = InetAddress.getByName(theRoute.getLocalIPAddress()); + } catch (Exception e1) { + } + log.warn("Error <" + e.getMessage() + "> on determining interface to reply for <" + remoteAddress + + ">. Using default route IP Address of " + localAddress.getHostAddress()); + } + log.debug("getOutbountAddress returning IP Address of " + localAddress.getHostAddress()); + return localAddress; + } + + // added by https://github.com/pvint + // Ruthlessly stolen from + // https://stackoverflow.com/questions/22045165/java-datagrampacket-receive-how-to-determine-local-ip-interface + // Try to get a source IP that makes sense for the requestor to contact for use + // in the LOCATION header in replies + public static InetAddress getOutboundAddress(SocketAddress remoteAddress) throws SocketException { + DatagramSocket sock = new DatagramSocket(); + // connect is needed to bind the socket and retrieve the local address + // later (it would return 0.0.0.0 otherwise) + sock.connect(remoteAddress); + final InetAddress localAddress = sock.getLocalAddress(); + sock.disconnect(); + sock.close(); + sock = null; + return localAddress; + } +} \ No newline at end of file From bddc7c1c3194d3e3c075b68e8c9ca0600e05fcba Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Tue, 24 Sep 2019 16:07:36 -0500 Subject: [PATCH 02/19] Updated handling for use upnp iface and start color upgrades --- pom.xml | 6 +- .../bwssystems/HABridge/hue/ColorDecode.java | 186 ++++++++++----- .../HABridge/upnp/UpnpListener.java | 218 ++++++++---------- .../HABridge/upnp/UpnpSettingsResource.java | 5 - .../bwssystems/HABridge/util/AddressUtil.java | 18 +- 5 files changed, 226 insertions(+), 207 deletions(-) diff --git a/pom.xml b/pom.xml index 23e5c631..87105157 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.3.0 + 5.3.0a jar HA Bridge @@ -111,7 +111,7 @@ org.eclipse.paho org.eclipse.paho.client.mqttv3 - 1.2.0 + 1.2.1 junit @@ -196,7 +196,7 @@ maven-surefire-plugin 3.0.0-M3 - true + false diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java index 4fee0102..4cca2262 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -21,6 +21,11 @@ public class ColorDecode { private static final String COLOR_BX = "${color.bx}"; private static final String COLOR_RGBX = "${color.rgbx}"; private static final String COLOR_HSL = "${color.hsl}"; + private static final String COLOR_H = "${color.h}"; + private static final String COLOR_S = "${color.s}"; + private static final String COLOR_L = "${color.l}"; + private static final String COLOR_XY = "${color.xy}"; + private static final String COLOR_BRI = "${colorbri}"; private static final Pattern COLOR_MILIGHT = Pattern.compile("\\$\\{color.milight\\:([01234])\\}"); public static List convertHSLtoRGB(HueSatBri hsl) { @@ -35,46 +40,43 @@ public static List convertHSLtoRGB(HueSatBri hsl) { double g = 0.0; double b = 0.0; - if(hsl.getBri() > 0) + if (hsl.getBri() > 0) decimalBrightness = (float) (hsl.getBri() / 255.0); - if(hsl.getHue() > 0) { - h = ((float)hsl.getHue() / (float)65535.0); - h2 = h + (float)0.5; - if(h2 > 1.0) { - h2 = h2 - (float)1.0; + if (hsl.getHue() > 0) { + h = ((float) hsl.getHue() / (float) 65535.0); + h2 = h + (float) 0.5; + if (h2 > 1.0) { + h2 = h2 - (float) 1.0; } } - if(hsl.getSat() > 0) { - s = (float)(hsl.getSat() / 254.0); + if (hsl.getSat() > 0) { + s = (float) (hsl.getSat() / 254.0); } - if (s == 0) - { - r = decimalBrightness * (float)255; - g = decimalBrightness * (float)255; - b = decimalBrightness * (float)255; - } - else - { - if (decimalBrightness < 0.5) - { - var_2 = decimalBrightness * (1 + s); - } - else - { - var_2 = (decimalBrightness + s) - (s * decimalBrightness); - }; - - var_1 = 2 * decimalBrightness - var_2; - float onethird = (float)0.33333; - float h2Plus = (h2 + onethird); - float h2Minus = (h2 - onethird); - log.debug("calculate HSL vars - var1: " + var_1 + ", var_2: " + var_2 + ", h2: " + h2 + ", h2 + 1/3: " + h2Plus + ", h2 - 1/3: " + h2Minus); - r = 255 * hue_2_rgb(var_1, var_2, h2Plus); - g = 255 * hue_2_rgb(var_1, var_2, h2); - b = 255 * hue_2_rgb(var_1, var_2, h2Minus); - }; + if (s == 0) { + r = decimalBrightness * (float) 255; + g = decimalBrightness * (float) 255; + b = decimalBrightness * (float) 255; + } else { + if (decimalBrightness < 0.5) { + var_2 = decimalBrightness * (1 + s); + } else { + var_2 = (decimalBrightness + s) - (s * decimalBrightness); + } + ; + + var_1 = 2 * decimalBrightness - var_2; + float onethird = (float) 0.33333; + float h2Plus = (h2 + onethird); + float h2Minus = (h2 - onethird); + log.debug("calculate HSL vars - var1: " + var_1 + ", var_2: " + var_2 + ", h2: " + h2 + ", h2 + 1/3: " + + h2Plus + ", h2 - 1/3: " + h2Minus); + r = 255 * hue_2_rgb(var_1, var_2, h2Plus); + g = 255 * hue_2_rgb(var_1, var_2, h2); + b = 255 * hue_2_rgb(var_1, var_2, h2Minus); + } + ; rgb = new ArrayList(); rgb.add((int) Math.round(r)); @@ -88,30 +90,30 @@ public static List convertHSLtoRGB(HueSatBri hsl) { public static float hue_2_rgb(float v1, float v2, float vh) { log.debug("hue_2_rgb vh: " + vh); - if (vh < 0.0) - { - vh = vh + (float)1; - }; - - if (vh > 1.0) - { - vh = vh - (float)1; - }; - - if (((float)6.0 * vh) < 1.0) - { - return (v1 + (v2 - v1) * (float)6.0 * vh); - }; - - if (((float)2.0 * vh) < 1.0) - { - return (v2); - }; - - if ((3.0 * vh) < 2.0) - { - return (v1 + (v2 - v1) * (((float)2.0 / (float)3.0 - vh) * (float)6.0)); - }; + if (vh < 0.0) { + vh = vh + (float) 1; + } + ; + + if (vh > 1.0) { + vh = vh - (float) 1; + } + ; + + if (((float) 6.0 * vh) < 1.0) { + return (v1 + (v2 - v1) * (float) 6.0 * vh); + } + ; + + if (((float) 2.0 * vh) < 1.0) { + return (v2); + } + ; + + if ((3.0 * vh) < 2.0) { + return (v1 + (v2 - v1) * (((float) 2.0 / (float) 3.0 - vh) * (float) 6.0)); + } + ; return (v1); } @@ -297,13 +299,69 @@ public static String replaceColorData(String request, ColorData colorData, int s notDone = true; } + if (request.contains(COLOR_XY)) { + if (colorMode == ColorData.ColorMode.XY) { + List xyData = (List) colorData.getData(); + request = request.replace(COLOR_XY, String.format("%f,%f", xyData.get(0), xyData.get(1))); + } else { + List xyData = (List) colorData.getData(); + request = request.replace(COLOR_XY, String.format("%f,%f", xyData.get(0), xyData.get(1))); + } + notDone = true; + } + + if (request.contains(COLOR_H)) { + if (colorMode == ColorData.ColorMode.HS) { + HueSatBri hslData = (HueSatBri) colorData.getData(); + request = request.replace(COLOR_H, String.format("%d", hslData.getHue())); + } else { + float[] hsb = new float[3]; + Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); + float hue = hsb[0] * (float) 360.0; + request = request.replace(COLOR_H, String.format("%f", hue)); + } + notDone = true; + } + + if (request.contains(COLOR_S)) { + if (colorMode == ColorData.ColorMode.HS) { + HueSatBri hslData = (HueSatBri) colorData.getData(); + request = request.replace(COLOR_S, String.format("%d", hslData.getSat())); + } else { + float[] hsb = new float[3]; + Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); + float sat = hsb[1] * (float) 100.0; + request = request.replace(COLOR_S, String.format("%f", sat)); + } + notDone = true; + } + + if (request.contains(COLOR_L)) { + if (colorMode == ColorData.ColorMode.HS) { + HueSatBri hslData = (HueSatBri) colorData.getData(); + request = request.replace(COLOR_L, String.format("%d", hslData.getBri())); + } else { + float[] hsb = new float[3]; + Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); + float bright = hsb[2] * (float) 100.0; + request = request.replace(COLOR_L, String.format("%f", bright)); + } + notDone = true; + } + if (request.contains(COLOR_HSL)) { - float[] hsb = new float[3]; - Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); - float hue = hsb[0] * (float) 360.0; - float sat = hsb[1] * (float) 100.0; - float bright = hsb[2] * (float) 100.0; - request = request.replace(COLOR_HSL, String.format("%f,%f,%f", hue, sat, bright)); + if (colorMode == ColorData.ColorMode.HS) { + HueSatBri hslData = (HueSatBri) colorData.getData(); + request = request.replace(COLOR_HSL, + String.format("%d,%d,%d", hslData.getHue(), hslData.getSat(), hslData.getBri())); + } else { + float[] hsb = new float[3]; + Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); + float hue = hsb[0] * (float) 360.0; + float sat = hsb[1] * (float) 100.0; + float bright = hsb[2] * (float) 100.0; + request = request.replace(COLOR_HSL, String.format("%f,%f,%f", hue, sat, bright)); + } notDone = true; } diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java index 57710b96..1fd47220 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java @@ -38,63 +38,33 @@ public class UpnpListener { private String httpType; private HuePublicConfig aHueConfig; private Integer theUpnpSendDelay; - private String responseTemplate1 = "HTTP/1.1 200 OK\r\n" + - "HOST: %s:%s\r\n" + - "CACHE-CONTROL: max-age=100\r\n" + - "EXT:\r\n" + - "LOCATION: %s://%s:%s/description.xml\r\n" + - "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + HueConstants.API_VERSION + "\r\n" + - "hue-bridgeid: %s\r\n" + - "ST: upnp:rootdevice\r\n" + - "USN: uuid:" + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; - private String responseTemplate2 = "HTTP/1.1 200 OK\r\n" + - "HOST: %s:%s\r\n" + - "CACHE-CONTROL: max-age=100\r\n" + - "EXT:\r\n" + - "LOCATION: %s://%s:%s/description.xml\r\n" + - "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + HueConstants.API_VERSION + "\r\n" + - "hue-bridgeid: %s\r\n" + - "ST: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n" + - "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; - private String responseTemplate3 = "HTTP/1.1 200 OK\r\n" + - "HOST: %s:%s\r\n" + - "CACHE-CONTROL: max-age=100\r\n" + - "EXT:\r\n" + - "LOCATION: %s://%s:%s/description.xml\r\n" + - "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + HueConstants.API_VERSION + "\r\n" + - "hue-bridgeid: %s\r\n" + - "ST: urn:schemas-upnp-org:device:basic:1\r\n" + - "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; - - private String notifyTemplate1 = "NOTIFY * HTTP/1.1\r\n" + - "HOST: %s:%s\r\n" + - "CACHE-CONTROL: max-age=100\r\n" + - "LOCATION: %s://%s:%s/description.xml\r\n" + - "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + HueConstants.API_VERSION + "\r\n" + - "NTS: ssdp:alive\r\n" + - "hue-bridgeid: %s\r\n" + - "NT: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n" + - "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; - - private String notifyTemplate2 = "NOTIFY * HTTP/1.1\r\n" + - "HOST: %s:%s\r\n" + - "CACHE-CONTROL: max-age=100\r\n" + - "LOCATION: %s://%s:%s/description.xml\r\n" + - "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + HueConstants.API_VERSION + "\r\n" + - "NTS: ssdp:alive\r\n" + - "hue-bridgeid: %s\r\n" + - "NT: upnp:rootdevice\r\n" + - "USN: uuid:" + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; - - private String notifyTemplate3 = "NOTIFY * HTTP/1.1\r\n" + - "HOST: %s:%s\r\n" + - "CACHE-CONTROL: max-age=100\r\n" + - "LOCATION: %s://%s:%s/description.xml\r\n" + - "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + HueConstants.API_VERSION + "\r\n" + - "NTS: ssdp:alive\r\n" + - "hue-bridgeid: %s\r\n" + - "NT: urn:schemas-upnp-org:device:basic:1\r\n" + - "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; + private String responseTemplate1 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" + + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:" + + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; + private String responseTemplate2 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" + + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: uuid:" + HueConstants.UUID_PREFIX + + "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; + private String responseTemplate3 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" + + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n" + + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; + + private String notifyTemplate1 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" + + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + "NT: uuid:" + + HueConstants.UUID_PREFIX + "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; + + private String notifyTemplate2 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" + + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + + "NT: upnp:rootdevice\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; + + private String notifyTemplate3 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" + + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + + "NT: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; public UpnpListener(BridgeSettings theSettings, BridgeControlDescriptor theControl, UDPDatagramSender aUdpDatagramSender) throws IOException { @@ -113,7 +83,7 @@ public UpnpListener(BridgeSettings theSettings, BridgeControlDescriptor theContr theSettings.getBridgeSettingsDescriptor().getHubmac()); bridgeId = aHueConfig.getBridgeid(); bridgeSNUUID = aHueConfig.getSNUUIDFromMac(); - if(theSettings.getBridgeSecurity().isUseHttps()) { + if (theSettings.getBridgeSecurity().isUseHttps()) { httpType = "https"; } else { httpType = "http"; @@ -204,7 +174,8 @@ public boolean startListening() { final HashMap values = new HashMap(); values.put("modelid", HueConstants.MODEL_ID); values.put("bridgeid", bridgeId); - ServiceInfo serviceInfo = ServiceInfo.create("_hue._tcp.local.", "Philips Hue - " + bridgeId.substring(bridgeId.length() - 6), httpServerPort, 0, 0, values); + ServiceInfo serviceInfo = ServiceInfo.create("_hue._tcp.local.", + "Philips Hue - " + bridgeId.substring(bridgeId.length() - 6), httpServerPort, 0, 0, values); jmdns.registerService(serviceInfo); } catch (IOException e) { @@ -225,8 +196,8 @@ public boolean startListening() { } catch (SocketException e1) { log.warn("Could not sent soTimeout on multi-cast socket"); } -// Instant current, previous; -// previous = Instant.now(); + // Instant current, previous; + // previous = Instant.now(); while (loopControl) { // trigger shutdown here byte[] buf = new byte[1024]; DatagramPacket packet = new DatagramPacket(buf, buf.length); @@ -235,26 +206,23 @@ public boolean startListening() { if (isSSDPDiscovery(packet)) { try { sendUpnpResponse(packet); - } catch (IOException e) { + } catch (Exception e) { log.warn("UpnpListener encountered an error sending upnp response packet. IP: " + packet.getAddress().getHostAddress() + " with message: " + e.getMessage()); log.debug("UpnpListener send upnp exception: ", e); } } -/* - current = Instant.now(); - if (ChronoUnit.MILLIS.between(previous, current) > Configuration.UPNP_NOTIFY_TIMEOUT) { - try { - sendUpnpNotify(socketAddress.getAddress()); - } catch (IOException e) { - log.warn("UpnpListener encountered an error sending upnp notify packets. IP: " - + packet.getAddress().getHostAddress() + " with message: " + e.getMessage()); - log.debug("UpnpListener send upnp notify exception: ", e); - } - previous = Instant.now(); - - } -*/ + /* + * current = Instant.now(); if (ChronoUnit.MILLIS.between(previous, current) > + * Configuration.UPNP_NOTIFY_TIMEOUT) { try { + * sendUpnpNotify(socketAddress.getAddress()); } catch (IOException e) { log. + * warn("UpnpListener encountered an error sending upnp notify packets. IP: " + + * packet.getAddress().getHostAddress() + " with message: " + e.getMessage()); + * log.debug("UpnpListener send upnp notify exception: ", e); } previous = + * Instant.now(); + * + * } + */ } catch (SocketTimeoutException e) { try { sendUpnpNotify(socketAddress.getAddress()); @@ -277,7 +245,7 @@ public boolean startListening() { } } upnpMulticastSocket.close(); - if(jmdns != null) { + if (jmdns != null) { // Unregister all services jmdns.unregisterAllServices(); } @@ -300,9 +268,8 @@ protected boolean isSSDPDiscovery(DatagramPacket packet) { String packetString = new String(packet.getData(), 0, packet.getLength()); if (packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1") && packetString.contains("\"ssdp:discover\"")) { - if ((packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") || - packetString.contains("ST: upnp:rootdevice") || - packetString.contains("ST: ssdp:all"))) { + if ((packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") + || packetString.contains("ST: upnp:rootdevice") || packetString.contains("ST: ssdp:all"))) { if (traceupnp) { log.info("Traceupnp: SSDP M-SEARCH packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort()); @@ -328,16 +295,16 @@ protected boolean isSSDPDiscovery(DatagramPacket packet) { } protected void sendUpnpResponse(DatagramPacket aPacket) throws IOException { - SocketAddress requesterAddress = aPacket.getSocketAddress(); + // SocketAddress requesterAddress = aPacket.getSocketAddress(); InetAddress requester = aPacket.getAddress(); int sourcePort = aPacket.getPort(); String discoveryResponse = null; String httpLocationAddress = null; - if(useUpnpIface) { + if (useUpnpIface) { httpLocationAddress = upnpConfigIP; } else { // refactored suggestion by https://github.com/pvint - httpLocationAddress = AddressUtil.getOutboundAddress(requesterAddress).getHostAddress(); + httpLocationAddress = AddressUtil.getOutboundAddress(requester.getHostAddress(), sourcePort).getHostAddress(); } try { @@ -346,51 +313,53 @@ protected void sendUpnpResponse(DatagramPacket aPacket) throws IOException { // noop } - discoveryResponse = String.format(responseTemplate1, Configuration.UPNP_MULTICAST_ADDRESS, - Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID); - if (traceupnp) { - log.info("Traceupnp: send upnp discovery template 1 with response address: " + httpLocationAddress + ":" - + httpServerPort + " to address: " + requester + ":" + sourcePort); - } - log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort - + " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>"); - sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); + discoveryResponse = String.format(responseTemplate1, Configuration.UPNP_MULTICAST_ADDRESS, + Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, + bridgeSNUUID); + if (traceupnp) { + log.info("Traceupnp: send upnp discovery template 1 with response address: " + httpLocationAddress + ":" + + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); + } + log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort + + " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>"); + sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); - try { - Thread.sleep(theUpnpSendDelay); - } catch (InterruptedException e) { - // noop - } - discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS, - Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID, - bridgeSNUUID); - if (traceupnp) { - log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":" - + httpServerPort + " to address: " + requester + ":" + sourcePort); - } - log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort - + " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>"); - sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); + try { + Thread.sleep(theUpnpSendDelay); + } catch (InterruptedException e) { + // noop + } + discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS, + Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, + bridgeSNUUID, bridgeSNUUID); + if (traceupnp) { + log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":" + + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); + } + log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort + + " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>"); + sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); - try { - Thread.sleep(theUpnpSendDelay); - } catch (InterruptedException e) { - // noop - } - discoveryResponse = String.format(responseTemplate3,Configuration.UPNP_MULTICAST_ADDRESS, - Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID); - if (traceupnp) { - log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":" - + httpServerPort + " to address: " + requester + ":" + sourcePort); - } - log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort - + " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>"); - sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); + try { + Thread.sleep(theUpnpSendDelay); + } catch (InterruptedException e) { + // noop + } + discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS, + Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, + bridgeSNUUID); + if (traceupnp) { + log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":" + + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); + } + log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort + + " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>"); + sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); } private void sendUDPResponse(byte[] udpMessage, InetAddress requester, int sourcePort) throws IOException { log.debug("Sending response string: <<<" + new String(udpMessage) + ">>>"); - if(upnpOriginal) { + if (upnpOriginal) { theUDPDatagramSender.sendUDPResponse(udpMessage, requester, sourcePort); } else { if (upnpMulticastSocket == null) @@ -409,7 +378,8 @@ protected void sendUpnpNotify(InetAddress aSocketAddress) throws IOException { } notifyData = String.format(notifyTemplate1, Configuration.UPNP_MULTICAST_ADDRESS, - Configuration.UPNP_DISCOVERY_PORT, httpType, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID, bridgeSNUUID); + Configuration.UPNP_DISCOVERY_PORT, httpType, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID, + bridgeSNUUID); if (traceupnp) { log.info("Traceupnp: sendUpnpNotify notifyTemplate1"); } @@ -429,7 +399,7 @@ protected void sendUpnpNotify(InetAddress aSocketAddress) throws IOException { } log.debug("sendUpnpNotify notifyTemplate2 is <<<{}>>>", notifyData); sendUDPResponse(notifyData.getBytes(), aSocketAddress, Configuration.UPNP_DISCOVERY_PORT); - + try { Thread.sleep(theUpnpSendDelay); } catch (InterruptedException e) { diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java index 33d75349..30a1fa81 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java @@ -8,14 +8,9 @@ import com.bwssystems.HABridge.api.hue.HueConstants; import com.bwssystems.HABridge.api.hue.HuePublicConfig; import com.bwssystems.HABridge.util.AddressUtil; -import com.bwssystems.HABridge.util.ParseRoute; import static spark.Spark.get; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.InetSocketAddress; - /** * */ diff --git a/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java b/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java index 6eb4aa20..77dd9607 100644 --- a/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java +++ b/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java @@ -16,24 +16,20 @@ public class AddressUtil { public static InetAddress getOutboundAddress(String remoteAddress, int remotePort) { InetAddress localAddress = null; try { - DatagramSocket sock = new DatagramSocket(); - // connect is needed to bind the socket and retrieve the local address - // later (it would return 0.0.0.0 otherwise) - sock.connect(new InetSocketAddress(remoteAddress, remotePort)); - localAddress = sock.getLocalAddress(); - sock.disconnect(); - sock.close(); - sock = null; + getOutboundAddress(new InetSocketAddress(remoteAddress, remotePort)); } catch (Exception e) { ParseRoute theRoute = ParseRoute.getInstance(); try { localAddress = InetAddress.getByName(theRoute.getLocalIPAddress()); + log.warn("Error <" + e.getMessage() + "> on determining interface to reply for <" + remoteAddress + + ">. Using default route IP Address of " + localAddress.getHostAddress()); } catch (Exception e1) { + log.error("Cannot find address for parsed local ip address: " + e.getMessage()); } - log.warn("Error <" + e.getMessage() + "> on determining interface to reply for <" + remoteAddress - + ">. Using default route IP Address of " + localAddress.getHostAddress()); } - log.debug("getOutbountAddress returning IP Address of " + localAddress.getHostAddress()); + + if(localAddress != null) + log.debug("getOutbountAddress returning IP Address of " + localAddress.getHostAddress()); return localAddress; } From 79ce23b80a68a607413e034f9ebaf33c8890a292 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Wed, 25 Sep 2019 16:38:30 -0500 Subject: [PATCH 03/19] Continue color validation --- .../HABridge/hue/ColorConverter.java | 942 ++++++++++++++++++ .../bwssystems/HABridge/hue/ColorDecode.java | 52 +- .../bwssystems/HABridge/hue/XYColorSpace.java | 26 + .../color/test/ConvertCIEColorTestCase.java | 25 + 4 files changed, 1019 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java create mode 100644 src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java new file mode 100644 index 00000000..4d7c3c68 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java @@ -0,0 +1,942 @@ +package com.bwssystems.HABridge.hue; + +/** + * Convert between different color spaces supported. + * RGB -> CMYK -> RGB + * RGB -> YIQ -> RGB + * RGB -> YCbCr -> RGB + * RGB -> YUV -> RGB + * RGB -> RGChromaticity + * RGB -> HSV -> RGB + * RGB -> YCC -> RGB + * RGB -> YCoCg -> RGB + * RGB -> XYZ -> RGB + * RGB -> HunterLAB -> RGB + * RGB -> HLS -> RGB + * RGB -> CIE-LAB -> RGB + * XYZ -> HunterLAB -> XYZ + * XYZ -> CIE-LAB -> XYZ + * @author Diego Catalano + */ +public class ColorConverter { + + /** + * Don't let anyone instantiate this class. + */ + private ColorConverter() {} + + public static enum YCbCrColorSpace {ITU_BT_601,ITU_BT_709_HDTV}; + + // XYZ (Tristimulus) Reference values of a perfect reflecting diffuser + + //2o Observer (CIE 1931) + // X2, Y2, Z2 + public static float[] CIE2_A = {109.850f, 100f, 35.585f}; //Incandescent + public static float[] CIE2_C = {98.074f, 100f, 118.232f}; + public static float[] CIE2_D50 = {96.422f, 100f, 82.521f}; + public static float[] CIE2_D55 = {95.682f, 100f, 92.149f}; + public static float[] CIE2_D65 = {95.047f, 100f, 108.883f}; //Daylight + public static float[] CIE2_D75 = {94.972f, 100f, 122.638f}; + public static float[] CIE2_F2 = {99.187f, 100f, 67.395f}; //Fluorescent + public static float[] CIE2_F7 = {95.044f, 100f, 108.755f}; + public static float[] CIE2_F11 = {100.966f, 100f, 64.370f}; + + //10o Observer (CIE 1964) + // X2, Y2, Z2 + public static float[] CIE10_A = {111.144f, 100f, 35.200f}; //Incandescent + public static float[] CIE10_C = {97.285f, 100f, 116.145f}; + public static float[] CIE10_D50 = {96.720f, 100f, 81.427f}; + public static float[] CIE10_D55 = {95.799f, 100f, 90.926f}; + public static float[] CIE10_D65 = {94.811f, 100f, 107.304f}; //Daylight + public static float[] CIE10_D75 = {94.416f, 100f, 120.641f}; + public static float[] CIE10_F2 = {103.280f, 100f, 69.026f}; //Fluorescent + public static float[] CIE10_F7 = {95.792f, 100f, 107.687f}; + public static float[] CIE10_F11 = {103.866f, 100f, 65.627f}; + + /** + * RFB -> CMYK + * @param red Values in the range [0..255]. + * @param green Values in the range [0..255]. + * @param blue Values in the range [0..255]. + * @return CMYK color space. Normalized. + */ + public static float[] RGBtoCMYK(int red, int green, int blue){ + float[] cmyk = new float[4]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float k = 1.0f - Math.max(r, Math.max(g, b)); + float c = (1f-r-k) / (1f-k); + float m = (1f-g-k) / (1f-k); + float y = (1f-b-k) / (1f-k); + + cmyk[0] = c; + cmyk[1] = m; + cmyk[2] = y; + cmyk[3] = k; + + return cmyk; + } + + /** + * CMYK -> RGB + * @param c Cyan. + * @param m Magenta. + * @param y Yellow. + * @param k Black. + * @return RGB color space. + */ + public static int[] CMYKtoRGB(float c, float m, float y, float k){ + int[] rgb = new int[3]; + + rgb[0] = (int)(255 * (1-c) * (1-k)); + rgb[1] = (int)(255 * (1-m) * (1-k)); + rgb[2] = (int)(255 * (1-y) * (1-k)); + + return rgb; + } + + /** + * RGB -> YUV. + * Y in the range [0..1]. + * U in the range [-0.5..0.5]. + * V in the range [-0.5..0.5]. + * @param red Values in the range [0..255]. + * @param green Values in the range [0..255]. + * @param blue Values in the range [0..255]. + * @return YUV color space. + */ + public static float[] RGBtoYUV(int red, int green, int blue){ + + float r = (float)red / 255; + float g = (float)green / 255; + float b = (float)blue / 255; + + float[] yuv = new float[3]; + float y,u,v; + + y = (float)(0.299 * r + 0.587 * g + 0.114 * b); + u = (float)(-0.14713 * r - 0.28886 * g + 0.436 * b); + v = (float)(0.615 * r - 0.51499 * g - 0.10001 * b); + + yuv[0] = y; + yuv[1] = u; + yuv[2] = v; + + return yuv; + } + + /** + * YUV -> RGB. + * @param y Luma. In the range [0..1]. + * @param u Chrominance. In the range [-0.5..0.5]. + * @param v Chrominance. In the range [-0.5..0.5]. + * @return RGB color space. + */ + public static int[] YUVtoRGB(float y, float u, float v){ + int[] rgb = new int[3]; + float r,g,b; + + r = (float)((y + 0.000 * u + 1.140 * v) * 255); + g = (float)((y - 0.396 * u - 0.581 * v) * 255); + b = (float)((y + 2.029 * u + 0.000 * v) * 255); + + rgb[0] = (int)r; + rgb[1] = (int)g; + rgb[2] = (int)b; + + return rgb; + } + + /** + * RGB -> YIQ. + * @param red Values in the range [0..255]. + * @param green Values in the range [0..255]. + * @param blue Values in the range [0..255]. + * @return YIQ color space. + */ + public static float[] RGBtoYIQ(int red, int green, int blue){ + float[] yiq = new float[3]; + float y,i,q; + + float r = (float)red / 255; + float g = (float)green / 255; + float b = (float)blue / 255; + + y = (float)(0.299 * r + 0.587 * g + 0.114 * b); + i = (float)(0.596 * r - 0.275 * g - 0.322 * b); + q = (float)(0.212 * r - 0.523 * g + 0.311 * b); + + yiq[0] = y; + yiq[1] = i; + yiq[2] = q; + + return yiq; + } + + /** + * YIQ -> RGB. + * @param y Luma. Values in the range [0..1]. + * @param i In-phase. Values in the range [-0.5..0.5]. + * @param q Quadrature. Values in the range [-0.5..0.5]. + * @return RGB color space. + */ + public static int[] YIQtoRGB(double y, double i, double q){ + int[] rgb = new int[3]; + int r,g,b; + + r = (int)((y + 0.956 * i + 0.621 * q) * 255); + g = (int)((y - 0.272 * i - 0.647 * q) * 255); + b = (int)((y - 1.105 * i + 1.702 * q) * 255); + + r = Math.max(0,Math.min(255,r)); + g = Math.max(0,Math.min(255,g)); + b = Math.max(0,Math.min(255,b)); + + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + + return rgb; + } + + public static float[] RGBtoYCbCr(int red, int green, int blue, YCbCrColorSpace colorSpace){ + + float r = (float)red / 255; + float g = (float)green / 255; + float b = (float)blue / 255; + + float[] YCbCr = new float[3]; + float y,cb,cr; + + if (colorSpace == YCbCrColorSpace.ITU_BT_601) { + y = (float)(0.299 * r + 0.587 * g + 0.114 * b); + cb = (float)(-0.169 * r - 0.331 * g + 0.500 * b); + cr = (float)(0.500 * r - 0.419 * g - 0.081 * b); + } + else{ + y = (float)(0.2215 * r + 0.7154 * g + 0.0721 * b); + cb = (float)(-0.1145 * r - 0.3855 * g + 0.5000 * b); + cr = (float)(0.5016 * r - 0.4556 * g - 0.0459 * b); + } + + YCbCr[0] = (float)y; + YCbCr[1] = (float)cb; + YCbCr[2] = (float)cr; + + return YCbCr; + } + + public static int[] YCbCrtoRGB(float y, float cb, float cr, YCbCrColorSpace colorSpace){ + int[] rgb = new int[3]; + float r,g,b; + + if (colorSpace == YCbCrColorSpace.ITU_BT_601) { + r = (float)(y + 0.000 * cb + 1.403 * cr) * 255; + g = (float)(y - 0.344 * cb - 0.714 * cr) * 255; + b = (float)(y + 1.773 * cb + 0.000 * cr) * 255; + } + else{ + r = (float)(y + 0.000 * cb + 1.5701 * cr) * 255; + g = (float)(y - 0.1870 * cb - 0.4664 * cr) * 255; + b = (float)(y + 1.8556 * cb + 0.000 * cr) * 255; + } + + rgb[0] = (int)r; + rgb[1] = (int)g; + rgb[2] = (int)b; + + return rgb; + } + + /** + * Rg-Chromaticity space is already known to remove ambiguities due to illumination or surface pose. + * @see Neural Information Processing - Chi Sing Leung. p. 668 + * @param red Red coefficient. + * @param green Green coefficient. + * @param blue Blue coefficient. + * @return Normalized RGChromaticity. Range[0..1]. + */ + public static double[] RGChromaticity(int red, int green, int blue){ + double[] color = new double[5]; + + double sum = red + green + blue; + + //red + color[0] = red / sum; + + //green + color[1] = green / sum; + + //blue + color[2] = 1 - color[0] - color[1]; + + double rS = color[0] - 0.333; + double gS = color[1] - 0.333; + + //saturation + color[3] = Math.sqrt(rS * rS + gS * gS); + + //hue + color[4] = Math.atan(rS / gS); + + return color; + } + + /** + * RGB -> HSV. + * Adds (hue + 360) % 360 for represent hue in the range [0..359]. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return HSV color space. + */ + public static float[] RGBtoHSV(int red, int green, int blue){ + float[] hsv = new float[3]; + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float max = Math.max(r, Math.max(g, b)); + float min = Math.min(r, Math.min(g, b)); + float delta = max - min; + + // Hue + if (max == min){ + hsv[0] = 0; + } + else if (max == r){ + hsv[0] = ((g - b) / delta) * 60f; + } + else if (max == g){ + hsv[0] = ((b - r) / delta + 2f) * 60f; + } + else if (max == b){ + hsv[0] = ((r - g) / delta + 4f) * 60f; + } + + // Saturation + if (delta == 0) + hsv[1] = 0; + else + hsv[1] = delta / max; + + //Value + hsv[2] = max; + + return hsv; + } + + /** + * HSV -> RGB. + * @param hue Hue. + * @param saturation Saturation. In the range[0..1]. + * @param value Value. In the range[0..1]. + * @return RGB color space. In the range[0..255]. + */ + public static int[] HSVtoRGB(float hue, float saturation, float value){ + int[] rgb = new int[3]; + + float hi = (float)Math.floor(hue / 60.0) % 6; + float f = (float)((hue / 60.0) - Math.floor(hue / 60.0)); + float p = (float)(value * (1.0 - saturation)); + float q = (float)(value * (1.0 - (f * saturation))); + float t = (float)(value * (1.0 - ((1.0 - f) * saturation))); + + if (hi == 0){ + rgb[0] = (int)(value * 255); + rgb[1] = (int)(t * 255); + rgb[2] = (int)(p * 255); + } + else if (hi == 1){ + rgb[0] = (int)(q * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(p * 255); + } + else if (hi == 2){ + rgb[0] = (int)(p * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(t * 255); + } + else if (hi == 3){ + rgb[0] = (int)(p * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(q * 255); + } + else if (hi == 4){ + rgb[0] = (int)(t * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(p * 255); + } + else if (hi == 5){ + rgb[0] = (int)(value * 255); + rgb[1] = (int)(p * 255); + rgb[2] = (int)(q * 255); + } + + return rgb; + } + + /** + * RGB -> YCC. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return YCC color space. In the range [0..1]. + */ + public static float[] RGBtoYCC(int red, int green, int blue){ + float[] ycc = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float y = 0.213f * r + 0.419f * g + 0.081f * b; + float c1 = -0.131f * r - 0.256f * g + 0.387f * b + 0.612f; + float c2 = 0.373f * r - 0.312f * r - 0.061f * b + 0.537f; + + ycc[0] = y; + ycc[1] = c1; + ycc[2] = c2; + + return ycc; + } + + /** + * YCC -> RGB. + * @param y Y coefficient. + * @param c1 C coefficient. + * @param c2 C coefficient. + * @return RGB color space. + */ + public static int[] YCCtoRGB(float y, float c1, float c2){ + int[] rgb = new int[3]; + + float r = 0.981f * y + 1.315f * (c2 - 0.537f); + float g = 0.981f * y - 0.311f * (c1 - 0.612f)- 0.669f * (c2 - 0.537f); + float b = 0.981f * y + 1.601f * (c1 - 0.612f); + + rgb[0] = (int)(r * 255f); + rgb[1] = (int)(g * 255f); + rgb[2] = (int)(b * 255f); + + return rgb; + } + + /** + * RGB -> YCoCg. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return YCoCg color space. + */ + public static float[] RGBtoYCoCg(int red, int green, int blue){ + float[] yCoCg = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float y = r / 4f + g / 2f + b / 4f; + float co = r / 2f - b / 2f; + float cg = -r / 4f + g / 2f - b / 4f; + + yCoCg[0] = y; + yCoCg[1] = co; + yCoCg[2] = cg; + + return yCoCg; + } + + /** + * YCoCg -> RGB. + * @param y Pseudo luminance, or intensity. + * @param co Orange chrominance. + * @param cg Green chrominance. + * @return RGB color space. + */ + public static int[] YCoCgtoRGB(float y, float co, float cg){ + int[] rgb = new int[3]; + + float r = y + co - cg; + float g = y + cg; + float b = y - co - cg; + + rgb[0] = (int)(r * 255f); + rgb[1] = (int)(g * 255f); + rgb[2] = (int)(b * 255f); + + return rgb; + } + + /** + * RGB -> XYZ + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return XYZ color space. + */ + public static float[] RGBtoXYZ(int red, int green, int blue){ + float[] xyz = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + //R + if ( r > 0.04045) + r = (float)Math.pow(( ( r + 0.055f ) / 1.055f ), 2.4f); + else + r /= 12.92f; + + //G + if ( g > 0.04045) + g = (float)Math.pow(( ( g + 0.055f ) / 1.055f ), 2.4f); + else + g /= 12.92f; + + //B + if ( b > 0.04045) + b = (float)Math.pow(( ( b + 0.055f ) / 1.055f ), 2.4f); + else + b /= 12.92f; + + r *= 100; + g *= 100; + b *= 100; + + float x = 0.412453f * r + 0.35758f * g + 0.180423f * b; + float y = 0.212671f * r + 0.71516f * g + 0.072169f * b; + float z = 0.019334f * r + 0.119193f * g + 0.950227f * b; + + xyz[0] = x; + xyz[1] = y; + xyz[2] = z; + + return xyz; + } + + /** + * XYZ -> RGB + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @return RGB color space. + */ + public static int[] XYZtoRGB(float x, float y, float z){ + int[] rgb = new int[3]; + + x /= 100; + y /= 100; + z /= 100; + + float r = 3.240479f * x - 1.53715f * y - 0.498535f * z; + float g = -0.969256f * x + 1.875991f * y + 0.041556f * z; + float b = 0.055648f * x - 0.204043f * y + 1.057311f * z; + + if ( r > 0.0031308 ) + r = 1.055f * ( (float)Math.pow(r, 0.4166f) ) - 0.055f; + else + r = 12.92f * r; + + if ( g > 0.0031308 ) + g = 1.055f * ( (float)Math.pow(g, 0.4166f) ) - 0.055f; + else + g = 12.92f * g; + + if ( b > 0.0031308 ) + b = 1.055f * ( (float)Math.pow(b, 0.4166f) ) - 0.055f; + else + b = 12.92f * b; + + rgb[0] = (int)(r * 255); + rgb[1] = (int)(g * 255); + rgb[2] = (int)(b * 255); + + return rgb; + } + + /** + * XYZ -> HunterLAB + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @return HunterLab coefficient. + */ + public static float[] XYZtoHunterLAB(float x, float y, float z){ + float[] hunter = new float[3]; + + + float sqrt = (float)Math.sqrt(y); + + float l = 10 * sqrt; + float a = 17.5f * (((1.02f * x) - y) / sqrt); + float b = 7f * ((y - (0.847f * z)) / sqrt); + + hunter[0] = l; + hunter[1] = a; + hunter[2] = b; + + return hunter; + } + + /** + * HunterLAB -> XYZ + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @return XYZ color space. + */ + public static float[] HunterLABtoXYZ(float l, float a, float b){ + float[] xyz = new float[3]; + + + float tempY = l / 10f; + float tempX = a / 17.5f * l / 10f; + float tempZ = b / 7f * l / 10f; + + float y = tempY * tempY; + float x = (tempX + y) / 1.02f; + float z = -(tempZ - y) / 0.847f; + + xyz[0] = x; + xyz[1] = y; + xyz[2] = z; + + return xyz; + } + + /** + * RGB -> HunterLAB. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return HunterLAB color space. + */ + public static float[] RGBtoHunterLAB(int red, int green, int blue){ + float[] xyz = RGBtoXYZ(red, green, blue); + return XYZtoHunterLAB(xyz[0], xyz[1], xyz[2]); + } + + /** + * HunterLAB -> RGB. + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @return RGB color space. + */ + public static int[] HunterLABtoRGB(float l, float a, float b){ + float[] xyz = HunterLABtoXYZ(l, a, b); + return XYZtoRGB(xyz[0], xyz[1], xyz[2]); + } + + /** + * RGB -> HLS. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return HLS color space. + */ + public static float[] RGBtoHLS(int red, int green, int blue){ + float[] hsl = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float max = Math.max(r,Math.max(r,b)); + float min = Math.min(r,Math.min(r,b)); + float delta = max - min; + + //HSK + float h = 0; + float s = 0; + float l = (max + min) / 2; + + if ( delta == 0 ){ + // gray color + h = 0; + s = 0.0f; + } + else + { + // get saturation value + s = ( l <= 0.5 ) ? ( delta / ( max + min ) ) : ( delta / ( 2 - max - min ) ); + + // get hue value + float hue; + + if ( r == max ) + { + hue = ( ( g - b ) / 6 ) / delta; + } + else if ( g == max ) + { + hue = ( 1.0f / 3 ) + ( ( b - r ) / 6 ) / delta; + } + else + { + hue = ( 2.0f / 3 ) + ( ( r - g ) / 6 ) / delta; + } + + // correct hue if needed + if ( hue < 0 ) + hue += 1; + if ( hue > 1 ) + hue -= 1; + + h = (int) ( hue * 360 ); + } + + hsl[0] = h; + hsl[1] = s; + hsl[2] = l; + + return hsl; + } + + /** + * HLS -> RGB. + * @param hue Hue. + * @param saturation Saturation. + * @param luminance Luminance. + * @return RGB color space. + */ + public static int[] HSLtoRGB(float hue, float saturation, float luminance){ + int[] rgb = new int[3]; + float r = 0, g = 0, b = 0; + + if ( saturation == 0 ) + { + // gray values + r = g = b = (int) ( luminance * 255 ); + } + else + { + float v1, v2; + float h = (float) hue / 360; + + v2 = ( luminance < 0.5 ) ? + ( luminance * ( 1 + saturation ) ) : + ( ( luminance + saturation ) - ( luminance * saturation ) ); + v1 = 2 * luminance - v2; + + r = (int) ( 255 * Hue_2_RGB( v1, v2, h + ( 1.0f / 3 ) ) ); + g = (int) ( 255 * Hue_2_RGB( v1, v2, h ) ); + b = (int) ( 255 * Hue_2_RGB( v1, v2, h - ( 1.0f / 3 ) ) ); + } + + rgb[0] = (int)r; + rgb[1] = (int)g; + rgb[2] = (int)b; + + return rgb; + } + + private static float Hue_2_RGB( float v1, float v2, float vH ){ + if ( vH < 0 ) + vH += 1; + if ( vH > 1 ) + vH -= 1; + if ( ( 6 * vH ) < 1 ) + return ( v1 + ( v2 - v1 ) * 6 * vH ); + if ( ( 2 * vH ) < 1 ) + return v2; + if ( ( 3 * vH ) < 2 ) + return ( v1 + ( v2 - v1 ) * ( ( 2.0f / 3 ) - vH ) * 6 ); + return v1; + } + + /** + * RGB -> CIE-LAB. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @param tristimulus XYZ Tristimulus. + * @return CIE-LAB color space. + */ + public static float[] RGBtoLAB(int red, int green, int blue, float[] tristimulus){ + float[] xyz = RGBtoXYZ(red, green, blue); + float[] lab = XYZtoLAB(xyz[0], xyz[1], xyz[2], tristimulus); + + return lab; + } + + /** + * CIE-LAB -> RGB. + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @param tristimulus XYZ Tristimulus. + * @return RGB color space. + */ + public static int[] LABtoRGB(float l, float a, float b, float[] tristimulus){ + float[] xyz = LABtoXYZ(l, a, b, tristimulus); + return XYZtoRGB(xyz[0], xyz[1], xyz[2]); + } + + /** + * XYZ -> CIE-LAB. + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @param tristimulus XYZ Tristimulus. + * @return CIE-LAB color space. + */ + public static float[] XYZtoLAB(float x, float y, float z, float[] tristimulus){ + float[] lab = new float[3]; + + x /= tristimulus[0]; + y /= tristimulus[1]; + z /= tristimulus[2]; + + if (x > 0.008856) + x = (float)Math.pow(x,0.33f); + else + x = (7.787f * x) + ( 0.1379310344827586f ); + + if (y > 0.008856) + y = (float)Math.pow(y,0.33f); + else + y = (7.787f * y) + ( 0.1379310344827586f ); + + if (z > 0.008856) + z = (float)Math.pow(z,0.33f); + else + z = (7.787f * z) + ( 0.1379310344827586f ); + + lab[0] = ( 116 * y ) - 16; + lab[1] = 500 * ( x - y ); + lab[2] = 200 * ( y - z ); + + return lab; + } + + /** + * CIE-LAB -> XYZ. + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @param tristimulus XYZ Tristimulus. + * @return XYZ color space. + */ + public static float[] LABtoXYZ(float l, float a, float b, float[] tristimulus){ + float[] xyz = new float[3]; + + float y = ( l + 16f ) / 116f; + float x = a / 500f + y; + float z = y - b / 200f; + + //Y + if ( Math.pow(y,3) > 0.008856 ) + y = (float)Math.pow(y,3); + else + y = (float)(( y - 16 / 116 ) / 7.787); + + //X + if ( Math.pow(x,3) > 0.008856 ) + x = (float)Math.pow(x,3); + else + x = (float)(( x - 16 / 116 ) / 7.787); + + // Z + if ( Math.pow(z,3) > 0.008856 ) + z = (float)Math.pow(z,3); + else + z = (float)(( z - 16 / 116 ) / 7.787); + + xyz[0] = x * tristimulus[0]; + xyz[1] = y * tristimulus[1]; + xyz[2] = z * tristimulus[2]; + + return xyz; + } + + /** + * RGB -> C1C2C3. + * @param r Red coefficient. Values in the range [0..255]. + * @param g Green coefficient. Values in the range [0..255]. + * @param b Blue coefficient. Values in the range [0..255]. + * @return C1C2C3 color space. + */ + public static float[] RGBtoC1C2C3(int r, int g, int b){ + + float[] c = new float[3]; + + c[0] = (float)Math.atan(r / Math.max(g, b)); + c[1] = (float)Math.atan(g / Math.max(r, b)); + c[2] = (float)Math.atan(b / Math.max(r, g)); + + return c; + + } + + /** + * RGB -> O1O2. + * @param r Red coefficient. Values in the range [0..255]. + * @param g Green coefficient. Values in the range [0..255]. + * @param b Blue coefficient. Values in the range [0..255]. + * @return O1O2 color space. + */ + public static float[] RGBtoO1O2(int r, int g, int b){ + + float[] o = new float[2]; + + o[0] = (r - g) / 2f; + o[1] = (r + g) / 4f - (b / 2f); + + return o; + + } + + /** + * RGB -> Grayscale. + * @param r Red coefficient. Values in the range [0..255]. + * @param g Green coefficient. Values in the range [0..255]. + * @param b Blue coefficient. Values in the range [0..255]. + * @return Grayscale color space. + */ + public static float RGBtoGrayscale(int r, int g, int b){ + + return r*0.2125f + g*0.7154f + b*0.0721f; + + } + + /** + * XYZ -> Philips Hue XY + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @return Hue xy array + */ + public static XYColorSpace XYZtoXY(float x, float y, float z){ + float[] xy = new float[2]; + + xy[0] = x / (x + y + z); + xy[1] = y / (x + y + z); + + XYColorSpace xyColor = new XYColorSpace(); + xyColor.setBrightness((int)Math.round(y * 254.0f)); + xyColor.setXy(xy); + return xyColor; + } + + /** + * Philips Hue XY -> XYZ + * @param x X coefficient. + * @param y Y coefficient. + * @return XYZ array + */ + public static float[] XYtoXYZ(XYColorSpace xy){ + float[] xyz = new float[3]; + + xyz[0] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * xy.getXy()[0]; + xyz[1] = xy.getBrightnessAdjusted(); + xyz[2] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * (1.0f - xy.getXy()[0] - xy.getXy()[1]); + + return xyz; + } + +} \ 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 index 4cca2262..27c1590b 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -122,64 +122,64 @@ public static List convertCIEtoRGB(List xy, int brightness) { List rgb; double x = xy.get(0); // the given x value double y = xy.get(1); // the given y value - double z = 1.0 - x - y; - double Y = (double) brightness / (double) 254.00; // The given brightness value + double z = 1.0f - x - y; + double Y = (double) brightness / (double) 254.00f; // The given brightness value double X = (Y / y) * x; double Z = (Y / y) * z; - double r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; - double g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; - double b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; + double r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; + double g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; + double b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; - if (r > b && r > g && r > 1.0) { + if (r > b && r > g && r > 1.0f) { g = g / r; b = b / r; - r = 1.0; - } else if (g > b && g > r && g > 1.0) { + r = 1.0f; + } else if (g > b && g > r && g > 1.0f) { r = r / g; b = b / g; - g = 1.0; - } else if (b > r && b > g && b > 1.0) { + g = 1.0f; + } else if (b > r && b > g && b > 1.0f) { r = r / b; g = g / b; - b = 1.0; + b = 1.0f; } - r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055; - g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055; - b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055; + r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * Math.pow(r, (1.0f / 2.4f)) - 0.055f; + g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * Math.pow(g, (1.0f / 2.4f)) - 0.055f; + b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * Math.pow(b, (1.0f / 2.4f)) - 0.055f; if (r > b && r > g) { // red is biggest - if (r > 1.0) { + if (r > 1.0f) { g = g / r; b = b / r; - r = 1.0; + r = 1.0f; } } else if (g > b && g > r) { // green is biggest - if (g > 1.0) { + if (g > 1.0f) { r = r / g; b = b / g; - g = 1.0; + g = 1.0f; } } else if (b > r && b > g) { // blue is biggest - if (b > 1.0) { + if (b > 1.0f) { r = r / b; g = g / b; - b = 1.0; + b = 1.0f; } } - if (r < 0.0) - r = 0; - if (g < 0.0) - g = 0; - if (b < 0.0) - b = 0; + if (r < 0.0f) + r = 0.0f; + if (g < 0.0f) + g = 0.0f; + if (b < 0.0f) + b = 0.0f; rgb = new ArrayList(); rgb.add((int) Math.round(r * 255)); diff --git a/src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java b/src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java new file mode 100644 index 00000000..148bb91d --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java @@ -0,0 +1,26 @@ +package com.bwssystems.HABridge.hue; + +public class XYColorSpace { + float[] xy; + int brightness; + + public float[] getXy() { + return xy; + } + + public void setXy(float[] xy) { + this.xy = xy; + } + + public int getBrightness() { + return brightness; + } + + public float getBrightnessAdjusted() { + return ((float) brightness / 254.0f) * 100f; + } + + public void setBrightness(int brightness) { + this.brightness = brightness; + } +} \ No newline at end of file diff --git a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java index 7e5323f0..9ed54386 100644 --- a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java +++ b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java @@ -9,6 +9,8 @@ import com.bwssystems.HABridge.hue.ColorData; import com.bwssystems.HABridge.hue.ColorDecode; +import com.bwssystems.HABridge.hue.XYColorSpace; +import com.bwssystems.HABridge.hue.ColorConverter; public class ConvertCIEColorTestCase { @@ -16,6 +18,29 @@ public class ConvertCIEColorTestCase { public void testColorConversion() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); + XYColorSpace xyColor = new XYColorSpace(); + xyColor.setBrightness(254); + float[] xyFloat = new float[2]; + xyFloat[0] = xy.get(0).floatValue(); + xyFloat[1] = xy.get(1).floatValue(); + xyColor.setXy(xyFloat); + float[] xyz = ColorConverter.XYtoXYZ(xyColor); + int[] rgb = ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2]); + List rgbDecode = new ArrayList(); + rgbDecode.add(0, rgb[0]); + rgbDecode.add(1, rgb[1]); + rgbDecode.add(2, rgb[2]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 255); + assertDecode.add(1, 47); + assertDecode.add(2, 43); + Assert.assertEquals(rgbDecode, assertDecode); + } + + @Test + public void testColorConversionRGB() { + ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); + List colorDecode = ColorDecode.convertCIEtoRGB(xy, 254); List assertDecode = new ArrayList(); assertDecode.add(0, 255); From 768eebfc7825b7655ed20edef80b6677b52cd0a2 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Thu, 26 Sep 2019 14:12:41 -0500 Subject: [PATCH 04/19] Updated color conversions and tests --- .../HABridge/hue/ColorConverter.java | 21 +++ .../bwssystems/HABridge/hue/ColorDecode.java | 164 ++---------------- .../color/test/ConvertCIEColorTestCase.java | 64 +++++-- 3 files changed, 91 insertions(+), 158 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java index 4d7c3c68..faa089f2 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java @@ -939,4 +939,25 @@ public static float[] XYtoXYZ(XYColorSpace xy){ return xyz; } + public static int[] normalizeRGB(int[] rgb) { + int[] newRGB = new int[3]; + + newRGB[0] = assureBounds(rgb[0]); + newRGB[1] = assureBounds(rgb[1]); + newRGB[2] = assureBounds(rgb[2]); + + + return newRGB; + } + + private static int assureBounds(int value) { + if (value < 0.0) { + value = 0; + } + if (value > 255.0) { + value = 255; + } + return value; + } + } \ 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 index 27c1590b..50fe4abb 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -30,162 +30,32 @@ public class ColorDecode { public static List convertHSLtoRGB(HueSatBri hsl) { List rgb; - float decimalBrightness = (float) 0.0; - float var_1 = (float) 0.0; - float var_2 = (float) 0.0; - float h = (float) 0.0; - float h2 = (float) 0.0; - float s = (float) 0.0; - double r = 0.0; - double g = 0.0; - double b = 0.0; - - if (hsl.getBri() > 0) - decimalBrightness = (float) (hsl.getBri() / 255.0); - - if (hsl.getHue() > 0) { - h = ((float) hsl.getHue() / (float) 65535.0); - h2 = h + (float) 0.5; - if (h2 > 1.0) { - h2 = h2 - (float) 1.0; - } - } - if (hsl.getSat() > 0) { - s = (float) (hsl.getSat() / 254.0); - } - - if (s == 0) { - r = decimalBrightness * (float) 255; - g = decimalBrightness * (float) 255; - b = decimalBrightness * (float) 255; - } else { - if (decimalBrightness < 0.5) { - var_2 = decimalBrightness * (1 + s); - } else { - var_2 = (decimalBrightness + s) - (s * decimalBrightness); - } - ; - - var_1 = 2 * decimalBrightness - var_2; - float onethird = (float) 0.33333; - float h2Plus = (h2 + onethird); - float h2Minus = (h2 - onethird); - log.debug("calculate HSL vars - var1: " + var_1 + ", var_2: " + var_2 + ", h2: " + h2 + ", h2 + 1/3: " - + h2Plus + ", h2 - 1/3: " + h2Minus); - r = 255 * hue_2_rgb(var_1, var_2, h2Plus); - g = 255 * hue_2_rgb(var_1, var_2, h2); - b = 255 * hue_2_rgb(var_1, var_2, h2Minus); - } - ; - + int[] rgbInt = ColorConverter.normalizeRGB(ColorConverter.HSLtoRGB((float)((float)hsl.getHue()/65535.0f), (float)((float)hsl.getSat()/254.0f), (float)((float)hsl.getBri()/254))); rgb = new ArrayList(); - rgb.add((int) Math.round(r)); - rgb.add((int) Math.round(g)); - rgb.add((int) Math.round(b)); + rgb.add(rgbInt[0]); + rgb.add(rgbInt[1]); + rgb.add(rgbInt[2]); - log.debug("Color change with HSL: " + hsl + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + log.info("Color change with HSL: " + hsl + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.get(2)); return rgb; } - public static float hue_2_rgb(float v1, float v2, float vh) { - log.debug("hue_2_rgb vh: " + vh); - if (vh < 0.0) { - vh = vh + (float) 1; - } - ; - - if (vh > 1.0) { - vh = vh - (float) 1; - } - ; - - if (((float) 6.0 * vh) < 1.0) { - return (v1 + (v2 - v1) * (float) 6.0 * vh); - } - ; - - if (((float) 2.0 * vh) < 1.0) { - return (v2); - } - ; - - if ((3.0 * vh) < 2.0) { - return (v1 + (v2 - v1) * (((float) 2.0 / (float) 3.0 - vh) * (float) 6.0)); - } - ; - - return (v1); - } - public static List convertCIEtoRGB(List xy, int brightness) { List rgb; - double x = xy.get(0); // the given x value - double y = xy.get(1); // the given y value - double z = 1.0f - x - y; - double Y = (double) brightness / (double) 254.00f; // The given brightness value - double X = (Y / y) * x; - double Z = (Y / y) * z; - - double r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; - double g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; - double b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; - - if (r > b && r > g && r > 1.0f) { - - g = g / r; - b = b / r; - r = 1.0f; - } else if (g > b && g > r && g > 1.0f) { - - r = r / g; - b = b / g; - g = 1.0f; - } else if (b > r && b > g && b > 1.0f) { - - r = r / b; - g = g / b; - b = 1.0f; - } - - r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * Math.pow(r, (1.0f / 2.4f)) - 0.055f; - g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * Math.pow(g, (1.0f / 2.4f)) - 0.055f; - b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * Math.pow(b, (1.0f / 2.4f)) - 0.055f; - - if (r > b && r > g) { - // red is biggest - if (r > 1.0f) { - g = g / r; - b = b / r; - r = 1.0f; - } - } else if (g > b && g > r) { - // green is biggest - if (g > 1.0f) { - r = r / g; - b = b / g; - g = 1.0f; - } - } else if (b > r && b > g) { - // blue is biggest - if (b > 1.0f) { - r = r / b; - g = g / b; - b = 1.0f; - } - } - if (r < 0.0f) - r = 0.0f; - if (g < 0.0f) - g = 0.0f; - if (b < 0.0f) - b = 0.0f; - + XYColorSpace xyColor = new XYColorSpace(); + xyColor.setBrightness(brightness); + float[] xyFloat = new float[2]; + xyFloat[0] = xy.get(0).floatValue(); + xyFloat[1] = xy.get(1).floatValue(); + xyColor.setXy(xyFloat); + float[] xyz = ColorConverter.XYtoXYZ(xyColor); + int[] rgbInt = ColorConverter.normalizeRGB(ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2])); rgb = new ArrayList(); - rgb.add((int) Math.round(r * 255)); - rgb.add((int) Math.round(g * 255)); - rgb.add((int) Math.round(b * 255)); - log.debug("Color change with XY: " + x + " " + y + " Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + rgb.add(rgbInt[0]); + rgb.add(rgbInt[1]); + rgb.add(rgbInt[2]); + log.debug("Color change with XY: " + xy.get(0) + " " + xy.get(1) + " Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.get(2)); return rgb; } diff --git a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java index 9ed54386..39bda8d7 100644 --- a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java +++ b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java @@ -9,48 +9,90 @@ import com.bwssystems.HABridge.hue.ColorData; import com.bwssystems.HABridge.hue.ColorDecode; +import com.bwssystems.HABridge.hue.HueSatBri; import com.bwssystems.HABridge.hue.XYColorSpace; import com.bwssystems.HABridge.hue.ColorConverter; public class ConvertCIEColorTestCase { @Test - public void testColorConversion() { + public void testColorConverterXYtoRGB() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); XYColorSpace xyColor = new XYColorSpace(); - xyColor.setBrightness(254); + xyColor.setBrightness(50); float[] xyFloat = new float[2]; xyFloat[0] = xy.get(0).floatValue(); xyFloat[1] = xy.get(1).floatValue(); xyColor.setXy(xyFloat); float[] xyz = ColorConverter.XYtoXYZ(xyColor); - int[] rgb = ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2]); + int[] rgb = ColorConverter.normalizeRGB(ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2])); List rgbDecode = new ArrayList(); rgbDecode.add(0, rgb[0]); rgbDecode.add(1, rgb[1]); rgbDecode.add(2, rgb[2]); List assertDecode = new ArrayList(); assertDecode.add(0, 255); - assertDecode.add(1, 47); - assertDecode.add(2, 43); + assertDecode.add(1, 0); + assertDecode.add(2, 5); Assert.assertEquals(rgbDecode, assertDecode); } @Test - public void testColorConversionRGB() { + public void testColorConverterHSLtoRGB() { + int[] rgb = ColorConverter.normalizeRGB(ColorConverter.HSLtoRGB(0.0f, 1.0f, 0.50000f)); + List rgbDecode = new ArrayList(); + rgbDecode.add(0, rgb[0]); + rgbDecode.add(1, rgb[1]); + rgbDecode.add(2, rgb[2]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 255); + assertDecode.add(1, 0); + assertDecode.add(2, 0); + Assert.assertEquals(rgbDecode, assertDecode); + } + + @Test + public void testColorConversionXYtoRGB() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); - List colorDecode = ColorDecode.convertCIEtoRGB(xy, 254); + List colorDecode = ColorDecode.convertCIEtoRGB(xy, 50); List assertDecode = new ArrayList(); assertDecode.add(0, 255); - assertDecode.add(1, 47); - assertDecode.add(2, 43); + assertDecode.add(1, 0); + assertDecode.add(2, 5); Assert.assertEquals(colorDecode, assertDecode); ColorData colorData = new ColorData(ColorData.ColorMode.XY, xy); - int rgbIntVal = ColorDecode.getIntRGB(colorData, 254); - Assert.assertEquals(rgbIntVal, 16723755); + int rgbIntVal = ColorDecode.getIntRGB(colorData, 50); + Assert.assertEquals(rgbIntVal, 16711685); + } + + @Test + public void testColorConversionHSLtoRGB() { + HueSatBri hsb = new HueSatBri(); + hsb.setHue(0); + hsb.setSat(254); + hsb.setBri((int)Math.round(0.50000 * 254)); + + List colorDecode = ColorDecode.convertHSLtoRGB(hsb); + List assertDecode = new ArrayList(); + assertDecode.add(0, 255); + assertDecode.add(1, 0); + assertDecode.add(2, 0); + Assert.assertEquals(colorDecode, assertDecode); + } + + @Test + public void testColorConversionCTtoRGB() { + Integer ct = 500; + + List colorDecode = ColorDecode.convertCTtoRGB(ct); + List assertDecode = new ArrayList(); + assertDecode.add(0, 255); + assertDecode.add(1, 214); + assertDecode.add(2, 73); + Assert.assertEquals(colorDecode, assertDecode); } } From fe4df16e10e504654122cbb6b804f5296ee0c930 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Tue, 1 Oct 2019 14:42:20 -0500 Subject: [PATCH 05/19] Continue test cases --- .../bwssystems/HABridge/hue/ColorDecode.java | 2 +- .../color/test/ConvertCIEColorTestCase.java | 81 ++++++++++++++++++- 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java index 50fe4abb..1144bd65 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -36,7 +36,7 @@ public static List convertHSLtoRGB(HueSatBri hsl) { rgb.add(rgbInt[1]); rgb.add(rgbInt[2]); - log.info("Color change with HSL: " + hsl + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + log.debug("Color change with HSL: " + hsl + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.get(2)); return rgb; } diff --git a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java index 39bda8d7..ec4a94ea 100644 --- a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java +++ b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java @@ -53,7 +53,49 @@ public void testColorConverterHSLtoRGB() { } @Test - public void testColorConversionXYtoRGB() { + public void testColorConverterRGBtoHSL() { + float[] rgb = ColorConverter.RGBtoHLS(255,0,0); + List rgbDecode = new ArrayList(); + rgbDecode.add(0, rgb[0]); + rgbDecode.add(1, rgb[1]); + rgbDecode.add(2, rgb[2]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 0.0f); + assertDecode.add(1, 1.0f); + assertDecode.add(2, 0.50000f); + Assert.assertEquals(rgbDecode, assertDecode); + } + + @Test + public void testColorConverterHSLtoRGB2() { + int[] rgb = ColorConverter.normalizeRGB(ColorConverter.HSLtoRGB(0.57629f, 0.56299f, 0.50394f)); + List rgbDecode = new ArrayList(); + rgbDecode.add(0, rgb[0]); + rgbDecode.add(1, rgb[1]); + rgbDecode.add(2, rgb[2]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 58); + assertDecode.add(1, 135); + assertDecode.add(2, 200); + Assert.assertEquals(rgbDecode, assertDecode); + } + + @Test + public void testColorConverterRGBtoHSL2() { + float[] rgb = ColorConverter.RGBtoHLS(58,135,200); + List rgbDecode = new ArrayList(); + rgbDecode.add(0, rgb[0]); + rgbDecode.add(1, rgb[1]); + rgbDecode.add(2, rgb[2]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 0.57629f); + assertDecode.add(1, 0.56299f); + assertDecode.add(2, 0.50394f); + Assert.assertEquals(rgbDecode, assertDecode); + } + + @Test + public void testColorConversionXYtoRGB1() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); List colorDecode = ColorDecode.convertCIEtoRGB(xy, 50); @@ -69,7 +111,23 @@ public void testColorConversionXYtoRGB() { } @Test - public void testColorConversionHSLtoRGB() { + public void testColorConversionXYtoRGB2() { + ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.32312"), Double.parseDouble("0.15539"))); + + List colorDecode = ColorDecode.convertCIEtoRGB(xy, 59); + List assertDecode = new ArrayList(); + assertDecode.add(0, 233); + assertDecode.add(1, 0); + assertDecode.add(2, 231); + Assert.assertEquals(colorDecode, assertDecode); + + ColorData colorData = new ColorData(ColorData.ColorMode.XY, xy); + int rgbIntVal = ColorDecode.getIntRGB(colorData, 59); + Assert.assertEquals(rgbIntVal, 15270119); + } + + @Test + public void testColorConversionHSLtoRGB1() { HueSatBri hsb = new HueSatBri(); hsb.setHue(0); hsb.setSat(254); @@ -83,6 +141,21 @@ public void testColorConversionHSLtoRGB() { Assert.assertEquals(colorDecode, assertDecode); } + @Test + public void testColorConversionHSLtoRGB2() { + HueSatBri hsb = new HueSatBri(); + hsb.setHue(37767); + hsb.setSat(143); + hsb.setBri(128); + + List colorDecode = ColorDecode.convertHSLtoRGB(hsb); + List assertDecode = new ArrayList(); + assertDecode.add(0, 58); + assertDecode.add(1, 135); + assertDecode.add(2, 200); + Assert.assertEquals(colorDecode, assertDecode); + } + @Test public void testColorConversionCTtoRGB() { Integer ct = 500; @@ -90,8 +163,8 @@ public void testColorConversionCTtoRGB() { List colorDecode = ColorDecode.convertCTtoRGB(ct); List assertDecode = new ArrayList(); assertDecode.add(0, 255); - assertDecode.add(1, 214); - assertDecode.add(2, 73); + assertDecode.add(1, 137); + assertDecode.add(2, 14); Assert.assertEquals(colorDecode, assertDecode); } From a3fd2ca7225e59683ce3fd5f14a82715a52c1f74 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Wed, 2 Oct 2019 15:21:35 -0500 Subject: [PATCH 06/19] finished color decode fixes and tests, updated deviceId counting, fixing min bright value --- .../HABridge/dao/DeviceRepository.java | 2 + .../HABridge/hue/BrightnessDecode.java | 8 +- .../bwssystems/HABridge/hue/ColorDecode.java | 113 +++++++++++++++--- .../bwssystems/HABridge/hue/HueMulator.java | 14 +-- .../color/test/ConvertCIEColorTestCase.java | 83 +------------ 5 files changed, 113 insertions(+), 107 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java index 1b4bf996..13a7dadb 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java @@ -75,6 +75,8 @@ private void _loadRepository(Path aPath) { nextId = Integer.decode(list[i].getId()); } } + + nextId = nextId + 1; } } diff --git a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java index 8ccaf7c8..276b08a1 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java @@ -26,7 +26,7 @@ public static int calculateIntensity(int setIntensity, Integer targetBri, Intege if (targetBri != null) { setIntensity = targetBri; } else if (targetBriInc != null) { - if ((setIntensity + targetBriInc) <= 0) + if ((setIntensity + targetBriInc) <= 1) setIntensity = targetBriInc; else if ((setIntensity + targetBriInc) > 254) setIntensity = targetBriInc; @@ -53,7 +53,7 @@ public static String replaceIntensityValue(String request, int intensity, boolea String replaceValue = null; String replaceTarget = null; int percentBrightness = 0; - float decimalBrightness = (float) 0.0; + float decimalBrightness = (float) 1.0; Map variables = new HashMap(); String mathDescriptor = null; @@ -64,8 +64,8 @@ public static String replaceIntensityValue(String request, int intensity, boolea else percentBrightness = (int) Math.round(intensity / 255.0 * 100); } else { - decimalBrightness = (float) 0.0; - percentBrightness = 0; + decimalBrightness = (float) 1.0; + percentBrightness = 1; } while(notDone) { diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java index 1144bd65..1284b8ed 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -20,23 +20,101 @@ public class ColorDecode { private static final String COLOR_GX = "${color.gx}"; private static final String COLOR_BX = "${color.bx}"; private static final String COLOR_RGBX = "${color.rgbx}"; - private static final String COLOR_HSL = "${color.hsl}"; + private static final String COLOR_HSB = "${color.hsb}"; private static final String COLOR_H = "${color.h}"; private static final String COLOR_S = "${color.s}"; - private static final String COLOR_L = "${color.l}"; private static final String COLOR_XY = "${color.xy}"; private static final String COLOR_BRI = "${colorbri}"; private static final Pattern COLOR_MILIGHT = Pattern.compile("\\$\\{color.milight\\:([01234])\\}"); - public static List convertHSLtoRGB(HueSatBri hsl) { + public static List convertHSBtoRGB(HueSatBri hsb) { List rgb; - int[] rgbInt = ColorConverter.normalizeRGB(ColorConverter.HSLtoRGB((float)((float)hsl.getHue()/65535.0f), (float)((float)hsl.getSat()/254.0f), (float)((float)hsl.getBri()/254))); + Float hue = (Float)(hsb.getHue()*1.0f); + Float saturation = (Float)(hsb.getSat()*1.0f); + Float brightness = (Float)(hsb.getBri()*1.0f); + log.info("Hue = " + hue + ", Sat = " + saturation + ", Bri = " + brightness); + //Convert Hue into degrees for HSB + hue = hue / 182.04f; + //Bri and Sat must be values from 0-1 (~percentage) + brightness = brightness / 255.0f; + saturation = saturation / 255.0f; + + Float r = 0f; + Float g = 0f; + Float b = 0f; + + if (saturation == 0) + { + r = g = b = brightness; + } + else + { + // the color wheel consists of 6 sectors. + Float sectorPos = hue / 60.0f; + int sectorNumber = (int)(Math.floor(sectorPos)); + // get the fractional part of the sector + Float fractionalSector = sectorPos - sectorNumber; + + // calculate values for the three axes of the color. + Float p = brightness * (1.0f - saturation); + Float q = brightness * (1.0f - (saturation * fractionalSector)); + Float t = brightness * (1.0f - (saturation * (1f - fractionalSector))); + + // assign the fractional colors to r, g, and b based on the sector the angle is in. + switch (sectorNumber) + { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + case 5: + r = brightness; + g = p; + b = q; + break; + } + } + + //Check if any value is out of byte range + if (r < 0f) + { + r = 0f; + } + if (g < 0f) + { + g = 0f; + } + if (b < 0f) + { + b = 0f; + } + rgb = new ArrayList(); - rgb.add(rgbInt[0]); - rgb.add(rgbInt[1]); - rgb.add(rgbInt[2]); - - log.debug("Color change with HSL: " + hsl + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.add((int)Math.round(r*255)); + rgb.add((int)Math.round(g*255)); + rgb.add((int)Math.round(b*255)); + log.debug("Color change with HSB: " + hsb + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.get(2)); return rgb; } @@ -125,7 +203,7 @@ public static String replaceColorData(String request, ColorData colorData, int s } else if (colorMode == ColorData.ColorMode.CT) { rgb = convertCTtoRGB((Integer) colorData.getData()); } else if (colorMode == ColorData.ColorMode.HS) { - rgb = convertHSLtoRGB((HueSatBri) colorData.getData()); + rgb = convertHSBtoRGB((HueSatBri) colorData.getData()); } while (notDone) { @@ -206,23 +284,20 @@ public static String replaceColorData(String request, ColorData colorData, int s notDone = true; } - if (request.contains(COLOR_L)) { + if (request.contains(COLOR_BRI)) { if (colorMode == ColorData.ColorMode.HS) { HueSatBri hslData = (HueSatBri) colorData.getData(); - request = request.replace(COLOR_L, String.format("%d", hslData.getBri())); + request = request.replace(COLOR_BRI, String.format("%d", hslData.getBri())); } else { - float[] hsb = new float[3]; - Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); - float bright = hsb[2] * (float) 100.0; - request = request.replace(COLOR_L, String.format("%f", bright)); + request = request.replace(COLOR_BRI, String.format("%f", setIntensity)); } notDone = true; } - if (request.contains(COLOR_HSL)) { + if (request.contains(COLOR_HSB)) { if (colorMode == ColorData.ColorMode.HS) { HueSatBri hslData = (HueSatBri) colorData.getData(); - request = request.replace(COLOR_HSL, + request = request.replace(COLOR_HSB, String.format("%d,%d,%d", hslData.getHue(), hslData.getSat(), hslData.getBri())); } else { float[] hsb = new float[3]; @@ -230,7 +305,7 @@ public static String replaceColorData(String request, ColorData colorData, int s float hue = hsb[0] * (float) 360.0; float sat = hsb[1] * (float) 100.0; float bright = hsb[2] * (float) 100.0; - request = request.replace(COLOR_HSL, String.format("%f,%f,%f", hue, sat, bright)); + request = request.replace(COLOR_HSB, String.format("%f,%f,%f", hue, sat, bright)); } notDone = true; } diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 75484710..254708f6 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -473,9 +473,9 @@ private String formatSuccessHueResponse(StateChangeBody stateChanges, String bod if (deviceState != null) { deviceState.setOn(stateChanges.isOn()); if (!deviceState.isOn() && deviceState.getBri() == 254) - deviceState.setBri(0); + deviceState.setBri(1); if (!deviceState.isOn() && offState) - deviceState.setBri(0); + deviceState.setBri(1); } notFirstChange = true; } @@ -609,7 +609,7 @@ private String formatSuccessHueResponse(StateChangeBody stateChanges, String bod notFirstChange = true; } - if ((deviceState != null) && deviceState.isOn() && deviceState.getBri() <= 0) + if ((deviceState != null) && deviceState.isOn() && deviceState.getBri() <= 1) deviceState.setBri(254); // if((deviceState != null) && !deviceState.isOn() && (targetBri != null || @@ -1416,8 +1416,8 @@ private ColorData parseColorInfo(String body, StateChangeBody theStateChanges, D int bri = 0; if (targetBriInc != null) { bri = state.getBri() - targetBriInc; - if (bri < 0) - bri = 0; + if (bri < 1) + bri = 1; } else if (targetBri != null) { bri = targetBri; } else { @@ -1440,8 +1440,8 @@ private ColorData parseColorInfo(String body, StateChangeBody theStateChanges, D int bri = 0; if (targetBriInc != null) { bri = state.getBri() - targetBriInc; - if (bri < 0) - bri = 0; + if (bri < 1) + bri = 1; } else if (targetBri != null) { bri = targetBri; } else { diff --git a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java index ec4a94ea..af101155 100644 --- a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java +++ b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java @@ -38,62 +38,6 @@ public void testColorConverterXYtoRGB() { Assert.assertEquals(rgbDecode, assertDecode); } - @Test - public void testColorConverterHSLtoRGB() { - int[] rgb = ColorConverter.normalizeRGB(ColorConverter.HSLtoRGB(0.0f, 1.0f, 0.50000f)); - List rgbDecode = new ArrayList(); - rgbDecode.add(0, rgb[0]); - rgbDecode.add(1, rgb[1]); - rgbDecode.add(2, rgb[2]); - List assertDecode = new ArrayList(); - assertDecode.add(0, 255); - assertDecode.add(1, 0); - assertDecode.add(2, 0); - Assert.assertEquals(rgbDecode, assertDecode); - } - - @Test - public void testColorConverterRGBtoHSL() { - float[] rgb = ColorConverter.RGBtoHLS(255,0,0); - List rgbDecode = new ArrayList(); - rgbDecode.add(0, rgb[0]); - rgbDecode.add(1, rgb[1]); - rgbDecode.add(2, rgb[2]); - List assertDecode = new ArrayList(); - assertDecode.add(0, 0.0f); - assertDecode.add(1, 1.0f); - assertDecode.add(2, 0.50000f); - Assert.assertEquals(rgbDecode, assertDecode); - } - - @Test - public void testColorConverterHSLtoRGB2() { - int[] rgb = ColorConverter.normalizeRGB(ColorConverter.HSLtoRGB(0.57629f, 0.56299f, 0.50394f)); - List rgbDecode = new ArrayList(); - rgbDecode.add(0, rgb[0]); - rgbDecode.add(1, rgb[1]); - rgbDecode.add(2, rgb[2]); - List assertDecode = new ArrayList(); - assertDecode.add(0, 58); - assertDecode.add(1, 135); - assertDecode.add(2, 200); - Assert.assertEquals(rgbDecode, assertDecode); - } - - @Test - public void testColorConverterRGBtoHSL2() { - float[] rgb = ColorConverter.RGBtoHLS(58,135,200); - List rgbDecode = new ArrayList(); - rgbDecode.add(0, rgb[0]); - rgbDecode.add(1, rgb[1]); - rgbDecode.add(2, rgb[2]); - List assertDecode = new ArrayList(); - assertDecode.add(0, 0.57629f); - assertDecode.add(1, 0.56299f); - assertDecode.add(2, 0.50394f); - Assert.assertEquals(rgbDecode, assertDecode); - } - @Test public void testColorConversionXYtoRGB1() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); @@ -127,32 +71,17 @@ public void testColorConversionXYtoRGB2() { } @Test - public void testColorConversionHSLtoRGB1() { - HueSatBri hsb = new HueSatBri(); - hsb.setHue(0); - hsb.setSat(254); - hsb.setBri((int)Math.round(0.50000 * 254)); - - List colorDecode = ColorDecode.convertHSLtoRGB(hsb); - List assertDecode = new ArrayList(); - assertDecode.add(0, 255); - assertDecode.add(1, 0); - assertDecode.add(2, 0); - Assert.assertEquals(colorDecode, assertDecode); - } - - @Test - public void testColorConversionHSLtoRGB2() { + public void testColorConversionHSBtoRGB1() { HueSatBri hsb = new HueSatBri(); hsb.setHue(37767); - hsb.setSat(143); + hsb.setSat(135); hsb.setBri(128); - List colorDecode = ColorDecode.convertHSLtoRGB(hsb); + List colorDecode = ColorDecode.convertHSBtoRGB(hsb); List assertDecode = new ArrayList(); - assertDecode.add(0, 58); - assertDecode.add(1, 135); - assertDecode.add(2, 200); + assertDecode.add(0, 60); + assertDecode.add(1, 97); + assertDecode.add(2, 128); Assert.assertEquals(colorDecode, assertDecode); } From c3762534880ed6c96d4b2e2cdbc8f9d302c0af1d Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Thu, 26 Mar 2020 13:34:27 -0400 Subject: [PATCH 07/19] Fix getting outbound route from inbound address --- .../HABridge/upnp/UpnpSettingsResource.java | 1 + .../com/bwssystems/HABridge/util/AddressUtil.java | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java index 30a1fa81..58c692db 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java @@ -98,6 +98,7 @@ public void setupServer() { if(theSettings.isUseupnpiface()) { httpLocationAddr = theSettings.getUpnpConfigAddress(); } else { + log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port()); httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress(); } hueTemplate = hueTemplate_pre + hueTemplate_post; diff --git a/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java b/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java index 77dd9607..6af47f25 100644 --- a/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java +++ b/src/main/java/com/bwssystems/HABridge/util/AddressUtil.java @@ -15,9 +15,11 @@ public class AddressUtil { // in the LOCATION header in replies public static InetAddress getOutboundAddress(String remoteAddress, int remotePort) { InetAddress localAddress = null; + log.debug("Entering getOutboundAddress with args."); try { - getOutboundAddress(new InetSocketAddress(remoteAddress, remotePort)); + localAddress = getOutboundAddress(new InetSocketAddress(remoteAddress, remotePort)); } catch (Exception e) { + log.debug("getOutboundAddress(SocketAddress) Threw an Exception: " + e.getMessage()); ParseRoute theRoute = ParseRoute.getInstance(); try { localAddress = InetAddress.getByName(theRoute.getLocalIPAddress()); @@ -29,7 +31,9 @@ public static InetAddress getOutboundAddress(String remoteAddress, int remotePor } if(localAddress != null) - log.debug("getOutbountAddress returning IP Address of " + localAddress.getHostAddress()); + log.debug("localAddress is IP Address of " + localAddress.getHostAddress()); + else + log.debug("localAddress returning NULL"); return localAddress; } @@ -42,11 +46,17 @@ public static InetAddress getOutboundAddress(SocketAddress remoteAddress) throws DatagramSocket sock = new DatagramSocket(); // connect is needed to bind the socket and retrieve the local address // later (it would return 0.0.0.0 otherwise) + log.debug("Entering getOutboundAddress with socket arg."); sock.connect(remoteAddress); + log.debug("getOutboundAddress(SocketAddress) getLocalAddress."); final InetAddress localAddress = sock.getLocalAddress(); sock.disconnect(); sock.close(); sock = null; + if(localAddress != null) + log.debug("getOutbountAddress(SocketAddress) returning IP Address of " + localAddress.getHostAddress()); + else + log.debug("getOutboundAddress(SocketAddress) returning NULL"); return localAddress; } } \ No newline at end of file From f5e100667e210c8668bede746bef822d7c2bf2c2 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Thu, 26 Mar 2020 15:25:10 -0400 Subject: [PATCH 08/19] Needed to not fix the multicast socket to a specific ip address This is when upnp use interface only --- .../com/bwssystems/HABridge/upnp/UpnpListener.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java index 1fd47220..a2ab7a04 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java @@ -90,10 +90,11 @@ public UpnpListener(BridgeSettings theSettings, BridgeControlDescriptor theContr } try { - if (useUpnpIface) - upnpMulticastSocket = new MulticastSocket( - new InetSocketAddress(upnpConfigIP, Configuration.UPNP_DISCOVERY_PORT)); - else +// This commented out code does not work... leave for review +// if (useUpnpIface) +// upnpMulticastSocket = new MulticastSocket( +// new InetSocketAddress(upnpConfigIP, Configuration.UPNP_DISCOVERY_PORT)); +// else upnpMulticastSocket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT); } catch (IOException e) { log.error("Upnp Discovery Port is in use, or restricted by admin (try running with sudo or admin privs): " @@ -136,7 +137,7 @@ public boolean startListening() { if (traceupnp) log.info("Traceupnp: Interface: " + name + " valid usable IP address: " + addr); IPsPerNic++; - } else if (addr.getHostAddress().equals(upnpConfigIP)) { + } else if (useUpnpIface && (addr.getHostAddress().equals(upnpConfigIP) || name.equals("lo"))) { if (traceupnp) log.info("Traceupnp: Interface: " + name + " matches upnp config address of IP address: " + addr); @@ -266,6 +267,7 @@ public boolean startListening() { protected boolean isSSDPDiscovery(DatagramPacket packet) { // Only respond to discover request for strict upnp form String packetString = new String(packet.getData(), 0, packet.getLength()); +// log.info("Packet string <<<" + packetString + ">>>"); if (packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1") && packetString.contains("\"ssdp:discover\"")) { if ((packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") From 0d568d8d68a2e545fc71866536e2555ed77decba Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Thu, 26 Mar 2020 15:31:55 -0400 Subject: [PATCH 09/19] Changed version to 5.3.1RC1 --- README.md | 8 ++++---- pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 98c2f961..31a8b781 100644 --- a/README.md +++ b/README.md @@ -57,20 +57,20 @@ Then locate the jar and start the server with: ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below. ``` -java -jar ha-bridge-5.3.0.jar +java -jar ha-bridge-5.3.1RC1.jar ``` ## Manual installation of ha-bridge and setup of systemd service Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services. Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units -Create the directory and make sure that ha-bridge-5.3.0.jar is in your /home/pi/ha-bridge directory. +Create the directory and make sure that ha-bridge-5.3.1RC1.jar is in your /home/pi/ha-bridge directory. ``` pi@raspberrypi:~ $ mkdir ha-bridge pi@raspberrypi:~ $ cd ha-bridge -pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.0/ha-bridge-5.3.0.jar +pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC1/ha-bridge-5.3.1RC1.jar ``` Create the ha-bridge.service unit file: @@ -89,7 +89,7 @@ After=network.target Type=simple WorkingDirectory=/home/pi/ha-bridge -ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.0.jar +ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC1.jar [Install] WantedBy=multi-user.target diff --git a/pom.xml b/pom.xml index 87105157..894ee817 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.3.0a + 5.3.1RC1 jar HA Bridge From 9a355b79063729ea6bd22eda247e4bb6c0bd458b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 06:42:38 +0000 Subject: [PATCH 10/19] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 23e5c631..ad071de4 100644 --- a/pom.xml +++ b/pom.xml @@ -116,7 +116,7 @@ junit junit - 4.12 + 4.13.1 test From c840f2bc4d04a3c12bfa34416f4ff637ac242002 Mon Sep 17 00:00:00 2001 From: Marco Pollacci Date: Fri, 6 Nov 2020 10:07:40 +0100 Subject: [PATCH 11/19] Update DeviceResponse.java --- .../HABridge/api/hue/DeviceResponse.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/api/hue/DeviceResponse.java b/src/main/java/com/bwssystems/HABridge/api/hue/DeviceResponse.java index 23662764..45bdd288 100644 --- a/src/main/java/com/bwssystems/HABridge/api/hue/DeviceResponse.java +++ b/src/main/java/com/bwssystems/HABridge/api/hue/DeviceResponse.java @@ -17,6 +17,7 @@ public class DeviceResponse { private String swversion; private String swconfigid; private String productid; + private String productname; public DeviceState getState() { return state; @@ -90,6 +91,14 @@ public void setProductid(String productid) { this.productid = productid; } + public String getProductName() { + return productname; + } + + public void setProductName(String productname) { + this.productname = productname; + } + public String getLuminaireuniqueid() { return luminaireuniqueid; @@ -109,10 +118,11 @@ public static DeviceResponse createResponse(DeviceDescriptor device){ if (device.isColorDevice()) { response.setType("Extended color light"); - response.setModelid("LCT010"); - response.setSwversion("1.15.2_r19181"); - response.setSwconfigid("F921C859"); - response.setProductid("Philips-LCT010-1-A19ECLv4"); + response.setModelid("LCT015"); + response.setSwversion("1.46.13_r26312"); + response.setSwconfigid("52E3234B"); + response.setProductid("Philips-LCT015-1-A19ECLv5"); + response.setProductName("Hue color lamp"); } else { response.setType("Dimmable light"); response.setModelid("LWB007"); @@ -129,13 +139,14 @@ public static DeviceResponse createResponseForVirtualLight(GroupDescriptor group response.setState(group.getAction()); response.setName(group.getName()); - response.setUniqueid("00:17:88:5E:D3:FF-" + String.format("%02X", Integer.parseInt(group.getId()))); + response.setUniqueid("00:11:22:33:44:55:66:77-" + String.format("%02X", Integer.parseInt(group.getId()))); response.setManufacturername("Philips"); response.setType("Extended color light"); - response.setModelid("LCT010"); - response.setSwversion("1.15.2_r19181"); - response.setSwconfigid("F921C859"); - response.setProductid("Philips-LCT010-1-A19ECLv4"); + response.setModelid("LCT015"); + response.setSwversion("1.46.13_r26312"); + response.setSwconfigid("52E3234B"); + response.setProductid("Philips-LCT015-1-A19ECLv5"); + response.setProductName("Hue color lamp"); response.setLuminaireuniqueid(null); From 9887042f4df1517c694475d45434283414fafbff Mon Sep 17 00:00:00 2001 From: Marco Pollacci Date: Fri, 6 Nov 2020 10:26:00 +0100 Subject: [PATCH 12/19] Update DeviceRepository.java --- .../java/com/bwssystems/HABridge/dao/DeviceRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java index 13a7dadb..5526a49e 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java @@ -188,7 +188,7 @@ public void save(DeviceDescriptor[] descriptors) { nextId++; } if (descriptors[i].getUniqueid() == null || descriptors[i].getUniqueid().length() == 0) { - descriptors[i].setUniqueid("00:17:88:5E:D3:" + hueUniqueId(Integer.valueOf(descriptors[i].getId()))); + descriptors[i].setUniqueid("00:11:22:33:44:55:66:" + hueUniqueId(Integer.valueOf(descriptors[i].getId()))); } put(descriptors[i].getId(), descriptors[i]); theNames = theNames + " " + descriptors[i].getName() + ", "; @@ -228,7 +228,7 @@ public void renumber() { } } theDevice.setId(String.valueOf(nextId)); - theDevice.setUniqueid("00:17:88:5E:D3:" + hueUniqueId(nextId)); + theDevice.setUniqueid("00:11:22:33:44:55:66:" + hueUniqueId(nextId)); nextId++; } newdevices.put(theDevice.getId(), theDevice); @@ -320,4 +320,4 @@ else if (newValue > 255) return theUniqueId; } -} \ No newline at end of file +} From f9e9f16756d408a3d9c90f0147e53059d545013f Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Mon, 9 Nov 2020 14:20:53 -0500 Subject: [PATCH 13/19] Update Readme for RC2 --- .gitignore | 1 + README.md | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c27a1e64..3f00330d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ data /.settings/ /start.bat /.classpath +/.project sftp-config\.json /bin/ diff --git a/README.md b/README.md index c4c9bf67..0afefd2f 100644 --- a/README.md +++ b/README.md @@ -57,20 +57,20 @@ Then locate the jar and start the server with: ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below. ``` -java -jar ha-bridge-5.3.1RC1.jar +java -jar ha-bridge-5.3.1RC2.jar ``` ## Manual installation of ha-bridge and setup of systemd service Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services. Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units -Create the directory and make sure that ha-bridge-5.3.1RC1.jar is in your /home/pi/ha-bridge directory. +Create the directory and make sure that ha-bridge-5.3.1RC2.jar is in your /home/pi/ha-bridge directory. ``` pi@raspberrypi:~ $ mkdir ha-bridge pi@raspberrypi:~ $ cd ha-bridge -pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC1/ha-bridge-5.3.1RC1.jar +pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC2/ha-bridge-5.3.1RC2.jar ``` Create the ha-bridge.service unit file: @@ -89,7 +89,7 @@ After=network.target Type=simple WorkingDirectory=/home/pi/ha-bridge -ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC1.jar +ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC2.jar [Install] WantedBy=multi-user.target From add9617a0796ce557d465536bbd1e11a52f4d6de Mon Sep 17 00:00:00 2001 From: bwssystems Date: Thu, 19 Nov 2020 12:09:13 -0600 Subject: [PATCH 14/19] Fix command line config path issue When the command line config path is used and the ocnfig file has no entry for configfile a null pointer exception occurrs --- .gitignore | 3 ++- src/main/java/com/bwssystems/HABridge/BridgeSettings.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3f00330d..2df1baa3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ sftp-config\.json # dependencies /node_modules -package-lock.json \ No newline at end of file +package-lock.json +.project diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java index e12dc454..f2026a84 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java @@ -250,10 +250,10 @@ private void _loadConfig(Path aPath) { return; try { theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class); + theBridgeSettings.setConfigfile(aPath.toString()); } catch (Exception e) { log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed."); theBridgeSettings = new BridgeSettingsDescriptor(); - theBridgeSettings.setConfigfile(aPath.toString()); } } From c98513c365fbefcfcf6bc7cf18fa46d88e6a11ab Mon Sep 17 00:00:00 2001 From: bwssystems Date: Thu, 19 Nov 2020 12:16:08 -0600 Subject: [PATCH 15/19] remove .project file --- .project | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .project diff --git a/.project b/.project deleted file mode 100644 index 3830182d..00000000 --- a/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - ha-bridge - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - From 969ed352f7a04f1603d284b177e244861aa43a63 Mon Sep 17 00:00:00 2001 From: bwssystems Date: Tue, 1 Dec 2020 19:32:42 -0600 Subject: [PATCH 16/19] Update handling of upnp and updated how unique ids are generated --- README.md | 8 +- pom.xml | 6 +- .../bwssystems/HABridge/BridgeSettings.java | 2 +- .../HABridge/BridgeSettingsDescriptor.java | 13 +++ .../HABridge/api/hue/HueConstants.java | 2 +- .../HABridge/dao/DeviceRepository.java | 71 ++++++++----- .../HABridge/upnp/UpnpListener.java | 99 +++++++++++-------- .../HABridge/upnp/UpnpSettingsResource.java | 24 +++-- src/main/resources/public/views/system.html | 5 + 9 files changed, 148 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 0afefd2f..7f6eea28 100644 --- a/README.md +++ b/README.md @@ -57,20 +57,20 @@ Then locate the jar and start the server with: ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below. ``` -java -jar ha-bridge-5.3.1RC2.jar +java -jar ha-bridge-5.3.1RC3.jar ``` ## Manual installation of ha-bridge and setup of systemd service Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services. Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units -Create the directory and make sure that ha-bridge-5.3.1RC2.jar is in your /home/pi/ha-bridge directory. +Create the directory and make sure that ha-bridge-5.3.1RC3.jar is in your /home/pi/ha-bridge directory. ``` pi@raspberrypi:~ $ mkdir ha-bridge pi@raspberrypi:~ $ cd ha-bridge -pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC2/ha-bridge-5.3.1RC2.jar +pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC3/ha-bridge-5.3.1RC3.jar ``` Create the ha-bridge.service unit file: @@ -89,7 +89,7 @@ After=network.target Type=simple WorkingDirectory=/home/pi/ha-bridge -ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC2.jar +ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC3.jar [Install] WantedBy=multi-user.target diff --git a/pom.xml b/pom.xml index 4a110a5a..527f7453 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.3.1RC1 + 5.3.1RC3 jar HA Bridge @@ -172,7 +172,7 @@ - + 3.6 @@ -184,7 +184,7 @@ maven-compiler-plugin 3.8.1 - + 11 diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java index f2026a84..1af0d295 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java @@ -252,7 +252,7 @@ private void _loadConfig(Path aPath) { theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class); theBridgeSettings.setConfigfile(aPath.toString()); } catch (Exception e) { - log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed."); + log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed. Using default settings."); theBridgeSettings = new BridgeSettingsDescriptor(); } } diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java b/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java index 31f4623e..e7140385 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java @@ -132,6 +132,9 @@ public class BridgeSettingsDescriptor { @SerializedName("haaddressessecured") @Expose private boolean haaddressessecured; + @SerializedName("upnpadvanced") + @Expose + private boolean upnpadvanced; // @SerializedName("activeloggers") // @Expose // private List activeloggers; @@ -192,6 +195,8 @@ public BridgeSettingsDescriptor() { this.upnporiginal = false; this.seedid = 100; this.haaddressessecured = false; + this.configfile = Configuration.CONFIG_FILE; + this.upnpadvanced = false; } public String getUpnpConfigAddress() { @@ -847,4 +852,12 @@ public boolean isHaaddressessecured() { public void setHaaddressessecured(boolean haaddressessecured) { this.haaddressessecured = haaddressessecured; } + + public boolean isUpnpadvanced() { + return upnpadvanced; + } + + public void setUpnpadvanced(boolean upnpadvanced) { + this.upnpadvanced = upnpadvanced; + } } diff --git a/src/main/java/com/bwssystems/HABridge/api/hue/HueConstants.java b/src/main/java/com/bwssystems/HABridge/api/hue/HueConstants.java index 72f136dd..2782d46b 100644 --- a/src/main/java/com/bwssystems/HABridge/api/hue/HueConstants.java +++ b/src/main/java/com/bwssystems/HABridge/api/hue/HueConstants.java @@ -2,7 +2,7 @@ public class HueConstants { public final static String HUB_VERSION = "9999999999"; - public final static String API_VERSION = "1.19.0"; + public final static String API_VERSION = "1.17.0"; public final static String MODEL_ID = "BSB002"; public final static String UUID_PREFIX = "2f402f80-da50-11e1-9b23-"; } diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java index 5526a49e..15b2afba 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceRepository.java @@ -30,6 +30,8 @@ import java.util.Collection; import java.util.List; import java.util.Arrays; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; /* * This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later @@ -188,7 +190,7 @@ public void save(DeviceDescriptor[] descriptors) { nextId++; } if (descriptors[i].getUniqueid() == null || descriptors[i].getUniqueid().length() == 0) { - descriptors[i].setUniqueid("00:11:22:33:44:55:66:" + hueUniqueId(Integer.valueOf(descriptors[i].getId()))); + descriptors[i].setUniqueid(hueUniqueId(Integer.valueOf(descriptors[i].getId()))); } put(descriptors[i].getId(), descriptors[i]); theNames = theNames + " " + descriptors[i].getName() + ", "; @@ -206,11 +208,10 @@ public void renumber() { DeviceDescriptor theDevice; boolean findNext = true; - nextId = seedId; - while(deviceIterator.hasNext()) { + while (deviceIterator.hasNext()) { theDevice = deviceIterator.next(); - if(theDevice.isLockDeviceId()) { + if (theDevice.isLockDeviceId()) { lockedIds.add(theDevice.getId()); } } @@ -220,15 +221,15 @@ public void renumber() { theDevice = deviceIterator.next(); if (!theDevice.isLockDeviceId()) { findNext = true; - while(findNext) { - if(lockedIds.contains(String.valueOf(nextId))) { + while (findNext) { + if (lockedIds.contains(String.valueOf(nextId))) { nextId++; } else { findNext = false; } } theDevice.setId(String.valueOf(nextId)); - theDevice.setUniqueid("00:11:22:33:44:55:66:" + hueUniqueId(nextId)); + theDevice.setUniqueid(hueUniqueId(nextId)); nextId++; } newdevices.put(theDevice.getId(), theDevice); @@ -297,27 +298,51 @@ private String repositoryReader(Path filePath) { } private String hueUniqueId(Integer anId) { - String theUniqueId; + String theUniqueId = null; Integer newValue; String hexValueLeft; String hexValueRight; - newValue = anId % 256; - if (newValue <= 0) - newValue = 1; - else if (newValue > 255) - newValue = 255; - hexValueLeft = HexLibrary.byteToHex(newValue.byteValue()); - newValue = anId / 256; - newValue = newValue % 256; - if (newValue < 0) - newValue = 0; - else if (newValue > 255) - newValue = 255; - hexValueRight = HexLibrary.byteToHex(newValue.byteValue()); - - theUniqueId = String.format("%s-%s", hexValueLeft, hexValueRight).toUpperCase(); + MessageDigest md = null; + + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + log.warn("Cannot get MD5 utility to hash unique ids."); + } + + if(md != null) { + md.update(anId.toString().getBytes()); + byte[] digest = md.digest(); + theUniqueId = String.format("%s:%s:%s:%s:%s:%s:%s-%s", + HexLibrary.encodeHexString(digest).substring(0, 2), + HexLibrary.encodeHexString(digest).substring(2, 4), + HexLibrary.encodeHexString(digest).substring(4, 6), + HexLibrary.encodeHexString(digest).substring(6, 8), + HexLibrary.encodeHexString(digest).substring(8, 10), + HexLibrary.encodeHexString(digest).substring(10, 12), + HexLibrary.encodeHexString(digest).substring(12, 14), + HexLibrary.encodeHexString(digest).substring(14, 16)); + } + + if(theUniqueId == null) { + newValue = anId % 256; + if (newValue <= 0) + newValue = 1; + else if (newValue > 255) + newValue = 255; + hexValueLeft = HexLibrary.byteToHex(newValue.byteValue()); + newValue = anId / 256; + newValue = newValue % 256; + if (newValue < 0) + newValue = 0; + else if (newValue > 255) + newValue = 255; + hexValueRight = HexLibrary.byteToHex(newValue.byteValue()); + + theUniqueId = String.format("11:22:33:44:55:66:%s-%s", hexValueLeft, hexValueRight).toUpperCase(); + } return theUniqueId; } } diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java index a2ab7a04..da85b41c 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpListener.java @@ -30,6 +30,7 @@ public class UpnpListener { private String upnpConfigIP; // private boolean strict; private boolean upnpOriginal; + private boolean upnpAdvanced; private boolean traceupnp; private boolean useUpnpIface; private BridgeControlDescriptor bridgeControl; @@ -38,31 +39,37 @@ public class UpnpListener { private String httpType; private HuePublicConfig aHueConfig; private Integer theUpnpSendDelay; + + /* This is the minimum response needed, all others are for the advanced setting */ private String responseTemplate1 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" - + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" - + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:" - + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; + + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n" + + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; + + /* These next 2 templates are for the advanced upnp option */ private String responseTemplate2 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" - + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/" + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; private String responseTemplate3 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" - + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" - + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n" - + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; + + "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/" + + HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:" + + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; + + /* These notify templates are for the advanced upnp option */ private String notifyTemplate1 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" - + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/" + HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + "NT: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; private String notifyTemplate2 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" - + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/" + HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + "NT: upnp:rootdevice\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n"; private String notifyTemplate3 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n" - + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/" + + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/" + HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + "NT: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n"; @@ -75,6 +82,7 @@ public UpnpListener(BridgeSettings theSettings, BridgeControlDescriptor theContr upnpConfigIP = theSettings.getBridgeSettingsDescriptor().getUpnpConfigAddress(); // strict = theSettings.isUpnpStrict(); upnpOriginal = theSettings.getBridgeSettingsDescriptor().isUpnporiginal(); + upnpAdvanced = theSettings.getBridgeSettingsDescriptor().isUpnpadvanced(); traceupnp = theSettings.getBridgeSettingsDescriptor().isTraceupnp(); useUpnpIface = theSettings.getBridgeSettingsDescriptor().isUseupnpiface(); theUpnpSendDelay = theSettings.getBridgeSettingsDescriptor().getUpnpsenddelay(); @@ -192,10 +200,13 @@ public boolean startListening() { log.info("UPNP Discovery Listener running and ready...."); boolean loopControl = true; boolean error = false; - try { - upnpMulticastSocket.setSoTimeout((int) Configuration.UPNP_NOTIFY_TIMEOUT); - } catch (SocketException e1) { - log.warn("Could not sent soTimeout on multi-cast socket"); + + if(upnpAdvanced) { + try { + upnpMulticastSocket.setSoTimeout((int) Configuration.UPNP_NOTIFY_TIMEOUT); + } catch (SocketException e1) { + log.warn("Could not sent soTimeout on multi-cast socket"); + } } // Instant current, previous; // previous = Instant.now(); @@ -326,37 +337,39 @@ protected void sendUpnpResponse(DatagramPacket aPacket) throws IOException { + " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>"); sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); - try { - Thread.sleep(theUpnpSendDelay); - } catch (InterruptedException e) { - // noop - } - discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS, - Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, - bridgeSNUUID, bridgeSNUUID); - if (traceupnp) { - log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":" - + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); - } - log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort - + " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>"); - sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); + if(upnpAdvanced) { + try { + Thread.sleep(theUpnpSendDelay); + } catch (InterruptedException e) { + // noop + } + discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS, + Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, + bridgeSNUUID, bridgeSNUUID); + if (traceupnp) { + log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":" + + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); + } + log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort + + " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>"); + sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); - try { - Thread.sleep(theUpnpSendDelay); - } catch (InterruptedException e) { - // noop - } - discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS, - Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, - bridgeSNUUID); - if (traceupnp) { - log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":" - + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); + try { + Thread.sleep(theUpnpSendDelay); + } catch (InterruptedException e) { + // noop + } + discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS, + Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId, + bridgeSNUUID); + if (traceupnp) { + log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":" + + httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort); + } + log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort + + " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>"); + sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); } - log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort - + " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>"); - sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort); } private void sendUDPResponse(byte[] udpMessage, InetAddress requester, int sourcePort) throws IOException { diff --git a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java index 58c692db..57664f88 100644 --- a/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java +++ b/src/main/java/com/bwssystems/HABridge/upnp/UpnpSettingsResource.java @@ -55,10 +55,12 @@ public class UpnpSettingsResource { + "24\n" + "hue_logo_3.png\n" + "\n" - + "\n" - + "\n" - + "\n"; + + "\n"; + private String hueTemplate_end = "\n" + + "\n"; + + /* not utilizing this section any more private String hueTemplate_mid_orig = "\n" + "\n" + "(null)\n" @@ -68,7 +70,7 @@ public class UpnpSettingsResource { + "(null)\n" + "\n" + "\n"; - + */ public UpnpSettingsResource(BridgeSettings theBridgeSettings) { super(); @@ -92,16 +94,24 @@ public void setupServer() { String hueTemplate = null; if(theSettings.isUpnporiginal()) { httpLocationAddr = theSettings.getUpnpConfigAddress(); - hueTemplate = hueTemplate_pre + hueTemplate_mid_orig + hueTemplate_post; + hueTemplate = hueTemplate_pre + hueTemplate_end; + } else if(!theSettings.isUpnpadvanced()) { + if(theSettings.isUseupnpiface()) { + httpLocationAddr = theSettings.getUpnpConfigAddress(); + } else { + log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port()); + httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress(); + } + hueTemplate = hueTemplate_pre + hueTemplate_end; } else { - + if(theSettings.isUseupnpiface()) { httpLocationAddr = theSettings.getUpnpConfigAddress(); } else { log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port()); httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress(); } - hueTemplate = hueTemplate_pre + hueTemplate_post; + hueTemplate = hueTemplate_pre + hueTemplate_post + hueTemplate_end; } String bridgeIdMac = HuePublicConfig.createConfig("temp", httpLocationAddr, HueConstants.HUB_VERSION, theSettings.getHubmac()).getSNUUIDFromMac(); diff --git a/src/main/resources/public/views/system.html b/src/main/resources/public/views/system.html index e0397a98..34f16b41 100644 --- a/src/main/resources/public/views/system.html +++ b/src/main/resources/public/views/system.html @@ -824,6 +824,11 @@

Bridge Settings

{{bridge.settings.upnporiginal}} + + UPNP Advanced (use multiple responses and notifies) + {{bridge.settings.upnpadvanced}} + Trace UPNP Calls Date: Mon, 7 Dec 2020 18:07:42 -0600 Subject: [PATCH 17/19] Fix all color handling and dim with no on request --- pom.xml | 2 +- .../HABridge/dao/DeviceDescriptor.java | 13 +- .../HABridge/hue/ColorConverter.java | 127 +++++---- .../bwssystems/HABridge/hue/ColorDecode.java | 245 +++++++++++++----- .../bwssystems/HABridge/hue/HueMulator.java | 36 ++- src/main/resources/public/scripts/app.js | 58 +++-- .../resources/public/views/editdevice.html | 6 + .../color/test/ConvertCIEColorTestCase.java | 68 ++++- 8 files changed, 383 insertions(+), 172 deletions(-) diff --git a/pom.xml b/pom.xml index 527f7453..0fd9cc19 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.3.1RC3 + 5.3.1RC4-java11 jar HA Bridge diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java index d24d5632..b52e99f6 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java @@ -92,7 +92,10 @@ public class DeviceDescriptor{ @SerializedName("startupActions") @Expose private String startupActions; - + @SerializedName("dimNoOn") + @Expose + private boolean dimNoOn; + public String getName() { return name; } @@ -355,4 +358,12 @@ public String getStartupActions() { public void setStartupActions(String startupActions) { this.startupActions = startupActions; } + + public boolean isDimNoOn() { + return dimNoOn; + } + + public void setDimNoOn(boolean dimNoOn) { + this.dimNoOn = dimNoOn; + } } \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java index faa089f2..769a2b51 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java @@ -18,6 +18,8 @@ * XYZ -> CIE-LAB -> XYZ * @author Diego Catalano */ + + public class ColorConverter { /** @@ -26,6 +28,7 @@ public class ColorConverter { private ColorConverter() {} public static enum YCbCrColorSpace {ITU_BT_601,ITU_BT_709_HDTV}; + private final static double EPSILON = 0.00001; // XYZ (Tristimulus) Reference values of a perfect reflecting diffuser @@ -259,7 +262,7 @@ public static int[] YCbCrtoRGB(float y, float cb, float cr, YCbCrColorSpace colo * @param blue Blue coefficient. * @return Normalized RGChromaticity. Range[0..1]. */ - public static double[] RGChromaticity(int red, int green, int blue){ + public static float[] RGChromaticity(int red, int green, int blue){ double[] color = new double[5]; double sum = red + green + blue; @@ -276,24 +279,7 @@ public static double[] RGChromaticity(int red, int green, int blue){ double rS = color[0] - 0.333; double gS = color[1] - 0.333; - //saturation - color[3] = Math.sqrt(rS * rS + gS * gS); - - //hue - color[4] = Math.atan(rS / gS); - - return color; - } - - /** - * RGB -> HSV. - * Adds (hue + 360) % 360 for represent hue in the range [0..359]. - * @param red Red coefficient. Values in the range [0..255]. - * @param green Green coefficient. Values in the range [0..255]. - * @param blue Blue coefficient. Values in the range [0..255]. - * @return HSV color space. - */ - public static float[] RGBtoHSV(int red, int green, int blue){ + //saturationBRGBtoHSV(int red, int green, int blue){ float[] hsv = new float[3]; float r = red / 255f; float g = green / 255f; @@ -630,69 +616,69 @@ public static float[] RGBtoHunterLAB(int red, int green, int blue){ public static int[] HunterLABtoRGB(float l, float a, float b){ float[] xyz = HunterLABtoXYZ(l, a, b); return XYZtoRGB(xyz[0], xyz[1], xyz[2]); - } + } /** - * RGB -> HLS. + * RGB -> HSL. * @param red Red coefficient. Values in the range [0..255]. * @param green Green coefficient. Values in the range [0..255]. * @param blue Blue coefficient. Values in the range [0..255]. - * @return HLS color space. + * @return HSL color space. */ - public static float[] RGBtoHLS(int red, int green, int blue){ + public static float[] RGBtoHSL(int red, int green, int blue){ float[] hsl = new float[3]; - float r = red / 255f; - float g = green / 255f; - float b = blue / 255f; + double r = red; + double g = green; + double b = blue; - float max = Math.max(r,Math.max(r,b)); - float min = Math.min(r,Math.min(r,b)); - float delta = max - min; + double max = Math.max(r,Math.max(g,b)); + double min = Math.min(r,Math.min(g,b)); +// double delta = max - min; //HSK - float h = 0; - float s = 0; - float l = (max + min) / 2; - - if ( delta == 0 ){ - // gray color - h = 0; - s = 0.0f; - } - else - { - // get saturation value - s = ( l <= 0.5 ) ? ( delta / ( max + min ) ) : ( delta / ( 2 - max - min ) ); - - // get hue value - float hue; - - if ( r == max ) - { - hue = ( ( g - b ) / 6 ) / delta; + Double h = 0d; + Double s = 0d; + Double l = 0d; + + //saturation + double cnt = (max + min) / 2d; + if (cnt <= 127d) { + s = ((max - min) / (max + min)); } - else if ( g == max ) - { - hue = ( 1.0f / 3 ) + ( ( b - r ) / 6 ) / delta; + else { + s = ((max - min) / (510d - max - min)); } - else - { - hue = ( 2.0f / 3 ) + ( ( r - g ) / 6 ) / delta; + + //lightness + l = ((max + min) / 2d) / 255d; + + //hue + if (Math.abs(max - min) <= EPSILON) { + h = 0d; + s = 0d; } + else { + double diff = max - min; - // correct hue if needed - if ( hue < 0 ) - hue += 1; - if ( hue > 1 ) - hue -= 1; + if (Math.abs(max - r) <= EPSILON) { + h = 60d * (g - b) / diff; + } + else if (Math.abs(max - g) <= EPSILON) { + h = 60d * (b - r) / diff + 120d; + } + else { + h = 60d * (r - g) / diff + 240d; + } - h = (int) ( hue * 360 ); - } + if (h < 0d) { + h += 360d; + } + } - hsl[0] = h; - hsl[1] = s; - hsl[2] = l; + hsl[0] = h.floatValue(); + hsl[1] = s.floatValue(); + hsl[2] = l.floatValue(); return hsl; } @@ -931,14 +917,19 @@ public static XYColorSpace XYZtoXY(float x, float y, float z){ */ public static float[] XYtoXYZ(XYColorSpace xy){ float[] xyz = new float[3]; - + /* Old Way xyz[0] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * xy.getXy()[0]; xyz[1] = xy.getBrightnessAdjusted(); xyz[2] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * (1.0f - xy.getXy()[0] - xy.getXy()[1]); - + */ + // New Way + xyz[0] = xy.getXy()[0] * (xy.getBrightnessAdjusted() / xy.getXy()[1]) ; + xyz[1] = xy.getBrightnessAdjusted(); + xyz[2] = (float) ((1.0 - xy.getXy()[0] - xy.getXy()[1]) * (xy.getBrightnessAdjusted() / xy.getXy()[1])); + return xyz; } - + public static int[] normalizeRGB(int[] rgb) { int[] newRGB = new int[3]; diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java index 1284b8ed..98550361 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -4,12 +4,12 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.awt.Color; +// import java.awt.Color; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.bwssystems.HABridge.hue.ColorData; +// import com.bwssystems.HABridge.hue.ColorData; public class ColorDecode { private static final Logger log = LoggerFactory.getLogger(ColorDecode.class); @@ -27,75 +27,150 @@ public class ColorDecode { private static final String COLOR_BRI = "${colorbri}"; private static final Pattern COLOR_MILIGHT = Pattern.compile("\\$\\{color.milight\\:([01234])\\}"); - public static List convertHSBtoRGB(HueSatBri hsb) { + + /* This is supersceded by the next iteration function below this original one + public static List convertHSBtoRGBOrig(HueSatBri hsb) { List rgb; Float hue = (Float)(hsb.getHue()*1.0f); Float saturation = (Float)(hsb.getSat()*1.0f); Float brightness = (Float)(hsb.getBri()*1.0f); log.info("Hue = " + hue + ", Sat = " + saturation + ", Bri = " + brightness); //Convert Hue into degrees for HSB - hue = hue / 182.04f; + // hue = hue / 182.04f; + hue = (hue / 65535.0f) * 360.0f; //Bri and Sat must be values from 0-1 (~percentage) - brightness = brightness / 255.0f; - saturation = saturation / 255.0f; - + // ightness = brightness / 255.0f; + // saturation = saturation / 255.0f; + + brightness = brightness / 254.0f; + saturation = saturation / 254.0f; + Float r = 0f; Float g = 0f; Float b = 0f; - - if (saturation == 0) + + if(brightness > 0.0f) { + if (saturation == 0) + { + r = g = b = brightness; + } + else + { + // the color wheel consists of 6 sectors. + Float sectorPos = hue / 60.0f; + int sectorNumber = (int)(Math.floor(sectorPos)); + // get the fractional part of the sector + Float fractionalSector = sectorPos - sectorNumber; + + // calculate values for the three axes of the color. + Float p = brightness * (1.0f - saturation); + Float q = brightness * (1.0f - (saturation * fractionalSector)); + Float t = brightness * (1.0f - (saturation * (1f - fractionalSector))); + + // assign the fractional colors to r, g, and b based on the sector the angle is in. + switch (sectorNumber) + { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + case 5: + r = brightness; + g = p; + b = q; + break; + } + } + } + + //Check if any value is out of byte range + if (r < 0f) { - r = g = b = brightness; + r = 0f; } - else + if (g < 0f) { - // the color wheel consists of 6 sectors. - Float sectorPos = hue / 60.0f; - int sectorNumber = (int)(Math.floor(sectorPos)); - // get the fractional part of the sector - Float fractionalSector = sectorPos - sectorNumber; - - // calculate values for the three axes of the color. - Float p = brightness * (1.0f - saturation); - Float q = brightness * (1.0f - (saturation * fractionalSector)); - Float t = brightness * (1.0f - (saturation * (1f - fractionalSector))); - - // assign the fractional colors to r, g, and b based on the sector the angle is in. - switch (sectorNumber) - { - case 0: - r = brightness; - g = t; - b = p; - break; - case 1: - r = q; - g = brightness; - b = p; - break; - case 2: - r = p; - g = brightness; - b = t; - break; - case 3: - r = p; - g = q; - b = brightness; - break; - case 4: - r = t; - g = p; - b = brightness; - break; - case 5: - r = brightness; - g = p; - b = q; - break; - } + g = 0f; + } + if (b < 0f) + { + b = 0f; } + rgb = new ArrayList(); + rgb.add((int)Math.round(r*255)); + rgb.add((int)Math.round(g*255)); + rgb.add((int)Math.round(b*255)); + log.info("Color change with HSB: " + hsb + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + + rgb.get(2)); + + int theRGB = Color.HSBtoRGB(hue, saturation, brightness); + Color decodedRGB = new Color(theRGB); + log.info("Color change with HSB using java Color: " + hsb + ". Resulting RGB Values: " + decodedRGB.getRed() + " " + decodedRGB.getGreen() + " " + + decodedRGB.getBlue()); + + return rgb; + } + */ + public static List convertHSBtoRGB(HueSatBri hsb) { + List rgb; + Float hue = (Float)(hsb.getHue()*1.0f); + Float saturation = (Float)(hsb.getSat()*1.0f); + Float brightness = (Float)(hsb.getBri()*1.0f); + log.info("Hue = " + hue + ", Sat = " + saturation + ", Bri = " + brightness); + //Convert Hue into degrees for HSB + // hue = hue / 182.04f; + hue = (hue / 65535.0f); + //Bri and Sat must be values from 0-1 (~percentage) + // ightness = brightness / 255.0f; + // saturation = saturation / 255.0f; + + brightness = brightness / 254.0f; + saturation = saturation / 254.0f; + + Float r = 0f; + Float g = 0f; + Float b = 0f; + Float temp2 = 0f; + Float temp1 = 0f; + + if(brightness > 0.0f) { + if (saturation == 0) + { + r = g = b = brightness; + } + else + { + temp2 = (brightness < 0.5f) ? brightness * (1.0f + saturation) : brightness + saturation - (brightness * saturation); + temp1 = 2.0f * brightness - temp2; + + r = GetColorComponent(temp1, temp2, hue + 1.0f/3.0f); + g = GetColorComponent(temp1, temp2, hue); + b = GetColorComponent(temp1, temp2, hue - 1.0f/3.0f); + } + } + //Check if any value is out of byte range if (r < 0f) { @@ -114,11 +189,40 @@ public static List convertHSBtoRGB(HueSatBri hsb) { rgb.add((int)Math.round(r*255)); rgb.add((int)Math.round(g*255)); rgb.add((int)Math.round(b*255)); - log.debug("Color change with HSB: " + hsb + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + log.debug("Color change with HSB New: " + hsb + ". Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.get(2)); + return rgb; } + private static Float GetColorComponent(Float temp1, Float temp2, Float temp3) + { + temp3 = MoveIntoRange(temp3); + if (temp3 < 1.0f/6.0f) + { + return temp1 + (temp2 - temp1) * 6.0f * temp3; + } + + if (temp3 < 0.5f) + { + return temp2; + } + + if (temp3 < 2.0f/3.0f) + { + return temp1 + ((temp2 - temp1) * ((2.0f/3.0f) - temp3) * 6.0f); + } + + return temp1; + } + + private static Float MoveIntoRange(Float temp3) + { + if (temp3 < 0.0f) return temp3 + 1f; + if (temp3 > 1.0f) return temp3 - 1f; + return temp3; + } + public static List convertCIEtoRGB(List xy, int brightness) { List rgb; XYColorSpace xyColor = new XYColorSpace(); @@ -133,7 +237,7 @@ public static List convertCIEtoRGB(List xy, int brightness) { rgb.add(rgbInt[0]); rgb.add(rgbInt[1]); rgb.add(rgbInt[2]); - log.debug("Color change with XY: " + xy.get(0) + " " + xy.get(1) + " Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + log.debug("Color change with XY: " + xy.get(0) + " " + xy.get(1) + " " + brightness + " Resulting RGB Values: " + rgb.get(0) + " " + rgb.get(1) + " " + rgb.get(2)); return rgb; } @@ -179,10 +283,10 @@ public static List convertCTtoRGB(Integer ct) { private static double assureBounds(double value) { if (value < 0.0) { - value = 0; + value = 0.0; } if (value > 255.0) { - value = 255; + value = 255.0; } return value; } @@ -252,8 +356,9 @@ public static String replaceColorData(String request, ColorData colorData, int s List xyData = (List) colorData.getData(); request = request.replace(COLOR_XY, String.format("%f,%f", xyData.get(0), xyData.get(1))); } else { - List xyData = (List) colorData.getData(); - request = request.replace(COLOR_XY, String.format("%f,%f", xyData.get(0), xyData.get(1))); + float[] xyz = ColorConverter.RGBtoXYZ(rgb.get(0), rgb.get(1), rgb.get(2)); + XYColorSpace theXYcolor = ColorConverter.XYZtoXY(xyz[0], xyz[1], xyz[2]); + request = request.replace(COLOR_XY, String.format("%f,%f",theXYcolor.getXy()[0], theXYcolor.getXy()[1])); } notDone = true; } @@ -263,9 +368,9 @@ public static String replaceColorData(String request, ColorData colorData, int s HueSatBri hslData = (HueSatBri) colorData.getData(); request = request.replace(COLOR_H, String.format("%d", hslData.getHue())); } else { - float[] hsb = new float[3]; - Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); - float hue = hsb[0] * (float) 360.0; + float[] hsb; + hsb = ColorConverter.RGBtoHSL(rgb.get(0), rgb.get(1), rgb.get(2)); + float hue = hsb[0]; request = request.replace(COLOR_H, String.format("%f", hue)); } notDone = true; @@ -276,8 +381,8 @@ public static String replaceColorData(String request, ColorData colorData, int s HueSatBri hslData = (HueSatBri) colorData.getData(); request = request.replace(COLOR_S, String.format("%d", hslData.getSat())); } else { - float[] hsb = new float[3]; - Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); + float[] hsb; + hsb = ColorConverter.RGBtoHSL(rgb.get(0), rgb.get(1), rgb.get(2)); float sat = hsb[1] * (float) 100.0; request = request.replace(COLOR_S, String.format("%f", sat)); } @@ -289,7 +394,7 @@ public static String replaceColorData(String request, ColorData colorData, int s HueSatBri hslData = (HueSatBri) colorData.getData(); request = request.replace(COLOR_BRI, String.format("%d", hslData.getBri())); } else { - request = request.replace(COLOR_BRI, String.format("%f", setIntensity)); + request = request.replace(COLOR_BRI, String.format("%d", setIntensity)); } notDone = true; } @@ -301,8 +406,8 @@ public static String replaceColorData(String request, ColorData colorData, int s String.format("%d,%d,%d", hslData.getHue(), hslData.getSat(), hslData.getBri())); } else { float[] hsb = new float[3]; - Color.RGBtoHSB(rgb.get(0), rgb.get(1), rgb.get(2), hsb); - float hue = hsb[0] * (float) 360.0; + hsb = ColorConverter.RGBtoHSL(rgb.get(0), rgb.get(1), rgb.get(2)); + float hue = hsb[0]; float sat = hsb[1] * (float) 100.0; float bright = hsb[2] * (float) 100.0; request = request.replace(COLOR_HSB, String.format("%f,%f,%f", hue, sat, bright)); diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 254708f6..a136a388 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -1221,12 +1221,41 @@ private String changeState(String userId, String lightId, String body, String ip isOnRequest = true; } + if(device.isOnFirstDim()) { + if(isDimRequest && !device.getDeviceState().isOn()) { + isOnRequest = true; + theStateChanges.setOn(true); + // isDimRequest = false; + // isColorRequest = false; + } else if (isDimRequest && device.getDeviceState().isOn()) { + if (device.getDeviceState().getBri() == theStateChanges.getBri()) { + isOnRequest = true; + theStateChanges.setOn(true); + // isDimRequest = false; + // isColorRequest = false; + } else { + isOnRequest = false; + // isDimRequest = true; + // isColorRequest = false; + } + } + } else if (device.isOnWhenDimPresent()) { + if (isDimRequest) { + isOnRequest = true; + theStateChanges.setOn(true); + } + } else if (device.isDimNoOn()) { + if (isDimRequest && isOnRequest) { + isOnRequest = false; + } + } + + +/* Old code supperceded by the above block if (!device.isOnFirstDim() && device.isOnWhenDimPresent() && isDimRequest && !isOnRequest) { isOnRequest = true; theStateChanges.setOn(true); - } else if (!device.isOnFirstDim() && !device.isOnWhenDimPresent() && isDimRequest) { - // isOnRequest = false; - } + } else if (device.isOnFirstDim() && isDimRequest && !device.getDeviceState().isOn()) { isOnRequest = true; @@ -1245,6 +1274,7 @@ private String changeState(String userId, String lightId, String body, String ip isColorRequest = false; } } +*/ if (isOnRequest) { if (bridgeSettings.isTracestate()) diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index 8cee1e98..a67ea1b1 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -1547,26 +1547,42 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng }; this.toXY = function (red, green, blue) { - //Gamma corrective - red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92); - green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92); - blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92); - - //Apply wide gamut conversion D65 - var X = red * 0.664511 + green * 0.154324 + blue * 0.162028; - var Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; - var Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; - - var fx = X / (X + Y + Z); - var fy = Y / (X + Y + Z); - if (isNaN(fx)) { - fx = 0.0; - } - if (isNaN(fy)) { - fy = 0.0; - } - - return [fx.toPrecision(4), fy.toPrecision(4)]; + var r = red / 255; + var g = green / 255; + var b = blue / 255; + + //R + if ( r > 0.04045) + r = Math.pow(( ( r + 0.055 ) / 1.055 ), 2.4); + else + r /= 12.92; + + //G + if ( g > 0.04045) + g = Math.pow(( ( g + 0.055 ) / 1.055 ), 2.4); + else + g /= 12.92; + + //B + if ( b > 0.04045) + b = Math.pow(( ( b + 0.055 ) / 1.055 ), 2.4); + else + b /= 12.92; + + r *= 100; + g *= 100; + b *= 100; + + var x = 0.412453 * r + 0.35758 * g + 0.180423 * b; + var y = 0.212671 * r + 0.71516 * g + 0.072169 * b; + var z = 0.019334 * r + 0.119193 * g + 0.950227 * b; + + var newX = x / (x + y + z); + var newY = y / (x + y + z); + var interBright = (y / 100) * 254; + var newBright = Math.round(interBright); + + return [newX.toPrecision(6), newY.toPrecision(6), newBright]; }; this.testUrl = function (device, type, value, valueType) { @@ -1592,7 +1608,7 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng if (valueType === "color" && value) { if (addComma) testBody = testBody + ","; - testBody = testBody + "\"xy\": [" + value[0] + "," + value[1] + "]"; + testBody = testBody + "\"xy\": [" + value[0] + "," + value[1] + "],\"bri\":" + value[2]; } testBody = testBody + "}"; if (testBody === "{}") { diff --git a/src/main/resources/public/views/editdevice.html b/src/main/resources/public/views/editdevice.html index 96a0de06..9630f4ae 100644 --- a/src/main/resources/public/views/editdevice.html +++ b/src/main/resources/public/views/editdevice.html @@ -114,6 +114,12 @@

Edit/Copy a device

ng-model="device.onFirstDim" ng-true-value=true ng-false-value=false> {{device.onFirstDim}} + + + {{device.dimNoOn}} + xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); + ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.20821628789344535"), Double.parseDouble("0.22503526273269103"))); XYColorSpace xyColor = new XYColorSpace(); - xyColor.setBrightness(50); + xyColor.setBrightness(56); float[] xyFloat = new float[2]; xyFloat[0] = xy.get(0).floatValue(); xyFloat[1] = xy.get(1).floatValue(); @@ -32,12 +33,29 @@ public void testColorConverterXYtoRGB() { rgbDecode.add(1, rgb[1]); rgbDecode.add(2, rgb[2]); List assertDecode = new ArrayList(); - assertDecode.add(0, 255); - assertDecode.add(1, 0); - assertDecode.add(2, 5); + assertDecode.add(0, 60); + assertDecode.add(1, 134); + assertDecode.add(2, 196); Assert.assertEquals(rgbDecode, assertDecode); } + @Test + public void testColorConverterRGBtoXY() { + int red = 60; + int green = 134; + int blue = 196; + + float[] xyz = ColorConverter.RGBtoXYZ(red, green, blue); + XYColorSpace theColorSpace = ColorConverter.XYZtoXY(xyz[0], xyz[1], xyz[2]); + List xyDecode = new ArrayList(); + xyDecode.add(0, theColorSpace.getXy()[0]); + xyDecode.add(1, theColorSpace.getXy()[1]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 0.20821705f); + assertDecode.add(1, 0.22506176f); + Assert.assertEquals(xyDecode, assertDecode); + } + @Test public void testColorConversionXYtoRGB1() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); @@ -70,6 +88,22 @@ public void testColorConversionXYtoRGB2() { Assert.assertEquals(rgbIntVal, 15270119); } + @Test + public void testColorConversionXYtoRGB3() { + ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.20821628789344535"), Double.parseDouble("0.22503526273269103"))); + + List colorDecode = ColorDecode.convertCIEtoRGB(xy, 56); + List assertDecode = new ArrayList(); + assertDecode.add(0, 60); + assertDecode.add(1, 134); + assertDecode.add(2, 196); + Assert.assertEquals(colorDecode, assertDecode); + +// ColorData colorData = new ColorData(ColorData.ColorMode.XY, xy); +// int rgbIntVal = ColorDecode.getIntRGB(colorData, 56); +// Assert.assertEquals(rgbIntVal, 15270119); + } + @Test public void testColorConversionHSBtoRGB1() { HueSatBri hsb = new HueSatBri(); @@ -79,12 +113,30 @@ public void testColorConversionHSBtoRGB1() { List colorDecode = ColorDecode.convertHSBtoRGB(hsb); List assertDecode = new ArrayList(); - assertDecode.add(0, 60); - assertDecode.add(1, 97); - assertDecode.add(2, 128); + assertDecode.add(0, 61); + assertDecode.add(1, 134); + assertDecode.add(2, 196); Assert.assertEquals(colorDecode, assertDecode); } + @Test + public void testColorConverterRGBtoHSB() { + int red = 61; + int green = 134; + int blue = 196; + + float[] hsl = ColorConverter.RGBtoHSL(red, green, blue); + List hsbDecode = new ArrayList(); + hsbDecode.add(0, (int) (hsl[0] / 360f * 65535f)); + hsbDecode.add(1, (int) (hsl[1] * 254f)); + hsbDecode.add(2, (int) (hsl[2] * 254f)); + List assertDecode = new ArrayList(); + assertDecode.add(0, 37783); + assertDecode.add(1, 135); + assertDecode.add(2, 127); + Assert.assertEquals(hsbDecode, assertDecode); + } + @Test public void testColorConversionCTtoRGB() { Integer ct = 500; From 675e74df7bbaaab5cde0588c2783eedd401fa30b Mon Sep 17 00:00:00 2001 From: bwssystems Date: Tue, 8 Dec 2020 17:03:24 -0600 Subject: [PATCH 18/19] Add switch for dim when color request is present and other items --- README.md | 8 ++-- pom.xml | 2 +- .../bwssystems/HABridge/BridgeSettings.java | 5 ++- .../HABridge/BridgeSettingsDescriptor.java | 13 ++++++ .../bwssystems/HABridge/Configuration.java | 1 + .../bwssystems/HABridge/SystemControl.java | 4 +- .../HABridge/dao/DeviceDescriptor.java | 11 +++++ .../HABridge/hue/BrightnessDecode.java | 40 ++++++++++++++++++- .../bwssystems/HABridge/hue/HueMulator.java | 3 ++ .../HABridge/plugins/hass/HassHome.java | 3 +- src/main/resources/public/scripts/app.js | 2 +- .../resources/public/views/editdevice.html | 6 +++ src/main/resources/public/views/system.html | 5 +++ 13 files changed, 90 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7f6eea28..3fab54ab 100644 --- a/README.md +++ b/README.md @@ -57,20 +57,20 @@ Then locate the jar and start the server with: ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below. ``` -java -jar ha-bridge-5.3.1RC3.jar +java -jar ha-bridge-5.3.1RC5.jar ``` ## Manual installation of ha-bridge and setup of systemd service Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services. Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units -Create the directory and make sure that ha-bridge-5.3.1RC3.jar is in your /home/pi/ha-bridge directory. +Create the directory and make sure that ha-bridge-5.3.1RC5.jar is in your /home/pi/ha-bridge directory. ``` pi@raspberrypi:~ $ mkdir ha-bridge pi@raspberrypi:~ $ cd ha-bridge -pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC3/ha-bridge-5.3.1RC3.jar +pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC5/ha-bridge-5.3.1RC5.jar ``` Create the ha-bridge.service unit file: @@ -89,7 +89,7 @@ After=network.target Type=simple WorkingDirectory=/home/pi/ha-bridge -ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC3.jar +ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC5.jar [Install] WantedBy=multi-user.target diff --git a/pom.xml b/pom.xml index 0fd9cc19..9d46c6db 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.3.1RC4-java11 + 5.3.1RC5-java11 jar HA Bridge diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java index 1af0d295..f67e7a0e 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java @@ -200,7 +200,10 @@ public void buildSettings() { theBridgeSettings.setNumberoflogmessages(Integer.valueOf(Configuration.NUMBER_OF_LOG_MESSAGES)); if(theBridgeSettings.getButtonsleep() == null || theBridgeSettings.getButtonsleep() < 0) - theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP)); + theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP)); + + if(theBridgeSettings.getLinkbuttontimeout() < 30) + theBridgeSettings.setLinkbuttontimeout(Configuration.LINK_BUTTON_TIMEOUT); theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera()); theBridgeSettings.setFibaroconfigured(theBridgeSettings.isValidFibaro()); diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java b/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java index e7140385..a5727689 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettingsDescriptor.java @@ -135,6 +135,10 @@ public class BridgeSettingsDescriptor { @SerializedName("upnpadvanced") @Expose private boolean upnpadvanced; + @SerializedName("linkbuttontimeout") + @Expose + private Integer linkbuttontimeout; + // @SerializedName("activeloggers") // @Expose // private List activeloggers; @@ -197,6 +201,7 @@ public BridgeSettingsDescriptor() { this.haaddressessecured = false; this.configfile = Configuration.CONFIG_FILE; this.upnpadvanced = false; + this.linkbuttontimeout = Configuration.LINK_BUTTON_TIMEOUT; } public String getUpnpConfigAddress() { @@ -860,4 +865,12 @@ public boolean isUpnpadvanced() { public void setUpnpadvanced(boolean upnpadvanced) { this.upnpadvanced = upnpadvanced; } + + public Integer getLinkbuttontimeout() { + return linkbuttontimeout; + } + + public void setLinkbuttontimeout(Integer linkbuttontimeout) { + this.linkbuttontimeout = linkbuttontimeout; + } } diff --git a/src/main/java/com/bwssystems/HABridge/Configuration.java b/src/main/java/com/bwssystems/HABridge/Configuration.java index 7ad5ad8c..c82a4b77 100644 --- a/src/main/java/com/bwssystems/HABridge/Configuration.java +++ b/src/main/java/com/bwssystems/HABridge/Configuration.java @@ -17,4 +17,5 @@ public class Configuration { public static final int UPNP_SEND_DELAY = 650; public static final int BROADLINK_DISCOVER_PORT = 40000; public static final int BROADLINK_DISCONVER_TIMEOUT = 5000; + public static final int LINK_BUTTON_TIMEOUT = 45; } diff --git a/src/main/java/com/bwssystems/HABridge/SystemControl.java b/src/main/java/com/bwssystems/HABridge/SystemControl.java index 1393a0af..f4c4cf93 100644 --- a/src/main/java/com/bwssystems/HABridge/SystemControl.java +++ b/src/main/java/com/bwssystems/HABridge/SystemControl.java @@ -276,12 +276,12 @@ public void setupServer() { if(!request.body().isEmpty()) { linkParams = new Gson().fromJson(request.body(), LinkParams.class); if(linkParams.getSeconds() <= 0) - linkParams.setSeconds(1); + linkParams.setSeconds(3); } else { linkParams = new LinkParams(); linkParams.setSilent(false); - linkParams.setSeconds(30); + linkParams.setSeconds(bridgeSettings.getBridgeSettingsDescriptor().getLinkbuttontimeout()); } if(!linkParams.isSilent()) log.info("Link button pressed...."); diff --git a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java index b52e99f6..4199d2dc 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java @@ -95,6 +95,9 @@ public class DeviceDescriptor{ @SerializedName("dimNoOn") @Expose private boolean dimNoOn; + @SerializedName("dimOnColor") + @Expose + private boolean dimOnColor; public String getName() { return name; @@ -366,4 +369,12 @@ public boolean isDimNoOn() { public void setDimNoOn(boolean dimNoOn) { this.dimNoOn = dimNoOn; } + + public boolean isDimOnColor() { + return dimOnColor; + } + + public void setDimOnColor(boolean dimOnColor) { + this.dimOnColor = dimOnColor; + } } \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java index 276b08a1..ce0a7016 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java @@ -21,6 +21,9 @@ public class BrightnessDecode { 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}"; + private static final String INTENSITY_PREVIOUS_PERCENT = "${intensity.previous_percent}"; + private static final String INTENSITY_PREVIOUS_DECIMAL_PERCENT = "${intensity.previous_decimal_percent}"; + private static final String INTENSITY_PREVIOUS_BYTE = "${intensity.previous_byte}"; public static int calculateIntensity(int setIntensity, Integer targetBri, Integer targetBriInc) { if (targetBri != null) { @@ -45,7 +48,7 @@ else if ((setIntensity + targetBriInc) > 254) * intensity.math(X*1) : where X is the value from the interface call and * can use net.java.dev.eval math */ - public static String replaceIntensityValue(String request, int intensity, boolean isHex) { + private static String replaceIntensityValue(String request, int previous_intensity, int intensity, boolean isHex) { if (request == null) { return null; } @@ -54,6 +57,8 @@ public static String replaceIntensityValue(String request, int intensity, boolea String replaceTarget = null; int percentBrightness = 0; float decimalBrightness = (float) 1.0; + int previousPercentBrightness = 0; + float previousDecimalBrightness = (float) 1.0; Map variables = new HashMap(); String mathDescriptor = null; @@ -68,6 +73,17 @@ public static String replaceIntensityValue(String request, int intensity, boolea percentBrightness = 1; } + if(previous_intensity > 0) { + previousDecimalBrightness = (float) (previous_intensity / 255.0); + if(previous_intensity > 0 && previous_intensity < 5) + previousPercentBrightness = 1; + else + previousPercentBrightness = (int) Math.round(previous_intensity / 255.0 * 100); + } else { + previousDecimalBrightness = (float) 1.0; + previousPercentBrightness = 1; + } + while(notDone) { notDone = false; if (request.contains(INTENSITY_BYTE)) { @@ -78,6 +94,14 @@ public static String replaceIntensityValue(String request, int intensity, boolea } replaceTarget = INTENSITY_BYTE; notDone = true; + } else if (request.contains(INTENSITY_PREVIOUS_BYTE)) { + if (isHex) { + replaceValue = convertToHex(previous_intensity); + } else { + replaceValue = String.valueOf(previous_intensity); + } + replaceTarget = INTENSITY_PREVIOUS_BYTE; + notDone = true; } else if (request.contains(INTENSITY_BYTE_HEX)) { replaceValue = convertToHex(intensity); replaceTarget = INTENSITY_BYTE_HEX; @@ -90,6 +114,14 @@ public static String replaceIntensityValue(String request, int intensity, boolea } replaceTarget = INTENSITY_PERCENT; notDone = true; + } else if (request.contains(INTENSITY_PREVIOUS_PERCENT)) { + if (isHex) { + replaceValue = convertToHex(previousPercentBrightness); + } else { + replaceValue = String.valueOf(previousPercentBrightness); + } + replaceTarget = INTENSITY_PREVIOUS_PERCENT; + notDone = true; } else if (request.contains(INTENSITY_PERCENT_HEX)) { replaceValue = convertToHex(percentBrightness); replaceTarget = INTENSITY_PERCENT_HEX; @@ -98,6 +130,10 @@ public static String replaceIntensityValue(String request, int intensity, boolea replaceValue = String.format(Locale.ROOT, "%1.2f", decimalBrightness); replaceTarget = INTENSITY_DECIMAL_PERCENT; notDone = true; + } else if (request.contains(INTENSITY_PREVIOUS_DECIMAL_PERCENT)) { + replaceValue = String.format(Locale.ROOT, "%1.2f", previousDecimalBrightness); + replaceTarget = INTENSITY_PREVIOUS_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)); @@ -135,7 +171,7 @@ public static String replaceIntensityValue(String request, int intensity, boolea // Helper Method public static String calculateReplaceIntensityValue(String request, int theIntensity, Integer targetBri, Integer targetBriInc, boolean isHex) { - return replaceIntensityValue(request, calculateIntensity(theIntensity, targetBri, targetBriInc), isHex); + return replaceIntensityValue(request, theIntensity, calculateIntensity(theIntensity, targetBri, targetBriInc), isHex); } // Apache Commons Conversion utils likes little endian too much diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index a136a388..283892fb 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -1250,6 +1250,9 @@ private String changeState(String userId, String lightId, String body, String ip } } + if(isColorRequest && isDimRequest && !device.isDimOnColor()) { + isDimRequest = false; + } /* Old code supperceded by the above block if (!device.isOnFirstDim() && device.isOnWhenDimPresent() && isDimRequest && !isOnRequest) { diff --git a/src/main/java/com/bwssystems/HABridge/plugins/hass/HassHome.java b/src/main/java/com/bwssystems/HABridge/plugins/hass/HassHome.java index 090f45d1..ca8e863a 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/hass/HassHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/hass/HassHome.java @@ -139,8 +139,7 @@ public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String hassCommand = aGsonHandler.fromJson(anItem.getItem(), HassCommand.class); else hassCommand = aGsonHandler.fromJson(anItem.getItem().getAsString().replaceAll("^\"|\"$", ""), HassCommand.class); - hassCommand.setBri(BrightnessDecode.replaceIntensityValue(hassCommand.getBri(), - BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc), false)); + hassCommand.setBri(BrightnessDecode.calculateReplaceIntensityValue(hassCommand.getBri(), intensity, targetBri, targetBriInc, false)); HomeAssistant homeAssistant = getHomeAssistant(hassCommand.getHassName()); if (homeAssistant == null) { log.warn("Should not get here, no HomeAssistants available"); diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index a67ea1b1..f53175f1 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -479,7 +479,7 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng this.pushLinkButton = function () { return $http.put(this.state.systemsbase + "/presslinkbutton").then( function (response) { - self.displayTimer("Link your device", 30000); + self.displayTimer("Link your device", self.state.settings.linkbuttontimeout * 1000); }, function (error) { if (error.status === 401) diff --git a/src/main/resources/public/views/editdevice.html b/src/main/resources/public/views/editdevice.html index 9630f4ae..f6474a7a 100644 --- a/src/main/resources/public/views/editdevice.html +++ b/src/main/resources/public/views/editdevice.html @@ -120,6 +120,12 @@

Edit/Copy a device

ng-model="device.dimNoOn" ng-true-value=true ng-false-value=false> {{device.dimNoOn}} + + + {{device.dimOnColor}} + Bridge Settings + + Link Button Timeout (seconds) + + ID Seed (start numbering from this value) Date: Mon, 14 Dec 2020 12:17:44 -0600 Subject: [PATCH 19/19] Fixed HomeAssistant issues and this is teh release for 5.4.0 --- README.md | 103 ++++--- java8_pom.xml | 273 ++++++++++++++++++ pom.xml | 2 +- .../HABridge/plugins/hass/HomeAssistant.java | 9 +- 4 files changed, 345 insertions(+), 42 deletions(-) create mode 100644 java8_pom.xml diff --git a/README.md b/README.md index 3fab54ab..0cf49931 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ A Custom implementation path looks like this: **NOTE: This software does not control Philips Hue devices directly. A physical Philips Hue Hub is required for that, by which the ha-bridge can then proxy all of your real Hue bridges behind this bridge.** -**ISSUE: Google Home does NOT support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.** +**ISSUE: Google Home does NOT support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work.** **FAQ: Please look here for the current FAQs! https://github.com/bwssytems/ha-bridge/wiki/HA-Bridge-FAQs** @@ -57,20 +57,20 @@ Then locate the jar and start the server with: ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below. ``` -java -jar ha-bridge-5.3.1RC5.jar +java -jar ha-bridge-5.4.0.jar ``` ## Manual installation of ha-bridge and setup of systemd service Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services. Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units -Create the directory and make sure that ha-bridge-5.3.1RC5.jar is in your /home/pi/ha-bridge directory. +Create the directory and make sure that ha-bridge-5.4.0.jar is in your /home/pi/ha-bridge directory. ``` pi@raspberrypi:~ $ mkdir ha-bridge pi@raspberrypi:~ $ cd ha-bridge -pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC5/ha-bridge-5.3.1RC5.jar +pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.4.0/ha-bridge-5.4.0.jar ``` Create the ha-bridge.service unit file: @@ -89,7 +89,7 @@ After=network.target Type=simple WorkingDirectory=/home/pi/ha-bridge -ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC5.jar +ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.4.0.jar [Install] WantedBy=multi-user.target @@ -375,6 +375,10 @@ This setting is in bridge-id, uuid, etc. in ha-bridge hue config replies. Leave This setting is the time used in between button presses when there is multiple buttons in a button device. It also controls the time between multiple items in a custom device call. This is defaulted to 100ms and the number represents milliseconds (1000 milliseconds = 1 second). #### Log Messages to Buffer This controls how many log messages will be kept and displayed on the log tab. This does not affect what is written to the standard output for logging. The default is 512. Changing this will incur more memory usage of the process. +#### UPNP Original (simple version) #### +Use very simplistic UPNP handling that was used in versions previous to 4.0. (Not Recommended) +#### UPNP Advanced (use multiple responses and notifies) #### +Turns on advanced UPP that hue bridge version 1 used at the latest release, not very stable. (Not Recommended) #### Trace UPNP Calls Turn on tracing for upnp discovery messages to the log. The default is false. #### Trace State Changes @@ -516,6 +520,59 @@ e.g. [{"item":{"clientId":"TestClient","topic":"Yep","message":"This is the time ${time.format(yyyy-MM-ddTHH:mm:ssXXX)}"},"type":"mqttDevice"}] ``` +Listing of all intensity replacement values that can be used. + +Replacement target text | Description +------------------------|------------ +${intensity.percent} | Insert the whole number percentage value e.g. 45 +${intensity.decimal_percent} | Insert the decimal percentage value e.g. 0.45 +${intensity.byte} | Insert the byte value +${intensity.math(X)} | Insert the math function identified by X +${intensity.math(X).hex} | Insert the hex value of the math function identified by X +${intensity.percent.hex} | Insert the hex value of the integer percentage value +${intensity.byte.hex} | Insert the hex value of the byte value +${intensity.previous_percent} | Insert the previous integer percentage value +${intensity.previous_decimal_percent}Insert the previous decimal percentage value +${intensity.previous_byte} | Insert the previous byte value + +Listing of all color replacement values that can be used. + +Replacement target text | Description +------------------------| ----------- +${color.r} | Insert the integer value of Red e.g. 123 +${color.g} | Insert the integer value of Green e.g. 241 +${color.b} | Insert the integer value of Blue e.g. 255 +${color.rx} | Insert the hex value of Red e.g. 7BX +${color.gx} | Insert the hex value of Green e.g. F1X +${color.bx} | Insert the hex value of Blue e.g. FFX +${color.rgbx} | Insert the hex value of all rgb e.g. 7BF1FFX +${color.hsb} | Insert the hsb value e.g. 186.3636,100.0,74.1176 +${color.h} | Insert the decimal value of hue e.g. 186.3636 +${color.s} | Insert the decimal value of saturation e.g. 100.0 +${colorbri} | Insert the integer value of the intensity +${color.milight:([01234])} | Insert the converted value for milight + +Listing of all ha-bridge device data replacement values that can be used. + +Replacement target text | Description +------------------------|------------ +${device.id} | Insert the ID of the device +${device.uniqueid} | Insert the unique ID of the device +${device.name} | Insert the name of the device +${device.mapId} | Insert the map ID of the deivice +${device.mapType} | Insert the map type of the device +${device.deviceType} | Insert the device type of the device +${device.targetDevice} | Insert the target device of the device +${device.requesterAddress} | Insert the requester address of the device being addressed +${device.description} | Insert the description of the device +${device.comments} | Insert the comments of the device + +Listing of all time data replacement values that can be used. + +Replacement target text | Description +------------------------|------------ +${time.format([Java SimpleDateFormat style string]) | Insert the current time described by java SimpleDateFormat string descriptor +${time.millis} | Insert the current time in milliseconds that the system returns Also, you may want to use the REST APIs listed below to configure your devices. ## Ask Alexa @@ -537,41 +594,7 @@ DIM Commands| Alexa, set `` to `` To see what Alexa thinks you said, you can check in the home page for your Alexa. -To view or remove devices that Alexa knows about, you can use the mobile app `Menu / Settings / Connected Home` or go to http://echo.amazon.com/#cards. - -## Google Assistant -Google Home is supported as of v3.2.0 and forward, but only if the bridge is running on port 80. - -**ISSUE: Google Home does NOT support local connection to Philips Hue Hubs and requires that it connect to meethue.com. Since the ha-bridge only emulates the local API, and is not associated with Philips, this method will not work. If you have an older Google Home application, this may still work. YMMV.** - -Use the Google Home app on a phone to add new "home control" devices by going into `Settings / Home Control / +` -as described [here](https://support.google.com/googlehome/answer/7124115?hl=en&ref_topic=7125624#homecontrol). -Click on `Philips Hue` under the `Add new` section. If ha-bridge is on the same network as the -phone as well as the Home device, then the app should quickly pass through the pairing step and -populate with all of the devices. If instead it takes you to a Philips Hue login page, this means -that the bridge was not properly discovered. - -Then you can say "OK Google, Turn on the office light" or whatever name you have given your configured devices. - -The Google Assistant can also group lights into rooms as described in the main [help article](https://support.google.com/googlehome/answer/7072090?hl=en&ref_topic=7029100). - -Here is the table of items to use to tell Google what you want to do. Note that either "OK Google" -or "Hey Google" can be used as a trigger. - -To do this: | Say "Hey Google", then... -------------|-------------------------- -To turn on/off a light | "Turn on " -Dim a light | "Dim the " -Brighten a light | "Brighten the " -Set a light brightness to a certain percentage | "Set to 50%" -Dim/Brighten lights by a certain percentage | "Dim/Brighten by 50%" -Turn on/off all lights in room | "Turn on/off lights in " -Turn on/off all lights | "Turn on/off all of the lights" - -To see what Home thinks you said, you can ask "Hey Google, What did I say?" or check the history in the app. - -New or removed devices are picked up automatically as soon as they are added/removed from ha-bridge. -No re-discovery step is necessary. +To view or remove devices that Alexa knows about, you can use the mobile app click on the devices icon or go to http://echo.amazon.com/#cards. ## Configuration REST API Usage This section will describe the REST API available for configuration. The REST body examples are all formatted for easy reading, the actual body usage should be like this: diff --git a/java8_pom.xml b/java8_pom.xml new file mode 100644 index 00000000..e4207645 --- /dev/null +++ b/java8_pom.xml @@ -0,0 +1,273 @@ + + + 4.0.0 + + com.bwssystems.HABridge + ha-bridge + 5.4.0 + jar + + HA Bridge + Emulates a Philips Hue bridge to allow the Amazon Echo to hook up to other HA systems, i.e. Vera or Harmony Hub or Nest, using lightweight frameworks + + + UTF-8 + + + + + jitpack.io + https://jitpack.io + + + Eclipse Paho Repo + https://repo.eclipse.org/content/repositories/paho-releases/ + + + + + + com.github.bwssytems + harmony-java-client + master-SNAPSHOT + + + org.slf4j + slf4j-simple + + + org.slf4j + log4j-over-slf4j + + + + + com.github.bwssytems + nest-controller + 1.0.14 + + + org.slf4j + slf4j-simple + + + org.slf4j + log4j-over-slf4j + + + + + com.sparkjava + spark-core + 2.7.2 + + + slf4j-simple + org.slf4j + + + + + org.apache.httpcomponents + httpclient + 4.5.1 + + + org.apache.httpcomponents + httpcore + 4.4.4 + + + org.slf4j + slf4j-api + 1.7.24 + + + ch.qos.logback + logback-classic + 1.2.1 + + + com.google.code.gson + gson + 2.6.2 + + + net.java.dev.eval + eval + 0.5 + + + com.google.inject + guice + 4.1.0 + + + org.igniterealtime.smack + smack-core + 4.2.0 + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.1 + + + junit + junit + 4.13.1 + test + + + com.github.bwssytems + lifx-sdk-java + 2.1.6 + + + com.github.mob41 + broadlink-java-api + master-SNAPSHOT + + + org.apache.commons + commons-lang3 + 3.5 + + + org.jmdns + jmdns + 3.5.5 + + + + + + + src/main/resources + + version.properties + + true + + + src/main/resources + + version.properties + + false + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.0.0-M2 + + + enforce-maven + + enforce + + + + + 3.3 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M3 + + false + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.1 + + + package + + shade + + + true + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/*.txt + META-INF/maven/** + about_files/** + + + + *:* + + + org.slf4j:slf4j-api + + ** + + + + commons-logging:commons-logging + + ** + + + + xpp3:xpp3 + + ** + + + + org.igniterealtime.smack:* + + ** + + + + com.github.bwssytems:harmony-java-client + + ** + + + + org.eclipse.paho:org.eclipse.paho.client.mqttv3 + + ** + + + + + + com.bwssystems.HABridge.HABridge + + + + + + + + + diff --git a/pom.xml b/pom.xml index 9d46c6db..fead990c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.3.1RC5-java11 + 5.4.0-java11 jar HA Bridge diff --git a/src/main/java/com/bwssystems/HABridge/plugins/hass/HomeAssistant.java b/src/main/java/com/bwssystems/HABridge/plugins/hass/HomeAssistant.java index 5c1cd11c..730f658d 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/hass/HomeAssistant.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/hass/HomeAssistant.java @@ -102,6 +102,10 @@ private boolean isLegacyAuth() { if (theAuthType == null) { try { theAuthType = new Gson().fromJson(hassAddress.getExtensions(), HassAuth.class); + if(theAuthType == null) { + theAuthType = new HassAuth(); + theAuthType.setLegacyauth(false); + } } catch (Exception e) { log.warn("Could not read hass auth - continuing with defaults."); theAuthType = new HassAuth(); @@ -114,7 +118,7 @@ private boolean isLegacyAuth() { private NameValue[] getAuthHeader() { if (headers == null) { if (hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) { - headers = new NameValue[1]; + headers = new NameValue[2]; headers[0] = new NameValue(); if (isLegacyAuth()) { headers[0].setName("x-ha-access"); @@ -123,6 +127,9 @@ private NameValue[] getAuthHeader() { headers[0].setName("Authorization"); headers[0].setValue("Bearer " + hassAddress.getPassword()); } + headers[1] = new NameValue(); + headers[1].setName("Accept-Encoding"); + headers[1].setValue("gzip"); } } else if(hassAddress.getPassword() == null || hassAddress.getPassword().isEmpty()) { headers = null;