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>
<artifactId>ha-bridge</artifactId>
<version>1.4.3color</version>
<version>2.0.0</version>
<packaging>jar</packaging>
<name>HA Bridge</name>

View File

@@ -34,13 +34,18 @@ 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;
@@ -49,6 +54,7 @@ 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;
@@ -58,6 +64,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter;
/**
@@ -78,6 +85,10 @@ public class HueMulator implements HueErrorStringSet {
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 BridgeSettingsDescriptor bridgeSettings;
private byte[] sendData;
@@ -87,6 +98,22 @@ public class HueMulator implements HueErrorStringSet {
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;
@@ -495,9 +522,9 @@ public class HueMulator implements HueErrorStringSet {
if(thermoSetting.getControl().equalsIgnoreCase("temp")) {
if(request.body().contains("bri")) {
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
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");
theNest.getThermostat(thermoSetting.getName()).setTargetTemperature(Float.parseFloat(thermoSetting.getTemp()));
}
@@ -531,7 +558,7 @@ public class HueMulator implements HueErrorStringSet {
}
try {
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());
} catch (IOException e) {
log.warn("Could not execute request: " + callItems[i].getItem(), e);
@@ -556,15 +583,25 @@ public class HueMulator implements HueErrorStringSet {
}
try {
String intermediate = callItems[i].getItem().substring(callItems[i].getItem().indexOf("://") + 3);
String ipAddr = intermediate.substring(0, intermediate.indexOf(':'));
String port = intermediate.substring(intermediate.indexOf(':') + 1, intermediate.indexOf('/'));
String theBody = replaceIntensityValue(intermediate.substring(intermediate.indexOf('/')+1), state.getBri());
if(theBody.startsWith("0x")) {
sendData = DatatypeConverter.parseHexBinary(theBody.substring(2));
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));
@@ -584,12 +621,12 @@ public class HueMulator implements HueErrorStringSet {
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;
String anUrl = replaceIntensityValue(callItems[i].getItem(), 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(anUrl, device.getHttpVerb(), device.getContentType(), body, theHeaders) == null) {
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.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)){
String intensityByte = String.valueOf(intensity);
request = request.replace(INTENSITY_BYTE, intensityByte);
}else if(request.contains(INTENSITY_PERCENT)){
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);
String intensityPercent = String.valueOf(percentBrightness);
request = request.replace(INTENSITY_PERCENT, intensityPercent);
} else if(request.contains(INTENSITY_MATH)){
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));
variables.put(INTENSITY_MATH_VALUE, new BigDecimal(intensity));
@@ -641,7 +694,15 @@ public class HueMulator implements HueErrorStringSet {
Expression exp = new Expression(mathDescriptor);
BigDecimal result = exp.eval(variables);
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) {
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) {
HttpUriRequest request = null;
String theContent = null;
@@ -681,7 +742,11 @@ public class HueMulator implements HueErrorStringSet {
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());
if(response.getStatusLine().getStatusCode() >= 200 && response.getStatusLine().getStatusCode() < 300){
theContent = EntityUtils.toString(response.getEntity(), Charset.forName("UTF-8")); //read content for data

View File

@@ -33,68 +33,117 @@ public class UpnpListener {
bridgeControl = theControl;
}
@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(Configuration.UPNP_DISCOVERY_PORT);) {
InetSocketAddress socketAddress = new InetSocketAddress(Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT);
Enumeration<NetworkInterface> ifs = NetworkInterface.getNetworkInterfaces();
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++;
}
boolean portLoopControl = true;
int retryCount = 0;
while(portLoopControl) {
try {
responseSocket = new DatagramSocket(upnpResponsePort);
if(retryCount > 0)
log.error("Upnp Response Port is in use, found port: " + upnpResponsePort);
retryCount = 0;
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 - port is: " + upnpResponsePort);
if(retryCount >= 20) {
portLoopControl = false;
log.error("Upnp Response Port is in use, could not find - last port tried: " + upnpResponsePort + " with message: " + e.getMessage());
return false;
}
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);
if(traceupnp)
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....");
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");
if(bridgeControl.isStop())
if (bridgeControl.isStop())
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");
return false;
}