Compare commits

...

39 Commits

Author SHA1 Message Date
Admin
7e0fd6c21b Updated the register with hue function to not send a user name when
linking as this has been deprecated by Philips.

Fixes #99
2016-04-29 16:12:52 -05:00
BWS Systems
3bf52f5da0 Updated Readme
Added comment for Hue proxy and using the link button.
2016-04-29 12:54:08 -05:00
BWS Systems
bd856d8f9e Fixed spelling 2016-04-29 12:44:30 -05:00
Admin
73b2be752e Issue with http/https handling trying to token out line. Updated Readme
Fixes #96
Fixes #98
2016-04-29 12:27:15 -05:00
Admin
dda7a7a34a Merge remote-tracking branch 'origin/add-color-args' 2016-04-29 11:26:06 -05:00
Admin
f238e05533 Fixed bug where device state object was dropped from hue api device
response object. This caused devices to appear offline and invalid
response interpretation by the echo.

Fixes #93
2016-04-28 12:12:49 -05:00
Admin
aaaebd0c05 Merged add-color-args branch to master.
Fixes #72
Fixes #81
Fixes #82
Fixes #85
Fixes #87
Fixes #88
Fixes #89
Fixes #90
2016-04-27 14:42:52 -05:00
Admin
9a1924422e Updated Readme 2016-04-27 13:07:49 -05:00
Admin
e446c618ce MAde the generate buttons consistent in the UI. Updated the Readme. 2016-04-27 12:40:54 -05:00
Admin
60d35acff9 update readme for formatting 2016-04-27 09:11:11 -05:00
Admin
c9adab53a9 Updated selections in device add or editor and updated loggin for upnp
listener and updated the readme
2016-04-27 09:08:41 -05:00
Admin
72b6b2027b Finished updating UPNP handling with ports and errors and testing for
new features.

Updateding README
2016-04-26 16:49:26 -05:00
Admin
60239bad82 Finished testing and refactoring for udp/tcp and http 2016-04-26 10:12:36 -05:00
Admin
aecd589308 Finished implementation of headers and tested. Added calls for TCP
request with UDP and also added the value replacement for dimming, this
needs to be tested.
2016-04-25 16:48:59 -05:00
Admin
ee45cee8e3 Fixed error handling in for loops 2016-04-22 14:00:47 -05:00
Admin
21e5dfb338 added multiple command execution. Added commandline Exec. NEeds testing 2016-04-22 13:59:12 -05:00
Admin
b73a4cd666 Finished testing and updating HUE passthru handling 2016-04-22 10:58:54 -05:00
Admin
05418fdda1 Updated passthru after more testing 2016-04-21 15:41:06 -05:00
Admin
a717fd7c68 Add new classes. 2016-04-20 16:44:39 -05:00
Admin
8408d7350e More testing on HUE passthru. 2016-04-20 16:44:02 -05:00
Admin
3ba8f56db2 Completed Hue passthru 2016-04-18 16:41:15 -05:00
Admin
7a0946e3b7 Continue adding HUE pass thru. 2016-04-15 16:30:34 -05:00
Admin
50c9369d71 Added config override for server port. Updated Readme. Continued
enhancements to pass color to a real hue.
2016-04-14 17:12:06 -05:00
Admin
6c2a34f507 Updated code for collor passing. 2016-04-04 16:36:05 -05:00
Admin
ee2c105040 Started implementation 2016-04-01 16:33:55 -05:00
Admin
3ac83912f3 Fixed success return validation on calls and implemented validation
catching logic for URL issues.

Fixes #73
Fixes #75
2016-03-29 16:24:12 -05:00
Admin
e62fcf7765 Adding URL formatting for various characters and updating test for
success return.
2016-03-28 16:36:21 -05:00
Admin
9c3d95f177 Finished implementing and testing state change and bug fixes from
testing

Fixes #44
Fixes #60
Fixes #61
Fixes #63
Fixes #66
Fixes #69
2016-03-21 16:37:13 -05:00
Admin
926a7f50dc Finished adding dim versus on, utf-8 and track state and return on api
calls.
2016-03-18 16:38:18 -05:00
Admin
ad820a68c9 Updated C/F setting. Started to implement UTF-8 2016-03-16 17:13:03 -05:00
Admin
73f0f766f7 Added more logging and updated nest controller version to fix issues
with range temp setting in nest.
2016-03-09 16:45:21 -06:00
Admin
113ce0ca59 Updated logging with new nest module. 2016-03-08 16:40:32 -06:00
bwssystems
a5860417c1 Update null numberoflogmessages 2016-02-27 12:56:42 -06:00
bwssystems
48af3d84a2 Finished configurable logging view and control.
Fixes #51
Fixes #52
2016-02-27 12:48:19 -06:00
Admin
8ce0483e54 Adding log level control 2016-02-26 16:38:41 -06:00
Admin
1a2024d92b Tables in all views updated to scrollable/sortable. Refactored
controllers for views. debugging logging message return for messages
with invalid text for json parsing.
2016-02-25 16:42:35 -06:00
Admin
8198919a27 playing with scrollable table for log 2016-02-24 16:46:52 -06:00
Admin
614734a2aa Adding logs window. 2016-02-23 16:52:00 -06:00
Admin
58fccb1fa7 fixed about issue. Looking into log view and changed log impl to logback
within slf4j.
2016-02-22 16:38:57 -06:00
55 changed files with 3469 additions and 739 deletions

322
README.md

File diff suppressed because one or more lines are too long

44
pom.xml
View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId> <groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId> <artifactId>ha-bridge</artifactId>
<version>1.4.0</version> <version>2.0.3</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HA Bridge</name> <name>HA Bridge</name>
@@ -29,16 +29,42 @@
<groupId>com.github.bwssytems</groupId> <groupId>com.github.bwssytems</groupId>
<artifactId>harmony-java-client</artifactId> <artifactId>harmony-java-client</artifactId>
<version>1.0.8</version> <version>1.0.8</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.bwssytems</groupId> <groupId>com.github.bwssytems</groupId>
<artifactId>nest-controller</artifactId> <artifactId>nest-controller</artifactId>
<version>1.0.4</version> <version>1.0.8</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.sparkjava</groupId> <groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId> <artifactId>spark-core</artifactId>
<version>2.3</version> <version>2.3</version>
<exclusions>
<exclusion>
<artifactId>slf4j-simple</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
@@ -52,14 +78,14 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.5</version> <version>1.7.5</version>
</dependency> </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.5</version>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
@@ -138,7 +164,7 @@
<artifact>*:*</artifact> <artifact>*:*</artifact>
</filter> </filter>
<filter> <filter>
<artifact>org.slf4j:*</artifact> <artifact>org.slf4j:slf4j-api</artifact>
<includes> <includes>
<include>**</include> <include>**</include>
</includes> </includes>

View File

@@ -15,6 +15,8 @@ import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.util.BackupHandler;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson; import com.google.gson.Gson;
public class BridgeSettings extends BackupHandler { public class BridgeSettings extends BackupHandler {
@@ -38,12 +40,14 @@ public class BridgeSettings extends BackupHandler {
String addressString = null; String addressString = null;
String theVeraAddress = null; String theVeraAddress = null;
String theHarmonyAddress = null; String theHarmonyAddress = null;
String configFileProperty = System.getProperty("config.file"); String configFileProperty = System.getProperty("config.file");
if(configFileProperty == null) { if(configFileProperty == null) {
Path filePath = Paths.get(Configuration.CONFIG_FILE); Path filePath = Paths.get(Configuration.CONFIG_FILE);
if(Files.exists(filePath) && Files.isReadable(filePath)) if(Files.exists(filePath) && Files.isReadable(filePath))
configFileProperty = Configuration.CONFIG_FILE; configFileProperty = Configuration.CONFIG_FILE;
} }
String serverPortOverride = System.getProperty("server.port");
if(configFileProperty != null) if(configFileProperty != null)
{ {
log.info("reading from config file: " + configFileProperty); log.info("reading from config file: " + configFileProperty);
@@ -53,6 +57,8 @@ public class BridgeSettings extends BackupHandler {
else else
{ {
log.info("reading from system properties"); log.info("reading from system properties");
theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES);
theBridgeSettings.setFarenheit(true);
theBridgeSettings.setConfigfile(Configuration.CONFIG_FILE); theBridgeSettings.setConfigfile(Configuration.CONFIG_FILE);
theBridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DEFAULT_WEB_PORT)); theBridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DEFAULT_WEB_PORT));
theBridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address")); theBridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address"));
@@ -139,12 +145,18 @@ public class BridgeSettings extends BackupHandler {
if(theBridgeSettings.getUpnpDeviceDb() == null) if(theBridgeSettings.getUpnpDeviceDb() == null)
theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY); theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY);
if(theBridgeSettings.getNumberoflogmessages() == null)
theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES);
if(theBridgeSettings.getButtonsleep() <= 0) if(theBridgeSettings.getButtonsleep() <= 0)
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP)); theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera()); theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera());
theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony()); theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony());
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest()); theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride);
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-"); setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
} }
@@ -176,6 +188,10 @@ public class BridgeSettings extends BackupHandler {
theBridgeSettings.setVeraconfigured(aBridgeSettings.isValidVera()); theBridgeSettings.setVeraconfigured(aBridgeSettings.isValidVera());
theBridgeSettings.setHarmonyconfigured(aBridgeSettings.isValidHarmony()); theBridgeSettings.setHarmonyconfigured(aBridgeSettings.isValidHarmony());
theBridgeSettings.setNestConfigured(aBridgeSettings.isValidNest()); theBridgeSettings.setNestConfigured(aBridgeSettings.isValidNest());
theBridgeSettings.setNumberoflogmessages(aBridgeSettings.getNumberoflogmessages());
theBridgeSettings.setFarenheit(aBridgeSettings.isFarenheit());
theBridgeSettings.setHueaddress(aBridgeSettings.getHueaddress());
theBridgeSettings.setHueconfigured(aBridgeSettings.isValidHue());
} }
public void save(BridgeSettingsDescriptor newBridgeSettings) { public void save(BridgeSettingsDescriptor newBridgeSettings) {

View File

@@ -19,7 +19,11 @@ public class BridgeSettingsDescriptor {
private boolean veraconfigured; private boolean veraconfigured;
private boolean harmonyconfigured; private boolean harmonyconfigured;
private boolean nestconfigured; private boolean nestconfigured;
private boolean farenheit;
private String configfile; private String configfile;
private Integer numberoflogmessages;
private IpList hueaddress;
private boolean hueconfigured;
public BridgeSettingsDescriptor() { public BridgeSettingsDescriptor() {
super(); super();
@@ -28,6 +32,8 @@ public class BridgeSettingsDescriptor {
this.nestconfigured = false; this.nestconfigured = false;
this.veraconfigured = false; this.veraconfigured = false;
this.harmonyconfigured = false; this.harmonyconfigured = false;
this.hueconfigured = false;
this.farenheit = true;
} }
public String getUpnpConfigAddress() { public String getUpnpConfigAddress() {
return upnpconfigaddress; return upnpconfigaddress;
@@ -137,8 +143,32 @@ public class BridgeSettingsDescriptor {
public void setConfigfile(String configfile) { public void setConfigfile(String configfile) {
this.configfile = configfile; this.configfile = configfile;
} }
public Integer getNumberoflogmessages() {
return numberoflogmessages;
}
public void setNumberoflogmessages(Integer numberoflogmessages) {
this.numberoflogmessages = numberoflogmessages;
}
public boolean isFarenheit() {
return farenheit;
}
public void setFarenheit(boolean farenheit) {
this.farenheit = farenheit;
}
public IpList getHueaddress() {
return hueaddress;
}
public void setHueaddress(IpList hueaddress) {
this.hueaddress = hueaddress;
}
public boolean isHueconfigured() {
return hueconfigured;
}
public void setHueconfigured(boolean hueconfigured) {
this.hueconfigured = hueconfigured;
}
public Boolean isValidVera() { public Boolean isValidVera() {
if(this.getVeraAddress() == null) if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false; return false;
List<NamedIP> devicesList = this.getVeraAddress().getDevices(); List<NamedIP> devicesList = this.getVeraAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS)) if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
@@ -164,5 +194,12 @@ public class BridgeSettingsDescriptor {
return false; return false;
return true; return true;
} }
public Boolean isValidHue() {
if(this.getHueaddress() == null || this.getHueaddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getHueaddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
} }

View File

@@ -11,4 +11,5 @@ public class Configuration {
public static final int UPNP_DISCOVERY_PORT = 1900; public static final int UPNP_DISCOVERY_PORT = 1900;
public static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250"; public static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250";
public static final String CONFIG_FILE = "data/habridge.config"; public static final String CONFIG_FILE = "data/habridge.config";
public static final int NUMBER_OF_LOG_MESSAGES = 512;
} }

View File

@@ -11,6 +11,7 @@ import com.bwssystems.HABridge.upnp.UpnpListener;
import com.bwssystems.HABridge.upnp.UpnpSettingsResource; import com.bwssystems.HABridge.upnp.UpnpSettingsResource;
import com.bwssystems.NestBridge.NestHome; import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.HarmonyHome; import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.hue.HueHome;
public class HABridge { public class HABridge {
@@ -34,6 +35,7 @@ public class HABridge {
DeviceResource theResources; DeviceResource theResources;
HarmonyHome harmonyHome; HarmonyHome harmonyHome;
NestHome nestHome; NestHome nestHome;
HueHome hueHome;
HueMulator theHueMulator; HueMulator theHueMulator;
UpnpSettingsResource theSettingResponder; UpnpSettingsResource theSettingResponder;
UpnpListener theUpnpListener; UpnpListener theUpnpListener;
@@ -62,10 +64,12 @@ public class HABridge {
harmonyHome = new HarmonyHome(bridgeSettings.getBridgeSettingsDescriptor()); harmonyHome = new HarmonyHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the nest connection if available //setup the nest connection if available
nestHome = new NestHome(bridgeSettings.getBridgeSettingsDescriptor()); nestHome = new NestHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the hue passtrhu configuration if available
hueHome = new HueHome(bridgeSettings.getBridgeSettingsDescriptor());
// setup the class to handle the resource setup rest api // setup the class to handle the resource setup rest api
theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), harmonyHome, nestHome); theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), harmonyHome, nestHome, hueHome);
// setup the class to handle the hue emulator rest api // setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome); theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome);
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.getBridgeSettingsDescriptor()); theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
@@ -77,6 +81,8 @@ public class HABridge {
theUpnpListener = new UpnpListener(bridgeSettings.getBridgeSettingsDescriptor(), bridgeSettings.getBridgeControl()); theUpnpListener = new UpnpListener(bridgeSettings.getBridgeSettingsDescriptor(), bridgeSettings.getBridgeControl());
if(theUpnpListener.startListening()) if(theUpnpListener.startListening())
log.info("HA Bridge (v" + theVersion.getVersion() + ") reinitialization requessted...."); log.info("HA Bridge (v" + theVersion.getVersion() + ") reinitialization requessted....");
else
bridgeSettings.getBridgeControl().setStop(true);
bridgeSettings.getBridgeControl().setReinit(false); bridgeSettings.getBridgeControl().setReinit(false);
stop(); stop();

View File

@@ -9,28 +9,52 @@ import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.MulticastSocket; import java.net.MulticastSocket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.dao.BackupFilename; import com.bwssystems.HABridge.dao.BackupFilename;
import com.bwssystems.logservices.LoggerInfo;
import com.bwssystems.logservices.LoggingForm;
import com.bwssystems.logservices.LoggingManager;
import com.bwssystems.util.TextStringFormatter;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson; import com.google.gson.Gson;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.read.CyclicBufferAppender;
public class SystemControl { public class SystemControl {
private static final Logger log = LoggerFactory.getLogger(SystemControl.class); private static final Logger log = LoggerFactory.getLogger(SystemControl.class);
public static final String CYCLIC_BUFFER_APPENDER_NAME = "CYCLIC";
private LoggerContext lc;
private static final String SYSTEM_CONTEXT = "/system"; private static final String SYSTEM_CONTEXT = "/system";
private BridgeSettings bridgeSettings; private BridgeSettings bridgeSettings;
private Version version; private Version version;
private CyclicBufferAppender<ILoggingEvent> cyclicBufferAppender;
private DateFormat dateFormat;
private LoggingManager theLogServiceMgr;
public SystemControl(BridgeSettings theBridgeSettings, Version theVersion) { public SystemControl(BridgeSettings theBridgeSettings, Version theVersion) {
this.bridgeSettings = theBridgeSettings; this.bridgeSettings = theBridgeSettings;
this.version = theVersion; this.version = theVersion;
this.lc = (LoggerContext) LoggerFactory.getILoggerFactory();
this.dateFormat = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss.SSS");
reacquireCBA();
theLogServiceMgr = new LoggingManager();
theLogServiceMgr.init();
} }
// This function sets up the sparkjava rest calls for the hue api // This function sets up the sparkjava rest calls for the hue api
public void setupServer() { public void setupServer() {
log.info("Hue emulator service started...."); log.info("System control service started....");
// http://ip_address:port/system/habridge/version gets the version of this bridge instance // http://ip_address:port/system/habridge/version gets the version of this bridge instance
get (SYSTEM_CONTEXT + "/habridge/version", "application/json", (request, response) -> { get (SYSTEM_CONTEXT + "/habridge/version", "application/json", (request, response) -> {
log.debug("Get HA Bridge version: v" + version.getVersion()); log.debug("Get HA Bridge version: v" + version.getVersion());
@@ -38,7 +62,68 @@ public class SystemControl {
return "{\"version\":\"" + version.getVersion() + "\"}"; return "{\"version\":\"" + version.getVersion() + "\"}";
}); });
// http://ip_address:port/system/settings which returns the bridge configuration settings // http://ip_address:port/system/logmsgs gets the log messages for the bridge
get (SYSTEM_CONTEXT + "/logmsgs", "application/json", (request, response) -> {
log.debug("Get logmsgs.");
response.status(HttpStatus.SC_OK);
String logMsgs;
int count = -1;
if(cyclicBufferAppender == null)
reacquireCBA();
if (cyclicBufferAppender != null) {
count = cyclicBufferAppender.getLength();
}
logMsgs = "[";
if (count == -1) {
logMsgs = logMsgs + "{\"message\":\"Failed to locate CyclicBuffer\"}";
} else if (count == 0) {
logMsgs = logMsgs + "{\"message\":\"No logging events to display\"}";
} else {
LoggingEvent le;
for (int i = 0; i < count; i++) {
le = (LoggingEvent) cyclicBufferAppender.get(i);
logMsgs = logMsgs + ( i > 0?",{":"{") + "\"time\":\"" + dateFormat.format(le.getTimeStamp()) + "\",\"level\":\"" + le.getLevel().levelStr + "\",\"component\":\"" + le.getLoggerName() + "\",\"message\":\"" + TextStringFormatter.forJSON(le.getFormattedMessage()) + "\"}";
}
}
logMsgs = logMsgs + "]";
response.status(200);
return logMsgs;
});
// http://ip_address:port/system/logmgmt/loggers gets the logger info for the bridge
get (SYSTEM_CONTEXT + "/logmgmt/loggers/:all", "application/json", (request, response) -> {
log.debug("Get loggers info with showAll argument: " + request.params(":all"));
Boolean showAll = false;
if(request.params(":all").equals("true"))
showAll = true;
theLogServiceMgr.setShowAll(showAll);
theLogServiceMgr.init();
response.status(200);
return theLogServiceMgr.getConfiguredLoggers();
}, new JsonTransformer());
// http://ip_address:port/system/logmgmt/update CORS request
options(SYSTEM_CONTEXT + "/logmgmt/update", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/system/logmgmt/update which changes logging parameters for the process
put(SYSTEM_CONTEXT + "/logmgmt/update", "application/json", (request, response) -> {
log.debug("update loggers: " + request.body());
response.status(200);
LoggerInfo updateLoggers[];
updateLoggers = new Gson().fromJson(request.body(), LoggerInfo[].class);
LoggingForm theModel = theLogServiceMgr.getModel();
theModel.setUpdatedLoggers(Arrays.asList(updateLoggers));
theLogServiceMgr.updateLogLevels();
return theLogServiceMgr.getConfiguredLoggers();
}, new JsonTransformer());
// http://ip_address:port/system/settings which returns the bridge configuration settings
get(SYSTEM_CONTEXT + "/settings", "application/json", (request, response) -> { get(SYSTEM_CONTEXT + "/settings", "application/json", (request, response) -> {
log.debug("bridge settings requested from " + request.ip()); log.debug("bridge settings requested from " + request.ip());
@@ -155,9 +240,15 @@ public class SystemControl {
} }
else else
log.warn("No filename given for restore backup."); log.warn("No filename given for restore backup.");
return null; return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer()); }, new JsonTransformer());
} }
void reacquireCBA() {
cyclicBufferAppender = (CyclicBufferAppender<ILoggingEvent>) lc.getLogger(
Logger.ROOT_LOGGER_NAME).getAppender(CYCLIC_BUFFER_APPENDER_NAME);
cyclicBufferAppender.setMaxSize(bridgeSettings.getBridgeSettingsDescriptor().getNumberoflogmessages());
}
protected void pingListener() { protected void pingListener() {
try { try {

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.api;
public class CallItem {
private String item;
public String getItem() {
return item;
}
public void setItem(String anitem) {
item = anitem;
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.api;
public class NameValue {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.api;
public class SuccessUserResponse {
private UserCreateResponse success;
public UserCreateResponse getSuccess() {
return success;
}
public void setSuccess(UserCreateResponse success) {
this.success = success;
}
}

View File

@@ -0,0 +1,13 @@
package com.bwssystems.HABridge.api;
public class UserCreateResponse {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.HABridge.api.hue; package com.bwssystems.HABridge.api.hue;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
/** /**
* Created by arm on 4/14/15. * Created by arm on 4/14/15.
*/ */
@@ -68,18 +70,12 @@ public class DeviceResponse {
this.swversion = swversion; this.swversion = swversion;
} }
public static DeviceResponse createResponse(String name, String id){ public static DeviceResponse createResponse(DeviceDescriptor device){
DeviceState deviceState = new DeviceState();
DeviceResponse response = new DeviceResponse(); DeviceResponse response = new DeviceResponse();
response.setState(deviceState); response.setState(device.getDeviceState());
deviceState.setOn(false);
deviceState.setReachable(true);
deviceState.setEffect("none");
deviceState.setAlert("none");
deviceState.setBri(254);
response.setName(name); response.setName(device.getName());
response.setUniqueid(id); response.setUniqueid(device.getId());
response.setManufacturername("Philips"); response.setManufacturername("Philips");
response.setType("Dimmable light"); response.setType("Dimmable light");
response.setModelid("LWB004"); response.setModelid("LWB004");

View File

@@ -1,15 +1,22 @@
package com.bwssystems.HABridge.api.hue; package com.bwssystems.HABridge.api.hue;
// import java.util.ArrayList;
import java.util.List;
/** /**
* Created by arm on 4/14/15. * Created by arm on 4/14/15.
*/ */
public class DeviceState { public class DeviceState {
private boolean on; private boolean on;
private int bri = 255; private int bri;
private int hue;
private int sat;
private String effect; private String effect;
private int ct;
private String alert; private String alert;
private String colormode;
private boolean reachable; private boolean reachable;
private List<Double> xy;
public boolean isOn() { public boolean isOn() {
return on; return on;
@@ -27,6 +34,22 @@ public class DeviceState {
this.bri = bri; this.bri = bri;
} }
public int getHue() {
return hue;
}
public void setHue(int hue) {
this.hue = hue;
}
public int getSat() {
return sat;
}
public void setSat(int sat) {
this.sat = sat;
}
public String getEffect() { public String getEffect() {
return effect; return effect;
} }
@@ -35,6 +58,14 @@ public class DeviceState {
this.effect = effect; this.effect = effect;
} }
public int getCt() {
return ct;
}
public void setCt(int ct) {
this.ct = ct;
}
public String getAlert() { public String getAlert() {
return alert; return alert;
} }
@@ -43,6 +74,14 @@ public class DeviceState {
this.alert = alert; this.alert = alert;
} }
public String getColormode() {
return colormode;
}
public void setColormode(String colormode) {
this.colormode = colormode;
}
public boolean isReachable() { public boolean isReachable() {
return reachable; return reachable;
} }
@@ -51,6 +90,31 @@ public class DeviceState {
this.reachable = reachable; this.reachable = reachable;
} }
public List<Double> getXy() {
return xy;
}
public void setXy(List<Double> xy) {
this.xy = xy;
}
public static DeviceState createDeviceState() {
DeviceState newDeviceState = new DeviceState();
newDeviceState.fillIn();
// newDeviceState.setColormode("none");
// ArrayList<Double> doubleArray = new ArrayList<Double>();
// doubleArray.add(new Double(0));
// doubleArray.add(new Double(0));
// newDeviceState.setXy(doubleArray);
return newDeviceState;
}
public void fillIn() {
if(this.getAlert() == null)
this.setAlert("none");
if(this.getEffect() == null)
this.setEffect("none");
this.setReachable(true);
}
@Override @Override
public String toString() { public String toString() {
return "DeviceState{" + return "DeviceState{" +

View File

@@ -4,17 +4,18 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import com.bwssystems.HABridge.api.hue.DeviceResponse; import com.bwssystems.HABridge.api.hue.DeviceResponse;
import com.google.gson.JsonObject;
/** /**
* Created by arm on 4/14/15. * Created by arm on 4/14/15.
*/ */
public class HueApiResponse { public class HueApiResponse {
private Map<String, DeviceResponse> lights; private Map<String, DeviceResponse> lights;
private Map<String, String> scenes; private Map<String, JsonObject> scenes;
private Map<String, String> groups; private Map<String, JsonObject> groups;
private Map<String, String> schedules; private Map<String, JsonObject> schedules;
private Map<String, String> sensors; private Map<String, JsonObject> sensors;
private Map<String, String> rules; private Map<String, JsonObject> 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) {
@@ -35,43 +36,43 @@ public class HueApiResponse {
this.lights = lights; this.lights = lights;
} }
public Map<String, String> getScenes() { public Map<String, JsonObject> getScenes() {
return scenes; return scenes;
} }
public void setScenes(Map<String, String> scenes) { public void setScenes(Map<String, JsonObject> scenes) {
this.scenes = scenes; this.scenes = scenes;
} }
public Map<String, String> getGroups() { public Map<String, JsonObject> getGroups() {
return groups; return groups;
} }
public void setGroups(Map<String, String> groups) { public void setGroups(Map<String, JsonObject> groups) {
this.groups = groups; this.groups = groups;
} }
public Map<String, String> getSchedules() { public Map<String, JsonObject> getSchedules() {
return schedules; return schedules;
} }
public void setSchedules(Map<String, String> schedules) { public void setSchedules(Map<String, JsonObject> schedules) {
this.schedules = schedules; this.schedules = schedules;
} }
public Map<String, String> getSensors() { public Map<String, JsonObject> getSensors() {
return sensors; return sensors;
} }
public void setSensors(Map<String, String> sensors) { public void setSensors(Map<String, JsonObject> sensors) {
this.sensors = sensors; this.sensors = sensors;
} }
public Map<String, String> getRules() { public Map<String, JsonObject> getRules() {
return rules; return rules;
} }
public void setRules(Map<String, String> rules) { public void setRules(Map<String, JsonObject> rules) {
this.rules = rules; this.rules = rules;
} }

View File

@@ -1,21 +1,58 @@
package com.bwssystems.HABridge.dao; package com.bwssystems.HABridge.dao;
import com.bwssystems.HABridge.api.hue.DeviceState;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/* /*
* Object to handle the device configuration * Object to handle the device configuration
*/ */
public class DeviceDescriptor{ public class DeviceDescriptor{
private String id; @SerializedName("id")
@Expose
private String id;
@SerializedName("name")
@Expose
private String name; private String name;
@SerializedName("mapId")
@Expose
private String mapId; private String mapId;
@SerializedName("mapType")
@Expose
private String mapType; private String mapType;
@SerializedName("deviceType")
@Expose
private String deviceType; private String deviceType;
@SerializedName("targetDevice")
@Expose
private String targetDevice; private String targetDevice;
@SerializedName("offUrl")
@Expose
private String offUrl; private String offUrl;
@SerializedName("dimUrl")
@Expose
private String dimUrl;
@SerializedName("onUrl")
@Expose
private String onUrl; private String onUrl;
@SerializedName("headers")
@Expose
private String headers;
@SerializedName("httpVerb")
@Expose
private String httpVerb; private String httpVerb;
@SerializedName("contentType")
@Expose
private String contentType; private String contentType;
@SerializedName("contentBody")
@Expose
private String contentBody; private String contentBody;
@SerializedName("contentBodyOff")
@Expose
private String contentBodyOff; private String contentBodyOff;
private DeviceState deviceState;
public String getName() { public String getName() {
return name; return name;
} }
@@ -64,7 +101,15 @@ public class DeviceDescriptor{
this.offUrl = offUrl; this.offUrl = offUrl;
} }
public String getOnUrl() { public String getDimUrl() {
return dimUrl;
}
public void setDimUrl(String dimUrl) {
this.dimUrl = dimUrl;
}
public String getOnUrl() {
return onUrl; return onUrl;
} }
@@ -80,6 +125,14 @@ public class DeviceDescriptor{
this.id = id; this.id = id;
} }
public String getHeaders() {
return headers;
}
public void setHeaders(String headers) {
this.headers = headers;
}
public String getHttpVerb() { public String getHttpVerb() {
return httpVerb; return httpVerb;
} }
@@ -111,6 +164,15 @@ public class DeviceDescriptor{
public void setContentBodyOff(String contentBodyOff) { public void setContentBodyOff(String contentBodyOff) {
this.contentBodyOff = contentBodyOff; this.contentBodyOff = contentBodyOff;
} }
public DeviceState getDeviceState() {
if(deviceState == null)
deviceState = DeviceState.createDeviceState();
return deviceState;
}
public void setDeviceState(DeviceState deviceState) {
this.deviceState = deviceState;
}
} }

View File

@@ -2,7 +2,6 @@ package com.bwssystems.HABridge.dao;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader;
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;
@@ -15,25 +14,30 @@ import java.util.Random;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BackupHandler;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.dao.DeviceDescriptor; import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.google.gson.stream.JsonReader; import com.bwssystems.util.BackupHandler;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List; import java.util.List;
import java.util.ListIterator;
/* /*
* This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later * This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later
* loading. * loading.
*/ */
public class DeviceRepository extends BackupHandler { public class DeviceRepository extends BackupHandler {
Map<String, DeviceDescriptor> devices; private Map<String, DeviceDescriptor> devices;
Path repositoryPath; private Path repositoryPath;
final Random random = new Random(); private Gson gson;
final Logger log = LoggerFactory.getLogger(DeviceRepository.class); final private Random random = new Random();
private Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public DeviceRepository(String deviceDb) { public DeviceRepository(String deviceDb) {
super(); super();
gson =
new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
repositoryPath = null; repositoryPath = null;
repositoryPath = Paths.get(deviceDb); repositoryPath = Paths.get(deviceDb);
setupParams(repositoryPath, ".bk", "device.db-"); setupParams(repositoryPath, ".bk", "device.db-");
@@ -50,15 +54,11 @@ public class DeviceRepository extends BackupHandler {
if(jsonContent != null) if(jsonContent != null)
{ {
List<DeviceDescriptor> list = readJsonStream(jsonContent); DeviceDescriptor list[] = gson.fromJson(jsonContent, DeviceDescriptor[].class);
ListIterator<DeviceDescriptor> theIterator = list.listIterator(); for(int i = 0; i < list.length; i++) {
DeviceDescriptor theDevice = null; put(list[i].getId(), list[i]);
while (theIterator.hasNext()) {
theDevice = theIterator.next();
put(theDevice.getId(), theDevice);
} }
} }
} }
public List<DeviceDescriptor> findAll() { public List<DeviceDescriptor> findAll() {
@@ -82,15 +82,14 @@ public class DeviceRepository extends BackupHandler {
public void save(DeviceDescriptor[] descriptors) { public void save(DeviceDescriptor[] descriptors) {
String theNames = ""; String theNames = "";
for(int i = 0; i < descriptors.length; i++) { for(int i = 0; i < descriptors.length; i++) {
if(descriptors[i].getId() != null) if(descriptors[i].getId() != null && descriptors[i].getId().length() > 0)
devices.remove(descriptors[i].getId()); devices.remove(descriptors[i].getId());
else else
descriptors[i].setId(String.valueOf(random.nextInt(Integer.MAX_VALUE))); descriptors[i].setId(String.valueOf(random.nextInt(Integer.MAX_VALUE)));
put(descriptors[i].getId(), descriptors[i]); put(descriptors[i].getId(), descriptors[i]);
theNames = theNames + " " + descriptors[i].getName() + ", "; theNames = theNames + " " + descriptors[i].getName() + ", ";
} }
JsonTransformer aRenderer = new JsonTransformer(); String jsonValue = gson.toJson(findAll());
String jsonValue = aRenderer.render(findAll());
repositoryWriter(jsonValue, repositoryPath); repositoryWriter(jsonValue, repositoryPath);
log.debug("Save device(s): " + theNames); log.debug("Save device(s): " + theNames);
} }
@@ -153,82 +152,4 @@ public class DeviceRepository extends BackupHandler {
return content; return content;
} }
}
private List<DeviceDescriptor> readJsonStream(String context) {
JsonReader reader = new JsonReader(new StringReader(context));
List<DeviceDescriptor> theDescriptors = null;
try {
theDescriptors = readDescriptorArray(reader);
} catch (IOException e) {
log.error("Error reading json array: " + context + " message: " + e.getMessage(), e);
} finally {
try {
reader.close();
} catch (IOException e) {
log.error("Error closing json reader: " + context + " message: " + e.getMessage(), e);
}
}
return theDescriptors;
}
public List<DeviceDescriptor> readDescriptorArray(JsonReader reader) throws IOException {
List<DeviceDescriptor> descriptors = new ArrayList<DeviceDescriptor>();
reader.beginArray();
while (reader.hasNext()) {
descriptors.add(readDescriptor(reader));
}
reader.endArray();
return descriptors;
}
public DeviceDescriptor readDescriptor(JsonReader reader) throws IOException {
DeviceDescriptor deviceEntry = new DeviceDescriptor();
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
if (name.equals("id")) {
deviceEntry.setId(reader.nextString());
log.debug("Read a Device - device json id: " + deviceEntry.getId());
} else if (name.equals("name")) {
deviceEntry.setName(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getName());
} else if (name.equals("mapType")) {
deviceEntry.setMapType(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getMapType());
} else if (name.equals("mapId")) {
deviceEntry.setMapId(reader.nextString());
log.debug("Read a Device - device json name: " + deviceEntry.getMapId());
} else if (name.equals("deviceType")) {
deviceEntry.setDeviceType(reader.nextString());
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")) {
deviceEntry.setOffUrl(reader.nextString());
log.debug("Read a Device - device json off URL:" + deviceEntry.getOffUrl());
} else if (name.equals("onUrl")) {
deviceEntry.setOnUrl(reader.nextString());
log.debug("Read a Device - device json on URL:" + deviceEntry.getOnUrl());
} else if (name.equals("httpVerb")) {
deviceEntry.setHttpVerb(reader.nextString());
log.debug("Read a Device - device json httpVerb:" + deviceEntry.getHttpVerb());
} else if (name.equals("contentType")) {
deviceEntry.setContentType(reader.nextString());
log.debug("Read a Device - device json contentType:" + deviceEntry.getContentType());
} else if (name.equals("contentBody")) {
deviceEntry.setContentBody(reader.nextString());
log.debug("Read a Device - device json contentBody:" + deviceEntry.getContentBody());
} else if (name.equals("contentBodyOff")) {
deviceEntry.setContentBodyOff(reader.nextString());
log.debug("Read a Device - device json contentBodyOff:" + deviceEntry.getContentBodyOff());
} else {
reader.skipValue();
}
}
reader.endObject();
return deviceEntry;
}
}

View File

@@ -16,15 +16,16 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor; import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.dao.BackupFilename; 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.HABridge.dao.ErrorMessage; import com.bwssystems.HABridge.dao.ErrorMessage;
import com.bwssystems.NestBridge.NestHome; import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.HarmonyHome; import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.hue.HueHome;
import com.bwssystems.luupRequests.Device; import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene; import com.bwssystems.luupRequests.Scene;
import com.bwssystems.util.JsonTransformer;
import com.bwssystems.vera.VeraHome; import com.bwssystems.vera.VeraHome;
import com.google.gson.Gson; import com.google.gson.Gson;
@@ -34,14 +35,14 @@ import com.google.gson.Gson;
public class DeviceResource { public class DeviceResource {
private static final String API_CONTEXT = "/api/devices"; private static final String API_CONTEXT = "/api/devices";
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 VeraHome veraHome; private VeraHome veraHome;
private HarmonyHome myHarmonyHome; private HarmonyHome myHarmonyHome;
private NestHome nestHome; private NestHome nestHome;
private HueHome hueHome;
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(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome) { public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome) {
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb()); this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
if(theSettings.isValidVera()) if(theSettings.isValidVera())
@@ -58,7 +59,11 @@ public class DeviceResource {
this.nestHome = aNestHome; this.nestHome = aNestHome;
else else
this.nestHome = null; this.nestHome = null;
if(theSettings.isValidHue())
this.hueHome = aHueHome;
else
this.hueHome = null;
setupEndpoints(); setupEndpoints();
} }
@@ -117,35 +122,24 @@ public class DeviceResource {
put (API_CONTEXT + "/:id", "application/json", (request, response) -> { put (API_CONTEXT + "/:id", "application/json", (request, response) -> {
log.debug("Edit a Device - request body: " + request.body()); log.debug("Edit a Device - request body: " + request.body());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class); DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
DeviceDescriptor deviceEntry = deviceRepository.findOne(request.params(":id")); if(deviceRepository.findOne(request.params(":id")) == null){
if(deviceEntry == null){ log.debug("Could not save an edited device, Device Id not found: " + request.params(":id"));
log.debug("Could not save an edited Device Id: " + request.params(":id"));
response.status(HttpStatus.SC_BAD_REQUEST); response.status(HttpStatus.SC_BAD_REQUEST);
return new ErrorMessage("Could not save an edited Device Id: " + request.params(":id") + " "); return new ErrorMessage("Could not save an edited device, Device Id not found: " + request.params(":id") + " ");
} }
else else
{ {
log.debug("Saving an edited Device: " + deviceEntry.getName()); log.debug("Saving an edited Device: " + device.getName());
deviceEntry.setName(device.getName());
if (device.getDeviceType() != null) if (device.getDeviceType() != null)
deviceEntry.setDeviceType(device.getDeviceType()); device.setDeviceType(device.getDeviceType());
deviceEntry.setMapId(device.getMapId());
deviceEntry.setMapType(device.getMapType());
deviceEntry.setTargetDevice(device.getTargetDevice());
deviceEntry.setOnUrl(device.getOnUrl());
deviceEntry.setOffUrl(device.getOffUrl());
deviceEntry.setHttpVerb(device.getHttpVerb());
deviceEntry.setContentType(device.getContentType());
deviceEntry.setContentBody(device.getContentBody());
deviceEntry.setContentBodyOff(device.getContentBodyOff());
DeviceDescriptor[] theDevices = new DeviceDescriptor[1]; DeviceDescriptor[] theDevices = new DeviceDescriptor[1];
theDevices[0] = deviceEntry; theDevices[0] = device;
deviceRepository.save(theDevices); deviceRepository.save(theDevices);
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
} }
return deviceEntry; return device;
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT, "application/json", (request, response) -> { get (API_CONTEXT, "application/json", (request, response) -> {
@@ -258,6 +252,16 @@ public class DeviceResource {
return nestHome.getItems(); return nestHome.getItems();
}, new JsonTransformer()); }, new JsonTransformer());
get (API_CONTEXT + "/hue/devices", "application/json", (request, response) -> {
log.debug("Get hue items");
if(hueHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("A Hue is not available.");
}
response.status(HttpStatus.SC_OK);
return hueHome.getDevices();
}, new JsonTransformer());
get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> { get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> {
log.debug("Get backup filenames"); log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);

View File

@@ -1,7 +1,8 @@
package com.bwssystems.HABridge.hue; package com.bwssystems.HABridge.hue;
import com.bwssystems.HABridge.BridgeSettingsDescriptor; import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.JsonTransformer; import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.NameValue;
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;
@@ -13,7 +14,12 @@ import com.bwssystems.harmony.ButtonPress;
import com.bwssystems.harmony.HarmonyHandler; import com.bwssystems.harmony.HarmonyHandler;
import com.bwssystems.harmony.HarmonyHome; import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.harmony.RunActivity; import com.bwssystems.harmony.RunActivity;
import com.bwssystems.hue.HueDeviceIdentifier;
import com.bwssystems.hue.HueErrorStringSet;
import com.bwssystems.hue.HueHome;
import com.bwssystems.hue.HueUtil;
import com.bwssystems.nest.controller.Nest; import com.bwssystems.nest.controller.Nest;
import com.bwssystems.util.JsonTransformer;
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;
@@ -28,34 +34,44 @@ import static spark.Spark.put;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.Charset;
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.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter; 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
*/ */
public class HueMulator { public class HueMulator implements HueErrorStringSet {
private static final Logger log = LoggerFactory.getLogger(HueMulator.class); private static final Logger log = LoggerFactory.getLogger(HueMulator.class);
private static final String INTENSITY_PERCENT = "${intensity.percent}"; private static final String INTENSITY_PERCENT = "${intensity.percent}";
private static final String INTENSITY_BYTE = "${intensity.byte}"; private static final String INTENSITY_BYTE = "${intensity.byte}";
@@ -67,14 +83,37 @@ public class HueMulator {
private DeviceRepository repository; private DeviceRepository repository;
private HarmonyHome myHarmonyHome; private HarmonyHome myHarmonyHome;
private Nest theNest; private Nest theNest;
private HueHome myHueHome;
private HttpClient httpClient; private HttpClient httpClient;
private CloseableHttpClient httpclientSSL;
private SSLContext sslcontext;
private SSLConnectionSocketFactory sslsf;
private RequestConfig globalConfig;
private ObjectMapper mapper; private ObjectMapper mapper;
private BridgeSettingsDescriptor bridgeSettings; private BridgeSettingsDescriptor bridgeSettings;
private byte[] sendData; private byte[] sendData;
private String hueUser;
private String errorString;
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome){ public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome){
httpClient = HttpClients.createDefault(); httpClient = HttpClients.createDefault();
// Trust own CA and all self-signed certs
sslcontext = SSLContexts.createDefault();
// Allow TLSv1 protocol only
sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
globalConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD)
.build();
httpclientSSL = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(globalConfig)
.build();
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;
@@ -86,7 +125,13 @@ public class HueMulator {
this.theNest = aNestHome.getTheNest(); this.theNest = aNestHome.getTheNest();
else else
this.theNest = null; this.theNest = null;
if(theBridgeSettings.isValidHue())
this.myHueHome = aHueHome;
else
this.myHueHome = null;
bridgeSettings = theBridgeSettings; bridgeSettings = theBridgeSettings;
hueUser = null;
errorString = null;
} }
// This function sets up the sparkjava rest calls for the hue api // This function sets up the sparkjava rest calls for the hue api
@@ -101,7 +146,7 @@ public class HueMulator {
List<DeviceDescriptor> deviceList = repository.findAll(); List<DeviceDescriptor> deviceList = repository.findAll();
Map<String, DeviceResponse> deviceResponseMap = new HashMap<>(); Map<String, DeviceResponse> deviceResponseMap = new HashMap<>();
for (DeviceDescriptor device : deviceList) { for (DeviceDescriptor device : deviceList) {
DeviceResponse deviceResponse = DeviceResponse.createResponse(device.getName(), device.getId()); DeviceResponse deviceResponse = DeviceResponse.createResponse(device);
deviceResponseMap.put(device.getId(), deviceResponse); deviceResponseMap.put(device.getId(), deviceResponse);
} }
response.type("application/json; charset=utf-8"); response.type("application/json; charset=utf-8");
@@ -225,7 +270,7 @@ public class HueMulator {
Map<String, DeviceResponse> deviceList = new HashMap<>(); Map<String, DeviceResponse> deviceList = new HashMap<>();
descriptorList.forEach(descriptor -> { descriptorList.forEach(descriptor -> {
DeviceResponse deviceResponse = DeviceResponse.createResponse(descriptor.getName(), descriptor.getId()); DeviceResponse deviceResponse = DeviceResponse.createResponse(descriptor);
deviceList.put(descriptor.getId(), deviceResponse); deviceList.put(descriptor.getId(), deviceResponse);
} }
); );
@@ -249,13 +294,53 @@ public class HueMulator {
} else { } else {
log.debug("found device named: " + device.getName()); log.debug("found device named: " + device.getName());
} }
DeviceResponse lightResponse = DeviceResponse.createResponse(device.getName(), device.getId()); DeviceResponse lightResponse = DeviceResponse.createResponse(device);
response.type("application/json; charset=utf-8"); response.type("application/json; charset=utf-8");
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
return lightResponse; return lightResponse;
}, new JsonTransformer()); }, new JsonTransformer());
// http://ip_address:port/api/:userid/lights/:id/bridgeupdatestate CORS request
options(HUE_CONTEXT + "/:userid/lights/:id/bridgeupdatestate", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "GET, POST, PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
// http://ip_address:port/api/{userId}/lights/{lightId}/bridgeupdatestate uses json object to update the internal bridge lights state.
// THIS IS NOT A HUE API CALL... It is for state management if so desired.
put(HUE_CONTEXT + "/:userid/lights/:id/bridgeupdatestate", "application/json", (request, response) -> {
String userId = request.params(":userid");
String lightId = request.params(":id");
String responseString = null;
DeviceState state = null;
log.debug("Update state 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 {
state = mapper.readValue(request.body(), DeviceState.class);
} catch (IOException e) {
log.warn("Object mapper barfed on input of body.", e);
responseString = "[{\"error\":{\"type\": 2, \"address\": \"/lights/" + lightId + "\",\"description\": \"Object mapper barfed on input of body.\"}}]";
return responseString;
}
DeviceDescriptor device = repository.findOne(lightId);
if (device == null) {
log.warn("Could not find device: " + lightId + " for hue state change request: " + userId + " from " + request.ip() + " body: " + request.body());
responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]";
return responseString;
}
responseString = this.formatSuccessHueResponse(state, request.body(), lightId);
device.setDeviceState(state);
return responseString;
});
// http://ip_address:port/api/:userid/lights/:id/state CORS request // http://ip_address:port/api/:userid/lights/:id/state CORS request
options(HUE_CONTEXT + "/:userid/lights/:id/state", "application/json", (request, response) -> { options(HUE_CONTEXT + "/:userid/lights/:id/state", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK); response.status(HttpStatus.SC_OK);
@@ -275,6 +360,7 @@ public class HueMulator {
String lightId = request.params(":id"); String lightId = request.params(":id");
String responseString = null; String responseString = null;
String url = null; String url = null;
NameValue[] theHeaders = null;
DeviceState state = null; DeviceState state = null;
log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body()); log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body());
response.header("Access-Control-Allow-Origin", request.headers("Origin")); response.header("Access-Control-Allow-Origin", request.headers("Origin"));
@@ -295,30 +381,72 @@ public class HueMulator {
responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]"; responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]";
return responseString; return responseString;
} }
state.fillIn();
theHeaders = new Gson().fromJson(device.getHeaders(), NameValue[].class);
responseString = this.formatSuccessHueResponse(state, request.body(), lightId);
if(device.getDeviceType().toLowerCase().contains("hue") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("hueDevice")))
{
if(myHueHome != null) {
url = device.getOnUrl();
HueDeviceIdentifier deviceId = new Gson().fromJson(url, HueDeviceIdentifier.class);
if(myHueHome.getTheHUERegisteredUser() == null) {
hueUser = HueUtil.registerWithHue(httpClient, deviceId.getIpAddress(), device.getName(), myHueHome.getTheHUERegisteredUser(), this);
if(hueUser == null) {
return errorString;
}
myHueHome.setTheHUERegisteredUser(hueUser);
}
if (state.isOn()) { // make call
responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":true}}"; responseString = doHttpRequest("http://"+deviceId.getIpAddress()+"/api/"+myHueHome.getTheHUERegisteredUser()+"/lights/"+deviceId.getDeviceId()+"/state", HttpPut.METHOD_NAME, device.getContentType(), request.body(), null);
url = device.getOnUrl(); if (responseString == null) {
} else if (request.body().contains("false")) { log.warn("Error on calling url to change device state: " + url);
responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":false}}"; responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling HUE to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
url = device.getOffUrl(); }
else if(responseString.contains("[{\"error\":") && responseString.contains("unauthorized user")) {
myHueHome.setTheHUERegisteredUser(null);
hueUser = HueUtil.registerWithHue(httpClient, deviceId.getIpAddress(), device.getName(), myHueHome.getTheHUERegisteredUser(), this);
if(hueUser == null) {
return errorString;
}
myHueHome.setTheHUERegisteredUser(hueUser);
}
else if(!responseString.contains("[{\"error\":"))
device.setDeviceState(state);
}
else
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"No HUE configured\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
return responseString;
} }
if(request.body().contains("bri")) if(request.body().contains("bri"))
{ {
if(url == null) url = device.getDimUrl();
{
url = device.getOnUrl();
responseString = "[";
}
else
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/bri\":" + state.getBri() + "}}]"; if(url == null || url.length() == 0)
url = device.getOnUrl();
} }
else else
responseString = responseString + "]"; {
if (state.isOn()) {
url = device.getOnUrl();
state.setBri(255);
} else if (request.body().contains("false")) {
url = device.getOffUrl();
state.setBri(0);
}
}
if (url == null) {
log.warn("Could not find url: " + lightId + " for hue state change request: " + userId + " from " + request.ip() + " body: " + request.body());
responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + "\",\"description\": \"Could not find url\", \"resource\": \"/lights/" + lightId + "\"}}]";
return responseString;
}
if(device.getDeviceType().toLowerCase().contains("activity") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyActivity"))) if(device.getDeviceType().toLowerCase().contains("activity") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyActivity")))
{ {
log.debug("executing HUE api request to change activity to Harmony: " + url); log.debug("executing HUE api request to change activity to Harmony: " + url);
@@ -337,7 +465,6 @@ public class HueMulator {
else { else {
log.warn("Should not get here, no harmony configured"); log.warn("Should not get here, no harmony configured");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no harmony configured\", \"parameter\": \"/lights/" + lightId + "state\"}}]"; responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no harmony configured\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
} }
} }
else if(device.getDeviceType().toLowerCase().contains("button") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyButton"))) else if(device.getDeviceType().toLowerCase().contains("button") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyButton")))
@@ -395,7 +522,10 @@ public class HueMulator {
NestInstruction thermoSetting = new Gson().fromJson(url, NestInstruction.class); NestInstruction thermoSetting = new Gson().fromJson(url, NestInstruction.class);
if(thermoSetting.getControl().equalsIgnoreCase("temp")) { if(thermoSetting.getControl().equalsIgnoreCase("temp")) {
if(request.body().contains("bri")) { if(request.body().contains("bri")) {
thermoSetting.setTemp(String.valueOf((Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri())) - 32.0)/1.8)); if(bridgeSettings.isFarenheit())
thermoSetting.setTemp(String.valueOf((Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri(), false)) - 32.0)/1.8));
else
thermoSetting.setTemp(String.valueOf(Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri(), false))));
log.debug("Setting thermostat: " + thermoSetting.getName() + " to " + thermoSetting.getTemp() + "C"); log.debug("Setting thermostat: " + thermoSetting.getName() + " to " + thermoSetting.getTemp() + "C");
theNest.getThermostat(thermoSetting.getName()).setTargetTemperature(Float.parseFloat(thermoSetting.getTemp())); theNest.getThermostat(thermoSetting.getName()).setTargetTemperature(Float.parseFloat(thermoSetting.getTemp()));
} }
@@ -414,46 +544,110 @@ public class HueMulator {
} }
} }
} }
else if(url.startsWith("udp://")) else if(device.getDeviceType().startsWith("exec")) {
{ log.debug("Exec Request called with url: " + url);
log.debug("executing HUE api request to UDP: " + url); if(!url.startsWith("[")) {
try { if(url.startsWith("{\"item"))
String intermediate = url.substring(6); url = "[" + url + "]";
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 else
sendData = theBody.getBytes(); url = "[{\"item\":\"" + url +"\"}]";
InetAddress IPAddress = InetAddress.getByName(ipAddr); }
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, Integer.parseInt(port)); CallItem[] callItems = new Gson().fromJson(url, CallItem[].class);
responseSocket.send(sendPacket); for(int i = 0; i < callItems.length; i++) {
responseSocket.close(); if( i > 0) {
} catch (IOException e) { Thread.sleep(bridgeSettings.getButtonsleep());
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\"}}]"; try {
} log.debug("Executing request: " + callItems[i].getItem());
Process p = Runtime.getRuntime().exec(replaceIntensityValue(callItems[i].getItem(), state.getBri(), false));
log.debug("Process running: " + p.isAlive());
} catch (IOException e) {
log.warn("Could not execute request: " + callItems[i].getItem(), e);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
}
} }
else else // This section allows the usage of http/tcp/udp calls in a given set of items
{ {
log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + url); log.debug("executing HUE api request for network call: " + url);
// quick template if(!url.startsWith("[")) {
String body; if(url.startsWith("{\"item"))
url = replaceIntensityValue(url, state.getBri()); url = "[" + url + "]";
if (state.isOn()) else
body = replaceIntensityValue(device.getContentBody(), state.getBri()); url = "[{\"item\":\"" + url +"\"}]";
else }
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri()); CallItem[] callItems = new Gson().fromJson(url, CallItem[].class);
// make call for(int i = 0; i < callItems.length; i++) {
if (!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)) { if( i > 0) {
log.warn("Error on calling url to change device state: " + url); Thread.sleep(bridgeSettings.getButtonsleep());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]"; }
} try {
if(callItems[i].getItem().contains("udp://") || callItems[i].getItem().contains("tcp://")) {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/')+1);
String hostAddr = null;
String port = null;
if(hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
}
else
hostAddr = hostPortion;
InetAddress IPAddress = InetAddress.getByName(hostAddr);;
if(theUrlBody.startsWith("0x")) {
theUrlBody = replaceIntensityValue(theUrlBody, state.getBri(), true);
sendData = DatatypeConverter.parseHexBinary(theUrlBody.substring(2));
}
else {
theUrlBody = replaceIntensityValue(theUrlBody, state.getBri(), false);
sendData = theUrlBody.getBytes();
}
if(callItems[i].getItem().contains("udp://")) {
log.debug("executing HUE api request to UDP: " + callItems[i].getItem());
DatagramSocket responseSocket = new DatagramSocket(Integer.parseInt(port));
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, Integer.parseInt(port));
responseSocket.send(sendPacket);
responseSocket.close();
}
else if(callItems[i].getItem().contains("tcp://"))
{
log.debug("executing HUE api request to TCP: " + callItems[i].getItem());
Socket dataSendSocket = new Socket(IPAddress, Integer.parseInt(port));
DataOutputStream outToClient = new DataOutputStream(dataSendSocket.getOutputStream());
outToClient.write(sendData);
outToClient.flush();
dataSendSocket.close();
}
}
else {
log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + callItems[i].getItem());
String anUrl = replaceIntensityValue(callItems[i].getItem(), state.getBri(), false);
String body;
if (state.isOn())
body = replaceIntensityValue(device.getContentBody(), state.getBri(), false);
else
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri(), false);
// make call
if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), body, theHeaders) == null) {
log.warn("Error on calling url to change device state: " + anUrl);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
}
} catch (Exception e) {
log.warn("Change device state, Could not send data for network request: " + callItems[i].getItem() + " with Message: " + e.getMessage());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
i = callItems.length+1;
}
}
}
if(!responseString.contains("[{\"error\":")) {
device.setDeviceState(state);
} }
return responseString; return responseString;
}); });
} }
@@ -466,18 +660,34 @@ public class HueMulator {
* intensity.percent : 0-100, adjusted for the vera * intensity.percent : 0-100, adjusted for the vera
* intensity.math(X*1) : where X is the value from the interface call and can use net.java.dev.eval math * intensity.math(X*1) : where X is the value from the interface call and can use net.java.dev.eval math
*/ */
protected String replaceIntensityValue(String request, int intensity){ protected String replaceIntensityValue(String request, int intensity, boolean isHex){
if(request == null){ if(request == null){
return ""; return "";
} }
if(request.contains(INTENSITY_BYTE)){ if(request.contains(INTENSITY_BYTE)) {
String intensityByte = String.valueOf(intensity); if(isHex) {
request = request.replace(INTENSITY_BYTE, intensityByte); BigInteger bigInt = BigInteger.valueOf(intensity);
}else if(request.contains(INTENSITY_PERCENT)){ byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_BYTE, hexValue);
}
else {
String intensityByte = String.valueOf(intensity);
request = request.replace(INTENSITY_BYTE, intensityByte);
}
} else if(request.contains(INTENSITY_PERCENT)) {
int percentBrightness = (int) Math.round(intensity/255.0*100); int percentBrightness = (int) Math.round(intensity/255.0*100);
String intensityPercent = String.valueOf(percentBrightness); if(isHex) {
request = request.replace(INTENSITY_PERCENT, intensityPercent); BigInteger bigInt = BigInteger.valueOf(percentBrightness);
} else if(request.contains(INTENSITY_MATH)){ byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_PERCENT, hexValue);
}
else {
String intensityPercent = String.valueOf(percentBrightness);
request = request.replace(INTENSITY_PERCENT, intensityPercent);
}
} else if(request.contains(INTENSITY_MATH)) {
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>(); Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE)); String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity)); variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
@@ -487,7 +697,15 @@ public class HueMulator {
Expression exp = new Expression(mathDescriptor); Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables); BigDecimal result = exp.eval(variables);
Integer endResult = Math.round(result.floatValue()); Integer endResult = Math.round(result.floatValue());
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString()); if(isHex) {
BigInteger bigInt = BigInteger.valueOf(endResult);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue);
}
else {
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString());
}
} catch (Exception e) { } catch (Exception e) {
log.warn("Could not execute Math: " + mathDescriptor, e); log.warn("Could not execute Math: " + mathDescriptor, e);
} }
@@ -496,35 +714,124 @@ public class HueMulator {
} }
// This function executes the url from the device repository against the vera // This function executes the url from the device repository against the target as http or https as defined
protected boolean doHttpRequest(String url, String httpVerb, String contentType, String body) { protected String doHttpRequest(String url, String httpVerb, String contentType, String body, NameValue[] headers) {
HttpUriRequest request = null; HttpUriRequest request = null;
if(HttpGet.METHOD_NAME.equalsIgnoreCase(httpVerb) || httpVerb == null) { String theContent = null;
request = new HttpGet(url); try {
}else if(HttpPost.METHOD_NAME.equalsIgnoreCase(httpVerb)){ if(HttpGet.METHOD_NAME.equalsIgnoreCase(httpVerb) || httpVerb == null) {
HttpPost postRequest = new HttpPost(url); request = new HttpGet(url);
ContentType parsedContentType = ContentType.parse(contentType); }else if(HttpPost.METHOD_NAME.equalsIgnoreCase(httpVerb)){
StringEntity requestBody = new StringEntity(body, parsedContentType); HttpPost postRequest = new HttpPost(url);
postRequest.setEntity(requestBody); ContentType parsedContentType = ContentType.parse(contentType);
request = postRequest; StringEntity requestBody = new StringEntity(body, parsedContentType);
}else if(HttpPut.METHOD_NAME.equalsIgnoreCase(httpVerb)){ postRequest.setEntity(requestBody);
HttpPut putRequest = new HttpPut(url); request = postRequest;
ContentType parsedContentType = ContentType.parse(contentType); }else if(HttpPut.METHOD_NAME.equalsIgnoreCase(httpVerb)){
StringEntity requestBody = new StringEntity(body, parsedContentType); HttpPut putRequest = new HttpPut(url);
putRequest.setEntity(requestBody); ContentType parsedContentType = ContentType.parse(contentType);
request = putRequest; StringEntity requestBody = new StringEntity(body, parsedContentType);
putRequest.setEntity(requestBody);
request = putRequest;
}
} catch(IllegalArgumentException e) {
log.warn("Error calling out to HA gateway: IllegalArgumentException in log", e);
return null;
} }
log.debug("Making outbound call in doHttpRequest: " + request); log.debug("Making outbound call in doHttpRequest: " + request);
try { try {
HttpResponse response = httpClient.execute(request); if(headers != null && headers.length > 0) {
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content for(int i = 0; i < headers.length; i++) {
request.setHeader(headers[i].getName(), headers[i].getValue());
}
}
HttpResponse response;
if(url.startsWith("https"))
response = httpclientSSL.execute(request);
else
response = httpClient.execute(request);
log.debug((httpVerb == null?"GET":httpVerb) + " execute on URL responded: " + response.getStatusLine().getStatusCode()); log.debug((httpVerb == null?"GET":httpVerb) + " execute on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){ if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
return true; theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
} }
} catch (IOException e) { } catch (IOException e) {
log.warn("Error calling out to HA gateway", e); log.warn("Error calling out to HA gateway: IOException in log", e);
} }
return false; return theContent;
} }
private String formatSuccessHueResponse(DeviceState state, String body, String lightId) {
String responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":";
boolean justState = true;
if(body.contains("bri"))
{
if(justState)
responseString = responseString + "true}}";
responseString = responseString + ",{\"success\":{\"/lights/" + lightId + "/state/bri\":" + state.getBri() + "}}";
justState = false;
}
if(body.contains("ct"))
{
if(justState)
responseString = responseString + "true}}";
responseString = responseString + ",{\"success\":{\"/lights/" + lightId + "/state/ct\":" + state.getCt() + "}}";
justState = false;
}
if(body.contains("xy"))
{
if(justState)
responseString = responseString + "true}}";
responseString = responseString + ",{\"success\":{\"/lights/" + lightId + "/state/xy\":" + state.getXy() + "}}";
justState = false;
}
if(body.contains("hue"))
{
if(justState)
responseString = responseString + "true}}";
responseString = responseString + ",{\"success\":{\"/lights/" + lightId + "/state/hue\":" + state.getHue() + "}}";
justState = false;
}
if(body.contains("sat"))
{
if(justState)
responseString = responseString + "true}}";
responseString = responseString + ",{\"success\":{\"/lights/" + lightId + "/state/sat\":" + state.getSat() + "}}";
justState = false;
}
if(body.contains("colormode"))
{
if(justState)
responseString = responseString + "true}}";
responseString = responseString + ",{\"success\":{\"/lights/" + lightId + "/state/colormode\":" + state.getColormode() + "}}";
justState = false;
}
if(justState)
{
if (state.isOn()) {
responseString = responseString + "true}}";
state.setBri(255);
} else if (body.contains("false")) {
responseString = responseString + "false}}";
state.setBri(0);
}
}
responseString = responseString + "]";
return responseString;
}
@Override
public void setErrorString(String anError) {
errorString = anError;
}
} }

View File

@@ -33,69 +33,121 @@ public class UpnpListener {
bridgeControl = theControl; bridgeControl = theControl;
} }
@SuppressWarnings("resource")
public boolean startListening(){ public boolean startListening(){
log.info("UPNP Discovery Listener starting...."); log.info("UPNP Discovery Listener starting....");
DatagramSocket responseSocket = null;
MulticastSocket upnpMulticastSocket = null;
Enumeration<NetworkInterface> ifs = null;
try (DatagramSocket responseSocket = new DatagramSocket(upnpResponsePort); boolean portLoopControl = true;
MulticastSocket upnpMulticastSocket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT);) { int retryCount = 0;
InetSocketAddress socketAddress = new InetSocketAddress(Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT); while(portLoopControl) {
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces(); try {
responseSocket = new DatagramSocket(upnpResponsePort);
while (ifs.hasMoreElements()) { if(retryCount > 0)
NetworkInterface xface = ifs.nextElement(); log.info("Upnp Response Port issue, found open port: " + upnpResponsePort);
Enumeration<InetAddress> addrs = xface.getInetAddresses(); portLoopControl = false;
String name = xface.getName(); } catch(SocketException e) {
int IPsPerNic = 0; if(retryCount == 0)
log.warn("Upnp Response Port is in use, starting loop to find open port for 20 tries - configured port is: " + upnpResponsePort);
while (addrs.hasMoreElements()) { if(retryCount >= 20) {
InetAddress addr = addrs.nextElement(); portLoopControl = false;
if(traceupnp) log.error("Upnp Response Port issue, could not find open port - last port tried: " + upnpResponsePort + " with message: " + e.getMessage());
log.info("Traceupnp: " + name + " ... has addr " + addr); return false;
else
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
}
} }
log.debug("Checking " + name + " to our interface set"); }
if (IPsPerNic > 0) { if(portLoopControl) {
retryCount++;
upnpResponsePort++;
}
}
try {
upnpMulticastSocket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT);
} catch(IOException e){
log.error("Upnp Discovery Port is in use, or restricted by admin (try running with sudo or admin privs): " + Configuration.UPNP_DISCOVERY_PORT + " with message: " + e.getMessage());
return false;
}
InetSocketAddress socketAddress = new InetSocketAddress(Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT);
try {
ifs = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
log.error("Could not get network interfaces for this machine: " + e.getMessage());
return false;
}
while (ifs.hasMoreElements()) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if (traceupnp)
log.info("Traceupnp: " + name + " ... has addr " + addr);
else
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
}
}
log.debug("Checking " + name + " to our interface set");
if (IPsPerNic > 0) {
try {
upnpMulticastSocket.joinGroup(socketAddress, xface); upnpMulticastSocket.joinGroup(socketAddress, xface);
if(traceupnp) if (traceupnp)
log.info("Traceupnp: Adding " + name + " to our interface set"); log.info("Traceupnp: Adding " + name + " to our interface set");
else else
log.debug("Adding " + name + " to our interface set"); log.debug("Adding " + name + " to our interface set");
} catch (IOException e) {
log.warn("Multicast join failed for: " + socketAddress.getHostName() + " to interface: "
+ xface.getName() + " with message: " + e.getMessage());
} }
} }
log.info("UPNP Discovery Listener running and ready....");
boolean loopControl = true;
while(loopControl){ //trigger shutdown here
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
upnpMulticastSocket.receive(packet);
if(isSSDPDiscovery(packet)){
sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort());
}
if(bridgeControl.isReinit() || bridgeControl.isStop()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// noop
}
loopControl = false;
}
}
upnpMulticastSocket.close();
responseSocket.close();
} catch (IOException e) {
log.error("UpnpListener encountered an error opening sockets. Shutting down", e);
} }
if(bridgeControl.isReinit())
log.info("UPNP Discovery Listener running and ready....");
boolean loopControl = true;
boolean error = false;
while (loopControl) { // trigger shutdown here
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
try {
upnpMulticastSocket.receive(packet);
if (isSSDPDiscovery(packet)) {
try {
sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort());
} catch (IOException e) {
log.error("UpnpListener encountered an error sending upnp response packet. Shutting down", e);
error = true;
}
}
} catch (IOException e) {
log.error("UpnpListener encountered an error reading socket. Shutting down", e);
error = true;
}
if (error || bridgeControl.isReinit() || bridgeControl.isStop()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// noop
}
loopControl = false;
}
}
upnpMulticastSocket.close();
responseSocket.close();
if (bridgeControl.isReinit())
log.info("UPNP Discovery Listener - ended, restart found"); log.info("UPNP Discovery Listener - ended, restart found");
if(bridgeControl.isStop()) if (bridgeControl.isStop())
log.info("UPNP Discovery Listener - ended, stop found"); log.info("UPNP Discovery Listener - ended, stop found");
if(!bridgeControl.isStop()&& !bridgeControl.isReinit()) if (!bridgeControl.isStop() && !bridgeControl.isReinit()) {
log.info("UPNP Discovery Listener - ended, error found"); log.info("UPNP Discovery Listener - ended, error found");
return false;
}
return bridgeControl.isReinit(); return bridgeControl.isReinit();
} }
@@ -104,7 +156,7 @@ public class UpnpListener {
*/ */
protected boolean isSSDPDiscovery(DatagramPacket packet){ protected boolean isSSDPDiscovery(DatagramPacket packet){
//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(), 0, packet.getLength());
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\"")){
log.debug("isSSDPDiscovery Found message to be an M-SEARCH message."); log.debug("isSSDPDiscovery Found message to be an M-SEARCH message.");
log.debug("isSSDPDiscovery Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString); log.debug("isSSDPDiscovery Got SSDP packet from " + packet.getAddress().getHostAddress() + ":" + packet.getPort() + ", body: " + packetString);
@@ -148,9 +200,9 @@ public class UpnpListener {
String discoveryResponse = null; String discoveryResponse = null;
discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort); discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort);
if(traceupnp) if(traceupnp)
log.info("Traceupnp: sendUpnpResponse: " + discoveryResponse); log.info("Traceupnp: sendUpnpResponse discovery template with address: " + responseAddress + " and port: " + httpServerPort);
else else
log.debug("sendUpnpResponse: " + discoveryResponse); log.debug("sendUpnpResponse discovery template with address: " + responseAddress + " and port: " + httpServerPort);
DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort); DatagramPacket response = new DatagramPacket(discoveryResponse.getBytes(), discoveryResponse.length(), requester, sourcePort);
socket.send(response); socket.send(response);
} }

View File

@@ -54,9 +54,9 @@ public class UpnpSettingsResource {
String filledTemplate = null; String filledTemplate = null;
filledTemplate = String.format(hueTemplate, theSettings.getUpnpConfigAddress(), portNumber, theSettings.getUpnpConfigAddress()); filledTemplate = String.format(hueTemplate, theSettings.getUpnpConfigAddress(), portNumber, theSettings.getUpnpConfigAddress());
if(theSettings.isTraceupnp()) if(theSettings.isTraceupnp())
log.info("Traceupnp: upnp device settings response: " + filledTemplate); log.info("Traceupnp: upnp device settings template filled with address: " + theSettings.getUpnpConfigAddress() + " and port: " + portNumber);
else else
log.debug("upnp device settings response: " + filledTemplate); log.debug("Traceupnp: upnp device settings template filled with address: " + theSettings.getUpnpConfigAddress() + " and port: " + portNumber);
// response.header("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0"); // response.header("Cache-Control", "no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
// response.header("Pragma", "no-cache"); // response.header("Pragma", "no-cache");
// response.header("Expires", "Mon, 1 Aug 2011 09:00:00 GMT"); // response.header("Expires", "Mon, 1 Aug 2011 09:00:00 GMT");

View File

@@ -1,5 +1,7 @@
package com.bwssystems.NestBridge; package com.bwssystems.NestBridge;
import java.io.UnsupportedEncodingException;
public class NestItem { public class NestItem {
private String name; private String name;
private String id; private String id;
@@ -9,7 +11,14 @@ public class NestItem {
return name; return name;
} }
public void setName(String name) { public void setName(String name) {
this.name = name; byte ptext[];
String theLabel = new String(name);
try {
ptext = theLabel.getBytes("ISO-8859-1");
this.name = new String(ptext, "UTF-8");
} catch (UnsupportedEncodingException e) {
this.name = theLabel;
}
} }
public String getId() { public String getId() {
return id; return id;

View File

@@ -1,5 +1,7 @@
package com.bwssystems.harmony; package com.bwssystems.harmony;
import java.io.UnsupportedEncodingException;
import net.whistlingfish.harmony.config.Activity; import net.whistlingfish.harmony.config.Activity;
public class HarmonyActivity { public class HarmonyActivity {
@@ -15,6 +17,14 @@ public class HarmonyActivity {
return activity; return activity;
} }
public void setActivity(Activity activity) { public void setActivity(Activity activity) {
byte ptext[];
String theLabel = activity.getLabel();
try {
ptext = theLabel.getBytes("ISO-8859-1");
activity.setLabel(new String(ptext, "UTF-8"));
} catch (UnsupportedEncodingException e) {
activity.setLabel(theLabel);
}
this.activity = activity; this.activity = activity;
} }

View File

@@ -1,5 +1,7 @@
package com.bwssystems.harmony; package com.bwssystems.harmony;
import java.io.UnsupportedEncodingException;
import net.whistlingfish.harmony.config.Device; import net.whistlingfish.harmony.config.Device;
public class HarmonyDevice { public class HarmonyDevice {
@@ -9,6 +11,14 @@ public class HarmonyDevice {
return device; return device;
} }
public void setDevice(Device device) { public void setDevice(Device device) {
byte ptext[];
String theLabel = device.getLabel();
try {
ptext = theLabel.getBytes("ISO-8859-1");
device.setLabel(new String(ptext, "UTF-8"));
} catch (UnsupportedEncodingException e) {
device.setLabel(theLabel);
}
this.device = device; this.device = device;
} }
public String getHub() { public String getHub() {

View File

@@ -0,0 +1,35 @@
package com.bwssystems.hue;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
public class HueDevice {
private DeviceResponse device;
private String huedeviceid;
private String hueaddress;
private String huename;
public DeviceResponse getDevice() {
return device;
}
public void setDevice(DeviceResponse adevice) {
this.device = adevice;
}
public String getHuedeviceid() {
return huedeviceid;
}
public void setHuedeviceid(String huedeviceid) {
this.huedeviceid = huedeviceid;
}
public String getHueaddress() {
return hueaddress;
}
public void setHueaddress(String ahueaddress) {
this.hueaddress = ahueaddress;
}
public String getHuename() {
return huename;
}
public void setHuename(String ahuename) {
this.huename = ahuename;
}
}

View File

@@ -0,0 +1,18 @@
package com.bwssystems.hue;
public class HueDeviceIdentifier {
private String ipAddress;
private String deviceId;
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
}

View File

@@ -0,0 +1,5 @@
package com.bwssystems.hue;
public interface HueErrorStringSet {
public void setErrorString(String anError);
}

View File

@@ -0,0 +1,73 @@
package com.bwssystems.hue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
import com.bwssystems.HABridge.api.hue.HueApiResponse;
public class HueHome {
private static final Logger log = LoggerFactory.getLogger(HueHome.class);
private Map<String, HueInfo> hues;
private String theHUERegisteredUser;
public HueHome(BridgeSettingsDescriptor bridgeSettings) {
hues = new HashMap<String, HueInfo>();
if(!bridgeSettings.isValidHue())
return;
Iterator<NamedIP> theList = bridgeSettings.getHueaddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aHue = theList.next();
hues.put(aHue.getName(), new HueInfo(aHue, this));
}
theHUERegisteredUser = null;
}
public List<HueDevice> getDevices() {
log.debug("consolidating devices for hues");
Iterator<String> keys = hues.keySet().iterator();
ArrayList<HueDevice> deviceList = new ArrayList<HueDevice>();
while(keys.hasNext()) {
String key = keys.next();
HueApiResponse theResponse = hues.get(key).getHueApiResponse();
if(theResponse != null) {
Map<String, DeviceResponse> theDevices = theResponse.getLights();
if(theDevices != null) {
Iterator<String> deviceKeys = theDevices.keySet().iterator();
while(deviceKeys.hasNext()) {
String theDeviceKey = deviceKeys.next();
HueDevice aNewHueDevice = new HueDevice();
aNewHueDevice.setDevice(theDevices.get(theDeviceKey));
aNewHueDevice.setHuedeviceid(theDeviceKey);
aNewHueDevice.setHueaddress(hues.get(key).getHueAddress().getIp());
aNewHueDevice.setHuename(key);
deviceList.add(aNewHueDevice);
}
}
else {
deviceList = null;
break;
}
}
else
log.warn("Cannot get lights for Hue with name: " + key);
}
return deviceList;
}
public String getTheHUERegisteredUser() {
return theHUERegisteredUser;
}
public void setTheHUERegisteredUser(String theHUERegisteredUser) {
this.theHUERegisteredUser = theHUERegisteredUser;
}
}

View File

@@ -0,0 +1,111 @@
package com.bwssystems.hue;
import java.io.IOException;
import java.nio.charset.Charset;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.hue.HueApiResponse;
import com.google.gson.Gson;
public class HueInfo implements HueErrorStringSet {
private static final Logger log = LoggerFactory.getLogger(HueInfo.class);
private HttpClient httpClient;
private NamedIP hueAddress;
private String theUser;
private HueHome theHueHome;
private String errorString = null;
public HueInfo(NamedIP addressName, HueHome aHueHome) {
super();
httpClient = HttpClients.createDefault();
hueAddress = addressName;
theUser = "habridge";
theHueHome = aHueHome;
}
public HueApiResponse getHueApiResponse() {
HueApiResponse theHueApiResponse = null;
String theUrl = "http://" + hueAddress.getIp() + HueUtil.HUE_REQUEST + "/" + theUser;
String theData;
boolean loopControl = true;
int retryCount = 0;
while(loopControl) {
if(retryCount > 3) {
log.warn("Max Retry reached to get Hue data from " + hueAddress.getName());
loopControl = false;
break;
}
theUrl = "http://" + hueAddress.getIp() + HueUtil.HUE_REQUEST + "/" + theUser;
theData = doHttpGETRequest(theUrl);
if(theData != null) {
log.debug("GET HueApiResponse - data: " + theData);
if(theData.contains("[{\"error\":")) {
if(theData.contains("unauthorized user")) {
theUser = HueUtil.registerWithHue(httpClient, hueAddress.getIp(), hueAddress.getName(), theHueHome.getTheHUERegisteredUser(), this);
if(theUser == null) {
log.warn("Register to Hue for " + hueAddress.getName() + " returned error: " + errorString);
return null;
}
else
theHueHome.setTheHUERegisteredUser(theUser);
retryCount++;
}
else {
log.warn("GET HueApiResponse for " + hueAddress.getName() + " - returned error: " + theData);
return null;
}
}
else {
theHueApiResponse = new Gson().fromJson(theData, HueApiResponse.class);
log.debug("GET HueApiResponse for " + hueAddress.getName() + " - Gson parse - name: " + theHueApiResponse.getConfig().getName() + ", mac addr: " + theHueApiResponse.getConfig().getMac());
loopControl = false;
}
}
else {
log.warn("GET HueApiResponse for " + hueAddress.getName() + " - returned null, no data.");
loopControl = false;
}
}
return theHueApiResponse;
}
// This function executes the url against the vera
protected String doHttpGETRequest(String url) {
String theContent = null;
log.debug("calling GET on URL: " + url);
HttpGet httpGet = new HttpGet(url);
try {
HttpResponse response = httpClient.execute(httpGet);
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
}
} catch (IOException e) {
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
}
return theContent;
}
public NamedIP getHueAddress() {
return hueAddress;
}
public void setHueAddress(NamedIP hueAddress) {
this.hueAddress = hueAddress;
}
@Override
public void setErrorString(String anError) {
errorString = anError;
}
}

View File

@@ -0,0 +1,55 @@
package com.bwssystems.hue;
import java.io.IOException;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.api.SuccessUserResponse;
import com.bwssystems.HABridge.api.UserCreateRequest;
import com.google.gson.Gson;
public class HueUtil {
private static final Logger log = LoggerFactory.getLogger(HueUtil.class);
public static final String HUE_REQUEST = "/api";
public static final String registerWithHue(HttpClient anHttpClient, String ipAddress, String aName, String theUser, HueErrorStringSet errorStringSet) {
UserCreateRequest theLogin = new UserCreateRequest();
theLogin.setDevicetype("HABridge#MyMachine");
HttpPost postRequest = new HttpPost("http://" + ipAddress + HUE_REQUEST);
ContentType parsedContentType = ContentType.parse("application/json");
StringEntity requestBody = new StringEntity(new Gson().toJson(theLogin), parsedContentType);
HttpResponse response = null;
postRequest.setEntity(requestBody);
try {
response = anHttpClient.execute(postRequest);
log.debug("POST execute on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
String theBody = EntityUtils.toString(response.getEntity());
log.debug("registerWithHue response data: " + theBody);
if(theBody.contains("[{\"error\":")) {
if(theBody.contains("link button not")) {
log.warn("registerWithHue needs link button pressed on HUE bridge: " + aName);
}
else
log.warn("registerWithHue returned an unexpected error: " + theBody);
errorStringSet.setErrorString(theBody);
}
else {
SuccessUserResponse[] theResponses = new Gson().fromJson(theBody, SuccessUserResponse[].class); //read content for data, SuccessUserResponse[].class);
theUser = theResponses[0].getSuccess().getUsername();
}
}
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
} catch (IOException e) {
log.warn("Error logging into HUE: IOException in log", e);
}
return theUser;
}
}

View File

@@ -0,0 +1,50 @@
/*
* http://www.jrecruiter.org
*
* Disclaimer of Warranty.
*
* Unless required by applicable law or agreed to in writing, Licensor provides
* the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
* including, without limitation, any warranties or conditions of TITLE,
* NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
* solely responsible for determining the appropriateness of using or
* redistributing the Work and assume any risks associated with Your exercise of
* permissions under this License.
*
*/
package com.bwssystems.logservices;
import java.util.Date;
/**
* Log file information.
*
*/
public class LogFileInfo {
private String fileName;
private Long fileSize;
private Date fileLastChanged;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Long getFileSize() {
return fileSize;
}
public void setFileSize(Long fileSize) {
this.fileSize = fileSize;
}
public Date getFileLastChanged() {
return fileLastChanged;
}
public void setFileLastChanged(Date fileLastChanged) {
this.fileLastChanged = fileLastChanged;
}
}

View File

@@ -0,0 +1,51 @@
package com.bwssystems.logservices;
import com.bwssystems.logservices.LoggingUtil.LogLevels;
/**
* Logger information.
*
*
*/
public class LoggerInfo {
private String loggerName;
private LogLevels logLevel;
private LogLevels newLogLevel;
//~~~~~Constructors~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
*
*/
public LoggerInfo() {
super();
}
public LoggerInfo(String loggerName, int logLevelAsInt) {
super();
this.loggerName = loggerName;
this.logLevel = LogLevels.getLogLevelFromId(logLevelAsInt);
}
//~~~~~Getters and Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public String getLoggerName() {
return loggerName;
}
public void setLoggerName(String loggerName) {
this.loggerName = loggerName;
}
public LogLevels getLogLevel() {
return logLevel;
}
public void setLogLevel(LogLevels logLevel) {
this.logLevel = logLevel;
}
public LogLevels getNewLogLevel() {
return newLogLevel;
}
public void setNewLogLevel(LogLevels newLogLevel) {
this.newLogLevel = newLogLevel;
}
}

View File

@@ -0,0 +1,58 @@
package com.bwssystems.logservices;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* Form for the Logging Action.
*/
public class LoggingForm implements Serializable {
/** serialVersionUID. */
private static final long serialVersionUID = 5970927715241338665L;
/** List of Loggers, simplified for display purposes */
private List<LoggerInfo> updatedLoggers = new ArrayList<LoggerInfo>();
/** The user can enter a new logger to be configured */
private LoggerInfo newLogger;
/** Used for requesting a logfile download */
private String fileName;
//~~~~~Constructors~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public LoggingForm() {
super();
}
//~~~~~Getters and Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public List<LoggerInfo> getUpdatedLoggers() {
return updatedLoggers;
}
public void setUpdatedLoggers(List<LoggerInfo> updatedLoggers) {
this.updatedLoggers = updatedLoggers;
}
public LoggerInfo getNewLogger() {
return newLogger;
}
public void setNewLogger(LoggerInfo newLogger) {
this.newLogger = newLogger;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
}

View File

@@ -0,0 +1,163 @@
package com.bwssystems.logservices;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import com.bwssystems.logservices.LoggingUtil;
import com.bwssystems.logservices.LoggingUtil.LogLevels;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
/**
* Show log files and allow to set log levels for the configured loggers (logback)
* to be changed dynamically at runtime.
*/
public class LoggingManager {
/** Show all loggers or only the configured loggers */
private boolean showAll = false;
/** List of log files and associated information */
private List<LogFileInfo> logFileInfos = new ArrayList<LogFileInfo>();
/** List of Loggers, simplified for display purposes */
private List<LoggerInfo> configuredLoggers = new ArrayList<LoggerInfo>();
/** Used to stream a logfile back to the client */
private transient InputStream fileToDownLoad;
private LoggingForm model = new LoggingForm();
//~~~~~Reference Data~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Defines a collection of log levels. Unfortunately logback does not use
* enums to define its log levels. */
private Set<LogLevels> logLevels = EnumSet.allOf(LogLevels.class);
//~~~~~Prepare data~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void init() {
loadLoggers();
loadLogFiles();
}
//~~~~~Helper Methods~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private void loadLoggers() {
this.configuredLoggers.clear();
final List<Logger> loggers = LoggingUtil.getLoggers(this.showAll);
for (Logger logger : loggers) {
this.configuredLoggers.add(new LoggerInfo(logger.getName(), logger.getEffectiveLevel().levelInt));
}
}
private void loadLogFiles() {
this.logFileInfos = LoggingUtil.getLogFileInfos();
}
/** Updates loglevels for loggers */
public String updateLogLevels() {
if (this.model.getUpdatedLoggers() != null && !this.model.getUpdatedLoggers().isEmpty()) {
for (LoggerInfo loggerInfo : this.model.getUpdatedLoggers()) {
if (loggerInfo != null && loggerInfo.getNewLogLevel() != null) {
LoggingUtil.getLogger(loggerInfo.getLoggerName())
.setLevel(Level.toLevel(loggerInfo.getNewLogLevel().getLogLevel()));
}
}
//Need to refresh the loggers
loadLoggers();
}
return "successRedirect";
}
/** Adds a new logger at runtime. */
public String addNewLogger() {
if (this.model.getNewLogger() != null
&& this.model.getNewLogger().getLoggerName() != null
&& this.model.getNewLogger().getNewLogLevel() != null) {
final Logger newLogger = LoggingUtil.getLogger(this.model.getNewLogger().getLoggerName());
newLogger.setLevel(Level.toLevel(this.model.getNewLogger().getNewLogLevel().getLogLevel()));
//Need to refresh the loggers
loadLoggers();
}
return "successRedirect";
}
/**
* Retrieve the requested log file.
*
* @return
* @throws Exception
*/
public String download() throws Exception {
if (this.model.getFileName() == null) {
throw new IllegalArgumentException("FileName must not be null.");
}
final File logFile = LoggingUtil.getLogFile(this.model.getFileName());
if (logFile != null) {
this.fileToDownLoad = new FileInputStream(logFile);
}
return "download";
}
//~~~~~Getters and Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public List<LogFileInfo> getLogFileInfos() {
return logFileInfos;
}
public InputStream getFileToDownLoad() {
return fileToDownLoad;
}
public List<LoggerInfo> getConfiguredLoggers() {
return configuredLoggers;
}
public boolean isShowAll() {
return showAll;
}
public void setShowAll(boolean showAll) {
this.showAll = showAll;
}
public Set<LogLevels> getLogLevels() {
return logLevels;
}
public LoggingForm getModel() {
return model;
}
public void setModel(LoggingForm model) {
this.model = model;
}
}

View File

@@ -0,0 +1,198 @@
package com.bwssystems.logservices;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import com.bwssystems.logservices.LogFileInfo;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.FileAppender;
/**
* Contains utility methods to interact with the logback during runtime.
*/
public class LoggingUtil {
/**
* re-defines the logback logging levels as a Java enumeration. This is quite
* helpful if you need to render the various log-levels as select-box. I wish
* logback @see {@link ch.qos.logback.classic.Level} would not use static variables
* but use enums instead.
*/
public enum LogLevels {
OFF(Integer.MAX_VALUE, "Off"),
ERROR_INT(40000, "Error"),
WARN_INT(30000, "Warn"),
INFO_INT(20000, "Info"),
DEBUG_INT(10000, "Debug"),
TRACE(5000, "Trace"),
ALL(Integer.MIN_VALUE, "All");
private int logLevel;
private String logLevelName;
LogLevels(final int logLevel, final String logLevelName) {
this.logLevel = logLevel;
this.logLevelName = logLevelName;
}
public int getLogLevel() {
return this.logLevel;
}
public String getLogLevelName() {
return this.logLevelName;
}
public static LogLevels getLogLevelFromId(final int logLevelAsInt) {
for (LogLevels logLevel : LogLevels.values()) {
if (logLevelAsInt == logLevel.logLevel) {
return logLevel;
}
}
throw new IllegalStateException("Loglevel " + logLevelAsInt + " does not exist.");
}
@Override
public String toString() {
return this.logLevelName;
}
}
/**
* Retrieve all configured logback loggers.
*
* @param showAll If set return ALL loggers, not only the configured ones.
* @return List of Loggers
*/
public static List<ch.qos.logback.classic.Logger> getLoggers(final boolean showAll) {
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
final List<ch.qos.logback.classic.Logger> loggers = new ArrayList<ch.qos.logback.classic.Logger>();
for (ch.qos.logback.classic.Logger log : lc.getLoggerList()) {
if(showAll == false) {
if(log.getLevel() != null || LoggingUtil.hasAppenders(log)) {
loggers.add(log);
}
} else {
loggers.add(log);
}
}
return loggers;
}
/**
* Get a single logger.
*
* @return Logger
*/
public static ch.qos.logback.classic.Logger getLogger(final String loggerName) {
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
return lc.getLogger(loggerName);
}
/**
* Test whether the provided logger has appenders.
*
* @param logger The logger to test
* @return true if the logger has appenders.
*/
public static boolean hasAppenders(ch.qos.logback.classic.Logger logger) {
Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
return it.hasNext();
}
/**
* Get the logfile information for the roor logger.
*
* @return List of LogFileInfo obejcts
*/
public static List<LogFileInfo> getLogFileInfos() {
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
final List<LogFileInfo> logFileInfos = new ArrayList<LogFileInfo>();
final Logger logger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
final Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
while (it.hasNext()) {
final Appender<ILoggingEvent> appender = it.next();
if (appender instanceof FileAppender) {
final FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>) appender;
final File logFile = new File(fileAppender.getFile());
final LogFileInfo logFileInfo = new LogFileInfo();
logFileInfo.setFileName(logFile.getName());
logFileInfo.setFileLastChanged(new Date(logFile.lastModified()));
logFileInfo.setFileSize(logFile.length());
logFileInfos.add(logFileInfo);
}
}
return logFileInfos;
}
/**
* Get the log file.
*
* @param logFileName The name of the log file
* @return The actual file
*/
public static File getLogFile(final String logFileName) {
if (logFileName == null) {
throw new IllegalArgumentException("logFileName cannot be null.");
}
final LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
final Logger logger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
final Iterator<Appender<ILoggingEvent>> it = logger.iteratorForAppenders();
while (it.hasNext()) {
final Appender<ILoggingEvent> appender = it.next();
if (appender instanceof FileAppender) {
final FileAppender<ILoggingEvent> fileAppender = (FileAppender<ILoggingEvent>) appender;
final File logFile = new File(fileAppender.getFile());
if (logFile.getName().equalsIgnoreCase(logFileName)) {
return logFile;
}
}
}
return null;
}
}

View File

@@ -1,4 +1,4 @@
package com.bwssystems.HABridge; package com.bwssystems.util;
import java.io.IOException; import java.io.IOException;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;

View File

@@ -1,4 +1,4 @@
package com.bwssystems.HABridge; package com.bwssystems.util;
import com.google.gson.Gson; import com.google.gson.Gson;
import spark.ResponseTransformer; import spark.ResponseTransformer;

View File

@@ -0,0 +1,259 @@
package com.bwssystems.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
public final class TextStringFormatter {
private TextStringFormatter() {
// empty - prevent construction
}
/**
Escapes characters for text appearing as data in the
<a href='http://www.json.org/'>Javascript Object Notation</a>
(JSON) data interchange format.
<P>The following commonly used control characters are escaped :
<table border='1' cellpadding='3' cellspacing='0'>
<tr><th> Character </th><th> Escaped As </th></tr>
<tr><td> " </td><td> \" </td></tr>
<tr><td> \ </td><td> \\ </td></tr>
<tr><td> / </td><td> \/ </td></tr>
<tr><td> back space </td><td> \b </td></tr>
<tr><td> form feed </td><td> \f </td></tr>
<tr><td> line feed </td><td> \n </td></tr>
<tr><td> carriage return </td><td> \r </td></tr>
<tr><td> tab </td><td> \t </td></tr>
</table>
<P>See <a href='http://www.ietf.org/rfc/rfc4627.txt'>RFC 4627</a> for more information.
*/
public static String forJSON(String aText) {
final StringBuilder result = new StringBuilder();
StringCharacterIterator iterator = new StringCharacterIterator(aText);
char character = iterator.current();
while (character != StringCharacterIterator.DONE) {
if (character == '\"') {
result.append("\\\"");
} else if (character == '\\') {
result.append("\\\\");
} else if (character == '/') {
result.append("\\/");
} else if (character == '\b') {
result.append("\\b");
} else if (character == '\f') {
result.append("\\f");
} else if (character == '\n') {
result.append("\\n");
} else if (character == '\r') {
result.append("\\r");
} else if (character == '\t') {
result.append("\\t");
} else {
// the char is not a special one
// add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
Escape characters for text appearing in HTML markup.
<P>This method exists as a defence against Cross Site Scripting (XSS) hacks.
The idea is to neutralize control characters commonly used by scripts, such that
they will not be executed by the browser. This is done by replacing the control
characters with their escaped equivalents.
See {@link hirondelle.web4j.security.SafeText} as well.
<P>The following characters are replaced with corresponding
HTML character entities :
<table border='1' cellpadding='3' cellspacing='0'>
<tr><th> Character </th><th>Replacement</th></tr>
<tr><td> < </td><td> &lt; </td></tr>
<tr><td> > </td><td> &gt; </td></tr>
<tr><td> & </td><td> &amp; </td></tr>
<tr><td> " </td><td> &quot;</td></tr>
<tr><td> \t </td><td> &#009;</td></tr>
<tr><td> ! </td><td> &#033;</td></tr>
<tr><td> # </td><td> &#035;</td></tr>
<tr><td> $ </td><td> &#036;</td></tr>
<tr><td> % </td><td> &#037;</td></tr>
<tr><td> ' </td><td> &#039;</td></tr>
<tr><td> ( </td><td> &#040;</td></tr>
<tr><td> ) </td><td> &#041;</td></tr>
<tr><td> * </td><td> &#042;</td></tr>
<tr><td> + </td><td> &#043; </td></tr>
<tr><td> , </td><td> &#044; </td></tr>
<tr><td> - </td><td> &#045; </td></tr>
<tr><td> . </td><td> &#046; </td></tr>
<tr><td> / </td><td> &#047; </td></tr>
<tr><td> : </td><td> &#058;</td></tr>
<tr><td> ; </td><td> &#059;</td></tr>
<tr><td> = </td><td> &#061;</td></tr>
<tr><td> ? </td><td> &#063;</td></tr>
<tr><td> @ </td><td> &#064;</td></tr>
<tr><td> [ </td><td> &#091;</td></tr>
<tr><td> \ </td><td> &#092;</td></tr>
<tr><td> ] </td><td> &#093;</td></tr>
<tr><td> ^ </td><td> &#094;</td></tr>
<tr><td> _ </td><td> &#095;</td></tr>
<tr><td> ` </td><td> &#096;</td></tr>
<tr><td> { </td><td> &#123;</td></tr>
<tr><td> | </td><td> &#124;</td></tr>
<tr><td> } </td><td> &#125;</td></tr>
<tr><td> ~ </td><td> &#126;</td></tr>
</table>
<P>Note that JSTL's {@code <c:out>} escapes <em>only the first
five</em> of the above characters.
*/
public static String forHTML(String aText) {
final StringBuilder result = new StringBuilder();
final StringCharacterIterator iterator = new StringCharacterIterator(aText);
char character = iterator.current();
while (character != CharacterIterator.DONE) {
if (character == '<') {
result.append("&lt;");
} else if (character == '>') {
result.append("&gt;");
} else if (character == '&') {
result.append("&amp;");
} else if (character == '\"') {
result.append("&quot;");
} else if (character == '\t') {
addCharEntity(9, result);
} else if (character == '!') {
addCharEntity(33, result);
} else if (character == '#') {
addCharEntity(35, result);
} else if (character == '$') {
addCharEntity(36, result);
} else if (character == '%') {
addCharEntity(37, result);
} else if (character == '\'') {
addCharEntity(39, result);
} else if (character == '(') {
addCharEntity(40, result);
} else if (character == ')') {
addCharEntity(41, result);
} else if (character == '*') {
addCharEntity(42, result);
} else if (character == '+') {
addCharEntity(43, result);
} else if (character == ',') {
addCharEntity(44, result);
} else if (character == '-') {
addCharEntity(45, result);
} else if (character == '.') {
addCharEntity(46, result);
} else if (character == '/') {
addCharEntity(47, result);
} else if (character == ':') {
addCharEntity(58, result);
} else if (character == ';') {
addCharEntity(59, result);
} else if (character == '=') {
addCharEntity(61, result);
} else if (character == '?') {
addCharEntity(63, result);
} else if (character == '@') {
addCharEntity(64, result);
} else if (character == '[') {
addCharEntity(91, result);
} else if (character == '\\') {
addCharEntity(92, result);
} else if (character == ']') {
addCharEntity(93, result);
} else if (character == '^') {
addCharEntity(94, result);
} else if (character == '_') {
addCharEntity(95, result);
} else if (character == '`') {
addCharEntity(96, result);
} else if (character == '{') {
addCharEntity(123, result);
} else if (character == '|') {
addCharEntity(124, result);
} else if (character == '}') {
addCharEntity(125, result);
} else if (character == '~') {
addCharEntity(126, result);
} else {
// the char is not a special one
// add it to the result as is
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
* Escape all ampersand characters in a URL.
*
* <P>
* Replaces all <tt>'&'</tt> characters with <tt>'&amp;'</tt>.
*
* <P>
* An ampersand character may appear in the query string of a URL. The
* ampersand character is indeed valid in a URL.
* <em>However, URLs usually appear as an <tt>HREF</tt> attribute, and such
* attributes have the additional constraint that ampersands must be
* escaped.</em>
*
* <P>
* The JSTL <c:url> tag does indeed perform proper URL encoding of query
* parameters. But it does not, in general, produce text which is valid as
* an <tt>HREF</tt> attribute, simply because it does not escape the
* ampersand character. This is a nuisance when multiple query parameters
* appear in the URL, since it requires a little extra work.
*/
public static String forHrefAmpersand(String aURL) {
return aURL.replace("&", "&amp;");
}
public static String forQuerySpace(String aURL) {
return aURL.replace(" ", "\u0020");
}
/**
* Synonym for <tt>URLEncoder.encode(String, "UTF-8")</tt>.
*
* <P>
* Used to ensure that HTTP query strings are in proper form, by escaping
* special characters such as spaces.
*
* <P>
* It is important to note that if a query string appears in an
* <tt>HREF</tt> attribute, then there are two issues - ensuring the query
* string is valid HTTP (it is URL-encoded), and ensuring it is valid HTML
* (ensuring the ampersand is escaped).
*/
public static String forURL(String aURLFragment) {
String result = null;
try {
result = URLEncoder.encode(aURLFragment, "UTF-8");
} catch (UnsupportedEncodingException ex) {
throw new RuntimeException("UTF-8 not supported", ex);
}
return result;
}
private static void addCharEntity(Integer aIdx, StringBuilder aBuilder) {
String padding = "";
if (aIdx <= 9) {
padding = "00";
} else if (aIdx <= 99) {
padding = "0";
} else {
// no prefix
}
String number = padding + aIdx.toString();
aBuilder.append("&#" + number + ";");
}
}

View File

@@ -26,7 +26,7 @@ public class VeraHome {
Iterator<NamedIP> theList = bridgeSettings.getVeraAddress().getDevices().iterator(); Iterator<NamedIP> theList = bridgeSettings.getVeraAddress().getDevices().iterator();
while(theList.hasNext()) { while(theList.hasNext()) {
NamedIP aVera = theList.next(); NamedIP aVera = theList.next();
veras.put(aVera.getName(), new VeraInfo(aVera, bridgeSettings.isValidVera())); veras.put(aVera.getName(), new VeraInfo(aVera));
} }
} }

View File

@@ -1,6 +1,7 @@
package com.bwssystems.vera; package com.bwssystems.vera;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map; import java.util.Map;
@@ -27,19 +28,15 @@ public class VeraInfo {
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 NamedIP veraAddress; private NamedIP veraAddress;
private Boolean validVera;
public VeraInfo(NamedIP addressName, Boolean isValidVera) { public VeraInfo(NamedIP addressName) {
super(); super();
httpClient = HttpClients.createDefault(); httpClient = HttpClients.createDefault();
veraAddress = addressName; veraAddress = addressName;
validVera = isValidVera;
} }
public Sdata getSdata() { public Sdata getSdata() {
Sdata theSdata = null; Sdata theSdata = null;
if(!validVera)
return theSdata;
String theUrl = "http://" + veraAddress.getIp() + SDATA_REQUEST; String theUrl = "http://" + veraAddress.getIp() + SDATA_REQUEST;
String theData; String theData;
@@ -101,7 +98,7 @@ public class VeraInfo {
HttpResponse response = httpClient.execute(httpGet); HttpResponse response = httpClient.execute(httpGet);
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode()); log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){ if(response.getStatusLine().getStatusCode() == 200){
theContent = EntityUtils.toString(response.getEntity()); //read content for data theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
} }
} catch (IOException e) { } catch (IOException e) {

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<appender name="CYCLIC" class="ch.qos.logback.core.read.CyclicBufferAppender">
<MaxSize>512</MaxSize>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="CYCLIC" />
</root>
</configuration>

View File

@@ -0,0 +1,127 @@
.scrollableContainer {
height: 800px;
position: relative;
padding-top: 35px;
overflow: hidden;
}
.scrollableContainer .headerSpacer {
border: 1px solid #d5d5d5;
border-bottom-color: #bbb;
position: absolute;
height: 36px;
top: 0;
right: 0;
left: 0;
}
.scrollableContainer th .orderWrapper {
position: absolute;
top: 0;
right: 2px;
cursor: pointer;
}
.scrollableContainer th .orderWrapper .order {
font-size: 8pt;
color: #BDBDBD;
}
.scrollableContainer th .orderWrapper .active {
color: #464646;
}
.scrollArea {
height: 100%;
overflow-x: auto;
overflow-y: auto;
border: 1px solid #d5d5d5;
/* the implementation of this is still quite buggy; specifically, it doesn't like the
absolutely positioned headers within
-webkit-overflow-scrolling: touch; */
}
.scrollArea table {
overflow-x: hidden;
overflow-y: auto;
margin-bottom: 0;
width: 100%;
border: none;
/*border-collapse: separate;*/
}
.scrollArea table th {
padding: 0 !important;
border: none !important;
min-width: 60px;
}
.scrollArea table .th-inner {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: absolute;
top: 0;
height: 36px;
line-height: 36px;
}
.scrollArea table th .box {
padding: 0 8px;
padding-right: 11px; /* order icon width*/
border-left: 1px solid #ddd;
}
/* to hack fix firefox border issue */
@-moz-document url-prefix() {
.scrollArea table th .box{
border-right: 1px solid #ddd;
border-left: none;
}
}
.scrollArea table .th-inner .ng-scope {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
.scrollArea table tr th:first-child th .box {
border-left: none;
}
.scrollArea table .th-inner.condensed {
padding: 0 3px;
}
.scrollArea table tbody tr td:first-child {
border-left: none;
}
.scrollArea table tbody tr td:last-child {
border-right: none;
}
.scrollArea table tbody tr:first-child td {
border-top: none;
}
.scrollArea table tbody tr:last-child td {
border-bottom: 2px solid #ddd;
}
.scrollArea table tbody tr td {
border-bottom: 1px solid #ddd;
overflow: hidden;
text-overflow: ellipsis;
}
.scrollableContainer .scaler {
position: absolute;
top: 0px;
width: 2px;
height: 100%;
background-color: #CFCFCF;
}
.scrollableContainer th .resize-rod {
position: absolute;
top: 0;
right: 0;
cursor: col-resize;
width: 4px;
height: 100%;
}
.scrollable-resizing .scrollableContainer {
cursor: col-resize;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}

View File

@@ -12,6 +12,7 @@
<link href="css/rzslider.min.css" rel="stylesheet"> <link href="css/rzslider.min.css" rel="stylesheet">
<link href="css/ngDialog.min.css" rel="stylesheet"> <link href="css/ngDialog.min.css" rel="stylesheet">
<link href="css/ngDialog-theme-default.min.css" rel="stylesheet"> <link href="css/ngDialog-theme-default.min.css" rel="stylesheet">
<link href="css/scrollable-table.css" rel="stylesheet">
<!--[if lt IE 9]> <!--[if lt IE 9]>
<script type="text/javascript" src="js/html5shiv.min.js"></script> <script type="text/javascript" src="js/html5shiv.min.js"></script>
@@ -59,10 +60,11 @@
<script src="js/angular.min.js"></script> <script src="js/angular.min.js"></script>
<script src="js/angular-route.min.js"></script> <script src="js/angular-route.min.js"></script>
<script src="js/angular-sanitize.min.js"></script> <script src="js/angular-sanitize.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/ngToast.min.js"></script> <script src="js/ngToast.min.js"></script>
<script src="js/rzslider.min.js"></script> <script src="js/rzslider.min.js"></script>
<script src="js/rzslider.min.js"></script>
<script src="js/ngDialog.min.js"></script> <script src="js/ngDialog.min.js"></script>
<script src="js/angular-scrollable-table.min.js"></script>
<script src="scripts/app.js"></script> <script src="scripts/app.js"></script>
</body> </body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#">Bridge Devices</a></li> <li role="presentation" class="active"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</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,26 +15,19 @@
<div class="panel-heading"> <div class="panel-heading">
<h2 class="panel-title">Current devices ({{bridge.devices.length}}) </h2> <h2 class="panel-title">Current devices ({{bridge.devices.length}}) </h2>
</div> </div>
<scrollable-table watch="bridge.devices">
<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>Row</th>
<th> <th sortable-header col="id">ID</th>
<a href="" ng-click="order('id')">ID</a> <th sortable-header col="name">Name</th>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span></th> <th sortable-header col="deviceType">Type</th>
<th> <th sortable-header col="targetDevice">Target</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('deviceType')">Type</a>
<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">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{device.id}}</td> <td>{{device.id}}</td>
<td>{{device.name}}</td> <td>{{device.name}}</td>
@@ -42,6 +37,8 @@
<p> <p>
<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>
<button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'dim')">Test Dim</button>
<button class="btn btn-info" type="submit" <button class="btn btn-info" type="submit"
ng-click="testUrl(device, 'off')">Test OFF</button> ng-click="testUrl(device, 'off')">Test OFF</button>
<button class="btn btn-warning" type="submit" <button class="btn btn-warning" type="submit"
@@ -52,6 +49,7 @@
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</div> </div>
<div class="panel panel-default backup"> <div class="panel panel-default backup">
<div class="panel-heading"> <div class="panel-heading">

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</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>
@@ -42,6 +44,28 @@
<button class="btn btn-primary" ng-click="copyDevice()"> <button class="btn btn-primary" ng-click="copyDevice()">
Add Bridge Device</button> Add Bridge Device</button>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-type">Device Type
</label>
<div class="col-xs-8 col-sm-7">
<select name="device-type" id="device-type" ng-model="device.deviceType">
<option value="">---Types if needed---</option> <!-- not selected / blank option -->
<option value="custom">Custom</option>
<option value="UDP">UDP</option>
<option value="TCP">TCP</option>
<option value="exec">Execute Script/Program</option>
<option value="switch">Switch</option>
<option value="scene">Scene</option>
<option value="activity">Activity</option>
<option value="button">Button</option>
<option value="thermo">Thermo</option>
<option value="passthru">Pass Thru</option>
</select>
</div>
</div>
</div>
<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-map-type">Map Type <label class="col-xs-12 col-sm-2 control-label" for="device-map-type">Map Type
@@ -56,6 +80,7 @@
<option value="harmonyButton">Harmony Button</option> <option value="harmonyButton">Harmony Button</option>
<option value="nestHomeAway">Nest Home Status</option> <option value="nestHomeAway">Nest Home Status</option>
<option value="nestThermoSet">Nest Thermostat</option> <option value="nestThermoSet">Nest Thermostat</option>
<option value="hueDevice">Hue Device</option>
</select> </select>
</div> </div>
<button class="btn btn-danger" ng-click="clearDevice()"> <button class="btn btn-danger" ng-click="clearDevice()">
@@ -82,6 +107,17 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-dim-url">Dim
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
<label class="col-xs-12 col-sm-2 control-label" <label class="col-xs-12 col-sm-2 control-label"
@@ -93,6 +129,17 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-headers">HTTP Headers </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-headers"
ng-model="device.headers" placeholder="format like: [{&quot;name&quot;:&quot;A name&quot;,&quot;value&quot;:&quot;a value&quot;}]"></textarea>
</div>
</div>
</div>
<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-http-verb">Http Verb <label class="col-xs-12 col-sm-2 control-label" for="device-http-verb">Http Verb

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</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>
@@ -76,7 +78,10 @@
<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 <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> the http verb type below and configure a payload for either on, dim or off methods. Currently, https is not supported. For Execution of
a script or program, plese fill in the path. All manually entered calls can use Json notation of array with
[{&quot;item&quot;:&quot;the payload&quot;},{&quot;item&quot;:&quot;another payload&quot;}] to execute multiple entries. Adding the value replacements (${intensity..byte},${intensity.percent},${intensity.math(X*1)})
will also work.</p>
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<form class="form-horizontal"> <form class="form-horizontal">
@@ -93,6 +98,22 @@
Add Bridge Device</button> Add Bridge Device</button>
</div> </div>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-type">Device Type
</label>
<div class="col-xs-8 col-sm-7">
<select name="device-type" id="device-type" ng-model="device.deviceType">
<option value="">---Types if needed---</option> <!-- not selected / blank option -->
<option value="custom">Custom</option>
<option value="UDP">UDP</option>
<option value="TCP">TCP</option>
<option value="exec">Execute Script/Program</option>
</select>
</div>
</div>
</div>
<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-on-url">On <label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
@@ -106,6 +127,17 @@
Clear Device</button> Clear Device</button>
</div> </div>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-dim-url">Dim
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
<label class="col-xs-12 col-sm-2 control-label" <label class="col-xs-12 col-sm-2 control-label"
@@ -117,6 +149,17 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label"
for="device-headers">HTTP Headers </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-headers"
ng-model="device.headers" placeholder="format like: [{&quot;name&quot;:&quot;A name&quot;,&quot;value&quot;:&quot;a value&quot;}]"></textarea>
</div>
</div>
</div>
<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-http-verb">Http Verb <label class="col-xs-12 col-sm-2 control-label" for="device-http-verb">Http Verb

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -19,37 +21,29 @@
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. 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> The 'Already Configured Activities' list below will show what is already setup for your Harmony Hubs.</p>
<scrollable-table watch="bridge.harmonyactivities">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('label')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="hub">Hub</th>
</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('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableHarmonyActivityId | orderBy:predicate:reverse"> <tr ng-repeat="harmonyactivity in bridge.harmonyactivities | availableHarmonyActivityId">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{harmonyactivity.activity.label}}</td> <td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td> <td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</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 Bridge Device</button>
Activity URLs</button>
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
@@ -57,26 +51,18 @@
</div> </div>
<ul ng-if="buttonsVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<scrollable-table watch="bridge.harmonyactivities">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('label')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="hub">Hub</th>
</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('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableHarmonyActivityId | orderBy:predicate:reverse"> <tr ng-repeat="harmonyactivity in bridge.harmonyactivities | unavailableHarmonyActivityId">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{harmonyactivity.activity.label}}</td> <td>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td> <td>{{harmonyactivity.activity.id}}</td>
@@ -85,6 +71,7 @@
ng-click="deleteDeviceByMapId(harmonyactivity.activity.id, 'harmonyActivity')">Delete</button></td> ng-click="deleteDeviceByMapId(harmonyactivity.activity.id, 'harmonyActivity')">Delete</button></td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -20,28 +22,20 @@
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. 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> The 'Already Configured Harmony Buttons' list below will show what is already setup for your Harmony Hubs.</p>
<scrollable-table watch="bridge.harmonydevices">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('label')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="hub">Hub</th>
</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('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th>On Button</th> <th>On Button</th>
<th>Off Button</th> <th>Off Button</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="harmonydevice in bridge.harmonydevices | orderBy:predicate:reverse"> <tr ng-repeat="harmonydevice in bridge.harmonydevices">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{harmonydevice.device.label}}</td> <td>{{harmonydevice.device.label}}</td>
<td>{{harmonydevice.device.id}}</td> <td>{{harmonydevice.device.id}}</td>
@@ -66,6 +60,7 @@
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
@@ -73,26 +68,15 @@
</div> </div>
<ul ng-if="buttonsVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<scrollable-table watch="bridge.harmonydevices">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="id">Device Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="targetDevice">Hub</th>
</th> <th>Harmony Device-Button On-Button Off</th>
<th>
<a href="" ng-click="order('id')">Device Id</a>
<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>
<a href="" ng-click="order('mapId')">Harmony Device-Button On-Button Off</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -108,6 +92,7 @@
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -0,0 +1,119 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li role="presentation" class="active"><a href="#/huedevices">Hue Devices</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">Hue Device List ({{bridge.huedevices.length}})</h2>
</div>
<ul class="list-group">
<li class="list-group-item">
<p class="text-muted">For any Hue Device, use the action buttons to generate the device addition information below automatically.
Then you can modify the name to anything you want that will be the keyword for Alexa. Click the 'Add Bridge Device' to finish that selection setup.
The 'Already Configured Hue Devices' list below will show what is already setup for your Hue.</p>
<p>Use the check boxes by the names to use the bulk addition feature. Select your items, then click bulk add below.
Your items will be added with on and off or dim and off if selected with the name of the device from the Hue.
</p>
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="huename">Hue</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="huedevice in bridge.huedevices | availableHueDeviceId">
<td>{{$index+1}}</td>
<td><input type="checkbox" name="bulk.devices[]" value="{{huedevice.id}}" ng-checked="bulk.devices.indexOf(huedevice.id) > -1" ng-click="toggleSelection(huedevice.id)"> {{huedevice.device.name}}</td>
<td>{{huedevice.device.uniqueid}}</td>
<td>{{huedevice.huename}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(huedevice)">Generate Bridge Device</button>
</td>
</tr>
</table>
</scrollable-table>
<p>
<button class="btn btn-success" type="submit"
ng-click="bulkHueDevices()">Bulk Add ({{bulk.devices.length}})</button>
</p>
</li>
</ul>
<div class="panel-heading">
<h2 class="panel-title">Already Configured Hue Devices <a ng-click="toggleButtons()"><span class={{imgButtonsUrl}} aria-hidden="true"></span></a></a></h2>
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<scrollable-table watch="bridge.huedevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="huename">hue</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="huedevice in bridge.huedevices | unavailableHueDeviceId">
<td>{{$index+1}}</td>
<td>{{huedevice.device.name}}</td>
<td>{{huedevice.device.uniqueid}}</td>
<td>{{huedevice.huename}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="deleteDeviceByMapId(huedevice.uniqueid, 'hueDevice')">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading">
<h2 class="panel-title">Add Bridge Device for a Hue Device</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>
</form>
</li>
</ul>
</div>

View File

@@ -0,0 +1,76 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation" class="active"><a href="#/logs">Logs</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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul>
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h1 class="panel-title">Log Messages</h1>
</div>
<div class="panel-body">
<p>
<button class="btn btn-primary" type="submit"
ng-click="updateLogs()">Update Log</button>
</p>
<scrollable-table watch="bridge.logMsgs">
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th sortable-header col="time">Time</th>
<th sortable-header col="level">Level</th>
<th sortable-header col="message">Message</th>
<th sortable-header col="component">Component</th>
</tr>
</thead>
<tr ng-repeat="logMessage in bridge.logMsgs">
<td>{{logMessage.time}}</td>
<td>{{logMessage.level}}</td>
<td>{{logMessage.message}}</td>
<td>{{logMessage.component}}</td>
</tr>
</table>
</scrollable-table>
</div>
</div>
<div class="panel panel-default logconfig">
<div class="panel-heading">
<h1 class="panel-title">Logging Configuration <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1>
</div>
<div ng-if="visible" class="animate-if" class="panel-body">
<p>
<button class="btn btn-primary" type="submit"
ng-click="updateLoggers()">Update Log Levels</button>
Show All Loggers <input type="checkbox" ng-model="bridge.logShowAll"
ng-change="reloadLoggers()" ng-true-value=true ng-false-value=false> {{bridge.logShowAll}}
</p>
<scrollable-table watch="bridge.loggerInfo">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th sortable-header col="logLevel">Log Level</th>
<th sortable-header col="loggerName">Component</th>
<th>New Log Level</th>
</tr>
</thead>
<tr ng-repeat="logInfo in bridge.loggerInfo">
<td>{{logInfo.logLevel.substr(0,logInfo.logLevel.indexOf("_"))}}</td>
<td>{{logInfo.loggerName}}</td>
<td>
<select name="new-log-level" id="new-log-level" ng-change="addToUpdate(logInfo)" ng-model="logInfo.newLogLevel">
<option ng-repeat="alevel in levels" value="{{alevel}}">{{alevel.substr(0,alevel.indexOf("_"))}}</option>
</select>
</td>
</tr>
</table>
</scrollable-table>
</div>
</div>

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 role="presentation" class="active"><a href="#/nest">Nest</a></li> <li role="presentation" class="active"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -19,22 +21,14 @@
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. 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> The 'Already Configured Nest Items' list below will show what is already setup for your Nest.</p>
<scrollable-table watch="bridge.nestitems">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="type">Type</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="location">Location</th>
</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> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -71,6 +65,7 @@
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
@@ -78,22 +73,14 @@
</div> </div>
<ul ng-if="buttonsVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<scrollable-table watch="bridge.nestitems">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="id">Device Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th>mapId</th>
</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> <th>Actions</th>
</tr> </tr>
</thead> </thead>
@@ -106,6 +93,7 @@
ng-click="deleteDeviceByMapId(device.mapId, 'nest')">Delete</button></td> ng-click="deleteDeviceByMapId(device.mapId, 'nest')">Delete</button></td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
</div> </div>

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation" class="active"><a href="#/system">Bridge Control</a></li> <li role="presentation" class="active"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</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="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -25,9 +27,7 @@
ng-model="bridge.base" placeholder="URL to bridge"> ng-model="bridge.base" placeholder="URL to bridge">
</div> </div>
<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="goBridgeUrl(bridge.base)">Test</button>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="goBridgeUrl(bridge.base)">Go</button>
</div> </div>
</form> </form>
<form name="form"> <form name="form">
@@ -143,9 +143,31 @@
ng-model="bridge.settings.harmonypwd" placeholder="thepassword"></td> ng-model="bridge.settings.harmonypwd" placeholder="thepassword"></td>
</tr> </tr>
<tr> <tr>
<td>Button Press Sleep Interval (ms)</td> <td>Hue Names and IP Addresses</td>
<td><input id="bridge-settings-buttonsleep" class="form-control" type="number" name="input" <td><table class="table table-bordered table-striped table-hover">
ng-model="bridge.settings.buttonsleep" min="100" max="9999"></td> <thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="hue in bridge.settings.hueaddress.devices">
<td>{{hue.name}}</td>
<td>{{hue.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHuetoSettings(hue.name, hue.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-hue-name" class="form-control" type="text"
ng-model="newhuename" placeholder="A Hue"></td>
<td><input id="bridge-settings-next-hue-ip" class="form-control" type="text"
ng-model="newhueip" placeholder="192.168.1.3"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHuetoSettings(newhuename, newhueip)">Add</button></td>
</tr>
</table>
</td>
</tr> </tr>
<tr> <tr>
<td>Nest Username</td> <td>Nest Username</td>
@@ -157,6 +179,21 @@
<td><input id="bridge-settings-nestpwd" class="form-control" type="password" <td><input id="bridge-settings-nestpwd" class="form-control" type="password"
ng-model="bridge.settings.nestpwd" placeholder="thepassword"></td> ng-model="bridge.settings.nestpwd" placeholder="thepassword"></td>
</tr> </tr>
<tr>
<td>Nest Temp Farenheit</td>
<td><input type="checkbox" ng-model="bridge.settings.farenheit"
ng-true-value=true ng-false-value=false> {{bridge.settings.farenheit}}</td>
</tr>
<tr>
<td>Button Press/Call Item Loop Sleep Interval (ms)</td>
<td><input id="bridge-settings-buttonsleep" class="form-control" type="number" name="input"
ng-model="bridge.settings.buttonsleep" min="100" max="9999"></td>
</tr>
<tr>
<td>Log Messages to Buffer</td>
<td><input id="bridge-settings-numberoflogmessages" class="form-control" type="number"
ng-model="bridge.settings.numberoflogmessages" min="100" max="65535"></td>
</tr>
<tr> <tr>
<td>UPNP Strict Handling</td> <td>UPNP Strict Handling</td>
<td><input type="checkbox" ng-model="bridge.settings.upnpstrict" <td><input type="checkbox" ng-model="bridge.settings.upnpstrict"
@@ -173,9 +210,9 @@
</div> </div>
<div class="panel panel-default backup"> <div class="panel panel-default backup">
<div class="panel-heading"> <div class="panel-heading">
<h1 class="panel-title">Bridge Settings Backup <a ng-click="toggleBk()"><span class={{imgBkUrl}} aria-hidden="true"></a></h1> <h1 class="panel-title">Bridge Settings Backup <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1>
</div> </div>
<div ng-if="visibleBk" 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-2 control-label" for="backup-name">Backup Settings File Name</label> <label class="col-xs-12 col-sm-2 control-label" for="backup-name">Backup Settings File Name</label>

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</a></li>
<li role="presentation" class="active"><a href="#/veradevices">Vera Devices</a></li> <li role="presentation" class="active"><a href="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -30,34 +32,20 @@
<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. <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. 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> </p>
<scrollable-table watch="bridge.veradevices">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="category">Category</th>
</th> <th sortable-header col="room">Room</th>
<th> <th sortable-header col="veraname">Vera</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('category')">Category</a>
<span class="sortorder" ng-show="predicate === 'category'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<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>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="veradevice in bridge.veradevices | availableVeraDeviceId | orderBy:predicate:reverse"> <tr ng-repeat="veradevice in bridge.veradevices | availableVeraDeviceId">
<td>{{$index+1}}</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><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>
@@ -66,11 +54,11 @@
<td>{{veradevice.veraname}}</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 Bridge Device</button>
Device URLs</button>
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
<p> <p>
<button class="btn btn-success" type="submit" <button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add ({{bulk.devices.length}})</button> ng-click="bulkAddDevices(device_dim_control)">Bulk Add ({{bulk.devices.length}})</button>
@@ -82,34 +70,20 @@
</div> </div>
<ul ng-if="buttonsVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<scrollable-table watch="bridge.veradevices">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="category">Category</th>
</th> <th sortable-header col="room">Room</th>
<th> <th sortable-header col="veraname">Vera</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('category')">Category</a>
<span class="sortorder" ng-show="predicate === 'category'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('room')">Room</a>
<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>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="veradevice in bridge.veradevices | unavailableVeraDeviceId | orderBy:predicate:reverse"> <tr ng-repeat="veradevice in bridge.veradevices | unavailableVeraDeviceId">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{veradevice.name}}</td> <td>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td> <td>{{veradevice.id}}</td>
@@ -122,6 +96,7 @@
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
</div> </div>
@@ -155,6 +130,17 @@
<button class="btn btn-danger" ng-click="clearDevice()"> <button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button> Clear Device</button>
</div> </div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-dim-url">Dim
URL </label>
<div class="col-xs-8 col-sm-7">
<textarea rows="3" class="form-control" id="device-dim-url"
ng-model="device.dimUrl" placeholder="URL to dim device"></textarea>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="row"> <div class="row">
<label class="col-xs-12 col-sm-2 control-label" <label class="col-xs-12 col-sm-2 control-label"

View File

@@ -1,11 +1,13 @@
<ul class="nav nav-pills" role="tablist"> <ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</a></li> <li role="presentation"><a href="#">Bridge Devices</a></li>
<li role="presentation"><a href="#/system">Bridge Control</a></li> <li role="presentation"><a href="#/system">Bridge Control</a></li>
<li role="presentation"><a href="#/logs">Logs</a></li>
<li role="presentation"><a href="#/veradevices">Vera Devices</a></li> <li role="presentation"><a href="#/veradevices">Vera Devices</a></li>
<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 ng-if="bridge.showNest" role="presentation"><a href="#/nest">Nest</a></li>
<li ng-if="bridge.showHue" role="presentation"><a href="#/huedevices">Hue Devices</a></li>
<li role="presentation"><a href="#/editor">Manual Add</a></li> <li role="presentation"><a href="#/editor">Manual Add</a></li>
</ul> </ul>
@@ -19,30 +21,19 @@
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. 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> The 'Already Configured Vera Scenes' list below will show what is already setup for your Vera.</p>
<scrollable-table watch="bridge.verascenes">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="room">Room</th>
</th> <th sortable-header col="veraname">Vera</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('room')">Room</a>
<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>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="verascene in bridge.verascenes | availableVeraSceneId | orderBy:predicate:reverse"> <tr ng-repeat="verascene in bridge.verascenes | availableVeraSceneId">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{verascene.name}}</td> <td>{{verascene.name}}</td>
<td>{{verascene.id}}</td> <td>{{verascene.id}}</td>
@@ -50,11 +41,11 @@
<td>{{verascene.veraname}}</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 Bridge Device</button>
Scene URLs</button>
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
<div class="panel-heading"> <div class="panel-heading">
@@ -62,30 +53,19 @@
</div> </div>
<ul ng-if="buttonsVisible" class="list-group"> <ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item"> <li class="list-group-item">
<scrollable-table watch="bridge.verascenes">
<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>Row</th>
<th> <th sortable-header col="name">Name</th>
<a href="" ng-click="order('name')">Name</a> <th sortable-header col="id">Id</th>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> <th sortable-header col="room">Room</th>
</th> <th sortable-header col="veraname">Vera</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('room')">Room</a>
<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>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
<tr ng-repeat="verascene in bridge.verascenes | unavailableVeraSceneId | orderBy:predicate:reverse"> <tr ng-repeat="verascene in bridge.verascenes | unavailableVeraSceneId">
<td>{{$index+1}}</td> <td>{{$index+1}}</td>
<td>{{verascene.name}}</td> <td>{{verascene.name}}</td>
<td>{{verascene.id}}</td> <td>{{verascene.id}}</td>
@@ -97,6 +77,7 @@
</td> </td>
</tr> </tr>
</table> </table>
</scrollable-table>
</li> </li>
</ul> </ul>
</div> </div>