Updated description xml to remove null service list. Most likely culprit

of /null calls to spark.

Started adding FHEM integration
This commit is contained in:
bsamuels
2017-12-29 15:57:37 -06:00
parent ca2c5f7b04
commit 4c694cb285
14 changed files with 811 additions and 12 deletions

View File

@@ -102,6 +102,9 @@ public class BridgeSettingsDescriptor {
@SerializedName("homewizardaddress")
@Expose
private IpList homewizardaddress;
@SerializedName("upnpsenddelay")
@Expose
private Integer upnpsenddelay;
private boolean settingsChanged;
private boolean veraconfigured;
@@ -148,6 +151,7 @@ public class BridgeSettingsDescriptor {
this.webaddress = "0.0.0.0";
this.hubversion = HueConstants.HUB_VERSION;
this.hubmac = null;
this.upnpsenddelay = 1500;
}
public String getUpnpConfigAddress() {
return upnpconfigaddress;
@@ -437,6 +441,12 @@ public class BridgeSettingsDescriptor {
public void setSecurityData(String securityData) {
this.securityData = securityData;
}
public Integer getUpnpsenddelay() {
return upnpsenddelay;
}
public void setUpnpsenddelay(Integer upnpsenddelay) {
this.upnpsenddelay = upnpsenddelay;
}
public Boolean isValidVera() {
if(this.getVeraAddress() == null || this.getVeraAddress().getDevices().size() <= 0)
return false;

View File

@@ -372,6 +372,8 @@ public class SystemControl {
put(SYSTEM_CONTEXT + "/settings", (request, response) -> {
log.debug("save bridge settings requested from " + request.ip() + " with body: " + request.body());
BridgeSettingsDescriptor newBridgeSettings = new Gson().fromJson(request.body(), BridgeSettingsDescriptor.class);
if(newBridgeSettings.getUpnpsenddelay() > 15000)
newBridgeSettings.setUpnpsenddelay(15000);
bridgeSettings.save(newBridgeSettings);
response.status(HttpStatus.SC_OK);
response.type("application/json");

View File

@@ -0,0 +1,19 @@
package com.bwssystems.HABridge.plugins.fhem;
public class FHEMCommand {
private String url;
private String command;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
}

View File

@@ -0,0 +1,27 @@
package com.bwssystems.HABridge.plugins.fhem;
public class FHEMDevice {
private String address;
private String name;
private FHEMItem item;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public FHEMItem getItem() {
return item;
}
public void setItem(FHEMItem item) {
this.item = item;
}
}

View File

@@ -0,0 +1,197 @@
package com.bwssystems.HABridge.plugins.fhem;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.BridgeSettings;
import com.bwssystems.HABridge.Home;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.CallItem;
import com.bwssystems.HABridge.api.hue.HueError;
import com.bwssystems.HABridge.api.hue.HueErrorResponse;
import com.bwssystems.HABridge.dao.DeviceDescriptor;
import com.bwssystems.HABridge.hue.BrightnessDecode;
import com.bwssystems.HABridge.hue.ColorData;
import com.bwssystems.HABridge.hue.ColorDecode;
import com.bwssystems.HABridge.hue.DeviceDataDecode;
import com.bwssystems.HABridge.hue.MultiCommandUtil;
import com.bwssystems.HABridge.hue.TimeDecode;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.bwssystems.HABridge.plugins.http.HTTPHome;
import com.google.gson.Gson;
public class FHEMHome implements Home {
private static final Logger log = LoggerFactory.getLogger(FHEMHome.class);
private Map<String, FHEMInstance> fhemMap;
private Boolean validFhem;
private HTTPHandler httpClient;
private boolean closed;
public FHEMHome(BridgeSettings bridgeSettings) {
super();
closed = true;
createHome(bridgeSettings);
closed = false;
}
@Override
public String deviceHandler(CallItem anItem, MultiCommandUtil aMultiUtil, String lightId, int intensity,
Integer targetBri, Integer targetBriInc, ColorData colorData, DeviceDescriptor device, String body) {
String theUrl = anItem.getItem().getAsString();
String responseString = null;
if(theUrl != null && !theUrl.isEmpty()) {
FHEMCommand theCommand = null;
try {
theCommand = new Gson().fromJson(theUrl, FHEMCommand.class);
} catch(Exception e) {
log.warn("Cannot parse command to FHEM <<<" + theUrl + ">>>", e);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
return responseString;
}
String intermediate = theCommand.getUrl().substring(theCommand.getUrl().indexOf("://") + 3);
String hostPortion = intermediate.substring(0, intermediate.indexOf('/'));
String theUrlBody = intermediate.substring(intermediate.indexOf('/') + 1);
String hostAddr = null;
if (hostPortion.contains(":")) {
hostAddr = hostPortion.substring(0, intermediate.indexOf(':'));
} else
hostAddr = hostPortion;
FHEMInstance theHandler = findHandlerByAddress(hostAddr);
if(theHandler != null) {
String anUrl = BrightnessDecode.calculateReplaceIntensityValue(theUrlBody,
intensity, targetBri, targetBriInc, false);
if (colorData != null) {
anUrl = ColorDecode.replaceColorData(anUrl, colorData, BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc), false);
}
anUrl = DeviceDataDecode.replaceDeviceData(anUrl, device);
anUrl = TimeDecode.replaceTimeValue(anUrl);
String aCommand = null;
if(theCommand.getCommand() != null && !theCommand.getCommand().isEmpty()) {
aCommand = BrightnessDecode.calculateReplaceIntensityValue(theCommand.getCommand(),
intensity, targetBri, targetBriInc, false);
if (colorData != null) {
aCommand = ColorDecode.replaceColorData(aCommand, colorData, BrightnessDecode.calculateIntensity(intensity, targetBri, targetBriInc), false);
}
aCommand = DeviceDataDecode.replaceDeviceData(aCommand, device);
aCommand = TimeDecode.replaceTimeValue(aCommand);
}
try {
theHandler.callCommand(anUrl, aCommand, httpClient);
} catch (Exception e) {
log.warn("Cannot send comand to FHEM", e);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} else {
log.warn("FHEM Call could not complete, no address found: " + theUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
} else {
log.warn("FHEM Call to be presented as http(s)://<ip_address>(:<port>)/payload, format of request unknown: " + theUrl);
responseString = new Gson().toJson(HueErrorResponse.createResponse("6", "/lights/" + lightId,
"Error on calling url to change device state", "/lights/"
+ lightId + "state", null, null).getTheErrors(), HueError[].class);
}
return responseString;
}
private FHEMInstance findHandlerByAddress(String hostAddress) {
FHEMInstance aHandler = null;
boolean found = false;
Iterator<String> keys = fhemMap.keySet().iterator();
while(keys.hasNext()) {
String key = keys.next();
aHandler = fhemMap.get(key);
if(aHandler != null && aHandler.getFhemAddress().getIp().equals(hostAddress)) {
found = true;
break;
}
}
if(!found)
aHandler = null;
return aHandler;
}
@Override
public Object getItems(String type) {
if(!validFhem)
return null;
log.debug("consolidating devices for java.lang.String");
List<FHEMDevice> theResponse = null;
Iterator<String> keys = fhemMap.keySet().iterator();
List<FHEMDevice> deviceList = new ArrayList<FHEMDevice>();
while(keys.hasNext()) {
String key = keys.next();
theResponse = fhemMap.get(key).getDevices(httpClient);
if(theResponse != null)
addFHEMDevices(deviceList, theResponse, key);
else {
log.warn("Cannot get devices for FHEM: " + key + ", skipping this FHEM.");
continue;
}
}
return deviceList;
}
private Boolean addFHEMDevices(List<FHEMDevice> theDeviceList, List<FHEMDevice> theSourceList, String theKey) {
Iterator<FHEMDevice> devices = theSourceList.iterator();
while(devices.hasNext()) {
FHEMDevice theDevice = devices.next();
theDeviceList.add(theDevice);
}
return true;
}
@Override
public Home createHome(BridgeSettings bridgeSettings) {
fhemMap = null;
validFhem = bridgeSettings.getBridgeSettingsDescriptor().isValidOpenhab();
log.info("FHEM Home created." + (validFhem ? "" : " No FHEMs configured."));
if(validFhem) {
fhemMap = new HashMap<String,FHEMInstance>();
httpClient = HTTPHome.getHandler();
Iterator<NamedIP> theList = bridgeSettings.getBridgeSettingsDescriptor().getOpenhabaddress().getDevices().iterator();
while(theList.hasNext() && validFhem) {
NamedIP aFhem = theList.next();
try {
fhemMap.put(aFhem.getName(), new FHEMInstance(aFhem));
} catch (Exception e) {
log.error("Cannot get FHEM (" + aFhem.getName() + ") setup, Exiting with message: " + e.getMessage(), e);
validFhem = false;
}
}
}
return this;
}
@Override
public void closeHome() {
log.debug("Closing Home.");
if(!closed && validFhem) {
log.debug("Home is already closed....");
return;
}
if(httpClient != null)
httpClient.closeHandler();
fhemMap = null;
closed = true;
}
}

View File

@@ -0,0 +1,100 @@
package com.bwssystems.HABridge.plugins.fhem;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bwssystems.HABridge.NamedIP;
import com.bwssystems.HABridge.api.NameValue;
import com.bwssystems.HABridge.plugins.http.HTTPHandler;
import com.google.gson.Gson;
public class FHEMInstance {
private static final Logger log = LoggerFactory.getLogger(FHEMInstance.class);
private NamedIP theFhem;
public FHEMInstance(NamedIP fhemLocation) {
super();
theFhem = fhemLocation;
}
public NamedIP getFhemAddress() {
return theFhem;
}
public void setFhemAddress(NamedIP fhemAddress) {
this.theFhem = fhemAddress;
}
public Boolean callCommand(String aCommand, String commandData, HTTPHandler httpClient) {
log.debug("calling FHEM: " + theFhem.getIp() + ":" + theFhem.getPort() + aCommand);
String aUrl = null;
NameValue[] headers = null;
if(theFhem.getSecure() != null && theFhem.getSecure())
aUrl = "https://";
else
aUrl = "http://";
if(theFhem.getUsername() != null && !theFhem.getUsername().isEmpty() && theFhem.getPassword() != null && !theFhem.getPassword().isEmpty()) {
aUrl = aUrl + theFhem.getUsername() + ":" + theFhem.getPassword() + "@";
}
aUrl = aUrl + theFhem.getIp() + ":" + theFhem.getPort() + "/" + aCommand;
String theData = httpClient.doHttpRequest(aUrl, HttpPost.METHOD_NAME, "text/plain", commandData, headers);
log.debug("call Command return is: <" + theData + ">");
return true;
}
public List<FHEMDevice> getDevices(HTTPHandler httpClient) {
List<FHEMDevice> deviceList = null;
FHEMItem[] theFhemStates;
String theUrl = null;
String theData;
NameValue[] headers = null;
if(theFhem.getSecure() != null && theFhem.getSecure())
theUrl = "https://";
else
theUrl = "http://";
if(theFhem.getUsername() != null && !theFhem.getUsername().isEmpty() && theFhem.getPassword() != null && !theFhem.getPassword().isEmpty()) {
theUrl = theUrl + theFhem.getUsername() + ":" + theFhem.getPassword() + "@";
}
theUrl = theUrl + theFhem.getIp() + ":" + theFhem.getPort() + "/fhem?cmd=jsonlist2";
theData = httpClient.doHttpRequest(theUrl, HttpGet.METHOD_NAME, "application/json", null, headers);
if(theData != null) {
log.debug("GET FHEM States - data: " + theData);
theData = getJSONData(theData);
theFhemStates = new Gson().fromJson(theData, FHEMItem[].class);
if(theFhemStates == null) {
log.warn("Cannot get an devices for FHEM " + theFhem.getName() + " as response is not parsable.");
}
else {
deviceList = new ArrayList<FHEMDevice>();
for (int i = 0; i < theFhemStates.length; i++) {
FHEMDevice aNewFhemDeviceDevice = new FHEMDevice();
aNewFhemDeviceDevice.setItem(theFhemStates[i]);
aNewFhemDeviceDevice.setAddress(theFhem.getIp() + ":" + theFhem.getPort());
aNewFhemDeviceDevice.setName(theFhem.getName());
deviceList.add(aNewFhemDeviceDevice);
}
}
}
else
log.warn("Cannot get an devices for FHEM " + theFhem.getName() + " http call failed.");
return deviceList;
}
public String getJSONData(String response) {
String theData;
theData = response.substring(response.indexOf("<pre>") + 4);
theData = theData.substring(1, theData.indexOf("</pre>") - 1);
// TODO Fix stripping out new line chars
return theData;
}
protected void closeClient() {
}
}

View File

@@ -0,0 +1,44 @@
package com.bwssystems.HABridge.plugins.fhem;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class FHEMItem {
@SerializedName("Arg")
@Expose
private String arg;
@SerializedName("Results")
@Expose
private List<Result> results = null;
@SerializedName("totalResultsReturned")
@Expose
private Integer totalResultsReturned;
public String getArg() {
return arg;
}
public void setArg(String arg) {
this.arg = arg;
}
public List<Result> getResults() {
return results;
}
public void setResults(List<Result> results) {
this.results = results;
}
public Integer getTotalResultsReturned() {
return totalResultsReturned;
}
public void setTotalResultsReturned(Integer totalResultsReturned) {
this.totalResultsReturned = totalResultsReturned;
}
}

View File

@@ -0,0 +1,43 @@
package com.bwssystems.HABridge.plugins.fhem;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
public class Result {
@SerializedName("Name")
@Expose
private String name;
@SerializedName("PossibleSets")
@Expose
private String possibleSets;
@SerializedName("PossibleAttrs")
@Expose
private String possibleAttrs;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPossibleSets() {
return possibleSets;
}
public void setPossibleSets(String possibleSets) {
this.possibleSets = possibleSets;
}
public String getPossibleAttrs() {
return possibleAttrs;
}
public void setPossibleAttrs(String possibleAttrs) {
this.possibleAttrs = possibleAttrs;
}
}

View File

@@ -30,6 +30,7 @@ public class UpnpListener {
private String bridgeId;
private String bridgeSNUUID;
private HuePublicConfig aHueConfig;
private Integer theUpnpSendDelay;
private String responseTemplate1 = "HTTP/1.1 200 OK\r\n" +
"HOST: %s:%s\r\n" +
"CACHE-CONTROL: max-age=100\r\n" +
@@ -76,6 +77,7 @@ public class UpnpListener {
strict = theSettings.isUpnpStrict();
traceupnp = theSettings.isTraceupnp();
useUpnpIface = theSettings.isUseupnpiface();
theUpnpSendDelay = theSettings.getUpnpsenddelay();
bridgeControl = theControl;
aHueConfig = HuePublicConfig.createConfig("temp", responseAddress, HueConstants.HUB_VERSION, theSettings.getHubmac());
bridgeId = aHueConfig.getBridgeid();
@@ -230,6 +232,11 @@ public class UpnpListener {
protected void sendUpnpResponse(InetAddress requester, int sourcePort) throws IOException {
String discoveryResponse = null;
try {
Thread.sleep(theUpnpSendDelay);
} catch (InterruptedException e) {
// noop
}
discoveryResponse = String.format(responseTemplate1, Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT, responseAddress, httpServerPort, bridgeId, bridgeSNUUID);
if(traceupnp) {
log.info("Traceupnp: send upnp discovery template 1 with response address: " + responseAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);
@@ -238,6 +245,11 @@ public class UpnpListener {
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort + " 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, responseAddress, httpServerPort, bridgeId, bridgeSNUUID, bridgeSNUUID);
if(traceupnp) {
log.info("Traceupnp: send upnp discovery template 2 with response address: " + responseAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);
@@ -246,6 +258,11 @@ public class UpnpListener {
log.debug("sendUpnpResponse to address: " + requester + ":" + sourcePort + " discovery responseTemplate2 is <<<" + discoveryResponse + ">>>");
sendUDPResponse(discoveryResponse.getBytes(), requester, sourcePort);
try {
Thread.sleep(theUpnpSendDelay);
} catch (InterruptedException e) {
// noop
}
discoveryResponse = String.format(responseTemplate3, Configuration.UPNP_MULTICAST_ADDRESS, Configuration.UPNP_DISCOVERY_PORT, responseAddress, httpServerPort, bridgeId, bridgeSNUUID);
if(traceupnp) {
log.info("Traceupnp: send upnp discovery template 3 with response address: " + responseAddress + ":" + httpServerPort + " to address: " + requester + ":" + sourcePort);

View File

@@ -35,15 +35,6 @@ public class UpnpSettingsResource {
+ "<modelURL>http://www.meethue.com</modelURL>\n"
+ "<serialNumber>%s</serialNumber>\n"
+ "<UDN>uuid:" + HueConstants.UUID_PREFIX + "%s</UDN>\n"
+ "<serviceList>\n"
+ "<service>\n"
+ "<serviceType>(null)</serviceType>\n"
+ "<serviceId>(null)</serviceId>\n"
+ "<controlURL>(null)</controlURL>\n"
+ "<eventSubURL>(null)</eventSubURL>\n"
+ "<SCPDURL>(null)</SCPDURL>\n"
+ "</service>\n"
+ "</serviceList>\n"
+ "<presentationURL>index.html</presentationURL>\n"
+ "<iconList>\n"
+ "<icon>\n"