diff --git a/pom.xml b/pom.xml index 778f96e..afc233f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 5.2.0RC1 + 5.2.0RC2 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 1d2b4ee..7d81c8f 100644 --- a/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java +++ b/src/main/java/com/bwssystems/HABridge/dao/DeviceDescriptor.java @@ -80,7 +80,10 @@ public class DeviceDescriptor{ @SerializedName("deviceState") @Expose private DeviceState deviceState; - + @SerializedName("onFirstDim") + @Expose + private boolean onFirstDim; + public String getName() { return name; } @@ -275,6 +278,14 @@ public class DeviceDescriptor{ this.comments = comments; } + public boolean isOnFirstDim() { + return onFirstDim; + } + + public void setOnFirstDim(boolean onFirstDim) { + this.onFirstDim = onFirstDim; + } + public boolean containsType(String aType) { if(aType == null) return false; diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 8c59d40..f45a3ee 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -1096,10 +1096,11 @@ public class HueMulator { DeviceState state = null; Integer targetBri = null; Integer targetBriInc = null; - MultiCommandUtil aMultiUtil = new MultiCommandUtil(); - aMultiUtil.setTheDelay(bridgeSettings.getButtonsleep()); - aMultiUtil.setDelayDefault(bridgeSettings.getButtonsleep()); - aMultiUtil.setSetCount(1); + boolean isColorRequest = false; + boolean isDimRequest = false; + boolean isOnRequest = false; + boolean previousError = false; + ColorData colorData = null; log.debug("hue state change requested: " + userId + " from " + ipAddress + " body: " + body); HueError[] theErrors = bridgeSettingMaster.getBridgeSecurity().validateWhitelistUser(userId, null, bridgeSettingMaster.getBridgeSecurity().isUseLinkButton()); if (theErrors != null) { @@ -1125,136 +1126,127 @@ public class HueMulator { "Could not find device.", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); } - if (body.contains("\"bri_inc\"")) { - targetBriInc = new Integer(theStateChanges.getBri_inc()); - } - else if (body.contains("\"bri\"")) { - targetBri = new Integer(theStateChanges.getBri()); - } - state = device.getDeviceState(); if (state == null) { state = DeviceState.createDeviceState(device.isColorDevice()); device.setDeviceState(state); } - if (targetBri != null || targetBriInc != null) { - url = device.getDimUrl(); + if (body.contains("\"bri_inc\"")) { + targetBriInc = new Integer(theStateChanges.getBri_inc()); + isDimRequest = true; + } + else if (body.contains("\"bri\"")) { + targetBri = new Integer(theStateChanges.getBri()); + isDimRequest = true; + } - if (url == null || url.length() == 0) - url = device.getOnUrl(); - } else { - if (body.contains("\"xy\"") || body.contains("\"ct\"") || body.contains("\"hue\"")) { - url = device.getColorUrl(); - } else if (theStateChanges.isOn()) { + if (body.contains("\"xy\"") || body.contains("\"ct\"") || body.contains("\"hue\"") || body.contains("\"xy_inc\"") || body.contains("\"ct_inc\"") || body.contains("\"hue_inc\"")) { + List xy = theStateChanges.getXy(); + List xyInc = theStateChanges.getXy_inc(); + Integer ct = theStateChanges.getCt(); + Integer ctInc = theStateChanges.getCt_inc(); + if (xy != null && xy.size() == 2) { + colorData = new ColorData(ColorData.ColorMode.XY, xy); + } else if (xyInc != null && xyInc.size() == 2) { + List current = state.getXy(); + current.set(0, current.get(0) + xyInc.get(0)); + current.set(1, current.get(1) + xyInc.get(1)); + colorData = new ColorData(ColorData.ColorMode.XY, current); + } else if (ct != null && ct != 0) { + colorData = new ColorData(ColorData.ColorMode.CT, ct); + } else if (ctInc != null && ctInc != 0) { + colorData = new ColorData(ColorData.ColorMode.CT, state.getCt() + ctInc); + } + if(colorData != null) + isColorRequest = true; + } + + if (body.contains("\"on\"")) { + isOnRequest = true; + } + + if(isOnRequest || (isDimRequest && device.isOnFirstDim() && !device.getDeviceState().isOn())) { + if (theStateChanges.isOn()) { url = device.getOnUrl(); } else if (!theStateChanges.isOn()) { url = device.getOffUrl(); } - } - // code for backwards compatibility - if(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) { - if(url == null) - url = device.getOnUrl(); - } - if (url != null && !url.equals("")) { - if (!url.startsWith("[")) { - if (url.startsWith("{\"item")) - url = "[" + url + "]"; - else { - if(url.startsWith("{")) - url = "[{\"item\":" + url + "}]"; - else - url = "[{\"item\":\"" + url + "\"}]"; - } - } else if(!url.startsWith("[{\"item\"")) - url = "[{\"item\":" + url + "}]"; - - log.debug("Decode Json for url items: " + url); - CallItem[] callItems = null; - try { - callItems = aGsonHandler.fromJson(url, CallItem[].class); - } catch(JsonSyntaxException e) { - log.warn("Could not decode Json for url items: " + lightId + " for hue state change request: " + userId + " from " - + ipAddress + " body: " + body + " url items: " + url); - return aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/lights/" + lightId, - "Could decode json in request", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); + // code for backwards compatibility + if(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) { + if(url == null) + url = device.getOnUrl(); } - - for (int i = 0; callItems != null && i < callItems.length; i++) { - if (!ignoreRequester) { - if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) { - log.warn("filter for requester address not present in: (device)" + device.getRequesterAddress() + " OR then (item)" + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress); - continue; - } - } - - if (callItems[i].getCount() != null && callItems[i].getCount() > 0) - aMultiUtil.setSetCount(callItems[i].getCount()); - else - aMultiUtil.setSetCount(1); - // code for backwards compatibility - if((callItems[i].getType() == null || callItems[i].getType().trim().isEmpty())) { - if(validMapTypes.validateType(device.getMapType())) - callItems[i].setType(device.getMapType().trim()); - else if(validMapTypes.validateType(device.getDeviceType())) - callItems[i].setType(device.getDeviceType().trim()); - else - callItems[i].setType(DeviceMapTypes.CUSTOM_DEVICE[DeviceMapTypes.typeIndex]); - } - - if (callItems[i].getType() != null) { - for (int x = 0; x < aMultiUtil.getSetCount(); x++) { - if (x > 0 || i > 0) { - try { - Thread.sleep(aMultiUtil.getTheDelay()); - } catch (InterruptedException e) { - // ignore - } - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) - aMultiUtil.setTheDelay(callItems[i].getDelay()); - else - aMultiUtil.setTheDelay(aMultiUtil.getDelayDefault()); - ColorData colorData = null; - List xy = theStateChanges.getXy(); - List xyInc = theStateChanges.getXy_inc(); - Integer ct = theStateChanges.getCt(); - Integer ctInc = theStateChanges.getCt_inc(); - if (xy != null && xy.size() == 2) { - colorData = new ColorData(ColorData.ColorMode.XY, xy); - } else if (xyInc != null && xyInc.size() == 2) { - List current = state.getXy(); - current.set(0, current.get(0) + xyInc.get(0)); - current.set(1, current.get(1) + xyInc.get(1)); - colorData = new ColorData(ColorData.ColorMode.XY, current); - } else if (ct != null && ct != 0) { - colorData = new ColorData(ColorData.ColorMode.CT, ct); - } else if (ctInc != null && ctInc != 0) { - colorData = new ColorData(ColorData.ColorMode.CT, state.getCt() + ctInc); - } - log.debug("Calling Home device handler for type : " + callItems[i].getType().trim()); - responseString = homeManager.findHome(callItems[i].getType().trim()).deviceHandler(callItems[i], aMultiUtil, lightId, state.getBri(), targetBri, targetBriInc, colorData, device, body); - if(responseString != null && responseString.contains("{\"error\":")) { - x = aMultiUtil.getSetCount(); - } + if (url != null && !url.equals("")) { + responseString = callUrl(url, device, userId, lightId, body, ipAddress, ignoreRequester, targetBri, targetBriInc, colorData); + } else { + log.warn("Could not find on/off url: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body); + responseString = aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/lights/" + lightId, + "Could not find on/off url.", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); + previousError = true; + } + } + + if (isDimRequest && !previousError) { + if(!device.isOnFirstDim() ) + url = device.getDimUrl(); + + if (url == null || url.length() == 0) + url = device.getOnUrl(); + + // code for backwards compatibility + if(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) { + if(url == null) + url = device.getOnUrl(); + } + + if (url != null && !url.equals("")) { + if(isOnRequest) { + try { + Thread.sleep(bridgeSettings.getButtonsleep()); + } catch (InterruptedException e) { + // ignore } } - else - log.warn("Call Items type is null <<<" + callItems[i] + ">>>"); + responseString = callUrl(url, device, userId, lightId, body, ipAddress, ignoreRequester, targetBri, targetBriInc, colorData); + } else { + log.warn("Could not find dim url: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body); + responseString = aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/lights/" + lightId, + "Could not find dim url.", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); + previousError = true; } - - if(callItems.length == 0) - log.warn("No call items were available: <<<" + url + ">>>"); - } else { - log.warn("Could not find url: " + lightId + " for hue state change request: " + userId + " from " - + ipAddress + " body: " + body); - responseString = aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/lights/" + lightId, - "Could not find url.", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); } + if (isColorRequest && !previousError) { + url = device.getColorUrl(); + // code for backwards compatibility + if(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) { + if(url == null) + url = device.getOnUrl(); + } + + if (url != null && !url.equals("")) { + if((isOnRequest && !isDimRequest) || isDimRequest) { + try { + Thread.sleep(bridgeSettings.getButtonsleep()); + } catch (InterruptedException e) { + // ignore + } + } + responseString = callUrl(url, device, userId, lightId, body, ipAddress, ignoreRequester, targetBri, targetBriInc, colorData); + } else { + log.warn("Could not find color url: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body); + responseString = aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/lights/" + lightId, + "Could not find color url.", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); + previousError = true; + } + } + if (responseString == null || !responseString.contains("[{\"error\":")) { log.debug("Response is in error: " + ((responseString == null) ? "null" : responseString)); if(!device.isNoState()) { @@ -1375,4 +1367,87 @@ public class HueMulator { return aGsonHandler.toJson(theErrors); } + + protected String callUrl(String url, DeviceDescriptor device, String userId, String lightId, String body, String ipAddress, boolean ignoreRequester, Integer targetBri, Integer targetBriInc, ColorData colorData) { + String responseString = null; + MultiCommandUtil aMultiUtil = new MultiCommandUtil(); + aMultiUtil.setTheDelay(bridgeSettings.getButtonsleep()); + aMultiUtil.setDelayDefault(bridgeSettings.getButtonsleep()); + aMultiUtil.setSetCount(1); + + if (!url.startsWith("[")) { + if (url.startsWith("{\"item")) + url = "[" + url + "]"; + else { + if(url.startsWith("{")) + url = "[{\"item\":" + url + "}]"; + else + url = "[{\"item\":\"" + url + "\"}]"; + } + } else if(!url.startsWith("[{\"item\"")) + url = "[{\"item\":" + url + "}]"; + + log.debug("Decode Json for url items: " + url); + CallItem[] callItems = null; + try { + callItems = aGsonHandler.fromJson(url, CallItem[].class); + } catch(JsonSyntaxException e) { + log.warn("Could not decode Json for url items: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body + " url items: " + url); + return aGsonHandler.toJson(HueErrorResponse.createResponse("3", "/lights/" + lightId, + "Could decode json in request", "/lights/" + lightId, null, null).getTheErrors(), HueError[].class); + } + + for (int i = 0; callItems != null && i < callItems.length; i++) { + if (!ignoreRequester) { + if(!filterByRequester(device.getRequesterAddress(), ipAddress) || !filterByRequester(callItems[i].getFilterIPs(), ipAddress)) { + log.warn("filter for requester address not present in: (device)" + device.getRequesterAddress() + " OR then (item)" + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress); + continue; + } + } + + if (callItems[i].getCount() != null && callItems[i].getCount() > 0) + aMultiUtil.setSetCount(callItems[i].getCount()); + else + aMultiUtil.setSetCount(1); + // code for backwards compatibility + if((callItems[i].getType() == null || callItems[i].getType().trim().isEmpty())) { + if(validMapTypes.validateType(device.getMapType())) + callItems[i].setType(device.getMapType().trim()); + else if(validMapTypes.validateType(device.getDeviceType())) + callItems[i].setType(device.getDeviceType().trim()); + else + callItems[i].setType(DeviceMapTypes.CUSTOM_DEVICE[DeviceMapTypes.typeIndex]); + } + + if (callItems[i].getType() != null) { + for (int x = 0; x < aMultiUtil.getSetCount(); x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(aMultiUtil.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + aMultiUtil.setTheDelay(callItems[i].getDelay()); + else + aMultiUtil.setTheDelay(aMultiUtil.getDelayDefault()); + + log.debug("Calling Home device handler for type : " + callItems[i].getType().trim()); + responseString = homeManager.findHome(callItems[i].getType().trim()).deviceHandler(callItems[i], aMultiUtil, lightId, device.getDeviceState().getBri(), targetBri, targetBriInc, colorData, device, body); + if(responseString != null && responseString.contains("{\"error\":")) { + x = aMultiUtil.getSetCount(); + } + } + } + else + log.warn("Call Items type is null <<<" + callItems[i] + ">>>"); + } + + if(callItems.length == 0) + log.warn("No call items were available: <<<" + url + ">>>"); + + return responseString; + } } diff --git a/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMHome.java b/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMHome.java index a26a818..d7beb70 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMHome.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMHome.java @@ -137,7 +137,7 @@ public class FHEMHome implements Home { List deviceList = new ArrayList(); while(keys.hasNext()) { String key = keys.next(); - theResponse = fhemMap.get(key).getDevices(httpClient); + theResponse = fhemMap.get(key).testGetDevices(httpClient); if(theResponse != null) addFHEMDevices(deviceList, theResponse, key); else { diff --git a/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMInstance.java b/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMInstance.java index 85a1f72..2bb9a2e 100644 --- a/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMInstance.java +++ b/src/main/java/com/bwssystems/HABridge/plugins/fhem/FHEMInstance.java @@ -88,6 +88,275 @@ public class FHEMInstance { return deviceList; } + public List testGetDevices(HTTPHandler httpClient) { + String TestData = "\n" + + "\n" + + " \n" + + " Home, Sweet Home\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " Save config\n" + + " ?\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " Alexa\n" + + "
\n" + + "
\n" + + "
\n" + + " System\n" + + "
\n" + + "
\n" + + "
\n" + + " WG-Zimmer\n" + + "
\n" + + "
\n" + + "
\n" + + " habridge\n" + + "
\n" + + "
\n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " Logfile\n" + + "
\n" + + "
\n" + + "
\n" + + " Commandref\n" + + "
\n" + + "
\n" + + "
\n" + + " Remote doc\n" + + "
\n" + + "
\n" + + "
\n" + + " Edit files\n" + + "
\n" + + "
\n" + + "
\n" + + " Select style\n" + + "
\n" + + "
\n" + + "
\n" + + " Event monitor\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
{ \n" + 
+				"  \"Arg\":\"room=habridge\", \n" + 
+				"  \"Results\": [ \n" + 
+				"  { \n" + 
+				"    \"Name\":\"Arbeitslicht\", \n" + 
+				"    \"PossibleSets\":\"on off\", \n" + 
+				"    \"PossibleAttrs\":\"alias comment:textField-long eventMap group room suppressReading userReadings:textField-long verbose:0,1,2,3,4,5 readingList setList useSetExtensions disable disabledForIntervals event-on-change-reading event-on-update-reading event-aggregator event-min-interval stateFormat:textField-long timestamp-on-change-reading alexaName alexaRoom cmdIcon devStateIcon devStateStyle fhem_widget_command fhem_widget_command_2 fhem_widget_command_3 genericDeviceType:security,ignore,switch,outlet,light,blind,thermometer,thermostat,contact,garage,window,lock homebridgeMapping:textField-long icon sortby webCmd widgetOverride userattr\", \n" + 
+				"    \"Internals\": { \n" + 
+				"      \"NAME\": \"Arbeitslicht\", \n" + 
+				"      \"NR\": \"28\", \n" + 
+				"      \"STATE\": \"-\", \n" + 
+				"      \"TYPE\": \"dummy\" \n" + 
+				"    }, \n" + 
+				"    \"Readings\": {      \"state\": { \"Value\":\"on\", \"Time\":\"2017-12-14 15:41:05\" } }, \n" + 
+				"    \"Attributes\": { \n" + 
+				"      \"alexaName\": \"Arbeitslicht\", \n" + 
+				"      \"alexaRoom\": \"alexaroom\", \n" + 
+				"      \"fhem_widget_command\": \"{  \\u0022allowed_values\\u0022 : [    \\u0022on\\u0022  ],  \\u0022order\\u0022 : 0}\", \n" + 
+				"      \"icon\": \"scene_office\", \n" + 
+				"      \"room\": \"Alexa,habridge\", \n" + 
+				"      \"setList\": \"on off\", \n" + 
+				"      \"stateFormat\": \"-\", \n" + 
+				"      \"webCmd\": \"on\" \n" + 
+				"    } \n" + 
+				"  }, \n" + 
+				"  { \n" + 
+				"    \"Name\":\"DeckenlampeLinks\", \n" + 
+				"    \"PossibleSets\":\"on off dim dimup dimdown HSV RGB sync pair unpair\", \n" + 
+				"    \"PossibleAttrs\":\"alias comment:textField-long eventMap group room suppressReading userReadings:textField-long verbose:0,1,2,3,4,5 gamma dimStep defaultColor defaultRamp colorCast whitePoint event-on-change-reading event-on-update-reading event-aggregator event-min-interval stateFormat:textField-long timestamp-on-change-reading alexaName alexaRoom cmdIcon devStateIcon devStateStyle fhem_widget_command fhem_widget_command_2 fhem_widget_command_3 genericDeviceType:security,ignore,switch,outlet,light,blind,thermometer,thermostat,contact,garage,window,lock homebridgeMapping:textField-long icon sortby webCmd widgetOverride \n" + 
+				"                AlleLampen AlleLampen_map\n" + 
+				"                DeckenLampen DeckenLampen_map structexclude userattr\", \n" + 
+				"    \"Internals\": { \n" + 
+				"      \"CONNECTION\": \"bridge-V3\", \n" + 
+				"      \"DEF\": \"RGBW2 bridge-V3:10.2.3.3\", \n" + 
+				"      \"IP\": \"10.2.3.3\", \n" + 
+				"      \"LEDTYPE\": \"RGBW2\", \n" + 
+				"      \"NAME\": \"DeckenlampeLinks\", \n" + 
+				"      \"NR\": \"18\", \n" + 
+				"      \"NTFY_ORDER\": \"50-DeckenlampeLinks\", \n" + 
+				"      \"PORT\": \"8899\", \n" + 
+				"      \"PROTO\": \"0\", \n" + 
+				"      \"SLOT\": \"5\", \n" + 
+				"      \"STATE\": \"off\", \n" + 
+				"      \"TYPE\": \"WifiLight\" \n" + 
+				"    }, \n" + 
+				"    \"Readings\": { \n" + 
+				"      \"RGB\": { \"Value\":\"000000\", \"Time\":\"2017-12-14 15:41:10\" }, \n" + 
+				"      \"brightness\": { \"Value\":\"0\", \"Time\":\"2017-12-14 15:41:10\" }, \n" + 
+				"      \"hue\": { \"Value\":\"0\", \"Time\":\"2017-12-14 15:41:10\" }, \n" + 
+				"      \"saturation\": { \"Value\":\"0\", \"Time\":\"2017-12-14 15:41:10\" }, \n" + 
+				"      \"state\": { \"Value\":\"off\", \"Time\":\"2017-12-14 15:41:10\" } \n" + 
+				"    }, \n" + 
+				"    \"Attributes\": { \n" + 
+				"      \"AlleLampen\": \"AlleLampen\", \n" + 
+				"      \"DeckenLampen\": \"DeckenLampen\", \n" + 
+				"      \"fhem_widget_command\": \"{ \\u0022locations\\u0022 : [ \\u0022APP\\u0022, \\u0022WATCH\\u0022, \\u0022WIDGET\\u0022 ], \\u0022allowed_values\\u0022 : [ \\u0022off\\u0022, \\u0022on\\u0022 ], \\u0022order\\u0022 : 6}\", \n" + 
+				"      \"room\": \"habridge,Alexa,WG-Zimmer\", \n" + 
+				"      \"userattr\": \"AlleLampen AlleLampen_map\n" + 
+				"                DeckenLampen DeckenLampen_map structexclude\", \n" + 
+				"      \"webCmd\": \"RGB\", \n" + 
+				"      \"widgetOverride\": \"RGB:colorpicker,RGB\" \n" + 
+				"    } \n" + 
+				"  }  ], \n" + 
+				"  \"totalResultsReturned\":2 \n" + 
+				"}\n" + 
+				"            
\n" + + "
\n" + + " \n" + + ""; + + String TestData2 = "
\n" + + "
\n" + "{   \"Arg\":\"room=HaBridge\",   \"Results\": [   {     \"Name\":\"wifi_steckdose3\",     \"PossibleSets\":\"on:noArg off:noArg off on toggle\",     \"PossibleAttrs\":\"alias comment:textField-long eventMap group room suppressReading userReadings:textField-long verbose:0,1,2,3,4,5 IODev qos retain publishSet publishSet_.* subscribeReading_.* autoSubscribeReadings event-on-change-reading event-on-update-reading event-aggregator event-min-interval stateFormat:textField-long timestamp-on-change-reading alarmDevice:Actor,Sensor alarmSettings cmdIcon devStateIcon devStateStyle icon lightSceneParamsToSave lightSceneRestoreOnlyIfChanged:1,0 sortby structexclude webCmd webCmdLabel:textField-long widgetOverride userattr\",     \"Internals\": {       \"CHANGED\": \"null\",       \"NAME\": \"wifi_steckdose3\",       \"NR\": \"270\",       \"STATE\": \"off\",       \"TYPE\": \"MQTT_DEVICE\",       \"retain\": \"*:1 \"     },     \"Readings\": {       \"state\": { \"Value\":\"OFF\", \"Time\":\"2018-01-01 23:01:21\" },       \"transmission-state\": { \"Value\":\"subscription acknowledged\", \"Time\":\"2018-01-03 22:34:00\" }     },     \"Attributes\": {       \"IODev\": \"myBroker\",       \"alias\": \"Stecki\",       \"devStateIcon\": \"on:black_Steckdose.on off:black_Steckdose.off\",       \"event-on-change-reading\": \"state\",       \"eventMap\": \"ON:on OFF:off\",       \"publishSet\": \"on off toggle /SmartHome/az/stecker/cmnd/POWER\",       \"retain\": \"1\",       \"room\": \"HaBridge,Arbeitszimmer,mqtt\",       \"stateFormat\": \"state\",       \"subscribeReading_state\": \"/SmartHome/az/stecker/stat/POWER\",       \"webCmd\": \"on:off:toggle\"     }   }  ],   \"totalResultsReturned\":1 }" + 
+				"            
\n" + + "
\n" + + " \n" + + ""; + List deviceList = null; + FHEMItem theFhemStates; + String theUrl = null; + String theData; + NameValue[] headers = null; + if(theFhem.getSecure() != null && theFhem.getSecure()) + theUrl = "https://"; + else + theUrl = "http://"; + if(theFhem.getUsername() != null && !theFhem.getUsername().isEmpty() && theFhem.getPassword() != null && !theFhem.getPassword().isEmpty()) { + theUrl = theUrl + theFhem.getUsername() + ":" + theFhem.getPassword() + "@"; + } + theUrl = theUrl + theFhem.getIp() + ":" + theFhem.getPort() + "/fhem?cmd=jsonlist2"; + if(theFhem.getWebhook() != null && !theFhem.getWebhook().trim().isEmpty()) + theUrl = theUrl + "%20room=" + theFhem.getWebhook().trim(); +// theData = httpClient.doHttpRequest(theUrl, HttpGet.METHOD_NAME, "application/json", null, headers); + theData = TestData; + if(theData != null) { + log.debug("GET FHEM States - data: " + theData); + theData = getJSONData(theData); + theFhemStates = new Gson().fromJson(theData, FHEMItem.class); + if(theFhemStates == null) { + log.warn("Cannot get any devices for FHEM " + theFhem.getName() + " as response is not parsable."); + } + else { + deviceList = new ArrayList(); + + for (Result aResult:theFhemStates.getResults()) { + FHEMDevice aNewFhemDeviceDevice = new FHEMDevice(); + aNewFhemDeviceDevice.setItem(aResult); + aNewFhemDeviceDevice.setAddress(theFhem.getIp() + ":" + theFhem.getPort()); + aNewFhemDeviceDevice.setName(theFhem.getName()); + deviceList.add(aNewFhemDeviceDevice); + } + } + } + else + log.warn("Cannot get an devices for FHEM " + theFhem.getName() + " http call failed."); + return deviceList; + } + public String getJSONData(String response) { String theData; theData = response.substring(response.indexOf("
") + 4);
diff --git a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java
index 195076a..6484be4 100644
--- a/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java
+++ b/src/main/java/com/bwssystems/HABridge/plugins/http/HTTPHandler.java
@@ -103,9 +103,10 @@ public class HTTPHandler {
 					retryCount = 2;
 				} else if (response != null) {
 					log.warn("HTTP response code was not an expected successful response of between 200 - 299, the code was: "
-									+ response.getStatusLine());
+									+ response.getStatusLine() + " with the content of <<<" + theContent + ">>>");
 					if (response.getStatusLine().getStatusCode() == 504) {
 						log.warn("HTTP response code was 504, retrying...");
+						log.debug("The 504 error content is <<<" + theContent + ">>>");
 					} else
 						retryCount = 2;
 					
diff --git a/src/main/java/com/bwssystems/HABridge/plugins/somfy/SomfyInfo.java b/src/main/java/com/bwssystems/HABridge/plugins/somfy/SomfyInfo.java
index 4b74c6e..be1d862 100644
--- a/src/main/java/com/bwssystems/HABridge/plugins/somfy/SomfyInfo.java
+++ b/src/main/java/com/bwssystems/HABridge/plugins/somfy/SomfyInfo.java
@@ -75,7 +75,7 @@ public class SomfyInfo {
 		urlEncodedFormEntity.writeTo(bos);
 		String body = bos.toString();
 		String response = httpClient.doHttpRequest(BASE_URL + "json/login",HttpPost.METHOD_NAME, "application/x-www-form-urlencoded", body,httpHeader);
-		log.debug(response);
+		log.debug("Somfy login response <<<" + response + ">>>");
 	}
 
 	private NameValue[] getHttpHeaders() {
@@ -89,7 +89,7 @@ public class SomfyInfo {
 		NameValue[] httpHeader = getHttpHeaders();
 		log.info("Making SOMFY http setup call");
 		String response = httpClient.doHttpRequest(BASE_URL + "json/getSetup", HttpGet.METHOD_NAME, "", "", httpHeader );
-		log.debug(response);
+		log.debug("Somfy getSetup response <<<" + response + ">>>");
 		GetSetup setupData = new Gson().fromJson(response, GetSetup.class);
 		return setupData;
 	}
@@ -98,7 +98,7 @@ public class SomfyInfo {
 		login(namedIP.getUsername(), namedIP.getPassword());
 		log.info("Making SOMFY http exec call");
 		String response = httpClient.doHttpRequest(BASE_URL_ENDUSER + "exec/apply", HttpPost.METHOD_NAME, "application/json;charset=UTF-8", jsonToPost, getHttpHeaders());
-		log.info(response);
+		log.debug("Somfy exec reply response <<<" + response + ">>>");
 	}
 
 
diff --git a/src/main/resources/public/css/colorpicker.min.css b/src/main/resources/public/css/colorpicker.min.css
new file mode 100644
index 0000000..92a57d8
--- /dev/null
+++ b/src/main/resources/public/css/colorpicker.min.css
@@ -0,0 +1 @@
+.colorpicker-visible,.colorpicker-visible .dropdown-menu{display:block!important}colorpicker-saturation{display:block;width:100px;height:100px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAFJhJREFUeAGMU+/q4kAMzFYf4LgP96a+q4c+gSIqxf/r5maWDoTgz15gmM0kW5uMLa21v2b2G6jAG2iEzqUU6q5c/OlMuHtL/ULNd5TP6EJ1RP7NuXvKE397jmbg7MrzHI748T1UA3eopyGQV2qK1+vVHo/Hm1itVm0J7Q+afwGMmgeRphf7Noh6lCeuHJAvm/X8rAQNlw2VScoj6863OQjl2ZB3qkeu5Lh0RJ3qynuNjJA21FppQAHa8/l83263No5jOxwOttlsbL1e2xIXXpMZzzRsXoTw34bQgLiQbKh6M9SXDBSypn4XGOSkGUO1cJdn1Yh4/qYVgctmGSwXyARNcOSFRuBMHvA1GMzwy+Vix+PRdrvdYrvd2vl87oZYDCxBL9B/jEyopghzlNjL0DlB+gAoPNXyOfa3oA9puXonyVHzdH+g9MEISa5z0qUNkwkm6MJkxALg8mlMAxvNwBfhYLvf7w50vl6vBV9H2e/3BjYateQDY8gM5bmWovygdyEb87k/G5Zz9c/2zfEQFysO5nDJ6mMel91Z//pwFpygIWIaMXE3AoYssPBCI/B12DiONMT5VZxOJ0f+j/MyWo5chYGowfn/j03lNXBpl85Up8d46u6DChBMvKv2UePrrAsChtdLTi73oEBjFDYpmIp/KSgRhRw357sXuHLknRgI8d90F8QL761oI8iQeJqvUOGnAoEkgNblF13iiJASZCwhLkG+v7/Halvt5+enr7x+02lZOwKymJ7jMAXK32RxaXnNxfXzCOkCxTO2I3NiR0i9gAjQLLoVHkKG8pCi0UT4Q0h5xUFIlBJEYmg+1yg6TrUq+YfEWKK0lWsSS8+DkNvCWtvJXu0UbDyH/NYjoDHFybPd/cPeficiW5LvkVdBNY4UoIqOQMwPBXm9vUYIVCj3GkXCCo1tRS//uMhYoVG3q46HaBQtamESTs/+0o92hOTaBbqjBwpu8reCuzAP4rkXpQBzQwbhZxD7jNYEAS4CI2Rg4hLitPUor2hGh6j4hQ5FfWt8LQF+SwyJIpGUV05nu56VHqJhR8ybb+Q+/dnPYZYSigIICvY3xfYxCckz/qazprjDiFJ+5DWVwscaMpSDgkleI2uutaKk5kPFNTqO8pBDlBQZEqKvoJXp7+lxzx7Cuoqp2M7zlrm5JbH/9oZ/GLdzBGf9FNmmvPi+h2FXnm8L5WhdCMJNyr1D6yvKP1rFQYgSjWpteE0JMraEME8ykpzo/0/+wcg5yGHMooegQYIRyXU1i52tCSfn9oSQFH+Fe4jypxs3RHA+xNCcNUZ+BXRg7iu0lhgDAesisSfE6UA0iudz9sNHKChek5eBX9a+FwKkKQ+Nd6JljkLX6B4x2L8hhHOsVdhR4iHEEe1LeWJCSI2tCo9AU3OIKHGgbupv6NvyRciGgMzPPLP5LmhPdKTx8qgSWIXxVkZx8QJihmCvPP8nE6IRXniKT9GThhKF0QkZal3KQYcLgn+s8YwWJiNnLVL4mOz1b+4piM8/+YYb8xNlLoASXqC7c9DCOokhl4RAKgSkCNM9wklBOPL4BIJoblQggsb8Km9W/IlIJkKrPN4xEETPwtS3hczrp8//pxmzpoUIBfeSO8r8/OE59wgTIsnIb4yBj7Cft6pYI8Sbh5TBD749IANSlKPgJQQmrr0uUfjbCnlV/V9OCfLpDc9b0nw4x3bznAbWFAyfUeBLEIhA3uaCKifFyUBg8pg+Ro8nOCVOintKoy0xj5bFvhNCQMAfoUqgc8UwMRot8dWy/qPcDHQk5XkgOPD97//Gl/xC2kKlkhl2V4pMQmAON+22E+4XgIzMKYMSmp7S0ymTcpYEYM6eVjKlJV25HgYguZ6lD1hR4S4byoxRwMUQjm87MYVxrW19nCqbgTD4kSEde7FlxcF4tKDCPu41lBUKa7tjgGuHJuPCwpBPdcONuVTiMEMgbPpugJE0+GIRusL+yD9qGhrP05ClFHfOuppZeV4ZkgyJdJc3dkxh0+8YDBpbsyuxrXYzV4VfmJLFwxdAEq7azIj9Yw9AuUCjEL3I7pD1xgo0BPv5Y2U9MCBvegpGxq9/vK7BftOQssP3ueL+HfJwLkAqVLlgPH9CF7phgMoKj/X8EjiNCtGQHzFU9mM7gsR8W/5wkuZ9ZUKBcYuWdE3qU2YYqLIllfo5aog2m2haKnlsvP30YUHO+3f9Yu2GrNpW2rYV8id2bM/9KxBPc/QgZQT9AlotQRtgSls1pIDADvo+3hL0bXBU0yxqG4Fx2ZshdXSBaEjZYtIdh/uxQDOrpMV/Biatjj8nzRgq8p0Ud6w9fAwwe+9mSJPWUMWzPG+A21ZWG45nhoQp1RdaBZ1WYDteUQ4gffvH1jomTHlkxk9GTDi5AS0YAODxMoRjMiqsjq/MyqvFFJX9buv6+18Y8mwDyFRH7Dj+T1rBtYivRV1v9mHtiKhfD10QrRFdsxor9Z4bgChAtF22fLsv1sfsGL4B0grazi9DygpagZ0A2s2WBMBmLn0de15D11KG5WSAvh20rB92fEIVwk0jdV2qPdyWr4mYuw75NMua7FFtmZxbkN7qi4DSFgaqQjc75GwDwbgBMkMMAPfD6cR1wpl1o7GftDhMMSPaFmm05+Tv/HWW9aQXbMfiBAZ4cNLdnz6hyAyj7ki9oQYYqdYd4h405JRwXs4DhLKozKhzG65449eQ4i5nX2LKXYjCVDcWF58Y0uJvj2EpY2VN79NzjHlVF8E1M2JZxOzW62rIEhhlywoAMMhrS8dFBJhgMJx5aRxg/fv9rSW9WN7LeQxZPn4bo6ExYgppsWNkQIt2pOAU8DAK8Oh/yK7ECM8pCAboJDTRFNsJQ3ZkBdtFXgV+A5qAEasl4sk2WxjOzn9PY6sZ1ZxJD/p9FMwoc1pjLNhEbLd2eX2Kpv6Y+aSCn8OUxhqyGBOwS9fxUiwDPIXfZw2JeNs+hS2/2R6r2Lfv+S/ivt3PG7eHh3S/52EDDD0j5h9rStliJiQLc5/fW9wP0PNcfB77nmVpX6Js2WaOQwb9OrXLJ9UMz7UmuJaAQj7fjxi6V97wIGftsqVMwcGZN2ZTL30fr7IYkL4xpG/9Y9bV677pUYXfrHk5tzuvoS1aRPX9ScV+3+Mn1FU7YMR6GT8LEP38xs2OyzVZRjIz9mWrIV2lTYo6LLk3BKXGKCM47jycKCb4zb4GzBi0g3Ec0a9OsBVQQsp+YwTjo+Mr9C/MQluIJmIkYYvvzpL2RhuiKT1uttTrK+q74p8siUsR64/nlS3XedcfZgY6kfUsv/FOUZfOlwGTfjyPCxjrRDbCvMLr4vCc9kN26pBR7H8KuW0wHZrkYCzj2+z5WbPCoZM2rISeEwop48KRZdhiPtmYLXNSyZs91YAeH06dow/Vpg3o+W9a/hbgPI5jTnvdx5YxZUbrCY1V7De22qProHXrDL/9B8dlHIuM3QQqUxL7d/pLyyqrzKlV6/2O/F/GBXP6ochjYdiKvQ4saA1ddlTvY/bEkY9Wa1iLkEN2JVavOhxHRlvqMF/XAnEVOJgXy3fAhCgY0N8bC0Nnpl8Lw/bt2LnCnsENO6o5r7bMcn2hONzQswSK2WVbphBy2kcjGqqWNRJQfU6ALwFgHTlo41pWWaE+O4V2zuhYZ1jYinTnvVmcC0Oclck+MgTH1jZU9Ty/VWaUIS42JwFPpkiWyDiZafZDygiJTseTIrc/g5v1qYQ6kgVnT48A+bztNN774MD2U8kDphjxaP18nyGzZBGUh7Y9L1uGvIp6Mq3EePVl7Xxf2/pE9gWI2KTFX2J3xx8Z0jWvnv+VhaG6tr8vkccerdTfdvhaXzTlLUM8t6HNIa9a4DfuDYgCWEaUCQ5jBcz2YI43lgqsrRi21F+pRThPhW5NvqLDK0Nw5E7RV7DKjDreX69/ZVY14wGQ8+HN733OxHGci9MKTBlkMNCSMqJA/udFzgg5IWqXKW9pbwvDu9VxFIjREGtHAS1w7rs4bcXtpIEV1t7H9QdgfmPIGrTmKDAW+gmIFHxNaRl3iCKEvYcsjuPAgTEzQ5zO2SHY3q+FX98oqti8casANPRxL19nx34JWZ9XQJ4r+uOLpmwxZxGyZF8Bcb9lf+dUR0zZgjwCigMKmGltbXG/SzZmUzQlxzMzGCdcmVUDEH3OijXp7k5StNVSh7xnV6Nju98+MaTMaMjK+b7xCy2gD1vk+G54eVxM6PEzM2TLqjJk3IdR/4iG7RSF+y1klQk4jqGvb/h4n6TBQwHZ77GYE0alLzZOTOl1+ShiFvUyBYRJIvSC1PFPfYemMoW+EpWXtaznArCLitWSnX6BKnPOLkTWIT/3vB3SEFemqF+m0Bh7ZIoY13m7IfmhfyxNzsu90j/f65Bn5hSoMSHIvPE+nptWAKesbWRY6xJax/NG30AnvS4AAPkVEOuDmHBk3KJ+dsewIt41+3t8+kqRB85Sy82QAmZmpGrfGnfqejZrKlP8Rv+GIZ0nnWi/Ys74kWzs6Ly7INTfYvA4CshcGL6wpSJ8I1/GtLCs9ji0lQG+vgzBOkmInRh2a0Y1If0DYPmr5vTPwIv9hCWPXyBcjUGL+n+PhWEdL9Rhi390Wv2lxeHRjGZbYR0Os7DWgb0f15VVzPd5XVcgClAZ41se8uE/3efT7Eq7oQXk7xpSB3NcrfAYzGmFLUAL1MwYtabPjBeYXhcAAGolA3vSjJkZ88Zdx6+/pzrEaf35x9XeODgANYTlPox167cOfmJMAbnv4+1QGNA6pAwpg8qInhdAPa4GFTjrKQi5XSAsjrsf0qWSb3sjm+M4vQz5fNEaA1TGTMxo9vVu2wIATgWozlfavegXZJ7h/+3dYY7cOA7F8VQnu/e/5u4dtnqRwThD/KA/ywU0Jl9GQGCZkl0yHx9JSW4nGPRxKXuA8RzG/BygfJchBtujW5Ipzso9R5HGA998fC7MCMX2kYyrC+mrwd9z7+n5ZIMM9bODV8kYUjuEoeTPAyPemVxq0feB+cJ/xJYuKj3qFoL3r0khi4uHLEvLJwitTGlA4pz734whA7A+ElO05MEer2vLt9hPRgoQcVQmBUM6oNf7VxPlOXudyl7AYEI3Yk0z4TOYxXyEVDliCvOZp+NH8cmAEXNmevVk1p7fpr89MZyKP1l9xZIXzPmIdPmkkLlrqEL8bpbu8XUBJOTT+psdPAvPMV1LAnOlvgkIfyPoPsjxMxquUV2ncX/do65KmSvOriY70XSF2OecL0A7Yf08eYKMJeiHuFq7hE+A2V3WUGgE7sEKGHFjTuNKwAfp8HPGLRUA4N+GAv8QaOkqr91Yl9E+ActMDb059ktX92PIolQB+Hx9Ta9voeB8y/4A9mmCqjyzMvY5PhjHMchf7REHZNH15xSTIfafLss/R2hABEKmtPK5VwPszNms7xOwLB8VM5RrqZbprier+Ei/cncBZYzxQkCaIdv+uUvr9Mm3ViKmPIwBk0GAL7N0mRFjWh6z/+d0oRrAVGTMMSZwMwP9dOvW6077IcWUufB3cjnXUspxM2l+YvZwf2PHtFI+vEm/YMbhvGIJ/SI7Q4kyJGJVsk9mjIniD7Os27GEPjLiaLE323Nyimu85GXtMhM3kvs3F8BPsr+NISPNpR0WhKuaxvkSkIeu5PpW1rWqSrvnMqn+nvE4TxkymeA5zOnzcZnn34whtD281rb511sX+5mTPGVmrWWZP1vPd31Xix+gvMGcEwO9RycRvdFm7DH1TMB1VbqnQ1enDMYUWfNwtRelN1Nc32KVuIBA3qlxj6GMpZlhPYB4VIzyt4JhFyN+nXs/FH8vy5p7HlMZIjwyiGNGtCluDOrElNXKG+gEeAcw5j4HVygTbBf4qfTt/V7rP+YX5a7B+S1FFxFBvSeMs06/zfVk3f0Zf98gbJJge9WV6bKW9hP7vG8EeHcMUYxKOLkvY4D+WoDGMQO9TAt31oC2mysDAqhwaWBmf5iTACADEPZDZMJunYJG8NU9IburyOhztPgyjOt4igUE+3xW2SCYm7IFPQJ6z9SvclKKD6tchS0uyz4F/gocYDcjPLaRnTBIVzaex+Mp/pxiyXMAknvq5bZOR/vLBhX0oH8r/v6xx6d27x8FAgU3a2RixRCSooshjxUQPyV+zGooKD+PBUSwLmVfAWa09bOgZ9zQAwZMr/AcR1n10mWlHzaoNrv6WAogdazY9iXHUalOt+8FsJ8nt49rHYAMlxWAzBRYBWbATdkAVIujr22vLNd4Z+Z253pdh8quMQuMCp4NHUcA5PjWieDAjqByANJ1gRV0AcQ0yfioe6Rpldm2M6d/WuVHW7isUJ5lUV4yQ5mgC2y4SYGq/t5vjU8b4xmDynxo/TIi6iX7fgRk/ueQbrSMPY+rbrawAlfgFLA1nylSAHRbbjDj/n36ehk8ZBPome5eRZfV8w/lo+hK7Huso1TqgDkskvoat+L/X3QsGoDM2tHouKDcJmPWeT9kobMvXc+dwrkOhtJeK/Nm/XEXaCx01ssAWtc99rUOAJ6Uu/srhrQyWgn2g4K6GOvZL5TBwwSA742/x7ijZF3F7tfNUi7Lh5grvwbisoxwYReZamaf9VC8cWhVsAuV4Y5oYyaNcdAuoPa1TcPQZX3v/y16+N55kyFTGQIxNLcDpwIvFKe7cU7keHd2VMxrhR+Y+WXnx+xsrPbm4Mf+eTMn1mYGaFrjptDj/ZmkmjaXVWv19slxkTXJynCvnIdL8zdZOnn83A9ZFSTAB4VsSpGmyipunCjuGN9liwzZs8ddwQVEyzeyuGPYD7APuOS6o7aO9xWo/P3fbrnF8e5y2+7Lnamvyg8GKNeKog2m2NaW+SjwLCQhlr5/M6DamjnNlAbJLKtBaQZU226Ru2KbCe+Ph6Tk3THb/v5zaRQ7yz4M6usa1HywJU50n+7bgb4Z0sC2XIa8P56+JvvuWHTaa6kgbLn7ELvV9bU76A0+hpTKuh3PoryDwKvrflwx5F/1IMaK9wrK+h3ltf/+bb8d5d8/XdZ//txS/N9hxfdvGNg/ZQT1//4fih7V/hdi/qwAAAAASUVORK5CYII=);background-size:contain;cursor:crosshair;float:left}colorpicker-saturation i{display:block;height:7px;width:7px;border:1px solid #000;border-radius:5px;position:absolute;top:0;left:0;margin:-4px 0 0 -4px}colorpicker-saturation i::after{content:'';display:block;height:7px;width:7px;border:1px solid #fff;border-radius:5px}colorpicker-alpha,colorpicker-hue{width:15px;height:100px;float:left;cursor:row-resize;margin-left:4px;margin-bottom:4px}colorpicker-alpha i,colorpicker-hue i{display:block;height:2px;background:#000;border-top:1px solid #fff;position:absolute;top:0;left:0;width:100%;margin-top:-1px}.colorpicker,colorpicker-alpha{display:none}colorpicker-hue{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAABkCAMAAABw8qpSAAABLFBMVEUA/z8AuP//JAAA/33/3AAA/1ABAv8A/7r/AH7/jgD2AP8A//j/AEHmAP/XAP/HAP+4AP//ALyoAP+aAP+JAP97AP9rAP9cAP9MAP8+AP8tAP8fAP8PAP8ATv//AG7/cAD/vgD/APoAmv//ADH/AKwB/wMA5//4Eg4AL///AOr/UQD/nwAA/27/7AAA/+kAe/8Ayf8A/5sA/zEA/6z/ABEAEP8A/17/MgAA/9n/ACL/gAD/AJ0AXP8Aqv//AMoA/yHqFBb/zAD/AGD/ANsA9//1/wDk/wDV/wDF/wC3/wD/AI2m/wD/FACY/wCI/wB5/wBp/wD/YgBb/wBK/wA8/wAs/wAd/wAN/wAAPv8A/xH/AFAAi///rQAA/8r/+gAA1///QwAAH/8Abf8A/43c/JNGAAAAiUlEQVR4AQXBg2EDAAAAsMy2bds2ttp2+/8PTby79mDLsKJPq/oFPdk24dWXAxsGjRg1ZtykKdNmzJozb8GiJct63WjYl7fiWdOZkk0vOpyr2fVtyKl7FX2uXGjpcuxWDy69KdiRk5WRlpIUFxMVERLw78+vH1Unun1YV3ZkwKM1CYfq7nQK22sD03ITV2Aqp0IAAAAASUVORK5CYII=);background-size:contain}.colorpicker-color,colorpicker-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAABkCAMAAACIElGlAAADAFBMVEUAAADT09PT09P////T09P////e3t7q6urT09Px8fHT09P////////T09PT09P////////T09PT09P////////////T09PT09P////////////T09P////T09PT09PT09P////T09PT09P////////////////T09P////T09PT09P////T09PT09PT09PT09PT09PT09P////T09P////T09PT09P////////////T09P////T09P////////////T09PT09P////T09P////////////////////T09P////////T09PT09P////////////////////////T09PT09P////////////////////////T09PT09P////T09PT09P////////T09P////////////T09P////////T09P////T09P////T09P////T09PT09PT09PT09P////T09PT09PT09PT09PT09PT09P////T09P////T09PT09P////////////T09PT09PT09P////T09PT09PT09PT09PT09PT09PT09P////////////////T09PT09P////////////T09P////////T09P////T09PT09PT09P////////T09P////////T09P////T09PT09P////////////////T09PT09PT09PT09P////T09PT09PT09PT09PT09PT09PT09P////T09P////T09PT09PT09PT09PT09P////////////////////////////////////T09P////T09P////T09P////T09PT09P////////////T09P////T09P////T09P////////////T09PT09P////////////////T09PT09P////T09P////T09PT09P////T09P////T09PT09P////T09PT09PT09PT09P////T09PT09P////////////T09PT09P////T09P////////T09MQsm1FAAABAHRSTlMAgJN8/vcDAfcCnJyGaZmZlomGk4yJOmM/eTxs8wY0YDFC7HNdLx18n5/7aUvzCcW9+qKiK8P0ZiltRwfdw/n8Px3WduJjItj78ss5PDHUNELbwP5wplA2FglEVwvkqNarCs4Z7b2sDLgQ0xNdyLrr0eLLUeW1Vs5TWQLwjPI3ZvQGdvxFyFrAeevaLCLvGd0kpRskGyf4qK605xKvFrGyDRHnBYMEkJaDkIBvB/gpH99O6CrbIC4nH3Lg2SXp4A7Qul/GDEgPSlMQ6LjqFU0SjyCCj5V/gnBN7xglL3O70WBU7gjFFEfft0sPTo1ndRipwXr2yRemWRVENq+ytbGuxGaWGQAAAnpJREFUeNpNxmdcDHAAxvEHOWXvmXX23ntv2XvvMg/Z44x0KaRBSnfcKSqlnYZKp1QqGpT20KZh783/eePj+3vzw3/q/AM9iURPTy6XS+RwcXNzcZMQTE1dTE3bE7y9S0u9xxD600rCGtpI2ErOsbGxzoiMrIqscnaOi4vDdnp4XYSrguwxQSb7KpMllycnl2MFaTSaTxrY5NrY5BoKb2BnZ2doZ29vb2OPSdSd4Bcc7OcXGhqamgqrYCsrq+mEiEtC3gUBDg55Dg53IkQYSYG+gb6+GEqB5wV0o5D8/JAQdKK5hA40n3BS+L6c0JJmE+ZQW8JEak1YQJ0Jq+jbaQEPnotGE+IrK+Pj+xKqU6pTUrIDAgKysZ+WEY5QQkJxcQIO0WrCUbL4YmFhgZ3UlVAUHR1d1JEwjVoRRpDJKxMTE/ShBoRR1I6whLyyvLyyUFAQFhbWhTCFFArFawXOCu/bEHpQLYKnZ0aGZw2CR1qah0dmpqurK+pTU0ILakJoTi+PC7j5RHTrqQg1qRkh3T3d3b0xoSE1ItSleoSe1IswmIYQPl8UlhLWkaOj41tHbKENBLX6j1qt/KFUKnFN+Onk9NvJCQdJpfqlUqGiIjExcRthN91/JsJmGkhYRO/OCBhHwwgxMWUxZQMIu8j8o7m5OfbRLMJh2kTYQ1FRhYVR2EHzCOtpMmEG3SUsJEvLoKAg2Prb2tqOJYSH+/uH33shwlTaS1hLBwhX6AYX2tuiRwStVntOa5yUZJwEqbREKjUi5Bjl5BhZW1uXWOMYGRBOnNDX1zfoxz1FwwkfSJfgo6vr41P7soBBNJ7Qm2YSzHTMzMwmEHRoMf0Fm5mYOUrzNBYAAAAASUVORK5CYII=);background-size:10px 100%}.colorpicker{top:0;left:0;z-index:99999}.colorpicker colorpicker-alpha,.colorpicker colorpicker-hue,.colorpicker colorpicker-saturation{position:relative}.colorpicker input{width:100px;font-size:11px;color:#000;background-color:#fff}.colorpicker.alpha{min-width:140px}.colorpicker.alpha colorpicker-alpha{display:block}.colorpicker.dropdown{position:absolute}.colorpicker.colorpicker-fixed-position{position:fixed}.colorpicker .dropdown-menu::after,.colorpicker .dropdown-menu::before{content:'';display:inline-block;position:absolute}.colorpicker .dropdown-menu::after{clear:both;border:6px solid transparent;top:-5px;left:7px}.colorpicker .dropdown-menu::before{border:7px solid transparent;top:-6px;left:6px}.colorpicker .dropdown-menu{position:static;top:0;left:0;min-width:129px;padding:4px;margin-top:0}.colorpicker-position-top .dropdown-menu::after{border-top:6px solid #fff;border-bottom:0;top:auto;bottom:-5px}.colorpicker-position-top .dropdown-menu::before{border-top:7px solid rgba(0,0,0,.2);border-bottom:0;top:auto;bottom:-6px}.colorpicker-position-right .dropdown-menu::after{border-right:6px solid #fff;border-left:0;top:11px;left:-5px}.colorpicker-position-right .dropdown-menu::before{border-right:7px solid rgba(0,0,0,.2);border-left:0;top:10px;left:-6px}.colorpicker-position-bottom .dropdown-menu::after{border-bottom:6px solid #fff;border-top:0}.colorpicker-position-bottom .dropdown-menu::before{border-bottom:7px solid rgba(0,0,0,.2);border-top:0}.colorpicker-position-left .dropdown-menu::after{border-left:6px solid #fff;border-right:0;top:11px;left:auto;right:-5px}.colorpicker-position-left .dropdown-menu::before{border-left:7px solid rgba(0,0,0,.2);border-right:0;top:10px;left:auto;right:-6px}colorpicker-preview{display:block;height:10px;margin:5px 0 3px;clear:both;background-position:0 100%}
\ No newline at end of file
diff --git a/src/main/resources/public/img/alpha.png b/src/main/resources/public/img/alpha.png
new file mode 100644
index 0000000..eaddb40
Binary files /dev/null and b/src/main/resources/public/img/alpha.png differ
diff --git a/src/main/resources/public/img/hue.png b/src/main/resources/public/img/hue.png
new file mode 100644
index 0000000..ad35ccb
Binary files /dev/null and b/src/main/resources/public/img/hue.png differ
diff --git a/src/main/resources/public/img/saturation.png b/src/main/resources/public/img/saturation.png
new file mode 100644
index 0000000..82a4e3d
Binary files /dev/null and b/src/main/resources/public/img/saturation.png differ
diff --git a/src/main/resources/public/index.html b/src/main/resources/public/index.html
index 6d23c7e..afc97f4 100644
--- a/src/main/resources/public/index.html
+++ b/src/main/resources/public/index.html
@@ -15,6 +15,7 @@
 	
 	
 	
+