Compare commits

...

68 Commits

Author SHA1 Message Date
bwssystems
51ce10cfc7 Fixed build device button in Harmony Device helper tab.
Fixes #116
2016-05-15 12:25:28 -05:00
Admin
b5f7144c9c Update readme with a script construction. 2016-05-11 14:36:58 -05:00
Admin
86a931d383 added exec:// type for loops. updated button maptypeid naming.
Fixes #114
Fixes #115
2016-05-11 12:23:43 -05:00
Admin
3e890721c5 Fixed handling of dim and brigthen requests as they were not setting the
state appropriately for other systems. Fixed handling of data return
from an http call if the entity was empty.

Fixes #102
Fixes #107
2016-05-10 12:29:37 -05:00
Admin
62d1c64a3d Updated entitiy handling in HueMulator http call. 2016-05-06 08:30:37 -05:00
Admin
c025b186cd Updated a comment for the seb server port 2016-05-05 14:13:06 -05:00
Admin
e999c3a969 Update spelling in readme 2016-05-05 10:45:03 -05:00
Admin
351403e611 Fixed success parsing on http to return success. 2016-05-04 14:38:37 -05:00
Admin
c773477a43 Added delete dialog for confirmation. Fixed bug with text in
intensity.byte for vera device tab. Fixed issue with parsing replies for
http requests. Fixed Hue device bulk add. recommit


Fixes #100
Fixes #102
Fixes #104
Fixes #105
2016-05-04 11:45:51 -05:00
BWS Systems
5d1f0ce3b6 update versions in unit file 2016-05-02 15:41:01 -05:00
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
Admin
77d3084b01 Finished slider for testint items with percentage items. Many refactors
and fixes.

Fixes #43
Fixes #46
Fixes #47
Fixes #48
2016-02-19 16:34:04 -06:00
Admin
922bb54143 add js and css dialog files 2016-02-18 16:40:25 -06:00
Admin
8586bbd965 Found slider and popup dialog. 2016-02-18 16:39:41 -06:00
Admin
d64a028f30 removed slider.js and .css changing to a nicer slider 2016-02-18 08:49:49 -06:00
Admin
fff30d17d6 Finished updating settings editor and reinit control. Starting number
slider for input.
2016-02-17 16:41:34 -06:00
Admin
13fa5dea73 update ui components. 2016-02-16 16:53:01 -06:00
Admin
1d6a4c1432 Updated UI for configuration and added shutdown of harmony and nest
connections.
2016-02-16 16:52:12 -06:00
bwssystems
9cb275230e Updating editing for Settings UI. 2016-02-13 17:02:51 -06:00
Admin
c97ab2cd38 Fixed bulk add race conditions. added save capability into UI. UI
settings layout update.
2016-02-12 15:51:38 -06:00
Admin
7b45ca9438 added class backup handler to git 2016-02-11 16:52:08 -06:00
Admin
e6da9950d6 Added config settings page and refactored backup handling and bridge
settings and cotntrol. Next is to add the editing to the config screen.
2016-02-11 16:51:11 -06:00
Admin
20328b15d8 Added config file handling for the next step in config screens. 2016-02-10 16:47:58 -06:00
Admin
2ff73e5672 Added reinitialize and stop commands in prep for building the config
file handling.
2016-02-09 16:41:05 -06:00
Admin
cca9a6be78 Finished button sleep time addition. Updated documentation for
button.sleep and corrected the device DB line and updated ip list
directives for vera.address and harmony.address.

Fixes #39
Fixes #40
Fixes #42
2016-02-08 16:21:34 -06:00
Admin
5d5b68209a Updated test notifcation to a toast, added config button sleep time. 2016-02-05 16:10:47 -06:00
Admin
d2e906caa3 Fixed variable mis name in apps.js for olddevicename 2016-02-03 14:32:33 -06:00
Admin
a1708f2a88 Vera device and scene creation testing and added Copy capability to add
an already configured device with a different name.

Fixes #32
Fixes #38
2016-02-02 12:04:48 -06:00
Admin
49c3b85894 Fixed the vera device and scene build. 2016-02-01 16:34:04 -06:00
Admin
f9f5a3a878 Updated code in the html and javascript to select the coorect name for
the button press command. Fixed the calculation of celsius conversion
for setting nest temperatures.

Fixes #33
Fixes #34
2016-01-29 13:49:34 -06:00
70 changed files with 5808 additions and 1892 deletions

390
README.md

File diff suppressed because one or more lines are too long

40
pom.xml
View File

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

View File

@@ -0,0 +1,25 @@
package com.bwssystems.HABridge;
public class BridgeControlDescriptor {
private boolean reinit;
private boolean stop;
public BridgeControlDescriptor() {
super();
this.reinit = false;
this.stop = false;
}
public boolean isReinit() {
return reinit;
}
public void setReinit(boolean reinit) {
this.reinit = reinit;
}
public boolean isStop() {
return stop;
}
public void setStop(boolean stop) {
this.stop = stop;
}
}

View File

@@ -1,135 +1,255 @@
package com.bwssystems.HABridge;
import java.util.List;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Enumeration;
public class BridgeSettings {
private String upnpconfigaddress;
private String serverport;
private String upnpresponseport;
private String upnpdevicedb;
private IpList veraaddress;
private IpList harmonyaddress;
private String harmonyuser;
private String harmonypwd;
private Integer upnpresponsedevices;
private boolean upnpstrict;
private boolean traceupnp;
private boolean devmode;
private String nestuser;
private String nestpwd;
private boolean nestconfigured;
import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public String getUpnpConfigAddress() {
return upnpconfigaddress;
import com.bwssystems.util.BackupHandler;
import com.bwssystems.util.JsonTransformer;
import com.google.gson.Gson;
public class BridgeSettings extends BackupHandler {
private BridgeSettingsDescriptor theBridgeSettings;
private BridgeControlDescriptor bridgeControl;
public BridgeSettings() {
super();
bridgeControl = new BridgeControlDescriptor();
theBridgeSettings = new BridgeSettingsDescriptor();
}
public void setUpnpConfigAddress(String upnpConfigAddress) {
this.upnpconfigaddress = upnpConfigAddress;
public BridgeControlDescriptor getBridgeControl() {
return bridgeControl;
}
public String getServerPort() {
return serverport;
public BridgeSettingsDescriptor getBridgeSettingsDescriptor() {
return theBridgeSettings;
}
public void setServerPort(String serverPort) {
this.serverport = serverPort;
public void buildSettings() {
Logger log = LoggerFactory.getLogger(BridgeSettings.class);
InetAddress address = null;
String addressString = null;
String theVeraAddress = null;
String theHarmonyAddress = null;
String configFileProperty = System.getProperty("config.file");
if(configFileProperty == null) {
Path filePath = Paths.get(Configuration.CONFIG_FILE);
if(Files.exists(filePath) && Files.isReadable(filePath))
configFileProperty = Configuration.CONFIG_FILE;
}
public String getUpnpResponsePort() {
return upnpresponseport;
String serverPortOverride = System.getProperty("server.port");
if(configFileProperty != null)
{
log.info("reading from config file: " + configFileProperty);
theBridgeSettings.setConfigfile(configFileProperty);
_loadConfig();
}
public void setUpnpResponsePort(String upnpResponsePort) {
this.upnpresponseport = upnpResponsePort;
}
public String getUpnpDeviceDb() {
return upnpdevicedb;
}
public void setUpnpDeviceDb(String upnpDeviceDb) {
this.upnpdevicedb = upnpDeviceDb;
}
public IpList getVeraAddress() {
return veraaddress;
}
public void setVeraAddress(IpList veraAddress) {
this.veraaddress = veraAddress;
}
public IpList getHarmonyAddress() {
return harmonyaddress;
}
public void setHarmonyAddress(IpList harmonyaddress) {
this.harmonyaddress = harmonyaddress;
}
public String getHarmonyUser() {
return harmonyuser;
}
public void setHarmonyUser(String harmonyuser) {
this.harmonyuser = harmonyuser;
}
public String getHarmonyPwd() {
return harmonypwd;
}
public void setHarmonyPwd(String harmonypwd) {
this.harmonypwd = harmonypwd;
}
public Integer getUpnpResponseDevices() {
return upnpresponsedevices;
}
public void setUpnpResponseDevices(Integer upnpresponsedevices) {
this.upnpresponsedevices = upnpresponsedevices;
}
public boolean isUpnpStrict() {
return upnpstrict;
}
public void setUpnpStrict(boolean upnpStrict) {
this.upnpstrict = upnpStrict;
}
public boolean isTraceupnp() {
return traceupnp;
}
public void setTraceupnp(boolean traceupnp) {
this.traceupnp = traceupnp;
}
public boolean isDevMode() {
return devmode;
}
public void setDevMode(boolean devmode) {
this.devmode = devmode;
}
public String getNestuser() {
return nestuser;
}
public void setNestuser(String nestuser) {
this.nestuser = nestuser;
}
public String getNestpwd() {
return nestpwd;
}
public void setNestpwd(String nestpwd) {
this.nestpwd = nestpwd;
}
public boolean isNestConfigured() {
return nestconfigured;
}
public void setNestConfigured(boolean isNestConfigured) {
this.nestconfigured = isNestConfigured;
}
public Boolean isValidVera() {
List<NamedIP> devicesList = this.veraaddress.getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
public Boolean isValidHarmony() {
List<NamedIP> devicesList = this.harmonyaddress.getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
if(this.harmonypwd == null || this.harmonypwd == "")
return false;
if(this.harmonyuser == null || this.harmonyuser == "")
return false;
return true;
}
public Boolean isValidNest() {
if(this.nestpwd == null || this.nestpwd == "")
return false;
if(this.nestuser == null || this.nestuser == "")
return false;
return true;
else
{
log.info("reading from system properties");
theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES);
theBridgeSettings.setFarenheit(true);
theBridgeSettings.setConfigfile(Configuration.CONFIG_FILE);
theBridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DEFAULT_WEB_PORT));
theBridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address"));
theBridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db"));
theBridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
theVeraAddress = System.getProperty("vera.address");
IpList theVeraList = null;
if(theVeraAddress != null) {
try {
theVeraList = new Gson().fromJson(theVeraAddress, IpList.class);
} catch (Exception e) {
try {
theVeraList = new Gson().fromJson("{devices:[{name:default,ip:" + theVeraAddress + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse vera.address, not set with message: " + e.getMessage(), e);
theVeraList = null;
}
}
}
theBridgeSettings.setVeraAddress(theVeraList);
theHarmonyAddress = System.getProperty("harmony.address");
IpList theHarmonyList = null;
if(theHarmonyAddress != null) {
try {
theHarmonyList = new Gson().fromJson(theHarmonyAddress, IpList.class);
} catch (Exception e) {
try {
theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + theHarmonyAddress + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse harmony.address, not set with message: " + e.getMessage(), e);
theHarmonyList = null;
}
}
}
theBridgeSettings.setHarmonyAddress(theHarmonyList);
theBridgeSettings.setHarmonyUser(System.getProperty("harmony.user"));
theBridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd"));
theBridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
theBridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
theBridgeSettings.setButtonsleep(Integer.parseInt(System.getProperty("button.sleep", Configuration.DEFAULT_BUTTON_SLEEP)));
theBridgeSettings.setNestuser(System.getProperty("nest.user"));
theBridgeSettings.setNestpwd(System.getProperty("nest.pwd"));
}
if(theBridgeSettings.getUpnpConfigAddress() == null || theBridgeSettings.getUpnpConfigAddress().equals("")) {
try {
log.info("Getting an IP address for this host....");
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
while (ifs.hasMoreElements() && addressString == null) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements() && IPsPerNic == 0) {
address = addrs.nextElement();
if (InetAddressUtils.isIPv4Address(address.getHostAddress())) {
log.debug(name + " ... has IPV4 addr " + address);
if(!name.equalsIgnoreCase(Configuration.LOOP_BACK_INTERFACE)|| !address.getHostAddress().equalsIgnoreCase(Configuration.LOOP_BACK_ADDRESS)) {
IPsPerNic++;
addressString = address.getHostAddress();
log.info("Adding " + addressString + " from interface " + name + " as our default upnp config address.");
}
}
}
}
} catch (SocketException e) {
log.error("Cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
return;
}
theBridgeSettings.setUpnpConfigAddress(addressString);
}
if(theBridgeSettings.getUpnpResponsePort() == null)
theBridgeSettings.setUpnpResponsePort(Configuration.UPNP_RESPONSE_PORT);
if(theBridgeSettings.getServerPort() == null)
theBridgeSettings.setServerPort(Configuration.DEFAULT_WEB_PORT);
if(theBridgeSettings.getUpnpDeviceDb() == null)
theBridgeSettings.setUpnpDeviceDb(Configuration.DEVICE_DB_DIRECTORY);
if(theBridgeSettings.getNumberoflogmessages() == null)
theBridgeSettings.setNumberoflogmessages(Configuration.NUMBER_OF_LOG_MESSAGES);
if(theBridgeSettings.getButtonsleep() <= 0)
theBridgeSettings.setButtonsleep(Integer.parseInt(Configuration.DEFAULT_BUTTON_SLEEP));
theBridgeSettings.setVeraconfigured(theBridgeSettings.isValidVera());
theBridgeSettings.setHarmonyconfigured(theBridgeSettings.isValidHarmony());
theBridgeSettings.setNestConfigured(theBridgeSettings.isValidNest());
theBridgeSettings.setHueconfigured(theBridgeSettings.isValidHue());
if(serverPortOverride != null)
theBridgeSettings.setServerPort(serverPortOverride);
setupParams(Paths.get(theBridgeSettings.getConfigfile()), ".cfgbk", "habridge.config-");
}
public void loadConfig() {
if(theBridgeSettings.getConfigfile() != null)
_loadConfig();
}
private void _loadConfig() {
Path configPath = Paths.get(theBridgeSettings.getConfigfile());
_loadConfig(configPath);
}
private void _loadConfig(Path aPath) {
String jsonContent = configReader(aPath);
BridgeSettingsDescriptor aBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class);
theBridgeSettings.setButtonsleep(aBridgeSettings.getButtonsleep());
theBridgeSettings.setUpnpConfigAddress(aBridgeSettings.getUpnpConfigAddress());
theBridgeSettings.setServerPort(aBridgeSettings.getServerPort());
theBridgeSettings.setUpnpResponsePort(aBridgeSettings.getUpnpResponsePort());
theBridgeSettings.setUpnpDeviceDb(aBridgeSettings.getUpnpDeviceDb());
theBridgeSettings.setVeraAddress(aBridgeSettings.getVeraAddress());
theBridgeSettings.setHarmonyAddress(aBridgeSettings.getHarmonyAddress());
theBridgeSettings.setHarmonyUser(aBridgeSettings.getHarmonyUser());
theBridgeSettings.setHarmonyPwd(aBridgeSettings.getHarmonyPwd());
theBridgeSettings.setUpnpStrict(aBridgeSettings.isUpnpStrict());
theBridgeSettings.setTraceupnp(aBridgeSettings.isTraceupnp());
theBridgeSettings.setNestuser(aBridgeSettings.getNestuser());
theBridgeSettings.setNestpwd(aBridgeSettings.getNestpwd());
theBridgeSettings.setVeraconfigured(aBridgeSettings.isValidVera());
theBridgeSettings.setHarmonyconfigured(aBridgeSettings.isValidHarmony());
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) {
Logger log = LoggerFactory.getLogger(BridgeSettings.class);
log.debug("Save HA Bridge settings.");
Path configPath = Paths.get(theBridgeSettings.getConfigfile());
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(newBridgeSettings);
configWriter(jsonValue, configPath);
_loadConfig(configPath);
}
private void configWriter(String content, Path filePath) {
Logger log = LoggerFactory.getLogger(BridgeSettings.class);
if(Files.exists(filePath) && !Files.isWritable(filePath)){
log.error("Error file is not writable: " + filePath);
return;
}
if(Files.notExists(filePath.getParent())) {
try {
Files.createDirectories(filePath.getParent());
} catch (IOException e) {
log.error("Error creating the directory: " + filePath + " message: " + e.getMessage(), e);
}
}
try {
Path target = null;
if(Files.exists(filePath)) {
target = FileSystems.getDefault().getPath(filePath.getParent().toString(), "habridge.config.old");
Files.move(filePath, target);
}
Files.write(filePath, content.getBytes(), StandardOpenOption.CREATE);
if(target != null)
Files.delete(target);
} catch (IOException e) {
log.error("Error writing the file: " + filePath + " message: " + e.getMessage(), e);
}
}
private String configReader(Path filePath) {
Logger log = LoggerFactory.getLogger(BridgeSettings.class);
String content = null;
if(Files.notExists(filePath) || !Files.isReadable(filePath)){
log.warn("Error reading the file: " + filePath + " - Does not exist or is not readable. continuing...");
return null;
}
try {
content = new String(Files.readAllBytes(filePath));
} catch (IOException e) {
log.error("Error reading the file: " + filePath + " message: " + e.getMessage(), e);
}
return content;
}
}

View File

@@ -0,0 +1,205 @@
package com.bwssystems.HABridge;
import java.util.List;
public class BridgeSettingsDescriptor {
private String upnpconfigaddress;
private Integer serverport;
private Integer upnpresponseport;
private String upnpdevicedb;
private IpList veraaddress;
private IpList harmonyaddress;
private String harmonyuser;
private String harmonypwd;
private Integer buttonsleep;
private boolean upnpstrict;
private boolean traceupnp;
private String nestuser;
private String nestpwd;
private boolean veraconfigured;
private boolean harmonyconfigured;
private boolean nestconfigured;
private boolean farenheit;
private String configfile;
private Integer numberoflogmessages;
private IpList hueaddress;
private boolean hueconfigured;
public BridgeSettingsDescriptor() {
super();
this.upnpstrict = true;
this.traceupnp = false;
this.nestconfigured = false;
this.veraconfigured = false;
this.harmonyconfigured = false;
this.hueconfigured = false;
this.farenheit = true;
}
public String getUpnpConfigAddress() {
return upnpconfigaddress;
}
public void setUpnpConfigAddress(String upnpConfigAddress) {
this.upnpconfigaddress = upnpConfigAddress;
}
public Integer getServerPort() {
return serverport;
}
public void setServerPort(Integer serverPort) {
this.serverport = serverPort;
}
public void setServerPort(String serverPort) {
this.serverport = Integer.valueOf(serverPort);
}
public Integer getUpnpResponsePort() {
return upnpresponseport;
}
public void setUpnpResponsePort(Integer upnpResponsePort) {
this.upnpresponseport = upnpResponsePort;
}
public void setUpnpResponsePort(String upnpResponsePort) {
this.upnpresponseport = Integer.valueOf(upnpResponsePort);
}
public String getUpnpDeviceDb() {
return upnpdevicedb;
}
public void setUpnpDeviceDb(String upnpDeviceDb) {
this.upnpdevicedb = upnpDeviceDb;
}
public IpList getVeraAddress() {
return veraaddress;
}
public void setVeraAddress(IpList veraAddress) {
this.veraaddress = veraAddress;
}
public IpList getHarmonyAddress() {
return harmonyaddress;
}
public void setHarmonyAddress(IpList harmonyaddress) {
this.harmonyaddress = harmonyaddress;
}
public String getHarmonyUser() {
return harmonyuser;
}
public void setHarmonyUser(String harmonyuser) {
this.harmonyuser = harmonyuser;
}
public String getHarmonyPwd() {
return harmonypwd;
}
public void setHarmonyPwd(String harmonypwd) {
this.harmonypwd = harmonypwd;
}
public boolean isUpnpStrict() {
return upnpstrict;
}
public void setUpnpStrict(boolean upnpStrict) {
this.upnpstrict = upnpStrict;
}
public boolean isTraceupnp() {
return traceupnp;
}
public void setTraceupnp(boolean traceupnp) {
this.traceupnp = traceupnp;
}
public String getNestuser() {
return nestuser;
}
public void setNestuser(String nestuser) {
this.nestuser = nestuser;
}
public String getNestpwd() {
return nestpwd;
}
public void setNestpwd(String nestpwd) {
this.nestpwd = nestpwd;
}
public boolean isVeraconfigured() {
return veraconfigured;
}
public void setVeraconfigured(boolean veraconfigured) {
this.veraconfigured = veraconfigured;
}
public boolean isHarmonyconfigured() {
return harmonyconfigured;
}
public void setHarmonyconfigured(boolean harmonyconfigured) {
this.harmonyconfigured = harmonyconfigured;
}
public boolean isNestConfigured() {
return nestconfigured;
}
public void setNestConfigured(boolean isNestConfigured) {
this.nestconfigured = isNestConfigured;
}
public Integer getButtonsleep() {
return buttonsleep;
}
public void setButtonsleep(Integer buttonsleep) {
this.buttonsleep = buttonsleep;
}
public String getConfigfile() {
return configfile;
}
public void setConfigfile(String 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() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getVeraAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
return true;
}
public Boolean isValidHarmony() {
if(this.getHarmonyAddress() == null || this.getHarmonyAddress().getDevices().size() <= 0)
return false;
List<NamedIP> devicesList = this.getHarmonyAddress().getDevices();
if(devicesList.get(0).getIp().contains(Configuration.DEFAULT_ADDRESS))
return false;
if(this.getHarmonyPwd() == null || this.getHarmonyPwd().equals(""))
return false;
if(this.getHarmonyUser() == null || this.getHarmonyUser().equals(""))
return false;
return true;
}
public Boolean isValidNest() {
if(this.getNestpwd() == null || this.getNestpwd().equals(""))
return false;
if(this.getNestuser() == null || this.getNestuser().equals(""))
return false;
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

@@ -3,12 +3,13 @@ package com.bwssystems.HABridge;
public class Configuration {
public final static String DEVICE_DB_DIRECTORY = "data/device.db";
public final static String UPNP_RESPONSE_PORT = "50000";
public final static String UPNP_RESPONSE_DEVICES = "30";
public final static String DEFAULT_ADDRESS = "1.1.1.1";
public final static String LOOP_BACK_ADDRESS = "127.0.0.1";
public final static String LOOP_BACK_INTERFACE = "lo";
public final static String DEFAULT_HARMONY_ADDRESS_LIST = "{devices:[{name:default,ip:1.1.1.1}]}";
public final static String DEFAULT_USER = "";
public final static String DEFAULT_PWD = "";
public final static String DFAULT_WEB_PORT = "8080";
public final static String DEFAULT_WEB_PORT = "8080";
public final static String DEFAULT_BUTTON_SLEEP = "100";
public static final int UPNP_DISCOVERY_PORT = 1900;
public static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250";
public static final String CONFIG_FILE = "data/habridge.config";
public static final int NUMBER_OF_LOG_MESSAGES = 512;
}

View File

@@ -2,12 +2,6 @@ package com.bwssystems.HABridge;
import static spark.Spark.*;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import org.apache.http.conn.util.InetAddressUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,7 +11,7 @@ import com.bwssystems.HABridge.upnp.UpnpListener;
import com.bwssystems.HABridge.upnp.UpnpSettingsResource;
import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.HarmonyHome;
import com.google.gson.Gson;
import com.bwssystems.hue.HueHome;
public class HABridge {
@@ -41,112 +35,63 @@ public class HABridge {
DeviceResource theResources;
HarmonyHome harmonyHome;
NestHome nestHome;
HueHome hueHome;
HueMulator theHueMulator;
UpnpSettingsResource theSettingResponder;
UpnpListener theUpnpListener;
InetAddress address = null;
String addressString = null;
SystemControl theSystem;
BridgeSettings bridgeSettings;
Version theVersion;
theVersion = new Version();
log.info("HA Bridge (v" + theVersion.getVersion() + ") starting setup....");
log.info("HA Bridge (v" + theVersion.getVersion() + ") starting....");
bridgeSettings = new BridgeSettings();
bridgeSettings.setServerPort(System.getProperty("server.port", Configuration.DFAULT_WEB_PORT));
bridgeSettings.setUpnpConfigAddress(System.getProperty("upnp.config.address", Configuration.DEFAULT_ADDRESS));
if(bridgeSettings.getUpnpConfigAddress().equalsIgnoreCase(Configuration.DEFAULT_ADDRESS)) {
try {
log.info("Getting an IP address for this host....");
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
while (ifs.hasMoreElements() && addressString == null) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements() && IPsPerNic == 0) {
address = addrs.nextElement();
if (InetAddressUtils.isIPv4Address(address.getHostAddress())) {
log.debug(name + " ... has IPV4 addr " + address);
if(!name.equalsIgnoreCase(Configuration.LOOP_BACK_INTERFACE)|| !address.getHostAddress().equalsIgnoreCase(Configuration.LOOP_BACK_ADDRESS)) {
IPsPerNic++;
addressString = address.getHostAddress();
log.info("Adding " + addressString + " from interface " + name + " as our default upnp config address.");
}
}
}
}
} catch (SocketException e) {
log.error("Cannot get ip address of this host, Exiting with message: " + e.getMessage(), e);
return;
}
bridgeSettings.setUpnpConfigAddress(addressString);
}
bridgeSettings.setUpnpDeviceDb(System.getProperty("upnp.device.db", Configuration.DEVICE_DB_DIRECTORY));
bridgeSettings.setUpnpResponsePort(System.getProperty("upnp.response.port", Configuration.UPNP_RESPONSE_PORT));
IpList theVeraList;
try {
theVeraList = new Gson().fromJson(System.getProperty("vera.address", Configuration.DEFAULT_HARMONY_ADDRESS_LIST), IpList.class);
} catch (Exception e) {
try {
theVeraList = new Gson().fromJson("{devices:[{name:default,ip:" + System.getProperty("vera.address", Configuration.DEFAULT_ADDRESS) + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse vera.address, Exiting with message: " + e.getMessage(), e);
return;
}
}
bridgeSettings.setVeraAddress(theVeraList);
IpList theHarmonyList;
try {
theHarmonyList = new Gson().fromJson(System.getProperty("harmony.address", Configuration.DEFAULT_HARMONY_ADDRESS_LIST), IpList.class);
} catch (Exception e) {
try {
theHarmonyList = new Gson().fromJson("{devices:[{name:default,ip:" + System.getProperty("harmony.address", Configuration.DEFAULT_ADDRESS) + "}]}", IpList.class);
} catch (Exception et) {
log.error("Cannot parse harmony.address, Exiting with message: " + e.getMessage(), e);
return;
}
}
bridgeSettings.setHarmonyAddress(theHarmonyList);
bridgeSettings.setHarmonyUser(System.getProperty("harmony.user", Configuration.DEFAULT_USER));
bridgeSettings.setHarmonyPwd(System.getProperty("harmony.pwd", Configuration.DEFAULT_PWD));
bridgeSettings.setUpnpStrict(Boolean.parseBoolean(System.getProperty("upnp.strict", "true")));
bridgeSettings.setTraceupnp(Boolean.parseBoolean(System.getProperty("trace.upnp", "false")));
bridgeSettings.setDevMode(Boolean.parseBoolean(System.getProperty("dev.mode", "false")));
bridgeSettings.setUpnpResponseDevices(Integer.parseInt(System.getProperty("upnp.response.devices", Configuration.UPNP_RESPONSE_DEVICES)));
bridgeSettings.setNestuser(System.getProperty("nest.user", Configuration.DEFAULT_USER));
bridgeSettings.setNestpwd(System.getProperty("nest.pwd", Configuration.DEFAULT_PWD));
while(!bridgeSettings.getBridgeControl().isStop()) {
bridgeSettings.buildSettings();
log.info("HA Bridge (v" + theVersion.getVersion() + ") initializing....");
// sparkjava config directive to set ip address for the web server to listen on
// ipAddress("0.0.0.0"); // not used
// sparkjava config directive to set port for the web server to listen on
port(Integer.valueOf(bridgeSettings.getServerPort()));
port(bridgeSettings.getBridgeSettingsDescriptor().getServerPort());
// sparkjava config directive to set html static file location for Jetty
staticFileLocation("/public");
// setup system control api first
theSystem = new SystemControl(bridgeSettings, theVersion);
theSystem.setupServer();
//setup the harmony connection if available
harmonyHome = new HarmonyHome(bridgeSettings);
harmonyHome = new HarmonyHome(bridgeSettings.getBridgeSettingsDescriptor());
//setup the nest connection if available
nestHome = new NestHome(bridgeSettings);
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
theResources = new DeviceResource(bridgeSettings, theVersion, harmonyHome, nestHome);
theResources = new DeviceResource(bridgeSettings.getBridgeSettingsDescriptor(), harmonyHome, nestHome, hueHome);
// setup the class to handle the hue emulator rest api
theHueMulator = new HueMulator(bridgeSettings, theResources.getDeviceRepository(), harmonyHome, nestHome);
theHueMulator = new HueMulator(bridgeSettings.getBridgeSettingsDescriptor(), theResources.getDeviceRepository(), harmonyHome, nestHome, hueHome);
theHueMulator.setupServer();
// setup the class to handle the upnp response rest api
theSettingResponder = new UpnpSettingsResource(bridgeSettings);
theSettingResponder = new UpnpSettingsResource(bridgeSettings.getBridgeSettingsDescriptor());
theSettingResponder.setupServer();
// wait for the sparkjava initialization of the rest api classes to be complete
awaitInitialization();
// start the upnp ssdp discovery listener
theUpnpListener = new UpnpListener(bridgeSettings);
theUpnpListener.startListening();
theUpnpListener = new UpnpListener(bridgeSettings.getBridgeSettingsDescriptor(), bridgeSettings.getBridgeControl());
if(theUpnpListener.startListening())
log.info("HA Bridge (v" + theVersion.getVersion() + ") reinitialization requessted....");
else
bridgeSettings.getBridgeControl().setStop(true);
bridgeSettings.getBridgeControl().setReinit(false);
stop();
nestHome.closeTheNest();
nestHome = null;
harmonyHome.shutdownHarmonyHubs();
harmonyHome = null;
}
log.info("HA Bridge (v" + theVersion.getVersion() + ") exiting....");
System.exit(0);
}
}

View File

@@ -0,0 +1,283 @@
package com.bwssystems.HABridge;
import static spark.Spark.get;
import static spark.Spark.options;
import static spark.Spark.post;
import static spark.Spark.put;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 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 {
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 BridgeSettings bridgeSettings;
private Version version;
private CyclicBufferAppender<ILoggingEvent> cyclicBufferAppender;
private DateFormat dateFormat;
private LoggingManager theLogServiceMgr;
public SystemControl(BridgeSettings theBridgeSettings, Version theVersion) {
this.bridgeSettings = theBridgeSettings;
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
public void setupServer() {
log.info("System control service started....");
// http://ip_address:port/system/habridge/version gets the version of this bridge instance
get (SYSTEM_CONTEXT + "/habridge/version", "application/json", (request, response) -> {
log.debug("Get HA Bridge version: v" + version.getVersion());
response.status(HttpStatus.SC_OK);
return "{\"version\":\"" + version.getVersion() + "\"}";
});
// 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) -> {
log.debug("bridge settings requested from " + request.ip());
response.status(200);
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
// http://ip_address:port/system/settings CORS request
options(SYSTEM_CONTEXT + "/settings", "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/settings which returns the bridge configuration settings
put(SYSTEM_CONTEXT + "/settings", "application/json", (request, response) -> {
log.debug("save bridge settings requested from " + request.ip() + " with body: " + request.body());
BridgeSettingsDescriptor newBridgeSettings = new Gson().fromJson(request.body(), BridgeSettingsDescriptor.class);
bridgeSettings.save(newBridgeSettings);
response.status(200);
return bridgeSettings.getBridgeSettingsDescriptor();
}, new JsonTransformer());
// http://ip_address:port/system/control/reinit CORS request
options(SYSTEM_CONTEXT + "/control/reinit", "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/control/reinit sets the parameter reinit the server
put(SYSTEM_CONTEXT + "/control/reinit", "application/json", (request, response) -> {
return reinit();
});
// http://ip_address:port/system/control/stop CORS request
options(SYSTEM_CONTEXT + "/control/stop", "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/control/stop sets the parameter stop the server
put(SYSTEM_CONTEXT + "/control/stop", "application/json", (request, response) -> {
return stop();
});
// http://ip_address:port/system/backup/available returns a list of config backup filenames
get (SYSTEM_CONTEXT + "/backup/available", "application/json", (request, response) -> {
log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK);
return bridgeSettings.getBackups();
}, new JsonTransformer());
// http://ip_address:port/system/backup/create CORS request
options(SYSTEM_CONTEXT + "/backup/create", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "PUT");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
put (SYSTEM_CONTEXT + "/backup/create", "application/json", (request, response) -> {
log.debug("Create backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
BackupFilename returnFilename = new BackupFilename();
returnFilename.setFilename(bridgeSettings.backup(aFilename.getFilename()));
return returnFilename;
}, new JsonTransformer());
// http://ip_address:port/system/backup/delete CORS request
options(SYSTEM_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (SYSTEM_CONTEXT + "/backup/delete", "application/json", (request, response) -> {
log.debug("Delete backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null)
bridgeSettings.deleteBackup(aFilename.getFilename());
else
log.warn("No filename given for delete backup.");
return null;
}, new JsonTransformer());
// http://ip_address:port/system/backup/restore CORS request
options(SYSTEM_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.header("Access-Control-Allow-Methods", "POST");
response.header("Access-Control-Allow-Headers", request.headers("Access-Control-Request-Headers"));
response.header("Content-Type", "text/html; charset=utf-8");
return "";
});
post (SYSTEM_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
log.debug("Restore backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null) {
bridgeSettings.restoreBackup(aFilename.getFilename());
bridgeSettings.loadConfig();
}
else
log.warn("No filename given for restore backup.");
return bridgeSettings.getBridgeSettingsDescriptor();
}, 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() {
try {
byte[] buf = new byte[256];
String testData = "M-SEARCH * HTTP/1.1\nHOST: " + Configuration.UPNP_MULTICAST_ADDRESS + ":" + Configuration.UPNP_DISCOVERY_PORT + "ST: urn:schemas-upnp-org:device:CloudProxy:1\nMAN: \"ssdp:discover\"\nMX: 3";
buf = testData.getBytes();
MulticastSocket socket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT);
InetAddress group = InetAddress.getByName(Configuration.UPNP_MULTICAST_ADDRESS);
DatagramPacket packet;
packet = new DatagramPacket(buf, buf.length, group, Configuration.UPNP_DISCOVERY_PORT);
socket.send(packet);
socket.close();
}
catch (IOException e) {
log.warn("Error pinging listener. " + e.getMessage());
}
}
public String reinit() {
bridgeSettings.getBridgeControl().setReinit(true);
pingListener();
return "{\"control\":\"reiniting\"}";
}
public String stop() {
bridgeSettings.getBridgeControl().setStop(true);
pingListener();
return "{\"control\":\"stopping\"}";
}
}

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

View File

@@ -1,15 +1,22 @@
package com.bwssystems.HABridge.api.hue;
// import java.util.ArrayList;
import java.util.List;
/**
* Created by arm on 4/14/15.
*/
public class DeviceState {
private boolean on;
private int bri = 255;
private int bri;
private int hue;
private int sat;
private String effect;
private int ct;
private String alert;
private String colormode;
private boolean reachable;
private List<Double> xy;
public boolean isOn() {
return on;
@@ -27,6 +34,22 @@ public class DeviceState {
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() {
return effect;
}
@@ -35,6 +58,14 @@ public class DeviceState {
this.effect = effect;
}
public int getCt() {
return ct;
}
public void setCt(int ct) {
this.ct = ct;
}
public String getAlert() {
return alert;
}
@@ -43,6 +74,14 @@ public class DeviceState {
this.alert = alert;
}
public String getColormode() {
return colormode;
}
public void setColormode(String colormode) {
this.colormode = colormode;
}
public boolean isReachable() {
return reachable;
}
@@ -51,6 +90,31 @@ public class DeviceState {
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
public String toString() {
return "DeviceState{" +

View File

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

View File

@@ -1,21 +1,58 @@
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
*/
public class DeviceDescriptor{
@SerializedName("id")
@Expose
private String id;
@SerializedName("name")
@Expose
private String name;
@SerializedName("mapId")
@Expose
private String mapId;
@SerializedName("mapType")
@Expose
private String mapType;
@SerializedName("deviceType")
@Expose
private String deviceType;
@SerializedName("targetDevice")
@Expose
private String targetDevice;
@SerializedName("offUrl")
@Expose
private String offUrl;
@SerializedName("dimUrl")
@Expose
private String dimUrl;
@SerializedName("onUrl")
@Expose
private String onUrl;
@SerializedName("headers")
@Expose
private String headers;
@SerializedName("httpVerb")
@Expose
private String httpVerb;
@SerializedName("contentType")
@Expose
private String contentType;
@SerializedName("contentBody")
@Expose
private String contentBody;
@SerializedName("contentBodyOff")
@Expose
private String contentBodyOff;
private DeviceState deviceState;
public String getName() {
return name;
}
@@ -64,6 +101,14 @@ public class DeviceDescriptor{
this.offUrl = offUrl;
}
public String getDimUrl() {
return dimUrl;
}
public void setDimUrl(String dimUrl) {
this.dimUrl = dimUrl;
}
public String getOnUrl() {
return onUrl;
}
@@ -80,6 +125,14 @@ public class DeviceDescriptor{
this.id = id;
}
public String getHeaders() {
return headers;
}
public void setHeaders(String headers) {
this.headers = headers;
}
public String getHttpVerb() {
return httpVerb;
}
@@ -112,5 +165,14 @@ public class DeviceDescriptor{
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,65 +2,63 @@ package com.bwssystems.HABridge.dao;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.JsonTransformer;
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.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
* loading.
*/
public class DeviceRepository {
Map<String, DeviceDescriptor> devices;
Path repositoryPath;
final Random random = new Random();
final Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public class DeviceRepository extends BackupHandler {
private Map<String, DeviceDescriptor> devices;
private Path repositoryPath;
private Gson gson;
final private Random random = new Random();
private Logger log = LoggerFactory.getLogger(DeviceRepository.class);
public DeviceRepository(String deviceDb) {
super();
_loadRepository(deviceDb);
}
private void _loadRepository(String aFilePath){
repositoryPath = Paths.get(aFilePath);
gson =
new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
repositoryPath = null;
repositoryPath = Paths.get(deviceDb);
setupParams(repositoryPath, ".bk", "device.db-");
_loadRepository(repositoryPath);
}
public void loadRepository() {
if(repositoryPath != null)
_loadRepository(repositoryPath);
}
private void _loadRepository(Path aPath){
String jsonContent = repositoryReader(aPath);
devices = new HashMap<String, DeviceDescriptor>();
if(jsonContent != null)
{
List<DeviceDescriptor> list = readJsonStream(jsonContent);
ListIterator<DeviceDescriptor> theIterator = list.listIterator();
DeviceDescriptor theDevice = null;
while (theIterator.hasNext()) {
theDevice = theIterator.next();
put(theDevice.getId(), theDevice);
DeviceDescriptor list[] = gson.fromJson(jsonContent, DeviceDescriptor[].class);
for(int i = 0; i < list.length; i++) {
put(list[i].getId(), list[i]);
}
}
}
public List<DeviceDescriptor> findAll() {
@@ -81,76 +79,19 @@ public class DeviceRepository {
devices.put(id, aDescriptor);
}
public void save(DeviceDescriptor aDescriptor) {
if(aDescriptor.getId() != null)
devices.remove(aDescriptor.getId());
public void save(DeviceDescriptor[] descriptors) {
String theNames = "";
for(int i = 0; i < descriptors.length; i++) {
if(descriptors[i].getId() != null && descriptors[i].getId().length() > 0)
devices.remove(descriptors[i].getId());
else
aDescriptor.setId(String.valueOf(random.nextInt(Integer.MAX_VALUE)));
put(aDescriptor.getId(), aDescriptor);
JsonTransformer aRenderer = new JsonTransformer();
String jsonValue = aRenderer.render(findAll());
descriptors[i].setId(String.valueOf(random.nextInt(Integer.MAX_VALUE)));
put(descriptors[i].getId(), descriptors[i]);
theNames = theNames + " " + descriptors[i].getName() + ", ";
}
String jsonValue = gson.toJson(findAll());
repositoryWriter(jsonValue, repositoryPath);
log.debug("Save device: " + aDescriptor.getName());
}
public String backup(String aFilename) {
if(aFilename == null || aFilename.equalsIgnoreCase("")) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
aFilename = "devicedb-" + dateFormat.format(Calendar.getInstance().getTime()) + ".bk";
}
else
aFilename = aFilename + ".bk";
try {
Files.copy(repositoryPath, FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Could not backup to file: " + aFilename + " message: " + e.getMessage(), e);
}
log.debug("Backup repository: " + aFilename);
return aFilename;
}
public String deleteBackup(String aFilename) {
log.debug("Delete backup repository: " + aFilename);
try {
Files.delete(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename));
} catch (IOException e) {
log.error("Could not delete file: " + aFilename + " message: " + e.getMessage(), e);
}
return aFilename;
}
public String restoreBackup(String aFilename) {
log.debug("Restore backup repository: " + aFilename);
try {
Path target = null;
if(Files.exists(repositoryPath)) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
target = FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), "devicedb-" + dateFormat.format(Calendar.getInstance().getTime()) + ".bk");
Files.move(repositoryPath, target);
}
Files.copy(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), repositoryPath, StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Error restoring the file: " + aFilename + " message: " + e.getMessage(), e);
return null;
}
_loadRepository(repositoryPath);
return aFilename;
}
public List<String> getBackups() {
List<String> theFilenames = new ArrayList<String>();
Path dir = repositoryPath.getParent();
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(dir, "*.{bk}")) {
for (Path entry: stream) {
theFilenames.add(entry.getFileName().toString());
}
} catch (IOException x) {
// IOException can never be thrown by the iteration.
// In this snippet, it can // only be thrown by newDirectoryStream.
log.error("Issue getting direcotyr listing for backups - " + x.getMessage());
}
return theFilenames;
log.debug("Save device(s): " + theNames);
}
public String delete(DeviceDescriptor aDescriptor) {
@@ -211,82 +152,4 @@ public class DeviceRepository {
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

@@ -0,0 +1,18 @@
package com.bwssystems.HABridge.dao;
public class ErrorMessage {
private String message;
public ErrorMessage(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -15,17 +15,18 @@ import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.Version;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.dao.BackupFilename;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.dao.DeviceRepository;
import com.bwssystems.HABridge.dao.ErrorMessage;
import com.bwssystems.NestBridge.NestHome;
import com.bwssystems.harmony.HarmonyHome;
import com.bwssystems.luupRequests.Sdata;
import com.bwssystems.hue.HueHome;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene;
import com.bwssystems.util.JsonTransformer;
import com.bwssystems.vera.VeraHome;
import com.bwssystems.vera.VeraInfo;
import com.google.gson.Gson;
/**
@@ -34,15 +35,14 @@ import com.google.gson.Gson;
public class DeviceResource {
private static final String API_CONTEXT = "/api/devices";
private static final Logger log = LoggerFactory.getLogger(DeviceResource.class);
private DeviceRepository deviceRepository;
private VeraHome veraHome;
private Version version;
private HarmonyHome myHarmonyHome;
private NestHome nestHome;
private HueHome hueHome;
private static final Set<String> supportedVerbs = new HashSet<>(Arrays.asList("get", "put", "post"));
public DeviceResource(BridgeSettings theSettings, Version theVersion, HarmonyHome theHarmonyHome, NestHome aNestHome) {
public DeviceResource(BridgeSettingsDescriptor theSettings, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome) {
this.deviceRepository = new DeviceRepository(theSettings.getUpnpDeviceDb());
if(theSettings.isValidVera())
@@ -60,7 +60,10 @@ public class DeviceResource {
else
this.nestHome = null;
this.version = theVersion;
if(theSettings.isValidHue())
this.hueHome = aHueHome;
else
this.hueHome = null;
setupEndpoints();
}
@@ -80,24 +83,31 @@ public class DeviceResource {
return "";
});
post(API_CONTEXT, "application/json", (request, response) -> {
log.debug("Create a Device - request body: " + request.body());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
if(device.getContentBody() != null ) {
if (device.getContentType() == null || device.getHttpVerb() == null || !supportedVerbs.contains(device.getHttpVerb().toLowerCase())) {
device = null;
log.debug("Create a Device(s) - request body: " + request.body());
DeviceDescriptor devices[];
if(request.body().substring(0,1).equalsIgnoreCase("[") == true) {
devices = new Gson().fromJson(request.body(), DeviceDescriptor[].class);
}
else {
devices = new Gson().fromJson("[" + request.body() + "]", DeviceDescriptor[].class);
}
for(int i = 0; i < devices.length; i++) {
if(devices[i].getContentBody() != null ) {
if (devices[i].getContentType() == null || devices[i].getHttpVerb() == null || !supportedVerbs.contains(devices[i].getHttpVerb().toLowerCase())) {
response.status(HttpStatus.SC_BAD_REQUEST);
log.debug("Bad http verb in create a Device: " + request.body());
return device;
log.debug("Bad http verb in create a Device(s): " + request.body());
return new ErrorMessage("Bad http verb in create a Device(s): " + request.body() + " ");
}
}
}
deviceRepository.save(device);
log.debug("Created a Device: " + request.body());
deviceRepository.save(devices);
log.debug("Created a Device(s): " + request.body());
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.status(HttpStatus.SC_CREATED);
return device;
return devices;
}, new JsonTransformer());
// http://ip_address:port/api/devices/:id CORS request
@@ -112,32 +122,24 @@ public class DeviceResource {
put (API_CONTEXT + "/:id", "application/json", (request, response) -> {
log.debug("Edit a Device - request body: " + request.body());
DeviceDescriptor device = new Gson().fromJson(request.body(), DeviceDescriptor.class);
DeviceDescriptor deviceEntry = deviceRepository.findOne(request.params(":id"));
if(deviceEntry == null){
log.debug("Could not save an edited Device Id: " + request.params(":id"));
if(deviceRepository.findOne(request.params(":id")) == null){
log.debug("Could not save an edited device, Device Id not found: " + request.params(":id"));
response.status(HttpStatus.SC_BAD_REQUEST);
return new ErrorMessage("Could not save an edited device, Device Id not found: " + request.params(":id") + " ");
}
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)
deviceEntry.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());
device.setDeviceType(device.getDeviceType());
deviceRepository.save(deviceEntry);
DeviceDescriptor[] theDevices = new DeviceDescriptor[1];
theDevices[0] = device;
deviceRepository.save(theDevices);
response.status(HttpStatus.SC_OK);
}
return deviceEntry;
return device;
}, new JsonTransformer());
get (API_CONTEXT, "application/json", (request, response) -> {
@@ -153,8 +155,10 @@ public class DeviceResource {
get (API_CONTEXT + "/:id", "application/json", (request, response) -> {
log.debug("Get a device");
DeviceDescriptor descriptor = deviceRepository.findOne(request.params(":id"));
if(descriptor == null)
if(descriptor == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("Could not find, id: " + request.params(":id") + " ");
}
else
response.status(HttpStatus.SC_OK);
return descriptor;
@@ -164,8 +168,10 @@ public class DeviceResource {
String anId = request.params(":id");
log.debug("Delete a device: " + anId);
DeviceDescriptor deleted = deviceRepository.findOne(anId);
if(deleted == null)
if(deleted == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("Could not delete, id: " + anId + " not found. ");
}
else
{
deviceRepository.delete(deleted);
@@ -174,38 +180,43 @@ public class DeviceResource {
return null;
}, new JsonTransformer());
get (API_CONTEXT + "/habridge/version", "application/json", (request, response) -> {
log.debug("Get HA Bridge version: v" + version.getVersion());
response.status(HttpStatus.SC_OK);
return "{\"version\":\"" + version.getVersion() + "\"}";
});
get (API_CONTEXT + "/vera/devices", "application/json", (request, response) -> {
log.debug("Get vera devices");
if(veraHome == null){
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Vera is not available.");
}
List<Device> theDevices = veraHome.getDevices();
if(theDevices == null) {
response.status(HttpStatus.SC_SERVICE_UNAVAILABLE);
return new ErrorMessage("A Vera request failed to get devices. Check your Vera IP addresses.");
}
else
response.status(HttpStatus.SC_OK);
return veraHome.getDevices();
return theDevices;
}, new JsonTransformer());
get (API_CONTEXT + "/vera/scenes", "application/json", (request, response) -> {
log.debug("Get vera scenes");
if(veraHome == null){
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Vera is not available.");
}
List<Scene> theScenes = veraHome.getScenes();
if(theScenes == null) {
response.status(HttpStatus.SC_SERVICE_UNAVAILABLE);
return new ErrorMessage("A Vera is not available and failed to get scenes. Check your Vera IP addresses.");
}
else
response.status(HttpStatus.SC_OK);
return veraHome.getScenes();
return theScenes;
}, new JsonTransformer());
get (API_CONTEXT + "/harmony/activities", "application/json", (request, response) -> {
log.debug("Get harmony activities");
if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Harmony is not available.");
}
response.status(HttpStatus.SC_OK);
return myHarmonyHome.getActivities();
@@ -215,7 +226,7 @@ public class DeviceResource {
log.debug("Get harmony current activity");
if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Harmony is not available.");
}
response.status(HttpStatus.SC_OK);
return myHarmonyHome.getCurrentActivities();
@@ -225,7 +236,7 @@ public class DeviceResource {
log.debug("Get harmony devices");
if(myHarmonyHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Harmony is not available.");
}
response.status(HttpStatus.SC_OK);
return myHarmonyHome.getDevices();
@@ -235,12 +246,22 @@ public class DeviceResource {
log.debug("Get nest items");
if(nestHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return new ErrorMessage("A Nest is not available.");
}
response.status(HttpStatus.SC_OK);
return nestHome.getItems();
}, new JsonTransformer());
get (API_CONTEXT + "/hue/devices", "application/json", (request, response) -> {
log.debug("Get hue items");
if(hueHome == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return new ErrorMessage("A Hue is not available.");
}
response.status(HttpStatus.SC_OK);
return hueHome.getDevices();
}, new JsonTransformer());
get (API_CONTEXT + "/backup/available", "application/json", (request, response) -> {
log.debug("Get backup filenames");
response.status(HttpStatus.SC_OK);
@@ -295,8 +316,10 @@ public class DeviceResource {
post (API_CONTEXT + "/backup/restore", "application/json", (request, response) -> {
log.debug("Restore backup: " + request.body());
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
if(aFilename != null)
if(aFilename != null) {
deviceRepository.restoreBackup(aFilename.getFilename());
deviceRepository.loadRepository();
}
else
log.warn("No filename given for restore backup.");
return null;

View File

@@ -1,7 +1,8 @@
package com.bwssystems.HABridge.hue;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.NameValue;
import com.bwssystems.HABridge.api.UserCreateRequest;
import com.bwssystems.HABridge.api.hue.DeviceResponse;
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.HarmonyHome;
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.util.JsonTransformer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
@@ -28,34 +34,44 @@ import static spark.Spark.put;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
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.HttpPost;
import org.apache.http.client.methods.HttpPut;
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.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter;
/**
* 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 String INTENSITY_PERCENT = "${intensity.percent}";
private static final String INTENSITY_BYTE = "${intensity.byte}";
@@ -67,14 +83,37 @@ public class HueMulator {
private DeviceRepository repository;
private HarmonyHome myHarmonyHome;
private Nest theNest;
private HueHome myHueHome;
private HttpClient httpClient;
private CloseableHttpClient httpclientSSL;
private SSLContext sslcontext;
private SSLConnectionSocketFactory sslsf;
private RequestConfig globalConfig;
private ObjectMapper mapper;
private BridgeSettings bridgeSettings;
private BridgeSettingsDescriptor bridgeSettings;
private byte[] sendData;
private String hueUser;
private String errorString;
public HueMulator(BridgeSettings theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome){
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome){
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.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
repository = aDeviceRepository;
@@ -86,7 +125,13 @@ public class HueMulator {
this.theNest = aNestHome.getTheNest();
else
this.theNest = null;
if(theBridgeSettings.isValidHue())
this.myHueHome = aHueHome;
else
this.myHueHome = null;
bridgeSettings = theBridgeSettings;
hueUser = null;
errorString = null;
}
// This function sets up the sparkjava rest calls for the hue api
@@ -101,7 +146,7 @@ public class HueMulator {
List<DeviceDescriptor> deviceList = repository.findAll();
Map<String, DeviceResponse> deviceResponseMap = new HashMap<>();
for (DeviceDescriptor device : deviceList) {
DeviceResponse deviceResponse = DeviceResponse.createResponse(device.getName(), device.getId());
DeviceResponse deviceResponse = DeviceResponse.createResponse(device);
deviceResponseMap.put(device.getId(), deviceResponse);
}
response.type("application/json; charset=utf-8");
@@ -225,7 +270,7 @@ public class HueMulator {
Map<String, DeviceResponse> deviceList = new HashMap<>();
descriptorList.forEach(descriptor -> {
DeviceResponse deviceResponse = DeviceResponse.createResponse(descriptor.getName(), descriptor.getId());
DeviceResponse deviceResponse = DeviceResponse.createResponse(descriptor);
deviceList.put(descriptor.getId(), deviceResponse);
}
);
@@ -245,17 +290,60 @@ public class HueMulator {
DeviceDescriptor device = repository.findOne(lightId);
if (device == null) {
response.status(HttpStatus.SC_NOT_FOUND);
return null;
return "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + "\",\"description\": \"Object not found\"}}]";
} else {
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.status(HttpStatus.SC_OK);
return lightResponse;
}, 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;
boolean stateHasOn = false;
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);
if(request.body().contains("\"on\""))
stateHasOn = true;
} 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(), stateHasOn, lightId);
device.setDeviceState(state);
return responseString;
});
// http://ip_address:port/api/:userid/lights/:id/state CORS request
options(HUE_CONTEXT + "/:userid/lights/:id/state", "application/json", (request, response) -> {
response.status(HttpStatus.SC_OK);
@@ -275,7 +363,10 @@ public class HueMulator {
String lightId = request.params(":id");
String responseString = null;
String url = null;
NameValue[] theHeaders = null;
DeviceState state = null;
boolean stateHasBri = false;
boolean stateHasOn = false;
log.debug("hue state change requested: " + userId + " from " + request.ip() + " body: " + request.body());
response.header("Access-Control-Allow-Origin", request.headers("Origin"));
response.type("application/json; charset=utf-8");
@@ -283,58 +374,117 @@ public class HueMulator {
try {
state = mapper.readValue(request.body(), DeviceState.class);
if(request.body().contains("\"bri\""))
stateHasBri = true;
if(request.body().contains("\"on\""))
stateHasOn = true;
} 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.\"}}]";
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 + "\"}}]";
responseString = "[{\"error\":{\"type\": 3, \"address\": \"/lights/" + lightId + "\",\"description\": \"Could not find device\", \"resource\": \"/lights/" + lightId + "\"}}]";
return responseString;
}
state.fillIn();
theHeaders = new Gson().fromJson(device.getHeaders(), NameValue[].class);
responseString = this.formatSuccessHueResponse(state, request.body(), stateHasOn, 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);
}
// make call
responseString = doHttpRequest("http://"+deviceId.getIpAddress()+"/api/"+myHueHome.getTheHUERegisteredUser()+"/lights/"+deviceId.getDeviceId()+"/state", HttpPut.METHOD_NAME, device.getContentType(), request.body(), null);
if (responseString == null) {
log.warn("Error on calling url to change device state: " + url);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling HUE to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
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(stateHasBri)
{
if(state.getBri() > 0 && !state.isOn())
state.setOn(true);
url = device.getDimUrl();
if(url == null || url.length() == 0)
url = device.getOnUrl();
}
else
{
if (state.isOn()) {
responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":true}}";
url = device.getOnUrl();
} else if (request.body().contains("false")) {
responseString = "[{\"success\":{\"/lights/" + lightId + "/state/on\":false}}";
if(state.getBri() <= 0)
state.setBri(255);
} else {
url = device.getOffUrl();
state.setBri(0);
}
}
if(request.body().contains("bri"))
{
if(url == null)
{
url = device.getOnUrl();
responseString = "[";
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;
}
else
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/bri\":" + state.getBri() + "}}]";
}
else
responseString = responseString + "]";
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);
if(myHarmonyHome != null)
{
RunActivity anActivity = new Gson().fromJson(url, RunActivity.class);
HarmonyHandler myHarmony = myHarmonyHome.getHarmonyHandler(device.getTargetDevice());
if(myHarmony == null)
{
log.warn("Should not get here, no harmony hub available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else
myHarmony.startActivity(anActivity);
}
else {
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\"}}]";
}
}
else if(device.getDeviceType().toLowerCase().contains("button") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("harmonyButton")))
{
log.debug("executing HUE api request to button press(es) to Harmony: " + url);
if(myHarmonyHome != null)
{
if(url.substring(0, 1).equalsIgnoreCase("{")) {
url = "[" + url +"]";
}
@@ -343,42 +493,52 @@ public class HueMulator {
if(myHarmony == null)
{
log.warn("Should not get here, no harmony hub available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no harmony hub available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else {
for(int i = 0; i < deviceButtons.length; i++) {
if( i > 0)
Thread.sleep(100);
Thread.sleep(bridgeSettings.getButtonsleep());
log.debug("pressing button: " + deviceButtons[i].getDevice() + " - " + deviceButtons[i].getButton() + " - iteration: " + String.valueOf(i));
myHarmony.pressButton(deviceButtons[i]);
}
}
}
else {
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\"}}]";
}
}
else if(device.getDeviceType().toLowerCase().contains("home") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("nestHomeAway")))
{
log.debug("executing HUE api request to set away for nest home: " + url);
NestInstruction homeAway = new Gson().fromJson(url, NestInstruction.class);
if(theNest == null)
{
log.warn("Should not get here, no Nest available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else
else {
NestInstruction homeAway = new Gson().fromJson(url, NestInstruction.class);
theNest.getHome(homeAway.getName()).setAway(homeAway.getAway());
}
}
else if(device.getDeviceType().toLowerCase().contains("thermo") || (device.getMapType() != null && device.getMapType().equalsIgnoreCase("nestThermoSet")))
{
log.debug("executing HUE api request to set thermostat for nest: " + url);
NestInstruction thermoSetting = new Gson().fromJson(url, NestInstruction.class);
if(theNest == null)
{
log.warn("Should not get here, no Nest available");
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Should not get here, no Nest available\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
else {
NestInstruction thermoSetting = new Gson().fromJson(url, NestInstruction.class);
if(thermoSetting.getControl().equalsIgnoreCase("temp")) {
if(request.body().contains("bri")) {
thermoSetting.setTemp(String.valueOf(Math.round((Integer.parseInt(replaceIntensityValue(thermoSetting.getTemp(), state.getBri())) - 32)/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");
theNest.getThermostat(thermoSetting.getName()).setTargetTemperature(Float.parseFloat(thermoSetting.getTemp()));
}
@@ -393,50 +553,123 @@ public class HueMulator {
}
else {
log.warn("no valid Nest control info: " + thermoSetting.getControl());
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"no valid Nest control info\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"no valid Nest control info\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
}
}
else if(url.startsWith("udp://"))
else if(device.getDeviceType().startsWith("exec")) {
log.debug("Exec Request called with url: " + url);
if(!url.startsWith("[")) {
if(url.startsWith("{\"item"))
url = "[" + url + "]";
else
url = "[{\"item\":\"" + url +"\"}]";
}
CallItem[] callItems = new Gson().fromJson(url, CallItem[].class);
for(int i = 0; i < callItems.length; i++) {
if( i > 0) {
Thread.sleep(bridgeSettings.getButtonsleep());
}
String intermediate;
if(callItems[i].getItem().contains("exec://"))
intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
else
intermediate = callItems[i].getItem();
String anError = doExecRequest(intermediate, state, lightId);
if(anError != null) {
responseString = anError;
i = callItems.length+1;
}
}
}
else // This section allows the usage of http/tcp/udp/exec calls in a given set of items
{
log.debug("executing HUE api request to UDP: " + url);
log.debug("executing HUE api request for network call: " + url);
if(!url.startsWith("[")) {
if(url.startsWith("{\"item"))
url = "[" + url + "]";
else
url = "[{\"item\":\"" + url +"\"}]";
}
CallItem[] callItems = new Gson().fromJson(url, CallItem[].class);
for(int i = 0; i < callItems.length; i++) {
if( i > 0) {
Thread.sleep(bridgeSettings.getButtonsleep());
}
try {
String intermediate = url.substring(6);
String ipAddr = intermediate.substring(0, intermediate.indexOf(':'));
String port = intermediate.substring(intermediate.indexOf(':') + 1, intermediate.indexOf('/'));
String theBody = intermediate.substring(intermediate.indexOf('/')+1);
DatagramSocket responseSocket = new DatagramSocket(Integer.parseInt(port));
if(theBody.startsWith("0x")) {
sendData = DatatypeConverter.parseHexBinary(theBody.substring(2));
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
sendData = theBody.getBytes();
InetAddress IPAddress = InetAddress.getByName(ipAddr);
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();
} catch (IOException e) {
log.warn("Could not send UDP Datagram packet for request.", e);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
}
else
else if(callItems[i].getItem().contains("tcp://"))
{
log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + url);
// quick template
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 if(callItems[i].getItem().contains("exec://")) {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
String anError = doExecRequest(intermediate, state, lightId);
if(anError != null) {
responseString = anError;
i = callItems.length+1;
}
}
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;
url = replaceIntensityValue(url, state.getBri());
if (state.isOn())
body = replaceIntensityValue(device.getContentBody(), state.getBri());
body = replaceIntensityValue(device.getContentBody(), state.getBri(), false);
else
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri());
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri(), false);
// make call
if (!doHttpRequest(url, device.getHttpVerb(), device.getContentType(), body)) {
log.warn("Error on calling url to change device state: " + url);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + ",\"description\": \"Error on calling url to change device state\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
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;
});
}
@@ -449,17 +682,33 @@ public class HueMulator {
* 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
*/
protected String replaceIntensityValue(String request, int intensity){
protected String replaceIntensityValue(String request, int intensity, boolean isHex){
if(request == null){
return "";
}
if(request.contains(INTENSITY_BYTE)) {
if(isHex) {
BigInteger bigInt = BigInteger.valueOf(intensity);
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);
if(isHex) {
BigInteger bigInt = BigInteger.valueOf(percentBrightness);
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>();
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE));
@@ -470,7 +719,15 @@ public class HueMulator {
Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables);
Integer endResult = Math.round(result.floatValue());
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) {
log.warn("Could not execute Math: " + mathDescriptor, e);
}
@@ -479,9 +736,11 @@ public class HueMulator {
}
// This function executes the url from the device repository against the vera
protected boolean doHttpRequest(String url, String httpVerb, String contentType, String body) {
// This function executes the url from the device repository against the target as http or https as defined
protected String doHttpRequest(String url, String httpVerb, String contentType, String body, NameValue[] headers) {
HttpUriRequest request = null;
String theContent = null;
try {
if(HttpGet.METHOD_NAME.equalsIgnoreCase(httpVerb) || httpVerb == null) {
request = new HttpGet(url);
}else if(HttpPost.METHOD_NAME.equalsIgnoreCase(httpVerb)){
@@ -497,17 +756,127 @@ public class HueMulator {
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);
try {
HttpResponse response = httpClient.execute(request);
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
if(headers != null && headers.length > 0) {
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());
if(response.getStatusLine().getStatusCode() == 200){
return true;
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
if(response.getEntity() != null ) {
try {
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
} catch(Exception e) {
log.debug("Error ocurred in handling response entity after successful call, still responding success. "+ e.getMessage(), e);
}
}
if(theContent == null)
theContent = "";
}
} 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 doExecRequest(String anItem, DeviceState state, String lightId) {
log.debug("Executing request: " + anItem);
String responseString = null;
try {
Process p = Runtime.getRuntime().exec(replaceIntensityValue(anItem, state.getBri(), false));
log.debug("Process running: " + p.isAlive());
} catch (IOException e) {
log.warn("Could not execute request: " + anItem, e);
responseString = "[{\"error\":{\"type\": 6, \"address\": \"/lights/" + lightId + "\",\"description\": \"Error on calling out to device\", \"parameter\": \"/lights/" + lightId + "state\"}}]";
}
return responseString;
}
private String formatSuccessHueResponse(DeviceState state, String body, boolean stateHasOn, String lightId) {
String responseString = "[";
boolean justState = false;
if(stateHasOn)
{
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/on\":";
if (state.isOn()) {
responseString = responseString + "true}}";
if(state.getBri() <= 0)
state.setBri(255);
} else {
responseString = responseString + "false}}";
state.setBri(0);
}
justState = true;
}
if(body.contains("bri"))
{
if(justState)
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/bri\":" + state.getBri() + "}}";
justState = true;
}
if(body.contains("ct"))
{
if(justState)
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/ct\":" + state.getCt() + "}}";
justState = true;
}
if(body.contains("xy"))
{
if(justState)
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/xy\":" + state.getXy() + "}}";
justState = true;
}
if(body.contains("hue"))
{
if(justState)
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/hue\":" + state.getHue() + "}}";
justState = true;
}
if(body.contains("sat"))
{
if(justState)
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/sat\":" + state.getSat() + "}}";
justState = true;
}
if(body.contains("colormode"))
{
if(justState)
responseString = responseString + ",";
responseString = responseString + "{\"success\":{\"/lights/" + lightId + "/state/colormode\":" + state.getColormode() + "}}";
justState = true;
}
responseString = responseString + "]";
return responseString;
}
@Override
public void setErrorString(String anError) {
errorString = anError;
}
}

View File

@@ -3,7 +3,9 @@ package com.bwssystems.HABridge.upnp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeControlDescriptor;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.Configuration;
import java.io.IOException;
import java.net.*;
@@ -14,35 +16,67 @@ import org.apache.http.conn.util.*;
public class UpnpListener {
private Logger log = LoggerFactory.getLogger(UpnpListener.class);
private static final int UPNP_DISCOVERY_PORT = 1900;
private static final String UPNP_MULTICAST_ADDRESS = "239.255.255.250";
private int upnpResponsePort;
private int httpServerPort;
private String responseAddress;
private boolean strict;
private boolean traceupnp;
private BridgeControlDescriptor bridgeControl;
public UpnpListener(BridgeSettings theSettings) {
public UpnpListener(BridgeSettingsDescriptor theSettings, BridgeControlDescriptor theControl) {
super();
upnpResponsePort = Integer.valueOf(theSettings.getUpnpResponsePort());
upnpResponsePort = theSettings.getUpnpResponsePort();
httpServerPort = Integer.valueOf(theSettings.getServerPort());
responseAddress = theSettings.getUpnpConfigAddress();
strict = theSettings.isUpnpStrict();
traceupnp = theSettings.isTraceupnp();
bridgeControl = theControl;
}
public void startListening(){
@SuppressWarnings("resource")
public boolean startListening(){
log.info("UPNP Discovery Listener starting....");
DatagramSocket responseSocket = null;
MulticastSocket upnpMulticastSocket = null;
Enumeration<NetworkInterface> ifs = null;
try (DatagramSocket responseSocket = new DatagramSocket(upnpResponsePort);
MulticastSocket upnpMulticastSocket = new MulticastSocket(UPNP_DISCOVERY_PORT);) {
InetSocketAddress socketAddress = new InetSocketAddress(UPNP_MULTICAST_ADDRESS, UPNP_DISCOVERY_PORT);
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
boolean portLoopControl = true;
int retryCount = 0;
while(portLoopControl) {
try {
responseSocket = new DatagramSocket(upnpResponsePort);
if(retryCount > 0)
log.info("Upnp Response Port issue, found open port: " + upnpResponsePort);
portLoopControl = false;
} catch(SocketException e) {
if(retryCount == 0)
log.warn("Upnp Response Port is in use, starting loop to find open port for 20 tries - configured port is: " + upnpResponsePort);
if(retryCount >= 20) {
portLoopControl = false;
log.error("Upnp Response Port issue, could not find open port - last port tried: " + upnpResponsePort + " with message: " + e.getMessage());
return false;
}
}
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();
@@ -62,31 +96,59 @@ public class UpnpListener {
}
log.debug("Checking " + name + " to our interface set");
if (IPsPerNic > 0) {
try {
upnpMulticastSocket.joinGroup(socketAddress, xface);
if (traceupnp)
log.info("Traceupnp: Adding " + name + " to our interface set");
else
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....");
while(true){ //trigger shutdown here
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 opening sockets. Shutting down", e);
log.error("UpnpListener encountered an error sending upnp response packet. Shutting down", e);
error = true;
}
log.info("UPNP Discovery Listener Stopped");
}
} 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");
if (bridgeControl.isStop())
log.info("UPNP Discovery Listener - ended, stop found");
if (!bridgeControl.isStop() && !bridgeControl.isReinit()) {
log.info("UPNP Discovery Listener - ended, error found");
return false;
}
return bridgeControl.isReinit();
}
/**
@@ -94,7 +156,7 @@ public class UpnpListener {
*/
protected boolean isSSDPDiscovery(DatagramPacket packet){
//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\"")){
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);
@@ -138,9 +200,9 @@ public class UpnpListener {
String discoveryResponse = null;
discoveryResponse = String.format(discoveryTemplate, responseAddress, httpServerPort);
if(traceupnp)
log.info("Traceupnp: sendUpnpResponse: " + discoveryResponse);
log.info("Traceupnp: sendUpnpResponse discovery template with address: " + responseAddress + " and port: " + httpServerPort);
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);
socket.send(response);
}

View File

@@ -3,8 +3,7 @@ package com.bwssystems.HABridge.upnp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.JsonTransformer;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import static spark.Spark.get;
@@ -12,11 +11,9 @@ import static spark.Spark.get;
*
*/
public class UpnpSettingsResource {
private static final String UPNP_CONTEXT = "/upnp";
private Logger log = LoggerFactory.getLogger(UpnpSettingsResource.class);
private BridgeSettings theSettings;
private BridgeSettingsDescriptor theSettings;
private String hueTemplate = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<root xmlns=\"urn:schemas-upnp-org:device-1-0\">\n"
+ "<specVersion>\n" + "<major>1</major>\n" + "<minor>0</minor>\n" + "</specVersion>\n"
@@ -40,20 +37,9 @@ public class UpnpSettingsResource {
+ "<depth>24</depth>\n" + "<url>hue_logo_3.png</url>\n" + "</icon>\n" + "</iconList>\n" + "</device>\n"
+ "</root>\n";
public UpnpSettingsResource(BridgeSettings theBridgeSettings) {
public UpnpSettingsResource(BridgeSettingsDescriptor theBridgeSettings) {
super();
this.theSettings = new BridgeSettings();
this.theSettings.setDevMode(theBridgeSettings.isDevMode());
this.theSettings.setHarmonyAddress(theBridgeSettings.getHarmonyAddress());
this.theSettings.setServerPort(theBridgeSettings.getServerPort());
this.theSettings.setTraceupnp(theBridgeSettings.isTraceupnp());
this.theSettings.setUpnpConfigAddress(theBridgeSettings.getUpnpConfigAddress());
this.theSettings.setUpnpDeviceDb(theBridgeSettings.getUpnpDeviceDb());
this.theSettings.setUpnpResponseDevices(theBridgeSettings.getUpnpResponseDevices());
this.theSettings.setUpnpResponsePort(theBridgeSettings.getUpnpResponsePort());
this.theSettings.setUpnpStrict(theBridgeSettings.isUpnpStrict());
this.theSettings.setVeraAddress(theBridgeSettings.getVeraAddress());
this.theSettings.setNestConfigured(theBridgeSettings.isValidNest());
this.theSettings = theBridgeSettings;
}
public void setupServer() {
@@ -68,9 +54,9 @@ public class UpnpSettingsResource {
String filledTemplate = null;
filledTemplate = String.format(hueTemplate, theSettings.getUpnpConfigAddress(), portNumber, theSettings.getUpnpConfigAddress());
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
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("Pragma", "no-cache");
// response.header("Expires", "Mon, 1 Aug 2011 09:00:00 GMT");
@@ -86,14 +72,5 @@ public class UpnpSettingsResource {
return filledTemplate;
} );
// http://ip_address:port/upnp/settings which returns the bridge configuration settings
get(UPNP_CONTEXT + "/settings", "application/json", (request, response) -> {
log.debug("bridge settings requested from " + request.ip());
response.status(200);
return theSettings;
}, new JsonTransformer());
}
}

View File

@@ -8,7 +8,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.nest.controller.Home;
import com.bwssystems.nest.controller.Nest;
import com.bwssystems.nest.controller.NestSession;
@@ -23,13 +23,13 @@ public class NestHome {
private Nest theNest;
private ArrayList<NestItem> nestItems;
public NestHome(BridgeSettings bridgeSettings) {
public NestHome(BridgeSettingsDescriptor bridgeSettings) {
theSession = null;
theNest = null;
nestItems = null;
if(!bridgeSettings.isValidNest()) {
log.info("not a valid nest");
log.debug("not a valid nest");
return;
}
@@ -97,5 +97,14 @@ public class NestHome {
public Nest getTheNest() {
return theNest;
}
public void closeTheNest() {
if(theSession != null) {
theNest.endNestSession();
theNest = null;
theSession = null;
nestItems = null;
}
}
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.NestBridge;
import java.io.UnsupportedEncodingException;
public class NestItem {
private String name;
private String id;
@@ -9,7 +11,14 @@ public class NestItem {
return 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() {
return id;

View File

@@ -1,5 +1,7 @@
package com.bwssystems.harmony;
import java.io.UnsupportedEncodingException;
import net.whistlingfish.harmony.config.Activity;
public class HarmonyActivity {
@@ -15,6 +17,14 @@ public class HarmonyActivity {
return 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;
}

View File

@@ -1,5 +1,7 @@
package com.bwssystems.harmony;
import java.io.UnsupportedEncodingException;
import net.whistlingfish.harmony.config.Device;
public class HarmonyDevice {
@@ -9,6 +11,14 @@ public class HarmonyDevice {
return 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;
}
public String getHub() {

View File

@@ -119,4 +119,14 @@ public class HarmonyHandler {
return true;
}
public void shutdown() {
log.debug("Harmony api shutdown requested.");
if(devMode)
return;
harmonyClient.disconnect();
harmonyClient = null;
}
}

View File

@@ -10,7 +10,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.IpList;
import com.bwssystems.HABridge.NamedIP;
@@ -20,13 +20,15 @@ import net.whistlingfish.harmony.config.Device;
public class HarmonyHome {
private static final Logger log = LoggerFactory.getLogger(HarmonyHome.class);
private Map<String, HarmonyServer> hubs;
private Boolean isDevMode;
public HarmonyHome(BridgeSettings bridgeSettings) {
public HarmonyHome(BridgeSettingsDescriptor bridgeSettings) {
super();
isDevMode = Boolean.parseBoolean(System.getProperty("dev.mode", "false"));
hubs = new HashMap<String, HarmonyServer>();
if(!bridgeSettings.isValidHarmony() && !bridgeSettings.isDevMode())
if(!bridgeSettings.isValidHarmony() && !isDevMode)
return;
if(bridgeSettings.isDevMode()) {
if(isDevMode) {
NamedIP devModeIp = new NamedIP();
devModeIp.setIp("10.10.10.10");
devModeIp.setName("devMode");
@@ -40,7 +42,7 @@ public class HarmonyHome {
while(theList.hasNext()) {
NamedIP aHub = theList.next();
try {
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings, aHub));
hubs.put(aHub.getName(), HarmonyServer.setup(bridgeSettings, isDevMode, aHub));
} catch (Exception e) {
log.error("Cannot get harmony client (" + aHub.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
return;
@@ -48,6 +50,16 @@ public class HarmonyHome {
}
}
public void shutdownHarmonyHubs() {
if(isDevMode)
return;
Iterator<String> keys = hubs.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
hubs.get(key).getMyHarmony().shutdown();
}
}
public HarmonyHandler getHarmonyHandler(String aName) {
HarmonyHandler aHandler = null;
if(aName == null || aName.equals("")) {

View File

@@ -7,7 +7,7 @@ import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
import com.google.inject.Guice;
import com.google.inject.Injector;
@@ -26,7 +26,7 @@ public class HarmonyServer {
private DevModeResponse devResponse;
private OAReplyProvider dummyProvider;
private NamedIP myNameAndIP;
private Boolean isDevMode;
private Logger log = LoggerFactory.getLogger(HarmonyServer.class);
public HarmonyServer(NamedIP theHarmonyAddress) {
@@ -34,33 +34,35 @@ public class HarmonyServer {
myHarmony = null;
dummyProvider = null;
myNameAndIP = theHarmonyAddress;
isDevMode = false;
}
public static HarmonyServer setup(BridgeSettings bridgeSettings, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony() && !bridgeSettings.isDevMode()) {
public static HarmonyServer setup(BridgeSettingsDescriptor bridgeSettings, Boolean harmonyDevMode, NamedIP theHarmonyAddress) throws Exception {
if(!bridgeSettings.isValidHarmony() && harmonyDevMode) {
return new HarmonyServer(theHarmonyAddress);
}
Injector injector = null;
if(!bridgeSettings.isDevMode())
if(!harmonyDevMode)
injector = Guice.createInjector(new HarmonyClientModule());
HarmonyServer mainObject = new HarmonyServer(theHarmonyAddress);
if(!bridgeSettings.isDevMode())
if(!harmonyDevMode)
injector.injectMembers(mainObject);
mainObject.execute(bridgeSettings);
mainObject.execute(bridgeSettings, harmonyDevMode);
return mainObject;
}
private void execute(BridgeSettings mySettings) throws Exception {
private void execute(BridgeSettingsDescriptor mySettings, Boolean harmonyDevMode) throws Exception {
Boolean noopCalls = Boolean.parseBoolean(System.getProperty("noop.calls", "false"));
isDevMode = harmonyDevMode;
String modeString = "";
if(dummyProvider != null)
log.debug("something is very wrong as dummyProvider is not null...");
if(mySettings.isDevMode())
if(isDevMode)
modeString = " (development mode)";
else if(noopCalls)
modeString = " (no op calls to harmony)";
log.info("setup initiated " + modeString + "....");
if(mySettings.isDevMode())
if(isDevMode)
{
harmonyClient = null;
devResponse = new DevModeResponse();

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

@@ -0,0 +1,95 @@
package com.bwssystems.util;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class BackupHandler {
final Logger log = LoggerFactory.getLogger(BackupHandler.class);
private Path repositoryPath;
private String fileExtension;
private String defaultName;
protected void setupParams(Path aPath, String anExtension, String adefaultName) {
repositoryPath = aPath;
if(anExtension.substring(0, 1).equalsIgnoreCase("."))
fileExtension = anExtension;
else
fileExtension = "." + anExtension;
defaultName = adefaultName;
log.debug("setupParams has defaultName: " + defaultName + " and file extension as: " + fileExtension);
}
public String backup(String aFilename) {
if(aFilename == null || aFilename.equalsIgnoreCase("")) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
aFilename = defaultName + dateFormat.format(Calendar.getInstance().getTime()) + fileExtension;
}
else
aFilename = aFilename + fileExtension;
try {
Files.copy(repositoryPath, FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Could not backup to file: " + aFilename + " message: " + e.getMessage(), e);
}
log.debug("Backup repository: " + aFilename);
return aFilename;
}
public String deleteBackup(String aFilename) {
log.debug("Delete backup repository: " + aFilename);
try {
Files.delete(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename));
} catch (IOException e) {
log.error("Could not delete file: " + aFilename + " message: " + e.getMessage(), e);
}
return aFilename;
}
public String restoreBackup(String aFilename) {
log.debug("Restore backup repository: " + aFilename);
try {
Path target = null;
if(Files.exists(repositoryPath)) {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
target = FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), defaultName + dateFormat.format(Calendar.getInstance().getTime()) + fileExtension);
Files.move(repositoryPath, target);
}
Files.copy(FileSystems.getDefault().getPath(repositoryPath.getParent().toString(), aFilename), repositoryPath, StandardCopyOption.COPY_ATTRIBUTES);
} catch (IOException e) {
log.error("Error restoring the file: " + aFilename + " message: " + e.getMessage(), e);
return null;
}
return aFilename;
}
public List<String> getBackups() {
List<String> theFilenames = new ArrayList<String>();
Path dir = repositoryPath.getParent();
try (DirectoryStream<Path> stream =
Files.newDirectoryStream(dir, "*.{"+ fileExtension.substring(1) + "}")) {
for (Path entry: stream) {
theFilenames.add(entry.getFileName().toString());
}
} catch (IOException x) {
// IOException can never be thrown by the iteration.
// In this snippet, it can // only be thrown by newDirectoryStream.
log.warn("Issue getting directory listing for backups in directory: " + x.getMessage());
}
return theFilenames;
}
}

View File

@@ -1,4 +1,4 @@
package com.bwssystems.HABridge;
package com.bwssystems.util;
import com.google.gson.Gson;
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

@@ -9,23 +9,24 @@ import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.BridgeSettingsDescriptor;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.luupRequests.Device;
import com.bwssystems.luupRequests.Scene;
import com.bwssystems.luupRequests.Sdata;
public class VeraHome {
private static final Logger log = LoggerFactory.getLogger(VeraHome.class);
private Map<String, VeraInfo> veras;
public VeraHome(BridgeSettings bridgeSettings) {
public VeraHome(BridgeSettingsDescriptor bridgeSettings) {
veras = new HashMap<String, VeraInfo>();
if(!bridgeSettings.isValidVera())
return;
Iterator<NamedIP> theList = bridgeSettings.getVeraAddress().getDevices().iterator();
while(theList.hasNext()) {
NamedIP aVera = theList.next();
veras.put(aVera.getName(), new VeraInfo(aVera, bridgeSettings.isValidVera()));
veras.put(aVera.getName(), new VeraInfo(aVera));
}
}
@@ -35,11 +36,18 @@ public class VeraHome {
ArrayList<Device> deviceList = new ArrayList<Device>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Device> devices = veras.get(key).getSdata().getDevices().iterator();
Sdata theSdata = veras.get(key).getSdata();
if(theSdata != null) {
Iterator<Device> devices = theSdata.getDevices().iterator();
while(devices.hasNext()) {
deviceList.add(devices.next());
}
}
else {
deviceList = null;
break;
}
}
return deviceList;
}
public List<Scene> getScenes() {
@@ -48,11 +56,18 @@ public class VeraHome {
ArrayList<Scene> sceneList = new ArrayList<Scene>();
while(keys.hasNext()) {
String key = keys.next();
Iterator<Scene> scenes = veras.get(key).getSdata().getScenes().iterator();
Sdata theSdata = veras.get(key).getSdata();
if(theSdata != null) {
Iterator<Scene> scenes = theSdata.getScenes().iterator();
while(scenes.hasNext()) {
sceneList.add(scenes.next());
}
}
else {
sceneList = null;
break;
}
}
return sceneList;
}
}

View File

@@ -1,6 +1,7 @@
package com.bwssystems.vera;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Map;
@@ -27,26 +28,25 @@ public class VeraInfo {
private HttpClient httpClient;
private static final String SDATA_REQUEST = ":3480/data_request?id=sdata&output_format=json";
private NamedIP veraAddress;
private Boolean validVera;
public VeraInfo(NamedIP addressName, Boolean isValidVera) {
public VeraInfo(NamedIP addressName) {
super();
httpClient = HttpClients.createDefault();
veraAddress = addressName;
validVera = isValidVera;
}
public Sdata getSdata() {
if(!validVera)
return new Sdata();
Sdata theSdata = null;
String theUrl = "http://" + veraAddress.getIp() + SDATA_REQUEST;
String theData;
theData = doHttpGETRequest(theUrl);
Sdata theSdata = new Gson().fromJson(theData, Sdata.class);
if(theData != null) {
theSdata = new Gson().fromJson(theData, Sdata.class);
log.debug("GET sdata - full: " + theSdata.getFull() + ", version: " + theSdata.getVersion());
denormalizeSdata(theSdata);
}
return theSdata;
}
@@ -91,19 +91,19 @@ public class VeraInfo {
// 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);
String theContent = EntityUtils.toString(response.getEntity()); //read content for data
EntityUtils.consume(response.getEntity()); //close out inputstream ignore content
log.debug("GET on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() == 200){
return theContent;
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("Error calling out to HA gateway", e);
log.error("doHttpGETRequest: Error calling out to HA gateway: " + e.getMessage());
}
return null;
return theContent;
}
}

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>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
@-webkit-keyframes ngdialog-flyin{0%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes ngdialog-flyin{0%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes ngdialog-flyout{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes ngdialog-flyout{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);transform:translateY(-40px)}}.ngdialog.ngdialog-theme-default{padding-bottom:160px;padding-top:160px}.ngdialog.ngdialog-theme-default.ngdialog-closing .ngdialog-content{-webkit-animation:ngdialog-flyout .5s;animation:ngdialog-flyout .5s}.ngdialog.ngdialog-theme-default .ngdialog-content{-webkit-animation:ngdialog-flyin .5s;animation:ngdialog-flyin .5s;background:#f0f0f0;border-radius:5px;color:#444;font-family:Helvetica,sans-serif;font-size:1.1em;line-height:1.5em;margin:0 auto;max-width:100%;padding:1em;position:relative;width:450px}.ngdialog.ngdialog-theme-default .ngdialog-close{border-radius:5px;cursor:pointer;position:absolute;right:0;top:0}.ngdialog.ngdialog-theme-default .ngdialog-close:before{background:0 0;border-radius:3px;color:#bbb;content:'\00D7';font-size:26px;font-weight:400;height:30px;line-height:26px;position:absolute;right:3px;text-align:center;top:3px;width:30px}.ngdialog.ngdialog-theme-default .ngdialog-close:active:before,.ngdialog.ngdialog-theme-default .ngdialog-close:hover:before{color:#777}.ngdialog.ngdialog-theme-default .ngdialog-message{margin-bottom:.5em}.ngdialog.ngdialog-theme-default .ngdialog-input{margin-bottom:1em}.ngdialog.ngdialog-theme-default .ngdialog-input input[type=text],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=password],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=email],.ngdialog.ngdialog-theme-default .ngdialog-input input[type=url],.ngdialog.ngdialog-theme-default .ngdialog-input textarea{background:#fff;border:0;border-radius:3px;font-family:inherit;font-size:inherit;font-weight:inherit;margin:0 0 .25em;min-height:2.5em;padding:.25em .67em;width:100%}.ngdialog.ngdialog-theme-default .ngdialog-input input[type=text]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=password]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=email]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input input[type=url]:focus,.ngdialog.ngdialog-theme-default .ngdialog-input textarea:focus{box-shadow:inset 0 0 0 2px #8dbdf1;outline:0}.ngdialog.ngdialog-theme-default .ngdialog-buttons:after{content:'';display:table;clear:both}.ngdialog.ngdialog-theme-default .ngdialog-button{border:0;border-radius:3px;cursor:pointer;float:right;font-family:inherit;font-size:.8em;letter-spacing:.1em;line-height:1em;margin:0 0 0 .5em;padding:.75em 2em;text-transform:uppercase}.ngdialog.ngdialog-theme-default .ngdialog-button:focus{-webkit-animation:ngdialog-pulse 1.1s infinite;animation:ngdialog-pulse 1.1s infinite;outline:0}@media (max-width:568px){.ngdialog.ngdialog-theme-default .ngdialog-button:focus{-webkit-animation:none;animation:none}}.ngdialog.ngdialog-theme-default .ngdialog-button.ngdialog-button-primary{background:#3288e6;color:#fff}.ngdialog.ngdialog-theme-default .ngdialog-button.ngdialog-button-secondary{background:#e0e0e0;color:#777}

View File

@@ -0,0 +1 @@
.ngdialog,.ngdialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0}@-webkit-keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@keyframes ngdialog-fadeout{0%{opacity:1}100%{opacity:0}}@-webkit-keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}@keyframes ngdialog-fadein{0%{opacity:0}100%{opacity:1}}.ngdialog{box-sizing:border-box;overflow:auto;-webkit-overflow-scrolling:touch;z-index:10000}.ngdialog *,.ngdialog :after,.ngdialog :before{box-sizing:inherit}.ngdialog.ngdialog-disabled-animation,.ngdialog.ngdialog-disabled-animation .ngdialog-content,.ngdialog.ngdialog-disabled-animation .ngdialog-overlay{-webkit-animation:none!important;animation:none!important}.ngdialog-overlay{background:rgba(0,0,0,.4);-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadein .5s;animation:ngdialog-fadein .5s}.ngdialog-no-overlay{pointer-events:none}.ngdialog.ngdialog-closing .ngdialog-overlay{-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadeout .5s;animation:ngdialog-fadeout .5s}.ngdialog-content{background:#fff;-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadein .5s;animation:ngdialog-fadein .5s;pointer-events:all}.ngdialog.ngdialog-closing .ngdialog-content{-webkit-backface-visibility:hidden;-webkit-animation:ngdialog-fadeout .5s;animation:ngdialog-fadeout .5s}.ngdialog-close:before{font-family:Helvetica,Arial,sans-serif;content:'\00D7';cursor:pointer}body.ngdialog-open,html.ngdialog-open{overflow:hidden}

View File

@@ -0,0 +1,7 @@
/*!
* ngToast v1.5.6 (http://tameraydin.github.io/ngToast)
* Copyright 2015 Tamer Aydin (http://tamerayd.in)
* Licensed under MIT (http://tameraydin.mit-license.org/)
*/
.ng-toast{position:fixed;z-index:1080;width:100%;height:0;margin-top:20px;text-align:center}.ng-toast.ng-toast--top,.ng-toast.ng-toast--top .ng-toast__list{top:0;bottom:auto}.ng-toast.ng-toast--top.ng-toast--center .ng-toast__list{position:static}.ng-toast.ng-toast--bottom,.ng-toast.ng-toast--bottom .ng-toast__list{top:auto;bottom:0}.ng-toast.ng-toast--bottom.ng-toast--center .ng-toast__list{pointer-events:none}.ng-toast.ng-toast--bottom.ng-toast--center .ng-toast__message .alert{pointer-events:auto}.ng-toast.ng-toast--right .ng-toast__list{left:auto;right:0;margin-right:20px}.ng-toast.ng-toast--right .ng-toast__message{text-align:right}.ng-toast.ng-toast--left .ng-toast__list{right:auto;left:0;margin-left:20px}.ng-toast.ng-toast--left .ng-toast__message{text-align:left}.ng-toast .ng-toast__list{display:inline-block;position:absolute;right:0;left:0;margin:0 auto;padding:0;list-style:none}.ng-toast .ng-toast__message{display:block;width:100%;text-align:center}.ng-toast .ng-toast__message .alert{display:inline-block}.ng-toast .ng-toast__message__count{display:inline-block;margin:0 15px 0 5px}

View File

@@ -0,0 +1,2 @@
/*! angularjs-slider - v2.8.0 - (c) Rafal Zajac <rzajac@gmail.com>, Valentin Hervieu <valentin@hervieu.me>, Jussi Saarivirta <jusasi@gmail.com>, Angelin Sirbu <angelin.sirbu@gmail.com> - https://github.com/angular-slider/angularjs-slider - 2016-02-08 */
rzslider{position:relative;display:inline-block;width:100%;height:4px;margin:35px 0 15px 0;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}rzslider[disabled]{cursor:not-allowed}rzslider[disabled] .rz-pointer{cursor:not-allowed;background-color:#d8e0f3}rzslider span{position:absolute;display:inline-block;white-space:nowrap}rzslider .rz-base{width:100%;height:100%;padding:0}rzslider .rz-bar-wrapper{left:0;z-index:1;width:100%;height:32px;padding-top:16px;margin-top:-16px;box-sizing:border-box}rzslider .rz-bar-wrapper.rz-draggable{cursor:move}rzslider .rz-bar{left:0;z-index:1;width:100%;height:4px;background:#d8e0f3;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-bar.rz-selection{z-index:2;background:#0db9f0;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px}rzslider .rz-pointer{top:-14px;z-index:3;width:32px;height:32px;cursor:pointer;background-color:#0db9f0;-webkit-border-radius:16px;-moz-border-radius:16px;border-radius:16px}rzslider .rz-pointer:after{position:absolute;top:12px;left:12px;width:8px;height:8px;background:#fff;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;content:''}rzslider .rz-pointer:hover:after{background-color:#fff}rzslider .rz-pointer.rz-active{z-index:4}rzslider .rz-pointer.rz-active:after{background-color:#451aff}rzslider .rz-bubble{bottom:16px;padding:1px 3px;color:#55637d;cursor:default}rzslider .rz-bubble.rz-selection{top:16px}rzslider .rz-bubble.rz-limit{color:#55637d}rzslider .rz-ticks{position:absolute;top:-3px;left:0;z-index:1;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;height:0;padding:0 11px;margin:0;list-style:none;box-sizing:border-box;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}rzslider .rz-ticks .tick{width:10px;height:10px;text-align:center;cursor:pointer;background:#d8e0f3;border-radius:50%}rzslider .rz-ticks .tick.selected{background:#0db9f0}rzslider .rz-ticks .tick .tick-value{position:absolute;top:-30px;transform:translate(-50%,0)}rzslider.vertical{position:relative;width:4px;height:100%;padding:0;margin:0 20px;vertical-align:baseline}rzslider.vertical .rz-base{width:100%;height:100%;padding:0}rzslider.vertical .rz-bar-wrapper{top:auto;left:0;width:32px;height:100%;padding:0 0 0 16px;margin:0 0 0 -16px}rzslider.vertical .rz-bar{bottom:0;left:auto;width:4px;height:100%}rzslider.vertical .rz-pointer{top:auto;bottom:0;left:-14px!important}rzslider.vertical .rz-bubble{bottom:0;left:16px!important;margin-left:3px}rzslider.vertical .rz-bubble.rz-selection{top:auto;left:16px!important}rzslider.vertical .rz-ticks{top:0;left:-3px;z-index:1;width:0;height:100%;padding:11px 0;-webkit-flex-direction:column-reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}rzslider.vertical .rz-ticks .tick{vertical-align:middle}rzslider.vertical .rz-ticks .tick .tick-value{top:auto;right:-30px;transform:translate(0,-28%)}

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

@@ -7,6 +7,12 @@
<title>HA Bridge</title>
<link href="css/main.css" rel="stylesheet">
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/bootstrap-theme.min.css" rel="stylesheet">
<link href="css/ngToast.min.css" rel="stylesheet">
<link href="css/rzslider.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/scrollable-table.css" rel="stylesheet">
<!--[if lt IE 9]>
<script type="text/javascript" src="js/html5shiv.min.js"></script>
@@ -14,14 +20,16 @@
<![endif]-->
</head>
<body>
<toast></toast>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container" ng-controller="VersionController">
<div class="navbar-header">
<button type="button" class="navbar-toggle">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">HA Bridge</a>
</div>
@@ -31,10 +39,8 @@
<li><a href="http://echo.amazon.com/#cards" target="_blank">My Echo</a></li>
<li><a href="https://github.com/bwssytems/ha-bridge/blob/master/README.md" target="_blank">Help</a></li>
<li class="dropdown">
<a id="dropdownMenu1" href="" class="dropdown-toggle"
data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">About <span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<a id="dLabel" href="" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">About <span class="caret"></span></a>
<ul class="dropdown-menu" aria-labelledby="dLabel">
<li><a href="http://www.bwssystems.com" target="_blank">Developed by BWS Systems</a></li>
<li><a href="http://www.amazon.com/echo" target="_blank">Amazon Echo</a></li>
<li><a href="">HA Bridge Version {{bridge.habridgeversion}}</a></li>
@@ -50,11 +56,15 @@
</div>
<script src="js/jquery-1.11.3.min.js"></script>
<script src="js/angular.min.js"></script>
<script src="js/angular-route.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/rzslider.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>
</body>
</html>

View File

@@ -0,0 +1,16 @@
/*
AngularJS v1.4.3
(c) 2010-2015 Google, Inc. http://angularjs.org
License: MIT
*/
(function(n,h,p){'use strict';function E(a){var f=[];r(f,h.noop).chars(a);return f.join("")}function g(a,f){var d={},c=a.split(","),b;for(b=0;b<c.length;b++)d[f?h.lowercase(c[b]):c[b]]=!0;return d}function F(a,f){function d(a,b,d,l){b=h.lowercase(b);if(s[b])for(;e.last()&&t[e.last()];)c("",e.last());u[b]&&e.last()==b&&c("",b);(l=v[b]||!!l)||e.push(b);var m={};d.replace(G,function(b,a,f,c,d){m[a]=q(f||c||d||"")});f.start&&f.start(b,m,l)}function c(b,a){var c=0,d;if(a=h.lowercase(a))for(c=e.length-
1;0<=c&&e[c]!=a;c--);if(0<=c){for(d=e.length-1;d>=c;d--)f.end&&f.end(e[d]);e.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,e=[],m=a,l;for(e.last=function(){return e[e.length-1]};a;){l="";k=!0;if(e.last()&&w[e.last()])a=a.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*"+e.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");f.chars&&f.chars(q(b));return""}),c("",e.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",
b)===b&&(f.comment&&f.comment(a.substring(4,b)),a=a.substring(b+3),k=!1);else if(x.test(a)){if(b=a.match(x))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(y))a=a.substring(b[0].length),b[0].replace(y,c),k=!1}else K.test(a)&&((b=a.match(z))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(z,d)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),f.chars&&f.chars(q(l)))}if(a==m)throw L("badparse",a);m=a}c()}function q(a){if(!a)return"";A.innerHTML=
a.replace(/</g,"&lt;");return A.textContent}function B(a){return a.replace(/&/g,"&amp;").replace(M,function(a){var d=a.charCodeAt(0);a=a.charCodeAt(1);return"&#"+(1024*(d-55296)+(a-56320)+65536)+";"}).replace(N,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"&lt;").replace(/>/g,"&gt;")}function r(a,f){var d=!1,c=h.bind(a,a.push);return{start:function(a,k,e){a=h.lowercase(a);!d&&w[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(k,function(d,e){var k=h.lowercase(e),g="img"===a&&"src"===k||
"background"===k;!0!==O[k]||!0===D[k]&&!f(d,g)||(c(" "),c(e),c('="'),c(B(d)),c('"'))}),c(e?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||c(B(a))}}}var L=h.$$minErr("$sanitize"),z=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,y=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^</,J=/^<\//,H=/\x3c!--(.*?)--\x3e/g,x=/<!DOCTYPE([^>]*?)>/i,
I=/<!\[CDATA\[(.*?)]]\x3e/g,M=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,N=/([^\#-~| |!])/g,v=g("area,br,col,hr,img,wbr");n=g("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");p=g("rp,rt");var u=h.extend({},p,n),s=h.extend({},n,g("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")),t=h.extend({},p,g("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
n=g("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,stop,svg,switch,text,title,tspan,use");var w=g("script,style"),C=h.extend({},v,s,t,u,n),D=g("background,cite,href,longdesc,src,usemap,xlink:href");n=g("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width");
p=g("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan",
!0);var O=h.extend({},D,p,n),A=document.createElement("pre");h.module("ngSanitize",[]).provider("$sanitize",function(){this.$get=["$$sanitizeUri",function(a){return function(f){var d=[];F(f,r(d,function(c,b){return!/^unsafe/.test(a(c,b))}));return d.join("")}}]});h.module("ngSanitize").filter("linky",["$sanitize",function(a){var f=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,d=/^mailto:/i;return function(c,b){function k(a){a&&g.push(E(a))}function e(a,
c){g.push("<a ");h.isDefined(b)&&g.push('target="',b,'" ');g.push('href="',a.replace(/"/g,"&quot;"),'">');k(c);g.push("</a>")}if(!c)return c;for(var m,l=c,g=[],n,p;m=l.match(f);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),e(n,m[0].replace(d,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular);
//# sourceMappingURL=angular-sanitize.min.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/*!
* ngToast v1.5.6 (http://tameraydin.github.io/ngToast)
* Copyright 2015 Tamer Aydin (http://tamerayd.in)
* Licensed under MIT (http://tameraydin.mit-license.org/)
*/
!function(a,b,c){"use strict";b.module("ngToast.provider",[]).provider("ngToast",[function(){function a(a){for(var d=Math.floor(1e3*Math.random());c.indexOf(d)>-1;)d=Math.floor(1e3*Math.random());this.id=d,this.count=0,this.animation=e.animation,this.className=e.className,this.additionalClasses=e.additionalClasses,this.dismissOnTimeout=e.dismissOnTimeout,this.timeout=e.timeout,this.dismissButton=e.dismissButton,this.dismissButtonHtml=e.dismissButtonHtml,this.dismissOnClick=e.dismissOnClick,this.compileContent=e.compileContent,b.extend(this,a)}var c=[],d=[],e={animation:!1,className:"success",additionalClasses:null,dismissOnTimeout:!0,timeout:4e3,dismissButton:!1,dismissButtonHtml:"&times;",dismissOnClick:!0,compileContent:!1,combineDuplications:!1,horizontalPosition:"right",verticalPosition:"top",maxNumber:0};this.configure=function(a){b.extend(e,a)},this.$get=[function(){var b=function(a,b){return b="object"==typeof b?b:{content:b},b.className=a,this.create(b)};return{settings:e,messages:c,dismiss:function(a){if(a){for(var b=c.length-1;b>=0;b--)if(c[b].id===a)return c.splice(b,1),void d.splice(d.indexOf(a),1)}else{for(;c.length>0;)c.pop();d=[]}},create:function(b){if(b="object"==typeof b?b:{content:b},e.combineDuplications)for(var f=d.length-1;f>=0;f--){var g=c[f],h=b.className||"success";if(g.content===b.content&&g.className===h)return void c[f].count++}e.maxNumber>0&&d.length>=e.maxNumber&&this.dismiss(d[0]);var i=new a(b);return"bottom"===e.verticalPosition?c.unshift(i):c.push(i),d.push(i.id),i.id},success:function(a){return b.call(this,"success",a)},info:function(a){return b.call(this,"info",a)},warning:function(a){return b.call(this,"warning",a)},danger:function(a){return b.call(this,"danger",a)}}}]}])}(window,window.angular),function(a,b){"use strict";b.module("ngToast.directives",["ngToast.provider"]).run(["$templateCache",function(a){a.put("ngToast/toast.html",'<div class="ng-toast ng-toast--{{hPos}} ng-toast--{{vPos}} {{animation ? \'ng-toast--animate-\' + animation : \'\'}}"><ul class="ng-toast__list"><toast-message ng-repeat="message in messages" message="message" count="message.count"><span ng-bind-html="message.content"></span></toast-message></ul></div>'),a.put("ngToast/toastMessage.html",'<li class="ng-toast__message {{message.additionalClasses}}"ng-mouseenter="onMouseEnter()"ng-mouseleave="onMouseLeave()"><div class="alert alert-{{message.className}}" ng-class="{\'alert-dismissible\': message.dismissButton}"><button type="button" class="close" ng-if="message.dismissButton" ng-bind-html="message.dismissButtonHtml" ng-click="!message.dismissOnClick && dismiss()"></button><span ng-if="count" class="ng-toast__message__count">{{count + 1}}</span><span ng-if="!message.compileContent" ng-transclude></span></div></li>')}]).directive("toast",["ngToast","$templateCache","$log",function(a,b,c){return{replace:!0,restrict:"EA",templateUrl:"ngToast/toast.html",compile:function(d,e){if(e.template){var f=b.get(e.template);f?d.replaceWith(f):c.warn("ngToast: Provided template could not be loaded. Please be sure that it is populated before the <toast> element is represented.")}return function(b){b.hPos=a.settings.horizontalPosition,b.vPos=a.settings.verticalPosition,b.animation=a.settings.animation,b.messages=a.messages}}}}]).directive("toastMessage",["$timeout","$compile","ngToast",function(a,b,c){return{replace:!0,transclude:!0,restrict:"EA",scope:{message:"=",count:"="},controller:["$scope","ngToast",function(a,b){a.dismiss=function(){b.dismiss(a.message.id)}}],templateUrl:"ngToast/toastMessage.html",link:function(d,e,f,g,h){e.attr("data-message-id",d.message.id);var i,j=d.message.compileContent;if(d.cancelTimeout=function(){a.cancel(i)},d.startTimeout=function(){d.message.dismissOnTimeout&&(i=a(function(){c.dismiss(d.message.id)},d.message.timeout))},d.onMouseEnter=function(){d.cancelTimeout()},d.onMouseLeave=function(){d.startTimeout()},j){var k;h(d,function(a){k=a,e.children().append(k)}),a(function(){b(k.contents())("boolean"==typeof j?d.$parent:j,function(a){k.replaceWith(a)})},0)}d.startTimeout(),d.message.dismissOnClick&&e.bind("click",function(){c.dismiss(d.message.id),d.$apply()})}}}])}(window,window.angular),function(a,b){"use strict";b.module("ngToast",["ngSanitize","ngToast.directives","ngToast.provider"])}(window,window.angular);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,140 +1,55 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation" class="active"><a href="#">Configuration</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="#/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 ng-controller="ErrorsController">
<div ng-if="bridge.error"
class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h2 ng-show='bridge.error != ""'>ERROR</h2>
<div ng-show='bridge.error != ""'>{{bridge.error}}</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">Current devices ({{bridge.devices.length}}) </h2>
</div>
<scrollable-table watch="bridge.devices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</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('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 sortable-header col="id">ID</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="deviceType">Type</th>
<th sortable-header col="targetDevice">Target</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="device in bridge.devices | orderBy:predicate:reverse">
<tr ng-repeat="device in bridge.devices">
<td>{{$index+1}}</td>
<td>{{device.id}}</td>
<td>{{device.name}}</td>
<td>{{device.deviceType}}</td>
<td>{{device.targetDevice}}</td>
<td>
<p>
<button class="btn btn-info" type="submit"
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"
ng-click="testUrl(device, 'off')">Test OFF</button>
<button class="btn btn-warning" type="submit"
ng-click="editDevice(device)">Edit</button>
ng-click="editDevice(device)">Edit/Copy</button>
<button class="btn btn-danger" type="submit"
ng-click="deleteDevice(device)">Delete</button>
</p>
</td>
</tr>
</table>
</div>
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h1 class="panel-title">Bridge Settings <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1>
</div>
<div ng-if="visible" class="animate-if" class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="bridge-base">Bridge
server</label>
<div class="col-xs-8 col-sm-7">
<input id="bridge-base" class="form-control" type="text"
ng-model="bridge.base" placeholder="URL to bridge">
</div>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="setBridgeUrl(bridge.base)">Load</button>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="goBridgeUrl(bridge.base)">Go</button>
</div>
</form>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Setting</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>upnp.config.address</td>
<td>{{bridge.settings.upnpconfigaddress}}</td>
</tr>
<tr>
<td>server.port</td>
<td>{{bridge.settings.serverport}}</td>
</tr>
<tr>
<td>upnp.devices.db</td>
<td>{{bridge.settings.upnpdevicedb}}</td>
</tr>
<tr>
<td>upnp.response.port</td>
<td>{{bridge.settings.upnpresponseport}}</td>
</tr>
<tr>
<td>vera.address</td>
<td>{{bridge.settings.veraaddress}}</td>
</tr>
<tr>
<td>harmony.address</td>
<td>{{bridge.settings.harmonyaddress}}</td>
</tr>
<tr>
<td>upnp.strict</td>
<td>{{bridge.settings.upnpstrict}}</td>
</tr>
<tr>
<td>trace.upnp</td>
<td>{{bridge.settings.traceupnp}}</td>
</tr>
<tr>
<td>dev.mode</td>
<td>{{bridge.settings.devmode}}</td>
</tr>
<tr>
<td>nest.configured</td>
<td>{{bridge.settings.nestconfigured}}</td>
</tr>
</table>
</div>
</scrollable-table>
</div>
<div class="panel panel-default backup">
<div class="panel-heading">
@@ -173,3 +88,29 @@
</div>
</div>
<script type="text/ng-template" id="valueDialog">
<div class="ngdialog-message">
<h2>Select value</h2>
<p>
<input type="radio" ng-model="valueType" value="percentage" ng-change="changeScale()"> Percentage
<input type="radio" ng-model="valueType" value="raw" ng-change="changeScale()"> Raw
</p>
<p>
<rzslider rz-slider-model="slider.value" rz-slider-options="slider.options"></rzslider>
</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-primary" ng-click="setValue()">Set</button>
</div>
</script>
<script type="text/ng-template" id="deleteDialog">
<div class="ngdialog-message">
<h2>Device to Delete?</h2>
<p>{{device.name}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteDevice(device)">Delete</button>
</div>
</script>

View File

@@ -1,20 +1,24 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/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>
<li role="presentation" class="active"><a href="#/editdevice">Edit Device</a></li>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel-heading">
<h2 class="panel-title">Edit a device</h2>
<h2 class="panel-title">Edit/Copy a device</h2>
</div>
<p class="text-muted">This screen allows the modification of many fields the bridge uses. Please use care when
updating these fields as you may break the settings used by the bridge to call a specific end point device.</p>
<p>When copying, update the name and select the "Add Bridge Device" Button.</p>
<ul class="list-group">
<li class="list-group-item">
<form class="form-horizontal">
@@ -26,7 +30,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()">
<button type="submit" class="col-xs-4 col-sm-2 btn btn-success" ng-click="addDevice()">
Update Bridge Device</button>
</div>
<div class="form-group">
@@ -37,8 +41,30 @@
<input type="text" class="form-control" id="device-target"
ng-model="device.targetDevice" placeholder="default">
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
<button class="btn btn-primary" ng-click="copyDevice()">
Add Bridge Device</button>
</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="row">
@@ -54,8 +80,11 @@
<option value="harmonyButton">Harmony Button</option>
<option value="nestHomeAway">Nest Home Status</option>
<option value="nestThermoSet">Nest Thermostat</option>
<option value="hueDevice">Hue Device</option>
</select>
</div>
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</div>
</div>
<div ng-if="device.mapType" class="form-group">
@@ -78,6 +107,17 @@
</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="row">
<label class="col-xs-12 col-sm-2 control-label"
@@ -89,6 +129,17 @@
</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="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-http-verb">Http Verb

View File

@@ -1,10 +1,13 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/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" class="active"><a href="#/editor">Manual Add</a></li>
</ul>
@@ -75,7 +78,10 @@
<h2 class="panel-title">Add a new device</h2>
</div>
<p class="text-muted">This area allows you to create any http or udp call to an endpoint. You can use the default GET or select
the http verb type below and configure a payload for either on or off methods. Currently, https is not supported.</p>
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">
<li class="list-group-item">
<form class="form-horizontal">
@@ -92,6 +98,22 @@
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>
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-on-url">On
@@ -105,6 +127,17 @@
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="row">
<label class="col-xs-12 col-sm-2 control-label"
@@ -116,6 +149,17 @@
</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="row">
<label class="col-xs-12 col-sm-2 control-label" for="device-http-verb">Http Verb

View File

@@ -1,14 +1,17 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/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 role="presentation" class="active"><a href="#/harmonyactivities">Harmony Activities</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.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" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Harmony Activity List</h2>
</div>
@@ -18,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.
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">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="hub">Hub</th>
<th>Actions</th>
</tr>
</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>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
<td>{{harmonyactivity.hub}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildActivityUrls(harmonyactivity)">Generate
Activity URLs</button>
ng-click="buildActivityUrls(harmonyactivity)">Generate Bridge Device</button>
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
<div class="panel-heading">
@@ -56,26 +51,18 @@
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<scrollable-table watch="bridge.harmonyactivities">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="hub">Hub</th>
<th>Actions</th>
</tr>
</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>{{harmonyactivity.activity.label}}</td>
<td>{{harmonyactivity.activity.id}}</td>
@@ -84,10 +71,11 @@
ng-click="deleteDeviceByMapId(harmonyactivity.activity.id, 'harmonyActivity')">Delete</button></td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Harmony Activity</h2>
</div>
@@ -133,3 +121,13 @@
</li>
</ul>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -1,14 +1,17 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/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 role="presentation"><a href="#/harmonyactivities">Harmony Activities</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.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" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Harmony Device List</h2>
</div>
@@ -19,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.
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">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('label')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('hub')">Hub</a>
<span class="sortorder" ng-show="predicate === 'hub'" ng-class="{reverse:reverse}"></span>
</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="hub">Hub</th>
<th>On Button</th>
<th>Off Button</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="harmonydevice in bridge.harmonydevices | orderBy:predicate:reverse">
<tr ng-repeat="harmonydevice in bridge.harmonydevices">
<td>{{$index+1}}</td>
<td>{{harmonydevice.device.label}}</td>
<td>{{harmonydevice.device.id}}</td>
@@ -48,24 +43,24 @@
<td>
<select name="device-ctrlon" id="device-ctrlon" ng-model="devicectrlon">
<optgroup ng-repeat="ctrlon in harmonydevice.device.controlGroup" label="{{ctrlon.name}}">
<option ng-repeat="funcon in ctrlon.function" value="{{funcon.label}}">{{funcon.name}}</option>
<option ng-repeat="funcon in ctrlon.function" value="{{funcon.action}}">{{funcon.label}}</option>
</optgroup >
</select>
</td>
<td>
<select name="device-ctrloff" id="device-ctrloff" ng-model="devicectrloff">
<optgroup ng-repeat="ctrloff in harmonydevice.device.controlGroup" label="{{ctrloff.name}}">
<option ng-repeat="funcoff in ctrloff.function" value="{{funcoff.label}}">{{funcoff.name}}</option>
<option ng-repeat="funcoff in ctrloff.function" value="{{funcoff.action}}">{{funcoff.label}}</option>
</optgroup >
</select>
</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Build
A Button</button>
ng-click="buildButtonUrls(harmonydevice, devicectrlon, devicectrloff)">Build A Button</button>
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
<div class="panel-heading">
@@ -73,26 +68,15 @@
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<scrollable-table watch="bridge.harmonydevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">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 sortable-header col="name">Name</th>
<th sortable-header col="id">Device Id</th>
<th sortable-header col="targetDevice">Hub</th>
<th>Harmony Device-Button On-Button Off</th>
<th>Actions</th>
</tr>
</thead>
@@ -108,10 +92,11 @@
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for Harmony Buttons</h2>
</div>
@@ -157,3 +142,13 @@
</li>
</ul>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,129 @@
<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">
<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.device.uniqueid}}" ng-checked="bulk.devices.indexOf(huedevice.device.uniqueid) > -1" ng-click="toggleSelection(huedevice.device.uniqueid)"> {{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="bulkAddDevices()">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.device.uniqueid, 'hueDevice')">Delete</button>
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer">
<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>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

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,14 +1,17 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/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 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>
</ul>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Nest Items List</h2>
</div>
@@ -18,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.
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">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('type')">Type</a>
<span class="sortorder" ng-show="predicate === 'type'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('location')">Location</a>
<span class="sortorder" ng-show="predicate === 'location'" ng-class="{reverse:reverse}"></span>
</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="type">Type</th>
<th sortable-header col="location">Location</th>
<th>Actions</th>
</tr>
</thead>
@@ -70,6 +65,7 @@
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
<div class="panel-heading">
@@ -77,22 +73,14 @@
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<scrollable-table watch="bridge.nestitems">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('mapId')">mapId</a>
<span class="sortorder" ng-show="predicate === 'mapId'" ng-class="{reverse:reverse}"></span>
</th>
<th sortable-header col="name">Name</th>
<th sortable-header col="id">Device Id</th>
<th>mapId</th>
<th>Actions</th>
</tr>
</thead>
@@ -105,10 +93,11 @@
ng-click="deleteDeviceByMapId(device.mapId, 'nest')">Delete</button></td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Nest Item</h2>
</div>
@@ -154,3 +143,13 @@
</li>
</ul>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -0,0 +1,247 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Bridge Devices</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="#/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">Bridge Settings</h1>
</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="bridge-base">Bridge
server</label>
<div class="col-xs-8 col-sm-7">
<input id="bridge-base" class="form-control" type="text"
ng-model="bridge.base" placeholder="URL to bridge">
</div>
<button type="submit" class="col-xs-2 col-sm-1 btn btn-primary"
ng-click="goBridgeUrl(bridge.base)">Test</button>
</div>
</form>
<form name="form">
<p>
<button ng-disabled="form.$pristine || bridge.isInControl" class="btn btn-success" type="submit"
ng-click="saveSettings()">Save</button>
<button ng-disabled="bridge.isInControl" class="btn btn-warning" type="submit"
ng-click="bridgeReinit()">Bridge Reinitialize</button>
<button ng-disabled="bridge.isInControl" class="btn btn-danger" type="submit"
ng-click="bridgeStop()">Bridge Stop</button>
<button class="btn btn-primary" type="submit"
onclick="myRefresh()">Refresh</button>
<script>
function myRefresh() {
location.reload();
}
</script>
</p>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Setting</th>
<th>Value</th>
</tr>
</thead>
<tr>
<td>Configuration Path and File</td>
<td><input id="bridge-settings-configfile" class="form-control" type="text"
ng-model="bridge.settings.configfile" placeholder="data/ha-bridge.config"></td>
</tr>
<tr>
<td>Device DB Path and File</td>
<td><input id="bridge-settings-upnpdevicedb" class="form-control" type="text"
ng-model="bridge.settings.upnpdevicedb" placeholder="data/device.db"></td>
</tr>
<tr>
<td>UPNP IP Address</td>
<td><input id="bridge-settings-upnpconfigaddress" class="form-control" type="text"
ng-model="bridge.settings.upnpconfigaddress" placeholder="192.168.1.1"></td>
</tr>
<tr>
<td>Web Server Port</td>
<td><input id="bridge-settings-serverport" class="form-control" type="number"
ng-model="bridge.settings.serverport" min="1" max="65535"></td>
</tr>
<tr>
<td>UPNP Response Port</td>
<td><input id="bridge-settings-upnpresponseport" class="form-control" type="number"
ng-model="bridge.settings.upnpresponseport" min="1" max="65535"></td>
</tr>
<tr>
<td>Vera Names and IP Addresses</td>
<td><table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="vera in bridge.settings.veraaddress.devices">
<td>{{vera.name}}</td>
<td>{{vera.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeVeratoSettings(vera.name, vera.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-vera-name" class="form-control" type="text"
ng-model="newveraname" placeholder="A Vera"></td>
<td><input id="bridge-settings-next-vera-ip" class="form-control" type="text"
ng-model="newveraip" placeholder="192.168.1.2"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addVeratoSettings(newveraname, newveraip)">Add</button></td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Harmony Names and IP Addresses</td>
<td><table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>IP</th>
<th>Manage</th>
</tr>
</thead>
<tr ng-repeat="harmony in bridge.settings.harmonyaddress.devices">
<td>{{harmony.name}}</td>
<td>{{harmony.ip}}</td>
<td><button class="btn btn-danger" type="submit"
ng-click="removeHarmonytoSettings(harmony.name, harmony.ip)">Del</button></td>
</tr>
<tr>
<td><input id="bridge-settings-next-harmony-name" class="form-control" type="text"
ng-model="newharmonyname" placeholder="A Harmony"></td>
<td><input id="bridge-settings-next-harmony-ip" class="form-control" type="text"
ng-model="newharmonyip" placeholder="192.168.1.3"></td>
<td><button class="btn btn-success" type="submit"
ng-click="addHarmonytoSettings(newharmonyname, newharmonyip)">Add</button></td>
</tr>
</table>
</td>
</tr>
<tr>
<td>Harmony Username</td>
<td><input id="bridge-settings-harmonyuser" class="form-control" type="text"
ng-model="bridge.settings.harmonyuser" placeholder="someone@gmail.com"></td>
</tr>
<tr>
<td>Harmony Password</td>
<td><input id="bridge-settings-harmonypwd" class="form-control" type="password"
ng-model="bridge.settings.harmonypwd" placeholder="thepassword"></td>
</tr>
<tr>
<td>Hue Names and IP Addresses</td>
<td><table class="table table-bordered table-striped table-hover">
<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>
<td>Nest Username</td>
<td><input id="bridge-settings-nestuser" class="form-control" type="text"
ng-model="bridge.settings.nestuser" placeholder="someone@gmail.com"></td>
</tr>
<tr>
<td>Nest Password</td>
<td><input id="bridge-settings-nestpwd" class="form-control" type="password"
ng-model="bridge.settings.nestpwd" placeholder="thepassword"></td>
</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>
<td>UPNP Strict Handling</td>
<td><input type="checkbox" ng-model="bridge.settings.upnpstrict"
ng-true-value=true ng-false-value=false> {{bridge.settings.upnpstrict}}</td>
</tr>
<tr>
<td>Trace UPNP Calls</td>
<td><input type="checkbox" ng-model="bridge.settings.traceupnp"
ng-true-value=true ng-false-value=false> {{bridge.settings.traceupnp}}</td>
</tr>
</table>
</form>
</div>
</div>
<div class="panel panel-default backup">
<div class="panel-heading">
<h1 class="panel-title">Bridge Settings Backup <a ng-click="toggle()"><span class={{imgUrl}} aria-hidden="true"></a></h1>
</div>
<div ng-if="visible" class="animate-if" class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label class="col-xs-12 col-sm-2 control-label" for="backup-name">Backup Settings File Name</label>
<div class="col-xs-8 col-sm-7">
<input id="backup-name" class="form-control" type="text"
ng-model="optionalbackupname" placeholder="Optional">
</div>
<button type="submit" class="btn btn-primary"
ng-click="backupSettings(optionalbackupname)">Backup Settings</button>
</div>
</form>
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Filename</th>
<th>Actions</th>
</tr>
</thead>
<tr ng-repeat="backup in bridge.configs">
<td>{{backup}}</td>
<td>
<button class="btn btn-danger" type="submit"
ng-click="restoreSettings(backup)">Restore</button>
<button class="btn btn-warning" type="submit"
ng-click="deleteSettingsBackup(backup)">Delete</button>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -1,14 +1,17 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/logs">Logs</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 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" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Vera Device List ({{bridge.veradevices.length}})</h2>
</div>
@@ -21,7 +24,7 @@
control you would like to be generated:
<select name="device-dim-control" id="device-dim-control" ng-model="device_dim_control">
<option value="">none</option>
<option value="${intensity..byte}">Pass-thru Value</option>
<option value="${intensity.byte}">Pass-thru Value</option>
<option value="${intensity.percent}">Percentage</option>
<option value="${intensity.math(X*1)}">Custom Math</option>
</select>
@@ -29,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.
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>
<scrollable-table watch="bridge.veradevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('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 sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</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><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>
@@ -65,11 +54,11 @@
<td>{{veradevice.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildDeviceUrls(veradevice, device_dim_control)">Generate
Device URLs</button>
ng-click="buildDeviceUrls(veradevice, device_dim_control)">Generate Bridge Device</button>
</td>
</tr>
</table>
</scrollable-table>
<p>
<button class="btn btn-success" type="submit"
ng-click="bulkAddDevices(device_dim_control)">Bulk Add ({{bulk.devices.length}})</button>
@@ -81,34 +70,20 @@
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<scrollable-table watch="bridge.veradevices">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('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 sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="category">Category</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</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>{{veradevice.name}}</td>
<td>{{veradevice.id}}</td>
@@ -121,10 +96,11 @@
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Add Bridge Device for a Vera Device</h2>
</div>
@@ -154,6 +130,17 @@
<button class="btn btn-danger" ng-click="clearDevice()">
Clear Device</button>
</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="row">
<label class="col-xs-12 col-sm-2 control-label"
@@ -168,3 +155,13 @@
</li>
</ul>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>

View File

@@ -1,14 +1,17 @@
<ul class="nav nav-pills" role="tablist">
<li role="presentation"><a href="#">Configuration</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="#/logs">Logs</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 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" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Vera Scene List</h2>
</div>
@@ -18,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.
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">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('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 sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</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>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
@@ -49,11 +41,11 @@
<td>{{verascene.veraname}}</td>
<td>
<button class="btn btn-success" type="submit"
ng-click="buildSceneUrls(verascene)">Generate
Scene URLs</button>
ng-click="buildSceneUrls(verascene)">Generate Bridge Device</button>
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
<div class="panel-heading">
@@ -61,30 +53,19 @@
</div>
<ul ng-if="buttonsVisible" class="list-group">
<li class="list-group-item">
<scrollable-table watch="bridge.verascenes">
<table class="table table-bordered table-striped table-hover">
<thead>
<tr>
<th>Row</th>
<th>
<a href="" ng-click="order('name')">Name</a>
<span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('id')">Id</a>
<span class="sortorder" ng-show="predicate === 'id'" ng-class="{reverse:reverse}"></span>
</th>
<th>
<a href="" ng-click="order('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 sortable-header col="name">Name</th>
<th sortable-header col="id">Id</th>
<th sortable-header col="room">Room</th>
<th sortable-header col="veraname">Vera</th>
<th>Actions</th>
</tr>
</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>{{verascene.name}}</td>
<td>{{verascene.id}}</td>
@@ -96,10 +77,11 @@
</td>
</tr>
</table>
</scrollable-table>
</li>
</ul>
</div>
<div class="panel panel-default bridgeServer" ng-if="!bridge.error">
<div class="panel panel-default bridgeServer">
<div class="panel-heading">
<h2 class="panel-title">Add a Bridge Device for a Vera scene</h2>
</div>
@@ -143,3 +125,13 @@
</li>
</ul>
</div>
<script type="text/ng-template" id="deleteMapandIdDialog">
<div class="ngdialog-message">
<h2>Device Map and Id?</h2>
<p>{{mapandid.mapType}} with {{mapandid.id}}</p>
<p>Are you Sure?</p>
</div>
<div class="ngdialog-buttons mt">
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteMapandId(mapandid)">Delete</button>
</div>
</script>