2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.neohub.test;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.openhab.binding.neohub.internal.NeoHubBindingConstants.*;
18 import java.io.BufferedReader;
19 import java.io.FileReader;
20 import java.io.IOException;
21 import java.math.BigDecimal;
22 import java.time.Instant;
23 import java.util.regex.Pattern;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.junit.jupiter.api.Test;
27 import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData;
28 import org.openhab.binding.neohub.internal.NeoHubAbstractDeviceData.AbstractRecord;
29 import org.openhab.binding.neohub.internal.NeoHubConfiguration;
30 import org.openhab.binding.neohub.internal.NeoHubGetEngineersData;
31 import org.openhab.binding.neohub.internal.NeoHubInfoResponse;
32 import org.openhab.binding.neohub.internal.NeoHubInfoResponse.InfoRecord;
33 import org.openhab.binding.neohub.internal.NeoHubLiveDeviceData;
34 import org.openhab.binding.neohub.internal.NeoHubReadDcbResponse;
35 import org.openhab.binding.neohub.internal.NeoHubSocket;
36 import org.openhab.core.library.unit.ImperialUnits;
37 import org.openhab.core.library.unit.SIUnits;
39 import com.google.gson.JsonElement;
40 import com.google.gson.JsonObject;
41 import com.google.gson.JsonParser;
44 * JUnit for testing JSON parsing.
46 * @author Andrew Fiddian-Green - Initial contribution
49 public class NeoHubJsonTests {
52 * to actually run tests on a physical device you must have a hub physically available, and its IP address must be
53 * correctly configured in the "hubIPAddress" string constant e.g. "192.168.1.123"
54 * note: only run the test if such a device is actually available
56 private static final String HUB_IP_ADDRESS = "192.168.1.xxx";
58 public static final Pattern VALID_IP_V4_ADDRESS = Pattern
59 .compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
62 * Load the test JSON payload string from a file
64 private String load(String fileName) {
65 try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
66 BufferedReader reader = new BufferedReader(file)) {
67 StringBuilder builder = new StringBuilder();
69 while ((line = reader.readLine()) != null) {
70 builder.append(line).append("\n");
72 return builder.toString();
73 } catch (IOException e) {
80 * Test an INFO JSON response string as produced by older firmware versions
83 public void testInfoJsonOld() {
84 // load INFO JSON response string in old JSON format
85 NeoHubAbstractDeviceData infoResponse = NeoHubInfoResponse.createDeviceData(load("info_old"));
86 assertNotNull(infoResponse);
89 AbstractRecord device = infoResponse.getDeviceRecord("Aardvark");
92 // existing type 12 thermostat device
93 device = infoResponse.getDeviceRecord("Dining Room");
94 assertNotNull(device);
95 assertEquals("Dining Room", device.getDeviceName());
96 assertEquals(new BigDecimal("22.0"), device.getTargetTemperature());
97 assertEquals(new BigDecimal("22.2"), device.getActualTemperature());
98 assertEquals(new BigDecimal("23"), device.getFloorTemperature());
99 assertTrue(device instanceof InfoRecord);
100 assertEquals(12, ((InfoRecord) device).getDeviceType());
101 assertFalse(device.isStandby());
102 assertFalse(device.isHeating());
103 assertFalse(device.isPreHeating());
104 assertFalse(device.isTimerOn());
105 assertFalse(device.offline());
106 assertFalse(device.stateManual());
107 assertTrue(device.stateAuto());
108 assertFalse(device.isWindowOpen());
109 assertFalse(device.isBatteryLow());
111 // existing type 6 plug device (MANUAL OFF)
112 device = infoResponse.getDeviceRecord("Plug South");
113 assertNotNull(device);
114 assertEquals("Plug South", device.getDeviceName());
115 assertTrue(device instanceof InfoRecord);
116 assertEquals(6, ((InfoRecord) device).getDeviceType());
117 assertFalse(device.isTimerOn());
118 assertTrue(device.stateManual());
120 // existing type 6 plug device (MANUAL ON)
121 device = infoResponse.getDeviceRecord("Plug North");
122 assertNotNull(device);
123 assertEquals("Plug North", device.getDeviceName());
124 assertTrue(device instanceof InfoRecord);
125 assertEquals(6, ((InfoRecord) device).getDeviceType());
126 assertTrue(device.isTimerOn());
127 assertTrue(device.stateManual());
129 // existing type 6 plug device (AUTO OFF)
130 device = infoResponse.getDeviceRecord("Watering System");
131 assertNotNull(device);
132 assertEquals("Watering System", device.getDeviceName());
133 assertTrue(device instanceof InfoRecord);
134 assertEquals(6, ((InfoRecord) device).getDeviceType());
135 assertFalse(device.isTimerOn());
136 assertFalse(device.stateManual());
140 * Test an INFO JSON response string as produced by newer firmware versions
143 public void testInfoJsonNew() {
144 // load INFO JSON response string in new JSON format
145 NeoHubAbstractDeviceData infoResponse = NeoHubInfoResponse.createDeviceData(load("info_new"));
146 assertNotNull(infoResponse);
148 // existing device (new JSON format)
149 AbstractRecord device = infoResponse.getDeviceRecord("Dining Room");
150 assertNotNull(device);
151 assertEquals("Dining Room", device.getDeviceName());
152 assertFalse(device.offline());
153 assertFalse(device.isWindowOpen());
155 // existing repeater device
156 device = infoResponse.getDeviceRecord("repeaternode54473");
157 assertNotNull(device);
158 assertEquals("repeaternode54473", device.getDeviceName());
159 assertEquals(new BigDecimal("127"), device.getFloorTemperature());
160 assertEquals(new BigDecimal("255.255"), device.getActualTemperature());
164 * Test for a READ_DCB JSON string that has valid CORF C response
167 public void testReadDcbJson() {
168 // load READ_DCB JSON response string with valid CORF C response
169 NeoHubReadDcbResponse dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_celsius"));
170 assertNotNull(dcbResponse);
171 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
172 assertEquals("2134", dcbResponse.getFirmwareVersion());
174 // load READ_DCB JSON response string with valid CORF F response
175 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_fahrenheit"));
176 assertNotNull(dcbResponse);
177 assertEquals(ImperialUnits.FAHRENHEIT, dcbResponse.getTemperatureUnit());
179 // load READ_DCB JSON response string with missing CORF element
180 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_corf_missing"));
181 assertNotNull(dcbResponse);
182 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
184 // load READ_DCB JSON response string where CORF element is an empty string
185 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_corf_empty"));
186 assertNotNull(dcbResponse);
187 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
191 * Test an INFO JSON string that has a door contact and a temperature sensor
194 public void testInfoJsonWithSensors() {
196 * load an INFO JSON response string that has a closed door contact and a
199 // save("info_sensors_closed", NEOHUB_JSON_TEST_STRING_INFO_SENSORS_CLOSED);
200 NeoHubAbstractDeviceData infoResponse = NeoHubInfoResponse.createDeviceData(load("info_sensors_closed"));
201 assertNotNull(infoResponse);
203 // existing contact device type 5 (CLOSED)
204 AbstractRecord device = infoResponse.getDeviceRecord("Back Door");
205 assertNotNull(device);
206 assertEquals("Back Door", device.getDeviceName());
207 assertTrue(device instanceof InfoRecord);
208 assertEquals(5, ((InfoRecord) device).getDeviceType());
209 assertFalse(device.isWindowOpen());
210 assertFalse(device.isBatteryLow());
212 // existing temperature sensor type 14
213 device = infoResponse.getDeviceRecord("Master Bedroom");
214 assertNotNull(device);
215 assertEquals("Master Bedroom", device.getDeviceName());
216 assertTrue(device instanceof InfoRecord);
217 assertEquals(14, ((InfoRecord) device).getDeviceType());
218 assertEquals(new BigDecimal("19.5"), device.getActualTemperature());
220 // existing thermostat type 1
221 device = infoResponse.getDeviceRecord("Living Room Floor");
222 assertNotNull(device);
223 assertEquals("Living Room Floor", device.getDeviceName());
224 assertTrue(device instanceof InfoRecord);
225 assertEquals(1, ((InfoRecord) device).getDeviceType());
226 assertEquals(new BigDecimal("19.8"), device.getActualTemperature());
228 // load an INFO JSON response string that has an open door contact
229 // save("info_sensors_open", NEOHUB_JSON_TEST_STRING_INFO_SENSORS_OPEN);
230 infoResponse = NeoHubInfoResponse.createDeviceData(load("info_sensors_open"));
231 assertNotNull(infoResponse);
233 // existing contact device type 5 (OPEN)
234 device = infoResponse.getDeviceRecord("Back Door");
235 assertNotNull(device);
236 assertEquals("Back Door", device.getDeviceName());
237 assertTrue(device instanceof InfoRecord);
238 assertEquals(5, ((InfoRecord) device).getDeviceType());
239 assertTrue(device.isWindowOpen());
240 assertTrue(device.isBatteryLow());
244 * From NeoHub rev2.6 onwards the READ_DCB command is "deprecated" so we can
245 * also test the replacement GET_SYSTEM command (valid CORF response)
248 public void testGetSystemJson() {
249 // load GET_SYSTEM JSON response string
250 NeoHubReadDcbResponse dcbResponse;
251 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("system"));
252 assertNotNull(dcbResponse);
253 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
254 assertEquals("2134", dcbResponse.getFirmwareVersion());
258 * From NeoHub rev2.6 onwards the INFO command is "deprecated" so we must test
259 * the replacement GET_LIVE_DATA command
262 public void testGetLiveDataJson() {
263 // load GET_LIVE_DATA JSON response string
264 NeoHubLiveDeviceData liveDataResponse = NeoHubLiveDeviceData.createDeviceData(load("live_data"));
265 assertNotNull(liveDataResponse);
267 // test the time stamps
268 assertEquals(1588494785, liveDataResponse.getTimestampEngineers());
269 assertEquals(0, liveDataResponse.getTimestampSystem());
272 AbstractRecord device = liveDataResponse.getDeviceRecord("Aardvark");
275 // test an existing thermostat device
276 device = liveDataResponse.getDeviceRecord("Dining Room");
277 assertNotNull(device);
278 assertEquals("Dining Room", device.getDeviceName());
279 assertEquals(new BigDecimal("22.0"), device.getTargetTemperature());
280 assertEquals(new BigDecimal("22.2"), device.getActualTemperature());
281 assertEquals(new BigDecimal("20.50"), device.getFloorTemperature());
282 assertFalse(device.isStandby());
283 assertFalse(device.isHeating());
284 assertFalse(device.isPreHeating());
285 assertFalse(device.isTimerOn());
286 assertFalse(device.offline());
287 assertFalse(device.stateManual());
288 assertTrue(device.stateAuto());
289 assertFalse(device.isWindowOpen());
290 assertFalse(device.isBatteryLow());
292 // test a plug device (MANUAL OFF)
293 device = liveDataResponse.getDeviceRecord("Living Room South");
294 assertNotNull(device);
295 assertEquals("Living Room South", device.getDeviceName());
296 assertFalse(device.isTimerOn());
297 assertTrue(device.stateManual());
299 // test a plug device (MANUAL ON)
300 device = liveDataResponse.getDeviceRecord("Living Room North");
301 assertNotNull(device);
302 assertEquals("Living Room North", device.getDeviceName());
303 assertTrue(device.isTimerOn());
304 assertTrue(device.stateManual());
306 // test a plug device (AUTO OFF)
307 device = liveDataResponse.getDeviceRecord("Green Wall Watering");
308 assertNotNull(device);
309 assertEquals("Green Wall Watering", device.getDeviceName());
310 assertFalse(device.isTimerOn());
311 assertFalse(device.stateManual());
313 // test a device that is offline
314 device = liveDataResponse.getDeviceRecord("Shower Room");
315 assertNotNull(device);
316 assertEquals("Shower Room", device.getDeviceName());
317 assertTrue(device.offline());
319 // test a device with a low battery
320 device = liveDataResponse.getDeviceRecord("Conservatory");
321 assertNotNull(device);
322 assertEquals("Conservatory", device.getDeviceName());
323 assertTrue(device.isBatteryLow());
325 // test a device with an open window alarm
326 device = liveDataResponse.getDeviceRecord("Door Contact");
327 assertNotNull(device);
328 assertEquals("Door Contact", device.getDeviceName());
329 assertTrue(device.isWindowOpen());
331 // test a wireless temperature sensor
332 device = liveDataResponse.getDeviceRecord("Room Sensor");
333 assertNotNull(device);
334 assertEquals("Room Sensor", device.getDeviceName());
335 assertEquals(new BigDecimal("21.5"), device.getActualTemperature());
337 // test a repeater node
338 device = liveDataResponse.getDeviceRecord("repeaternode54473");
339 assertNotNull(device);
340 assertEquals("repeaternode54473", device.getDeviceName());
341 assertTrue(MATCHER_HEATMISER_REPEATER.matcher(device.getDeviceName()).matches());
345 * From NeoHub rev2.6 onwards the INFO command is "deprecated" and the DEVICE_ID
346 * element is not returned in the GET_LIVE_DATA call so we must test the
347 * replacement GET_ENGINEERS command
350 public void testGetEngineersJson() {
351 // load GET_ENGINEERS JSON response string
352 NeoHubGetEngineersData engResponse = NeoHubGetEngineersData.createEngineersData(load("engineers"));
353 assertNotNull(engResponse);
355 // test device ID (type 12 thermostat device)
356 assertEquals(12, engResponse.getDeviceType("Dining Room"));
358 // test device ID (type 6 plug device)
359 assertEquals(6, engResponse.getDeviceType("Living Room South"));
363 * send JSON request to the socket and retrieve JSON response
365 private String testCommunicationInner(String requestJson) {
366 NeoHubConfiguration config = new NeoHubConfiguration();
367 config.hostName = HUB_IP_ADDRESS;
368 config.socketTimeout = 5;
370 NeoHubSocket socket = new NeoHubSocket(config, "test");
371 String responseJson = socket.sendMessage(requestJson);
374 } catch (Exception e) {
381 * Test the communications
384 public void testCommunications() {
386 * tests the actual communication with a real physical device on 'hubIpAddress'
387 * note: only run the test if such a device is actually available
389 if (!VALID_IP_V4_ADDRESS.matcher(HUB_IP_ADDRESS).matches()) {
393 String responseJson = testCommunicationInner(CMD_CODE_INFO);
394 assertFalse(responseJson.isEmpty());
396 responseJson = testCommunicationInner(CMD_CODE_READ_DCB);
397 assertFalse(responseJson.isEmpty());
399 NeoHubReadDcbResponse dcbResponse = NeoHubReadDcbResponse.createSystemData(responseJson);
400 assertNotNull(dcbResponse);
402 long timeStamp = dcbResponse.timeStamp;
403 assertEquals(Instant.now().getEpochSecond(), timeStamp, 1);
405 responseJson = testCommunicationInner(CMD_CODE_GET_LIVE_DATA);
406 assertFalse(responseJson.isEmpty());
408 NeoHubLiveDeviceData liveDataResponse = NeoHubLiveDeviceData.createDeviceData(responseJson);
409 assertNotNull(liveDataResponse);
411 assertTrue(timeStamp > liveDataResponse.getTimestampEngineers());
412 assertTrue(timeStamp > liveDataResponse.getTimestampSystem());
414 responseJson = testCommunicationInner(CMD_CODE_GET_ENGINEERS);
415 assertFalse(responseJson.isEmpty());
417 responseJson = testCommunicationInner(CMD_CODE_GET_SYSTEM);
418 assertFalse(responseJson.isEmpty());
420 responseJson = testCommunicationInner(String.format(CMD_CODE_TEMP, "20", "Hallway"));
421 assertFalse(responseJson.isEmpty());
425 public void testJsonValidation() {
426 JsonElement jsonElement;
428 jsonElement = JsonParser.parseString("");
429 assertFalse(jsonElement.isJsonObject());
431 jsonElement = JsonParser.parseString("xx");
432 assertFalse(jsonElement.isJsonObject());
434 jsonElement = JsonParser.parseString("{}");
435 assertTrue(jsonElement.isJsonObject());
436 assertEquals(0, ((JsonObject) jsonElement).keySet().size());
438 jsonElement = JsonParser.parseString(load("dcb_celsius"));
439 assertTrue(jsonElement.isJsonObject());
440 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());
442 jsonElement = JsonParser.parseString(load("live_data"));
443 assertTrue(jsonElement.isJsonObject());
444 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());
446 jsonElement = JsonParser.parseString(load("engineers"));
447 assertTrue(jsonElement.isJsonObject());
448 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());
450 jsonElement = JsonParser.parseString(load("info_new"));
451 assertTrue(jsonElement.isJsonObject());
452 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());
454 jsonElement = JsonParser.parseString(load("info_old"));
455 assertTrue(jsonElement.isJsonObject());
456 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());
458 jsonElement = JsonParser.parseString(load("system"));
459 assertTrue(jsonElement.isJsonObject());
460 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());
462 jsonElement = JsonParser.parseString(load("info_sensors_closed"));
463 assertTrue(jsonElement.isJsonObject());
464 assertTrue(!((JsonObject) jsonElement).keySet().isEmpty());