Compare commits

..

30 Commits

Author SHA1 Message Date
Admin
f9f5a3a878 Updated code in the html and javascript to select the coorect name for
the button press command. Fixed the calculation of celsius conversion
for setting nest temperatures.

Fixes #33
Fixes #34
2016-01-29 13:49:34 -06:00
Admin
2565183ee9 Finished implementation for multiple vera support. Tweaks to the UI.
Fixes #19
Fixes #21
Fixes #23
Fixes #26
Fixes #28
Fixes #30
Fixes #31
2016-01-28 16:39:20 -06:00
Admin
a6bb1ae3aa Fixed the harmony button to be a label and not the name. Implementation
of multi batch add completed.
2016-01-27 16:54:23 -06:00
Admin
4bc91be88b Added backup and restore for device db functionality. Testing multi
button press for harmony.
2016-01-26 16:47:16 -06:00
Admin
315fd31270 implemented multi button build for devices. more testing needed. fixed
tab display on first view.
2016-01-25 16:35:22 -06:00
Admin
09787fe08d Fixed undefined name error in nest setup and celsius conversion for the
nest thermostat.
2016-01-22 12:20:24 -06:00
Admin
37d346f558 Finished Nest implementation and default ip selection to not be
127.0.0.1

Fixes #12
Fixes #20
2016-01-21 16:44:24 -06:00
Admin
ac59398aa0 Testing nest functionality 2016-01-20 16:46:21 -06:00
Admin
87073435fc Updated code for IP address selection on startup fi none given. 2016-01-13 14:54:35 -06:00
Admin
8687f3482a Continuation of nest implementation 2016-01-12 16:34:05 -06:00
Admin
32a5f26ddd Added new object 2016-01-11 16:45:49 -06:00
Admin
c28f07d628 Continuation of nest implementation. 2016-01-11 16:45:02 -06:00
Admin
d3cc961dfb Initial updates for nest control 2016-01-08 16:14:42 -06:00
Admin
1b3d826f28 Fixed Readme format errors 2015-12-29 10:18:51 -06:00
Admin
c8f4d89a45 Update Readme for clarity on upnp.config.address usage. Also, updated
the 'ask alexa' section with more details.
2015-12-29 10:14:26 -06:00
Admin
2b335d6b9b Updated upnp listener logging. Fixed issue with HUE emulation for
configuration. HUE mobile app now connects.
2015-12-17 16:13:46 -06:00
Admin
b27bb5eef8 Updating hue emulation repsonses using hue android app. fixed issues in
returning the hue config and for requesting the mac address.
2015-12-16 16:41:08 -06:00
Admin
3c54ccd56d Finished implementation of generic UDP handling. This will support the
LimitlessLED Bridge. Updated Readme for UDP and more on configuration
setup. Fixed issue with HueMulator handliong due to devices without
mapTypes.

Fixes #6.
2015-12-07 16:34:37 -06:00
Admin
cf772334c4 Start adding UDP handling. 2015-12-04 16:21:11 -06:00
Admin
5a843f7569 Fixed handling when no harmony hub is given. dev.mode was updated as
well.

This closes #14.
2015-12-03 14:36:23 -06:00
Admin
2a52783bb1 Final release of multiple harmony hubs and fixes for GUI.
This closes #5 and #13
2015-12-02 15:42:09 -06:00
Admin
195f1854ec Remove Current Activity object as it is not needed. 2015-12-02 15:35:35 -06:00
Admin
2e5596a6e4 Pre-release of multiple Harmony Hub Support 2015-12-02 15:34:30 -06:00
Admin
9fc13c6c45 Conitinuation of multiple harmony hub implementation. 2015-12-01 16:40:29 -06:00
Admin
4b4d4e36c7 Update filtering capability. Still errors in html/angular. 2015-11-30 16:43:14 -06:00
Admin
1e7bdc560b Start adding components to handle multiple harmony hubs. 2015-11-24 14:27:21 -06:00
Admin
aff0f8d64c Removed the doling out of hue devices on a get call. 2015-11-23 15:17:17 -06:00
Admin
7a812d6e6b Updated VeraInfo to use default http client. Finished work on doling out
devices in the resource manager. Caveat, this did not work well with the
echo. Next version will roll this back.
2015-11-23 15:14:38 -06:00
Admin
26f2105801 First try at doling out lights when requested. 2015-11-20 16:40:43 -06:00
Admin
feef345a3b Update for start of virtualize multiple respnses 2015-11-19 16:43:30 -06:00
37 changed files with 1943 additions and 414 deletions

144
README.md

File diff suppressed because one or more lines are too long

18
pom.xml
View File

@@ -5,11 +5,11 @@
<groupId>com.bwssystems.HABridge</groupId> <groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId> <artifactId>ha-bridge</artifactId>
<version>1.1.0</version> <version>1.3.6</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HA Bridge</name> <name>HA Bridge</name>
<description>Emulates a Philips Hue bridge to allow the Amazon Echo to hook up to other HA systems, i.e. Vera or Harmony Hub, using lightweight frameworks</description> <description>Emulates a Philips Hue bridge to allow the Amazon Echo to hook up to other HA systems, i.e. Vera or Harmony Hub or Nest, using lightweight frameworks</description>
<properties> <properties>
<java.version>1.8</java.version> <java.version>1.8</java.version>
@@ -30,15 +30,25 @@
<artifactId>harmony-java-client</artifactId> <artifactId>harmony-java-client</artifactId>
<version>1.0.8</version> <version>1.0.8</version>
</dependency> </dependency>
<dependency>
<groupId>com.github.bwssytems</groupId>
<artifactId>nest-controller</artifactId>
<version>1.0.3</version>
</dependency>
<dependency> <dependency>
<groupId>com.sparkjava</groupId> <groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId> <artifactId>spark-core</artifactId>
<version>2.2</version> <version>2.3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<version>4.3.6</version> <version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.4</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>

View File

@@ -1,17 +1,23 @@
package com.bwssystems.HABridge; package com.bwssystems.HABridge;
import java.util.List;
public class BridgeSettings { public class BridgeSettings {
private String upnpconfigaddress; private String upnpconfigaddress;
private String serverport; private String serverport;
private String upnpresponseport; private String upnpresponseport;
private String upnpdevicedb; private String upnpdevicedb;
private String veraaddress; private IpList veraaddress;
private String harmonyaddress; private IpList harmonyaddress;
private String harmonyuser; private String harmonyuser;
private String harmonypwd; private String harmonypwd;
private Integer upnpresponsedevices;
private boolean upnpstrict; private boolean upnpstrict;
private boolean traceupnp; private boolean traceupnp;
private boolean devmode; private boolean devmode;
private String nestuser;
private String nestpwd;
private boolean nestconfigured;
public String getUpnpConfigAddress() { public String getUpnpConfigAddress() {
return upnpconfigaddress; return upnpconfigaddress;
@@ -37,16 +43,16 @@ public class BridgeSettings {
public void setUpnpDeviceDb(String upnpDeviceDb) { public void setUpnpDeviceDb(String upnpDeviceDb) {
this.upnpdevicedb = upnpDeviceDb; this.upnpdevicedb = upnpDeviceDb;
} }
public String getVeraAddress() { public IpList getVeraAddress() {
return veraaddress; return veraaddress;
} }
public void setVeraAddress(String veraAddress) { public void setVeraAddress(IpList veraAddress) {
this.veraaddress = veraAddress; this.veraaddress = veraAddress;
} }
public String getHarmonyAddress() { public IpList getHarmonyAddress() {
return harmonyaddress; return harmonyaddress;
} }
public void setHarmonyAddress(String harmonyaddress) { public void setHarmonyAddress(IpList harmonyaddress) {
this.harmonyaddress = harmonyaddress; this.harmonyaddress = harmonyaddress;
} }
public String getHarmonyUser() { public String getHarmonyUser() {
@@ -61,6 +67,12 @@ public class BridgeSettings {
public void setHarmonyPwd(String harmonypwd) { public void setHarmonyPwd(String harmonypwd) {
this.harmonypwd = harmonypwd; this.harmonypwd = harmonypwd;
} }
public Integer getUpnpResponseDevices() {
return upnpresponsedevices;
}
public void setUpnpResponseDevices(Integer upnpresponsedevices) {
this.upnpresponsedevices = upnpresponsedevices;
}
public boolean isUpnpStrict() { public boolean isUpnpStrict() {
return upnpstrict; return upnpstrict;
} }
@@ -79,13 +91,33 @@ public class BridgeSettings {
public void setDevMode(boolean devmode) { public void setDevMode(boolean devmode) {
this.devmode = devmode; this.devmode = devmode;
} }
public String getNestuser() {
return nestuser;
}
public void setNestuser(String nestuser) {
this.nestuser = nestuser;
}
public String getNestpwd() {
return nestpwd;
}
public void setNestpwd(String nestpwd) {
this.nestpwd = nestpwd;
}
public boolean isNestConfigured() {
return nestconfigured;
}
public void setNestConfigured(boolean isNestConfigured) {
this.nestconfigured = isNestConfigured;
}
public Boolean isValidVera() { public Boolean isValidVera() {
if(this.veraaddress.contains(Configuration.DEFAULT_VERA_ADDRESS)) List<NamedIP> devicesList = this.veraaddress.getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false; return false;
return true; return true;
} }
public Boolean isValidHarmony() { public Boolean isValidHarmony() {
if(this.harmonyaddress.contains(Configuration.DEFAULT_HARMONY_ADDRESS)) List<NamedIP> devicesList = this.harmonyaddress.getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false; return false;
if(this.harmonypwd == null || this.harmonypwd == "") if(this.harmonypwd == null || this.harmonypwd == "")
return false; return false;
@@ -93,4 +125,11 @@ public class BridgeSettings {
return false; return false;
return true; return true;
} }
public Boolean isValidNest() {
if(this.nestpwd == null || this.nestpwd == "")
return false;
if(this.nestuser == null || this.nestuser == "")
return false;
return true;
}
} }

View File

@@ -3,9 +3,12 @@ package com.bwssystems.HABridge;
public class Configuration { public class Configuration {
public final static String DEVICE_DB_DIRECTORY = "data/device.db"; public final static String DEVICE_DB_DIRECTORY = "data/device.db";
public final static String UPNP_RESPONSE_PORT = "50000"; public final static String UPNP_RESPONSE_PORT = "50000";
public final static String DEFAULT_VERA_ADDRESS = "1.1.1.1"; public final static String UPNP_RESPONSE_DEVICES = "30";
public final static String DEFAULT_HARMONY_ADDRESS = "1.1.1.1"; public final static String DEFAULT_ADDRESS = "1.1.1.1";
public final static String DEFAULT_HARMONY_USER = ""; public final static String LOOP_BACK_ADDRESS = "127.0.0.1";
public final static String DEFAULT_HARMONY_PWD = ""; public final static String LOOP_BACK_INTERFACE = "lo";
public final static String DEFAULT_HARMONY_ADDRESS_LIST = "{devices:[{name:default,ip:1.1.1.1}]}";
public final static String DEFAULT_USER = "";
public final static String DEFAULT_PWD = "";
public final static String DFAULT_WEB_PORT = "8080"; public final static String DFAULT_WEB_PORT = "8080";
} }

View File

@@ -3,8 +3,11 @@ package com.bwssystems.HABridge;
import static spark.Spark.*; import static spark.Spark.*;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -12,7 +15,9 @@ import com.bwssystems.HABridge.devicemanagmeent.*;
import com.bwssystems.HABridge.hue.HueMulator; import com.bwssystems.HABridge.hue.HueMulator;
import com.bwssystems.HABridge.upnp.UpnpListener; import com.bwssystems.HABridge.upnp.UpnpListener;
import com.bwssystems.HABridge.upnp.UpnpSettingsResource; import com.bwssystems.HABridge.upnp.UpnpSettingsResource;
import com.bwssystems.harmony.HarmonyServer; import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.HarmonyHome;
import com.google.gson.Gson;
public class HABridge { public class HABridge {
@@ -34,39 +39,90 @@ public class HABridge {
public static void main(String[] args) { public static void main(String[] args) {
Logger log = LoggerFactory.getLogger(HABridge.class); Logger log = LoggerFactory.getLogger(HABridge.class);
DeviceResource theResources; DeviceResource theResources;
HarmonyServer myHarmonyServer; HarmonyHome harmonyHome;
NestHome nestHome;
HueMulator theHueMulator; HueMulator theHueMulator;
UpnpSettingsResource theSettingResponder; UpnpSettingsResource theSettingResponder;
UpnpListener theUpnpListener; UpnpListener theUpnpListener;
InetAddress address; InetAddress address = null;
String addressString; String addressString = null;
BridgeSettings bridgeSettings; BridgeSettings bridgeSettings;
Version theVersion; Version theVersion;
theVersion = new Version(); theVersion = new Version();
log.info("HA Bridge (v" + theVersion.getVersion() + ") starting setup...."); log.info("HA Bridge (v" + theVersion.getVersion() + ") starting setup....");
//get ip address for upnp requests
bridgeSettings = new BridgeSettings();
bridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DFAULT_WEB_PORT));
bridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address", Configuration.DEFAULT_ADDRESS));
if(bridgeSettings.getUpnpConfigAddress().equalsIgnoreCase(Configuration.DEFAULT_ADDRESS)) {
try { try {
address = InetAddress.getLocalHost(); log.info("Getting an IP address for this host....");
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
while (ifs.hasMoreElements() && addressString == null) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements() && IPsPerNic == 0) {
address = addrs.nextElement();
if (InetAddressUtils.isIPv4Address(address.getHostAddress())) {
log.debug(name + " ... has IPV4 addr " + address);
if(!name.equalsIgnoreCase(Configuration.LOOP_BACK_INTERFACE)|| !address.getHostAddress().equalsIgnoreCase(Configuration.LOOP_BACK_ADDRESS)) {
IPsPerNic++;
addressString = address.getHostAddress(); addressString = address.getHostAddress();
} catch (UnknownHostException e) { log.info("Adding " + addressString + " from interface " + name + " as our default upnp config address.");
}
}
}
}
} catch (SocketException e) {
log.error("Cannot get ip address of this host, Exiting with message: " + e.getMessage(), e); log.error("Cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
return; return;
} }
bridgeSettings = new BridgeSettings(); bridgeSettings.setUpnpConfigAddress(addressString);
bridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DFAULT_WEB_PORT)); }
bridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address", addressString));
bridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db", Configuration.DEVICE_DB_DIRECTORY)); bridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db", Configuration.DEVICE_DB_DIRECTORY));
bridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT)); bridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
bridgeSettings.setVeraAddress(System.getProperty("vera.address", Configuration.DEFAULT_VERA_ADDRESS)); IpList theVeraList;
bridgeSettings.setHarmonyAddress(System.getProperty("harmony.address", Configuration.DEFAULT_HARMONY_ADDRESS));
bridgeSettings.setHarmonyUser(System.getProperty("harmony.user", Configuration.DEFAULT_HARMONY_USER)); try {
bridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd", Configuration.DEFAULT_HARMONY_PWD)); theVeraList = new Gson().fromJson(System.getProperty("vera.address", Configuration.DEFAULT_HARMONY_ADDRESS_LIST), IpList.class);
} catch (Exception e) {
try {
theVeraList = new Gson().fromJson("{devices:[{name:default,ip:" + System.getProperty("vera.address", Configuration.DEFAULT_ADDRESS) + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse vera.address, Exiting with message: " + e.getMessage(), e);
return;
}
}
bridgeSettings.setVeraAddress(theVeraList);
IpList theHarmonyList;
try {
theHarmonyList = new Gson().fromJson(System.getProperty("harmony.address", Configuration.DEFAULT_HARMONY_ADDRESS_LIST), IpList.class);
} catch (Exception e) {
try {
theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + System.getProperty("harmony.address", Configuration.DEFAULT_ADDRESS) + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse harmony.address, Exiting with message: " + e.getMessage(), e);
return;
}
}
bridgeSettings.setHarmonyAddress(theHarmonyList);
bridgeSettings.setHarmonyUser(System.getProperty("harmony.user", Configuration.DEFAULT_USER));
bridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd", Configuration.DEFAULT_PWD));
bridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true"))); bridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
bridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false"))); bridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
bridgeSettings.setDevMode(Boolean.parseBoolean(System.getProperty("dev.mode", "false"))); bridgeSettings.setDevMode(Boolean.parseBoolean(System.getProperty("dev.mode", "false")));
bridgeSettings.setUpnpResponseDevices(Integer.parseInt(System.getProperty("upnp.response.devices", Configuration.UPNP_RESPONSE_DEVICES)));
bridgeSettings.setNestuser(System.getProperty("nest.user", Configuration.DEFAULT_USER));
bridgeSettings.setNestpwd(System.getProperty("nest.pwd", Configuration.DEFAULT_PWD));
// sparkjava config directive to set ip address for the web server to listen on // sparkjava config directive to set ip address for the web server to listen on
// ipAddress("0.0.0.0"); // not used // ipAddress("0.0.0.0"); // not used
@@ -75,16 +131,13 @@ public class HABridge {
// sparkjava config directive to set html static file location for Jetty // sparkjava config directive to set html static file location for Jetty
staticFileLocation("/public"); staticFileLocation("/public");
//setup the harmony connection if available //setup the harmony connection if available
try { harmonyHome = new HarmonyHome(bridgeSettings);
myHarmonyServer = HarmonyServer.setup(bridgeSettings); //setup the nest connection if available
} catch (Exception e) { nestHome = new NestHome(bridgeSettings);
log.error("Cannot get harmony client setup, Exiting with message: " + e.getMessage(), e);
return;
}
// setup the class to handle the resource setup rest api // setup the class to handle the resource setup rest api
theResources = new DeviceResource(bridgeSettings, theVersion, myHarmonyServer.getMyHarmony()); theResources = new DeviceResource(bridgeSettings, theVersion, harmonyHome, nestHome);
// setup the class to handle the hue emulator rest api // setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(theResources.getDeviceRepository(), myHarmonyServer.getMyHarmony()); theHueMulator = new HueMulator(bridgeSettings, theResources.getDeviceRepository(), harmonyHome, nestHome);
theHueMulator.setupServer(); theHueMulator.setupServer();
// setup the class to handle the upnp response rest api // setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(bridgeSettings); theSettingResponder = new UpnpSettingsResource(bridgeSettings);

View File

@@ -0,0 +1,16 @@
package com.bwssystems.HABridge;
import java.util.List;
public class IpList {
private List<NamedIP> devices;
public List<NamedIP> getDevices() {
return devices;
}
public void setDevices(List<NamedIP> devices) {
this.devices = devices;
}
}

View File

@@ -0,0 +1,25 @@
package com.bwssystems.HABridge;
public class NamedIP {
private String name;
private String ip;
private String port;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
}

View File

@@ -12,11 +12,17 @@ public class HueApiResponse {
private Map<String, DeviceResponse> lights; private Map<String, DeviceResponse> lights;
private Map<String, String> scenes; private Map<String, String> scenes;
private Map<String, String> groups; private Map<String, String> groups;
private Map<String, String> schedules;
private Map<String, String> sensors;
private Map<String, String> rules;
private HueConfig config; private HueConfig config;
public HueApiResponse(String name, String ipaddress, String devicetype, String userid) { public HueApiResponse(String name, String ipaddress, String devicetype, String userid) {
super(); super();
this.setConfig(HueConfig.createConfig(name, ipaddress, devicetype, userid)); this.setConfig(HueConfig.createConfig(name, ipaddress, devicetype, userid));
this.setRules(new HashMap<>());
this.setSensors(new HashMap<>());
this.setSchedules(new HashMap<>());
this.setGroups(new HashMap<>()); this.setGroups(new HashMap<>());
this.setScenes(new HashMap<>()); this.setScenes(new HashMap<>());
} }
@@ -45,6 +51,30 @@ public class HueApiResponse {
this.groups = groups; this.groups = groups;
} }
public Map<String, String> getSchedules() {
return schedules;
}
public void setSchedules(Map<String, String> schedules) {
this.schedules = schedules;
}
public Map<String, String> getSensors() {
return sensors;
}
public void setSensors(Map<String, String> sensors) {
this.sensors = sensors;
}
public Map<String, String> getRules() {
return rules;
}
public void setRules(Map<String, String> rules) {
this.rules = rules;
}
public HueConfig getConfig() { public HueConfig getConfig() {
return config; return config;
} }

View File

@@ -84,6 +84,10 @@ public class HueConfig
sb.append("00:00:88:00:bb:ee"); sb.append("00:00:88:00:bb:ee");
} catch (Exception e){
sb.append("00:00:88:00:bb:ee");
} }
return sb.toString(); return sb.toString();

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.dao;
public class BackupFilename {
private String filename;
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}

View File

@@ -8,6 +8,7 @@ public class DeviceDescriptor{
private String mapId; private String mapId;
private String mapType; private String mapType;
private String deviceType; private String deviceType;
private String targetDevice;
private String offUrl; private String offUrl;
private String onUrl; private String onUrl;
private String httpVerb; private String httpVerb;
@@ -47,6 +48,14 @@ public class DeviceDescriptor{
this.deviceType = deviceType; this.deviceType = deviceType;
} }
public String getTargetDevice() {
return targetDevice;
}
public void setTargetDevice(String targetDevice) {
this.targetDevice = targetDevice;
}
public String getOffUrl() { public String getOffUrl() {
return offUrl; return offUrl;
} }

View File

@@ -3,12 +3,17 @@ package com.bwssystems.HABridge.dao;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import java.io.StringReader;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
@@ -33,8 +38,16 @@ public class DeviceRepository {
public DeviceRepository(String deviceDb) { public DeviceRepository(String deviceDb) {
super(); super();
repositoryPath = Paths.get(deviceDb); _loadRepository(deviceDb);
String jsonContent = repositoryReader(repositoryPath); }
private void _loadRepository(String aFilePath){
repositoryPath = Paths.get(aFilePath);
_loadRepository(repositoryPath);
}
private void _loadRepository(Path aPath){
String jsonContent = repositoryReader(aPath);
devices = new HashMap<String, DeviceDescriptor>(); devices = new HashMap<String, DeviceDescriptor>();
if(jsonContent != null) if(jsonContent != null)
@@ -47,6 +60,7 @@ public class DeviceRepository {
put(theDevice.getId(), theDevice); put(theDevice.getId(), theDevice);
} }
} }
} }
public List<DeviceDescriptor> findAll() { public List<DeviceDescriptor> findAll() {
@@ -79,6 +93,66 @@ public class DeviceRepository {
log.debug("Save device: " + aDescriptor.getName()); log.debug("Save device: " + aDescriptor.getName());
} }
public String backup(String aFilename) {
if(aFilename == null || aFilename.equalsIgnoreCase("")) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
aFilename = "devicedb-" + dateFormat.format(Calendar.getInstance().getTime()) + ".bk";
}
else
aFilename = aFilename + ".bk";
try {
Files.copy(repositoryPath, FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Could not backup to file: " + aFilename + " message: " + e.getMessage(), e);
}
log.debug("Backup repository: " + aFilename);
return aFilename;
}
public String deleteBackup(String aFilename) {
log.debug("Delete backup repository: " + aFilename);
try {
Files.delete(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename));
} catch (IOException e) {
log.error("Could not delete file: " + aFilename + " message: " + e.getMessage(), e);
}
return aFilename;
}
public String restoreBackup(String aFilename) {
log.debug("Restore backup repository: " + aFilename);
try {
Path target = null;
if(Files.exists(repositoryPath)) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
target = FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), "devicedb-" + dateFormat.format(Calendar.getInstance().getTime()) + ".bk");
Files.move(repositoryPath, target);
}
Files.copy(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), repositoryPath, StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Error restoring the file: " + aFilename + " message: " + e.getMessage(), e);
return null;
}
_loadRepository(repositoryPath);
return aFilename;
}
public List<String> getBackups() {
List<String> theFilenames = new ArrayList<String>();
Path dir = repositoryPath.getParent();
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(dir, "*.{bk}")) {
for (Path entry: stream) {
theFilenames.add(entry.getFileName().toString());
}
} catch (IOException x) {
// IOException can never be thrown by the iteration.
// In this snippet, it can // only be thrown by newDirectoryStream.
log.error("Issue getting direcotyr listing for backups - " + x.getMessage());
}
return theFilenames;
}
public String delete(DeviceDescriptor aDescriptor) { public String delete(DeviceDescriptor aDescriptor) {
if (aDescriptor != null) { if (aDescriptor != null) {
devices.remove(aDescriptor.getId()); devices.remove(aDescriptor.getId());
@@ -187,6 +261,9 @@ public class DeviceRepository {
} else if (name.equals("deviceType")) { } else if (name.equals("deviceType")) {
deviceEntry.setDeviceType(reader.nextString()); deviceEntry.setDeviceType(reader.nextString());
log.debug("Read a Device - device json type:" + deviceEntry.getDeviceType()); log.debug("Read a Device - device json type:" + deviceEntry.getDeviceType());
} else if (name.equals("targetDevice")) {
deviceEntry.setTargetDevice(reader.nextString());
log.debug("Read a Device - device json type:" + deviceEntry.getTargetDevice());
} else if (name.equals("offUrl")) { } else if (name.equals("offUrl")) {
deviceEntry.setOffUrl(reader.nextString()); deviceEntry.setOffUrl(reader.nextString());
log.debug("Read a Device - device json off URL:" + deviceEntry.getOffUrl()); log.debug("Read a Device - device json off URL:" + deviceEntry.getOffUrl());

View File

@@ -18,10 +18,13 @@ import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings; import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer; import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.Version; import com.bwssystems.HABridge.Version;
import com.bwssystems.HABridge.dao.BackupFilename;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.DeviceRepository; import com.bwssystems.HABridge.dao.DeviceRepository;
import com.bwssystems.harmony.HarmonyHandler; import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.luupRequests.Sdata; import com.bwssystems.luupRequests.Sdata;
import com.bwssystems.vera.VeraHome;
import com.bwssystems.vera.VeraInfo; import com.bwssystems.vera.VeraInfo;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -33,16 +36,30 @@ public class DeviceResource {
private static final Logger log = LoggerFactory.getLogger(DeviceResource.class); private static final Logger log = LoggerFactory.getLogger(DeviceResource.class);
private DeviceRepository deviceRepository; private DeviceRepository deviceRepository;
private VeraInfo veraInfo; private VeraHome veraHome;
private Version version; private Version version;
private HarmonyHandler myHarmonyHandler; private HarmonyHome myHarmonyHome;
private NestHome nestHome;
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post")); private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
public DeviceResource(BridgeSettings theSettings, Version theVersion, HarmonyHandler myHarmony) { public DeviceResource(BridgeSettings theSettings, Version theVersion, HarmonyHome theHarmonyHome, NestHome aNestHome) {
super();
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb()); this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
this.veraInfo = new VeraInfo(theSettings.getVeraAddress(), theSettings.isValidVera());
this.myHarmonyHandler = myHarmony; if(theSettings.isValidVera())
this.veraHome = new VeraHome(theSettings);
else
this.veraHome = null;
if(theSettings.isValidHarmony())
this.myHarmonyHome = theHarmonyHome;
else
this.myHarmonyHome = null;
if(theSettings.isValidNest())
this.nestHome = aNestHome;
else
this.nestHome = null;
this.version = theVersion; this.version = theVersion;
setupEndpoints(); setupEndpoints();
} }
@@ -109,6 +126,7 @@ public class DeviceResource {
deviceEntry.setDeviceType(device.getDeviceType()); deviceEntry.setDeviceType(device.getDeviceType());
deviceEntry.setMapId(device.getMapId()); deviceEntry.setMapId(device.getMapId());
deviceEntry.setMapType(device.getMapType()); deviceEntry.setMapType(device.getMapType());
deviceEntry.setTargetDevice(device.getTargetDevice());
deviceEntry.setOnUrl(device.getOnUrl()); deviceEntry.setOnUrl(device.getOnUrl());
deviceEntry.setOffUrl(device.getOffUrl()); deviceEntry.setOffUrl(device.getOffUrl());
deviceEntry.setHttpVerb(device.getHttpVerb()); deviceEntry.setHttpVerb(device.getHttpVerb());
@@ -164,56 +182,124 @@ public class DeviceResource {
get (API_CONTEXT + "/vera/devices", "application/json", (request, response) -> { get (API_CONTEXT + "/vera/devices", "application/json", (request, response) -> {
log.debug("Get vera devices"); log.debug("Get vera devices");
Sdata sData = veraInfo.getSdata(); if(veraHome == null){
if(sData == null){
response.status(HttpStatus.SC_NOT_FOUND); response.status(HttpStatus.SC_NOT_FOUND);
return null; return null;
} }
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
return sData.getDevices(); return veraHome.getDevices();
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/vera/scenes", "application/json", (request, response) -> { get (API_CONTEXT + "/vera/scenes", "application/json", (request, response) -> {
log.debug("Get vera scenes"); log.debug("Get vera scenes");
Sdata sData = veraInfo.getSdata(); if(veraHome == null){
if(sData == null){
response.status(HttpStatus.SC_NOT_FOUND); response.status(HttpStatus.SC_NOT_FOUND);
return null; return null;
} }
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
return sData.getScenes(); return veraHome.getScenes();
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/harmony/activities", "application/json", (request, response) -> { get (API_CONTEXT + "/harmony/activities", "application/json", (request, response) -> {
log.debug("Get harmony activities"); log.debug("Get harmony activities");
if(myHarmonyHandler == null) { if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND); response.status(HttpStatus.SC_NOT_FOUND);
return null; return null;
} }
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
return myHarmonyHandler.getActivities(); return myHarmonyHome.getActivities();
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/harmony/show", "application/json", (request, response) -> { get (API_CONTEXT + "/harmony/show", "application/json", (request, response) -> {
log.debug("Get harmony current activity"); log.debug("Get harmony current activity");
if(myHarmonyHandler == null) { if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND); response.status(HttpStatus.SC_NOT_FOUND);
return null; return null;
} }
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
return myHarmonyHandler.getCurrentActivity(); return myHarmonyHome.getCurrentActivities();
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/harmony/devices", "application/json", (request, response) -> { get (API_CONTEXT + "/harmony/devices", "application/json", (request, response) -> {
log.debug("Get harmony devices"); log.debug("Get harmony devices");
if(myHarmonyHandler == null) { if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND); response.status(HttpStatus.SC_NOT_FOUND);
return null; return null;
} }
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
return myHarmonyHandler.getDevices(); return myHarmonyHome.getDevices();
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/nest/items", "application/json", (request, response) -> {
log.debug("Get nest items");
if(nestHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
}
response.status(HttpStatus.SC_OK);
return nestHome.getItems();
}, new JsonTransformer());
get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> {
log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK);
return deviceRepository.getBackups();
}, new JsonTransformer());
// http://ip_address:port/api/devices/backup/create CORS request
options(API_CONTEXT + "/backup/create", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
put (API_CONTEXT + "/backup/create", "application/json", (request, response) -> {
log.debug("Create backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
BackupFilename returnFilename = new BackupFilename();
returnFilename.setFilename(deviceRepository.backup(aFilename.getFilename()));
return returnFilename;
}, new JsonTransformer());
// http://ip_address:port/api/devices/backup/delete CORS request
options(API_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (API_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
log.debug("Delete backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null)
deviceRepository.deleteBackup(aFilename.getFilename());
else
log.warn("No filename given for delete backup.");
return null;
}, new JsonTransformer());
// http://ip_address:port/api/devices/backup/restore CORS request
options(API_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (API_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
log.debug("Restore backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null)
deviceRepository.restoreBackup(aFilename.getFilename());
else
log.warn("No filename given for restore backup.");
return null;
}, new JsonTransformer());
} }
} }

View File

@@ -1,14 +1,19 @@
package com.bwssystems.HABridge.hue; package com.bwssystems.HABridge.hue;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer; import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.api.UserCreateRequest; import com.bwssystems.HABridge.api.UserCreateRequest;
import com.bwssystems.HABridge.api.hue.DeviceResponse; import com.bwssystems.HABridge.api.hue.DeviceResponse;
import com.bwssystems.HABridge.api.hue.DeviceState; import com.bwssystems.HABridge.api.hue.DeviceState;
import com.bwssystems.HABridge.api.hue.HueApiResponse; import com.bwssystems.HABridge.api.hue.HueApiResponse;
import com.bwssystems.HABridge.dao.*; import com.bwssystems.HABridge.dao.*;
import com.bwssystems.NestBridge.NestInstruction;
import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.ButtonPress; import com.bwssystems.harmony.ButtonPress;
import com.bwssystems.harmony.HarmonyHandler; import com.bwssystems.harmony.HarmonyHandler;
import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.harmony.RunActivity; import com.bwssystems.harmony.RunActivity;
import com.bwssystems.nest.controller.Nest;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -37,10 +42,15 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.xml.bind.DatatypeConverter;
/** /**
* Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server * Based on Armzilla's HueMulator - a Philips Hue emulator using sparkjava rest server
*/ */
@@ -55,17 +65,28 @@ public class HueMulator {
private static final String HUE_CONTEXT = "/api"; private static final String HUE_CONTEXT = "/api";
private DeviceRepository repository; private DeviceRepository repository;
private HarmonyHandler myHarmony; private HarmonyHome myHarmonyHome;
private Nest theNest;
private HttpClient httpClient; private HttpClient httpClient;
private ObjectMapper mapper; private ObjectMapper mapper;
private BridgeSettings bridgeSettings;
private byte[] sendData;
public HueMulator(DeviceRepository aDeviceRepository, HarmonyHandler theHandler){ public HueMulator(BridgeSettings theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome){
httpClient = HttpClients.createDefault(); httpClient = HttpClients.createDefault();
mapper = new ObjectMapper(); //armzilla: work around Echo incorrect content type and breaking mapping. Map manually mapper = new ObjectMapper(); //armzilla: work around Echo incorrect content type and breaking mapping. Map manually
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
repository = aDeviceRepository; repository = aDeviceRepository;
myHarmony = theHandler; if(theBridgeSettings.isValidHarmony())
this.myHarmonyHome = theHarmonyHome;
else
this.myHarmonyHome = null;
if(theBridgeSettings.isValidNest())
this.theNest = aNestHome.getTheNest();
else
this.theNest = null;
bridgeSettings = theBridgeSettings;
} }
// This function sets up the sparkjava rest calls for the hue api // This function sets up the sparkjava rest calls for the hue api
@@ -74,6 +95,8 @@ public class HueMulator {
// http://ip_address:port/api/{userId}/lights returns json objects of all lights configured // http://ip_address:port/api/{userId}/lights returns json objects of all lights configured
get(HUE_CONTEXT + "/:userid/lights", "application/json", (request, response) -> { get(HUE_CONTEXT + "/:userid/lights", "application/json", (request, response) -> {
String userId = request.params(":userid"); String userId = request.params(":userid");
if(bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue lights list requested: " + userId + " from " + request.ip());
log.debug("hue lights list requested: " + userId + " from " + request.ip()); log.debug("hue lights list requested: " + userId + " from " + request.ip());
List<DeviceDescriptor> deviceList = repository.findAll(); List<DeviceDescriptor> deviceList = repository.findAll();
Map<String, DeviceResponse> deviceResponseMap = new HashMap<>(); Map<String, DeviceResponse> deviceResponseMap = new HashMap<>();
@@ -101,6 +124,8 @@ public class HueMulator {
String newUser = null; String newUser = null;
String aDeviceType = null; String aDeviceType = null;
if(bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api/ user create requested: " + request.body() + " from " + request.ip());
log.debug("hue api user create requested: " + request.body() + " from " + request.ip()); log.debug("hue api user create requested: " + request.body() + " from " + request.ip());
if(request.body() != null && !request.body().isEmpty()) { if(request.body() != null && !request.body().isEmpty()) {
@@ -113,6 +138,8 @@ public class HueMulator {
if(aDeviceType == null) if(aDeviceType == null)
aDeviceType = "<not given>"; aDeviceType = "<not given>";
if(bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api user create requested for device type: " + aDeviceType + " and username: " + newUser);
log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser); log.debug("hue api user create requested for device type: " + aDeviceType + " and username: " + newUser);
response.header("Access-Control-Allow-Origin", request.headers("Origin")); response.header("Access-Control-Allow-Origin", request.headers("Origin"));
@@ -136,7 +163,8 @@ public class HueMulator {
String newUser = null; String newUser = null;
String aDeviceType = null; String aDeviceType = null;
log.info("HH trace: hue api user create requested: " + request.body() + " from " + request.ip()); if(bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api/* user create requested: " + request.body() + " from " + request.ip());
if(request.body() != null && !request.body().isEmpty()) { if(request.body() != null && !request.body().isEmpty()) {
aNewUser = new Gson().fromJson(request.body(), UserCreateRequest.class); aNewUser = new Gson().fromJson(request.body(), UserCreateRequest.class);
@@ -158,22 +186,26 @@ public class HueMulator {
// http://ip_address:port/api/config returns json objects for the config when no user is given // http://ip_address:port/api/config returns json objects for the config when no user is given
get(HUE_CONTEXT + "/config", "application/json", (request, response) -> { get(HUE_CONTEXT + "/config", "application/json", (request, response) -> {
String userId = request.params(":userid"); if(bridgeSettings.isTraceupnp())
log.debug("hue api config requested: " + userId + " from " + request.ip()); log.info("Traceupnp: hue api/config config requested: <no_user> from " + request.ip());
HueApiResponse apiResponse = new HueApiResponse("Philips hue", request.ip(), "My App", userId); log.debug("hue api config requested: <no_user> from " + request.ip());
HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(), "My App", "none");
response.type("application/json; charset=utf-8"); response.type("application/json; charset=utf-8");
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
String responseString = null; // String responseString = null;
responseString = "[{\"swversion\":\"" + apiResponse.getConfig().getSwversion() + "\",\"apiversion\":\"" + apiResponse.getConfig().getApiversion() + "\",\"name\":\"" + apiResponse.getConfig().getName() + "\",\"mac\":\"" + apiResponse.getConfig().getMac() + "\"}]"; // responseString = "[{\"swversion\":\"" + apiResponse.getConfig().getSwversion() + "\",\"apiversion\":\"" + apiResponse.getConfig().getApiversion() + "\",\"name\":\"" + apiResponse.getConfig().getName() + "\",\"mac\":\"" + apiResponse.getConfig().getMac() + "\"}]";
return responseString; // return responseString;
}); return apiResponse.getConfig();
}, new JsonTransformer());
// http://ip_address:port/api/{userId}/config returns json objects for the config // http://ip_address:port/api/{userId}/config returns json objects for the config
get(HUE_CONTEXT + "/:userid/config", "application/json", (request, response) -> { get(HUE_CONTEXT + "/:userid/config", "application/json", (request, response) -> {
String userId = request.params(":userid"); String userId = request.params(":userid");
if(bridgeSettings.isTraceupnp())
log.info("Traceupnp: hue api/:userid/config config requested: " + userId + " from " + request.ip());
log.debug("hue api config requested: " + userId + " from " + request.ip()); log.debug("hue api config requested: " + userId + " from " + request.ip());
HueApiResponse apiResponse = new HueApiResponse("Philips hue", request.ip(), "My App", userId); HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(), "My App", userId);
response.type("application/json; charset=utf-8"); response.type("application/json; charset=utf-8");
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
@@ -197,7 +229,7 @@ public class HueMulator {
deviceList.put(descriptor.getId(), deviceResponse); deviceList.put(descriptor.getId(), deviceResponse);
} }
); );
HueApiResponse apiResponse = new HueApiResponse("Philips hue", request.ip(), "My App", userId); HueApiResponse apiResponse = new HueApiResponse("Philips hue", bridgeSettings.getUpnpConfigAddress(), "My App", userId);
apiResponse.setLights(deviceList); apiResponse.setLights(deviceList);
response.type("application/json; charset=utf-8"); response.type("application/json; charset=utf-8");
@@ -241,26 +273,29 @@ public class HueMulator {
*/ */
String userId = request.params(":userid"); String userId = request.params(":userid");
String lightId = request.params(":id"); String lightId = request.params(":id");
log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body()); String responseString = null;
String url = null;
DeviceState state = null; DeviceState state = null;
log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body());
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json; charset=utf-8");
response.status(HttpStatus.SC_OK);
try { try {
state = mapper.readValue(request.body(), DeviceState.class); state = mapper.readValue(request.body(), DeviceState.class);
} catch (IOException e) { } catch (IOException e) {
log.error("Object mapper barfed on input of body.", e); log.warn("Object mapper barfed on input of body.", e);
response.status(HttpStatus.SC_BAD_REQUEST); responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId + ",\"description\": \"Object mapper barfed on input of body.\"}}]";
return null; return responseString;
} }
DeviceDescriptor device = repository.findOne(lightId); DeviceDescriptor device = repository.findOne(lightId);
if (device == null) { if (device == null) {
response.status(HttpStatus.SC_NOT_FOUND); log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " + request.ip() + " body: " + request.body());
log.error("Could not find devcie: " + 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 null; return responseString;
} }
String responseString =null;
String url = null;
if (state.isOn()) { if (state.isOn()) {
responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":true}}"; responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":true}}";
url = device.getOnUrl(); url = device.getOnUrl();
@@ -284,22 +319,110 @@ public class HueMulator {
else else
responseString = responseString + "]"; responseString = responseString + "]";
if(device.getDeviceType().toLowerCase().contains("activity") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyActivity")))
if(device.getDeviceType().toLowerCase().contains("activity"))
{ {
log.debug("executing activity to Harmony: " + url); log.debug("executing HUE api request to change activity to Harmony: " + url);
RunActivity anActivity = new Gson().fromJson(url, RunActivity.class); RunActivity anActivity = new Gson().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
myHarmony.startActivity(anActivity); myHarmony.startActivity(anActivity);
} }
else if(device.getDeviceType().toLowerCase().contains("button")) else if(device.getDeviceType().toLowerCase().contains("button") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyButton")))
{ {
log.debug("executing button press to Harmony: " + url); log.debug("executing HUE api request to button press(es) to Harmony: " + url);
ButtonPress aDeviceButton = new Gson().fromJson(url, ButtonPress.class); if(url.substring(0, 1).equalsIgnoreCase("{")) {
myHarmony.pressButton(aDeviceButton); url = "[" + url +"]";
}
ButtonPress[] deviceButtons = new Gson().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 {
for(int i = 0; i < deviceButtons.length; i++) {
if( i > 0)
Thread.sleep(100);
log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i));
myHarmony.pressButton(deviceButtons[i]);
}
}
}
else if(device.getDeviceType().toLowerCase().contains("home") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("nestHomeAway")))
{
log.debug("executing HUE api request to set away for nest home: " + url);
NestInstruction homeAway = new Gson().fromJson(url, NestInstruction.class);
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
theNest.getHome(homeAway.getName()).setAway(homeAway.getAway());
}
else if(device.getDeviceType().toLowerCase().contains("thermo") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("nestThermoSet")))
{
log.debug("executing HUE api request to set thermostat for nest: " + url);
NestInstruction thermoSetting = new Gson().fromJson(url, NestInstruction.class);
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 {
if(thermoSetting.getControl().equalsIgnoreCase("temp")) {
if(request.body().contains("bri")) {
thermoSetting.setTemp(String.valueOf((Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri())) - 32.0)/1.8));
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(url.startsWith("udp://"))
{
log.debug("executing HUE api request to UDP: " + url);
try {
String intermediate = url.substring(6);
String ipAddr = intermediate.substring(0, intermediate.indexOf(':'));
String port = intermediate.substring(intermediate.indexOf(':') + 1, intermediate.indexOf('/'));
String theBody = intermediate.substring(intermediate.indexOf('/')+1);
DatagramSocket responseSocket = new DatagramSocket(Integer.parseInt(port));
if(theBody.startsWith("0x")) {
sendData = DatatypeConverter.parseHexBinary(theBody.substring(2));
}
else
sendData = theBody.getBytes();
InetAddress IPAddress = InetAddress.getByName(ipAddr);
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, Integer.parseInt(port));
responseSocket.send(sendPacket);
responseSocket.close();
} catch (IOException e) {
log.warn("Could not send UDP Datagram packet for request.", e);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
} }
else else
{ {
log.debug("executing activity to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + url); log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + url);
// quick template // quick template
String body; String body;
url = replaceIntensityValue(url, state.getBri()); url = replaceIntensityValue(url, state.getBri());
@@ -309,15 +432,11 @@ public class HueMulator {
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri()); body = replaceIntensityValue(device.getContentBodyOff(), state.getBri());
// make call // make call
if (!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)) { if (!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)) {
response.status(HttpStatus.SC_SERVICE_UNAVAILABLE); log.warn("Error on calling url to change device state: " + url);
log.error("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\"}}]";
return null;
} }
} }
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json; charset=utf-8");
response.status(HttpStatus.SC_OK);
return responseString; return responseString;
}); });
} }
@@ -353,8 +472,9 @@ public class HueMulator {
Integer endResult = Math.round(result.floatValue()); Integer endResult = Math.round(result.floatValue());
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString()); request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString());
} catch (Exception e) { } catch (Exception e) {
log.error("Could not execute Math: " + mathDescriptor, e); log.warn("Could not execute Math: " + mathDescriptor, e);
} } }
}
return request; return request;
} }
@@ -386,7 +506,7 @@ public class HueMulator {
return true; return true;
} }
} catch (IOException e) { } catch (IOException e) {
log.error("Error calling out to HA gateway", e); log.warn("Error calling out to HA gateway", e);
} }
return false; return false;
} }

View File

@@ -96,30 +96,34 @@ public class UpnpListener {
//Only respond to discover request for strict upnp form //Only respond to discover request for strict upnp form
String packetString = new String(packet.getData()); String packetString = new String(packet.getData());
if(packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1") && packetString.contains("\"ssdp:discover\"")){ if(packetString != null && packetString.startsWith("M-SEARCH * HTTP/1.1") && packetString.contains("\"ssdp:discover\"")){
if(traceupnp) { log.debug("isSSDPDiscovery Found message to be an M-SEARCH message.");
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString); log.debug("isSSDPDiscovery Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
}
else {
log.debug("Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
log.debug("Found message to be an M-SEARCH message.");
}
if(strict && (packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") || packetString.contains("ST: upnp:rootdevice") || packetString.contains("ST: ssdp:all"))) if(strict && (packetString.contains("ST: urn:schemas-upnp-org:device:basic:1") || packetString.contains("ST: upnp:rootdevice") || packetString.contains("ST: ssdp:all")))
{ {
if(traceupnp) if(traceupnp) {
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
log.info("Traceupnp: isSSDPDiscovery found message to be valid under strict rules - strict: " + strict); log.info("Traceupnp: isSSDPDiscovery found message to be valid under strict rules - strict: " + strict);
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
log.debug("isSSDPDiscovery found message to be valid under strict rules - strict: " + strict);
return true; return true;
} }
else if (!strict) else if (!strict)
{ {
if(traceupnp) if(traceupnp) {
log.info("Traceupnp: isSSDPDiscovery found message to be an M-SEARCH message.");
log.info("Traceupnp: isSSDPDiscovery found message to be valid under loose rules - strict: " + strict); log.info("Traceupnp: isSSDPDiscovery found message to be valid under loose rules - strict: " + strict);
log.info("Traceupnp: SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
log.debug("isSSDPDiscovery found message to be valid under loose rules - strict: " + strict);
return true; return true;
} }
} }
if(traceupnp) else {
log.info("Traceupnp: isSSDPDiscovery found message to not be valid - strict: " + strict); // log.debug("isSSDPDiscovery found message to not be valid - strict: " + strict);
// log.debug("SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
}
return false; return false;
} }

View File

@@ -40,9 +40,20 @@ public class UpnpSettingsResource {
+ "<depth>24</depth>\n" + "<url>hue_logo_3.png</url>\n" + "</icon>\n" + "</iconList>\n" + "</device>\n" + "<depth>24</depth>\n" + "<url>hue_logo_3.png</url>\n" + "</icon>\n" + "</iconList>\n" + "</device>\n"
+ "</root>\n"; + "</root>\n";
public UpnpSettingsResource(BridgeSettings theSettings) { public UpnpSettingsResource(BridgeSettings theBridgeSettings) {
super(); super();
this.theSettings = theSettings; this.theSettings = new BridgeSettings();
this.theSettings.setDevMode(theBridgeSettings.isDevMode());
this.theSettings.setHarmonyAddress(theBridgeSettings.getHarmonyAddress());
this.theSettings.setServerPort(theBridgeSettings.getServerPort());
this.theSettings.setTraceupnp(theBridgeSettings.isTraceupnp());
this.theSettings.setUpnpConfigAddress(theBridgeSettings.getUpnpConfigAddress());
this.theSettings.setUpnpDeviceDb(theBridgeSettings.getUpnpDeviceDb());
this.theSettings.setUpnpResponseDevices(theBridgeSettings.getUpnpResponseDevices());
this.theSettings.setUpnpResponsePort(theBridgeSettings.getUpnpResponsePort());
this.theSettings.setUpnpStrict(theBridgeSettings.isUpnpStrict());
this.theSettings.setVeraAddress(theBridgeSettings.getVeraAddress());
this.theSettings.setNestConfigured(theBridgeSettings.isValidNest());
} }
public void setupServer() { public void setupServer() {

View File

@@ -0,0 +1,101 @@
package com.bwssystems.NestBridge;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.nest.controller.Home;
import com.bwssystems.nest.controller.Nest;
import com.bwssystems.nest.controller.NestSession;
import com.bwssystems.nest.controller.Thermostat;
import com.bwssystems.nest.protocol.error.LoginException;
import com.bwssystems.nest.protocol.status.WhereDetail;
import com.bwssystems.nest.protocol.status.WhereItem;
public class NestHome {
private static final Logger log = LoggerFactory.getLogger(NestHome.class);
private NestSession theSession;
private Nest theNest;
private ArrayList<NestItem> nestItems;
public NestHome(BridgeSettings bridgeSettings) {
theSession = null;
theNest = null;
nestItems = null;
if(!bridgeSettings.isValidNest()) {
log.info("not a valid nest");
return;
}
try {
theSession = new NestSession(bridgeSettings.getNestuser(), bridgeSettings.getNestpwd());
theNest = new Nest(theSession);
} catch (LoginException e) {
log.error("Caught Login Exception, exiting....");
theSession = null;
}
}
public List<NestItem> getItems() {
if(theNest == null)
return null;
if(nestItems == null) {
nestItems = new ArrayList<NestItem>();
Set<String> homeNames = theNest.getHomeNames();
Home aHome = null;
NestItem anItem = null;
for(String name : homeNames) {
aHome = theNest.getHome(name);
anItem = new NestItem();
anItem.setId(name);
anItem.setName(aHome.getDetail().getName());
anItem.setType("Home");
anItem.setLocation(aHome.getDetail().getLocation());
nestItems.add(anItem);
}
Thermostat thermo = null;
Set<String> thermoNames = theNest.getThermostatNames();
for(String name : thermoNames) {
thermo = theNest.getThermostat(name);
anItem = new NestItem();
anItem.setId(name);
anItem.setType("Thermostat");
String where = null;
String homeName= null;
Boolean found = false;
for(String aHomeName : homeNames) {
WhereDetail aDetail = theNest.getWhere(aHomeName);
ListIterator<WhereItem> anIterator = aDetail.getWheres().listIterator();
while(anIterator.hasNext()) {
WhereItem aWhereItem = (WhereItem) anIterator.next();
if(aWhereItem.getWhereId().equals(thermo.getDeviceDetail().getWhereId())) {
where = aWhereItem.getName();
homeName = theNest.getHome(aHomeName).getDetail().getName();
found = true;
break;
}
}
if(found)
break;
}
anItem.setName(where + "(" + name.substring(name.length() - 4) + ")");
anItem.setLocation(where + " - " + homeName);
nestItems.add(anItem);
}
}
return nestItems;
}
public Nest getTheNest() {
return theNest;
}
}

View File

@@ -0,0 +1,33 @@
package com.bwssystems.NestBridge;
public class NestInstruction {
private String name;
private Boolean away;
private String control;
private String temp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getAway() {
return away;
}
public void setAway(Boolean away) {
this.away = away;
}
public String getControl() {
return control;
}
public void setControl(String control) {
this.control = control;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
}

View File

@@ -0,0 +1,32 @@
package com.bwssystems.NestBridge;
public class NestItem {
private String name;
private String id;
private String type;
private String location;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String anid) {
id = anid;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}

View File

@@ -0,0 +1,21 @@
package com.bwssystems.harmony;
import net.whistlingfish.harmony.config.Activity;
public class HarmonyActivity {
private String hub;
private Activity activity;
public String getHub() {
return hub;
}
public void setHub(String hub) {
this.hub = hub;
}
public Activity getActivity() {
return activity;
}
public void setActivity(Activity activity) {
this.activity = activity;
}
}

View File

@@ -0,0 +1,20 @@
package com.bwssystems.harmony;
import net.whistlingfish.harmony.config.Device;
public class HarmonyDevice {
private Device device;
private String hub;
public Device getDevice() {
return device;
}
public void setDevice(Device device) {
this.device = device;
}
public String getHub() {
return hub;
}
public void setHub(String hub) {
this.hub = hub;
}
}

View File

@@ -0,0 +1,115 @@
package com.bwssystems.harmony;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.IpList;
import com.bwssystems.HABridge.NamedIP;
import net.whistlingfish.harmony.config.Activity;
import net.whistlingfish.harmony.config.Device;
public class HarmonyHome {
private static final Logger log = LoggerFactory.getLogger(HarmonyHome.class);
private Map<String, HarmonyServer> hubs;
public HarmonyHome(BridgeSettings bridgeSettings) {
super();
hubs = new HashMap<String, HarmonyServer>();
if(!bridgeSettings.isValidHarmony() && !bridgeSettings.isDevMode())
return;
if(bridgeSettings.isDevMode()) {
NamedIP devModeIp = new NamedIP();
devModeIp.setIp("10.10.10.10");
devModeIp.setName("devMode");
List<NamedIP> theList = new ArrayList<NamedIP>();
theList.add(devModeIp);
IpList thedevList = new IpList();
thedevList.setDevices(theList);
bridgeSettings.setHarmonyAddress(thedevList);
}
Iterator<NamedIP> theList = bridgeSettings.getHarmonyAddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHub = theList.next();
try {
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings, aHub));
} catch (Exception e) {
log.error("Cannot get harmony client (" + aHub.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
return;
}
}
}
public HarmonyHandler getHarmonyHandler(String aName) {
HarmonyHandler aHandler = null;
if(aName == null || aName.equals("")) {
aName = "default";
}
if(hubs.get(aName) == null) {
Set<String> keys = hubs.keySet();
if(!keys.isEmpty()) {
aHandler = hubs.get(keys.toArray()[0]).getMyHarmony();
}
else
aHandler = null;
}
else
aHandler = hubs.get(aName).getMyHarmony();
return aHandler;
}
public List<HarmonyActivity> getActivities() {
Iterator<String> keys = hubs.keySet().iterator();
ArrayList<HarmonyActivity> activityList = new ArrayList<HarmonyActivity>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Activity> activities = hubs.get(key).getMyHarmony().getActivities().iterator();
while(activities.hasNext()) {
HarmonyActivity anActivity = new HarmonyActivity();
anActivity.setActivity(activities.next());
anActivity.setHub(key);
activityList.add(anActivity);
}
}
return activityList;
}
public List<HarmonyActivity> getCurrentActivities() {
Iterator<String> keys = hubs.keySet().iterator();
ArrayList<HarmonyActivity> activityList = new ArrayList<HarmonyActivity>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Activity> activities = hubs.get(key).getMyHarmony().getActivities().iterator();
while(activities.hasNext()) {
HarmonyActivity anActivity = new HarmonyActivity();
anActivity.setActivity(activities.next());
anActivity.setHub(key);
activityList.add(anActivity);
}
}
return activityList;
}
public List<HarmonyDevice> getDevices() {
Iterator<String> keys = hubs.keySet().iterator();
ArrayList<HarmonyDevice> deviceList = new ArrayList<HarmonyDevice>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Device> devices = hubs.get(key).getMyHarmony().getDevices().iterator();
while(devices.hasNext()) {
HarmonyDevice aDevice = new HarmonyDevice();
aDevice.setDevice(devices.next());
aDevice.setHub(key);
deviceList.add(aDevice);
}
}
return deviceList;
}
}

View File

@@ -8,6 +8,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings; import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.NamedIP;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
@@ -24,23 +25,25 @@ public class HarmonyServer {
private HarmonyHandler myHarmony; private HarmonyHandler myHarmony;
private DevModeResponse devResponse; private DevModeResponse devResponse;
private OAReplyProvider dummyProvider; private OAReplyProvider dummyProvider;
private NamedIP myNameAndIP;
private Logger log = LoggerFactory.getLogger(HarmonyServer.class); private Logger log = LoggerFactory.getLogger(HarmonyServer.class);
public HarmonyServer() { public HarmonyServer(NamedIP theHarmonyAddress) {
super(); super();
myHarmony = null; myHarmony = null;
dummyProvider = null; dummyProvider = null;
myNameAndIP = theHarmonyAddress;
} }
public static HarmonyServer setup(BridgeSettings bridgeSettings) throws Exception { public static HarmonyServer setup(BridgeSettings bridgeSettings, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony()) { if(!bridgeSettings.isValidHarmony() && !bridgeSettings.isDevMode()) {
return new HarmonyServer(); return new HarmonyServer(theHarmonyAddress);
} }
Injector injector = null; Injector injector = null;
if(!bridgeSettings.isDevMode()) if(!bridgeSettings.isDevMode())
injector = Guice.createInjector(new HarmonyClientModule()); injector = Guice.createInjector(new HarmonyClientModule());
HarmonyServer mainObject = new HarmonyServer(); HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if(!bridgeSettings.isDevMode()) if(!bridgeSettings.isDevMode())
injector.injectMembers(mainObject); injector.injectMembers(mainObject);
mainObject.execute(bridgeSettings); mainObject.execute(bridgeSettings);
@@ -70,7 +73,7 @@ public class HarmonyServer {
log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel())); log.info(format("activity changed: [%d] %s", activity.getId(), activity.getLabel()));
} }
}); });
harmonyClient.connect(mySettings.getHarmonyAddress(), mySettings.getHarmonyUser(), mySettings.getHarmonyPwd()); harmonyClient.connect(myNameAndIP.getIp(), mySettings.getHarmonyUser(), mySettings.getHarmonyPwd());
} }
myHarmony = new HarmonyHandler(harmonyClient, noopCalls, devResponse); myHarmony = new HarmonyHandler(harmonyClient, noopCalls, devResponse);
} }

View File

@@ -13,6 +13,8 @@ public class Device {
private String level; private String level;
private String state; private String state;
private String comment; private String comment;
private String veraname;
private String veraaddress;
public String getName() { public String getName() {
return name; return name;
} }
@@ -79,5 +81,17 @@ public class Device {
public void setComment(String comment) { public void setComment(String comment) {
this.comment = comment; this.comment = comment;
} }
public String getVeraname() {
return veraname;
}
public void setVeraname(String veraname) {
this.veraname = veraname;
}
public String getVeraaddress() {
return veraaddress;
}
public void setVeraaddress(String veraaddress) {
this.veraaddress = veraaddress;
}
} }

View File

@@ -5,6 +5,8 @@ public class Scene {
private String name; private String name;
private String id; private String id;
private String room; private String room;
private String veraname;
private String veraaddress;
public String getActive() { public String getActive() {
return active; return active;
} }
@@ -29,5 +31,17 @@ public class Scene {
public void setRoom(String room) { public void setRoom(String room) {
this.room = room; this.room = room;
} }
public String getVeraname() {
return veraname;
}
public void setVeraname(String veraname) {
this.veraname = veraname;
}
public String getVeraaddress() {
return veraaddress;
}
public void setVeraaddress(String veraaddress) {
this.veraaddress = veraaddress;
}
} }

View File

@@ -0,0 +1,58 @@
package com.bwssystems.vera;
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.BridgeSettings;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene;
public class VeraHome {
private static final Logger log = LoggerFactory.getLogger(VeraHome.class);
private Map<String, VeraInfo> veras;
public VeraHome(BridgeSettings bridgeSettings) {
veras = new HashMap<String, VeraInfo>();
if(!bridgeSettings.isValidVera())
return;
Iterator<NamedIP> theList = bridgeSettings.getVeraAddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aVera = theList.next();
veras.put(aVera.getName(), new VeraInfo(aVera, bridgeSettings.isValidVera()));
}
}
public List<Device> getDevices() {
log.debug("consolidating devices for veras");
Iterator<String> keys = veras.keySet().iterator();
ArrayList<Device> deviceList = new ArrayList<Device>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Device> devices = veras.get(key).getSdata().getDevices().iterator();
while(devices.hasNext()) {
deviceList.add(devices.next());
}
}
return deviceList;
}
public List<Scene> getScenes() {
log.debug("consolidating scenes for veras");
Iterator<String> keys = veras.keySet().iterator();
ArrayList<Scene> sceneList = new ArrayList<Scene>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Scene> scenes = veras.get(key).getSdata().getScenes().iterator();
while(scenes.hasNext()) {
sceneList.add(scenes.next());
}
}
return sceneList;
}
}

View File

@@ -13,6 +13,7 @@ import org.apache.http.util.EntityUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.luupRequests.Categorie; import com.bwssystems.luupRequests.Categorie;
import com.bwssystems.luupRequests.Device; import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Room; import com.bwssystems.luupRequests.Room;
@@ -25,13 +26,13 @@ public class VeraInfo {
private static final Logger log = LoggerFactory.getLogger(VeraInfo.class); private static final Logger log = LoggerFactory.getLogger(VeraInfo.class);
private HttpClient httpClient; private HttpClient httpClient;
private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json"; private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json";
private String veraAddressString; private NamedIP veraAddress;
private Boolean validVera; private Boolean validVera;
public VeraInfo(String addressString, Boolean isValidVera) { public VeraInfo(NamedIP addressName, Boolean isValidVera) {
super(); super();
httpClient = HttpClients.createMinimal(); httpClient = HttpClients.createDefault();
veraAddressString = addressString; veraAddress = addressName;
validVera = isValidVera; validVera = isValidVera;
} }
@@ -39,7 +40,7 @@ public class VeraInfo {
if(!validVera) if(!validVera)
return new Sdata(); return new Sdata();
String theUrl = "http://" + veraAddressString + SDATA_REQUEST; String theUrl = "http://" + veraAddress.getIp() + SDATA_REQUEST;
String theData; String theData;
theData = doHttpGETRequest(theUrl); theData = doHttpGETRequest(theUrl);
@@ -71,6 +72,8 @@ public class VeraInfo {
theDevice.setCategory(categoryMap.get(theDevice.getCategory()).getName()); theDevice.setCategory(categoryMap.get(theDevice.getCategory()).getName());
else else
theDevice.setCategory("<unknown>"); theDevice.setCategory("<unknown>");
theDevice.setVeraaddress(veraAddress.getIp());
theDevice.setVeraname(veraAddress.getName());
} }
ListIterator<Scene> theSecneIter = theSdata.getScenes().listIterator(); ListIterator<Scene> theSecneIter = theSdata.getScenes().listIterator();
@@ -81,6 +84,8 @@ public class VeraInfo {
theScene.setRoom(roomMap.get(theScene.getRoom()).getName()); theScene.setRoom(roomMap.get(theScene.getRoom()).getName());
else else
theScene.setRoom("no room"); theScene.setRoom("no room");
theScene.setVeraaddress(veraAddress.getIp());
theScene.setVeraname(veraAddress.getName());
} }
} }

View File

@@ -29,6 +29,7 @@
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li> <li class="active"><a href="#">Home</a></li>
<li><a href="http://echo.amazon.com/#cards" target="_blank">My Echo</a></li> <li><a href="http://echo.amazon.com/#cards" target="_blank">My Echo</a></li>
<li><a href="https://github.com/bwssytems/ha-bridge/blob/master/README.md" target="_blank">Help</a></li>
<li class="dropdown"> <li class="dropdown">
<a id="dropdownMenu1" href="" class="dropdown-toggle" <a id="dropdownMenu1" href="" class="dropdown-toggle"
data-toggle="dropdown" role="button" aria-haspopup="true" data-toggle="dropdown" role="button" aria-haspopup="true"

View File

@@ -1,6 +1,4 @@
var app = angular.module('habridge', [ var app = angular.module('habridge', ['ngRoute']);
'ngRoute'
]);
app.config(function ($routeProvider) { app.config(function ($routeProvider) {
$routeProvider.when('/#', { $routeProvider.when('/#', {
@@ -24,6 +22,9 @@ app.config(function ($routeProvider) {
}).when('/harmonyactivities', { }).when('/harmonyactivities', {
templateUrl: 'views/harmonyactivity.html', templateUrl: 'views/harmonyactivity.html',
controller: 'AddingController' controller: 'AddingController'
}).when('/nest', {
templateUrl: 'views/nestactions.html',
controller: 'AddingController'
}).otherwise({ }).otherwise({
templateUrl: 'views/configuration.html', templateUrl: 'views/configuration.html',
controller: 'ViewingController' controller: 'ViewingController'
@@ -32,63 +33,12 @@ app.config(function ($routeProvider) {
app.run( function (bridgeService) { app.run( function (bridgeService) {
bridgeService.loadBridgeSettings(); bridgeService.loadBridgeSettings();
bridgeService.updateShowVera();
bridgeService.updateShowHarmony();
bridgeService.getHABridgeVersion(); bridgeService.getHABridgeVersion();
}); });
app.factory('BridgeSettings', function() { app.service('bridgeService', function ($http, $window) {
var BridgeSettings = {};
BridgeSettings.upnpconfigaddress = "";
BridgeSettings.serverport = "";
BridgeSettings.upnpdevicedb = "";
BridgeSettings.upnpresponseport = "";
BridgeSettings.veraaddress = "";
BridgeSettings.harmonyaddress = "";
BridgeSettings.upnpstrict = "";
BridgeSettings.traceupnp = "";
BridgeSettings.devmode = "";
BridgeSettings.setupnpconfigaddress = function(aconfigaddress){
BridgeSettings.upnpconfigaddress = aconfigaddress;
};
BridgeSettings.setserverport = function(aserverport){
BridgeSettings.serverport = aserverport;
};
BridgeSettings.setupnpdevicedb = function(aupnpdevicedb){
BridgeSettings.upnpdevicedb = aupnpdevicedb;
};
BridgeSettings.setupnpresponseport = function(aupnpresponseport){
BridgeSettings.upnpresponseport = aupnpresponseport;
};
BridgeSettings.setveraaddress = function(averaaddress){
BridgeSettings.veraaddress = averaaddress;
};
BridgeSettings.setharmonyaddress = function(aharmonyaddress){
BridgeSettings.harmonyaddress = aharmonyaddress;
};
BridgeSettings.setupnpstrict = function(aupnpstrict){
BridgeSettings.upnpstrict = aupnpstrict;
};
BridgeSettings.settraceupnp = function(atraceupnp){
BridgeSettings.traceupnp = atraceupnp;
};
BridgeSettings.setdevmode = function(adevmode){
BridgeSettings.devmode = adevmode;
};
return BridgeSettings;
});
app.service('bridgeService', function ($http, $window, BridgeSettings) {
var self = this; var self = this;
self.BridgeSettings = BridgeSettings; this.state = {base: window.location.origin + "/api/devices", upnpbase: window.location.origin + "/upnp/settings", huebase: window.location.origin + "/api", backups: [], devices: [], device: [], settings: [], error: "", showVera: false, showHarmony: false, showNest: false, habridgeversion: ""};
this.state = {base: window.location.origin + "/api/devices", upnpbase: window.location.origin + "/upnp/settings", huebase: window.location.origin + "/api", devices: [], device: [], error: "", showVera: false, showHarmony: false, habridgeversion: ""};
this.viewDevices = function () { this.viewDevices = function () {
this.state.error = ""; this.state.error = "";
@@ -124,27 +74,47 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
}; };
this.aContainsB = function (a, b) {
return a.indexOf(b) >= 0;
}
this.updateShowVera = function () {
if(this.aContainsB(self.state.settings.veraaddress.devices[0].ip, "1.1.1.1") || self.state.settings.veraaddress == "" || self.state.settings.veraaddress == null)
this.state.showVera = false;
else
this.state.showVera = true;
return;
}
this.updateShowNest = function () {
if(self.state.settings.nestconfigured == true)
this.state.showNest = true;
else
this.state.showNest = false;
return;
}
this.updateShowHarmony = function () {
if(self.state.settings.harmonyaddress.devices) {
if(this.aContainsB(self.state.settings.harmonyaddress.devices[0].ip, "1.1.1.1") || self.state.settings.harmonyaddress == "" || self.state.settings.harmonyaddress == null)
this.state.showHarmony = false;
else
this.state.showHarmony = true;
}
else
this.state.showHarmony = false;
return;
}
this.loadBridgeSettings = function () { this.loadBridgeSettings = function () {
this.state.error = ""; this.state.error = "";
return $http.get(this.state.upnpbase).then( return $http.get(this.state.upnpbase).then(
function (response) { function (response) {
self.BridgeSettings.setupnpconfigaddress(response.data.upnpconfigaddress); self.state.settings = response.data;
self.BridgeSettings.setserverport(response.data.serverport); self.updateShowVera();
self.BridgeSettings.setupnpdevicedb(response.data.upnpdevicedb); self.updateShowHarmony();
self.BridgeSettings.setupnpresponseport(response.data.upnpresponseport); self.updateShowNest();
self.BridgeSettings.setveraaddress(response.data.veraaddress);
self.BridgeSettings.setharmonyaddress(response.data.harmonyaddress);
self.BridgeSettings.settraceupnp(response.data.traceupnp);
self.BridgeSettings.setupnpstrict(response.data.upnpstrict);
self.BridgeSettings.setdevmode(response.data.devmode);
if(self.BridgeSettings.veraaddress == "1.1.1.1" || self.BridgeSettings.veraaddress == "")
self.state.showVera = false;
else
self.state.showVera = true;
if(self.BridgeSettings.harmonyaddress == "1.1.1.1" || self.BridgeSettings.harmonyaddress == "")
self.state.showHarmony = false;
else
self.state.showHarmony = true;
}, },
function (error) { function (error) {
if (error.data) { if (error.data) {
@@ -157,27 +127,44 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
}; };
this.updateShowVera = function () { this.viewBackups = function () {
if(self.BridgeSettings.veraaddress == "1.1.1.1" || self.BridgeSettings.veraaddress == "") this.state.error = "";
this.state.showVera = false; return $http.get(this.state.base + "/backup/available").then(
else function (response) {
this.state.showVera = true; self.state.backups = response.data;
return; },
function (error) {
if (error.data) {
$window.alert("Get Backups Error: " + error.data.message);
} else {
$window.alert("Get Backups Error: unknown");
} }
}
);
};
this.updateShowHarmony = function () { this.viewNestItems = function () {
if(self.BridgeSettings.harmonyaddress == "1.1.1.1" || self.BridgeSettings.harmonyaddress == "") this.state.error = "";
this.state.showHarmony = false; if(!this.state.showNest)
else
this.state.showHarmony = true;
return; return;
return $http.get(this.state.base + "/nest/items").then(
function (response) {
self.state.nestitems = response.data;
},
function (error) {
if (error.data) {
$window.alert("Get Nest Items Error: " + error.data.message);
} else {
$window.alert("Get Nest Items Error: unknown");
} }
}
);
};
this.viewVeraDevices = function () { this.viewVeraDevices = function () {
this.state.error = ""; this.state.error = "";
if(!this.state.showVera) if(!this.state.showVera)
return; return;
this.state.error = "";
return $http.get(this.state.base + "/vera/devices").then( return $http.get(this.state.base + "/vera/devices").then(
function (response) { function (response) {
self.state.veradevices = response.data; self.state.veradevices = response.data;
@@ -246,9 +233,17 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
}; };
this.findDeviceByMapId = function(id) { this.findDeviceByMapId = function(id, target, type) {
for(var i = 0; i < this.state.devices.length; i++) { for(var i = 0; i < this.state.devices.length; i++) {
if(this.state.devices[i].mapId == id) if(this.state.devices[i].mapId == id && this.state.devices[i].mapType == type && this.state.devices[i].targetDevice == target)
return true;
}
return false;
};
this.findNestItemByMapId = function(id, type) {
for(var i = 0; i < this.state.devices.length; i++) {
if(this.state.devices[i].mapId == id && this.aContainsB(this.state.devices[i].mapType, type))
return true; return true;
} }
return false; return false;
@@ -258,6 +253,8 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
this.state.error = ""; this.state.error = "";
if(device.httpVerb != null && device.httpVerb != "") if(device.httpVerb != null && device.httpVerb != "")
device.deviceType = "custom"; device.deviceType = "custom";
if(device.targetDevice == null || device.targetDevice == "")
device.targetDevice = "Encapsulated";
if (device.id) { if (device.id) {
var putUrl = this.state.base + "/" + device.id; var putUrl = this.state.base + "/" + device.id;
return $http.put(putUrl, { return $http.put(putUrl, {
@@ -266,6 +263,7 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
mapId: device.mapId, mapId: device.mapId,
mapType: device.mapType, mapType: device.mapType,
deviceType: device.deviceType, deviceType: device.deviceType,
targetDevice: device.targetDevice,
onUrl: device.onUrl, onUrl: device.onUrl,
offUrl: device.offUrl, offUrl: device.offUrl,
httpVerb: device.httpVerb, httpVerb: device.httpVerb,
@@ -285,14 +283,13 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
} else { } else {
if(device.deviceType == null || device.deviceType == "") if(device.deviceType == null || device.deviceType == "")
device.deviceType = "switch";
if(device.httpVerb != null && device.httpVerb != "")
device.deviceType = "custom"; device.deviceType = "custom";
return $http.post(this.state.base, { return $http.post(this.state.base, {
name: device.name, name: device.name,
mapId: device.mapId, mapId: device.mapId,
mapType: device.mapType, mapType: device.mapType,
deviceType: device.deviceType, deviceType: device.deviceType,
targetDevice: device.targetDevice,
onUrl: device.onUrl, onUrl: device.onUrl,
offUrl: device.offUrl, offUrl: device.offUrl,
httpVerb: device.httpVerb, httpVerb: device.httpVerb,
@@ -313,6 +310,58 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
} }
}; };
this.backupDeviceDb = function (afilename) {
this.state.error = "";
return $http.put(this.state.base + "/backup/create", {
filename: afilename
}).then(
function (response) {
self.viewBackups();
},
function (error) {
if (error.data) {
self.state.error = error.data.message;
}
$window.alert("Backup Device Db Error: unknown");
}
);
};
this.restoreBackup = function (afilename) {
this.state.error = "";
return $http.post(this.state.base + "/backup/restore", {
filename: afilename
}).then(
function (response) {
self.viewBackups();
self.viewDevices();
},
function (error) {
if (error.data) {
self.state.error = error.data.message;
}
$window.alert("Backup Db Restore Error: unknown");
}
);
};
this.deleteBackup = function (afilename) {
this.state.error = "";
return $http.post(this.state.base + "/backup/delete", {
filename: afilename
}).then(
function (response) {
self.viewBackups();
},
function (error) {
if (error.data) {
self.state.error = error.data.message;
}
$window.alert("Backup Db Frlryr Error: unknown");
}
);
};
this.deleteDevice = function (id) { this.deleteDevice = function (id) {
this.state.error = ""; this.state.error = "";
return $http.delete(this.state.base + "/" + id).then( return $http.delete(this.state.base + "/" + id).then(
@@ -328,9 +377,9 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
); );
}; };
this.deleteDeviceByMapId = function (id) { this.deleteDeviceByMapId = function (id, type) {
for(var i = 0; i < this.state.devices.length; i++) { for(var i = 0; i < this.state.devices.length; i++) {
if(this.state.devices[i].mapId == id) if(this.state.devices[i].mapId == id && this.aContainsB(this.state.devices[i].mapType, type))
return self.deleteDevice(this.state.devices[i].id); return self.deleteDevice(this.state.devices[i].id);
} }
}; };
@@ -365,15 +414,16 @@ app.service('bridgeService', function ($http, $window, BridgeSettings) {
}; };
}); });
app.controller('ViewingController', function ($scope, $location, $http, $window, bridgeService, BridgeSettings) { app.controller('ViewingController', function ($scope, $location, $http, $window, bridgeService) {
$scope.BridgeSettings = bridgeService.BridgeSettings;
bridgeService.viewDevices(); bridgeService.viewDevices();
bridgeService.viewBackups();
$scope.bridge = bridgeService.state; $scope.bridge = bridgeService.state;
bridgeService.updateShowVera(); $scope.optionalbackupname = "";
bridgeService.updateShowHarmony();
$scope.visible = false; $scope.visible = false;
$scope.imgUrl = "glyphicon glyphicon-plus"; $scope.imgUrl = "glyphicon glyphicon-plus";
$scope.visibleBk = false;
$scope.imgBkUrl = "glyphicon glyphicon-plus";
$scope.predicate = ''; $scope.predicate = '';
$scope.reverse = true; $scope.reverse = true;
$scope.order = function(predicate) { $scope.order = function(predicate) {
@@ -390,10 +440,22 @@ app.controller('ViewingController', function ($scope, $location, $http, $window,
bridgeService.state.base = url; bridgeService.state.base = url;
bridgeService.viewDevices(); bridgeService.viewDevices();
}; };
$scope.goBridgeUrl = function (url) {
window.open(url, "_blank");
};
$scope.editDevice = function (device) { $scope.editDevice = function (device) {
bridgeService.editDevice(device); bridgeService.editDevice(device);
$location.path('/editdevice'); $location.path('/editdevice');
}; };
$scope.backupDeviceDb = function (optionalbackupname) {
bridgeService.backupDeviceDb(optionalbackupname);
};
$scope.restoreBackup = function (backupname) {
bridgeService.restoreBackup(backupname);
};
$scope.deleteBackup = function (backupname) {
bridgeService.deleteBackup(backupname);
};
$scope.toggle = function () { $scope.toggle = function () {
$scope.visible = !$scope.visible; $scope.visible = !$scope.visible;
if($scope.visible) if($scope.visible)
@@ -401,43 +463,57 @@ app.controller('ViewingController', function ($scope, $location, $http, $window,
else else
$scope.imgUrl = "glyphicon glyphicon-plus"; $scope.imgUrl = "glyphicon glyphicon-plus";
}; };
$scope.toggleBk = function () {
$scope.visibleBk = !$scope.visibleBk;
if($scope.visibleBk)
$scope.imgBkUrl = "glyphicon glyphicon-minus";
else
$scope.imgBkUrl = "glyphicon glyphicon-plus";
};
}); });
app.controller('AddingController', function ($scope, $location, $http, bridgeService, BridgeSettings) { app.controller('AddingController', function ($scope, $location, $http, bridgeService) {
$scope.bridge = bridgeService.state;
$scope.device = {id: "", name: "", deviceType: "switch", onUrl: "", offUrl: ""}; $scope.device = $scope.bridge.device;
$scope.vera = {base: "", port: "3480", id: ""}; $scope.device_dim_control = "";
$scope.vera.base = "http://" + BridgeSettings.veraaddress; $scope.bulk = { devices: [] };
bridgeService.device = $scope.device; $scope.vera = {base: "http://" + $scope.bridge.settings.veraaddress, port: "3480", id: ""};
bridgeService.viewVeraDevices(); bridgeService.viewVeraDevices();
bridgeService.viewVeraScenes(); bridgeService.viewVeraScenes();
bridgeService.viewHarmonyActivities(); bridgeService.viewHarmonyActivities();
bridgeService.viewHarmonyDevices(); bridgeService.viewHarmonyDevices();
$scope.bridge = bridgeService.state; bridgeService.viewNestItems();
bridgeService.updateShowVera();
bridgeService.updateShowHarmony();
$scope.device = bridgeService.state.device;
$scope.activitiesVisible = false;
$scope.imgButtonsUrl = "glyphicon glyphicon-plus"; $scope.imgButtonsUrl = "glyphicon glyphicon-plus";
$scope.buttonsVisible = false; $scope.buttonsVisible = false;
$scope.imgActivitiesUrl = "glyphicon glyphicon-plus";
$scope.devicesVisible = false;
$scope.imgDevicesUrl = "glyphicon glyphicon-plus";
$scope.scenesVisible = false;
$scope.imgScenesUrl = "glyphicon glyphicon-plus";
$scope.predicate = ''; $scope.predicate = '';
$scope.reverse = true; $scope.reverse = true;
$scope.device_dim_control = "";
$scope.order = function(predicate) { $scope.order = function(predicate) {
$scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
$scope.predicate = predicate; $scope.predicate = predicate;
}; };
$scope.clearDevice = function () {
$scope.device.id = "";
$scope.device.mapType = null;
$scope.device.mapId = null;
$scope.device.name = "";
$scope.device.onUrl = "";
$scope.device.deviceType = "custom";
$scope.device.targetDevice = null;
$scope.device.offUrl = "";
$scope.device.httpVerb = null;
$scope.device.contentType = null;
$scope.device.contentBody = null;
$scope.device.contentBodyOff = null;
};
$scope.buildUrlsUsingDevice = function (dim_control) { $scope.buildUrlsUsingDevice = function (dim_control) {
if ($scope.vera.base.indexOf("http") < 0) { if ($scope.vera.base.indexOf("http") < 0) {
$scope.vera.base = "http://" + $scope.vera.base; $scope.vera.base = "http://" + $scope.vera.base;
} }
$scope.device.deviceType = "switch"; $scope.device.deviceType = "switch";
$scope.device.targetDevice = $scope.bridge.settings.veraaddress;
$scope.device.mapType = "veraDevice"; $scope.device.mapType = "veraDevice";
$scope.device.mapId = $scope.vera.id; $scope.device.mapId = $scope.vera.id;
if(dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0) if(dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0)
@@ -460,6 +536,7 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
$scope.vera.base = "http://" + $scope.vera.base; $scope.vera.base = "http://" + $scope.vera.base;
} }
$scope.device.deviceType = "scene"; $scope.device.deviceType = "scene";
$scope.device.targetDevice = $scope.bridge.settings.veraaddress;
$scope.device.mapType = "veraScene"; $scope.device.mapType = "veraScene";
$scope.device.mapId = $scope.vera.id; $scope.device.mapId = $scope.vera.id;
$scope.device.onUrl = $scope.vera.base + ":" + $scope.vera.port $scope.device.onUrl = $scope.vera.base + ":" + $scope.vera.port
@@ -476,6 +553,7 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
} }
$scope.device.deviceType = "switch"; $scope.device.deviceType = "switch";
$scope.device.name = veradevice.name; $scope.device.name = veradevice.name;
$scope.device.targetDevice = veradevice.veraname;
$scope.device.mapType = "veraDevice"; $scope.device.mapType = "veraDevice";
$scope.device.mapId = veradevice.id; $scope.device.mapId = veradevice.id;
if(dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0) if(dim_control.indexOf("byte") >= 0 || dim_control.indexOf("percent") >= 0 || dim_control.indexOf("math") >= 0)
@@ -499,7 +577,8 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
} }
$scope.device.deviceType = "scene"; $scope.device.deviceType = "scene";
$scope.device.name = verascene.name; $scope.device.name = verascene.name;
$scope.devoce.mapType = "veraScene"; $scope.device.targetDevice = verascene.veraname;
$scope.device.mapType = "veraScene";
$scope.device.mapId = verascene.id; $scope.device.mapId = verascene.id;
$scope.device.onUrl = $scope.vera.base + ":" + $scope.vera.port $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=" + "/data_request?id=action&output_format=json&serviceId=urn:micasaverde-com:serviceId:HomeAutomationGateway1&action=RunScene&SceneNum="
@@ -511,20 +590,102 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
$scope.buildActivityUrls = function (harmonyactivity) { $scope.buildActivityUrls = function (harmonyactivity) {
$scope.device.deviceType = "activity"; $scope.device.deviceType = "activity";
$scope.device.name = harmonyactivity.label; $scope.device.targetDevice = harmonyactivity.hub;
$scope.device.name = harmonyactivity.activity.label;
$scope.device.mapType = "harmonyActivity"; $scope.device.mapType = "harmonyActivity";
$scope.device.mapId = harmonyactivity.id; $scope.device.mapId = harmonyactivity.activity.id;
$scope.device.onUrl = "{\"name\":\"" + harmonyactivity.id + "\"}"; $scope.device.onUrl = "{\"name\":\"" + harmonyactivity.activity.id + "\"}";
$scope.device.offUrl = "{\"name\":\"-1\"}"; $scope.device.offUrl = "{\"name\":\"-1\"}";
}; };
$scope.buildButtonUrls = function (harmonydevice, onbutton, offbutton) { $scope.buildButtonUrls = function (harmonydevice, onbutton, offbutton) {
var currentOn = $scope.device.onUrl;
var currentOff = $scope.device.offUrl;
var actionOn = angular.fromJson(onbutton);
var actionOff = angular.fromJson(offbutton);
if( $scope.device.mapType == "harmonyButton") {
$scope.device.onUrl = currentOn.substr(0, currentOn.indexOf("]")) + ",{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOn.command + "\"}]";
$scope.device.offUrl = currentOff.substr(0, currentOff.indexOf("]")) + ",{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOff.command + "\"}]";
}
else if ($scope.device.mapType == null || $scope.device.mapType == "") {
$scope.device.deviceType = "button"; $scope.device.deviceType = "button";
$scope.device.name = harmonydevice.label; $scope.device.targetDevice = harmonydevice.hub;
$scope.device.name = harmonydevice.device.label;
$scope.device.mapType = "harmonyButton"; $scope.device.mapType = "harmonyButton";
$scope.device.mapId = harmonydevice.id + "-" + onbutton + "-" + offbutton; $scope.device.mapId = harmonydevice.device.id + "-" + actionOn.command + "-" + actionOff.command;
$scope.device.onUrl = "{\"device\":\"" + harmonydevice.id + "\",\"button\":\"" + onbutton + "\"}"; $scope.device.onUrl = "[{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOn.command + "\"}]";
$scope.device.offUrl = "{\"device\":\"" + harmonydevice.id + "\",\"button\":\"" + offbutton + "\"}"; $scope.device.offUrl = "[{\"device\":\"" + harmonydevice.device.id + "\",\"button\":\"" + actionOff.command + "\"}]";
}
};
$scope.buildNestHomeUrls = function (nestitem) {
$scope.device.deviceType = "home";
$scope.device.name = nestitem.name;
$scope.device.targetDevice = nestitem.name;
$scope.device.mapType = "nestHomeAway";
$scope.device.mapId = nestitem.id;
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"away\":false,\"control\":\"status\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"away\":true,\"control\":\"status\"}";
};
$scope.buildNestTempUrls = function (nestitem) {
$scope.device.deviceType = "thermo";
$scope.device.name = nestitem.name.substr(0, nestitem.name.indexOf("(")) + " Temperature";
$scope.device.targetDevice = nestitem.location;
$scope.device.mapType = "nestThermoSet";
$scope.device.mapId = nestitem.id + "-SetTemp";
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"temp\",\"temp\":\"${intensity.percent}\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"temp\",\"temp\":\"${intensity.percent}\"}";
};
$scope.buildNestHeatUrls = function (nestitem) {
$scope.device.deviceType = "thermo";
$scope.device.name = nestitem.name.substr(0, nestitem.name.indexOf("(")) + " Heat";
$scope.device.targetDevice = nestitem.location;
$scope.device.mapType = "nestThermoSet";
$scope.device.mapId = nestitem.id + "-SetHeat";
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"heat\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"off\"}";
};
$scope.buildNestCoolUrls = function (nestitem) {
$scope.device.deviceType = "thermo";
$scope.device.name = nestitem.name.substr(0, nestitem.name.indexOf("(")) + " Cool";
$scope.device.targetDevice = nestitem.location;
$scope.device.mapType = "nestThermoSet";
$scope.device.mapId = nestitem.id + "-SetCool";
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"cool\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"off\"}";
};
$scope.buildNestRangeUrls = function (nestitem) {
$scope.device.deviceType = "thermo";
$scope.device.name = nestitem.name.substr(0, nestitem.name.indexOf("(")) + " Range";
$scope.device.targetDevice = nestitem.location;
$scope.device.mapType = "nestThermoSet";
$scope.device.mapId = nestitem.id + "-SetRange";
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"range\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"off\"}";
};
$scope.buildNestOffUrls = function (nestitem) {
$scope.device.deviceType = "thermo";
$scope.device.name = nestitem.name.substr(0, nestitem.name.indexOf("(")) + " Thermostat";
$scope.device.targetDevice = nestitem.location;
$scope.device.mapType = "nestThermoSet";
$scope.device.mapId = nestitem.id + "-TurnOff";
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"range\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"off\"}";
};
$scope.buildNestFanUrls = function (nestitem) {
$scope.device.deviceType = "thermo";
$scope.device.name = nestitem.name.substr(0, nestitem.name.indexOf("(")) + " Fan";
$scope.device.targetDevice = nestitem.location;
$scope.device.mapType = "nestThermoSet";
$scope.device.mapId = nestitem.id + "-SetFan";
$scope.device.onUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"fan-on\"}";
$scope.device.offUrl = "{\"name\":\"" + nestitem.id + "\",\"control\":\"fan-auto\"}";
}; };
$scope.testUrl = function (device, type) { $scope.testUrl = function (device, type) {
@@ -532,32 +693,42 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
}; };
$scope.addDevice = function () { $scope.addDevice = function () {
if($scope.device.name == "" && $scope.device.onUrl == "")
return;
bridgeService.addDevice($scope.device).then( bridgeService.addDevice($scope.device).then(
function () { function () {
$scope.device.id = ""; $scope.clearDevice();
$scope.device.mapType = null;
$scope.device.mapId = null;
$scope.device.name = "";
$scope.device.onUrl = "";
$scope.device.deviceType = "switch";
$scope.device.offUrl = "";
$scope.device.httpVerb = null;
$scope.device.contentType = null;
$scope.device.contentBody = null;
$scope.device.contentBodyOff = null;
$location.path('/#');
}, },
function (error) { function (error) {
} }
); );
};
$scope.bulkAddDevices = function(dim_control) {
for(var i = 0; i < $scope.bulk.devices.length; i++) {
for(var x = 0; x < bridgeService.state.veradevices.length; x++) {
if(bridgeService.state.veradevices[x].id == $scope.bulk.devices[i]) {
$scope.buildDeviceUrls(bridgeService.state.veradevices[x],dim_control);
$scope.addDevice();
}
}
}
$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);
} }
$scope.toggleActivities = function () { // is newly selected
$scope.activitiesVisible = !$scope.activitiesVisible; else {
if($scope.activitiesVisible) $scope.bulk.devices.push(deviceId);
$scope.imgActivitiesUrl = "glyphicon glyphicon-minus"; }
else
$scope.imgActivitiesUrl = "glyphicon glyphicon-plus";
}; };
$scope.toggleButtons = function () { $scope.toggleButtons = function () {
@@ -568,35 +739,19 @@ app.controller('AddingController', function ($scope, $location, $http, bridgeSer
$scope.imgButtonsUrl = "glyphicon glyphicon-plus"; $scope.imgButtonsUrl = "glyphicon glyphicon-plus";
}; };
$scope.toggleDevices = function () { $scope.deleteDeviceByMapId = function (id, mapType) {
$scope.devicesVisible = !$scope.devicesVisible; bridgeService.deleteDeviceByMapId(id, mapType);
if($scope.devicesVisible)
$scope.imgDevicesUrl = "glyphicon glyphicon-minus";
else
$scope.imgDevicesUrl = "glyphicon glyphicon-plus";
};
$scope.toggleScenes = function () {
$scope.scenesVisible = !$scope.scenesVisible;
if($scope.scenesVisible)
$scope.imgScenesUrl = "glyphicon glyphicon-minus";
else
$scope.imgScenesUrl = "glyphicon glyphicon-plus";
};
$scope.deleteDeviceByMapId = function (id) {
bridgeService.deleteDeviceByMapId(id);
}; };
}); });
app.filter('availableId', function(bridgeService) { app.filter('availableHarmonyActivityId', function(bridgeService) {
return function(input) { return function(input) {
var out = []; var out = [];
if(input == null) if(input == null)
return out; return out;
for (var i = 0; i < input.length; i++) { for (var i = 0; i < input.length; i++) {
if(!bridgeService.findDeviceByMapId(input[i].id)){ if(!bridgeService.findDeviceByMapId(input[i].activity.id, input[i].hub, "harmonyActivity")){
out.push(input[i]); out.push(input[i]);
} }
} }
@@ -604,13 +759,13 @@ app.filter('availableId', function(bridgeService) {
} }
}); });
app.filter('unavailableId', function(bridgeService) { app.filter('unavailableHarmonyActivityId', function(bridgeService) {
return function(input) { return function(input) {
var out = []; var out = [];
if(input == null) if(input == null)
return out; return out;
for (var i = 0; i < input.length; i++) { for (var i = 0; i < input.length; i++) {
if(bridgeService.findDeviceByMapId(input[i].id)){ if(bridgeService.findDeviceByMapId(input[i].activity.id, input[i].hub, "harmonyActivity")){
out.push(input[i]); out.push(input[i]);
} }
} }
@@ -618,6 +773,90 @@ app.filter('unavailableId', function(bridgeService) {
} }
}); });
app.filter('availableVeraDeviceId', function(bridgeService) {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(!bridgeService.findDeviceByMapId(input[i].id, input[i].veraname, "veraDevice")){
out.push(input[i]);
}
}
return out;
}
});
app.filter('unavailableVeraDeviceId', function(bridgeService) {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(bridgeService.findDeviceByMapId(input[i].id, input[i].veraname, "veraDevice")){
out.push(input[i]);
}
}
return out;
}
});
app.filter('availableVeraSceneId', function(bridgeService) {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(!bridgeService.findDeviceByMapId(input[i].id, input[i].veraname, "veraScene")){
out.push(input[i]);
}
}
return out;
}
});
app.filter('unavailableVeraSceneId', function(bridgeService) {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(bridgeService.findDeviceByMapId(input[i].id,input[i].veraname, "veraScene")){
out.push(input[i]);
}
}
return out;
}
});
app.filter('availableNestItemId', function(bridgeService) {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(!bridgeService.findNestItemByMapId(input[i].id, "nestHomeAway")){
out.push(input[i]);
}
}
return out;
}
});
app.filter('unavailableNestItemId', function(bridgeService) {
return function(input) {
var out = [];
if(input == null)
return out;
for (var i = 0; i < input.length; i++) {
if(input[i].mapType != null && bridgeService.aContainsB(input[i].mapType, "nest")){
out.push(input[i]);
}
}
return out;
}
});
app.filter('configuredButtons', function() { app.filter('configuredButtons', function() {
return function(input) { return function(input) {
var out = []; var out = [];

View File

@@ -4,6 +4,7 @@
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li> <li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -23,11 +24,12 @@
</div> </div>
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Current devices</h2> <h2 class="panel-title">Current devices ({{bridge.devices.length}}) </h2>
</div> </div>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('id')">ID</a> <a href="" ng-click="order('id')">ID</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span></th> <span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span></th>
@@ -37,13 +39,18 @@
<th> <th>
<a href="" ng-click="order('deviceType')">Type</a> <a href="" ng-click="order('deviceType')">Type</a>
<span class="sortorder" ng-show="predicate === 'deviceType'" ng-class="{reverse:reverse}"></span></th> <span class="sortorder" ng-show="predicate === 'deviceType'" ng-class="{reverse:reverse}"></span></th>
<th>
<a href="" ng-click="order('targetDevice')">Target</a>
<span class="sortorder" ng-show="predicate === 'targetDevice'" ng-class="{reverse:reverse}"></span></th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="device in bridge.devices | orderBy:predicate:reverse"> <tr ng-repeat="device in bridge.devices | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.id}}</td> <td>{{device.id}}</td>
<td>{{device.name}}</td> <td>{{device.name}}</td>
<td>{{device.deviceType}}</td> <td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td> <td>
<button class="btn btn-info" type="submit" <button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'on')">Test ON</button> ng-click="testUrl(device, 'on')">Test ON</button>
@@ -60,13 +67,13 @@
<div class="panel panel-default bridgeServer"> <div class="panel panel-default bridgeServer">
<div class="panel-heading"> <div class="panel-heading">
<h1 class="panel-title">Bridge settings <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1> <h1 class="panel-title">Bridge Settings <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1>
</div> </div>
<div ng-if="visible" class="animate-if" class="panel-body"> <div ng-if="visible" class="animate-if" class="panel-body">
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-xs-12 col-sm-3 control-label" for="bridge-base">Bridge <label class="col-xs-12 col-sm-2 control-label" for="bridge-base">Bridge
server</label> server</label>
<div class="col-xs-8 col-sm-7"> <div class="col-xs-8 col-sm-7">
@@ -76,7 +83,7 @@
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary" <button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="setBridgeUrl(bridge.base)">Load</button> ng-click="setBridgeUrl(bridge.base)">Load</button>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary" <button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="testUrl(bridge.base)">Go</button> ng-click="goBridgeUrl(bridge.base)">Go</button>
</div> </div>
</form> </form>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
@@ -88,40 +95,81 @@
</thead> </thead>
<tr> <tr>
<td>upnp.config.address</td> <td>upnp.config.address</td>
<td>{{BridgeSettings.upnpconfigaddress}}</td> <td>{{bridge.settings.upnpconfigaddress}}</td>
</tr> </tr>
<tr> <tr>
<td>server.port</td> <td>server.port</td>
<td>{{BridgeSettings.serverport}}</td> <td>{{bridge.settings.serverport}}</td>
</tr> </tr>
<tr> <tr>
<td>upnp.devices.db</td> <td>upnp.devices.db</td>
<td>{{BridgeSettings.upnpdevicedb}}</td> <td>{{bridge.settings.upnpdevicedb}}</td>
</tr> </tr>
<tr> <tr>
<td>upnp.response.port</td> <td>upnp.response.port</td>
<td>{{BridgeSettings.upnpresponseport}}</td> <td>{{bridge.settings.upnpresponseport}}</td>
</tr> </tr>
<tr> <tr>
<td>vera.address</td> <td>vera.address</td>
<td>{{BridgeSettings.veraaddress}}</td> <td>{{bridge.settings.veraaddress}}</td>
</tr> </tr>
<tr> <tr>
<td>harmony.address</td> <td>harmony.address</td>
<td>{{BridgeSettings.harmonyaddress}}</td> <td>{{bridge.settings.harmonyaddress}}</td>
</tr> </tr>
<tr> <tr>
<td>upnp.strict</td> <td>upnp.strict</td>
<td>{{BridgeSettings.upnpstrict}}</td> <td>{{bridge.settings.upnpstrict}}</td>
</tr> </tr>
<tr> <tr>
<td>trace.upnp</td> <td>trace.upnp</td>
<td>{{BridgeSettings.traceupnp}}</td> <td>{{bridge.settings.traceupnp}}</td>
</tr> </tr>
<tr> <tr>
<td>dev.mode</td> <td>dev.mode</td>
<td>{{BridgeSettings.devmode}}</td> <td>{{bridge.settings.devmode}}</td>
</tr>
<tr>
<td>nest.configured</td>
<td>{{bridge.settings.nestconfigured}}</td>
</tr> </tr>
</table> </table>
</div> </div>
</div> </div>
<div class="panel panel-default backup">
<div class="panel-heading">
<h1 class="panel-title">Bridge Device DB Backup <a ng-click="toggleBk()"><span class={{imgBkUrl}} aria-hidden="true"></a></h1>
</div>
<div ng-if="visibleBk" class="animate-if" class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="backup-name">Backup File Name</label>
<div class="col-xs-8 col-sm-7">
<input id="backup-name" class="form-control" type="text"
ng-model="optionalbackupname" placeholder="Optional">
</div>
<button type="submit" class="btn btn-primary"
ng-click="backupDeviceDb(optionalbackupname)">Backup Device DB</button>
</div>
</form>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Filename</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="backup in bridge.backups">
<td>{{backup}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="restoreBackup(backup)">Restore</button>
<button class="btn btn-warning" type="submit"
ng-click="deleteBackup(backup)">Delete</button>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -4,6 +4,7 @@
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li> <li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
<li role="presentation" class="active"><a href="#/editdevice">Edit Device</a></li> <li role="presentation" class="active"><a href="#/editdevice">Edit Device</a></li>
</ul> </ul>
@@ -12,9 +13,11 @@
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Edit a device</h2> <h2 class="panel-title">Edit a device</h2>
</div> </div>
<p class="text-muted">This screen allows the modification of many fields the bridge uses. Please use care when
updating these fields as you may break the settings used by the bridge to call a specific end point device.</p>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name <label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label> </label>
@@ -23,8 +26,19 @@
<input type="text" class="form-control" id="device-name" <input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"> ng-model="device.name" placeholder="Device Name">
</div> </div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"> <button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Update Device</button> Update Bridge Device</button>
</div>
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-target">Target
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-target"
ng-model="device.targetDevice" placeholder="default">
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
@@ -38,6 +52,8 @@
<option value="veraScene">Vera Scene</option> <option value="veraScene">Vera Scene</option>
<option value="harmonyActivity">Harmony Activity</option> <option value="harmonyActivity">Harmony Activity</option>
<option value="harmonyButton">Harmony Button</option> <option value="harmonyButton">Harmony Button</option>
<option value="nestHomeAway">Nest Home Status</option>
<option value="nestThermoSet">Nest Thermostat</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -4,6 +4,7 @@
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li> <li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation" class="active"><a href="#/editor">Manual Add</a></li> <li role="presentation" class="active"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -73,9 +74,11 @@
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Add a new device</h2> <h2 class="panel-title">Add a new device</h2>
</div> </div>
<p class="text-muted">This area allows you to create any http or udp call to an endpoint. You can use the default GET or select
the http verb type below and configure a payload for either on or off methods. Currently, https is not supported.</p>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name <label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
@@ -85,8 +88,8 @@
<input type="text" class="form-control" id="device-name" <input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"> ng-model="device.name" placeholder="Device Name">
</div> </div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"> <button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Add Device</button> Add Bridge Device</button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -98,6 +101,8 @@
<textarea rows="3" class="form-control" id="device-on-url" <textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea> ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div> </div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -4,6 +4,7 @@
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li> <li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li role="presentation" class="active"><a href="#/harmonyactivities">Harmony Activities</a></li> <li role="presentation" class="active"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li> <li role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -13,12 +14,14 @@
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<p class="text-muted">You can select a Harmony Activity and generate <p class="text-muted">For any Harmony Activity, use the action buttons to generate the device addition information below automatically.
the add activity box selections automatically.</p> 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 Activities' list below will show what is already setup for your Harmony Hubs.</p>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('label')">Name</a> <a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -26,13 +29,19 @@
<th> <th>
<a href="" ng-click="order('id')">Id</a> <a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableId | orderBy:predicate:reverse"> <tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableHarmonyActivityId | orderBy:predicate:reverse">
<td>{{harmonyactivity.label}}</td> <td>{{$index+1}}</td>
<td>{{harmonyactivity.id}}</td> <td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td> <td>
<button class="btn btn-success" type="submit" <button class="btn btn-success" type="submit"
ng-click="buildActivityUrls(harmonyactivity)">Generate ng-click="buildActivityUrls(harmonyactivity)">Generate
@@ -43,13 +52,14 @@
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Already Configured Activities <a ng-click="toggleActivities()"><span class={{imgActivitiesUrl}} aria-hidden="true"></a></h2> <h2 class="panel-title">Already Configured Activities <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></h2>
</div> </div>
<ul ng-if="activitiesVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('label')">Name</a> <a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -57,15 +67,21 @@
<th> <th>
<a href="" ng-click="order('id')">Id</a> <a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableId | orderBy:predicate:reverse"> <tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableHarmonyActivityId | orderBy:predicate:reverse">
<td>{{harmonyactivity.label}}</td> <td>{{$index+1}}</td>
<td>{{harmonyactivity.id}}</td> <td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td><button class="btn btn-danger" type="submit" <td><button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.id)">Delete</button></td> ng-click="deleteDeviceByMapId(harmonyactivity.activity.id, 'harmonyActivity')">Delete</button></td>
</tr> </tr>
</table> </table>
</li> </li>
@@ -73,11 +89,11 @@
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Add a Harmony Activity</h2> <h2 class="panel-title">Add a Bridge Device for a Harmony Activity</h2>
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name <label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label> </label>
@@ -86,8 +102,8 @@
<input type="text" class="form-control" id="device-name" <input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"> ng-model="device.name" placeholder="Device Name">
</div> </div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"> <button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Add Activity</button> Add Bridge Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
@@ -98,6 +114,8 @@
<textarea rows="3" class="form-control" id="device-on-url" <textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea> ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div> </div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -4,6 +4,7 @@
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li> <li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li> <li role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li role="presentation" class="active"><a href="#/harmonydevices">Harmony Devices</a></li> <li role="presentation" class="active"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -13,12 +14,15 @@
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<p class="text-muted">You can select a Harmony Device and Button and generate <p class="text-muted">For any Harmony Device and Buttons, use the build button to generate the configuration for this bridge device. You can add button presses by
the add button box selections automatically.</p> selecting yoru devic and buttons and then selecting the Build Button. This will allow multiple button presses to be built for a given device.
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 Harmony Buttons' list below will show what is already setup for your Harmony Hubs.</p>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('label')">Name</a> <a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -26,6 +30,10 @@
<th> <th>
<a href="" ng-click="order('id')">Id</a> <a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>On Button</th> <th>On Button</th>
<th>Off Button</th> <th>Off Button</th>
@@ -33,26 +41,27 @@
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonydevice in bridge.harmonydevices | orderBy:predicate:reverse"> <tr ng-repeat="harmonydevice in bridge.harmonydevices | orderBy:predicate:reverse">
<td>{{harmonydevice.label}}</td> <td>{{$index+1}}</td>
<td>{{harmonydevice.id}}</td> <td>{{harmonydevice.device.label}}</td>
<td>{{harmonydevice.device.id}}</td>
<td>{{harmonydevice.hub}}</td>
<td> <td>
<select name="device-ctrlon" id="device-ctrlon" ng-model="devicectrlon"> <select name="device-ctrlon" id="device-ctrlon" ng-model="devicectrlon">
<optgroup ng-repeat="ctrlon in harmonydevice.controlGroup" label="{{ctrlon.name}}"> <optgroup ng-repeat="ctrlon in harmonydevice.device.controlGroup" label="{{ctrlon.name}}">
<option ng-repeat="funcon in ctrlon.function">{{funcon.name}}</option> <option ng-repeat="funcon in ctrlon.function" value="{{funcon.action}}">{{funcon.label}}</option>
</optgroup > </optgroup >
</select> </select>
</td> </td>
<td> <td>
<select name="device-ctrloff" id="device-ctrloff" ng-model="devicectrloff"> <select name="device-ctrloff" id="device-ctrloff" ng-model="devicectrloff">
<optgroup ng-repeat="ctrloff in harmonydevice.controlGroup" label="{{ctrloff.name}}"> <optgroup ng-repeat="ctrloff in harmonydevice.device.controlGroup" label="{{ctrloff.name}}">
<option ng-repeat="funcoff in ctrloff.function">{{funcoff.name}}</option> <option ng-repeat="funcoff in ctrloff.function" value="{{funcoff.action}}">{{funcoff.label}}</option>
</optgroup > </optgroup >
</select> </select>
</td> </td>
<td> <td>
<button class="btn btn-success" type="submit" <button class="btn btn-success" type="submit"
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Generate ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Build A Button</button>
Button URLs</button>
</td> </td>
</tr> </tr>
</table> </table>
@@ -66,6 +75,7 @@
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('name')">Name</a> <a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -73,6 +83,10 @@
<th> <th>
<a href="" ng-click="order('id')">Device Id</a> <a href="" ng-click="order('id')">Device Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('targetDevice')">Hub</a>
<span class="sortorder" ng-show="predicate === 'targetDevice'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th> <th>
<a href="" ng-click="order('mapId')">Harmony Device-Button On-Button Off</a> <a href="" ng-click="order('mapId')">Harmony Device-Button On-Button Off</a>
@@ -82,12 +96,14 @@
</tr> </tr>
</thead> </thead>
<tr ng-repeat="device in bridge.devices | configuredButtons | orderBy:predicate:reverse"> <tr ng-repeat="device in bridge.devices | configuredButtons | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td> <td>{{device.name}}</td>
<td>{{device.id}}</td> <td>{{device.id}}</td>
<td>{{device.targetDevice}}</td>
<td>{{device.mapId}}</td> <td>{{device.mapId}}</td>
<td> <td>
<button class="btn btn-danger" type="submit" <button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId)">Delete</button> ng-click="deleteDeviceByMapId(device.mapId, device.mapType)">Delete</button>
</td> </td>
</tr> </tr>
</table> </table>
@@ -96,11 +112,11 @@
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Add a Harmony Button</h2> <h2 class="panel-title">Add a Bridge Device for Harmony Buttons</h2>
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name <label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label> </label>
@@ -109,8 +125,8 @@
<input type="text" class="form-control" id="device-name" <input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"> ng-model="device.name" placeholder="Device Name">
</div> </div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"> <button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Add Button</button> Add Bridge Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
@@ -121,6 +137,8 @@
<textarea rows="3" class="form-control" id="device-on-url" <textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea> ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div> </div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -0,0 +1,156 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#/veradevices">Vera Devices</a></li>
<li ng-if="bridge.showVera" role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li role="presentation" class="active"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading">
<h2 class="panel-title">Nest Items List</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">For any Nest Item, 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 Nest Items' list below will show what is already setup for your Nest.</p>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('type')">Type</a>
<span class="sortorder" ng-show="predicate === 'type'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('location')">Location</a>
<span class="sortorder" ng-show="predicate === 'location'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="nestitem in bridge.nestitems | availableNestItemId | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{nestitem.name}}</td>
<td>{{nestitem.type}}</td>
<td>{{nestitem.location}}</td>
<td>
<ul class="list-group">
<li ng-if="nestitem.type ==='Home' " class="list-group-item">
<button class="btn btn-success" type="submit"
ng-click="buildNestHomeUrls(nestitem)">Home/Away</button>
</li>
<li ng-if="nestitem.type ==='Thermostat' " class="list-group-item">
<p>
<button class="btn btn-success" type="submit"
ng-click="buildNestTempUrls(nestitem)">Temp</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestHeatUrls(nestitem)">Heat</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestCoolUrls(nestitem)">Cool</button>
</p>
<p>
<button class="btn btn-success" type="submit"
ng-click="buildNestRangeUrls(nestitem)">Range</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestOffUrls(nestitem)">Off</button>
<button class="btn btn-success" type="submit"
ng-click="buildNestFanUrls(nestitem)">Fan</button>
</p>
</li>
</ul>
</td>
</tr>
</table>
</li>
</ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Nest Items <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></h2>
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('mapId')">mapId</a>
<span class="sortorder" ng-show="predicate === 'mapId'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | unavailableNestItemId | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{device.name}}</td>
<td>{{device.id}}</td>
<td>{{device.mapId}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(device.mapId, 'nest')">Delete</button></td>
</tr>
</table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Nest Item</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label>
<div class="col-xs-8 col-sm-7">
<input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name">
</div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Add Bridge Device</button>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-off-url">Off URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-off-url"
ng-model="device.offUrl" placeholder="URL to turn device off"></textarea>
</div>
</div>
</div>
</form>
</li>
</ul>
</div>

View File

@@ -4,17 +4,20 @@
<li role="presentation"><a href="#/verascenes">Vera Scenes</a></li> <li role="presentation"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Vera Device List</h2> <h2 class="panel-title">Vera Device List ({{bridge.veradevices.length}})</h2>
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<p class="text-muted">You can select a Vera device and generate <p class="text-muted">For any Vera Device, use the action buttons to generate the device addition information below automatically.
the add device box selections automatically.</p><p>Also, use this select menu for which type of dim 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 Vera Devices' list below will show what is already setup for your Vera.</p>
<p>Also, use this select menu for which type of dim
control you would like to be generated: control you would like to be generated:
<select name="device-dim-control" id="device-dim-control" ng-model="device_dim_control"> <select name="device-dim-control" id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option> <option value="">none</option>
@@ -23,9 +26,13 @@
<option value="${intensity.math(X*1)}">Custom Math</option> <option value="${intensity.math(X*1)}">Custom Math</option>
</select> </select>
</p> </p>
<p>Use the check boxes by the names to use the bulk addition feature. Select your items and dim control type if wanted, 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 Vera.
</p>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('name')">Name</a> <a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -41,15 +48,21 @@
<th> <th>
<a href="" ng-click="order('room')">Room</a> <a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('vera')">Vera</a>
<span class="sortorder" ng-show="predicate === 'vera'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="veradevice in bridge.veradevices | availableId | orderBy:predicate:reverse"> <tr ng-repeat="veradevice in bridge.veradevices | availableVeraDeviceId | orderBy:predicate:reverse">
<td>{{veradevice.name}}</td> <td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]" value="{{veradevice.id}}" ng-checked="bulk.devices.indexOf(veradevice.id) > -1" ng-click="toggleSelection(veradevice.id)"> {{veradevice.name}}</td>
<td>{{veradevice.id}}</td> <td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td> <td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td> <td>{{veradevice.room}}</td>
<td>{{veradevice.veraname}}</td>
<td> <td>
<button class="btn btn-success" type="submit" <button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(veradevice, device_dim_control)">Generate ng-click="buildDeviceUrls(veradevice, device_dim_control)">Generate
@@ -57,16 +70,21 @@
</td> </td>
</tr> </tr>
</table> </table>
<p>
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add ({{bulk.devices.length}})</button>
</p>
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Already Configured Vera Devices <a ng-click="toggleDevices()"><span class={{imgDevicesUrl}} aria-hidden="true"></a></h2> <h2 class="panel-title">Already Configured Vera Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></a></h2>
</div> </div>
<ul ng-if="devicesVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('name')">Name</a> <a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -82,18 +100,24 @@
<th> <th>
<a href="" ng-click="order('room')">Room</a> <a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('vera')">Vera</a>
<span class="sortorder" ng-show="predicate === 'vera'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="veradevice in bridge.veradevices | unavailableId | orderBy:predicate:reverse"> <tr ng-repeat="veradevice in bridge.veradevices | unavailableVeraDeviceId | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{veradevice.name}}</td> <td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td> <td>{{veradevice.id}}</td>
<td>{{veradevice.category}}</td> <td>{{veradevice.category}}</td>
<td>{{veradevice.room}}</td> <td>{{veradevice.room}}</td>
<td>{{veradevice.veraname}}</td>
<td> <td>
<button class="btn btn-danger" type="submit" <button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.id)">Delete</button> ng-click="deleteDeviceByMapId(veradevice.id, 'veraDevice')">Delete</button>
</td> </td>
</tr> </tr>
</table> </table>
@@ -102,11 +126,11 @@
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Add a Vera device</h2> <h2 class="panel-title">Add Bridge Device for a Vera Device</h2>
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name <label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label> </label>
@@ -115,8 +139,8 @@
<input type="text" class="form-control" id="device-name" <input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"> ng-model="device.name" placeholder="Device Name">
</div> </div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"> <button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Add Device</button> Add Bridge Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
@@ -127,6 +151,8 @@
<textarea rows="3" class="form-control" id="device-on-url" <textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea> ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div> </div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">

View File

@@ -4,6 +4,7 @@
<li role="presentation" class="active"><a href="#/verascenes">Vera Scenes</a></li> <li role="presentation" class="active"><a href="#/verascenes">Vera Scenes</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonyactivities">Harmony Activities</a></li>
<li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li> <li ng-if="bridge.showHarmony" role="presentation"><a href="#/harmonydevices">Harmony Devices</a></li>
<li ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -13,12 +14,14 @@
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<p class="text-muted">You can select a Vera scene and generate <p class="text-muted">For any Vera Scene, use the action buttons to generate the device addition information below automatically.
the add scene box selections automatically.</p> 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 Vera Scenes' list below will show what is already setup for your Vera.</p>
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('name')">Name</a> <a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -30,14 +33,20 @@
<th> <th>
<a href="" ng-click="order('room')">Room</a> <a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('vera')">Vera</a>
<span class="sortorder" ng-show="predicate === 'vera'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="verascene in bridge.verascenes | availableId | orderBy:predicate:reverse"> <tr ng-repeat="verascene in bridge.verascenes | availableVeraSceneId | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{verascene.name}}</td> <td>{{verascene.name}}</td>
<td>{{verascene.id}}</td> <td>{{verascene.id}}</td>
<td>{{verascene.room}}</td> <td>{{verascene.room}}</td>
<td>{{verascene.veraname}}</td>
<td> <td>
<button class="btn btn-success" type="submit" <button class="btn btn-success" type="submit"
ng-click="buildSceneUrls(verascene)">Generate ng-click="buildSceneUrls(verascene)">Generate
@@ -48,13 +57,14 @@
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Already Configured Vera Scenes <a ng-click="toggleScenes()"><span class={{imgScenesUrl}} aria-hidden="true"></a></h2> <h2 class="panel-title">Already Configured Vera Scenes <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></h2>
</div> </div>
<ul ng-if="scenesVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<table class="table table-bordered table-striped table-hover"> <table class="table table-bordered table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th>Row</th>
<th> <th>
<a href="" ng-click="order('name')">Name</a> <a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
@@ -66,17 +76,23 @@
<th> <th>
<a href="" ng-click="order('room')">Room</a> <a href="" ng-click="order('room')">Room</a>
<span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span> <span class="sortorder" ng-show="predicate === 'room'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('vera')">Vera</a>
<span class="sortorder" ng-show="predicate === 'vera'" ng-class="{reverse:reverse}"></span>
</th> </th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="verascene in bridge.verascenes | unavailableId | orderBy:predicate:reverse"> <tr ng-repeat="verascene in bridge.verascenes | unavailableVeraSceneId | orderBy:predicate:reverse">
<td>{{$index+1}}</td>
<td>{{verascene.name}}</td> <td>{{verascene.name}}</td>
<td>{{verascene.id}}</td> <td>{{verascene.id}}</td>
<td>{{verascene.room}}</td> <td>{{verascene.room}}</td>
<td>{{verascene.veraname}}</td>
<td> <td>
<button class="btn btn-danger" type="submit" <button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(harmonyactivity.id)">Delete</button> ng-click="deleteDeviceByMapId(verascene.id, 'veraScene')">Delete</button>
</td> </td>
</tr> </tr>
</table> </table>
@@ -85,11 +101,11 @@
</div> </div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error"> <div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Add a Vera scene</h2> <h2 class="panel-title">Add a Bridge Device for a Vera scene</h2>
</div> </div>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal" ng-submit="addDevice()"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="device-name">Name <label class="col-xs-12 col-sm-2 control-label" for="device-name">Name
</label> </label>
@@ -98,8 +114,8 @@
<input type="text" class="form-control" id="device-name" <input type="text" class="form-control" id="device-name"
ng-model="device.name" placeholder="Device Name"> ng-model="device.name" placeholder="Device Name">
</div> </div>
<button type="submit" class="col-xs-4 col-sm-2 btn btn-primary"> <button type="submit" class="col-xs-4 col-sm-2 btn btn-primary" ng-click="addDevice()">
Add Scene</button> Add Bridge Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
@@ -110,6 +126,8 @@
<textarea rows="3" class="form-control" id="device-on-url" <textarea rows="3" class="form-control" id="device-on-url"
ng-model="device.onUrl" placeholder="URL to turn device on"></textarea> ng-model="device.onUrl" placeholder="URL to turn device on"></textarea>
</div> </div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">