diff --git a/pom.xml b/pom.xml index ba1f985..1cb1b1d 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.armzilla.ha amazon-echo-bridge - 0.1.2 + 0.1.3 jar Amazon Echo Bridge @@ -48,6 +48,11 @@ httpclient 4.3.6 + + + + + diff --git a/src/main/java/com/armzilla/ha/api/hue/DeviceState.java b/src/main/java/com/armzilla/ha/api/hue/DeviceState.java index 52af575..38f90e2 100644 --- a/src/main/java/com/armzilla/ha/api/hue/DeviceState.java +++ b/src/main/java/com/armzilla/ha/api/hue/DeviceState.java @@ -7,7 +7,7 @@ */ public class DeviceState { private boolean on; - private int bri; + private int bri = 255; private int hue; private int sat; private String effect; @@ -96,4 +96,12 @@ public List getXy() { public void setXy(List xy) { this.xy = xy; } + + @Override + public String toString() { + return "DeviceState{" + + "on=" + on + + ", bri=" + bri + + '}'; + } } diff --git a/src/main/java/com/armzilla/ha/hue/HueMulator.java b/src/main/java/com/armzilla/ha/hue/HueMulator.java index 3fa207d..e407ff7 100644 --- a/src/main/java/com/armzilla/ha/hue/HueMulator.java +++ b/src/main/java/com/armzilla/ha/hue/HueMulator.java @@ -1,8 +1,11 @@ package com.armzilla.ha.hue; import com.armzilla.ha.api.hue.DeviceResponse; +import com.armzilla.ha.api.hue.DeviceState; import com.armzilla.ha.api.hue.HueApiResponse; import com.armzilla.ha.dao.*; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; @@ -25,7 +28,6 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -35,12 +37,18 @@ @RequestMapping("/api") public class HueMulator { private static final Logger log = Logger.getLogger(HueMulator.class); - protected static RestTemplate restTemplate = new RestTemplate(); + private static final String INTENSITY_PERCENT = "${intensity.percent}"; + private static final String INTENSITY_BYTE = "${intensity.byte}"; @Autowired private DeviceRepository repository; private HttpClient httpClient; + private ObjectMapper mapper; + + public HueMulator(){ httpClient = HttpClients.createDefault(); //patched for now, moving away from HueMulator doing work + mapper = new ObjectMapper(); //work around Echo incorrect content type and breaking mapping. Map manually + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } @@ -112,26 +120,53 @@ public ResponseEntity getLigth(@PathVariable(value = "lightId") } @RequestMapping(value = "/{userId}/lights/{lightId}/state", method = RequestMethod.PUT) - public ResponseEntity stateChange(@PathVariable(value = "lightId") String lightId, @PathVariable(value = "userId") String userId, HttpServletRequest request, @RequestBody String body) { + public ResponseEntity stateChange(@PathVariable(value = "lightId") String lightId, @PathVariable(value = "userId") String userId, HttpServletRequest request, @RequestBody String requestString) { + /** + * strangely enough the Echo sends a content type of application/x-www-form-urlencoded even though + * it sends a json object + */ log.info("hue state change requested: " + userId + " from " + request.getRemoteAddr()); - log.info("hue stage change body: " + body); - String setting; - if (body.contains("true")) { - setting = "[{\"success\":{\"/lights/" + lightId + "/state/on\":true}}]"; - } else { - setting = "[{\"success\":{\"/lights/" + lightId + "/state/on\":false}}]"; + log.info("hue stage change body: " + requestString ); + + DeviceState state = null; + try { + state = mapper.readValue(requestString, DeviceState.class); + } catch (IOException e) { + log.info("object mapper barfed on input", e); + return new ResponseEntity<>(null, null, HttpStatus.BAD_REQUEST); } + DeviceDescriptor device = repository.findOne(lightId); if (device == null) { return new ResponseEntity<>(null, null, HttpStatus.NOT_FOUND); } + String responseString; String url; - if (body.contains("true")) { + if (state.isOn()) { + responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":true}}]"; url = device.getOnUrl(); } else { + responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":false}}]"; url = device.getOffUrl(); } + + /* 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 only two variables: + * intensity.byte : 0-255 brightness. this is raw from the echo + * intensity.percent : 0-100, adjusted for the vera + */ + if(url.contains(INTENSITY_BYTE)){ + String intensityByte = String.valueOf(state.getBri()); + url = url.replace(INTENSITY_BYTE, intensityByte); + }else if(url.contains(INTENSITY_PERCENT)){ + int percentBrightness = (int) Math.round(state.getBri()/255.0*100); + String intensityPercent = String.valueOf(percentBrightness); + url = url.replace(INTENSITY_PERCENT, intensityPercent); + } + //make call if(!doHttpGETRequest(url)){ return new ResponseEntity<>(null, null, HttpStatus.SERVICE_UNAVAILABLE); @@ -151,15 +186,17 @@ public ResponseEntity stateChange(@PathVariable(value = "lightId") Strin headerMap.set("Content-Type", "application/json"); - ResponseEntity entity = new ResponseEntity<>(setting, headerMap, HttpStatus.OK); + ResponseEntity entity = new ResponseEntity<>(responseString, headerMap, HttpStatus.OK); return entity; } - protected boolean doHttpGETRequest(String url){ + protected boolean doHttpGETRequest(String url) { + log.info("calling GET on URL: " + url); HttpGet httpGet = new HttpGet(url); try { HttpResponse response = httpClient.execute(httpGet); EntityUtils.consume(response.getEntity()); //close out inputstream ignore content + log.info("GET on URL responded: " + response.getStatusLine().getStatusCode()); if(response.getStatusLine().getStatusCode() == 200){ return true; }