diff --git a/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java b/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java index 138ba6b..c0ea926 100644 --- a/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java +++ b/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java @@ -22,6 +22,7 @@ import com.bwssystems.HABridge.dao.DeviceRepository; import com.bwssystems.HABridge.dao.ErrorMessage; import com.bwssystems.NestBridge.NestHome; import com.bwssystems.harmony.HarmonyHome; +import com.bwssystems.hue.HueHome; import com.bwssystems.luupRequests.Device; import com.bwssystems.luupRequests.Scene; import com.bwssystems.util.JsonTransformer; @@ -38,6 +39,7 @@ public class DeviceResource { private VeraHome veraHome; private HarmonyHome myHarmonyHome; private NestHome nestHome; + private HueHome hueHome; private static final Set supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post")); public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome) { @@ -57,6 +59,11 @@ public class DeviceResource { this.nestHome = aNestHome; else this.nestHome = null; + + if(theSettings.isValidHue()) + this.hueHome = new HueHome(theSettings); + else + this.hueHome = null; setupEndpoints(); } @@ -245,6 +252,16 @@ public class DeviceResource { return nestHome.getItems(); }, new JsonTransformer()); + get (API_CONTEXT + "/hue/devices", "application/json", (request, response) -> { + log.debug("Get hue items"); + if(hueHome == null) { + response.status(HttpStatus.SC_NOT_FOUND); + return new ErrorMessage("A Hue is not available."); + } + response.status(HttpStatus.SC_OK); + return hueHome.getDevices(); + }, new JsonTransformer()); + get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> { log.debug("Get backup filenames"); response.status(HttpStatus.SC_OK); diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 034edb5..33eac79 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -12,6 +12,7 @@ import com.bwssystems.harmony.ButtonPress; import com.bwssystems.harmony.HarmonyHandler; import com.bwssystems.harmony.HarmonyHome; import com.bwssystems.harmony.RunActivity; +import com.bwssystems.hue.HueDeviceIdentifier; import com.bwssystems.nest.controller.Nest; import com.bwssystems.util.JsonTransformer; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -336,6 +337,23 @@ public class HueMulator { return responseString; } + responseString = this.formatSuccessHueResponse(state, request.body(), lightId); + + if(device.getDeviceType().toLowerCase().contains("hue") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("hueDevice"))) + { + url = device.getOnUrl(); + HueDeviceIdentifier deviceId = new Gson().fromJson(url, HueDeviceIdentifier.class); + + // make call + if (!doHttpRequest("http://"+deviceId.getIpAddress()+"/api/"+userId+"/lights/"+deviceId.getDeviceId(), HttpPut.METHOD_NAME, device.getContentType(), request.body())) { + log.warn("Error on calling url to change device state: " + url); + responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]"; + } + else + device.setDeviceState(state); + return responseString; + } + if(request.body().contains("bri")) { url = device.getDimUrl(); @@ -360,7 +378,6 @@ public class HueMulator { return responseString; } - responseString = this.formatSuccessHueResponse(state, request.body(), lightId); if(device.getDeviceType().toLowerCase().contains("activity") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyActivity"))) { diff --git a/src/main/java/com/bwssystems/hue/HueDevice.java b/src/main/java/com/bwssystems/hue/HueDevice.java new file mode 100644 index 0000000..27defd4 --- /dev/null +++ b/src/main/java/com/bwssystems/hue/HueDevice.java @@ -0,0 +1,28 @@ +package com.bwssystems.hue; + +import com.bwssystems.HABridge.api.hue.DeviceResponse; + + +public class HueDevice { + private DeviceResponse device; + private String hubaddress; + private String hubname; + public DeviceResponse getDevice() { + return device; + } + public void setDevice(DeviceResponse device) { + this.device = device; + } + public String getHubaddress() { + return hubaddress; + } + public void setHubaddress(String hubaddress) { + this.hubaddress = hubaddress; + } + public String getHubname() { + return hubname; + } + public void setHubname(String hubname) { + this.hubname = hubname; + } +} diff --git a/src/main/java/com/bwssystems/hue/HueDeviceIdentifier.java b/src/main/java/com/bwssystems/hue/HueDeviceIdentifier.java new file mode 100644 index 0000000..989ebe5 --- /dev/null +++ b/src/main/java/com/bwssystems/hue/HueDeviceIdentifier.java @@ -0,0 +1,18 @@ +package com.bwssystems.hue; + +public class HueDeviceIdentifier { + private String ipAddress; + private String deviceId; + public String getIpAddress() { + return ipAddress; + } + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + public String getDeviceId() { + return deviceId; + } + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } +} diff --git a/src/main/java/com/bwssystems/hue/HueHome.java b/src/main/java/com/bwssystems/hue/HueHome.java new file mode 100644 index 0000000..8db4fbe --- /dev/null +++ b/src/main/java/com/bwssystems/hue/HueHome.java @@ -0,0 +1,54 @@ +package com.bwssystems.hue; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.bwssystems.HABridge.BridgeSettingsDescriptor; +import com.bwssystems.HABridge.NamedIP; +import com.bwssystems.HABridge.api.hue.DeviceResponse; + +public class HueHome { + private static final Logger log = LoggerFactory.getLogger(HueHome.class); + private Map hues; + + public HueHome(BridgeSettingsDescriptor bridgeSettings) { + hues = new HashMap(); + if(!bridgeSettings.isValidHue()) + return; + Iterator theList = bridgeSettings.getVeraAddress().getDevices().iterator(); + while(theList.hasNext()) { + NamedIP aHue = theList.next(); + hues.put(aHue.getName(), new HueInfo(aHue)); + } + } + + public List getDevices() { + log.debug("consolidating devices for hues"); + Iterator keys = hues.keySet().iterator(); + ArrayList deviceList = new ArrayList(); + while(keys.hasNext()) { + String key = keys.next(); + Map theDevices = hues.get(key).getHueApiResponse().getLights(); + if(theDevices != null) { + Iterator deviceKeys = theDevices.keySet().iterator(); + while(deviceKeys.hasNext()) { + HueDevice aNewHueDevice = new HueDevice(); + aNewHueDevice.setDevice(theDevices.get(deviceKeys.next())); + aNewHueDevice.setHubaddress(""); + deviceList.add(aNewHueDevice); + } + } + else { + deviceList = null; + break; + } + } + return deviceList; + } +} diff --git a/src/main/java/com/bwssystems/hue/HueInfo.java b/src/main/java/com/bwssystems/hue/HueInfo.java new file mode 100644 index 0000000..9f6b986 --- /dev/null +++ b/src/main/java/com/bwssystems/hue/HueInfo.java @@ -0,0 +1,61 @@ +package com.bwssystems.hue; + +import java.io.IOException; +import java.nio.charset.Charset; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.bwssystems.HABridge.NamedIP; +import com.bwssystems.HABridge.api.hue.HueApiResponse; +import com.google.gson.Gson; + + +public class HueInfo { + private static final Logger log = LoggerFactory.getLogger(HueInfo.class); + private HttpClient httpClient; + private static final String HUE_REQUEST = "/api/habridge/config"; + private NamedIP hueAddress; + + public HueInfo(NamedIP addressName) { + super(); + httpClient = HttpClients.createDefault(); + hueAddress = addressName; + } + + public HueApiResponse getHueApiResponse() { + HueApiResponse theHueApiResponse = null; + + String theUrl = "http://" + hueAddress.getIp() + HUE_REQUEST; + String theData; + + theData = doHttpGETRequest(theUrl); + if(theData != null) { + theHueApiResponse = new Gson().fromJson(theData, HueApiResponse.class); + log.debug("GET HueApiResponse - name: " + theHueApiResponse.getConfig().getName() + ", mac addr: " + theHueApiResponse.getConfig().getMac()); + } + return theHueApiResponse; + } + + // This function executes the url against the vera + protected String doHttpGETRequest(String url) { + String theContent = null; + log.debug("calling GET on URL: " + url); + HttpGet httpGet = new HttpGet(url); + try { + HttpResponse response = httpClient.execute(httpGet); + log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode()); + if(response.getStatusLine().getStatusCode() == 200){ + theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data + EntityUtils.consume(response.getEntity()); //close out inputstream ignore content + } + } catch (IOException e) { + log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage()); + } + return theContent; + } +} diff --git a/src/main/java/com/bwssystems/vera/VeraHome.java b/src/main/java/com/bwssystems/vera/VeraHome.java index 96c57fb..d390c48 100644 --- a/src/main/java/com/bwssystems/vera/VeraHome.java +++ b/src/main/java/com/bwssystems/vera/VeraHome.java @@ -26,7 +26,7 @@ public class VeraHome { Iterator theList = bridgeSettings.getVeraAddress().getDevices().iterator(); while(theList.hasNext()) { NamedIP aVera = theList.next(); - veras.put(aVera.getName(), new VeraInfo(aVera, bridgeSettings.isValidVera())); + veras.put(aVera.getName(), new VeraInfo(aVera)); } } diff --git a/src/main/java/com/bwssystems/vera/VeraInfo.java b/src/main/java/com/bwssystems/vera/VeraInfo.java index 8af2593..1daddc7 100644 --- a/src/main/java/com/bwssystems/vera/VeraInfo.java +++ b/src/main/java/com/bwssystems/vera/VeraInfo.java @@ -28,19 +28,15 @@ public class VeraInfo { private HttpClient httpClient; private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json"; private NamedIP veraAddress; - private Boolean validVera; - public VeraInfo(NamedIP addressName, Boolean isValidVera) { + public VeraInfo(NamedIP addressName) { super(); httpClient = HttpClients.createDefault(); veraAddress = addressName; - validVera = isValidVera; } public Sdata getSdata() { Sdata theSdata = null; - if(!validVera) - return theSdata; String theUrl = "http://" + veraAddress.getIp() + SDATA_REQUEST; String theData; diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index 6d665e3..0a3307f 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -44,7 +44,7 @@ app.run( function (bridgeService) { app.service('bridgeService', function ($http, $window, ngToast) { var self = this; - this.state = {base: window.location.origin + "/api/devices", bridgelocation: window.location.origin, systemsbase: window.location.origin + "/system", huebase: window.location.origin + "/api", configs: [], backups: [], devices: [], device: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, habridgeversion: ""}; + this.state = {base: window.location.origin + "/api/devices", bridgelocation: window.location.origin, systemsbase: window.location.origin + "/system", huebase: window.location.origin + "/api", configs: [], backups: [], devices: [], device: [], type: "", settings: [], myToastMsg: [], logMsgs: [], loggerInfo: [], olddevicename: "", logShowAll: false, isInControl: false, showVera: false, showHarmony: false, showNest: false, showHue: false, habridgeversion: ""}; this.displayWarn = function(errorTitle, error) { var toastContent = errorTitle; @@ -140,6 +140,11 @@ app.service('bridgeService', function ($http, $window, ngToast) { return; } + this.updateShowHue = function () { + this.state.showHue = self.state.settings.hueconfigured; + return; + } + this.loadBridgeSettings = function () { return $http.get(this.state.systemsbase + "/settings").then( function (response) { @@ -147,6 +152,7 @@ app.service('bridgeService', function ($http, $window, ngToast) { self.updateShowVera(); self.updateShowHarmony(); self.updateShowNest(); + self.updateShowHue(); }, function (error) { self.displayWarn("Load Bridge Settings Error: ", error); @@ -211,6 +217,19 @@ app.service('bridgeService', function ($http, $window, ngToast) { ); }; + this.viewHueDevices = function () { + if(!this.state.showHue) + return; + return $http.get(this.state.base + "/hue/devices").then( + function (response) { + self.state.huedevices = response.data; + }, + function (error) { + self.displayWarn("Get Hue Items Error: ", error); + } + ); + }; + this.viewVeraDevices = function () { if(!this.state.showVera) return; @@ -1079,6 +1098,105 @@ app.controller('NestController', function ($scope, $location, $http, bridgeServi }); +app.controller('HueController', function ($scope, $location, $http, bridgeService) { + $scope.bridge = bridgeService.state; + $scope.device = $scope.bridge.device; + $scope.bulk = { devices: [] }; + bridgeService.viewHueDevices(); + $scope.imgButtonsUrl = "glyphicon glyphicon-plus"; + $scope.buttonsVisible = false; + + $scope.clearDevice = function () { + bridgeService.clearDevice(); + }; + + $scope.buildDeviceUrls = function (huedevice) { + bridgeService.clearDevice(); + $scope.device.deviceType = "switch"; + $scope.device.name = huedevice.name; + $scope.device.targetDevice = huedevice.huename; + $scope.device.mapType = "hueDevice"; + $scope.device.mapId = huedevice.id; + $scope.device.onUrl = "http://" + veradevice.veraaddress + ":" + $scope.vera.port + + "/data_request?id=action&output_format=json&serviceId=urn:upnp-org:serviceId:SwitchPower1&action=SetTarget&newTargetValue=1&DeviceNum=" + + veradevice.id; + }; + + $scope.addDevice = function () { + if($scope.device.name == "" && $scope.device.onUrl == "") + return; + bridgeService.addDevice($scope.device).then( + function () { + $scope.clearDevice(); + bridgeService.viewDevices(); + bridgeService.viewHueDevices(); + }, + function (error) { + bridgeService.displayWarn("Error adding device: " + $scope.device.name, error) + } + ); + + }; + + $scope.bulkAddDevices = function() { + var devicesList = []; + for(var i = 0; i < $scope.bulk.devices.length; i++) { + for(var x = 0; x < bridgeService.state.huedevices.length; x++) { + if(bridgeService.state.huedevices[x].id == $scope.bulk.devices[i]) { + $scope.buildDeviceUrls(bridgeService.state.huedevices[x]); + devicesList[i] = { + name: $scope.device.name, + mapId: $scope.device.mapId, + mapType: $scope.device.mapType, + deviceType: $scope.device.deviceType, + targetDevice: $scope.device.targetDevice, + onUrl: $scope.device.onUrl, + offUrl: $scope.device.offUrl, + httpVerb: $scope.device.httpVerb, + contentType: $scope.device.contentType, + contentBody: $scope.device.contentBody, + contentBodyOff: $scope.device.contentBodyOff + }; + } + } + } + bridgeService.bulkAddDevice(devicesList); + $scope.clearDevice(); + bridgeService.viewDevices(); + bridgeService.viewHueDevices(); + $scope.bulk = { devices: [] }; + }; + + $scope.toggleSelection = function toggleSelection(deviceId) { + var idx = $scope.bulk.devices.indexOf(deviceId); + + // is currently selected + if (idx > -1) { + $scope.bulk.devices.splice(idx, 1); + } + + // is newly selected + else { + $scope.bulk.devices.push(deviceId); + } + }; + + $scope.toggleButtons = function () { + $scope.buttonsVisible = !$scope.buttonsVisible; + if($scope.buttonsVisible) + $scope.imgButtonsUrl = "glyphicon glyphicon-minus"; + else + $scope.imgButtonsUrl = "glyphicon glyphicon-plus"; + }; + + $scope.deleteDeviceByMapId = function (id, mapType) { + bridgeService.deleteDeviceByMapId(id, mapType); + bridgeService.viewDevices(); + bridgeService.viewHueDevices(); + }; + +}); + app.controller('EditController', function ($scope, $location, $http, bridgeService) { $scope.bridge = bridgeService.state; $scope.device = $scope.bridge.device; diff --git a/src/main/resources/public/views/configuration.html b/src/main/resources/public/views/configuration.html index 843db52..a665c80 100644 --- a/src/main/resources/public/views/configuration.html +++ b/src/main/resources/public/views/configuration.html @@ -7,6 +7,7 @@
  • Harmony Activities
  • Harmony Devices
  • Nest
  • +
  • Hue Devices
  • Manual Add
  • diff --git a/src/main/resources/public/views/huedevice.html b/src/main/resources/public/views/huedevice.html new file mode 100644 index 0000000..3119c47 --- /dev/null +++ b/src/main/resources/public/views/huedevice.html @@ -0,0 +1,120 @@ + + +
    +
    +

    Hue Device List ({{bridge.huedevices.length}})

    +
    +
      +
    • +

      For any Hue Device, use the action buttons to generate the device addition information below automatically. + Then you can modify the name to anything you want that will be the keyword for Alexa. Click the 'Add Bridge Device' to finish that selection setup. + The 'Already Configured Hue Devices' list below will show what is already setup for your Hue.

      +

      Use the check boxes by the names to use the bulk addition feature. Select your items, then click bulk add below. + Your items will be added with on and off or dim and off if selected with the name of the device from the Hue. +

      + + + + + + + + + + + + + + + + + + +
      RowNameIdHueActions
      {{$index+1}} {{huedevice.name}}{{huedevice.uniqueid}}{{huedevice.huename}} + +
      +
      +

      + +

      +
    • +
    +
    +

    Already Configured Hue Devices

    +
    +
      +
    • + + + + + + + + + + + + + + + + + + +
      RowNameIdhueActions
      {{$index+1}}{{huedevice.name}}{{huedevice.uniqueid}}{{huedevice.huename}} + +
      +
      +
    • +
    +
    +
    +
    +

    Add Bridge Device for a Hue Device

    +
    +
      +
    • +
      +
      + + +
      + +
      + +
      +
      +
      + + +
      + +
      + +
      + +
    • +
    +