mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-18 16:17:30 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0205633684 | ||
|
|
7b48590807 | ||
|
|
5f7cd70710 | ||
|
|
46ad4489ad | ||
|
|
bfd1b94473 | ||
|
|
7d920d3885 | ||
|
|
2dbf4c96c4 |
9
pom.xml
9
pom.xml
@@ -5,7 +5,7 @@
|
||||
|
||||
<groupId>com.bwssystems.HABridge</groupId>
|
||||
<artifactId>ha-bridge</artifactId>
|
||||
<version>5.2.next_e</version>
|
||||
<version>5.3.0RC5</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>HA Bridge</name>
|
||||
@@ -167,6 +167,7 @@
|
||||
<configuration>
|
||||
<rules>
|
||||
<requireMavenVersion>
|
||||
<!-- Change this to Version 3.3 for Java 1.8 and Raspberry PI compilation -->
|
||||
<version>3.6</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
@@ -178,7 +179,11 @@
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<!-- Comment this release line out for Java 1.8 and Raspberry PI compilation -->
|
||||
<release>11</release>
|
||||
<!-- Uncomment the next two lines for Java 1.8 and Raspberry PI compilation -->
|
||||
<!-- <source>1.8</source> -->
|
||||
<!-- <target>1.8</target> -->
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
@@ -264,4 +269,4 @@
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -437,6 +437,41 @@ public class SystemControl {
|
||||
return stop();
|
||||
});
|
||||
|
||||
// http://ip_address:port/system/devices/backup/download CORS request
|
||||
options(SYSTEM_CONTEXT + "/backup/download", "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/download", "application/json", (request, response) -> {
|
||||
log.debug("Create download: {}", request.body());
|
||||
BackupFilename aFilename = new Gson().fromJson(request.body(), BackupFilename.class);
|
||||
String backupContent = bridgeSettings.downloadBackup(aFilename.getFilename());
|
||||
return backupContent;
|
||||
}, new JsonTransformer());
|
||||
|
||||
// http://ip_address:port/system/devices/backup/upload CORS request
|
||||
options(SYSTEM_CONTEXT + "/backup/upload/:filename", "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/upload/:filename", "application/json", (request, response) -> {
|
||||
log.debug("Create upload: {} - {}", request.params(":filename"), request.body());
|
||||
String theSuccess = bridgeSettings.uploadBackup(request.params(":filename"), request.body());
|
||||
if(theSuccess.contains("Error:"))
|
||||
response.status(HttpStatus.SC_METHOD_FAILURE);
|
||||
else
|
||||
response.status(HttpStatus.SC_OK);
|
||||
return theSuccess;
|
||||
}, new JsonTransformer());
|
||||
|
||||
// http://ip_address:port/system/backup/available returns a list of config backup filenames
|
||||
get (SYSTEM_CONTEXT + "/backup/available", (request, response) -> {
|
||||
log.debug("Get backup filenames");
|
||||
|
||||
@@ -89,6 +89,9 @@ public class DeviceDescriptor{
|
||||
@SerializedName("lockDeviceId")
|
||||
@Expose
|
||||
private boolean lockDeviceId;
|
||||
@SerializedName("startupActions")
|
||||
@Expose
|
||||
private String startupActions;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
@@ -344,4 +347,12 @@ public class DeviceDescriptor{
|
||||
public void setLockDeviceId(boolean lockDeviceId) {
|
||||
this.lockDeviceId = lockDeviceId;
|
||||
}
|
||||
|
||||
public String getStartupActions() {
|
||||
return startupActions;
|
||||
}
|
||||
|
||||
public void setStartupActions(String startupActions) {
|
||||
this.startupActions = startupActions;
|
||||
}
|
||||
}
|
||||
@@ -202,15 +202,33 @@ public class DeviceRepository extends BackupHandler {
|
||||
List<DeviceDescriptor> list = new ArrayList<DeviceDescriptor>(devices.values());
|
||||
Iterator<DeviceDescriptor> deviceIterator = list.iterator();
|
||||
Map<String, DeviceDescriptor> newdevices = new HashMap<String, DeviceDescriptor>();
|
||||
|
||||
nextId = seedId;
|
||||
List<String> lockedIds = new ArrayList<String>();
|
||||
String hexValue;
|
||||
Integer newValue;
|
||||
DeviceDescriptor theDevice;
|
||||
log.debug("Renumber devices with seed: {}", seedId);
|
||||
boolean findNext = true;
|
||||
|
||||
|
||||
nextId = seedId;
|
||||
while(deviceIterator.hasNext()) {
|
||||
theDevice = deviceIterator.next();
|
||||
if(theDevice.isLockDeviceId()) {
|
||||
lockedIds.add(theDevice.getId());
|
||||
}
|
||||
}
|
||||
log.debug("Renumber devices starting with: {}", nextId);
|
||||
deviceIterator = list.iterator();
|
||||
while (deviceIterator.hasNext()) {
|
||||
theDevice = deviceIterator.next();
|
||||
if (!theDevice.isLockDeviceId()) {
|
||||
findNext = true;
|
||||
while(findNext) {
|
||||
if(lockedIds.contains(String.valueOf(nextId))) {
|
||||
nextId++;
|
||||
} else {
|
||||
findNext = false;
|
||||
}
|
||||
}
|
||||
theDevice.setId(String.valueOf(nextId));
|
||||
newValue = nextId % 256;
|
||||
if (newValue <= 0)
|
||||
|
||||
@@ -41,6 +41,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.awt.Color;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -77,6 +78,7 @@ public class HueMulator {
|
||||
// This function sets up the sparkjava rest calls for the hue api
|
||||
public void setupServer() {
|
||||
log.info("Hue emulator service started....");
|
||||
startupDeviceCall();
|
||||
before(HUE_CONTEXT + "/*", (request, response) -> {
|
||||
// This currently causes an error with Spark replies
|
||||
// String path = request.pathInfo();
|
||||
@@ -1671,4 +1673,50 @@ public class HueMulator {
|
||||
|
||||
return responseString;
|
||||
}
|
||||
|
||||
private void startupDeviceCall() {
|
||||
String aUserId = bridgeSettingMaster.getBridgeSecurity().createWhitelistUser("test_ha_bridge");
|
||||
List<DeviceDescriptor> deviceList = repository.findAll();
|
||||
String aChangeBody;
|
||||
String[] components;
|
||||
boolean comma = false;
|
||||
|
||||
for (DeviceDescriptor aDevice : deviceList) {
|
||||
if(aDevice.getStartupActions() != null && !aDevice.getStartupActions().isEmpty()) {
|
||||
log.info("Startup call for {} with startupActions {}", aDevice.getName(), aDevice.getStartupActions());
|
||||
aChangeBody = "{";
|
||||
components = aDevice.getStartupActions().split(":");
|
||||
if(components.length > 0 && components[0] != null && components[0].length() > 0) {
|
||||
if(components[0].equals("On")) {
|
||||
aChangeBody = aChangeBody + "\"on\":true";
|
||||
}
|
||||
else {
|
||||
aChangeBody = aChangeBody + "\"on\":false";
|
||||
}
|
||||
comma = true;
|
||||
}
|
||||
if(components.length > 1 && components[1] != null && components[1].length() > 0 && !(components.length > 2 && components[2] != null && components[2].length() > 0)) {
|
||||
if(comma)
|
||||
aChangeBody = aChangeBody + ",";
|
||||
aChangeBody = aChangeBody + "\"bri\":" + components[1];
|
||||
comma = true;
|
||||
}
|
||||
if(components.length > 2 && components[2] != null && components[2].length() > 0) {
|
||||
if(comma)
|
||||
aChangeBody = aChangeBody + ",";
|
||||
String theRGB = components[2].substring(components[2].indexOf('(') + 1, components[2].indexOf(')'));
|
||||
String[] RGB = theRGB.split(",");
|
||||
float[] hsb = new float[3];
|
||||
Color.RGBtoHSB(Integer.parseInt(RGB[0]), Integer.parseInt(RGB[1]), Integer.parseInt(RGB[2]), hsb);
|
||||
float hue = hsb[0] * (float) 360.0;
|
||||
float sat = hsb[1] * (float) 100.0;
|
||||
float bright = hsb[2] * (float) 100.0;
|
||||
aChangeBody = String.format("%s\"hue\":%.2f,\"sat\":%.2f,\"bri\":%d", aChangeBody, hue, sat, Math.round(bright));
|
||||
}
|
||||
aChangeBody = aChangeBody + "}";
|
||||
log.info("Startup call to set state for {} with body {}", aDevice.getName(), aChangeBody);
|
||||
changeState(aUserId, aDevice.getId(), aChangeBody, "localhost", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.bwssystems.HABridge.plugins.hass;
|
||||
|
||||
public class HassAuth {
|
||||
boolean legacyauth;
|
||||
|
||||
public boolean isLegacyauth() {
|
||||
return legacyauth;
|
||||
}
|
||||
|
||||
public void setLegacyauth(boolean legacyauth) {
|
||||
this.legacyauth = legacyauth;
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,19 @@ import com.bwssystems.HABridge.plugins.http.HTTPHome;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class HomeAssistant {
|
||||
private static final Logger log = LoggerFactory.getLogger(HomeAssistant.class);
|
||||
private NamedIP hassAddress;
|
||||
private HTTPHandler anHttpHandler;
|
||||
private static final Logger log = LoggerFactory.getLogger(HomeAssistant.class);
|
||||
private NamedIP hassAddress;
|
||||
private HTTPHandler anHttpHandler;
|
||||
private HassAuth theAuthType;
|
||||
private NameValue[] headers;
|
||||
|
||||
public HomeAssistant(NamedIP addressName) {
|
||||
super();
|
||||
anHttpHandler = HTTPHome.getHandler();
|
||||
hassAddress = addressName;
|
||||
hassAddress = addressName;
|
||||
theAuthType = null;
|
||||
headers = null;
|
||||
isLegacyAuth();
|
||||
}
|
||||
|
||||
public NamedIP getHassAddress() {
|
||||
@@ -35,38 +40,30 @@ public class HomeAssistant {
|
||||
}
|
||||
|
||||
public Boolean callCommand(HassCommand aCommand) {
|
||||
log.debug("calling HomeAssistant: " + aCommand.getHassName() + " - "
|
||||
+ aCommand.getEntityId() + " - " + aCommand.getState() + " - " + aCommand.getBri());
|
||||
log.debug("calling HomeAssistant: " + aCommand.getHassName() + " - " + aCommand.getEntityId() + " - "
|
||||
+ aCommand.getState() + " - " + aCommand.getBri());
|
||||
String aUrl = null;
|
||||
String domain = aCommand.getEntityId().substring(0, aCommand.getEntityId().indexOf("."));
|
||||
aUrl = hassAddress.getHttpPreamble() + "/api/services/";
|
||||
if(domain.equals("group"))
|
||||
if (domain.equals("group"))
|
||||
aUrl = aUrl + "homeassistant";
|
||||
else
|
||||
aUrl = aUrl + domain;
|
||||
String aBody = "{\"entity_id\":\"" + aCommand.getEntityId() + "\"";
|
||||
NameValue[] headers = null;
|
||||
if(hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
|
||||
NameValue password = new NameValue();
|
||||
password.setName("x-ha-access");
|
||||
password.setValue(hassAddress.getPassword());
|
||||
headers = new NameValue[1];
|
||||
headers[0] = password;
|
||||
}
|
||||
if(aCommand.getState().equalsIgnoreCase("on")) {
|
||||
headers = getAuthHeader();
|
||||
if (aCommand.getState().equalsIgnoreCase("on")) {
|
||||
aUrl = aUrl + "/turn_on";
|
||||
if(aCommand.getBri() != null)
|
||||
if (aCommand.getBri() != null)
|
||||
aBody = aBody + ",\"brightness\":" + aCommand.getBri() + "}";
|
||||
else
|
||||
aBody = aBody + "}";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
aUrl = aUrl + "/turn_off";
|
||||
aBody = aBody + "}";
|
||||
aBody = aBody + "}";
|
||||
}
|
||||
log.debug("Calling HomeAssistant with url: " + aUrl);
|
||||
String theData = anHttpHandler.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", aBody, headers);
|
||||
log.debug("call Command return is: <" + theData + ">");
|
||||
String theData = anHttpHandler.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "application/json", aBody, headers);
|
||||
log.debug("call Command return is: <" + theData + ">");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -74,39 +71,62 @@ public class HomeAssistant {
|
||||
List<State> theDeviceStates = null;
|
||||
State[] theHassStates;
|
||||
String theUrl = null;
|
||||
String theData;
|
||||
NameValue[] headers = null;
|
||||
if(hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
|
||||
NameValue password = new NameValue();
|
||||
password.setName("x-ha-access");
|
||||
password.setValue(hassAddress.getPassword());
|
||||
headers = new NameValue[1];
|
||||
headers[0] = password;
|
||||
}
|
||||
if(hassAddress.getSecure() != null && hassAddress.getSecure())
|
||||
String theData;
|
||||
headers = getAuthHeader();
|
||||
if (hassAddress.getSecure() != null && hassAddress.getSecure())
|
||||
theUrl = "https";
|
||||
else
|
||||
theUrl = "http";
|
||||
theUrl = theUrl + "://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/states";
|
||||
theData = anHttpHandler.doHttpRequest(theUrl, HttpGet.METHOD_NAME, "application/json", null, headers);
|
||||
if(theData != null) {
|
||||
log.debug("GET Hass States - data: " + theData);
|
||||
theHassStates = new Gson().fromJson(theData, State[].class);
|
||||
if(theHassStates == null) {
|
||||
log.warn("Cannot get an devices for HomeAssistant " + hassAddress.getName() + " as response is not parsable.");
|
||||
}
|
||||
else {
|
||||
theDeviceStates = new ArrayList<State>(Arrays.asList(theHassStates));
|
||||
}
|
||||
}
|
||||
else
|
||||
log.warn("Cannot get an devices for HomeAssistant " + hassAddress.getName() + " http call failed.");
|
||||
theUrl = theUrl + "://" + hassAddress.getIp() + ":" + hassAddress.getPort() + "/api/states";
|
||||
theData = anHttpHandler.doHttpRequest(theUrl, HttpGet.METHOD_NAME, "application/json", null, headers);
|
||||
if (theData != null) {
|
||||
log.debug("GET Hass States - data: " + theData);
|
||||
theHassStates = new Gson().fromJson(theData, State[].class);
|
||||
if (theHassStates == null) {
|
||||
log.warn("Cannot get an devices for HomeAssistant " + hassAddress.getName()
|
||||
+ " as response is not parsable.");
|
||||
} else {
|
||||
theDeviceStates = new ArrayList<State>(Arrays.asList(theHassStates));
|
||||
}
|
||||
} else
|
||||
log.warn("Cannot get an devices for HomeAssistant " + hassAddress.getName() + " http call failed.");
|
||||
return theDeviceStates;
|
||||
}
|
||||
|
||||
|
||||
protected void closeClient() {
|
||||
anHttpHandler.closeHandler();
|
||||
anHttpHandler = null;
|
||||
}
|
||||
|
||||
private boolean isLegacyAuth() {
|
||||
if (theAuthType == null) {
|
||||
try {
|
||||
theAuthType = new Gson().fromJson(hassAddress.getExtensions(), HassAuth.class);
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not read hass auth - continuing with defaults.");
|
||||
theAuthType = new HassAuth();
|
||||
theAuthType.setLegacyauth(false);
|
||||
}
|
||||
}
|
||||
return theAuthType.isLegacyauth();
|
||||
}
|
||||
|
||||
private NameValue[] getAuthHeader() {
|
||||
if (headers == null) {
|
||||
if (hassAddress.getPassword() != null && !hassAddress.getPassword().isEmpty()) {
|
||||
headers = new NameValue[1];
|
||||
headers[0] = new NameValue();
|
||||
if (isLegacyAuth()) {
|
||||
headers[0].setName("x-ha-access");
|
||||
headers[0].setValue(hassAddress.getPassword());
|
||||
} else {
|
||||
headers[0].setName("Authorization");
|
||||
headers[0].setValue("Bearer " + hassAddress.getPassword());
|
||||
}
|
||||
}
|
||||
} else if(hassAddress.getPassword() == null || hassAddress.getPassword().isEmpty()) {
|
||||
headers = null;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ public class UpnpListener {
|
||||
+ HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n" + "NT: uuid:"
|
||||
+ HueConstants.UUID_PREFIX + "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
|
||||
|
||||
private String notifyTemplate2 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
|
||||
+ "LOCATION: http://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n"
|
||||
+ "NT: upnp:rootdevice\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n";
|
||||
|
||||
private String notifyTemplate3 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
|
||||
+ "LOCATION: http://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
|
||||
+ HueConstants.API_VERSION + "\r\n" + "NTS: ssdp:alive\r\n" + "hue-bridgeid: %s\r\n"
|
||||
+ "NT: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
|
||||
|
||||
public UpnpListener(BridgeSettingsDescriptor theSettings, BridgeControlDescriptor theControl,
|
||||
UDPDatagramSender aUdpDatagramSender) throws IOException {
|
||||
super();
|
||||
@@ -242,21 +252,21 @@ public class UpnpListener {
|
||||
discoveryResponse = String.format(responseTemplateOriginal, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template Original with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
} else
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplateOriginal is <<<" + discoveryResponse + ">>>");
|
||||
log.info("Traceupnp: send upnp discovery template Original with response address: "
|
||||
+ httpLocationAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplateOriginal is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
} else {
|
||||
discoveryResponse = String.format(responseTemplateOriginal, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, httpLocationAddress, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template Original with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
} else
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplateOriginal is <<<" + discoveryResponse + ">>>");
|
||||
log.info("Traceupnp: send upnp discovery template Original with response address: "
|
||||
+ httpLocationAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplateOriginal is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
@@ -268,9 +278,9 @@ public class UpnpListener {
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 1 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
} else
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
|
||||
try {
|
||||
@@ -284,9 +294,9 @@ public class UpnpListener {
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
} else
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
|
||||
try {
|
||||
@@ -299,9 +309,9 @@ public class UpnpListener {
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":"
|
||||
+ httpServerPort + " to address: " + requester + ":" + sourcePort);
|
||||
} else
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
|
||||
}
|
||||
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort
|
||||
+ " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
|
||||
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
|
||||
}
|
||||
}
|
||||
@@ -316,17 +326,50 @@ public class UpnpListener {
|
||||
|
||||
protected void sendUpnpNotify(InetAddress aSocketAddress) {
|
||||
String notifyData = null;
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
}
|
||||
|
||||
notifyData = String.format(notifyTemplate, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID, bridgeSNUUID);
|
||||
log.debug("sendUpnpNotify notifyTemplate is <<<" + notifyData + ">>>");
|
||||
sendNotifyDatagram(notifyData, aSocketAddress, "notifyTemplate1");
|
||||
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
}
|
||||
|
||||
notifyData = String.format(notifyTemplate2, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
sendNotifyDatagram(notifyData, aSocketAddress, "notifyTemplate2");
|
||||
|
||||
try {
|
||||
Thread.sleep(theUpnpSendDelay);
|
||||
} catch (InterruptedException e) {
|
||||
// noop
|
||||
}
|
||||
|
||||
notifyData = String.format(notifyTemplate3, Configuration.UPNP_MULTICAST_ADDRESS,
|
||||
Configuration.UPNP_DISCOVERY_PORT, upnpConfigIP, httpServerPort, bridgeId, bridgeSNUUID);
|
||||
sendNotifyDatagram(notifyData, aSocketAddress, "notifyTemplate3");
|
||||
}
|
||||
|
||||
public void sendNotifyDatagram(String notifyData, InetAddress aSocketAddress, String templateNumber) {
|
||||
if (traceupnp) {
|
||||
log.info("Traceupnp: sendUpnpNotify {}", templateNumber);
|
||||
}
|
||||
log.debug("sendUpnpNotify {} is <<<{}>>>", templateNumber, notifyData);
|
||||
DatagramPacket notifyPacket = new DatagramPacket(notifyData.getBytes(), notifyData.length(), aSocketAddress,
|
||||
Configuration.UPNP_DISCOVERY_PORT);
|
||||
try {
|
||||
upnpMulticastSocket.send(notifyPacket);
|
||||
} catch (IOException e1) {
|
||||
log.warn("UpnpListener encountered an error sending upnp notify packet. IP: "
|
||||
+ notifyPacket.getAddress().getHostAddress() + " with message: " + e1.getMessage());
|
||||
log.debug("UpnpListener send upnp notify exception: ", e1);
|
||||
log.warn("UpnpListener encountered an error sending upnp {}. IP: {} with message: {}", templateNumber,
|
||||
notifyPacket.getAddress().getHostAddress(), e1.getMessage());
|
||||
log.debug("UpnpListener send {} exception: ", templateNumber, e1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
|
||||
.scrollArea {
|
||||
height: 100%;
|
||||
/* THis makes the table scroll - disabled as a feature for now
|
||||
max-height: 800px;
|
||||
*/
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #d5d5d5;
|
||||
|
||||
@@ -17,7 +17,24 @@
|
||||
<link href="css/strength-meter.min.css" rel="stylesheet">
|
||||
<link href="css/colorpicker.min.css" rel="stylesheet">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<style id="compiled-css" type="text/css">
|
||||
.goToTop
|
||||
{
|
||||
position: fixed;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 100000;
|
||||
cursor: pointer;
|
||||
margin: 10px;
|
||||
-moz-opacity: 0.60;
|
||||
opacity: .60;
|
||||
filter: alpha(opacity=60);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script type="text/javascript" src="js/html5shiv.min.js"></script>
|
||||
<script type="text/javascript" src="js/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
@@ -75,8 +92,12 @@
|
||||
|
||||
|
||||
</div>
|
||||
<span class="goToTop">
|
||||
<button type="button" class="btn btn-light"><i class="glyphicon glyphicon-chevron-up"></i> Back to Top</button>
|
||||
</span>
|
||||
<!-- <input type="button" class="goToTop" value="Back to Top" style="display:none;background-color:blue" /> -->
|
||||
|
||||
<script src="js/jquery-1.11.3.min.js"></script>
|
||||
<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>
|
||||
@@ -93,5 +114,35 @@
|
||||
<script src="js/ng-file-upload-shim.min.js"></script>
|
||||
<script src="js/ng-file-upload.min.js"></script>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
<script type="text/javascript">
|
||||
$(window).load(function(){
|
||||
|
||||
$(window).scroll(function () {
|
||||
if ($(this).scrollTop() > 100) {
|
||||
$('.goToTop').fadeIn();
|
||||
} else {
|
||||
$('.goToTop').fadeOut();
|
||||
}
|
||||
});
|
||||
$('.goToTop').click(function () {
|
||||
$("html, body").animate({ scrollTop: 0 }, 1000);
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
<script>
|
||||
// tell the embed parent frame the height of the content
|
||||
if (window.parent && window.parent.parent){
|
||||
window.parent.parent.postMessage(["resultsFrame", {
|
||||
height: document.body.getBoundingClientRect().height,
|
||||
slug: "fvdrw83s"
|
||||
}], "*")
|
||||
}
|
||||
|
||||
// always overwrite window.name, in case users try to set it manually
|
||||
window.name = "result"
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1279,7 +1279,7 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng
|
||||
);
|
||||
};
|
||||
|
||||
this.downloadBackup = function (afilename) {
|
||||
this.downloadDeviceBackup = function (afilename) {
|
||||
return $http.put(this.state.base + "/backup/download", {
|
||||
filename: afilename
|
||||
}).then(
|
||||
@@ -1459,6 +1459,54 @@ app.service('bridgeService', function ($rootScope, $http, $base64, $location, ng
|
||||
);
|
||||
};
|
||||
|
||||
this.downloadConfigBackup = function (afilename) {
|
||||
return $http.put(this.state.systemsbase + "/backup/download", {
|
||||
filename: afilename
|
||||
}).then(
|
||||
function (response) {
|
||||
self.state.backupContent = response.data;
|
||||
var blob = new Blob([self.state.backupContent], {
|
||||
type: 'text/plain'
|
||||
});
|
||||
var downloadLink = angular.element('<a></a>');
|
||||
downloadLink.attr('href', window.URL.createObjectURL(blob));
|
||||
downloadLink.attr('download', afilename);
|
||||
downloadLink[0].click();
|
||||
},
|
||||
function (error) {
|
||||
if (error.status === 401)
|
||||
$rootScope.$broadcast('securityReinit', 'done');
|
||||
else
|
||||
self.displayWarn("Download Backup Config File Error:", error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
this.uploadConfigFile = function (filename, file) {
|
||||
file.upload = Upload.http({
|
||||
url: this.state.systemsbase + "/backup/upload/" + filename,
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': file.type
|
||||
},
|
||||
data: file
|
||||
});
|
||||
|
||||
file.upload.then(function (response) {
|
||||
file.result = response.data;
|
||||
self.viewConfigs();
|
||||
}, function (response) {
|
||||
if (response.status === 401)
|
||||
$rootScope.$broadcast('securityReinit', 'done');
|
||||
else if (response.status > 0)
|
||||
self.displayWarn('Upload Backup Config File Error:' + response.status + ': ' + response.data);
|
||||
});
|
||||
|
||||
file.upload.progress(function (evt) {
|
||||
file.progress = Math.min(100, parseInt(100.0 * evt.loaded / evt.total));
|
||||
});
|
||||
}
|
||||
|
||||
this.deleteDevice = function (id) {
|
||||
return $http.delete(this.state.base + "/" + id).then(
|
||||
function (response) {
|
||||
@@ -1827,7 +1875,7 @@ app.controller('SystemController', function ($scope, $location, bridgeService, n
|
||||
}
|
||||
}
|
||||
};
|
||||
$scope.addHasstoSettings = function (newhassname, newhassip, newhassport, newhasspassword, newhasssecure) {
|
||||
$scope.addHasstoSettings = function (newhassname, newhassip, newhassport, newhasspassword, newhasssecure, newhassauth) {
|
||||
if ($scope.bridge.settings.hassaddress === undefined || $scope.bridge.settings.hassaddress === null) {
|
||||
$scope.bridge.settings.hassaddress = {
|
||||
devices: []
|
||||
@@ -1838,7 +1886,8 @@ app.controller('SystemController', function ($scope, $location, bridgeService, n
|
||||
ip: newhassip,
|
||||
port: newhassport,
|
||||
password: newhasspassword,
|
||||
secure: newhasssecure
|
||||
secure: newhasssecure,
|
||||
extensions: newhassauth
|
||||
};
|
||||
$scope.bridge.settings.hassaddress.devices.push(newhass);
|
||||
$scope.newhassname = null;
|
||||
@@ -2074,6 +2123,17 @@ app.controller('SystemController', function ($scope, $location, bridgeService, n
|
||||
$scope.deleteSettingsBackup = function (backupname) {
|
||||
bridgeService.deleteSettingsBackup(backupname);
|
||||
};
|
||||
|
||||
$scope.downloadBackup = function (backupname) {
|
||||
bridgeService.downloadConfigBackup(backupname);
|
||||
};
|
||||
|
||||
$scope.uploadConfigFile = function (aFilename, aConfigFile) {
|
||||
if (aConfigFile != null) {
|
||||
bridgeService.uploadConfigFile(aFilename, aConfigFile);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggle = function () {
|
||||
$scope.visible = !$scope.visible;
|
||||
if ($scope.visible)
|
||||
@@ -2349,12 +2409,41 @@ app.controller('ViewingController', function ($scope, $location, bridgeService,
|
||||
bridgeService.editDevice(device);
|
||||
$location.path('/editdevice');
|
||||
};
|
||||
$scope.setStartupAction = function(device) {
|
||||
$scope.bridge.device = device;
|
||||
ngDialog.open({
|
||||
template: 'startupActionDialog',
|
||||
controller: 'StartupActionDialogCtrl',
|
||||
className: 'ngdialog-theme-default'
|
||||
});
|
||||
};
|
||||
|
||||
$scope.renumberDevices = function () {
|
||||
bridgeService.renumberDevices();
|
||||
};
|
||||
$scope.pushLinkButton = function () {
|
||||
bridgeService.pushLinkButton();
|
||||
};
|
||||
|
||||
$scope.toggleLock = function (device) {
|
||||
if(device.lockDeviceId) {
|
||||
device.lockDeviceId = false;
|
||||
} else {
|
||||
device.lockDeviceId = true;
|
||||
}
|
||||
console.log("toggle lock device called: " + device.name);
|
||||
bridgeService.addDevice(device).then(
|
||||
function () {
|
||||
bridgeService.state.queueDevId = device.id;
|
||||
console.log("Device updated for Q Id <<" + bridgeService.state.queueDevId + ">>");
|
||||
$location.path('/');
|
||||
},
|
||||
function (error) {
|
||||
bridgeService.displayWarn("Error updating lock id for device....", error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
$scope.manageLinksButton = function () {
|
||||
ngDialog.open({
|
||||
template: 'views/managelinksdialog.html',
|
||||
@@ -2372,7 +2461,7 @@ app.controller('ViewingController', function ($scope, $location, bridgeService,
|
||||
bridgeService.deleteBackup(backupname);
|
||||
};
|
||||
$scope.downloadBackup = function (backupname) {
|
||||
bridgeService.downloadBackup(backupname);
|
||||
bridgeService.downloadDeviceBackup(backupname);
|
||||
};
|
||||
|
||||
$scope.uploadDeviceFile = function (aFilename, aDeviceFile) {
|
||||
@@ -2408,23 +2497,23 @@ app.controller('ViewingController', function ($scope, $location, bridgeService,
|
||||
});
|
||||
|
||||
app.controller('ValueDialogCtrl', function ($scope, bridgeService, ngDialog) {
|
||||
$scope.bridge = bridgeService.state;
|
||||
$scope.valueType = "percentage";
|
||||
$scope.slider = {
|
||||
value: 100,
|
||||
value: Math.round($scope.bridge.device.deviceState.bri / 2.55),
|
||||
options: {
|
||||
floor: 1,
|
||||
ceil: 100,
|
||||
showSelectionBar: true
|
||||
}
|
||||
};
|
||||
$scope.bridge = bridgeService.state;
|
||||
$scope.valueType = "percentage";
|
||||
$scope.changeScale = function () {
|
||||
if ($scope.valueType === "raw") {
|
||||
$scope.slider.options.ceil = 254;
|
||||
$scope.slider.value = 254;
|
||||
$scope.slider.value = $scope.bridge.device.deviceState.bri;
|
||||
} else {
|
||||
$scope.slider.options.ceil = 100;
|
||||
$scope.slider.value = 100;
|
||||
$scope.slider.value = Math.round($scope.bridge.device.deviceState.bri / 2.55);
|
||||
}
|
||||
};
|
||||
$scope.setValue = function () {
|
||||
@@ -2473,6 +2562,57 @@ app.controller('DeleteDialogCtrl', function ($scope, bridgeService, ngDialog) {
|
||||
};
|
||||
});
|
||||
|
||||
app.controller('StartupActionDialogCtrl', function ($scope, bridgeService, ngDialog) {
|
||||
$scope.bridge = bridgeService.state;
|
||||
$scope.device = $scope.bridge.device;
|
||||
$scope.setDim = false;
|
||||
var components = [];
|
||||
if($scope.device.startupActions != undefined) {
|
||||
components = $scope.device.startupActions.split(":");
|
||||
if(components[1] != undefined && components[1].length > 0)
|
||||
$scope.setDim = true;
|
||||
} else {
|
||||
components = "::".split(":");
|
||||
}
|
||||
|
||||
$scope.slider = {
|
||||
value: parseInt(components[1]),
|
||||
options: {
|
||||
floor: 1,
|
||||
ceil: 254,
|
||||
showSelectionBar: true
|
||||
}
|
||||
};
|
||||
$scope.rgbPicker = {
|
||||
color: components[2]
|
||||
};
|
||||
|
||||
$scope.theState = components[0];
|
||||
|
||||
$scope.startupActionSave = function (device) {
|
||||
console.log("Startup action set for device called: " + device.name);
|
||||
ngDialog.close('ngdialog1');
|
||||
var theValue = 1;
|
||||
if($scope.setDim) {
|
||||
theValue = $scope.theState + ":" + $scope.slider.value + ":" + $scope.rgbPicker.color;
|
||||
} else {
|
||||
theValue = $scope.theState + "::" + $scope.rgbPicker.color;
|
||||
}
|
||||
if(theValue == "::")
|
||||
theValue = "";
|
||||
device.startupActions = theValue;
|
||||
bridgeService.addDevice(device).then(
|
||||
function () {
|
||||
bridgeService.state.queueDevId = device.id;
|
||||
console.log("Device updated for Q Id <<" + bridgeService.state.queueDevId + ">>");
|
||||
},
|
||||
function (error) {
|
||||
bridgeService.displayWarn("Error updating lock id for device....", error);
|
||||
}
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
app.controller('VeraController', function ($scope, $location, bridgeService, ngDialog) {
|
||||
$scope.bridge = bridgeService.state;
|
||||
$scope.device = bridgeService.state.device;
|
||||
|
||||
@@ -68,9 +68,9 @@
|
||||
<th sortable-header col="id" comparator-fn="comparatorUniqueId">ID</th>
|
||||
<th sortable-header col="name">Name</th>
|
||||
<th sortable-header col="description">Description</th>
|
||||
<th sortable-header col="devicestate">Device State</th>
|
||||
<th sortable-header col="deviceType">Type</th>
|
||||
<th sortable-header col="targetDevice">Target</th>
|
||||
<th sortable-header col="startupAction">Startup Action</th>
|
||||
<th sortable-header col="inactive">Inactive</th>
|
||||
<th sortable-header col="noState">No State</th>
|
||||
<th>Actions</th>
|
||||
@@ -79,14 +79,17 @@
|
||||
<tr ng-repeat="device in bridge.devices | filterDevicesByRequester:bridge.state.filterDevicesByIpAddress:bridge.state.filterDevicesOnlyFiltered:bridge.state.filterDeviceType"
|
||||
row-id="{{device.id}}" ng-class="{info: bridge.viewDevId == device.id}">
|
||||
<td>{{$index+1}}</td>
|
||||
<td>{{device.id}}</td>
|
||||
<td>{{device.name}}</td>
|
||||
<td class="cr">{{device.description}}</td>
|
||||
<td class="cr">
|
||||
on={{device.deviceState.on}},bri={{device.deviceState.bri}},hue={{device.deviceState.hue}},sat={{device.deviceState.sat}},effect={{device.deviceState.effect}},ct={{device.deviceState.ct}},alert={{device.deviceState.alert}},colormode={{device.deviceState.colormode}},reachable={{device.deviceState.reachable}},XYList={{device.deviceState.xy}}
|
||||
<td title="Locked: {{device.lockDeviceId}} - Click to Toggle" ng-click="toggleLock(device)">
|
||||
<b ng-if="device.lockDeviceId">{{device.id}}</b>
|
||||
<p ng-if="!device.lockDeviceId">{{device.id}}</p>
|
||||
</td>
|
||||
<td
|
||||
title="on={{device.deviceState.on}},bri={{device.deviceState.bri}},hue={{device.deviceState.hue}},sat={{device.deviceState.sat}},effect={{device.deviceState.effect}},ct={{device.deviceState.ct}},alert={{device.deviceState.alert}},colormode={{device.deviceState.colormode}},reachable={{device.deviceState.reachable}},XYList={{device.deviceState.xy}}">
|
||||
{{device.name}}</td>
|
||||
<td class="cr">{{device.description}}</td>
|
||||
<td>{{device.deviceType}}</td>
|
||||
<td>{{device.targetDevice}}</td>
|
||||
<td class="cr" title="Click to set actions for device on ha-bridge startup." ng-click="setStartupAction(device)">{{device.startupActions}}</td>
|
||||
<td>{{device.inactive}}</td>
|
||||
<td>{{device.noState}}</td>
|
||||
<td>
|
||||
@@ -139,13 +142,13 @@
|
||||
<input type="file" ngf-select="" ng-model="deviceFile" name="file">
|
||||
</div>
|
||||
|
||||
<button ng-disabled="!myForm.$valid" type="submit" class="btn btn-primary"
|
||||
<button ng-disabled="!myForm.$valid" type="submit" class="btn btn-primary"
|
||||
ng-click="uploadDeviceFile(deviceFile.name, deviceFile)">Upload</button>
|
||||
<span class="progress" ng-show="deviceFile.progress >= 0">
|
||||
<div style="width:{{deviceFile.progress}}%" ng-bind="deviceFile.progress + '%'"
|
||||
class="ng-binding"></div>
|
||||
</span>
|
||||
<span ng-show="deviceFile.result">Upload Successful</span>
|
||||
<span class="progress" ng-show="deviceFile.progress >= 0">
|
||||
<div style="width:{{deviceFile.progress}}%" ng-bind="deviceFile.progress + '%'" class="ng-binding">
|
||||
</div>
|
||||
</span>
|
||||
<span ng-show="deviceFile.result">Upload Successful</span>
|
||||
</div>
|
||||
</form>
|
||||
<table class="table table-bordered table-striped table-hover">
|
||||
@@ -201,4 +204,44 @@
|
||||
<div class="ngdialog-buttons mt">
|
||||
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="deleteDevice(device)">Delete</button>
|
||||
</div>
|
||||
</script>
|
||||
</script>
|
||||
<script type="text/ng-template" id="startupActionDialog">
|
||||
<div class="ngdialog-message">
|
||||
<h2>Select Actions for startup on {{device.name}}</h2>
|
||||
<table class="table table-bordered table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Setting</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td>State</td>
|
||||
<td>
|
||||
<select name="the-state" id="the-state" ng-model="theState">
|
||||
<option value="">---None---</option>
|
||||
<!-- not selected / blank option -->
|
||||
<option value="On">On</option>
|
||||
<option value="Off">Off</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dim</td>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
ng-model="setDim" ng-true-value=true
|
||||
ng-false-value=false>
|
||||
<rzslider rz-slider-model="slider.value" rz-slider-options="slider.options"></rzslider>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Color RGB</td>
|
||||
<td><input colorpicker="rgb" ng-model="rgbPicker.color" type="text"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ngdialog-buttons mt">
|
||||
<button type="button" class="ngdialog-button ngdialog-button-error" ng-click="startupActionSave(device)">Save</button>
|
||||
</div>
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user