2 * Copyright (c) 2010-2022 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;
40 * JUnit for testing JSON parsing.
42 * @author Andrew Fiddian-Green - Initial contribution
45 public class NeoHubJsonTests {
48 * to actually run tests on a physical device you must have a hub physically available, and its IP address must be
49 * correctly configured in the "hubIPAddress" string constant e.g. "192.168.1.123"
50 * note: only run the test if such a device is actually available
52 private static final String HUB_IP_ADDRESS = "192.168.1.xxx";
54 public static final Pattern VALID_IP_V4_ADDRESS = Pattern
55 .compile("\\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}\\b");
58 * Load the test JSON payload string from a file
60 private String load(String fileName) {
61 try (FileReader file = new FileReader(String.format("src/test/resources/%s.json", fileName));
62 BufferedReader reader = new BufferedReader(file)) {
63 StringBuilder builder = new StringBuilder();
65 while ((line = reader.readLine()) != null) {
66 builder.append(line).append("\n");
68 return builder.toString();
69 } catch (IOException e) {
76 * Test an INFO JSON response string as produced by older firmware versions
79 public void testInfoJsonOld() {
80 // load INFO JSON response string in old JSON format
81 NeoHubAbstractDeviceData infoResponse = NeoHubInfoResponse.createDeviceData(load("info_old"));
82 assertNotNull(infoResponse);
85 AbstractRecord device = infoResponse.getDeviceRecord("Aardvark");
88 // existing type 12 thermostat device
89 device = infoResponse.getDeviceRecord("Dining Room");
90 assertNotNull(device);
91 assertEquals("Dining Room", device.getDeviceName());
92 assertEquals(new BigDecimal("22.0"), device.getTargetTemperature());
93 assertEquals(new BigDecimal("22.2"), device.getActualTemperature());
94 assertEquals(new BigDecimal("23"), device.getFloorTemperature());
95 assertTrue(device instanceof InfoRecord);
96 assertEquals(12, ((InfoRecord) device).getDeviceType());
97 assertFalse(device.isStandby());
98 assertFalse(device.isHeating());
99 assertFalse(device.isPreHeating());
100 assertFalse(device.isTimerOn());
101 assertFalse(device.offline());
102 assertFalse(device.stateManual());
103 assertTrue(device.stateAuto());
104 assertFalse(device.isWindowOpen());
105 assertFalse(device.isBatteryLow());
107 // existing type 6 plug device (MANUAL OFF)
108 device = infoResponse.getDeviceRecord("Plug South");
109 assertNotNull(device);
110 assertEquals("Plug South", device.getDeviceName());
111 assertTrue(device instanceof InfoRecord);
112 assertEquals(6, ((InfoRecord) device).getDeviceType());
113 assertFalse(device.isTimerOn());
114 assertTrue(device.stateManual());
116 // existing type 6 plug device (MANUAL ON)
117 device = infoResponse.getDeviceRecord("Plug North");
118 assertNotNull(device);
119 assertEquals("Plug North", device.getDeviceName());
120 assertTrue(device instanceof InfoRecord);
121 assertEquals(6, ((InfoRecord) device).getDeviceType());
122 assertTrue(device.isTimerOn());
123 assertTrue(device.stateManual());
125 // existing type 6 plug device (AUTO OFF)
126 device = infoResponse.getDeviceRecord("Watering System");
127 assertNotNull(device);
128 assertEquals("Watering System", device.getDeviceName());
129 assertTrue(device instanceof InfoRecord);
130 assertEquals(6, ((InfoRecord) device).getDeviceType());
131 assertFalse(device.isTimerOn());
132 assertFalse(device.stateManual());
136 * Test an INFO JSON response string as produced by newer firmware versions
139 public void testInfoJsonNew() {
140 // load INFO JSON response string in new JSON format
141 NeoHubAbstractDeviceData infoResponse = NeoHubInfoResponse.createDeviceData(load("info_new"));
142 assertNotNull(infoResponse);
144 // existing device (new JSON format)
145 AbstractRecord device = infoResponse.getDeviceRecord("Dining Room");
146 assertNotNull(device);
147 assertEquals("Dining Room", device.getDeviceName());
148 assertFalse(device.offline());
149 assertFalse(device.isWindowOpen());
151 // existing repeater device
152 device = infoResponse.getDeviceRecord("repeaternode54473");
153 assertNotNull(device);
154 assertEquals("repeaternode54473", device.getDeviceName());
155 assertEquals(new BigDecimal("127"), device.getFloorTemperature());
156 assertEquals(new BigDecimal("255.255"), device.getActualTemperature());
160 * Test for a READ_DCB JSON string that has valid CORF C response
163 public void testReadDcbJson() {
164 // load READ_DCB JSON response string with valid CORF C response
165 NeoHubReadDcbResponse dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_celsius"));
166 assertNotNull(dcbResponse);
167 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
168 assertEquals("2134", dcbResponse.getFirmwareVersion());
170 // load READ_DCB JSON response string with valid CORF F response
171 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_fahrenheit"));
172 assertNotNull(dcbResponse);
173 assertEquals(ImperialUnits.FAHRENHEIT, dcbResponse.getTemperatureUnit());
175 // load READ_DCB JSON response string with missing CORF element
176 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_corf_missing"));
177 assertNotNull(dcbResponse);
178 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
180 // load READ_DCB JSON response string where CORF element is an empty string
181 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("dcb_corf_empty"));
182 assertNotNull(dcbResponse);
183 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
187 * Test an INFO JSON string that has a door contact and a temperature sensor
190 public void testInfoJsonWithSensors() {
192 * load an INFO JSON response string that has a closed door contact and a
195 // save("info_sensors_closed", NEOHUB_JSON_TEST_STRING_INFO_SENSORS_CLOSED);
196 NeoHubAbstractDeviceData infoResponse = NeoHubInfoResponse.createDeviceData(load("info_sensors_closed"));
197 assertNotNull(infoResponse);
199 // existing contact device type 5 (CLOSED)
200 AbstractRecord device = infoResponse.getDeviceRecord("Back Door");
201 assertNotNull(device);
202 assertEquals("Back Door", device.getDeviceName());
203 assertTrue(device instanceof InfoRecord);
204 assertEquals(5, ((InfoRecord) device).getDeviceType());
205 assertFalse(device.isWindowOpen());
206 assertFalse(device.isBatteryLow());
208 // existing temperature sensor type 14
209 device = infoResponse.getDeviceRecord("Master Bedroom");
210 assertNotNull(device);
211 assertEquals("Master Bedroom", device.getDeviceName());
212 assertTrue(device instanceof InfoRecord);
213 assertEquals(14, ((InfoRecord) device).getDeviceType());
214 assertEquals(new BigDecimal("19.5"), device.getActualTemperature());
216 // existing thermostat type 1
217 device = infoResponse.getDeviceRecord("Living Room Floor");
218 assertNotNull(device);
219 assertEquals("Living Room Floor", device.getDeviceName());
220 assertTrue(device instanceof InfoRecord);
221 assertEquals(1, ((InfoRecord) device).getDeviceType());
222 assertEquals(new BigDecimal("19.8"), device.getActualTemperature());
224 // load an INFO JSON response string that has an open door contact
225 // save("info_sensors_open", NEOHUB_JSON_TEST_STRING_INFO_SENSORS_OPEN);
226 infoResponse = NeoHubInfoResponse.createDeviceData(load("info_sensors_open"));
227 assertNotNull(infoResponse);
229 // existing contact device type 5 (OPEN)
230 device = infoResponse.getDeviceRecord("Back Door");
231 assertNotNull(device);
232 assertEquals("Back Door", device.getDeviceName());
233 assertTrue(device instanceof InfoRecord);
234 assertEquals(5, ((InfoRecord) device).getDeviceType());
235 assertTrue(device.isWindowOpen());
236 assertTrue(device.isBatteryLow());
240 * From NeoHub rev2.6 onwards the READ_DCB command is "deprecated" so we can
241 * also test the replacement GET_SYSTEM command (valid CORF response)
244 public void testGetSystemJson() {
245 // load GET_SYSTEM JSON response string
246 NeoHubReadDcbResponse dcbResponse;
247 dcbResponse = NeoHubReadDcbResponse.createSystemData(load("system"));
248 assertNotNull(dcbResponse);
249 assertEquals(SIUnits.CELSIUS, dcbResponse.getTemperatureUnit());
250 assertEquals("2134", dcbResponse.getFirmwareVersion());
254 * From NeoHub rev2.6 onwards the INFO command is "deprecated" so we must test
255 * the replacement GET_LIVE_DATA command
258 public void testGetLiveDataJson() {
259 // load GET_LIVE_DATA JSON response string
260 NeoHubLiveDeviceData liveDataResponse = NeoHubLiveDeviceData.createDeviceData(load("live_data"));
261 assertNotNull(liveDataResponse);
263 // test the time stamps
264 assertEquals(1588494785, liveDataResponse.getTimestampEngineers());
265 assertEquals(0, liveDataResponse.getTimestampSystem());
268 AbstractRecord device = liveDataResponse.getDeviceRecord("Aardvark");
271 // test an existing thermostat device
272 device = liveDataResponse.getDeviceRecord("Dining Room");
273 assertNotNull(device);
274 assertEquals("Dining Room", device.getDeviceName());
275 assertEquals(new BigDecimal("22.0"), device.getTargetTemperature());
276 assertEquals(new BigDecimal("22.2"), device.getActualTemperature());
277 assertEquals(new BigDecimal("20.50"), device.getFloorTemperature());
278 assertFalse(device.isStandby());
279 assertFalse(device.isHeating());
280 assertFalse(device.isPreHeating());
281 assertFalse(device.isTimerOn());
282 assertFalse(device.offline());
283 assertFalse(device.stateManual());
284 assertTrue(device.stateAuto());
285 assertFalse(device.isWindowOpen());
286 assertFalse(device.isBatteryLow());
288 // test a plug device (MANUAL OFF)
289 device = liveDataResponse.getDeviceRecord("Living Room South");
290 assertNotNull(device);
291 assertEquals("Living Room South", device.getDeviceName());
292 assertFalse(device.isTimerOn());
293 assertTrue(device.stateManual());
295 // test a plug device (MANUAL ON)
296 device = liveDataResponse.getDeviceRecord("Living Room North");
297 assertNotNull(device);
298 assertEquals("Living Room North", device.getDeviceName());
299 assertTrue(device.isTimerOn());
300 assertTrue(device.stateManual());
302 // test a plug device (AUTO OFF)
303 device = liveDataResponse.getDeviceRecord("Green Wall Watering");
304 assertNotNull(device);
305 assertEquals("Green Wall Watering", device.getDeviceName());
306 assertFalse(device.isTimerOn());
307 assertFalse(device.stateManual());
309 // test a device that is offline
310 device = liveDataResponse.getDeviceRecord("Shower Room");
311 assertNotNull(device);
312 assertEquals("Shower Room", device.getDeviceName());
313 assertTrue(device.offline());
315 // test a device with a low battery
316 device = liveDataResponse.getDeviceRecord("Conservatory");
317 assertNotNull(device);
318 assertEquals("Conservatory", device.getDeviceName());
319 assertTrue(device.isBatteryLow());
321 // test a device with an open window alarm
322 device = liveDataResponse.getDeviceRecord("Door Contact");
323 assertNotNull(device);
324 assertEquals("Door Contact", device.getDeviceName());
325 assertTrue(device.isWindowOpen());
327 // test a wireless temperature sensor
328 device = liveDataResponse.getDeviceRecord("Room Sensor");
329 assertNotNull(device);
330 assertEquals("Room Sensor", device.getDeviceName());
331 assertEquals(new BigDecimal("21.5"), device.getActualTemperature());
333 // test a repeater node
334 device = liveDataResponse.getDeviceRecord("repeaternode54473");
335 assertNotNull(device);
336 assertEquals("repeaternode54473", device.getDeviceName());
337 assertTrue(MATCHER_HEATMISER_REPEATER.matcher(device.getDeviceName()).matches());
341 * From NeoHub rev2.6 onwards the INFO command is "deprecated" and the DEVICE_ID
342 * element is not returned in the GET_LIVE_DATA call so we must test the
343 * replacement GET_ENGINEERS command
346 public void testGetEngineersJson() {
347 // load GET_ENGINEERS JSON response string
348 NeoHubGetEngineersData engResponse = NeoHubGetEngineersData.createEngineersData(load("engineers"));
349 assertNotNull(engResponse);
351 // test device ID (type 12 thermostat device)
352 assertEquals(12, engResponse.getDeviceType("Dining Room"));
354 // test device ID (type 6 plug device)
355 assertEquals(6, engResponse.getDeviceType("Living Room South"));
359 * send JSON request to the socket and retrieve JSON response
361 private String testCommunicationInner(String requestJson) {
362 NeoHubConfiguration config = new NeoHubConfiguration();
363 config.hostName = HUB_IP_ADDRESS;
364 config.socketTimeout = 5;
366 NeoHubSocket socket = new NeoHubSocket(config);
367 String responseJson = socket.sendMessage(requestJson);
370 } catch (Exception e) {
377 * Test the communications
380 public void testCommunications() {
382 * tests the actual communication with a real physical device on 'hubIpAddress'
383 * note: only run the test if such a device is actually available
385 if (!VALID_IP_V4_ADDRESS.matcher(HUB_IP_ADDRESS).matches()) {
389 String responseJson = testCommunicationInner(CMD_CODE_INFO);
390 assertFalse(responseJson.isEmpty());
392 responseJson = testCommunicationInner(CMD_CODE_READ_DCB);
393 assertFalse(responseJson.isEmpty());
395 NeoHubReadDcbResponse dcbResponse = NeoHubReadDcbResponse.createSystemData(responseJson);
396 assertNotNull(dcbResponse);
398 long timeStamp = dcbResponse.timeStamp;
399 assertEquals(Instant.now().getEpochSecond(), timeStamp, 1);
401 responseJson = testCommunicationInner(CMD_CODE_GET_LIVE_DATA);
402 assertFalse(responseJson.isEmpty());
404 NeoHubLiveDeviceData liveDataResponse = NeoHubLiveDeviceData.createDeviceData(responseJson);
405 assertNotNull(liveDataResponse);
407 assertTrue(timeStamp > liveDataResponse.getTimestampEngineers());
408 assertTrue(timeStamp > liveDataResponse.getTimestampSystem());
410 responseJson = testCommunicationInner(CMD_CODE_GET_ENGINEERS);
411 assertFalse(responseJson.isEmpty());
413 responseJson = testCommunicationInner(CMD_CODE_GET_SYSTEM);
414 assertFalse(responseJson.isEmpty());
416 responseJson = testCommunicationInner(String.format(CMD_CODE_TEMP, "20", "Hallway"));
417 assertFalse(responseJson.isEmpty());