diff --git a/pom.xml b/pom.xml index ae5b873..a0395bc 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,19 @@ 1.8 + + + jitpack.io + https://jitpack.io + + + + + com.github.bwssytems + harmony-java-client + 1.0.4 + com.sparkjava spark-core @@ -58,6 +70,16 @@ eval 0.5 + + com.google.inject + guice + 4.0-beta4 + + + org.igniterealtime.smack + smack-core + 4.0.2 + diff --git a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java index ab38394..8e9aecb 100644 --- a/src/main/java/com/bwssystems/HABridge/BridgeSettings.java +++ b/src/main/java/com/bwssystems/HABridge/BridgeSettings.java @@ -90,6 +90,10 @@ public class BridgeSettings { public Boolean isValidHarmony() { if(this.harmonyaddress.contains(Configuration.DEFAULT_HARMONY_ADDRESS)) return false; + if(this.harmonypwd == null || this.harmonypwd == "") + return false; + if(this.harmonyuser == null || this.harmonyuser == "") + return false; return true; } } diff --git a/src/main/java/com/bwssystems/HABridge/HABridge.java b/src/main/java/com/bwssystems/HABridge/HABridge.java index 7ad58e8..08ac11d 100644 --- a/src/main/java/com/bwssystems/HABridge/HABridge.java +++ b/src/main/java/com/bwssystems/HABridge/HABridge.java @@ -12,6 +12,7 @@ import com.bwssystems.HABridge.devicemanagmeent.*; import com.bwssystems.HABridge.hue.HueMulator; import com.bwssystems.HABridge.upnp.UpnpListener; import com.bwssystems.HABridge.upnp.UpnpSettingsResource; +import com.bwssystems.harmony.HarmonyServer; public class HABridge { @@ -33,6 +34,7 @@ public class HABridge { public static void main(String[] args) { Logger log = LoggerFactory.getLogger(HABridge.class); DeviceResource theResources; + HarmonyServer myHarmonyServer; HueMulator theHueMulator; UpnpSettingsResource theSettingResponder; UpnpListener theUpnpListener; @@ -69,10 +71,17 @@ public class HABridge { port(Integer.valueOf(bridgeSettings.getServerPort())); // sparkjava config directive to set html static file location for Jetty staticFileLocation("/public"); + //setup the harmony connection if available + try { + myHarmonyServer = HarmonyServer.setup(bridgeSettings); + } catch (Exception e) { + log.error("Cannot get harmony client setup, Exiting with message: " + e.getMessage(), e); + return; + } // setup the class to handle the resource setup rest api - theResources = new DeviceResource(bridgeSettings); + theResources = new DeviceResource(bridgeSettings, myHarmonyServer.getMyHarmony()); // setup the class to handle the hue emulator rest api - theHueMulator = new HueMulator(theResources.getDeviceRepository()); + theHueMulator = new HueMulator(theResources.getDeviceRepository(), myHarmonyServer.getMyHarmony()); theHueMulator.setupServer(); // setup the class to handle the upnp response rest api theSettingResponder = new UpnpSettingsResource(bridgeSettings); diff --git a/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java b/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java index 3fcef09..4364ccd 100644 --- a/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java +++ b/src/main/java/com/bwssystems/HABridge/devicemanagmeent/DeviceResource.java @@ -18,6 +18,7 @@ import com.bwssystems.HABridge.BridgeSettings; import com.bwssystems.HABridge.JsonTransformer; import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceRepository; +import com.bwssystems.harmony.HarmonyHandler; import com.bwssystems.luupRequests.Sdata; import com.bwssystems.vera.VeraInfo; import com.google.gson.Gson; @@ -31,12 +32,14 @@ public class DeviceResource { private DeviceRepository deviceRepository; private VeraInfo veraInfo; + private HarmonyHandler myHarmonyHandler; private static final Set supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post")); - public DeviceResource(BridgeSettings theSettings) { + public DeviceResource(BridgeSettings theSettings, HarmonyHandler myHarmony) { super(); deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb()); veraInfo = new VeraInfo(theSettings.getVeraAddress(), theSettings.isValidVera()); + myHarmonyHandler = myHarmony; setupEndpoints(); } @@ -150,5 +153,13 @@ public class DeviceResource { return sData.getScenes(); }, new JsonTransformer()); + get (API_CONTEXT + "/harmony/activities", "application/json", (request, response) -> { + log.debug("Get harmony activities"); + response.status(HttpStatus.SC_OK); + if(myHarmonyHandler != null) + return myHarmonyHandler.getActivities(); + return ""; + }, new JsonTransformer()); + } } \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java index 16d2eec..617a23b 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java +++ b/src/main/java/com/bwssystems/HABridge/hue/HueMulator.java @@ -6,6 +6,7 @@ import com.bwssystems.HABridge.api.hue.DeviceResponse; import com.bwssystems.HABridge.api.hue.DeviceState; import com.bwssystems.HABridge.api.hue.HueApiResponse; import com.bwssystems.HABridge.dao.*; +import com.bwssystems.harmony.HarmonyHandler; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; @@ -51,15 +52,17 @@ public class HueMulator { private static final String HUE_CONTEXT = "/api"; private DeviceRepository repository; + private HarmonyHandler myHarmony; private HttpClient httpClient; private ObjectMapper mapper; - public HueMulator(DeviceRepository aDeviceRepository){ + public HueMulator(DeviceRepository aDeviceRepository, HarmonyHandler theHandler){ httpClient = HttpClients.createDefault(); mapper = new ObjectMapper(); //armzilla: work around Echo incorrect content type and breaking mapping. Map manually mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); repository = aDeviceRepository; + myHarmony = theHandler; } // This function sets up the sparkjava rest calls for the hue api @@ -208,19 +211,28 @@ public class HueMulator { responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":false}}]"; url = device.getOffUrl(); } - - //quick template - String body; - url = replaceIntensityValue(url, state.getBri()); - if (state.isOn()) - body = replaceIntensityValue(device.getContentBody(), state.getBri()); + + if(device.getDeviceType() == "activity") + { + log.debug("executing activity to Harmony: " + url); + myHarmony.startActivity(url); + } else - body = replaceIntensityValue(device.getContentBodyOff(), state.getBri()); - //make call - if(!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)){ - response.status(HttpStatus.SC_SERVICE_UNAVAILABLE); - log.error("Error on calling url to change device state: " + url); - return null; + { + log.debug("executing activity to Http: " + url); + // quick template + String body; + url = replaceIntensityValue(url, state.getBri()); + if (state.isOn()) + body = replaceIntensityValue(device.getContentBody(), state.getBri()); + else + body = replaceIntensityValue(device.getContentBodyOff(), state.getBri()); + // make call + if (!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)) { + response.status(HttpStatus.SC_SERVICE_UNAVAILABLE); + log.error("Error on calling url to change device state: " + url); + return null; + } } response.type("application/json; charset=utf-8"); diff --git a/src/main/java/com/bwssystems/harmony/HarmonyActivities.java b/src/main/java/com/bwssystems/harmony/HarmonyActivities.java deleted file mode 100644 index 8b4de6f..0000000 --- a/src/main/java/com/bwssystems/harmony/HarmonyActivities.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.bwssystems.harmony; - -public class HarmonyActivities { - -} diff --git a/src/main/java/com/bwssystems/harmony/HarmonyHandler.java b/src/main/java/com/bwssystems/harmony/HarmonyHandler.java new file mode 100644 index 0000000..7cbd09f --- /dev/null +++ b/src/main/java/com/bwssystems/harmony/HarmonyHandler.java @@ -0,0 +1,89 @@ +package com.bwssystems.harmony; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import net.whistlingfish.harmony.HarmonyClient; +import net.whistlingfish.harmony.config.Activity; +import net.whistlingfish.harmony.config.Device; +import net.whistlingfish.harmony.config.HarmonyConfig; + +public class HarmonyHandler { + private static final Logger log = LoggerFactory.getLogger(HarmonyHandler.class); + private HarmonyClient harmonyClient; + private Boolean noopCalls; + + public HarmonyHandler(HarmonyClient theClient, Boolean noopCallsSetting) { + super(); + noopCalls = noopCallsSetting; + harmonyClient = theClient; + } + + public List getActivities() { + log.debug("Harmony api activities list requested."); + return harmonyClient.getConfig().getActivities(); + } + + public List getDevices() { + log.debug("Harmony api device list requested."); + return harmonyClient.getConfig().getDevices(); + } + + public HarmonyConfig getConfig() { + log.debug("Harmony api config requested."); + return harmonyClient.getConfig(); + } + + public Activity getCurrentActivity() { + log.debug("Harmony api current sctivity requested."); + return harmonyClient.getCurrentActivity(); + } + + public Boolean startActivity(String anActivity) { + log.debug("Harmony api start activity requested for: " + anActivity + " noop mode: " + noopCalls); + if (anActivity != null && !anActivity.isEmpty()) { + try { + if (!noopCalls) + harmonyClient.startActivity(Integer.parseInt(anActivity)); + } catch (IllegalArgumentException e) { + try { + if (!noopCalls) + harmonyClient.startActivityByName(anActivity); + } catch (IllegalArgumentException ei) { + log.error("Error in finding activity: " + anActivity); + return false; + } + } + } else { + log.error("Error in finding activity: " + anActivity); + return false; + } + + return true; + } + + public Boolean pressButton(String aDevice, String aDeviceButton) { + log.debug("Harmony api press a button requested for device: " + aDevice + " and a for button: " + aDeviceButton + " noop mode: " + noopCalls); + if (aDeviceButton != null && !aDeviceButton.isEmpty()) { + try { + if (!noopCalls) + harmonyClient.pressButton(Integer.parseInt(aDevice), aDeviceButton); + } catch (IllegalArgumentException e) { + try { + if (!noopCalls) + harmonyClient.pressButton(aDevice, aDeviceButton); + } catch (IllegalArgumentException ei) { + log.error("Error in finding device: " + aDevice +" and a button: " + aDeviceButton); + return false; + } + } + } else { + log.error("Error in finding device: " + aDevice +" and a button: " + aDeviceButton); + return false; + } + + return true; + } +} diff --git a/src/main/java/com/bwssystems/harmony/HarmonyServer.java b/src/main/java/com/bwssystems/harmony/HarmonyServer.java new file mode 100644 index 0000000..8b376ba --- /dev/null +++ b/src/main/java/com/bwssystems/harmony/HarmonyServer.java @@ -0,0 +1,60 @@ +package com.bwssystems.harmony; + +import static java.lang.String.format; + +import javax.inject.Inject; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.bwssystems.HABridge.BridgeSettings; +import com.google.inject.Guice; +import com.google.inject.Injector; + +import net.whistlingfish.harmony.ActivityChangeListener; +import net.whistlingfish.harmony.HarmonyClient; +import net.whistlingfish.harmony.HarmonyClientModule; +import net.whistlingfish.harmony.config.Activity; + +public class HarmonyServer { + @Inject + private HarmonyClient harmonyClient; + + private HarmonyHandler myHarmony; + + private Logger log = LoggerFactory.getLogger(HarmonyServer.class); + + public HarmonyServer() { + super(); + myHarmony = null; + } + + public static HarmonyServer setup(BridgeSettings bridgeSettings) throws Exception { + if(!bridgeSettings.isValidHarmony()) { + return new HarmonyServer(); + } + Injector injector = null; + injector = Guice.createInjector(new HarmonyClientModule()); + HarmonyServer mainObject = new HarmonyServer(); + injector.injectMembers(mainObject); + mainObject.execute(bridgeSettings); + return mainObject; + } + + private void execute(BridgeSettings mySettings) throws Exception { + log.debug("setup initiated...."); + harmonyClient.addListener(new ActivityChangeListener() { + @Override + public void activityStarted(Activity activity) { + log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel())); + } + }); + harmonyClient.connect(mySettings.getHarmonyAddress(), mySettings.getHarmonyUser(), mySettings.getHarmonyPwd()); + Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false")); + myHarmony = new HarmonyHandler(harmonyClient, noopCalls); + } + + public HarmonyHandler getMyHarmony() { + return myHarmony; + } +} diff --git a/src/main/resources/public/scripts/app.js b/src/main/resources/public/scripts/app.js index e3b4493..f8c9518 100644 --- a/src/main/resources/public/scripts/app.js +++ b/src/main/resources/public/scripts/app.js @@ -149,15 +149,15 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { this.updateShowHarmony = function () { if(self.BridgeSettings.harmonyaddress == "1.1.1.1" || self.BridgeSettings.harmonyaddress == "") - self.state.showHarmony = false; + this.state.showHarmony = false; else - self.state.showHarmony = true; + this.state.showHarmony = true; return; } this.viewVeraDevices = function () { this.state.error = ""; - if(BridgeSettings.veraaddress == "1.1.1.1" || BridgeSettings.veraaddress == "") + if(!this.state.showVera) return; this.state.error = ""; return $http.get(this.state.base + "/vera/devices").then( @@ -176,7 +176,7 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { this.viewVeraScenes = function () { this.state.error = ""; - if(BridgeSettings.veraaddress == "1.1.1.1" || BridgeSettings.veraaddress == "") + if(!this.state.showVera) return; return $http.get(this.state.base + "/vera/scenes").then( function (response) { @@ -192,8 +192,28 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { ); }; + this.viewHarmonyActivities = function () { + this.state.error = ""; + if(!this.state.showHarmony) + return; + return $http.get(this.state.base + "/harmony/activities").then( + function (response) { + self.state.harmonyactivities = response.data; + }, + function (error) { + if (error.data) { + $window.alert("Get Harmony Activities Error: " + error.data.message); + } else { + $window.alert("Get Harmony Activities Error: unknown"); + } + } + ); + }; + this.addDevice = function (id, name, type, onUrl, offUrl, httpVerb, contentType, contentBody, contentBodyOff) { this.state.error = ""; + if(httpVerb != null && httpVerb != "") + type = "custom"; if (id) { var putUrl = this.state.base + "/" + id; return $http.put(putUrl, { @@ -220,6 +240,8 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) { } else { if(type == null || type == "") type = "switch"; + if(httpVerb != null && httpVerb != "") + type = "custom"; return $http.post(this.state.base, { name: name, deviceType: type, @@ -280,6 +302,8 @@ app.controller('ViewingController', function ($scope, $location, $http, $window, bridgeService.deleteDevice(device.id); }; $scope.testUrl = function (device, type) { + if(device.deviceType == "activity") + return; if(type == "on") { if(device.httpVerb == "PUT") $http.put(device.onUrl, device.contentBody).then( @@ -343,6 +367,7 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer bridgeService.device = $scope.device; bridgeService.viewVeraDevices(); bridgeService.viewVeraScenes(); + bridgeService.viewHarmonyActivities(); $scope.bridge = bridgeService.state; bridgeService.updateShowVera(); bridgeService.updateShowHarmony(); @@ -423,20 +448,15 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer }; $scope.buildActivityUrls = function (harmonyactivity) { - if ($scope.vera.base.indexOf("http") < 0) { - $scope.vera.base = "http://" + $scope.vera.base; - } - $scope.device.deviceType = "scene"; - $scope.device.name = verascene.name; - $scope.device.onUrl = $scope.vera.base + ":" + $scope.vera.port - + "/data_request?id=action&output_format=json&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunScene&SceneNum=" - + verascene.id; - $scope.device.offUrl = $scope.vera.base + ":" + $scope.vera.port - + "/data_request?id=action&output_format=json&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunScene&SceneNum=" - + verascene.id; + $scope.device.deviceType = "activity"; + $scope.device.name = harmonyactivity.label; + $scope.device.onUrl = harmonyactivity.id; + $scope.device.offUrl = "-1"; }; - $scope.testUrl = function (url) { + $scope.testUrl = function (device, type) { + if(device.deviceType == "activity") + return; if(type == "on") { if(device.httpVerb == "PUT") $http.put(device.onUrl, device.contentBody).then( diff --git a/src/main/resources/public/views/harmonyactivity.html b/src/main/resources/public/views/harmonyactivity.html index 7b4349a..f4d6076 100644 --- a/src/main/resources/public/views/harmonyactivity.html +++ b/src/main/resources/public/views/harmonyactivity.html @@ -29,8 +29,8 @@ Actions - - {{harmonyactivity.name}} + + {{harmonyactivity.label}} {{harmonyactivity.id}}