[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:
Andrew Fiddian-Green
2022-08-01 20:58:39 +01:00
committed by GitHub
parent 8d3828a9a4
commit f60e324229
14 changed files with 521 additions and 89 deletions

View File

@@ -196,4 +196,16 @@ public class NeoHubBindingConstants {
public static final String PROPERTY_FIRMWARE_VERSION = "Firmware version";
public static final String PROPERTY_API_VERSION = "API version";
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";
}

View File

@@ -30,4 +30,6 @@ public class NeoHubConfiguration {
public int pollingInterval;
public int socketTimeout;
public boolean preferLegacyApi;
public String apiToken = "";
public boolean useWebSocket;
}

View File

@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
* Discovery service for neo devices
*
* @author Andrew Fiddian-Green - Initial contribution
*
*
*/
@NonNullByDefault
public class NeoHubDiscoveryService extends AbstractDiscoveryService {
@@ -113,11 +113,11 @@ public class NeoHubDiscoveryService extends AbstractDiscoveryService {
// the record came from the legacy API (deviceType included)
if (deviceRecord instanceof InfoRecord) {
deviceType = ((InfoRecord) deviceRecord).getDeviceType();
publishDevice((InfoRecord) deviceRecord, deviceType);
publishDevice(deviceRecord, deviceType);
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 (engineerData == null) {
break;
@@ -128,7 +128,7 @@ public class NeoHubDiscoveryService extends AbstractDiscoveryService {
continue;
}
deviceType = engineerData.getDeviceType(deviceName);
publishDevice((LiveDataRecord) deviceRecord, deviceType);
publishDevice(deviceRecord, deviceType);
}
}
}

View File

@@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* The {@link NeoHubException} is a custom exception for NeoHub
*
* @author Andrew Fiddian-Green - Initial contribution
*
*
*/
@NonNullByDefault
public class NeoHubException extends Exception {
@@ -28,4 +28,8 @@ public class NeoHubException extends Exception {
public NeoHubException(String message) {
super(message);
}
public NeoHubException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -65,7 +65,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
private final Map<String, Boolean> connectionStates = new HashMap<>();
private @Nullable NeoHubConfiguration config;
private @Nullable NeoHubSocket socket;
private @Nullable NeoHubSocketBase socket;
private @Nullable ScheduledFuture<?> lazyPollingScheduler;
private @Nullable ScheduledFuture<?> fastPollingScheduler;
@@ -113,7 +113,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
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!");
return;
}
@@ -142,7 +142,20 @@ public class NeoHubHandler extends BaseBridgeHandler {
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;
/*
@@ -206,6 +219,15 @@ public class NeoHubHandler extends BaseBridgeHandler {
fast.cancel(true);
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
*/
public synchronized NeoHubReturnResult toNeoHubSendChannelValue(String commandStr) {
NeoHubSocket socket = this.socket;
NeoHubSocketBase socket = this.socket;
if (socket == null || config == null) {
return NeoHubReturnResult.ERR_INITIALIZATION;
@@ -246,7 +268,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
* @return a class that contains the full status of all devices
*/
protected @Nullable NeoHubAbstractDeviceData fromNeoHubGetDeviceData() {
NeoHubSocket socket = this.socket;
NeoHubSocketBase socket = this.socket;
if (socket == null || config == null) {
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
*/
protected @Nullable NeoHubReadDcbResponse fromNeoHubReadSystemData() {
NeoHubSocket socket = this.socket;
NeoHubSocketBase socket = this.socket;
if (socket == null) {
return null;
@@ -443,7 +465,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
boolean supportsLegacyApi = false;
boolean supportsFutureApi = false;
NeoHubSocket socket = this.socket;
NeoHubSocketBase socket = this.socket;
if (socket != null) {
String responseJson;
NeoHubReadDcbResponse systemData;
@@ -498,7 +520,7 @@ public class NeoHubHandler extends BaseBridgeHandler {
* get the Engineers data
*/
public @Nullable NeoHubGetEngineersData fromNeoHubGetEngineersData() {
NeoHubSocket socket = this.socket;
NeoHubSocketBase socket = this.socket;
if (socket != null) {
String responseJson;
try {

View File

@@ -59,9 +59,11 @@ public class NeoHubReadDcbResponse {
}
public @Nullable String getFirmwareVersion() {
BigDecimal firmwareVersionNew = this.firmwareVersionNew;
if (firmwareVersionNew != null) {
return firmwareVersionNew.toString();
}
BigDecimal firmwareVersionOld = this.firmwareVersionOld;
if (firmwareVersionOld != null) {
return firmwareVersionOld.toString();
}

View File

@@ -25,54 +25,30 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* NeoHubConnector handles the ASCII based communication via TCP between openHAB
* and NeoHub
* Handles the ASCII based communication via TCP socket between openHAB and NeoHub
*
* @author Sebastian Prehn - Initial contribution
* @author Andrew Fiddian-Green - Refactoring for openHAB v2.x
*
*/
@NonNullByDefault
public class NeoHubSocket {
public class NeoHubSocket extends NeoHubSocketBase {
private final Logger logger = LoggerFactory.getLogger(NeoHubSocket.class);
/**
* Name of host or IP to connect to.
*/
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;
public NeoHubSocket(NeoHubConfiguration config) {
super(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 String sendMessage(final String requestJson) throws IOException, NeoHubException {
@Override
public synchronized String sendMessage(final String requestJson) throws IOException, NeoHubException {
IOException caughtException = null;
StringBuilder builder = new StringBuilder();
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(hostname, port), timeout);
socket.setSoTimeout(timeout);
int port = config.portNumber > 0 ? config.portNumber : NeoHubBindingConstants.PORT_TCP;
socket.connect(new InetSocketAddress(config.hostName, port), config.socketTimeout * 1000);
socket.setSoTimeout(config.socketTimeout * 1000);
try (InputStreamReader reader = new InputStreamReader(socket.getInputStream(), US_ASCII);
OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream(), US_ASCII)) {
@@ -128,4 +104,9 @@ public class NeoHubSocket {
return responseJson;
}
@Override
public void close() {
// nothing to do
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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.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.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.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.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.description = Device Name that identifies the NeoPlug device in the NeoHub and Heatmiser App
thing-type.config.neohub.neostat.deviceNameInHub.label = Device Name

View File

@@ -28,8 +28,8 @@
<parameter name="portNumber" type="integer" required="false">
<label>Port Number</label>
<description>Port number of the NeoHub</description>
<default>4242</default>
<description>Override port number to use to connect to the NeoHub (0=automatic)</description>
<default>0</default>
<advanced>true</advanced>
</parameter>
@@ -53,6 +53,17 @@
<default>false</default>
<advanced>true</advanced>
</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>
</bridge-type>

View File

@@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData;
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.NeoHubInfoResponse;
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;
/**
* The {@link NeoHubTestData} class defines common constants, which are used
* across the whole binding.
* JUnit for testing JSON parsing.
*
* @author Andrew Fiddian-Green - Initial contribution
*/
@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
* 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
*/
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");
/*
/**
* Load the test JSON payload string from a file
*/
private String load(String fileName) {
@@ -72,10 +72,9 @@ public class NeoHubTestData {
return "";
}
/*
/**
* Test an INFO JSON response string as produced by older firmware versions
*/
@SuppressWarnings("null")
@Test
public void testInfoJsonOld() {
// load INFO JSON response string in old JSON format
@@ -133,10 +132,9 @@ public class NeoHubTestData {
assertFalse(device.stateManual());
}
/*
/**
* Test an INFO JSON response string as produced by newer firmware versions
*/
@SuppressWarnings("null")
@Test
public void testInfoJsonNew() {
// load INFO JSON response string in new JSON format
@@ -158,10 +156,9 @@ public class NeoHubTestData {
assertEquals(new BigDecimal("255.255"), device.getActualTemperature());
}
/*
/**
* Test for a READ_DCB JSON string that has valid CORF C response
*/
@SuppressWarnings("null")
@Test
public void testReadDcbJson() {
// load READ_DCB JSON response string with valid CORF C response
@@ -186,10 +183,9 @@ public class NeoHubTestData {
assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
}
/*
/**
* Test an INFO JSON string that has a door contact and a temperature sensor
*/
@SuppressWarnings("null")
@Test
public void testInfoJsonWithSensors() {
/*
@@ -240,11 +236,10 @@ public class NeoHubTestData {
assertTrue(device.isBatteryLow());
}
/*
/**
* From NeoHub rev2.6 onwards the READ_DCB command is "deprecated" so we can
* also test the replacement GET_SYSTEM command (valid CORF response)
*/
@SuppressWarnings("null")
@Test
public void testGetSystemJson() {
// load GET_SYSTEM JSON response string
@@ -255,11 +250,10 @@ public class NeoHubTestData {
assertEquals("2134", dcbResponse.getFirmwareVersion());
}
/*
/**
* From NeoHub rev2.6 onwards the INFO command is "deprecated" so we must test
* the replacement GET_LIVE_DATA command
*/
@SuppressWarnings("null")
@Test
public void testGetLiveDataJson() {
// load GET_LIVE_DATA JSON response string
@@ -343,12 +337,11 @@ public class NeoHubTestData {
assertTrue(MATCHER_HEATMISER_REPEATER.matcher(device.getDeviceName()).matches());
}
/*
/**
* 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
* replacement GET_ENGINEERS command
*/
@SuppressWarnings("null")
@Test
public void testGetEngineersJson() {
// load GET_ENGINEERS JSON response string
@@ -362,31 +355,34 @@ public class NeoHubTestData {
assertEquals(6, engResponse.getDeviceType("Living Room South"));
}
/*
/**
* send JSON request to the socket and retrieve JSON response
*/
private String testCommunicationInner(String requestJson) {
NeoHubSocket socket = new NeoHubSocket(hubIpAddress, 4242, 5);
String responseJson = "";
NeoHubConfiguration config = new NeoHubConfiguration();
config.hostName = HUB_IP_ADDRESS;
config.socketTimeout = 5;
try {
responseJson = socket.sendMessage(requestJson);
NeoHubSocket socket = new NeoHubSocket(config);
String responseJson = socket.sendMessage(requestJson);
socket.close();
return responseJson;
} catch (Exception e) {
assertTrue(false);
}
return responseJson;
return "";
}
/*
/**
* Test the communications
*/
@SuppressWarnings("null")
@Test
public void testCommunications() {
/*
* tests the actual communication with a real physical device on 'hubIpAddress'
* 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;
}

View File

@@ -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();
}
}
}