diff --git a/pom.xml b/pom.xml index dad9ffd..bfdf537 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bwssystems.HABridge ha-bridge - 3.5.1k + 3.5.1l jar HA Bridge diff --git a/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java b/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java index 77738c0..1d983fc 100644 --- a/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java +++ b/src/main/java/com/bwssystems/HABridge/DeviceMapTypes.java @@ -15,7 +15,7 @@ public class DeviceMapTypes { public final static String[] HAL_HOME = { "halHome", "HAL Home Status"}; public final static String[] HAL_THERMO_SET = { "halThermoSet", "HAL Thermostat"}; public final static String[] MQTT_MESSAGE = { "mqttMessage", "MQTT Message"}; - public final static String[] EXEC_DEVICE = { "execDevice", "Execute Script/Program"}; + public final static String[] EXEC_DEVICE = { "exec", "Execute Script/Program"}; public final static String[] HASS_DEVICE = { "hassDevice", "HomeAssistant Device"}; public final static int typeIndex = 0; diff --git a/src/main/java/com/bwssystems/HABridge/Home.java b/src/main/java/com/bwssystems/HABridge/Home.java new file mode 100644 index 0000000..414bca0 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/Home.java @@ -0,0 +1,7 @@ +package com.bwssystems.HABridge; + +import com.bwssystems.HABridge.hue.HueMulatorHandler; + +public interface Home extends HueMulatorHandler { + public Home createHome(BridgeSettingsDescriptor bridgeSettings); +} diff --git a/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java new file mode 100644 index 0000000..8f2aee5 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/BrightnessDecode.java @@ -0,0 +1,103 @@ +package com.bwssystems.HABridge.hue; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.bind.DatatypeConverter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.bwssystems.HABridge.api.hue.DeviceState; +import com.bwssystems.HABridge.api.hue.StateChangeBody; + +import net.java.dev.eval.Expression; + +public class BrightnessDecode { + private static final Logger log = LoggerFactory.getLogger(BrightnessDecode.class); + private static final String INTENSITY_PERCENT = "${intensity.percent}"; + private static final String INTENSITY_BYTE = "${intensity.byte}"; + private static final String INTENSITY_MATH = "${intensity.math("; + private static final String INTENSITY_MATH_VALUE = "X"; + private static final String INTENSITY_MATH_CLOSE = ")}"; + + public static int calculateIntensity(DeviceState state, StateChangeBody theChanges, boolean hasBri, boolean hasBriInc) { + int setIntensity = state.getBri(); + if (hasBri) { + setIntensity = theChanges.getBri(); + } else if (hasBriInc) { + if ((setIntensity + theChanges.getBri_inc()) <= 0) + setIntensity = theChanges.getBri_inc(); + else if ((setIntensity + theChanges.getBri_inc()) > 255) + setIntensity = theChanges.getBri_inc(); + else + setIntensity = setIntensity + theChanges.getBri_inc(); + } + return setIntensity; + } + + /* + * light weight templating here, was going to use free marker but it was a + * bit too heavy for what we were trying to do. + * + * currently provides: intensity.byte : 0-255 brightness. this is raw from + * the echo intensity.percent : 0-100, adjusted for the vera + * 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) { + if (request == null) { + return null; + } + if (request.contains(INTENSITY_BYTE)) { + if (isHex) { + BigInteger bigInt = BigInteger.valueOf(intensity); + byte[] theBytes = bigInt.toByteArray(); + String hexValue = DatatypeConverter.printHexBinary(theBytes); + request = request.replace(INTENSITY_BYTE, hexValue); + } else { + String intensityByte = String.valueOf(intensity); + request = request.replace(INTENSITY_BYTE, intensityByte); + } + } else if (request.contains(INTENSITY_PERCENT)) { + int percentBrightness = (int) Math.round(intensity / 255.0 * 100); + if (isHex) { + BigInteger bigInt = BigInteger.valueOf(percentBrightness); + byte[] theBytes = bigInt.toByteArray(); + String hexValue = DatatypeConverter.printHexBinary(theBytes); + request = request.replace(INTENSITY_PERCENT, hexValue); + } else { + String intensityPercent = String.valueOf(percentBrightness); + request = request.replace(INTENSITY_PERCENT, intensityPercent); + } + } else if (request.contains(INTENSITY_MATH)) { + Map variables = new HashMap(); + String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(), + request.indexOf(INTENSITY_MATH_CLOSE)); + variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity)); + + try { + log.debug("Math eval is: " + mathDescriptor + ", Where " + INTENSITY_MATH_VALUE + " is: " + + String.valueOf(intensity)); + Expression exp = new Expression(mathDescriptor); + BigDecimal result = exp.eval(variables); + Integer endResult = Math.round(result.floatValue()); + if (isHex) { + BigInteger bigInt = BigInteger.valueOf(endResult); + byte[] theBytes = bigInt.toByteArray(); + String hexValue = DatatypeConverter.printHexBinary(theBytes); + request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue); + } else { + request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, + endResult.toString()); + } + } catch (Exception e) { + log.warn("Could not execute Math: " + mathDescriptor, e); + } + } + return request; + } + +} diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 7fb05e4..200a46b 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -228,6 +228,15 @@ public class HueMulator { return lightsListHandler("lights", request.params(":userid"), request.ip()); } , new JsonTransformer()); + // http://ip_address:port/api/{userId}/lights/ returns json objects of + // all lights configured + get(HUE_CONTEXT + "/:userid/lights/", "application/json", (request, response) -> { + response.header("Access-Control-Allow-Origin", request.headers("Origin")); + response.type("application/json"); + response.status(HttpStatus.SC_OK); + return lightsListHandler("lights", request.params(":userid"), request.ip()); + } , new JsonTransformer()); + // http://ip_address:port/api CORS request options(HUE_CONTEXT, "application/json", (request, response) -> { response.status(HttpStatus.SC_OK); @@ -327,69 +336,7 @@ public class HueMulator { response.header("Access-Control-Allow-Origin", request.headers("Origin")); response.type("application/json"); response.status(HttpStatus.SC_OK); - String userId = request.params(":userid"); - String lightId = request.params(":id"); - String responseString = null; - StateChangeBody theStateChanges = null; - DeviceState state = null; - boolean stateHasBri = false; - boolean stateHasBriInc = false; - log.debug("Update state requested: " + userId + " from " + request.ip() + " body: " + request.body()); - HueError[] theErrors = validateWhitelistUser(userId, false); - if (theErrors != null) - return aGsonHandler.toJson(theErrors); - theStateChanges = aGsonHandler.fromJson(request.body(), StateChangeBody.class); - if (theStateChanges == null) { - log.warn("Could not parse state change body. Light state not changed."); - responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Could not parse state change body.\"}}]"; - return responseString; - } - - if (request.body().contains("\"bri\"")) { - if (theStateChanges.isOn() && theStateChanges.getBri() == 0) - stateHasBri = false; - else - stateHasBri = true; - } - if (request.body().contains("\"bri_inc\"")) - stateHasBriInc = true; - - DeviceDescriptor device = repository.findOne(lightId); - if (device == null) { - log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " - + request.ip() + " body: " + request.body()); - responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]"; - return responseString; - } - state = device.getDeviceState(); - if (state == null) - state = DeviceState.createDeviceState(); - state.fillIn(); - if (stateHasBri) { - if (theStateChanges.getBri() > 0 && !state.isOn()) - state.setOn(true); - } else if (stateHasBriInc) { - if ((state.getBri() + theStateChanges.getBri_inc()) > 0 && !state.isOn()) - state.setOn(true); - else if ((state.getBri() + theStateChanges.getBri_inc()) <= 0 && state.isOn()) - state.setOn(false); - } else { - if (theStateChanges.isOn()) { - state.setOn(true); - if (state.getBri() <= 0) - state.setBri(255); - } else { - state.setOn(false); - state.setBri(0); - } - } - responseString = this.formatSuccessHueResponse(theStateChanges, request.body(), lightId, - device.getDeviceState()); - device.getDeviceState().setBri(calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc)); - - return responseString; + return updateState(request.params(":userid"), request.params(":id"), request.body(), request.ip()); }); // http://ip_address:port/api/:userid/lights/:id/state CORS request @@ -407,498 +354,7 @@ public class HueMulator { response.header("Access-Control-Allow-Origin", request.headers("Origin")); response.type("application/json"); response.status(HttpStatus.SC_OK); - /** - * strangely enough the Echo sends a content type of - * application/x-www-form-urlencoded even though it sends a json - * object - */ - String userId = request.params(":userid"); - String lightId = request.params(":id"); - String responseString = null; - String url = null; - NameValue[] theHeaders = null; - StateChangeBody theStateChanges = null; - DeviceState state = null; - boolean stateHasBri = false; - boolean stateHasBriInc = false; - this.setTheDelay(bridgeSettings.getButtonsleep()); - this.setSetCount(1); - log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body()); - HueError[] theErrors = validateWhitelistUser(userId, false); - if (theErrors != null) - return aGsonHandler.toJson(theErrors); - - theStateChanges = aGsonHandler.fromJson(request.body(), StateChangeBody.class); - if (theStateChanges == null) { - log.warn("Could not parse state change body. Light state not changed."); - responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Could not parse state change body.\"}}]"; - return responseString; - } - - if (request.body().contains("\"bri\"")) { - stateHasBri = true; - } - if (request.body().contains("\"bri_inc\"")) - stateHasBriInc = true; - - DeviceDescriptor device = repository.findOne(lightId); - if (device == null) { - log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " - + request.ip() + " body: " + request.body()); - responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]"; - return responseString; - } - - state = device.getDeviceState(); - if (state == null) - state = DeviceState.createDeviceState(); - - theHeaders = aGsonHandler.fromJson(device.getHeaders(), NameValue[].class); - - if (stateHasBri) { - if(!state.isOn()) - state.setOn(true); - - url = device.getDimUrl(); - - if (url == null || url.length() == 0) - url = device.getOnUrl(); - } else if (stateHasBriInc) { - if(!state.isOn()) - state.setOn(true); - if ((state.getBri() + theStateChanges.getBri_inc()) <= 0) - state.setBri(theStateChanges.getBri_inc()); - - url = device.getDimUrl(); - - if (url == null || url.length() == 0) - url = device.getOnUrl(); - } else { - if (theStateChanges.isOn()) { - url = device.getOnUrl(); - state.setOn(true); -// if (state.getBri() <= 0) -// state.setBri(255); - } else if (!theStateChanges.isOn()) { - url = device.getOffUrl(); - state.setOn(false); -// state.setBri(0); - } - } - - // code for backwards compatibility - if(!(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]))) { - if(url == null) - url = device.getOnUrl(); - } - CallItem[] callItems = null; - if (url == null) { - log.warn("Could not find url: " + lightId + " for hue state change request: " + userId + " from " - + request.ip() + " body: " + request.body()); - responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Could not find url\", \"resource\": \"/lights/" + lightId + "\"}}]"; - } - else { - if (!url.startsWith("[")) { - if (url.startsWith("{\"item")) - url = "[" + url + "]"; - else - url = "[{\"item\":\"" + url + "\"}]"; - } - - // CallItem[] callItems = callItemGson.fromJson(url, - // CallItem[].class); - callItems = aGsonHandler.fromJson(url, CallItem[].class); - } - - for (int i = 0; callItems != null && i < callItems.length; i++) { - if(!filterByRequester(callItems[i].getFilterIPs(), request.ip())) { - log.debug("filter for requester address not present in list: " + callItems[i].getFilterIPs() + " with request ip of: " + request.ip()); - continue; - } - if (callItems[i].getCount() != null && callItems[i].getCount() > 0) - setCount = callItems[i].getCount(); - else - setCount = 1; - // code for backwards compatibility - if((callItems[i].getType() == null || callItems[i].getType().trim().length() == 0)) { - if(device.getMapType() != null && device.getMapType().length() > 0) - callItems[i].setType(device.getMapType()); - else if(device.getDeviceType() != null && device.getDeviceType().length() > 0) - callItems[i].setType(device.getDeviceType()); - else - callItems[i].setType(DeviceMapTypes.CUSTOM_DEVICE[DeviceMapTypes.typeIndex]); - } - - if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) { - if (myHueHome != null) { - - HueDeviceIdentifier deviceId = aGsonHandler.fromJson(callItems[i].getItem(), HueDeviceIdentifier.class); - if (myHueHome.getTheHUERegisteredUser() == null) { - hueUser = HueUtil.registerWithHue(httpClient, deviceId.getIpAddress(), device.getName(), - myHueHome.getTheHUERegisteredUser()); - if (hueUser == null) { - return errorString; - } - myHueHome.setTheHUERegisteredUser(hueUser); - } - - // make call - for (int x = 0; x < setCount; x++) { - if (x > 0 || i > 0) { - Thread.sleep(theDelay); - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) - theDelay = callItems[i].getDelay(); - else - theDelay = bridgeSettings.getButtonsleep(); - responseString = doHttpRequest( - "http://" + deviceId.getIpAddress() + "/api/" + myHueHome.getTheHUERegisteredUser() - + "/lights/" + deviceId.getDeviceId() + "/state", - HttpPut.METHOD_NAME, device.getContentType(), request.body(), null); - if (responseString.contains("[{\"error\":")) - x = setCount; - } - if (responseString == null) { - log.warn("Error on calling url to change device state: " + url); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Error on calling HUE to change device state\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } else if (responseString.contains("[{\"error\":")) { - if(responseString.contains("unauthorized user")) { - myHueHome.setTheHUERegisteredUser(null); - hueUser = HueUtil.registerWithHue(httpClient, deviceId.getIpAddress(), device.getName(), - myHueHome.getTheHUERegisteredUser()); - if (hueUser == null) { - return errorString; - } - myHueHome.setTheHUERegisteredUser(hueUser); - } - else - log.warn("Error occurred when calling Hue Passthru: " + responseString); - } - } else { - log.warn("No HUE home configured for HUE device passthru call for deviceID: " + device.getId()); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"No HUE configured\", \"parameter\": \"/lights/" + lightId - + "state\"}}]"; - } - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HARMONY_ACTIVITY[DeviceMapTypes.typeIndex])) { - log.debug("executing HUE api request to change activity to Harmony: " + url); - if (myHarmonyHome != null) { - RunActivity anActivity = aGsonHandler.fromJson(url, RunActivity.class); - HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice()); - if (myHarmony == null) { - log.warn("Should not get here, no harmony hub available"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } else { - for (int x = 0; x < setCount; x++) { - if (x > 0 || i > 0) { - Thread.sleep(theDelay); - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) - theDelay = callItems[i].getDelay(); - else - theDelay = bridgeSettings.getButtonsleep(); - myHarmony.startActivity(anActivity); - } - } - } else { - log.warn("Should not get here, no harmony configured"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no harmony configured\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HARMONY_BUTTON[DeviceMapTypes.typeIndex])) { - log.debug("executing HUE api request to button press(es) to Harmony: " + url); - if (myHarmonyHome != null) { - if (url.substring(0, 1).equalsIgnoreCase("{")) { - url = "[" + url + "]"; - } - ButtonPress[] deviceButtons = aGsonHandler.fromJson(url, ButtonPress[].class); - HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice()); - if (myHarmony == null) { - log.warn("Should not get here, no harmony hub available"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } else { - if(deviceButtons.length > 1) { - Integer theCount = 1; - for(int z = 0; z < deviceButtons.length; z++) { - if(deviceButtons[z].getCount() != null && deviceButtons[z].getCount() > 0) - theCount = deviceButtons[z].getCount(); - else - theCount = 1; - for(int y = 0; y < theCount; y++) { - if( y > 0 || z > 0) { - Thread.sleep(theDelay); - } - if(deviceButtons[z].getDelay() != null && deviceButtons[z].getDelay() > 0) - theDelay = deviceButtons[z].getDelay(); - else - theDelay = bridgeSettings.getButtonsleep(); - log.debug("pressing button: " + deviceButtons[z].getDevice() + " - " + deviceButtons[z].getButton() + " - iteration: " + String.valueOf(z) + " - count: " + String.valueOf(y)); - myHarmony.pressButton(deviceButtons[z]); - } - } - } - else { - for (int x = 0; x < setCount; x++) { - if (x > 0 || i > 0) { - Thread.sleep(theDelay); - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) { - theDelay = callItems[i].getDelay(); - } - else - theDelay = bridgeSettings.getButtonsleep(); - log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " - + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i) - + " - count: " + String.valueOf(x)); - myHarmony.pressButton(deviceButtons[i]); - } - } - } - } else { - log.warn("Should not get here, no harmony configured"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no harmony configured\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - - } - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.NEST_HOMEAWAY[DeviceMapTypes.typeIndex])) { - log.debug("executing HUE api request to set away for nest home: " + url); - if (theNest == null) { - log.warn("Should not get here, no Nest available"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } else { - NestInstruction homeAway = aGsonHandler.fromJson(url, NestInstruction.class); - theNest.getHome(homeAway.getName()).setAway(homeAway.getAway()); - } - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.NEST_THERMO_SET[DeviceMapTypes.typeIndex])) { - log.debug("executing HUE api request to set thermostat for nest: " + url); - if (theNest == null) { - log.warn("Should not get here, no Nest available"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } else { - NestInstruction thermoSetting = aGsonHandler.fromJson(url, NestInstruction.class); - if (thermoSetting.getControl().equalsIgnoreCase("temp")) { - if (request.body().contains("bri")) { - if (bridgeSettings.isFarenheit()) - thermoSetting - .setTemp( - String.valueOf((Double - .parseDouble(replaceIntensityValue(thermoSetting.getTemp(), - calculateIntensity(state, theStateChanges, - stateHasBri, stateHasBriInc), - false)) - - 32.0) / 1.8)); - else - thermoSetting - .setTemp( - String.valueOf(Double.parseDouble(replaceIntensityValue( - thermoSetting.getTemp(), calculateIntensity(state, - theStateChanges, stateHasBri, stateHasBriInc), - false)))); - log.debug("Setting thermostat: " + thermoSetting.getName() + " to " - + thermoSetting.getTemp() + "C"); - theNest.getThermostat(thermoSetting.getName()) - .setTargetTemperature(Float.parseFloat(thermoSetting.getTemp())); - } - } else if (thermoSetting.getControl().contains("range") - || thermoSetting.getControl().contains("heat") - || thermoSetting.getControl().contains("cool") - || thermoSetting.getControl().contains("off")) { - log.debug("Setting thermostat target type: " + thermoSetting.getName() + " to " - + thermoSetting.getControl()); - theNest.getThermostat(thermoSetting.getName()).setTargetType(thermoSetting.getControl()); - } else if (thermoSetting.getControl().contains("fan")) { - log.debug("Setting thermostat fan mode: " + thermoSetting.getName() + " to " - + thermoSetting.getControl().substring(4)); - theNest.getThermostat(thermoSetting.getName()) - .setFanMode(thermoSetting.getControl().substring(4)); - } else { - log.warn("no valid Nest control info: " + thermoSetting.getControl()); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"no valid Nest control info\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } - } - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.MQTT_MESSAGE[DeviceMapTypes.typeIndex])) { - log.debug("executing HUE api request to send message to MQTT broker: " + url); - if (mqttHome != null) { - MQTTMessage[] mqttMessages = aGsonHandler.fromJson(replaceIntensityValue(url, - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false), MQTTMessage[].class); - MQTTHandler mqttHandler = mqttHome.getMQTTHandler(mqttMessages[i].getClientId()); - if (mqttHandler == null) { - log.warn("Should not get here, no mqtt hanlder available"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no mqtt handler available\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } - for (int x = 0; x < setCount; x++) { - if (x > 0 || i > 0) { - Thread.sleep(theDelay); - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) - theDelay = callItems[i].getDelay(); - else - theDelay = bridgeSettings.getButtonsleep(); - log.debug("publishing message: " + mqttMessages[i].getClientId() + " - " - + mqttMessages[i].getTopic() + " - " + mqttMessages[i].getMessage() - + " - iteration: " + String.valueOf(i) + " - count: " + String.valueOf(x)); - mqttHandler.publishMessage(mqttMessages[i].getTopic(), mqttMessages[i].getMessage()); - } - } else { - log.warn("Should not get here, no mqtt brokers configured"); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no mqtt brokers configured\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - - } - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HASS_DEVICE[DeviceMapTypes.typeIndex])) { - responseString = hassDeviceHandler(callItems[i], lightId, i, state, theStateChanges, stateHasBri, stateHasBriInc); - } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.EXEC_DEVICE[DeviceMapTypes.typeIndex])) { - log.debug("Exec Request called with url: " + url); - String intermediate; - if (callItems[i].getItem().getAsString().contains("exec://")) - intermediate = callItems[i].getItem().getAsString().substring(callItems[i].getItem().getAsString().indexOf("://") + 3); - else - intermediate = callItems[i].getItem().getAsString(); - for (int x = 0; x < setCount; x++) { - if (x > 0 || i > 0) { - Thread.sleep(theDelay); - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) - theDelay = callItems[i].getDelay(); - else - theDelay = bridgeSettings.getButtonsleep(); - String anError = doExecRequest(intermediate, - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), lightId); - if (anError != null) { - responseString = anError; - x = setCount; - } - } - } else // This section allows the usage of http/tcp/udp/exec - // calls in a given set of items - { - log.debug("executing HUE api request for network call: " + url); - for (int x = 0; x < setCount; x++) { - if (x > 0 || i > 0) { - Thread.sleep(theDelay); - } - if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) - theDelay = callItems[i].getDelay(); - else - theDelay = bridgeSettings.getButtonsleep(); - try { - if (callItems[i].getItem().getAsString().contains("udp://") - || callItems[i].getItem().getAsString().contains("tcp://")) { - String intermediate = callItems[i].getItem().getAsString() - .substring(callItems[i].getItem().getAsString().indexOf("://") + 3); - String hostPortion = intermediate.substring(0, intermediate.indexOf('/')); - String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1); - String hostAddr = null; - String port = null; - if (hostPortion.contains(":")) { - hostAddr = hostPortion.substring(0, intermediate.indexOf(':')); - port = hostPortion.substring(intermediate.indexOf(':') + 1); - } else - hostAddr = hostPortion; - InetAddress IPAddress = InetAddress.getByName(hostAddr); - - if (theUrlBody.startsWith("0x")) { - theUrlBody = replaceIntensityValue(theUrlBody, - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), - true); - sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2)); - } else { - theUrlBody = replaceIntensityValue(theUrlBody, - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), - false); - sendData = theUrlBody.getBytes(); - } - if (callItems[i].getItem().getAsString().contains("udp://")) { - log.debug("executing HUE api request to UDP: " + callItems[i].getItem().getAsString()); - theUDPDatagramSender.sendUDPResponse(new String(sendData), IPAddress, - Integer.parseInt(port)); - } else if (callItems[i].getItem().getAsString().contains("tcp://")) { - log.debug("executing HUE api request to TCP: " + callItems[i].getItem().getAsString()); - Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port)); - DataOutputStream outToClient = new DataOutputStream( - dataSendSocket.getOutputStream()); - outToClient.write(sendData); - outToClient.flush(); - dataSendSocket.close(); - } - } else if (callItems[i].getItem().getAsString().contains("exec://")) { - String intermediate = callItems[i].getItem().getAsString() - .substring(callItems[i].getItem().getAsString().indexOf("://") + 3); - String anError = doExecRequest(intermediate, - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), - lightId); - if (anError != null) { - responseString = anError; - x = setCount; - } - } else { - log.debug("executing HUE api request to Http " - + (device.getHttpVerb() == null ? "GET" : device.getHttpVerb()) + ": " - + callItems[i].getItem().getAsString()); - - String anUrl = replaceIntensityValue(callItems[i].getItem().getAsString(), - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false); - String body; - if (stateHasBri || stateHasBriInc) - body = replaceIntensityValue(device.getContentBodyDim(), - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), - false); - else if (state.isOn()) - body = replaceIntensityValue(device.getContentBody(), - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), - false); - else - body = replaceIntensityValue(device.getContentBodyOff(), - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), - false); - // make call - if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), body, - theHeaders) == null) { - log.warn("Error on calling url to change device state: " + anUrl); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - x = setCount; - } - } - } catch (Exception e) { - log.warn("Change device state, Could not send data for network request: " - + callItems[i].getItem().getAsString() + " with Message: " + e.getMessage()); - responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - x = setCount; - } - } - } - } - - if (responseString == null || !responseString.contains("[{\"error\":")) { - responseString = this.formatSuccessHueResponse(theStateChanges, request.body(), lightId, state); - state.setBri(calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc)); - device.setDeviceState(state); - } - return responseString; + return changeState(request.params(":userid"), request.params(":id"), request.body(), request.ip()); }); } @@ -1245,8 +701,8 @@ public class HueMulator { private HueError[] validateWhitelistUser(String aUser, boolean strict) { String validUser = null; boolean found = false; - if (aUser != null && aUser.equalsIgnoreCase("undefined") && aUser.equalsIgnoreCase("null") - && aUser.equalsIgnoreCase("")) { + if (aUser != null && !aUser.equalsIgnoreCase("undefined") && !aUser.equalsIgnoreCase("null") + && !aUser.equalsIgnoreCase("")) { if (bridgeSettings.getWhitelist() != null) { Set theUserIds = bridgeSettings.getWhitelist().keySet(); Iterator userIterator = theUserIds.iterator(); @@ -1494,38 +950,78 @@ public class HueMulator { return lightResponse; } - + + private String updateState(String userId, String lightId, String body,String ipAddress) { + String responseString = null; + StateChangeBody theStateChanges = null; + DeviceState state = null; + boolean stateHasBri = false; + boolean stateHasBriInc = false; + log.debug("Update state requested: " + userId + " from " + ipAddress + " body: " + body); + HueError[] theErrors = validateWhitelistUser(userId, false); + if (theErrors != null) + return aGsonHandler.toJson(theErrors); + theStateChanges = aGsonHandler.fromJson(body, StateChangeBody.class); + if (theStateChanges == null) { + log.warn("Could not parse state change body. Light state not changed."); + responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Could not parse state change body.\"}}]"; + return responseString; + } + + if (body.contains("\"bri\"")) { + if (theStateChanges.isOn() && theStateChanges.getBri() == 0) + stateHasBri = false; + else + stateHasBri = true; + } + if (body.contains("\"bri_inc\"")) + stateHasBriInc = true; + + DeviceDescriptor device = repository.findOne(lightId); + if (device == null) { + log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body); + responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]"; + return responseString; + } + state = device.getDeviceState(); + if (state == null) + state = DeviceState.createDeviceState(); + state.fillIn(); + if (stateHasBri) { + if (theStateChanges.getBri() > 0 && !state.isOn()) + state.setOn(true); + } else if (stateHasBriInc) { + if ((state.getBri() + theStateChanges.getBri_inc()) > 0 && !state.isOn()) + state.setOn(true); + else if ((state.getBri() + theStateChanges.getBri_inc()) <= 0 && state.isOn()) + state.setOn(false); + } else { + if (theStateChanges.isOn()) { + state.setOn(true); + if (state.getBri() <= 0) + state.setBri(255); + } else { + state.setOn(false); + state.setBri(0); + } + } + responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, + device.getDeviceState()); + device.getDeviceState().setBri(calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc)); + + return responseString; + + } private String hassDeviceHandler(CallItem anItem, String lightId, int iterationCount, DeviceState state, StateChangeBody theStateChanges, boolean stateHasBri, boolean stateHasBriInc) { String theReturn = null; + MultiCommandUtil aMultiCmdUtil = new MultiCommandUtil(); + // TODO set multi ommmand util here log.debug("executing HUE api request to send message to HomeAssistant: " + anItem.getItem().toString()); if (hassHome != null) { - HassCommand hassCommand = aGsonHandler.fromJson(anItem.getItem(), HassCommand.class); - hassCommand.setBri(replaceIntensityValue(hassCommand.getBri(), - calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false)); - HomeAssistant homeAssistant = hassHome.getHomeAssistant(hassCommand.getHassName()); - if (homeAssistant == null) { - log.warn("Should not get here, no HomeAssistants available"); - theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId - + "\",\"description\": \"Should not get here, no HiomeAssistant clients available\", \"parameter\": \"/lights/" - + lightId + "state\"}}]"; - } - for (int x = 0; x < this.getSetCount(); x++) { - if (x > 0 || iterationCount > 0) { - try { - Thread.sleep(this.getTheDelay()); - } catch (InterruptedException e) { - // ignore - } - } - if (anItem.getDelay() != null && anItem.getDelay() > 0) - this.setTheDelay(anItem.getDelay()); - else - this.setTheDelay(bridgeSettings.getButtonsleep()); - log.debug("calling HomeAssistant: " + hassCommand.getHassName() + " - " - + hassCommand.getEntityId() + " - " + hassCommand.getState() + " - " + hassCommand.getBri() - + " - iteration: " + String.valueOf(iterationCount) + " - count: " + String.valueOf(x)); - homeAssistant.callCommand(hassCommand); - } + hassHome.deviceHandler(anItem, aMultiCmdUtil, lightId, iterationCount, state, theStateChanges, stateHasBri, stateHasBriInc); } else { log.warn("Should not get here, no HomeAssistant clients configured"); theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId @@ -1553,4 +1049,521 @@ public class HueMulator { return null; } + + private String changeState(String userId, String lightId, String body,String ipAddress) { + String responseString = null; + String url = null; + NameValue[] theHeaders = null; + StateChangeBody theStateChanges = null; + DeviceState state = null; + boolean stateHasBri = false; + boolean stateHasBriInc = false; + this.setTheDelay(bridgeSettings.getButtonsleep()); + this.setSetCount(1); + log.debug("hue state change requested: " + userId + " from " + ipAddress + " body: " + body); + HueError[] theErrors = validateWhitelistUser(userId, false); + if (theErrors != null) + return aGsonHandler.toJson(theErrors); + + theStateChanges = aGsonHandler.fromJson(body, StateChangeBody.class); + if (theStateChanges == null) { + log.warn("Could not parse state change body. Light state not changed."); + responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Could not parse state change body.\"}}]"; + return responseString; + } + + if (body.contains("\"bri\"")) { + stateHasBri = true; + } + if (body.contains("\"bri_inc\"")) + stateHasBriInc = true; + + DeviceDescriptor device = repository.findOne(lightId); + if (device == null) { + log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body); + responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]"; + return responseString; + } + + state = device.getDeviceState(); + if (state == null) + state = DeviceState.createDeviceState(); + + theHeaders = aGsonHandler.fromJson(device.getHeaders(), NameValue[].class); + + if (stateHasBri) { + if(!state.isOn()) + state.setOn(true); + + url = device.getDimUrl(); + + if (url == null || url.length() == 0) + url = device.getOnUrl(); + } else if (stateHasBriInc) { + if(!state.isOn()) + state.setOn(true); + if ((state.getBri() + theStateChanges.getBri_inc()) <= 0) + state.setBri(theStateChanges.getBri_inc()); + + url = device.getDimUrl(); + + if (url == null || url.length() == 0) + url = device.getOnUrl(); + } else { + if (theStateChanges.isOn()) { + url = device.getOnUrl(); + state.setOn(true); +// if (state.getBri() <= 0) +// state.setBri(255); + } else if (!theStateChanges.isOn()) { + url = device.getOffUrl(); + state.setOn(false); +// state.setBri(0); + } + } + + // code for backwards compatibility + if(!(device.getMapType() != null && device.getMapType().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex]))) { + if(url == null) + url = device.getOnUrl(); + } + CallItem[] callItems = null; + if (url == null) { + log.warn("Could not find url: " + lightId + " for hue state change request: " + userId + " from " + + ipAddress + " body: " + body); + responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Could not find url\", \"resource\": \"/lights/" + lightId + "\"}}]"; + } + else { + if (!url.startsWith("[")) { + if (url.startsWith("{\"item")) + url = "[" + url + "]"; + else + url = "[{\"item\":\"" + url + "\"}]"; + } + + // CallItem[] callItems = callItemGson.fromJson(url, + // CallItem[].class); + callItems = aGsonHandler.fromJson(url, CallItem[].class); + } + + for (int i = 0; callItems != null && i < callItems.length; i++) { + if(!filterByRequester(callItems[i].getFilterIPs(), ipAddress)) { + log.debug("filter for requester address not present in list: " + callItems[i].getFilterIPs() + " with request ip of: " + ipAddress); + continue; + } + if (callItems[i].getCount() != null && callItems[i].getCount() > 0) + setCount = callItems[i].getCount(); + else + setCount = 1; + // code for backwards compatibility + if((callItems[i].getType() == null || callItems[i].getType().trim().length() == 0)) { + if(device.getMapType() != null && device.getMapType().length() > 0) + callItems[i].setType(device.getMapType()); + else if(device.getDeviceType() != null && device.getDeviceType().length() > 0) + callItems[i].setType(device.getDeviceType()); + else + callItems[i].setType(DeviceMapTypes.CUSTOM_DEVICE[DeviceMapTypes.typeIndex]); + } + + if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HUE_DEVICE[DeviceMapTypes.typeIndex])) { + if (myHueHome != null) { + + HueDeviceIdentifier deviceId = aGsonHandler.fromJson(callItems[i].getItem(), HueDeviceIdentifier.class); + if (myHueHome.getTheHUERegisteredUser() == null) { + hueUser = HueUtil.registerWithHue(httpClient, deviceId.getIpAddress(), device.getName(), + myHueHome.getTheHUERegisteredUser()); + if (hueUser == null) { + return errorString; + } + myHueHome.setTheHUERegisteredUser(hueUser); + } + + // make call + for (int x = 0; x < setCount; x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + responseString = doHttpRequest( + "http://" + deviceId.getIpAddress() + "/api/" + myHueHome.getTheHUERegisteredUser() + + "/lights/" + deviceId.getDeviceId() + "/state", + HttpPut.METHOD_NAME, device.getContentType(), body, null); + if (responseString.contains("[{\"error\":")) + x = setCount; + } + if (responseString == null) { + log.warn("Error on calling url to change device state: " + url); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Error on calling HUE to change device state\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } else if (responseString.contains("[{\"error\":")) { + if(responseString.contains("unauthorized user")) { + myHueHome.setTheHUERegisteredUser(null); + hueUser = HueUtil.registerWithHue(httpClient, deviceId.getIpAddress(), device.getName(), + myHueHome.getTheHUERegisteredUser()); + if (hueUser == null) { + return errorString; + } + myHueHome.setTheHUERegisteredUser(hueUser); + } + else + log.warn("Error occurred when calling Hue Passthru: " + responseString); + } + } else { + log.warn("No HUE home configured for HUE device passthru call for deviceID: " + device.getId()); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"No HUE configured\", \"parameter\": \"/lights/" + lightId + + "state\"}}]"; + } + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HARMONY_ACTIVITY[DeviceMapTypes.typeIndex])) { + log.debug("executing HUE api request to change activity to Harmony: " + url); + if (myHarmonyHome != null) { + RunActivity anActivity = aGsonHandler.fromJson(url, RunActivity.class); + HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice()); + if (myHarmony == null) { + log.warn("Should not get here, no harmony hub available"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } else { + for (int x = 0; x < setCount; x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + myHarmony.startActivity(anActivity); + } + } + } else { + log.warn("Should not get here, no harmony configured"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no harmony configured\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HARMONY_BUTTON[DeviceMapTypes.typeIndex])) { + log.debug("executing HUE api request to button press(es) to Harmony: " + url); + if (myHarmonyHome != null) { + if (url.substring(0, 1).equalsIgnoreCase("{")) { + url = "[" + url + "]"; + } + ButtonPress[] deviceButtons = aGsonHandler.fromJson(url, ButtonPress[].class); + HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice()); + if (myHarmony == null) { + log.warn("Should not get here, no harmony hub available"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } else { + if(deviceButtons.length > 1) { + Integer theCount = 1; + for(int z = 0; z < deviceButtons.length; z++) { + if(deviceButtons[z].getCount() != null && deviceButtons[z].getCount() > 0) + theCount = deviceButtons[z].getCount(); + else + theCount = 1; + for(int y = 0; y < theCount; y++) { + if( y > 0 || z > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + log.debug("pressing button: " + deviceButtons[z].getDevice() + " - " + deviceButtons[z].getButton() + " - iteration: " + String.valueOf(z) + " - count: " + String.valueOf(y)); + myHarmony.pressButton(deviceButtons[z]); + } + } + } + else { + for (int x = 0; x < setCount; x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " + + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i) + + " - count: " + String.valueOf(x)); + myHarmony.pressButton(deviceButtons[i]); + } + } + } + } else { + log.warn("Should not get here, no harmony configured"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no harmony configured\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + + } + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.NEST_HOMEAWAY[DeviceMapTypes.typeIndex])) { + log.debug("executing HUE api request to set away for nest home: " + url); + if (theNest == null) { + log.warn("Should not get here, no Nest available"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } else { + NestInstruction homeAway = aGsonHandler.fromJson(url, NestInstruction.class); + theNest.getHome(homeAway.getName()).setAway(homeAway.getAway()); + } + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.NEST_THERMO_SET[DeviceMapTypes.typeIndex])) { + log.debug("executing HUE api request to set thermostat for nest: " + url); + if (theNest == null) { + log.warn("Should not get here, no Nest available"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } else { + NestInstruction thermoSetting = aGsonHandler.fromJson(url, NestInstruction.class); + if (thermoSetting.getControl().equalsIgnoreCase("temp")) { + if (body.contains("bri")) { + if (bridgeSettings.isFarenheit()) + thermoSetting + .setTemp( + String.valueOf((Double + .parseDouble(replaceIntensityValue(thermoSetting.getTemp(), + calculateIntensity(state, theStateChanges, + stateHasBri, stateHasBriInc), + false)) + - 32.0) / 1.8)); + else + thermoSetting + .setTemp( + String.valueOf(Double.parseDouble(replaceIntensityValue( + thermoSetting.getTemp(), calculateIntensity(state, + theStateChanges, stateHasBri, stateHasBriInc), + false)))); + log.debug("Setting thermostat: " + thermoSetting.getName() + " to " + + thermoSetting.getTemp() + "C"); + theNest.getThermostat(thermoSetting.getName()) + .setTargetTemperature(Float.parseFloat(thermoSetting.getTemp())); + } + } else if (thermoSetting.getControl().contains("range") + || thermoSetting.getControl().contains("heat") + || thermoSetting.getControl().contains("cool") + || thermoSetting.getControl().contains("off")) { + log.debug("Setting thermostat target type: " + thermoSetting.getName() + " to " + + thermoSetting.getControl()); + theNest.getThermostat(thermoSetting.getName()).setTargetType(thermoSetting.getControl()); + } else if (thermoSetting.getControl().contains("fan")) { + log.debug("Setting thermostat fan mode: " + thermoSetting.getName() + " to " + + thermoSetting.getControl().substring(4)); + theNest.getThermostat(thermoSetting.getName()) + .setFanMode(thermoSetting.getControl().substring(4)); + } else { + log.warn("no valid Nest control info: " + thermoSetting.getControl()); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"no valid Nest control info\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } + } + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.MQTT_MESSAGE[DeviceMapTypes.typeIndex])) { + log.debug("executing HUE api request to send message to MQTT broker: " + url); + if (mqttHome != null) { + MQTTMessage[] mqttMessages = aGsonHandler.fromJson(replaceIntensityValue(url, + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false), MQTTMessage[].class); + MQTTHandler mqttHandler = mqttHome.getMQTTHandler(mqttMessages[i].getClientId()); + if (mqttHandler == null) { + log.warn("Should not get here, no mqtt hanlder available"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no mqtt handler available\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } + for (int x = 0; x < setCount; x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + log.debug("publishing message: " + mqttMessages[i].getClientId() + " - " + + mqttMessages[i].getTopic() + " - " + mqttMessages[i].getMessage() + + " - iteration: " + String.valueOf(i) + " - count: " + String.valueOf(x)); + mqttHandler.publishMessage(mqttMessages[i].getTopic(), mqttMessages[i].getMessage()); + } + } else { + log.warn("Should not get here, no mqtt brokers configured"); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no mqtt brokers configured\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + + } + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.HASS_DEVICE[DeviceMapTypes.typeIndex])) { + responseString = hassDeviceHandler(callItems[i], lightId, i, state, theStateChanges, stateHasBri, stateHasBriInc); + } else if (callItems[i].getType() != null && callItems[i].getType().trim().equalsIgnoreCase(DeviceMapTypes.EXEC_DEVICE[DeviceMapTypes.typeIndex])) { + log.debug("Exec Request called with url: " + url); + String intermediate; + if (callItems[i].getItem().getAsString().contains("exec://")) + intermediate = callItems[i].getItem().getAsString().substring(callItems[i].getItem().getAsString().indexOf("://") + 3); + else + intermediate = callItems[i].getItem().getAsString(); + for (int x = 0; x < setCount; x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + String anError = doExecRequest(intermediate, + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), lightId); + if (anError != null) { + responseString = anError; + x = setCount; + } + } + } else // This section allows the usage of http/tcp/udp/exec + // calls in a given set of items + { + log.debug("executing HUE api request for network call: " + url); + for (int x = 0; x < setCount; x++) { + if (x > 0 || i > 0) { + try { + Thread.sleep(this.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (callItems[i].getDelay() != null && callItems[i].getDelay() > 0) + this.setTheDelay(callItems[i].getDelay()); + else + this.setTheDelay(bridgeSettings.getButtonsleep()); + try { + if (callItems[i].getItem().getAsString().contains("udp://") + || callItems[i].getItem().getAsString().contains("tcp://")) { + String intermediate = callItems[i].getItem().getAsString() + .substring(callItems[i].getItem().getAsString().indexOf("://") + 3); + String hostPortion = intermediate.substring(0, intermediate.indexOf('/')); + String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1); + String hostAddr = null; + String port = null; + if (hostPortion.contains(":")) { + hostAddr = hostPortion.substring(0, intermediate.indexOf(':')); + port = hostPortion.substring(intermediate.indexOf(':') + 1); + } else + hostAddr = hostPortion; + InetAddress IPAddress = InetAddress.getByName(hostAddr); + + if (theUrlBody.startsWith("0x")) { + theUrlBody = replaceIntensityValue(theUrlBody, + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), + true); + sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2)); + } else { + theUrlBody = replaceIntensityValue(theUrlBody, + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), + false); + sendData = theUrlBody.getBytes(); + } + if (callItems[i].getItem().getAsString().contains("udp://")) { + log.debug("executing HUE api request to UDP: " + callItems[i].getItem().getAsString()); + theUDPDatagramSender.sendUDPResponse(new String(sendData), IPAddress, + Integer.parseInt(port)); + } else if (callItems[i].getItem().getAsString().contains("tcp://")) { + log.debug("executing HUE api request to TCP: " + callItems[i].getItem().getAsString()); + Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port)); + DataOutputStream outToClient = new DataOutputStream( + dataSendSocket.getOutputStream()); + outToClient.write(sendData); + outToClient.flush(); + dataSendSocket.close(); + } + } else if (callItems[i].getItem().getAsString().contains("exec://")) { + String intermediate = callItems[i].getItem().getAsString() + .substring(callItems[i].getItem().getAsString().indexOf("://") + 3); + String anError = doExecRequest(intermediate, + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), + lightId); + if (anError != null) { + responseString = anError; + x = setCount; + } + } else { + log.debug("executing HUE api request to Http " + + (device.getHttpVerb() == null ? "GET" : device.getHttpVerb()) + ": " + + callItems[i].getItem().getAsString()); + + String anUrl = replaceIntensityValue(callItems[i].getItem().getAsString(), + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false); + String aBody; + if (stateHasBri || stateHasBriInc) + aBody = replaceIntensityValue(device.getContentBodyDim(), + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), + false); + else if (state.isOn()) + aBody = replaceIntensityValue(device.getContentBody(), + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), + false); + else + aBody = replaceIntensityValue(device.getContentBodyOff(), + calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), + false); + // make call + if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), aBody, + theHeaders) == null) { + log.warn("Error on calling url to change device state: " + anUrl); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + x = setCount; + } + } + } catch (Exception e) { + log.warn("Change device state, Could not send data for network request: " + + callItems[i].getItem().getAsString() + " with Message: " + e.getMessage()); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + x = setCount; + } + } + } + } + + if (responseString == null || !responseString.contains("[{\"error\":")) { + responseString = this.formatSuccessHueResponse(theStateChanges, body, lightId, state); + state.setBri(calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc)); + device.setDeviceState(state); + } + return responseString; + + } + } diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulatorHandler.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulatorHandler.java new file mode 100644 index 0000000..c822227 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulatorHandler.java @@ -0,0 +1,9 @@ +package com.bwssystems.HABridge.hue; + +import com.bwssystems.HABridge.api.CallItem; +import com.bwssystems.HABridge.api.hue.DeviceState; +import com.bwssystems.HABridge.api.hue.StateChangeBody; + +public interface HueMulatorHandler { + public String deviceHandler(CallItem anItem, MultiCommandUtil multiComand, String lightId, int iterationCount, DeviceState state, StateChangeBody theStateChanges, boolean stateHasBri, boolean stateHasBriInc); +} diff --git a/src/main/java/com/bwssystems/HABridge/hue/MultiCommandUtil.java b/src/main/java/com/bwssystems/HABridge/hue/MultiCommandUtil.java new file mode 100644 index 0000000..ea96da1 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/MultiCommandUtil.java @@ -0,0 +1,22 @@ +package com.bwssystems.HABridge.hue; + +public class MultiCommandUtil { + private Integer setCount; + private Integer theDelay; + + public Integer getSetCount() { + return setCount; + } + + public void setSetCount(Integer setCount) { + this.setCount = setCount; + } + + public Integer getTheDelay() { + return theDelay; + } + + public void setTheDelay(Integer theDelay) { + this.theDelay = theDelay; + } +} diff --git a/src/main/java/com/bwssystems/hass/HassHome.java b/src/main/java/com/bwssystems/hass/HassHome.java index 30a5b5b..2a014e9 100644 --- a/src/main/java/com/bwssystems/hass/HassHome.java +++ b/src/main/java/com/bwssystems/hass/HassHome.java @@ -10,21 +10,34 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bwssystems.HABridge.BridgeSettingsDescriptor; +import com.bwssystems.HABridge.Home; import com.bwssystems.HABridge.NamedIP; +import com.bwssystems.HABridge.api.CallItem; +import com.bwssystems.HABridge.api.hue.DeviceState; +import com.bwssystems.HABridge.api.hue.StateChangeBody; +import com.bwssystems.HABridge.hue.BrightnessDecode; +import com.bwssystems.HABridge.hue.HueMulatorHandler; +import com.bwssystems.HABridge.hue.MultiCommandUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; -public class HassHome { +public class HassHome implements Home { private static final Logger log = LoggerFactory.getLogger(HassHome.class); private Map hassMap; private Boolean validHass; + private Gson aGsonHandler; public HassHome(BridgeSettingsDescriptor bridgeSettings) { super(); + createHome(bridgeSettings); + } + + @Override + public Home createHome(BridgeSettingsDescriptor bridgeSettings) { validHass = bridgeSettings.isValidHass(); if(!validHass) - return; + return null; hassMap = new HashMap(); - if(!bridgeSettings.isValidHass()) - return; Iterator theList = bridgeSettings.getHassaddress().getDevices().iterator(); while(theList.hasNext()) { NamedIP aHass = theList.next(); @@ -32,9 +45,13 @@ public class HassHome { hassMap.put(aHass.getName(), new HomeAssistant(aHass)); } catch (Exception e) { log.error("Cannot get hass (" + aHass.getName() + ") setup, Exiting with message: " + e.getMessage(), e); - return; + return null; } } + aGsonHandler = + new GsonBuilder() + .create(); + return this; } public HomeAssistant getHomeAssistant(String aName) { @@ -86,4 +103,40 @@ public class HassHome { } return true; } + + @Override + public String deviceHandler(CallItem anItem, MultiCommandUtil multiComand, String lightId, int iterationCount, DeviceState state, + StateChangeBody theStateChanges, boolean stateHasBri, boolean stateHasBriInc) { + String theReturn = null; + log.debug("executing HUE api request to send message to HomeAssistant: " + anItem.getItem().toString()); + HassCommand hassCommand = aGsonHandler.fromJson(anItem.getItem(), HassCommand.class); + hassCommand.setBri(BrightnessDecode.replaceIntensityValue(hassCommand.getBri(), + BrightnessDecode.calculateIntensity(state, theStateChanges, stateHasBri, stateHasBriInc), false)); + HomeAssistant homeAssistant = getHomeAssistant(hassCommand.getHassName()); + if (homeAssistant == null) { + log.warn("Should not get here, no HomeAssistants available"); + theReturn = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + + "\",\"description\": \"Should not get here, no HiomeAssistant clients available\", \"parameter\": \"/lights/" + + lightId + "state\"}}]"; + } else { + for (int x = 0; x < multiComand.getSetCount(); x++) { + if (x > 0 || iterationCount > 0) { + try { + Thread.sleep(multiComand.getTheDelay()); + } catch (InterruptedException e) { + // ignore + } + } + if (anItem.getDelay() != null && anItem.getDelay() > 0) + multiComand.setTheDelay(anItem.getDelay()); +// else +// this.setTheDelay(bridgeSettings.getButtonsleep()); + log.debug("calling HomeAssistant: " + hassCommand.getHassName() + " - " + + hassCommand.getEntityId() + " - " + hassCommand.getState() + " - " + hassCommand.getBri() + + " - iteration: " + String.valueOf(iterationCount) + " - count: " + String.valueOf(x)); + homeAssistant.callCommand(hassCommand); + } + } + return theReturn; + } }