The `controller` Thing has the following configuration parameters:
-| Parameter | Description | Required | Default value |
-|-----------------------------|------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
-| hostname | Network/IP address of the IHC / ELKO controller without https prefix, but can contain TCP port if default port is not used. | yes | |
-| username | User name to login to the IHC / ELKO controller. | yes | |
-| password | Password to login to the IHC / ELKO controller. | yes | |
-| timeout | Timeout in milliseconds to communicate to IHC / ELKO controller. | no | 5000 |
-| loadProjectFile | Load project file from controller. | no | true |
-| createChannelsAutomatically | Create channels automatically from project file. Project file loading parameter should be enabled as well. | no | true |
+| Parameter | Description | Required | Default value |
+|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------|
+| hostname | Network/IP address of the IHC / ELKO controller without https prefix, but can contain TCP port if default port is not used. | yes | |
+| username | User name to login to the IHC / ELKO controller. | yes | |
+| password | Password to login to the IHC / ELKO controller. | yes | |
+| timeout | Timeout in milliseconds to communicate to IHC / ELKO controller. | no | 5000 |
+| loadProjectFile | Load project file from controller. | no | true |
+| createChannelsAutomatically | Create channels automatically from project file. Project file loading parameter should be enabled as well. | no | true |
+| tlsVersion | TLS version used for controller communication. Choose `TLSv1` for older firmware versions and `TLSv1.2` for never versions (since fall 2021). `AUTO` mode try to recognize correct version. | no | TLSv1 |
## Channels
### example.things
```xtend
-ihc:controller:elko [ hostname="192.168.1.2", username="openhab", password="secret", timeout=5000, loadProjectFile=true, createChannelsAutomatically=false ] {
+ihc:controller:elko [ hostname="192.168.1.2", username="openhab", password="secret", timeout=5000, loadProjectFile=true, createChannelsAutomatically=false, tlsVersion="TLSv1" ] {
Channels:
Type switch : my_test_switch "My Test Switch" [ resourceId=3988827 ]
Type contact : my_test_contact "My Test Contact" [ resourceId=3988827 ]
public int timeout;
public boolean loadProjectFile;
public boolean createChannelsAutomatically;
+ public String tlsVersion;
@Override
public String toString() {
return "[" + "hostname=" + hostname + ", username=" + username + ", password=******" + ", timeout=" + timeout
+ ", loadProjectFile=" + loadProjectFile + ", createChannelsAutomatically="
- + createChannelsAutomatically + "]";
+ + createChannelsAutomatically + ", tlsVersion=" + tlsVersion + "]";
}
}
import org.openhab.binding.ihc.internal.ws.datatypes.WSTimeManagerSettings;
import org.openhab.binding.ihc.internal.ws.exeptions.ConversionException;
import org.openhab.binding.ihc.internal.ws.exeptions.IhcExecption;
+import org.openhab.binding.ihc.internal.ws.exeptions.IhcFatalExecption;
import org.openhab.binding.ihc.internal.ws.projectfile.IhcEnumValue;
import org.openhab.binding.ihc.internal.ws.projectfile.ProjectFileUtils;
import org.openhab.binding.ihc.internal.ws.resourcevalues.WSBooleanValue;
setConnectingState(true);
logger.debug("Connecting to IHC / ELKO LS controller [hostname='{}', username='{}'].", conf.hostname,
conf.username);
- ihc = new IhcClient(conf.hostname, conf.username, conf.password, conf.timeout);
+ ihc = new IhcClient(conf.hostname, conf.username, conf.password, conf.timeout, conf.tlsVersion);
ihc.openConnection();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
"Initializing communication to the IHC / ELKO controller");
}
connect();
setReconnectRequest(false);
+ } catch (IhcFatalExecption e) {
+ logger.warn("Can't open connection to controller {}", e.getMessage());
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ setReconnectRequest(false);
+ return;
} catch (IhcExecption e) {
logger.debug("Can't open connection to controller {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
import org.openhab.binding.ihc.internal.ws.datatypes.WSSystemInfo;
import org.openhab.binding.ihc.internal.ws.datatypes.WSTimeManagerSettings;
import org.openhab.binding.ihc.internal.ws.exeptions.IhcExecption;
+import org.openhab.binding.ihc.internal.ws.exeptions.IhcFatalExecption;
+import org.openhab.binding.ihc.internal.ws.exeptions.IhcTlsExecption;
import org.openhab.binding.ihc.internal.ws.http.IhcConnectionPool;
import org.openhab.binding.ihc.internal.ws.resourcevalues.WSResourceValue;
import org.openhab.binding.ihc.internal.ws.services.IhcAirlinkManagementService;
CONNECTED
}
+ public static final String TLS_VER_AUTO = "AUTO";
+ public static final String TLS_VER_V1 = "TLSv1";
+ public static final String TLS_VER_V1_2 = "TLSv1.2";
+
public static final String CONTROLLER_STATE_READY = "text.ctrl.state.ready";
public static final String CONTROLLER_STATE_INITIALIZE = "text.ctrl.state.initialize";
private String username;
private String password;
private String host;
+ private String tlsVersion;
/** Timeout in milliseconds */
private int timeout;
private List<IhcEventListener> eventListeners = new ArrayList<>();
public IhcClient(String host, String username, String password) {
- this(host, username, password, 5000);
+ this(host, username, password, 5000, TLS_VER_V1);
}
- public IhcClient(String host, String username, String password, int timeout) {
+ public IhcClient(String host, String username, String password, int timeout, String tlsVersion) {
this.host = host;
this.username = username;
this.password = password;
this.timeout = timeout;
+ this.tlsVersion = tlsVersion;
}
public synchronized ConnectionState getConnectionState() {
* @throws IhcExecption
*/
public void openConnection() throws IhcExecption {
- logger.debug("Opening connection");
+ if (TLS_VER_AUTO.equalsIgnoreCase(tlsVersion)) {
+ try {
+ openConnection(TLS_VER_V1);
+ } catch (IhcTlsExecption e) {
+ logger.debug("Connection failed with TLS {}, trying with TLS {}", TLS_VER_V1, TLS_VER_V1_2);
+ openConnection(TLS_VER_V1_2);
+ }
+ } else {
+ openConnection(tlsVersion);
+ }
+ }
+
+ private void openConnection(String tlsVersion) throws IhcExecption {
+ logger.debug("Opening connection with TLS version {}", tlsVersion);
setConnectionState(ConnectionState.CONNECTING);
- ihcConnectionPool = new IhcConnectionPool();
+ ihcConnectionPool = new IhcConnectionPool(tlsVersion);
authenticationService = new IhcAuthenticationService(host, timeout, ihcConnectionPool);
WSLoginResult loginResult = authenticationService.authenticate(username, password, "treeview");
setConnectionState(ConnectionState.DISCONNECTED);
if (loginResult.isLoginFailedDueToAccountInvalid()) {
- throw new IhcExecption("login failed because of invalid account");
+ throw new IhcFatalExecption("login failed because of invalid account");
}
if (loginResult.isLoginFailedDueToConnectionRestrictions()) {
- throw new IhcExecption("login failed because of connection restrictions");
+ throw new IhcFatalExecption("login failed because of connection restrictions");
}
if (loginResult.isLoginFailedDueToInsufficientUserRights()) {
- throw new IhcExecption("login failed because of insufficient user rights");
+ throw new IhcFatalExecption("login failed because of insufficient user rights");
}
- throw new IhcExecption("login failed because of unknown reason");
+ throw new IhcFatalExecption("login failed because of unknown reason");
}
logger.debug("Connection successfully opened");
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ihc.internal.ws.exeptions;
+
+/**
+ * Exception for handling fatal communication errors to controller.
+ *
+ * @author Pauli Anttila - Initial contribution
+ */
+public class IhcFatalExecption extends IhcExecption {
+
+ private static final long serialVersionUID = -8107948791816894352L;
+
+ public IhcFatalExecption() {
+ }
+
+ public IhcFatalExecption(String message) {
+ super(message);
+ }
+
+ public IhcFatalExecption(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public IhcFatalExecption(Throwable cause) {
+ super(cause);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.ihc.internal.ws.exeptions;
+
+/**
+ * Exception for handling TLS communication errors to controller.
+ *
+ * @author Pauli Anttila - Initial contribution
+ */
+public class IhcTlsExecption extends IhcFatalExecption {
+
+ private static final long serialVersionUID = -1366186910684967044L;
+
+ public IhcTlsExecption() {
+ }
+
+ public IhcTlsExecption(String message) {
+ super(message);
+ }
+
+ public IhcTlsExecption(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public IhcTlsExecption(Throwable cause) {
+ super(cause);
+ }
+}
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.openhab.binding.ihc.internal.ws.exeptions.IhcFatalExecption;
+import org.openhab.binding.ihc.internal.ws.exeptions.IhcTlsExecption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IhcConnectionPool {
private final Logger logger = LoggerFactory.getLogger(IhcConnectionPool.class);
+ private static final String DEFAULT_TLS_VER = "TLSv1";
/**
* Controller TLS certificate is self signed, which means that certificate
private HttpClientBuilder httpClientBuilder;
private HttpClientContext localContext;
+ private String tlsVersion = DEFAULT_TLS_VER;
- public IhcConnectionPool() {
+ public IhcConnectionPool() throws IhcFatalExecption {
+ this(DEFAULT_TLS_VER);
+ }
+
+ public IhcConnectionPool(String tlsVersion) throws IhcFatalExecption {
+ if (!tlsVersion.isEmpty()) {
+ this.tlsVersion = tlsVersion;
+ }
init();
}
- private void init() {
- // Create a local instance of cookie store
- cookieStore = new BasicCookieStore();
+ private void init() throws IhcFatalExecption {
+ try {
- // Create local HTTP context
- localContext = HttpClientContext.create();
+ // Create a local instance of cookie store
+ cookieStore = new BasicCookieStore();
- // Bind custom cookie store to the local context
- localContext.setCookieStore(cookieStore);
+ // Create local HTTP context
+ localContext = HttpClientContext.create();
- httpClientBuilder = HttpClientBuilder.create();
+ // Bind custom cookie store to the local context
+ localContext.setCookieStore(cookieStore);
- // Setup a Trust Strategy that allows all certificates.
+ httpClientBuilder = HttpClientBuilder.create();
- logger.debug("Initialize SSL context");
+ // Setup a Trust Strategy that allows all certificates.
- // Create a trust manager that does not validate certificate chains, but accept all.
- TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
+ logger.debug("Initialize SSL context");
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return null;
- }
+ // Create a trust manager that does not validate certificate chains, but accept all.
+ TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
- @Override
- public void checkClientTrusted(X509Certificate[] certs, String authType) {
- }
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
- @Override
- public void checkServerTrusted(X509Certificate[] certs, String authType) {
- logger.trace("Trusting server cert: {}", certs[0].getIssuerDN());
- }
- } };
+ @Override
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
- // Install the all-trusting trust manager
+ @Override
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ logger.trace("Trusting server cert: {}", certs[0].getIssuerDN());
+ }
+ } };
- try {
- // Controller supports only SSLv3 and TLSv1
- sslContext = SSLContext.getInstance("TLSv1");
+ // Install the all-trusting trust manager
+
+ // Old controller FW supports only SSLv3 and TLSv1, never controller TLSv1.2
+ sslContext = SSLContext.getInstance(tlsVersion);
+ logger.debug("Using TLS version {}", sslContext.getProtocol());
sslContext.init(null, trustAllCerts, new SecureRandom());
- } catch (NoSuchAlgorithmException e) {
- logger.warn("Exception", e);
- } catch (KeyManagementException e) {
- logger.warn("Exception", e);
- }
- // Controller accepts only HTTPS connections and because normally IP address are used on home network rather
- // than DNS names, create custom host name verifier.
- HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+ // Controller accepts only HTTPS connections and because normally IP address are used on home network rather
+ // than DNS names, create custom host name verifier.
+ HostnameVerifier hostnameVerifier = new HostnameVerifier() {
- @Override
- public boolean verify(String arg0, SSLSession arg1) {
- logger.trace("HostnameVerifier: arg0 = {}, arg1 = {}", arg0, arg1);
- return true;
- }
- };
+ @Override
+ public boolean verify(String arg0, SSLSession arg1) {
+ logger.trace("HostnameVerifier: arg0 = {}, arg1 = {}", arg0, arg1);
+ return true;
+ }
+ };
- // Create an SSL Socket Factory, to use our weakened "trust strategy"
- SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext,
- new String[] { "TLSv1" }, null, hostnameVerifier);
+ // Create an SSL Socket Factory, to use our weakened "trust strategy"
+ SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext,
+ new String[] { tlsVersion }, null, hostnameVerifier);
- Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
- .register("https", sslSocketFactory).build();
+ Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create()
+ .register("https", sslSocketFactory).build();
- // Create connection-manager using our Registry. Allows multi-threaded use
- PoolingHttpClientConnectionManager connMngr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+ // Create connection-manager using our Registry. Allows multi-threaded use
+ PoolingHttpClientConnectionManager connMngr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
- // Increase max connection counts
- connMngr.setMaxTotal(20);
- connMngr.setDefaultMaxPerRoute(6);
+ // Increase max connection counts
+ connMngr.setMaxTotal(20);
+ connMngr.setDefaultMaxPerRoute(6);
- httpClientBuilder.setConnectionManager(connMngr);
+ httpClientBuilder.setConnectionManager(connMngr);
+ } catch (KeyManagementException e) {
+ throw new IhcFatalExecption(e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IhcTlsExecption(e);
+ }
}
public HttpClient getHttpClient() {
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.net.ssl.SSLHandshakeException;
+
import org.apache.http.HttpResponse;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.openhab.binding.ihc.internal.ws.exeptions.IhcExecption;
+import org.openhab.binding.ihc.internal.ws.exeptions.IhcTlsExecption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
setRequestProperty(requestProperties);
try {
return sendQ(query, timeout);
+ } catch (SSLHandshakeException e) {
+ throw new IhcTlsExecption(e);
} catch (NoHttpResponseException | SocketTimeoutException e) {
try {
logger.debug("No response received, resend query");
well.</description>
<default>true</default>
</parameter>
+ <parameter name="tlsVersion" type="text" required="false">
+ <label>TLS version</label>
+ <description>TLS version used for controller communication. Choose TLSv1 for older firmware versions and TLSv1.2 for
+ never versions (since fall 2021). Auto mode try to recognize correct version.</description>
+ <default>TLSv1</default>
+ <options>
+ <option value="TLSv1">TLSv1</option>
+ <option value="TLSv1.2">TLSv1.2</option>
+ <option value="AUTO">Auto</option>
+ </options>
+ </parameter>
</config-description>
</thing-type>