From 07765061bd56b00e831c855eeab339df33f9e384 Mon Sep 17 00:00:00 2001 From: Mark Milligan Date: Sun, 6 Nov 2022 18:21:54 -0600 Subject: [PATCH] Add config flag for hubs to post to self-hosted server with a self-signed SSL certificate. Support Raspberry Pi 2B by handling missing BLE controller and supporting 32-bit pigpio. --- currentmonitor/lantern-currentmonitor/pom.xml | 2 +- .../currentmonitor/BluetoothConfig.java | 29 +++-- .../currentmonitor/MonitorApp.java | 2 +- .../currentmonitor/MonitorConfig.java | 9 ++ .../dao/MonitorConfigSerializer.java | 2 + .../MongoCurrentMonitorDao.java | 2 +- .../currentmonitor/HubConfigService.java | 4 +- .../resources/lib/armhf/lantern-pigpio.so | Bin 0 -> 8324 bytes .../lanternsoftware/util/http/HttpPool.java | 116 ++++++++++++++++-- .../util/http/HttpResponsePayload.java | 29 +++++ .../zwave/context/ZWaveApp.java | 19 ++- 11 files changed, 176 insertions(+), 38 deletions(-) create mode 100644 pigpio/lantern-pigpio/src/main/resources/lib/armhf/lantern-pigpio.so create mode 100644 util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpResponsePayload.java diff --git a/currentmonitor/lantern-currentmonitor/pom.xml b/currentmonitor/lantern-currentmonitor/pom.xml index 360449b..06b2cb3 100644 --- a/currentmonitor/lantern-currentmonitor/pom.xml +++ b/currentmonitor/lantern-currentmonitor/pom.xml @@ -2,7 +2,7 @@ 4.0.0 lantern-currentmonitor jar - 1.1.1 + 1.1.2 lantern-currentmonitor diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java index 9290598..e0fa9ec 100644 --- a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/BluetoothConfig.java @@ -7,27 +7,38 @@ import com.lanternsoftware.currentmonitor.bluetooth.BleHelper; import com.lanternsoftware.currentmonitor.bluetooth.BleService; import com.lanternsoftware.datamodel.currentmonitor.HubConfigService; import com.lanternsoftware.util.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; public class BluetoothConfig { + private static final Logger LOG = LoggerFactory.getLogger(BluetoothConfig.class); private final BleApplication app; public BluetoothConfig(String _hubName, BleCharacteristicListener _listener) { - BleHelper.getAdapter().setPowered(true); - BleHelper.requestBusName("com.lanternsoftware"); - BleHelper.setBasePath("/com/lanternsoftware"); - HubConfigService service = new HubConfigService(); - List chars = CollectionUtils.transform(service.getCharacteristics(), _c->new BleCharacteristic("HubConfig", _c.getUUID(), _c.name(), _c.getFlags())); - chars.forEach(_c->_c.setListener(_listener)); - app = new BleApplication("Lantern", _hubName, new BleService("HubConfig", service.getServiceUUID(), chars)); + BleApplication a = null; + try { + BleHelper.getAdapter().setPowered(true); + BleHelper.requestBusName("com.lanternsoftware"); + BleHelper.setBasePath("/com/lanternsoftware"); + List chars = CollectionUtils.transform(HubConfigService.getCharacteristics(), _c -> new BleCharacteristic("HubConfig", _c.getUUID(), _c.name(), _c.getFlags())); + chars.forEach(_c -> _c.setListener(_listener)); + a = new BleApplication("Lantern", _hubName, new BleService("HubConfig", HubConfigService.getServiceUUID(), chars)); + } + catch (Throwable _t) { + LOG.error("Failed to initialize BLE service", _t); + } + app = a; } public void start() { - app.start(); + if (app != null) + app.start(); } public void stop() { - app.stop(); + if (app != null) + app.stop(); } } diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java index 5d73f14..2150866 100644 --- a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorApp.java @@ -219,7 +219,7 @@ public class MonitorApp { config = new MonitorConfig(); ResourceLoader.writeFile(WORKING_DIR + "config.json", DaoSerializer.toJson(config)); } - pool = new HttpPool(10, 10, config.getSocketTimeout(), config.getConnectTimeout(), config.getSocketTimeout()); + pool = HttpPool.builder().withValidateSSLCertificates(!config.isAcceptSelfSignedCertificates()).build(); if (NullUtils.isNotEmpty(config.getHost())) host = NullUtils.terminateWith(config.getHost(), "/"); monitor.setDebug(config.isDebug()); diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java index dd971fd..6ef3911 100644 --- a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/MonitorConfig.java @@ -19,6 +19,7 @@ public class MonitorConfig { private int socketTimeout; private boolean postSamples = false; private boolean needsCalibration = true; + private boolean acceptSelfSignedCertificates = false; private String mqttBrokerUrl; private String mqttUserName; private String mqttPassword; @@ -115,6 +116,14 @@ public class MonitorConfig { needsCalibration = _needsCalibration; } + public boolean isAcceptSelfSignedCertificates() { + return acceptSelfSignedCertificates; + } + + public void setAcceptSelfSignedCertificates(boolean _acceptSelfSignedCertificates) { + acceptSelfSignedCertificates = _acceptSelfSignedCertificates; + } + public String getMqttBrokerUrl() { return mqttBrokerUrl; } diff --git a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java index a4e3e2b..374b2ac 100644 --- a/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java +++ b/currentmonitor/lantern-currentmonitor/src/main/java/com/lanternsoftware/currentmonitor/dao/MonitorConfigSerializer.java @@ -36,6 +36,7 @@ public class MonitorConfigSerializer extends AbstractDaoSerializer getCharacteristics() { + public static List getCharacteristics() { return Arrays.asList(HubConfigCharacteristic.values()); } diff --git a/pigpio/lantern-pigpio/src/main/resources/lib/armhf/lantern-pigpio.so b/pigpio/lantern-pigpio/src/main/resources/lib/armhf/lantern-pigpio.so new file mode 100644 index 0000000000000000000000000000000000000000..9561f72044faefb11c80c2986873644ee9dcef41 GIT binary patch literal 8324 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 0; - connectionManager = new PoolingHttpClientConnectionManager(); + Registry registry = null; + if ((_keystore != null) || !_validateSSLCertificates) { + try { + SSLContextBuilder contextBuilder = SSLContexts.custom(); + if (_keystore != null) + contextBuilder.loadKeyMaterial(_keystore, _keystorePassword.toCharArray()); + if (!_validateSSLCertificates) + contextBuilder.loadTrustMaterial(null, (x509CertChain, authType) -> true); + SSLConnectionSocketFactory socketFactory = _validateSSLCertificates ? new SSLConnectionSocketFactory(contextBuilder.build()) : new SSLConnectionSocketFactory(contextBuilder.build(), NoopHostnameVerifier.INSTANCE); + registry = RegistryBuilder.create().register("https", socketFactory).register("http", new PlainConnectionSocketFactory()).build(); + } catch (Exception _e) { + LOG.error("Failed to load SSL keystore", _e); + } + } + connectionManager = registry != null ? new PoolingHttpClientConnectionManager(registry) : new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(_maxTotalConnections); connectionManager.setDefaultMaxPerRoute(_maxPerRoute); } + public static Builder builder() { + return new Builder(); + } + public void shutdown() { connectionManager.shutdown(); } @@ -76,26 +111,28 @@ public class HttpPool { } public byte[] executeToByteArray(HttpUriRequest _request) { + return executeToPayload(_request).getPayload(); + } + + public HttpResponsePayload executeToPayload(HttpUriRequest _request) { InputStream is = null; CloseableHttpResponse resp = null; try { resp = execute(_request); if (resp == null) - return null; - if ((resp.getStatusLine().getStatusCode() < 200) || (resp.getStatusLine().getStatusCode() >= 300)) { + return new HttpResponsePayload(HttpStatus.SC_INTERNAL_SERVER_ERROR, null); + if ((resp.getStatusLine().getStatusCode() < 200) || (resp.getStatusLine().getStatusCode() >= 300)) LOG.error("Failed to make http request to " + _request.getURI().toString() + ". Status code: " + resp.getStatusLine().getStatusCode()); - return null; - } HttpEntity entity = resp.getEntity(); if (entity != null) { is = entity.getContent(); - return IOUtils.toByteArray(is); + return new HttpResponsePayload(resp.getStatusLine().getStatusCode(), IOUtils.toByteArray(is)); } - return null; + return new HttpResponsePayload(resp.getStatusLine().getStatusCode(), null); } catch (Exception _e) { LOG.error("Failed to make http request to " + _request.getURI().toString(), _e); - return null; + return new HttpResponsePayload(HttpStatus.SC_INTERNAL_SERVER_ERROR, null); } finally { IOUtils.closeQuietly(is); @@ -106,4 +143,57 @@ public class HttpPool { public static void addBasicAuthHeader(HttpUriRequest _request, String _username, String _password) { _request.addHeader("Authorization", "Basic " + Base64.encodeBase64String(NullUtils.toByteArray(_username + ":" + _password))); } + + public static final class Builder { + private int maxTotalConnections = 10; + private int maxPerRoute = 10; + private int socketTimeoutMs = 10000; + private int connectTimeoutMs = 5000; + private int connectionRequestTimeoutMs = 10000; + private KeyStore keystore; + private String keystorePassword; + private boolean validateSSLCertificates = true; + + private Builder() { + } + + public Builder withMaxTotalConnections(int val) { + maxTotalConnections = val; + return this; + } + + public Builder withMaxPerRoute(int val) { + maxPerRoute = val; + return this; + } + + public Builder withSocketTimeoutMs(int val) { + socketTimeoutMs = val; + return this; + } + + public Builder withConnectTimeoutMs(int val) { + connectTimeoutMs = val; + return this; + } + + public Builder withConnectionRequestTimeoutMs(int val) { + connectionRequestTimeoutMs = val; + return this; + } + + public Builder withKeystore(KeyStore _keystore, String _password) { + keystore = _keystore; + keystorePassword = _password; + return this; + } + + public Builder withValidateSSLCertificates(boolean val) { + validateSSLCertificates = val; + return this; + } + public HttpPool build() { + return new HttpPool(maxTotalConnections, maxPerRoute, socketTimeoutMs, connectTimeoutMs, connectionRequestTimeoutMs, keystore, keystorePassword, validateSSLCertificates); + } + } } diff --git a/util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpResponsePayload.java b/util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpResponsePayload.java new file mode 100644 index 0000000..062ab9d --- /dev/null +++ b/util/lantern-util-http/src/main/java/com/lanternsoftware/util/http/HttpResponsePayload.java @@ -0,0 +1,29 @@ +package com.lanternsoftware.util.http; + +import com.lanternsoftware.util.NullUtils; + +public class HttpResponsePayload { + private final int status; + private final byte[] payload; + + public HttpResponsePayload(int _status, byte[] _payload) { + status = _status; + payload = _payload; + } + + public int getStatus() { + return status; + } + + public byte[] getPayload() { + return payload; + } + + public boolean isSuccess() { + return (status >= 200) && (status < 300); + } + + public String asString() { + return NullUtils.toString(payload); + } +} diff --git a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java index 7a28df3..a9255c7 100644 --- a/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java +++ b/zwave/lantern-service-zwave/src/main/java/com/lanternsoftware/zwave/context/ZWaveApp.java @@ -76,7 +76,6 @@ public class ZWaveApp { private HttpPool pool; private SwitchScheduleTask nextScheduleTask; private final Map sensors = new HashMap<>(); - private final Object ZWAVE_MUTEX = new Object(); private ExecutorService executor = null; public void start() { @@ -559,17 +558,15 @@ public class ZWaveApp { if (_sw.isSourceUrlValid()) return DaoSerializer.getDouble(DaoSerializer.parse(pool.executeToString(new HttpGet(_sw.getSourceUrl()))), "temp"); else if (_sw.isZWaveThermostat() && config.isMySwitch(_sw)) { - synchronized (ZWAVE_MUTEX) { - synchronized (sensors) { - controller.send(new MultilevelSensorGetRequest((byte) _sw.getNodeId())); - try { - sensors.wait(3000); - } catch (InterruptedException _e) { - _e.printStackTrace(); - } - Double temp = sensors.get(_sw.getNodeId()); - return (temp == null) ? 0.0 : temp; + synchronized (sensors) { + controller.send(new MultilevelSensorGetRequest((byte) _sw.getNodeId())); + try { + sensors.wait(3000); + } catch (InterruptedException _e) { + _e.printStackTrace(); } + Double temp = sensors.get(_sw.getNodeId()); + return (temp == null) ? 0.0 : temp; } } return 0.0;