Finished updating UPNP handling with ports and errors and testing for

new features.

Updateding README
This commit is contained in:
Admin
2016-04-26 16:49:26 -05:00
parent 60239bad82
commit 72b6b2027b
4 changed files with 281 additions and 98 deletions

117
README.md

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@
<groupId>com.bwssystems.HABridge</groupId> <groupId>com.bwssystems.HABridge</groupId>
<artifactId>ha-bridge</artifactId> <artifactId>ha-bridge</artifactId>
<version>1.4.3color</version> <version>2.0.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>HA Bridge</name> <name>HA Bridge</name>

View File

@@ -34,13 +34,18 @@ import static spark.Spark.put;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -49,6 +54,7 @@ import org.slf4j.LoggerFactory;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
@@ -58,6 +64,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter; import javax.xml.bind.DatatypeConverter;
/** /**
@@ -78,6 +85,10 @@ public class HueMulator implements HueErrorStringSet {
private Nest theNest; private Nest theNest;
private HueHome myHueHome; private HueHome myHueHome;
private HttpClient httpClient; private HttpClient httpClient;
private CloseableHttpClient httpclientSSL;
private SSLContext sslcontext;
private SSLConnectionSocketFactory sslsf;
private RequestConfig globalConfig;
private ObjectMapper mapper; private ObjectMapper mapper;
private BridgeSettingsDescriptor bridgeSettings; private BridgeSettingsDescriptor bridgeSettings;
private byte[] sendData; private byte[] sendData;
@@ -87,6 +98,22 @@ public class HueMulator implements HueErrorStringSet {
public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome){ public HueMulator(BridgeSettingsDescriptor theBridgeSettings, DeviceRepository aDeviceRepository, HarmonyHome theHarmonyHome, NestHome aNestHome, HueHome aHueHome){
httpClient = HttpClients.createDefault(); httpClient = HttpClients.createDefault();
// Trust own CA and all self-signed certs
sslcontext = SSLContexts.createDefault();
// Allow TLSv1 protocol only
sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
globalConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.STANDARD)
.build();
httpclientSSL = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.setDefaultRequestConfig(globalConfig)
.build();
mapper = new ObjectMapper(); //armzilla: work around Echo incorrect content type and breaking mapping. Map manually mapper = new ObjectMapper(); //armzilla: work around Echo incorrect content type and breaking mapping. Map manually
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
repository = aDeviceRepository; repository = aDeviceRepository;
@@ -495,9 +522,9 @@ public class HueMulator implements HueErrorStringSet {
if(thermoSetting.getControl().equalsIgnoreCase("temp")) { if(thermoSetting.getControl().equalsIgnoreCase("temp")) {
if(request.body().contains("bri")) { if(request.body().contains("bri")) {
if(bridgeSettings.isFarenheit()) if(bridgeSettings.isFarenheit())
thermoSetting.setTemp(String.valueOf((Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri())) - 32.0)/1.8)); thermoSetting.setTemp(String.valueOf((Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri(), false)) - 32.0)/1.8));
else else
thermoSetting.setTemp(String.valueOf(Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri())))); thermoSetting.setTemp(String.valueOf(Double.parseDouble(replaceIntensityValue(thermoSetting.getTemp(), state.getBri(), false))));
log.debug("Setting thermostat: " + thermoSetting.getName() + " to " + thermoSetting.getTemp() + "C"); log.debug("Setting thermostat: " + thermoSetting.getName() + " to " + thermoSetting.getTemp() + "C");
theNest.getThermostat(thermoSetting.getName()).setTargetTemperature(Float.parseFloat(thermoSetting.getTemp())); theNest.getThermostat(thermoSetting.getName()).setTargetTemperature(Float.parseFloat(thermoSetting.getTemp()));
} }
@@ -531,7 +558,7 @@ public class HueMulator implements HueErrorStringSet {
} }
try { try {
log.debug("Executing request: " + callItems[i].getItem()); log.debug("Executing request: " + callItems[i].getItem());
Process p = Runtime.getRuntime().exec(callItems[i].getItem()); Process p = Runtime.getRuntime().exec(replaceIntensityValue(callItems[i].getItem(), state.getBri(), false));
log.debug("Process running: " + p.isAlive()); log.debug("Process running: " + p.isAlive());
} catch (IOException e) { } catch (IOException e) {
log.warn("Could not execute request: " + callItems[i].getItem(), e); log.warn("Could not execute request: " + callItems[i].getItem(), e);
@@ -556,15 +583,25 @@ public class HueMulator implements HueErrorStringSet {
} }
try { try {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3); String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
String ipAddr = intermediate.substring(0, intermediate.indexOf(':')); String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String port = intermediate.substring(intermediate.indexOf(':') + 1, intermediate.indexOf('/')); String theUrlBody = intermediate.substring(intermediate.indexOf('/')+1);
String theBody = replaceIntensityValue(intermediate.substring(intermediate.indexOf('/')+1), state.getBri()); String hostAddr = null;
if(theBody.startsWith("0x")) { String port = null;
sendData = DatatypeConverter.parseHexBinary(theBody.substring(2)); if(hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
port = hostPortion.substring(intermediate.indexOf(':') + 1);
} }
else else
sendData = theBody.getBytes(); hostAddr = hostPortion;
InetAddress IPAddress = InetAddress.getByName(ipAddr); 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://")) { if(callItems[i].getItem().contains("udp://")) {
log.debug("executing HUE api request to UDP: " + callItems[i].getItem()); log.debug("executing HUE api request to UDP: " + callItems[i].getItem());
DatagramSocket responseSocket = new DatagramSocket(Integer.parseInt(port)); DatagramSocket responseSocket = new DatagramSocket(Integer.parseInt(port));
@@ -584,12 +621,12 @@ public class HueMulator implements HueErrorStringSet {
else { else {
log.debug("executing HUE api request to Http " + (device.getHttpVerb() == null?"GET":device.getHttpVerb()) + ": " + callItems[i].getItem()); 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; String body;
String anUrl = replaceIntensityValue(callItems[i].getItem(), state.getBri());
if (state.isOn()) if (state.isOn())
body = replaceIntensityValue(device.getContentBody(), state.getBri()); body = replaceIntensityValue(device.getContentBody(), state.getBri(), false);
else else
body = replaceIntensityValue(device.getContentBodyOff(), state.getBri()); body = replaceIntensityValue(device.getContentBodyOff(), state.getBri(), false);
// make call // make call
if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), body, theHeaders) == null) { if (doHttpRequest(anUrl, device.getHttpVerb(), device.getContentType(), body, theHeaders) == null) {
log.warn("Error on calling url to change device state: " + anUrl); log.warn("Error on calling url to change device state: " + anUrl);
@@ -620,18 +657,34 @@ public class HueMulator implements HueErrorStringSet {
* intensity.percent : 0-100, adjusted for the vera * intensity.percent : 0-100, adjusted for the vera
* intensity.math(X*1) : where X is the value from the interface call and can use net.java.dev.eval math * intensity.math(X*1) : where X is the value from the interface call and can use net.java.dev.eval math
*/ */
protected String replaceIntensityValue(String request, int intensity){ protected String replaceIntensityValue(String request, int intensity, boolean isHex){
if(request == null){ if(request == null){
return ""; return "";
} }
if(request.contains(INTENSITY_BYTE)){ if(request.contains(INTENSITY_BYTE)) {
String intensityByte = String.valueOf(intensity); if(isHex) {
request = request.replace(INTENSITY_BYTE, intensityByte); BigInteger bigInt = BigInteger.valueOf(intensity);
}else if(request.contains(INTENSITY_PERCENT)){ byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_BYTE, hexValue);
}
else {
String intensityByte = String.valueOf(intensity);
request = request.replace(INTENSITY_BYTE, intensityByte);
}
} else if(request.contains(INTENSITY_PERCENT)) {
int percentBrightness = (int) Math.round(intensity/255.0*100); int percentBrightness = (int) Math.round(intensity/255.0*100);
String intensityPercent = String.valueOf(percentBrightness); if(isHex) {
request = request.replace(INTENSITY_PERCENT, intensityPercent); BigInteger bigInt = BigInteger.valueOf(percentBrightness);
} else if(request.contains(INTENSITY_MATH)){ byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_PERCENT, hexValue);
}
else {
String intensityPercent = String.valueOf(percentBrightness);
request = request.replace(INTENSITY_PERCENT, intensityPercent);
}
} else if(request.contains(INTENSITY_MATH)) {
Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>(); Map<String, BigDecimal> variables = new HashMap<String, BigDecimal>();
String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE)); String mathDescriptor = request.substring(request.indexOf(INTENSITY_MATH) + INTENSITY_MATH.length(),request.indexOf(INTENSITY_MATH_CLOSE));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity)); variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
@@ -641,7 +694,15 @@ public class HueMulator implements HueErrorStringSet {
Expression exp = new Expression(mathDescriptor); Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables); BigDecimal result = exp.eval(variables);
Integer endResult = Math.round(result.floatValue()); Integer endResult = Math.round(result.floatValue());
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString()); if(isHex) {
BigInteger bigInt = BigInteger.valueOf(endResult);
byte[] theBytes = bigInt.toByteArray();
String hexValue = DatatypeConverter.printHexBinary(theBytes);
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, hexValue);
}
else {
request = request.replace(INTENSITY_MATH + mathDescriptor + INTENSITY_MATH_CLOSE, endResult.toString());
}
} catch (Exception e) { } catch (Exception e) {
log.warn("Could not execute Math: " + mathDescriptor, e); log.warn("Could not execute Math: " + mathDescriptor, e);
} }
@@ -650,7 +711,7 @@ public class HueMulator implements HueErrorStringSet {
} }
// This function executes the url from the device repository against the vera // This function executes the url from the device repository against the target as http or https as defined
protected String doHttpRequest(String url, String httpVerb, String contentType, String body, NameValue[] headers) { protected String doHttpRequest(String url, String httpVerb, String contentType, String body, NameValue[] headers) {
HttpUriRequest request = null; HttpUriRequest request = null;
String theContent = null; String theContent = null;
@@ -681,7 +742,11 @@ public class HueMulator implements HueErrorStringSet {
request.setHeader(headers[i].getName(), headers[i].getValue()); request.setHeader(headers[i].getName(), headers[i].getValue());
} }
} }
HttpResponse response = httpClient.execute(request); HttpResponse response;
if(url.startsWith("https"))
response = httpclientSSL.execute(request);
else
response = httpClient.execute(request);
log.debug((httpVerb == null?"GET":httpVerb) + " execute on URL responded: " + response.getStatusLine().getStatusCode()); log.debug((httpVerb == null?"GET":httpVerb) + " execute on URL responded: " + response.getStatusLine().getStatusCode());
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){ if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data

View File

@@ -33,68 +33,117 @@ public class UpnpListener {
bridgeControl = theControl; bridgeControl = theControl;
} }
@SuppressWarnings("resource")
public boolean startListening(){ public boolean startListening(){
log.info("UPNP Discovery Listener starting...."); log.info("UPNP Discovery Listener starting....");
DatagramSocket responseSocket = null;
MulticastSocket upnpMulticastSocket = null;
Enumeration<NetworkInterface> ifs = null;
try (DatagramSocket responseSocket = new DatagramSocket(upnpResponsePort); boolean portLoopControl = true;
MulticastSocket upnpMulticastSocket = new MulticastSocket(Configuration.UPNP_DISCOVERY_PORT);) { int retryCount = 0;
InetSocketAddress socketAddress = new InetSocketAddress(Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT); while(portLoopControl) {
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces(); try {
responseSocket = new DatagramSocket(upnpResponsePort);
while (ifs.hasMoreElements()) { if(retryCount > 0)
NetworkInterface xface = ifs.nextElement(); log.error("Upnp Response Port is in use, found port: " + upnpResponsePort);
Enumeration<InetAddress> addrs = xface.getInetAddresses(); retryCount = 0;
String name = xface.getName(); portLoopControl = false;
int IPsPerNic = 0; } catch(SocketException e) {
if(retryCount == 0)
while (addrs.hasMoreElements()) { log.warn("Upnp Response Port is in use, starting loop to find open port for 20 tries - port is: " + upnpResponsePort);
InetAddress addr = addrs.nextElement(); if(retryCount >= 20) {
if(traceupnp) portLoopControl = false;
log.info("Traceupnp: " + name + " ... has addr " + addr); log.error("Upnp Response Port is in use, could not find - last port tried: " + upnpResponsePort + " with message: " + e.getMessage());
else return false;
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
}
} }
log.debug("Checking " + name + " to our interface set"); }
if (IPsPerNic > 0) { 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 duso 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 thsi machine: " + e.getMessage());
return false;
}
while (ifs.hasMoreElements()) {
NetworkInterface xface = ifs.nextElement();
Enumeration<InetAddress> addrs = xface.getInetAddresses();
String name = xface.getName();
int IPsPerNic = 0;
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if (traceupnp)
log.info("Traceupnp: " + name + " ... has addr " + addr);
else
log.debug(name + " ... has addr " + addr);
if (InetAddressUtils.isIPv4Address(addr.getHostAddress())) {
IPsPerNic++;
}
}
log.debug("Checking " + name + " to our interface set");
if (IPsPerNic > 0) {
try {
upnpMulticastSocket.joinGroup(socketAddress, xface); upnpMulticastSocket.joinGroup(socketAddress, xface);
if(traceupnp) if (traceupnp)
log.info("Traceupnp: Adding " + name + " to our interface set"); log.info("Traceupnp: Adding " + name + " to our interface set");
else else
log.debug("Adding " + name + " to our interface set"); log.debug("Adding " + name + " to our interface set");
} catch (IOException e) {
log.warn("Multicast join failed for: " + socketAddress.getHostName() + " to interface: "
+ xface.getName() + " with message: " + e.getMessage());
} }
} }
log.info("UPNP Discovery Listener running and ready....");
boolean loopControl = true;
while(loopControl){ //trigger shutdown here
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
upnpMulticastSocket.receive(packet);
if(isSSDPDiscovery(packet)){
sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort());
}
if(bridgeControl.isReinit() || bridgeControl.isStop()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// noop
}
loopControl = false;
}
}
upnpMulticastSocket.close();
responseSocket.close();
} catch (IOException e) {
log.error("UpnpListener encountered an error opening sockets. Shutting down", e);
} }
if(bridgeControl.isReinit())
log.info("UPNP Discovery Listener running and ready....");
boolean loopControl = true;
boolean error = false;
while (loopControl) { // trigger shutdown here
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
try {
upnpMulticastSocket.receive(packet);
if (isSSDPDiscovery(packet)) {
try {
sendUpnpResponse(responseSocket, packet.getAddress(), packet.getPort());
} catch (IOException e) {
log.error("UpnpListener encountered an error sending upnp response packet. Shutting down", e);
error = true;
}
}
} catch (IOException e) {
log.error("UpnpListener encountered an error reading socket. Shutting down", e);
error = true;
}
if (error || bridgeControl.isReinit() || bridgeControl.isStop()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// noop
}
loopControl = false;
}
}
upnpMulticastSocket.close();
responseSocket.close();
if (bridgeControl.isReinit())
log.info("UPNP Discovery Listener - ended, restart found"); log.info("UPNP Discovery Listener - ended, restart found");
if(bridgeControl.isStop()) if (bridgeControl.isStop())
log.info("UPNP Discovery Listener - ended, stop found"); log.info("UPNP Discovery Listener - ended, stop found");
if(!bridgeControl.isStop()&& !bridgeControl.isReinit()) { if (!bridgeControl.isStop() && !bridgeControl.isReinit()) {
log.info("UPNP Discovery Listener - ended, error found"); log.info("UPNP Discovery Listener - ended, error found");
return false; return false;
} }