mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[neohub] Add support for WebSocket connection to hub (#12915)
* [neohub] add support for secure web socket connection * [neohub] clean code * [neohub] synchronize api calls * [neohub] rename classes, fix compiler errors, remove SuppressWarnings Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
This commit is contained in:
parent
8d3828a9a4
commit
f60e324229
@ -33,21 +33,31 @@ It signs on to the hub using the supplied connection parameters, and it polls th
|
|||||||
The NeoHub supports two Application Programming Interfaces "API" (an older "legacy" one, and a modern one), and this binding can use either of them to communicate with it.
|
The NeoHub supports two Application Programming Interfaces "API" (an older "legacy" one, and a modern one), and this binding can use either of them to communicate with it.
|
||||||
Before the binding can communicate with the hub, the following Configuration Parameters must be entered.
|
Before the binding can communicate with the hub, the following Configuration Parameters must be entered.
|
||||||
|
|
||||||
| Configuration Parameter | Description |
|
| Configuration Parameter | Description |
|
||||||
|-------------------------|---------------------------------------------------------------------------------------------|
|
|----------------------------|----------------------------------------------------------------------------------------------------------|
|
||||||
| hostName | Host name (IP address) of the NeoHub (example 192.168.1.123) |
|
| hostName | Host name (IP address) of the NeoHub (example 192.168.1.123) |
|
||||||
| portNumber | Port number of the NeoHub (Default=4242) |
|
| useWebSocket<sup>1)</sup> | Use secure WebSocket to connect to the NeoHub (example `true`) |
|
||||||
| pollingInterval | Time (seconds) between polling requests to the NeoHub (Min=4, Max=60, Default=60) |
|
| apiToken<sup>1)</sup> | API Access Token for secure connection to hub. Create the token in the Heatmiser mobile App |
|
||||||
| socketTimeout | Time (seconds) to allow for TCP socket connections to the hub to succeed (Min=4, Max=20, Default=5) |
|
| pollingInterval | Time (seconds) between polling requests to the NeoHub (Min=4, Max=60, Default=60) |
|
||||||
| preferLegacyApi | ADVANCED: Prefer the binding to use older API calls; if these are not supported, it switches to the new calls (Default=false) |
|
| socketTimeout | Time (seconds) to allow for TCP socket connections to the hub to succeed (Min=4, Max=20, Default=5) |
|
||||||
|
| preferLegacyApi | ADVANCED: Prefer to use older API calls; but if not supported, it switches to new calls (Default=false) |
|
||||||
|
| portNumber<sup>2)</sup> | ADVANCED: Port number for connection to the NeoHub (Default=0 (automatic)) |
|
||||||
|
|
||||||
|
<sup>1)</sup> If `useWebSocket` is false, the binding will connect via an older and less secure TCP connection, in which case `apiToken` is not required.
|
||||||
|
However see the chapter "Connection Refused Errors" below.
|
||||||
|
Whereas if you prefer to connect via more secure WebSocket connections then an API access token `apiToken` is required.
|
||||||
|
You can create an API access token in the Heatmiser mobile App (Settings | System | API Access).
|
||||||
|
|
||||||
|
<sup>2)</sup> Normally the port number is chosen automatically (for TCP it is 4242 and for WebSocket it is 4243).
|
||||||
|
But you can override this in special cases if you want to use (say) port forwarding.
|
||||||
|
|
||||||
## Connection Refused Errors
|
## Connection Refused Errors
|
||||||
|
|
||||||
From early 2022 Heatmiser introduced NeoHub firmware that has the ability to enable / disable the NeoHub `portNumber` 4242.
|
From early 2022 Heatmiser introduced NeoHub firmware that has the ability to enable / disable connecting to it via a TCP port.
|
||||||
If this port is disabled the OpenHAB binding cannot connect and the binding will report a *"Connection Refused"* warning in the log.
|
If the TCP port is disabled the OpenHAB binding cannot connect and the binding will report a *"Connection Refused"* warning in the log.
|
||||||
In prior firmware versions the port was always enabled.
|
In prior firmware versions the TCP port was always enabled.
|
||||||
But in the new firmware the port is initially enabled on power up but if no communication occurs for 48 hours it is automatically disabled.
|
But in the new firmware the TCP port is initially enabled on power up but if no communication occurs for 48 hours it is automatically disabled.
|
||||||
Alternatively the Heatmiser mobile App has a setting (Settings | System | API Access | Legacy API Enable | On) whereby the port can be permanently enabled.
|
Alternatively the Heatmiser mobile app has a setting (Settings | System | API Access | Legacy API Enable | On) whereby the TCP port can be permanently enabled.
|
||||||
|
|
||||||
## Thing Configuration for "NeoStat" and "NeoPlug"
|
## Thing Configuration for "NeoStat" and "NeoPlug"
|
||||||
|
|
||||||
|
@ -196,4 +196,16 @@ public class NeoHubBindingConstants {
|
|||||||
public static final String PROPERTY_FIRMWARE_VERSION = "Firmware version";
|
public static final String PROPERTY_FIRMWARE_VERSION = "Firmware version";
|
||||||
public static final String PROPERTY_API_VERSION = "API version";
|
public static final String PROPERTY_API_VERSION = "API version";
|
||||||
public static final String PROPERTY_API_DEVICEINFO = "Devices [online/total]";
|
public static final String PROPERTY_API_DEVICEINFO = "Devices [online/total]";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reserved ports on the hub
|
||||||
|
*/
|
||||||
|
public static final int PORT_TCP = 4242;
|
||||||
|
public static final int PORT_WSS = 4243;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* web socket communication constants
|
||||||
|
*/
|
||||||
|
public static final String HM_GET_COMMAND_QUEUE = "hm_get_command_queue";
|
||||||
|
public static final String HM_SET_COMMAND_RESPONSE = "hm_set_command_response";
|
||||||
}
|
}
|
||||||
|
@ -30,4 +30,6 @@ public class NeoHubConfiguration {
|
|||||||
public int pollingInterval;
|
public int pollingInterval;
|
||||||
public int socketTimeout;
|
public int socketTimeout;
|
||||||
public boolean preferLegacyApi;
|
public boolean preferLegacyApi;
|
||||||
|
public String apiToken = "";
|
||||||
|
public boolean useWebSocket;
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
* Discovery service for neo devices
|
* Discovery service for neo devices
|
||||||
*
|
*
|
||||||
* @author Andrew Fiddian-Green - Initial contribution
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class NeoHubDiscoveryService extends AbstractDiscoveryService {
|
public class NeoHubDiscoveryService extends AbstractDiscoveryService {
|
||||||
@ -113,11 +113,11 @@ public class NeoHubDiscoveryService extends AbstractDiscoveryService {
|
|||||||
// the record came from the legacy API (deviceType included)
|
// the record came from the legacy API (deviceType included)
|
||||||
if (deviceRecord instanceof InfoRecord) {
|
if (deviceRecord instanceof InfoRecord) {
|
||||||
deviceType = ((InfoRecord) deviceRecord).getDeviceType();
|
deviceType = ((InfoRecord) deviceRecord).getDeviceType();
|
||||||
publishDevice((InfoRecord) deviceRecord, deviceType);
|
publishDevice(deviceRecord, deviceType);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the record came from the now API (deviceType NOT included)
|
// the record came from the new API (deviceType NOT included)
|
||||||
if (deviceRecord instanceof LiveDataRecord) {
|
if (deviceRecord instanceof LiveDataRecord) {
|
||||||
if (engineerData == null) {
|
if (engineerData == null) {
|
||||||
break;
|
break;
|
||||||
@ -128,7 +128,7 @@ public class NeoHubDiscoveryService extends AbstractDiscoveryService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
deviceType = engineerData.getDeviceType(deviceName);
|
deviceType = engineerData.getDeviceType(deviceName);
|
||||||
publishDevice((LiveDataRecord) deviceRecord, deviceType);
|
publishDevice(deviceRecord, deviceType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
* The {@link NeoHubException} is a custom exception for NeoHub
|
* The {@link NeoHubException} is a custom exception for NeoHub
|
||||||
*
|
*
|
||||||
* @author Andrew Fiddian-Green - Initial contribution
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class NeoHubException extends Exception {
|
public class NeoHubException extends Exception {
|
||||||
@ -28,4 +28,8 @@ public class NeoHubException extends Exception {
|
|||||||
public NeoHubException(String message) {
|
public NeoHubException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NeoHubException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
private final Map<String, Boolean> connectionStates = new HashMap<>();
|
private final Map<String, Boolean> connectionStates = new HashMap<>();
|
||||||
|
|
||||||
private @Nullable NeoHubConfiguration config;
|
private @Nullable NeoHubConfiguration config;
|
||||||
private @Nullable NeoHubSocket socket;
|
private @Nullable NeoHubSocketBase socket;
|
||||||
private @Nullable ScheduledFuture<?> lazyPollingScheduler;
|
private @Nullable ScheduledFuture<?> lazyPollingScheduler;
|
||||||
private @Nullable ScheduledFuture<?> fastPollingScheduler;
|
private @Nullable ScheduledFuture<?> fastPollingScheduler;
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
logger.debug("hub '{}' port={}", getThing().getUID(), config.portNumber);
|
logger.debug("hub '{}' port={}", getThing().getUID(), config.portNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.portNumber <= 0 || config.portNumber > 0xFFFF) {
|
if (config.portNumber < 0 || config.portNumber > 0xFFFF) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "portNumber is invalid!");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "portNumber is invalid!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -142,7 +142,20 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
logger.debug("hub '{}' preferLegacyApi={}", getThing().getUID(), config.preferLegacyApi);
|
logger.debug("hub '{}' preferLegacyApi={}", getThing().getUID(), config.preferLegacyApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
NeoHubSocket socket = this.socket = new NeoHubSocket(config.hostName, config.portNumber, config.socketTimeout);
|
// create a web or TCP socket based on the port number in the configuration
|
||||||
|
NeoHubSocketBase socket;
|
||||||
|
try {
|
||||||
|
if (config.useWebSocket) {
|
||||||
|
socket = new NeoHubWebSocket(config);
|
||||||
|
} else {
|
||||||
|
socket = new NeoHubSocket(config);
|
||||||
|
}
|
||||||
|
} catch (NeoHubException e) {
|
||||||
|
logger.debug("\"hub '{}' error creating web/tcp socket: '{}'", getThing().getUID(), e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.socket = socket;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -206,6 +219,15 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
fast.cancel(true);
|
fast.cancel(true);
|
||||||
this.fastPollingScheduler = null;
|
this.fastPollingScheduler = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NeoHubSocketBase socket = this.socket;
|
||||||
|
if (socket != null) {
|
||||||
|
try {
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
this.socket = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -220,7 +242,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
* device handlers call this method to issue commands to the NeoHub
|
* device handlers call this method to issue commands to the NeoHub
|
||||||
*/
|
*/
|
||||||
public synchronized NeoHubReturnResult toNeoHubSendChannelValue(String commandStr) {
|
public synchronized NeoHubReturnResult toNeoHubSendChannelValue(String commandStr) {
|
||||||
NeoHubSocket socket = this.socket;
|
NeoHubSocketBase socket = this.socket;
|
||||||
|
|
||||||
if (socket == null || config == null) {
|
if (socket == null || config == null) {
|
||||||
return NeoHubReturnResult.ERR_INITIALIZATION;
|
return NeoHubReturnResult.ERR_INITIALIZATION;
|
||||||
@ -246,7 +268,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
* @return a class that contains the full status of all devices
|
* @return a class that contains the full status of all devices
|
||||||
*/
|
*/
|
||||||
protected @Nullable NeoHubAbstractDeviceData fromNeoHubGetDeviceData() {
|
protected @Nullable NeoHubAbstractDeviceData fromNeoHubGetDeviceData() {
|
||||||
NeoHubSocket socket = this.socket;
|
NeoHubSocketBase socket = this.socket;
|
||||||
|
|
||||||
if (socket == null || config == null) {
|
if (socket == null || config == null) {
|
||||||
logger.warn(MSG_HUB_CONFIG, getThing().getUID());
|
logger.warn(MSG_HUB_CONFIG, getThing().getUID());
|
||||||
@ -322,7 +344,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
* @return a class that contains the status of the system
|
* @return a class that contains the status of the system
|
||||||
*/
|
*/
|
||||||
protected @Nullable NeoHubReadDcbResponse fromNeoHubReadSystemData() {
|
protected @Nullable NeoHubReadDcbResponse fromNeoHubReadSystemData() {
|
||||||
NeoHubSocket socket = this.socket;
|
NeoHubSocketBase socket = this.socket;
|
||||||
|
|
||||||
if (socket == null) {
|
if (socket == null) {
|
||||||
return null;
|
return null;
|
||||||
@ -443,7 +465,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
boolean supportsLegacyApi = false;
|
boolean supportsLegacyApi = false;
|
||||||
boolean supportsFutureApi = false;
|
boolean supportsFutureApi = false;
|
||||||
|
|
||||||
NeoHubSocket socket = this.socket;
|
NeoHubSocketBase socket = this.socket;
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
String responseJson;
|
String responseJson;
|
||||||
NeoHubReadDcbResponse systemData;
|
NeoHubReadDcbResponse systemData;
|
||||||
@ -498,7 +520,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
|
|||||||
* get the Engineers data
|
* get the Engineers data
|
||||||
*/
|
*/
|
||||||
public @Nullable NeoHubGetEngineersData fromNeoHubGetEngineersData() {
|
public @Nullable NeoHubGetEngineersData fromNeoHubGetEngineersData() {
|
||||||
NeoHubSocket socket = this.socket;
|
NeoHubSocketBase socket = this.socket;
|
||||||
if (socket != null) {
|
if (socket != null) {
|
||||||
String responseJson;
|
String responseJson;
|
||||||
try {
|
try {
|
||||||
|
@ -59,9 +59,11 @@ public class NeoHubReadDcbResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable String getFirmwareVersion() {
|
public @Nullable String getFirmwareVersion() {
|
||||||
|
BigDecimal firmwareVersionNew = this.firmwareVersionNew;
|
||||||
if (firmwareVersionNew != null) {
|
if (firmwareVersionNew != null) {
|
||||||
return firmwareVersionNew.toString();
|
return firmwareVersionNew.toString();
|
||||||
}
|
}
|
||||||
|
BigDecimal firmwareVersionOld = this.firmwareVersionOld;
|
||||||
if (firmwareVersionOld != null) {
|
if (firmwareVersionOld != null) {
|
||||||
return firmwareVersionOld.toString();
|
return firmwareVersionOld.toString();
|
||||||
}
|
}
|
||||||
|
@ -25,54 +25,30 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NeoHubConnector handles the ASCII based communication via TCP between openHAB
|
* Handles the ASCII based communication via TCP socket between openHAB and NeoHub
|
||||||
* and NeoHub
|
|
||||||
*
|
*
|
||||||
* @author Sebastian Prehn - Initial contribution
|
* @author Sebastian Prehn - Initial contribution
|
||||||
* @author Andrew Fiddian-Green - Refactoring for openHAB v2.x
|
* @author Andrew Fiddian-Green - Refactoring for openHAB v2.x
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class NeoHubSocket {
|
public class NeoHubSocket extends NeoHubSocketBase {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(NeoHubSocket.class);
|
private final Logger logger = LoggerFactory.getLogger(NeoHubSocket.class);
|
||||||
|
|
||||||
/**
|
public NeoHubSocket(NeoHubConfiguration config) {
|
||||||
* Name of host or IP to connect to.
|
super(config);
|
||||||
*/
|
|
||||||
private final String hostname;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The port to connect to
|
|
||||||
*/
|
|
||||||
private final int port;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The socket connect resp. read timeout value
|
|
||||||
*/
|
|
||||||
private final int timeout;
|
|
||||||
|
|
||||||
public NeoHubSocket(final String hostname, final int portNumber, final int timeoutSeconds) {
|
|
||||||
this.hostname = hostname;
|
|
||||||
this.port = portNumber;
|
|
||||||
this.timeout = timeoutSeconds * 1000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* sends the message over the network to the NeoHub and returns its response
|
public synchronized String sendMessage(final String requestJson) throws IOException, NeoHubException {
|
||||||
*
|
|
||||||
* @param requestJson the message to be sent to the NeoHub
|
|
||||||
* @return responseJson received from NeoHub
|
|
||||||
* @throws NeoHubException, IOException
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public String sendMessage(final String requestJson) throws IOException, NeoHubException {
|
|
||||||
IOException caughtException = null;
|
IOException caughtException = null;
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
try (Socket socket = new Socket()) {
|
try (Socket socket = new Socket()) {
|
||||||
socket.connect(new InetSocketAddress(hostname, port), timeout);
|
int port = config.portNumber > 0 ? config.portNumber : NeoHubBindingConstants.PORT_TCP;
|
||||||
socket.setSoTimeout(timeout);
|
socket.connect(new InetSocketAddress(config.hostName, port), config.socketTimeout * 1000);
|
||||||
|
socket.setSoTimeout(config.socketTimeout * 1000);
|
||||||
|
|
||||||
try (InputStreamReader reader = new InputStreamReader(socket.getInputStream(), US_ASCII);
|
try (InputStreamReader reader = new InputStreamReader(socket.getInputStream(), US_ASCII);
|
||||||
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream(), US_ASCII)) {
|
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream(), US_ASCII)) {
|
||||||
@ -128,4 +104,9 @@ public class NeoHubSocket {
|
|||||||
|
|
||||||
return responseJson;
|
return responseJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.neohub.internal;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base abstract class for ASCII based communication between openHAB and NeoHub
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class NeoHubSocketBase implements Closeable {
|
||||||
|
|
||||||
|
protected final NeoHubConfiguration config;
|
||||||
|
|
||||||
|
public NeoHubSocketBase(NeoHubConfiguration config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the message over the network to the NeoHub and returns its response
|
||||||
|
*
|
||||||
|
* @param requestJson the message to be sent to the NeoHub
|
||||||
|
* @return responseJson received from NeoHub
|
||||||
|
* @throws NeoHubException, IOException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public abstract String sendMessage(final String requestJson) throws IOException, NeoHubException;
|
||||||
|
}
|
@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.neohub.internal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||||
|
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||||
|
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the ASCII based communication via web socket between openHAB and NeoHub
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@WebSocket
|
||||||
|
public class NeoHubWebSocket extends NeoHubSocketBase {
|
||||||
|
|
||||||
|
private static final int SLEEP_MILLISECONDS = 100;
|
||||||
|
private static final String REQUEST_OUTER = "{\"message_type\":\"hm_get_command_queue\",\"message\":\"%s\"}";
|
||||||
|
private static final String REQUEST_INNER = "{\"token\":\"%s\",\"COMMANDS\":[{\"COMMAND\":\"%s\",\"COMMANDID\":1}]}";
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(NeoHubWebSocket.class);
|
||||||
|
private final Gson gson = new Gson();
|
||||||
|
private final WebSocketClient webSocketClient;
|
||||||
|
|
||||||
|
private @Nullable Session session = null;
|
||||||
|
private String responseOuter = "";
|
||||||
|
private boolean responseWaiting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO to receive and parse the response JSON.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*/
|
||||||
|
private static class Response {
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public @Nullable String command_id;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public @Nullable String device_id;
|
||||||
|
public @Nullable String message_type;
|
||||||
|
public @Nullable String response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NeoHubWebSocket(NeoHubConfiguration config) throws NeoHubException {
|
||||||
|
super(config);
|
||||||
|
|
||||||
|
// initialise and start ssl context factory, http client, web socket client
|
||||||
|
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
|
||||||
|
sslContextFactory.setTrustAll(true);
|
||||||
|
HttpClient httpClient = new HttpClient(sslContextFactory);
|
||||||
|
try {
|
||||||
|
httpClient.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new NeoHubException(String.format("Error starting http client: '%s'", e.getMessage()));
|
||||||
|
}
|
||||||
|
webSocketClient = new WebSocketClient(httpClient);
|
||||||
|
webSocketClient.setConnectTimeout(config.socketTimeout * 1000);
|
||||||
|
try {
|
||||||
|
webSocketClient.start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new NeoHubException(String.format("Error starting web socket client: '%s'", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the web socket session.
|
||||||
|
*
|
||||||
|
* @throws NeoHubException
|
||||||
|
*/
|
||||||
|
private void startSession() throws NeoHubException {
|
||||||
|
Session session = this.session;
|
||||||
|
if (session == null || !session.isOpen()) {
|
||||||
|
closeSession();
|
||||||
|
try {
|
||||||
|
int port = config.portNumber > 0 ? config.portNumber : NeoHubBindingConstants.PORT_WSS;
|
||||||
|
URI uri = new URI(String.format("wss://%s:%d", config.hostName, port));
|
||||||
|
webSocketClient.connect(this, uri).get();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new NeoHubException(String.format("Error starting session: '%s'", e.getMessage(), e));
|
||||||
|
} catch (ExecutionException | IOException | URISyntaxException e) {
|
||||||
|
throw new NeoHubException(String.format("Error starting session: '%s'", e.getMessage(), e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the web socket session.
|
||||||
|
*/
|
||||||
|
private void closeSession() {
|
||||||
|
Session session = this.session;
|
||||||
|
if (session != null) {
|
||||||
|
session.close();
|
||||||
|
this.session = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to escape the quote marks in a JSON string.
|
||||||
|
*
|
||||||
|
* @param json the input JSON string.
|
||||||
|
* @return the escaped JSON version.
|
||||||
|
*/
|
||||||
|
private String jsonEscape(String json) {
|
||||||
|
return json.replace("\"", "\\\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to remove quote escape marks from an escaped JSON string.
|
||||||
|
*
|
||||||
|
* @param escapedJson the escaped input string.
|
||||||
|
* @return the clean JSON version.
|
||||||
|
*/
|
||||||
|
private String jsonUnEscape(String escapedJson) {
|
||||||
|
return escapedJson.replace("\\\"", "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to replace double quote marks in a JSON string with single quote marks.
|
||||||
|
*
|
||||||
|
* @param json the input string.
|
||||||
|
* @return the modified version.
|
||||||
|
*/
|
||||||
|
private String jsonReplaceQuotes(String json) {
|
||||||
|
return json.replace("\"", "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized String sendMessage(final String requestJson) throws IOException, NeoHubException {
|
||||||
|
// start the session
|
||||||
|
startSession();
|
||||||
|
|
||||||
|
// session start failed
|
||||||
|
Session session = this.session;
|
||||||
|
if (session == null) {
|
||||||
|
throw new NeoHubException("Session is null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap the inner request in an outer request string
|
||||||
|
String requestOuter = String.format(REQUEST_OUTER,
|
||||||
|
jsonEscape(String.format(REQUEST_INNER, config.apiToken, jsonReplaceQuotes(requestJson))));
|
||||||
|
|
||||||
|
// initialise the response
|
||||||
|
responseOuter = "";
|
||||||
|
responseWaiting = true;
|
||||||
|
|
||||||
|
// send the request
|
||||||
|
logger.trace("Sending request: {}", requestOuter);
|
||||||
|
session.getRemote().sendString(requestOuter);
|
||||||
|
|
||||||
|
// sleep and loop until we get a response or the socket is closed
|
||||||
|
int sleepRemainingMilliseconds = config.socketTimeout * 1000;
|
||||||
|
while (responseWaiting && (sleepRemainingMilliseconds > 0)) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(SLEEP_MILLISECONDS);
|
||||||
|
sleepRemainingMilliseconds = sleepRemainingMilliseconds - SLEEP_MILLISECONDS;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new NeoHubException(String.format("Read timeout '%s'", e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract the inner response from the outer response string
|
||||||
|
Response responseDto = gson.fromJson(responseOuter, Response.class);
|
||||||
|
if (responseDto != null && NeoHubBindingConstants.HM_SET_COMMAND_RESPONSE.equals(responseDto.message_type)) {
|
||||||
|
String responseJson = responseDto.response;
|
||||||
|
if (responseJson != null) {
|
||||||
|
responseJson = jsonUnEscape(responseJson);
|
||||||
|
logger.trace("Received response: {}", responseJson);
|
||||||
|
return responseJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("Null or invalid response.");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
closeSession();
|
||||||
|
try {
|
||||||
|
webSocketClient.stop();
|
||||||
|
} catch (Exception e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnWebSocketConnect
|
||||||
|
public void onConnect(Session session) {
|
||||||
|
logger.trace("onConnect: ok");
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnWebSocketClose
|
||||||
|
public void onClose(int statusCode, String reason) {
|
||||||
|
logger.trace("onClose: code:{}, reason:{}", statusCode, reason);
|
||||||
|
responseWaiting = false;
|
||||||
|
this.session = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnWebSocketError
|
||||||
|
public void onError(Throwable cause) {
|
||||||
|
logger.trace("onError: cause:{}", cause.getMessage());
|
||||||
|
closeSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnWebSocketMessage
|
||||||
|
public void onMessage(String msg) {
|
||||||
|
logger.trace("onMessage: msg:{}", msg);
|
||||||
|
responseOuter = msg;
|
||||||
|
responseWaiting = false;
|
||||||
|
}
|
||||||
|
}
|
@ -35,11 +35,15 @@ thing-type.config.neohub.neohub.hostName.description = Host name (IP address) of
|
|||||||
thing-type.config.neohub.neohub.pollingInterval.label = Polling Interval
|
thing-type.config.neohub.neohub.pollingInterval.label = Polling Interval
|
||||||
thing-type.config.neohub.neohub.pollingInterval.description = Time (seconds) between polling the NeoHub (min=4, max/default=60)
|
thing-type.config.neohub.neohub.pollingInterval.description = Time (seconds) between polling the NeoHub (min=4, max/default=60)
|
||||||
thing-type.config.neohub.neohub.portNumber.label = Port Number
|
thing-type.config.neohub.neohub.portNumber.label = Port Number
|
||||||
thing-type.config.neohub.neohub.portNumber.description = Port number of the NeoHub
|
thing-type.config.neohub.neohub.portNumber.description = Override port number to use to connect to the NeoHub (0=automatic)
|
||||||
thing-type.config.neohub.neohub.preferLegacyApi.label = Prefer Legacy API
|
thing-type.config.neohub.neohub.preferLegacyApi.label = Prefer Legacy API
|
||||||
thing-type.config.neohub.neohub.preferLegacyApi.description = Use the legacy API instead of the new API (if available)
|
thing-type.config.neohub.neohub.preferLegacyApi.description = Use the legacy API instead of the new API (if available)
|
||||||
thing-type.config.neohub.neohub.socketTimeout.label = Socket Timeout
|
thing-type.config.neohub.neohub.socketTimeout.label = Socket Timeout
|
||||||
thing-type.config.neohub.neohub.socketTimeout.description = Time (seconds) to wait for connections to the Hub (min/default=5, max=20)
|
thing-type.config.neohub.neohub.socketTimeout.description = Time (seconds) to wait for connections to the Hub (min/default=5, max=20)
|
||||||
|
thing-type.config.neohub.neohub.apiToken.label = API Access Token
|
||||||
|
thing-type.config.neohub.neohub.apiToken.description = API access token for the hub (created on the Heatmiser mobile App)
|
||||||
|
thing-type.config.neohub.neohub.useWebSocket.label = Connect via WebSocket
|
||||||
|
thing-type.config.neohub.neohub.useWebSocket.description = Select whether to communicate with the Neohub via WebSocket or TCP
|
||||||
thing-type.config.neohub.neoplug.deviceNameInHub.label = Device Name
|
thing-type.config.neohub.neoplug.deviceNameInHub.label = Device Name
|
||||||
thing-type.config.neohub.neoplug.deviceNameInHub.description = Device Name that identifies the NeoPlug device in the NeoHub and Heatmiser App
|
thing-type.config.neohub.neoplug.deviceNameInHub.description = Device Name that identifies the NeoPlug device in the NeoHub and Heatmiser App
|
||||||
thing-type.config.neohub.neostat.deviceNameInHub.label = Device Name
|
thing-type.config.neohub.neostat.deviceNameInHub.label = Device Name
|
||||||
|
@ -28,8 +28,8 @@
|
|||||||
|
|
||||||
<parameter name="portNumber" type="integer" required="false">
|
<parameter name="portNumber" type="integer" required="false">
|
||||||
<label>Port Number</label>
|
<label>Port Number</label>
|
||||||
<description>Port number of the NeoHub</description>
|
<description>Override port number to use to connect to the NeoHub (0=automatic)</description>
|
||||||
<default>4242</default>
|
<default>0</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
|
||||||
@ -53,6 +53,17 @@
|
|||||||
<default>false</default>
|
<default>false</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
|
||||||
|
<parameter name="useWebSocket" type="boolean" required="false">
|
||||||
|
<label>Connect via WebSocket</label>
|
||||||
|
<description>Select whether to communicate with the Neohub via WebSocket or TCP</description>
|
||||||
|
<default>false</default>
|
||||||
|
</parameter>
|
||||||
|
|
||||||
|
<parameter name="apiToken" type="text" required="false">
|
||||||
|
<label>API Access Token</label>
|
||||||
|
<description>API access token for the hub (created with the Heatmiser mobile app)</description>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
|
|
||||||
</bridge-type>
|
</bridge-type>
|
||||||
|
@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData;
|
import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData;
|
||||||
import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData.AbstractRecord;
|
import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData.AbstractRecord;
|
||||||
|
import org.openhab.binding.neohub.internal.NeoHubConfiguration;
|
||||||
import org.openhab.binding.neohub.internal.NeoHubGetEngineersData;
|
import org.openhab.binding.neohub.internal.NeoHubGetEngineersData;
|
||||||
import org.openhab.binding.neohub.internal.NeoHubInfoResponse;
|
import org.openhab.binding.neohub.internal.NeoHubInfoResponse;
|
||||||
import org.openhab.binding.neohub.internal.NeoHubInfoResponse.InfoRecord;
|
import org.openhab.binding.neohub.internal.NeoHubInfoResponse.InfoRecord;
|
||||||
@ -36,25 +37,24 @@ import org.openhab.core.library.unit.ImperialUnits;
|
|||||||
import org.openhab.core.library.unit.SIUnits;
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link NeoHubTestData} class defines common constants, which are used
|
* JUnit for testing JSON parsing.
|
||||||
* across the whole binding.
|
|
||||||
*
|
*
|
||||||
* @author Andrew Fiddian-Green - Initial contribution
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class NeoHubTestData {
|
public class NeoHubJsonTests {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* to actually run tests on a physical device you must have a hub physically available, and its IP address must be
|
* to actually run tests on a physical device you must have a hub physically available, and its IP address must be
|
||||||
* correctly configured in the "hubIPAddress" string constant e.g. "192.168.1.123"
|
* correctly configured in the "hubIPAddress" string constant e.g. "192.168.1.123"
|
||||||
* note: only run the test if such a device is actually available
|
* note: only run the test if such a device is actually available
|
||||||
*/
|
*/
|
||||||
private static final String hubIpAddress = "192.168.1.xxx";
|
private static final String HUB_IP_ADDRESS = "192.168.1.xxx";
|
||||||
|
|
||||||
private static final Pattern VALID_IP_V4_ADDRESS = Pattern
|
public static final Pattern VALID_IP_V4_ADDRESS = Pattern
|
||||||
.compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
|
.compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Load the test JSON payload string from a file
|
* Load the test JSON payload string from a file
|
||||||
*/
|
*/
|
||||||
private String load(String fileName) {
|
private String load(String fileName) {
|
||||||
@ -72,10 +72,9 @@ public class NeoHubTestData {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Test an INFO JSON response string as produced by older firmware versions
|
* Test an INFO JSON response string as produced by older firmware versions
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testInfoJsonOld() {
|
public void testInfoJsonOld() {
|
||||||
// load INFO JSON response string in old JSON format
|
// load INFO JSON response string in old JSON format
|
||||||
@ -133,10 +132,9 @@ public class NeoHubTestData {
|
|||||||
assertFalse(device.stateManual());
|
assertFalse(device.stateManual());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Test an INFO JSON response string as produced by newer firmware versions
|
* Test an INFO JSON response string as produced by newer firmware versions
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testInfoJsonNew() {
|
public void testInfoJsonNew() {
|
||||||
// load INFO JSON response string in new JSON format
|
// load INFO JSON response string in new JSON format
|
||||||
@ -158,10 +156,9 @@ public class NeoHubTestData {
|
|||||||
assertEquals(new BigDecimal("255.255"), device.getActualTemperature());
|
assertEquals(new BigDecimal("255.255"), device.getActualTemperature());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Test for a READ_DCB JSON string that has valid CORF C response
|
* Test for a READ_DCB JSON string that has valid CORF C response
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadDcbJson() {
|
public void testReadDcbJson() {
|
||||||
// load READ_DCB JSON response string with valid CORF C response
|
// load READ_DCB JSON response string with valid CORF C response
|
||||||
@ -186,10 +183,9 @@ public class NeoHubTestData {
|
|||||||
assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
|
assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Test an INFO JSON string that has a door contact and a temperature sensor
|
* Test an INFO JSON string that has a door contact and a temperature sensor
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testInfoJsonWithSensors() {
|
public void testInfoJsonWithSensors() {
|
||||||
/*
|
/*
|
||||||
@ -240,11 +236,10 @@ public class NeoHubTestData {
|
|||||||
assertTrue(device.isBatteryLow());
|
assertTrue(device.isBatteryLow());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* From NeoHub rev2.6 onwards the READ_DCB command is "deprecated" so we can
|
* From NeoHub rev2.6 onwards the READ_DCB command is "deprecated" so we can
|
||||||
* also test the replacement GET_SYSTEM command (valid CORF response)
|
* also test the replacement GET_SYSTEM command (valid CORF response)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetSystemJson() {
|
public void testGetSystemJson() {
|
||||||
// load GET_SYSTEM JSON response string
|
// load GET_SYSTEM JSON response string
|
||||||
@ -255,11 +250,10 @@ public class NeoHubTestData {
|
|||||||
assertEquals("2134", dcbResponse.getFirmwareVersion());
|
assertEquals("2134", dcbResponse.getFirmwareVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* From NeoHub rev2.6 onwards the INFO command is "deprecated" so we must test
|
* From NeoHub rev2.6 onwards the INFO command is "deprecated" so we must test
|
||||||
* the replacement GET_LIVE_DATA command
|
* the replacement GET_LIVE_DATA command
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetLiveDataJson() {
|
public void testGetLiveDataJson() {
|
||||||
// load GET_LIVE_DATA JSON response string
|
// load GET_LIVE_DATA JSON response string
|
||||||
@ -343,12 +337,11 @@ public class NeoHubTestData {
|
|||||||
assertTrue(MATCHER_HEATMISER_REPEATER.matcher(device.getDeviceName()).matches());
|
assertTrue(MATCHER_HEATMISER_REPEATER.matcher(device.getDeviceName()).matches());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* From NeoHub rev2.6 onwards the INFO command is "deprecated" and the DEVICE_ID
|
* From NeoHub rev2.6 onwards the INFO command is "deprecated" and the DEVICE_ID
|
||||||
* element is not returned in the GET_LIVE_DATA call so we must test the
|
* element is not returned in the GET_LIVE_DATA call so we must test the
|
||||||
* replacement GET_ENGINEERS command
|
* replacement GET_ENGINEERS command
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetEngineersJson() {
|
public void testGetEngineersJson() {
|
||||||
// load GET_ENGINEERS JSON response string
|
// load GET_ENGINEERS JSON response string
|
||||||
@ -362,31 +355,34 @@ public class NeoHubTestData {
|
|||||||
assertEquals(6, engResponse.getDeviceType("Living Room South"));
|
assertEquals(6, engResponse.getDeviceType("Living Room South"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* send JSON request to the socket and retrieve JSON response
|
* send JSON request to the socket and retrieve JSON response
|
||||||
*/
|
*/
|
||||||
private String testCommunicationInner(String requestJson) {
|
private String testCommunicationInner(String requestJson) {
|
||||||
NeoHubSocket socket = new NeoHubSocket(hubIpAddress, 4242, 5);
|
NeoHubConfiguration config = new NeoHubConfiguration();
|
||||||
String responseJson = "";
|
config.hostName = HUB_IP_ADDRESS;
|
||||||
|
config.socketTimeout = 5;
|
||||||
try {
|
try {
|
||||||
responseJson = socket.sendMessage(requestJson);
|
NeoHubSocket socket = new NeoHubSocket(config);
|
||||||
|
String responseJson = socket.sendMessage(requestJson);
|
||||||
|
socket.close();
|
||||||
|
return responseJson;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
assertTrue(false);
|
assertTrue(false);
|
||||||
}
|
}
|
||||||
return responseJson;
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Test the communications
|
* Test the communications
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("null")
|
|
||||||
@Test
|
@Test
|
||||||
public void testCommunications() {
|
public void testCommunications() {
|
||||||
/*
|
/*
|
||||||
* tests the actual communication with a real physical device on 'hubIpAddress'
|
* tests the actual communication with a real physical device on 'hubIpAddress'
|
||||||
* note: only run the test if such a device is actually available
|
* note: only run the test if such a device is actually available
|
||||||
*/
|
*/
|
||||||
if (!VALID_IP_V4_ADDRESS.matcher(hubIpAddress).matches()) {
|
if (!VALID_IP_V4_ADDRESS.matcher(HUB_IP_ADDRESS).matches()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.neohub.test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.neohub.internal.NeoHubBindingConstants;
|
||||||
|
import org.openhab.binding.neohub.internal.NeoHubConfiguration;
|
||||||
|
import org.openhab.binding.neohub.internal.NeoHubException;
|
||||||
|
import org.openhab.binding.neohub.internal.NeoHubSocket;
|
||||||
|
import org.openhab.binding.neohub.internal.NeoHubWebSocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JUnit for testing WSS and TCP socket protocols.
|
||||||
|
*
|
||||||
|
* @author Andrew Fiddian-Green - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class NeoHubProtocolTests {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test online communication. Requires an actual Neohub to be present on the LAN. Configuration parameters must be
|
||||||
|
* entered for the actual specific Neohub instance as follows:
|
||||||
|
*
|
||||||
|
* - HUB_IP_ADDRESS the dotted ip address of the hub
|
||||||
|
* - HUB_API_TOKEN the api access token for the hub
|
||||||
|
* - SOCKET_TIMEOUT the connection time out
|
||||||
|
* - RUN_WSS_TEST enable testing the WSS communication
|
||||||
|
* - RUN_TCP_TEST enable testing the TCP communication
|
||||||
|
*
|
||||||
|
* NOTE: only run these tests if a device is actually available
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final String HUB_IP_ADDRESS = "192.168.1.xxx";
|
||||||
|
private static final String HUB_API_TOKEN = "12345678-1234-1234-1234-123456789ABC";
|
||||||
|
private static final int SOCKET_TIMEOUT = 5;
|
||||||
|
private static final boolean RUN_WSS_TEST = false;
|
||||||
|
private static final boolean RUN_TCP_TEST = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use web socket to send a request, and check for a response.
|
||||||
|
*
|
||||||
|
* @throws NeoHubException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testWssConnection() throws NeoHubException, IOException {
|
||||||
|
if (RUN_WSS_TEST) {
|
||||||
|
if (!NeoHubJsonTests.VALID_IP_V4_ADDRESS.matcher(HUB_IP_ADDRESS).matches()) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoHubConfiguration config = new NeoHubConfiguration();
|
||||||
|
config.hostName = HUB_IP_ADDRESS;
|
||||||
|
config.socketTimeout = SOCKET_TIMEOUT;
|
||||||
|
config.apiToken = HUB_API_TOKEN;
|
||||||
|
|
||||||
|
NeoHubWebSocket socket = new NeoHubWebSocket(config);
|
||||||
|
String requestJson = NeoHubBindingConstants.CMD_CODE_FIRMWARE;
|
||||||
|
String responseJson = socket.sendMessage(requestJson);
|
||||||
|
assertNotEquals(0, responseJson.length());
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use TCP socket to send a request, and check for a response.
|
||||||
|
*
|
||||||
|
* @throws NeoHubException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testTcpConnection() throws IOException, NeoHubException {
|
||||||
|
if (RUN_TCP_TEST) {
|
||||||
|
if (!NeoHubJsonTests.VALID_IP_V4_ADDRESS.matcher(HUB_IP_ADDRESS).matches()) {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoHubConfiguration config = new NeoHubConfiguration();
|
||||||
|
config.hostName = HUB_IP_ADDRESS;
|
||||||
|
config.socketTimeout = SOCKET_TIMEOUT;
|
||||||
|
config.apiToken = HUB_API_TOKEN;
|
||||||
|
|
||||||
|
NeoHubSocket socket = new NeoHubSocket(config);
|
||||||
|
String requestJson = NeoHubBindingConstants.CMD_CODE_FIRMWARE;
|
||||||
|
String responseJson = socket.sendMessage(requestJson);
|
||||||
|
assertNotEquals(0, responseJson.length());
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user