mirror of
https://github.com/bwssytems/ha-bridge.git
synced 2025-12-16 18:24:36 +00:00
Finished updating UPNP handling with ports and errors and testing for
new features. Updateding README
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user