Update handling of upnp and updated how unique ids are generated

This commit is contained in:
bwssystems
2020-12-01 19:32:42 -06:00
parent c98513c365
commit 969ed352f7
9 changed files with 148 additions and 82 deletions

View File

@@ -57,20 +57,20 @@ Then locate the jar and start the server with:
ATTENTION: Due to port 80 being the default, Linux restricts this to super user. Use the instructions below.
```
java -jar ha-bridge-5.3.1RC2.jar
java -jar ha-bridge-5.3.1RC3.jar
```
## Manual installation of ha-bridge and setup of systemd service
Next gen Linux systems (this includes the Raspberry Pi), use systemd to run and manage services.
Here is a link on how to use systemd: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units
Create the directory and make sure that ha-bridge-5.3.1RC2.jar is in your /home/pi/ha-bridge directory.
Create the directory and make sure that ha-bridge-5.3.1RC3.jar is in your /home/pi/ha-bridge directory.
```
pi@raspberrypi:~ $ mkdir ha-bridge
pi@raspberrypi:~ $ cd ha-bridge
pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC2/ha-bridge-5.3.1RC2.jar
pi@raspberrypi:~/ha-bridge $ wget https://github.com/bwssytems/ha-bridge/releases/download/v5.3.1RC3/ha-bridge-5.3.1RC3.jar
```
Create the ha-bridge.service unit file:
@@ -89,7 +89,7 @@ After=network.target
Type=simple
WorkingDirectory=/home/pi/ha-bridge
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC2.jar
ExecStart=/usr/bin/java -jar -Dconfig.file=/home/pi/ha-bridge/data/habridge.config /home/pi/ha-bridge/ha-bridge-5.3.1RC3.jar
[Install]
WantedBy=multi-user.target

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId>
<version>5.3.1RC1</version>
<version>5.3.1RC3</version>
<packaging>jar</packaging>
<name>HA Bridge</name>
@@ -172,7 +172,7 @@
<configuration>
<rules>
<requireMavenVersion>
<!-- Change this to Version 3.3 for Java 1.8 and Raspberry PI compilation -->
<!-- Change this to Version 3.3 for Java 1.8 and Raspberry PI compilation, Java 11 is 3.6 -->
<version>3.6</version>
</requireMavenVersion>
</rules>
@@ -184,7 +184,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<!-- Comment this release line out for Java 1.8 and Raspberry PI compilation -->
<!-- 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> -->

View File

@@ -252,7 +252,7 @@ public class BridgeSettings extends BackupHandler {
theBridgeSettings = new Gson().fromJson(jsonContent, BridgeSettingsDescriptor.class);
theBridgeSettings.setConfigfile(aPath.toString());
} catch (Exception e) {
log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed.");
log.warn("Issue loading values from file: " + aPath.toUri().toString() + ", Gson convert failed. Using default settings.");
theBridgeSettings = new BridgeSettingsDescriptor();
}
}

View File

@@ -132,6 +132,9 @@ public class BridgeSettingsDescriptor {
@SerializedName("haaddressessecured")
@Expose
private boolean haaddressessecured;
@SerializedName("upnpadvanced")
@Expose
private boolean upnpadvanced;
// @SerializedName("activeloggers")
// @Expose
// private List<NameValue> activeloggers;
@@ -192,6 +195,8 @@ public class BridgeSettingsDescriptor {
this.upnporiginal = false;
this.seedid = 100;
this.haaddressessecured = false;
this.configfile = Configuration.CONFIG_FILE;
this.upnpadvanced = false;
}
public String getUpnpConfigAddress() {
@@ -847,4 +852,12 @@ public class BridgeSettingsDescriptor {
public void setHaaddressessecured(boolean haaddressessecured) {
this.haaddressessecured = haaddressessecured;
}
public boolean isUpnpadvanced() {
return upnpadvanced;
}
public void setUpnpadvanced(boolean upnpadvanced) {
this.upnpadvanced = upnpadvanced;
}
}

View File

@@ -2,7 +2,7 @@ package com.bwssystems.HABridge.api.hue;
public class HueConstants {
public final static String HUB_VERSION = "9999999999";
public final static String API_VERSION = "1.19.0";
public final static String API_VERSION = "1.17.0";
public final static String MODEL_ID = "BSB002";
public final static String UUID_PREFIX = "2f402f80-da50-11e1-9b23-";
}

View File

@@ -30,6 +30,8 @@ import com.google.gson.JsonSyntaxException;
import java.util.Collection;
import java.util.List;
import java.util.Arrays;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/*
* This is an in memory list to manage the configured devices and saves the list as a JSON string to a file for later
@@ -188,7 +190,7 @@ public class DeviceRepository extends BackupHandler {
nextId++;
}
if (descriptors[i].getUniqueid() == null || descriptors[i].getUniqueid().length() == 0) {
descriptors[i].setUniqueid("00:11:22:33:44:55:66:" + hueUniqueId(Integer.valueOf(descriptors[i].getId())));
descriptors[i].setUniqueid(hueUniqueId(Integer.valueOf(descriptors[i].getId())));
}
put(descriptors[i].getId(), descriptors[i]);
theNames = theNames + " " + descriptors[i].getName() + ", ";
@@ -206,11 +208,10 @@ public class DeviceRepository extends BackupHandler {
DeviceDescriptor theDevice;
boolean findNext = true;
nextId = seedId;
while(deviceIterator.hasNext()) {
while (deviceIterator.hasNext()) {
theDevice = deviceIterator.next();
if(theDevice.isLockDeviceId()) {
if (theDevice.isLockDeviceId()) {
lockedIds.add(theDevice.getId());
}
}
@@ -220,15 +221,15 @@ public class DeviceRepository extends BackupHandler {
theDevice = deviceIterator.next();
if (!theDevice.isLockDeviceId()) {
findNext = true;
while(findNext) {
if(lockedIds.contains(String.valueOf(nextId))) {
while (findNext) {
if (lockedIds.contains(String.valueOf(nextId))) {
nextId++;
} else {
findNext = false;
}
}
theDevice.setId(String.valueOf(nextId));
theDevice.setUniqueid("00:11:22:33:44:55:66:" + hueUniqueId(nextId));
theDevice.setUniqueid(hueUniqueId(nextId));
nextId++;
}
newdevices.put(theDevice.getId(), theDevice);
@@ -297,27 +298,51 @@ public class DeviceRepository extends BackupHandler {
}
private String hueUniqueId(Integer anId) {
String theUniqueId;
String theUniqueId = null;
Integer newValue;
String hexValueLeft;
String hexValueRight;
newValue = anId % 256;
if (newValue <= 0)
newValue = 1;
else if (newValue > 255)
newValue = 255;
hexValueLeft = HexLibrary.byteToHex(newValue.byteValue());
newValue = anId / 256;
newValue = newValue % 256;
if (newValue < 0)
newValue = 0;
else if (newValue > 255)
newValue = 255;
hexValueRight = HexLibrary.byteToHex(newValue.byteValue());
MessageDigest md = null;
theUniqueId = String.format("%s-%s", hexValueLeft, hexValueRight).toUpperCase();
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
log.warn("Cannot get MD5 utility to hash unique ids.");
}
if(md != null) {
md.update(anId.toString().getBytes());
byte[] digest = md.digest();
theUniqueId = String.format("%s:%s:%s:%s:%s:%s:%s-%s",
HexLibrary.encodeHexString(digest).substring(0, 2),
HexLibrary.encodeHexString(digest).substring(2, 4),
HexLibrary.encodeHexString(digest).substring(4, 6),
HexLibrary.encodeHexString(digest).substring(6, 8),
HexLibrary.encodeHexString(digest).substring(8, 10),
HexLibrary.encodeHexString(digest).substring(10, 12),
HexLibrary.encodeHexString(digest).substring(12, 14),
HexLibrary.encodeHexString(digest).substring(14, 16));
}
if(theUniqueId == null) {
newValue = anId % 256;
if (newValue <= 0)
newValue = 1;
else if (newValue > 255)
newValue = 255;
hexValueLeft = HexLibrary.byteToHex(newValue.byteValue());
newValue = anId / 256;
newValue = newValue % 256;
if (newValue < 0)
newValue = 0;
else if (newValue > 255)
newValue = 255;
hexValueRight = HexLibrary.byteToHex(newValue.byteValue());
theUniqueId = String.format("11:22:33:44:55:66:%s-%s", hexValueLeft, hexValueRight).toUpperCase();
}
return theUniqueId;
}
}

View File

@@ -30,6 +30,7 @@ public class UpnpListener {
private String upnpConfigIP;
// private boolean strict;
private boolean upnpOriginal;
private boolean upnpAdvanced;
private boolean traceupnp;
private boolean useUpnpIface;
private BridgeControlDescriptor bridgeControl;
@@ -38,31 +39,37 @@ public class UpnpListener {
private String httpType;
private HuePublicConfig aHueConfig;
private Integer theUpnpSendDelay;
/* This is the minimum response needed, all others are for the advanced setting */
private String responseTemplate1 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:"
+ HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n";
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+ "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
/* These next 2 templates are for the advanced upnp option */
private String responseTemplate2 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: uuid:" + HueConstants.UUID_PREFIX
+ "%s\r\n" + "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
private String responseTemplate3 = "HTTP/1.1 200 OK\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n"
+ "USN: uuid:" + HueConstants.UUID_PREFIX + "%s\r\n\r\n";
+ "EXT:\r\n" + "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
+ HueConstants.API_VERSION + "\r\n" + "hue-bridgeid: %s\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:"
+ HueConstants.UUID_PREFIX + "%s::upnp:rootdevice\r\n\r\n";
/* These notify templates are for the advanced upnp option */
private String notifyTemplate1 = "NOTIFY * HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CACHE-CONTROL: max-age=100\r\n"
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, UPnP/1.0, IpBridge/"
+ 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: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, 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: %s://%s:%s/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/"
+ "LOCATION: %s://%s:%s/description.xml\r\n" + "SERVER: FreeRTOS/6.0.5, 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";
@@ -75,6 +82,7 @@ public class UpnpListener {
upnpConfigIP = theSettings.getBridgeSettingsDescriptor().getUpnpConfigAddress();
// strict = theSettings.isUpnpStrict();
upnpOriginal = theSettings.getBridgeSettingsDescriptor().isUpnporiginal();
upnpAdvanced = theSettings.getBridgeSettingsDescriptor().isUpnpadvanced();
traceupnp = theSettings.getBridgeSettingsDescriptor().isTraceupnp();
useUpnpIface = theSettings.getBridgeSettingsDescriptor().isUseupnpiface();
theUpnpSendDelay = theSettings.getBridgeSettingsDescriptor().getUpnpsenddelay();
@@ -192,10 +200,13 @@ public class UpnpListener {
log.info("UPNP Discovery Listener running and ready....");
boolean loopControl = true;
boolean error = false;
try {
upnpMulticastSocket.setSoTimeout((int) Configuration.UPNP_NOTIFY_TIMEOUT);
} catch (SocketException e1) {
log.warn("Could not sent soTimeout on multi-cast socket");
if(upnpAdvanced) {
try {
upnpMulticastSocket.setSoTimeout((int) Configuration.UPNP_NOTIFY_TIMEOUT);
} catch (SocketException e1) {
log.warn("Could not sent soTimeout on multi-cast socket");
}
}
// Instant current, previous;
// previous = Instant.now();
@@ -326,37 +337,39 @@ public class UpnpListener {
+ " with discovery responseTemplate1 is <<<" + discoveryResponse + ">>>");
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
try {
Thread.sleep(theUpnpSendDelay);
} catch (InterruptedException e) {
// noop
}
discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS,
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
bridgeSNUUID, bridgeSNUUID);
if (traceupnp) {
log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":"
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
}
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
+ " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
if(upnpAdvanced) {
try {
Thread.sleep(theUpnpSendDelay);
} catch (InterruptedException e) {
// noop
}
discoveryResponse = String.format(responseTemplate2, Configuration.UPNP_MULTICAST_ADDRESS,
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
bridgeSNUUID, bridgeSNUUID);
if (traceupnp) {
log.info("Traceupnp: send upnp discovery template 2 with response address: " + httpLocationAddress + ":"
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
}
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
+ " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
try {
Thread.sleep(theUpnpSendDelay);
} catch (InterruptedException e) {
// noop
try {
Thread.sleep(theUpnpSendDelay);
} catch (InterruptedException e) {
// noop
}
discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS,
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
bridgeSNUUID);
if (traceupnp) {
log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":"
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
}
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
+ " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
}
discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS,
Configuration.UPNP_DISCOVERY_PORT, httpType, httpLocationAddress, httpServerPort, bridgeId,
bridgeSNUUID);
if (traceupnp) {
log.info("Traceupnp: send upnp discovery template 3 with response address: " + httpLocationAddress + ":"
+ httpServerPort + " to address: " + requester.getHostAddress() + ":" + sourcePort);
}
log.debug("sendUpnpResponse to address: " + requester.getHostAddress() + ":" + sourcePort
+ " discovery responseTemplate3 is <<<" + discoveryResponse + ">>>");
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
}
private void sendUDPResponse(byte[] udpMessage, InetAddress requester, int sourcePort) throws IOException {

View File

@@ -55,10 +55,12 @@ public class UpnpSettingsResource {
+ "<depth>24</depth>\n"
+ "<url>hue_logo_3.png</url>\n"
+ "</icon>\n"
+ "</iconList>\n"
+ "</device>\n"
+ "</root>\n";
+ "</iconList>\n";
private String hueTemplate_end = "</device>\n"
+ "</root>\n";
/* not utilizing this section any more
private String hueTemplate_mid_orig = "<serviceList>\n"
+ "<service>\n"
+ "<serviceType>(null)</serviceType>\n"
@@ -68,7 +70,7 @@ public class UpnpSettingsResource {
+ "<SCPDURL>(null)</SCPDURL>\n"
+ "</service>\n"
+ "</serviceList>\n";
*/
public UpnpSettingsResource(BridgeSettings theBridgeSettings) {
super();
@@ -92,16 +94,24 @@ public class UpnpSettingsResource {
String hueTemplate = null;
if(theSettings.isUpnporiginal()) {
httpLocationAddr = theSettings.getUpnpConfigAddress();
hueTemplate = hueTemplate_pre + hueTemplate_mid_orig + hueTemplate_post;
} else {
hueTemplate = hueTemplate_pre + hueTemplate_end;
} else if(!theSettings.isUpnpadvanced()) {
if(theSettings.isUseupnpiface()) {
httpLocationAddr = theSettings.getUpnpConfigAddress();
} else {
log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port());
httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress();
}
hueTemplate = hueTemplate_pre + hueTemplate_post;
hueTemplate = hueTemplate_pre + hueTemplate_end;
} else {
if(theSettings.isUseupnpiface()) {
httpLocationAddr = theSettings.getUpnpConfigAddress();
} else {
log.debug("Get Outbound address for ip:" + request.ip() + " and port:" + request.port());
httpLocationAddr = AddressUtil.getOutboundAddress(request.ip(), request.port()).getHostAddress();
}
hueTemplate = hueTemplate_pre + hueTemplate_post + hueTemplate_end;
}
String bridgeIdMac = HuePublicConfig.createConfig("temp", httpLocationAddr, HueConstants.HUB_VERSION, theSettings.getHubmac()).getSNUUIDFromMac();

View File

@@ -824,6 +824,11 @@
<td><input type="checkbox" ng-model="bridge.settings.upnporiginal" ng-true-value=true
ng-false-value=false> {{bridge.settings.upnporiginal}}</td>
</tr>
<tr>
<td>UPNP Advanced (use multiple responses and notifies)</td>
<td><input type="checkbox" ng-model="bridge.settings.upnpadvanced" ng-true-value=true
ng-false-value=false> {{bridge.settings.upnpadvanced}}</td>
</tr>
<tr>
<td>Trace UPNP Calls</td>
<td><input type="checkbox" ng-model="bridge.settings.traceupnp" ng-true-value=true