]> git.basschouten.com Git - openhab-addons.git/blob
c016023df7ff66eabb4cb70f394c70bdd6dd7b71
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.modbus.tests;
14
15 import static org.hamcrest.CoreMatchers.equalTo;
16 import static org.hamcrest.core.Is.is;
17 import static org.junit.Assert.*;
18 import static org.mockito.ArgumentMatchers.*;
19 import static org.mockito.Mockito.*;
20 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
21
22 import java.lang.reflect.Field;
23 import java.util.concurrent.atomic.AtomicReference;
24
25 import org.openhab.core.config.core.Configuration;
26 import org.openhab.core.thing.Bridge;
27 import org.openhab.core.thing.Thing;
28 import org.openhab.core.thing.ThingStatus;
29 import org.openhab.core.thing.ThingStatusDetail;
30 import org.openhab.core.thing.ThingStatusInfo;
31 import org.openhab.core.thing.ThingUID;
32 import org.openhab.core.thing.binding.ThingHandlerCallback;
33 import org.openhab.core.thing.binding.builder.BridgeBuilder;
34 import org.hamcrest.Description;
35 import org.hamcrest.TypeSafeMatcher;
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 import org.mockito.ArgumentCaptor;
41 import org.mockito.Mock;
42 import org.mockito.Mockito;
43 import org.mockito.junit.MockitoJUnitRunner;
44 import org.openhab.binding.modbus.handler.ModbusPollerThingHandler;
45 import org.openhab.binding.modbus.internal.ModbusBindingConstantsInternal;
46 import org.openhab.binding.modbus.internal.handler.ModbusDataThingHandler;
47 import org.openhab.io.transport.modbus.AsyncModbusFailure;
48 import org.openhab.io.transport.modbus.AsyncModbusReadResult;
49 import org.openhab.io.transport.modbus.BitArray;
50 import org.openhab.io.transport.modbus.ModbusConstants;
51 import org.openhab.io.transport.modbus.ModbusFailureCallback;
52 import org.openhab.io.transport.modbus.ModbusReadCallback;
53 import org.openhab.io.transport.modbus.ModbusReadFunctionCode;
54 import org.openhab.io.transport.modbus.ModbusReadRequestBlueprint;
55 import org.openhab.io.transport.modbus.ModbusRegisterArray;
56 import org.openhab.io.transport.modbus.PollTask;
57 import org.openhab.io.transport.modbus.endpoint.ModbusSlaveEndpoint;
58 import org.openhab.io.transport.modbus.endpoint.ModbusTCPSlaveEndpoint;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 /**
63  * @author Sami Salonen - Initial contribution
64  */
65 @RunWith(MockitoJUnitRunner.class)
66 public class ModbusPollerThingHandlerTest extends AbstractModbusOSGiTest {
67
68     private static final String HOST = "thisishost";
69     private static final int PORT = 44;
70
71     private final Logger logger = LoggerFactory.getLogger(ModbusPollerThingHandlerTest.class);
72
73     private Bridge endpoint;
74     private Bridge poller;
75
76     @Mock
77     private ThingHandlerCallback thingCallback;
78
79     public static BridgeBuilder createTcpThingBuilder(String id) {
80         return BridgeBuilder
81                 .create(ModbusBindingConstantsInternal.THING_TYPE_MODBUS_TCP,
82                         new ThingUID(ModbusBindingConstantsInternal.THING_TYPE_MODBUS_TCP, id))
83                 .withLabel("label for " + id);
84     }
85
86     public static BridgeBuilder createPollerThingBuilder(String id) {
87         return BridgeBuilder
88                 .create(ModbusBindingConstantsInternal.THING_TYPE_MODBUS_POLLER,
89                         new ThingUID(ModbusBindingConstantsInternal.THING_TYPE_MODBUS_POLLER, id))
90                 .withLabel("label for " + id);
91     }
92
93     /**
94      * Verify that basic poller <-> endpoint interaction has taken place (on poller init)
95      */
96     private void verifyEndpointBasicInitInteraction() {
97         verify(mockedModbusManager).newModbusCommunicationInterface(any(), any());
98     }
99
100     public ModbusReadCallback getPollerCallback(ModbusPollerThingHandler handler)
101             throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
102         Field callbackField = ModbusPollerThingHandler.class.getDeclaredField("callbackDelegator");
103         callbackField.setAccessible(true);
104         return (ModbusReadCallback) callbackField.get(handler);
105     }
106
107     public ModbusFailureCallback<ModbusReadRequestBlueprint> getPollerFailureCallback(ModbusPollerThingHandler handler)
108             throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
109         Field callbackField = ModbusPollerThingHandler.class.getDeclaredField("callbackDelegator");
110         callbackField.setAccessible(true);
111         return (ModbusFailureCallback<ModbusReadRequestBlueprint>) callbackField.get(handler);
112     }
113
114     /**
115      * Before each test, setup TCP endpoint thing, configure mocked item registry
116      */
117     @Before
118     public void setUp() {
119         mockCommsToModbusManager();
120         Configuration tcpConfig = new Configuration();
121         tcpConfig.put("host", HOST);
122         tcpConfig.put("port", PORT);
123         tcpConfig.put("id", 9);
124         endpoint = createTcpThingBuilder("tcpendpoint").withConfiguration(tcpConfig).build();
125         addThing(endpoint);
126
127         assertThat(endpoint.getStatus(), is(equalTo(ThingStatus.ONLINE)));
128     }
129
130     @After
131     public void tearDown() {
132         if (endpoint != null) {
133             thingProvider.remove(endpoint.getUID());
134         }
135         if (poller != null) {
136             thingProvider.remove(poller.getUID());
137         }
138     }
139
140     @Test
141     public void testInitializeNonPolling()
142             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
143         Configuration pollerConfig = new Configuration();
144         pollerConfig.put("refresh", 0L); // 0 -> non polling
145         pollerConfig.put("start", 5);
146         pollerConfig.put("length", 9);
147         pollerConfig.put("type", ModbusBindingConstantsInternal.READ_TYPE_HOLDING_REGISTER);
148         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
149                 .build();
150
151         logger.info("Poller created, registering to registry...");
152         addThing(poller);
153         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
154         logger.info("Poller registered");
155
156         verifyEndpointBasicInitInteraction();
157         // polling is _not_ setup
158         verifyNoMoreInteractions(mockedModbusManager);
159     }
160
161     private void testPollerLengthCheck(String type, int length, boolean expectedOnline) {
162         Configuration pollerConfig = new Configuration();
163         pollerConfig.put("refresh", 0L);
164         pollerConfig.put("start", 5);
165         pollerConfig.put("length", length);
166         pollerConfig.put("type", type);
167         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
168                 .build();
169
170         addThing(poller);
171         assertThat(poller.getStatus(), is(equalTo(expectedOnline ? ThingStatus.ONLINE : ThingStatus.OFFLINE)));
172         if (!expectedOnline) {
173             assertThat(poller.getStatusInfo().getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR)));
174         }
175
176         verifyEndpointBasicInitInteraction();
177         verifyNoMoreInteractions(mockedModbusManager);
178     }
179
180     @Test
181     public void testPollerWithMaxRegisters()
182             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
183         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_HOLDING_REGISTER,
184                 ModbusConstants.MAX_REGISTERS_READ_COUNT, true);
185     }
186
187     @Test
188     public void testPollerLengthOutOfBoundsWithRegisters()
189             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
190         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_HOLDING_REGISTER,
191                 ModbusConstants.MAX_REGISTERS_READ_COUNT + 1, false);
192     }
193
194     @Test
195     public void testPollerWithMaxInputRegisters()
196             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
197         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_INPUT_REGISTER,
198                 ModbusConstants.MAX_REGISTERS_READ_COUNT, true);
199     }
200
201     @Test
202     public void testPollerLengthOutOfBoundsWithInputRegisters()
203             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
204         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_INPUT_REGISTER,
205                 ModbusConstants.MAX_REGISTERS_READ_COUNT + 1, false);
206     }
207
208     @Test
209     public void testPollerWithMaxCoils()
210             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
211         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_COIL, ModbusConstants.MAX_BITS_READ_COUNT, true);
212     }
213
214     @Test
215     public void testPollerLengthOutOfBoundsWithCoils()
216             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
217         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_COIL, ModbusConstants.MAX_BITS_READ_COUNT + 1,
218                 false);
219     }
220
221     @Test
222     public void testPollerWithMaxDiscreteInput()
223             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
224         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_DISCRETE_INPUT,
225                 ModbusConstants.MAX_BITS_READ_COUNT, true);
226     }
227
228     @Test
229     public void testPollerLengthOutOfBoundsWithDiscreteInput()
230             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
231         testPollerLengthCheck(ModbusBindingConstantsInternal.READ_TYPE_DISCRETE_INPUT,
232                 ModbusConstants.MAX_BITS_READ_COUNT + 1, false);
233     }
234
235     public void testPollingGeneric(String type, ModbusReadFunctionCode expectedFunctionCode)
236             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
237         PollTask pollTask = Mockito.mock(PollTask.class);
238         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
239
240         Configuration pollerConfig = new Configuration();
241         pollerConfig.put("refresh", 150L);
242         pollerConfig.put("start", 5);
243         pollerConfig.put("length", 13);
244         pollerConfig.put("type", type);
245         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
246                 .build();
247         addThing(poller);
248
249         assertThat(poller.getStatusInfo().toString(), poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
250
251         verifyEndpointBasicInitInteraction();
252         verify(mockedModbusManager).newModbusCommunicationInterface(argThat(new TypeSafeMatcher<ModbusSlaveEndpoint>() {
253
254             @Override
255             public void describeTo(Description description) {
256                 description.appendText("correct endpoint (");
257             }
258
259             @Override
260             protected boolean matchesSafely(ModbusSlaveEndpoint endpoint) {
261                 return checkEndpoint(endpoint);
262             }
263         }), any());
264
265         verify(comms).registerRegularPoll(argThat(new TypeSafeMatcher<ModbusReadRequestBlueprint>() {
266
267             @Override
268             public void describeTo(Description description) {
269                 description.appendText("correct request");
270             }
271
272             @Override
273             protected boolean matchesSafely(ModbusReadRequestBlueprint request) {
274                 return checkRequest(request, expectedFunctionCode);
275             }
276         }), eq(150l), eq(0L), notNull(), notNull());
277         verifyNoMoreInteractions(mockedModbusManager);
278     }
279
280     @SuppressWarnings("null")
281     private boolean checkEndpoint(ModbusSlaveEndpoint endpointParam) {
282         return endpointParam.equals(new ModbusTCPSlaveEndpoint(HOST, PORT));
283     }
284
285     private boolean checkRequest(ModbusReadRequestBlueprint request, ModbusReadFunctionCode functionCode) {
286         return request.getDataLength() == 13 && request.getFunctionCode() == functionCode
287                 && request.getProtocolID() == 0 && request.getReference() == 5 && request.getUnitID() == 9;
288     }
289
290     @Test
291     public void testInitializePollingWithCoils()
292             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
293         testPollingGeneric("coil", ModbusReadFunctionCode.READ_COILS);
294     }
295
296     @Test
297     public void testInitializePollingWithDiscrete()
298             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
299         testPollingGeneric("discrete", ModbusReadFunctionCode.READ_INPUT_DISCRETES);
300     }
301
302     @Test
303     public void testInitializePollingWithInputRegisters()
304             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
305         testPollingGeneric("input", ModbusReadFunctionCode.READ_INPUT_REGISTERS);
306     }
307
308     @Test
309     public void testInitializePollingWithHoldingRegisters()
310             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
311         testPollingGeneric("holding", ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS);
312     }
313
314     @Test
315     public void testPollUnregistrationOnDispose()
316             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
317         PollTask pollTask = Mockito.mock(PollTask.class);
318         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
319
320         Configuration pollerConfig = new Configuration();
321         pollerConfig.put("refresh", 150L);
322         pollerConfig.put("start", 5);
323         pollerConfig.put("length", 13);
324         pollerConfig.put("type", "coil");
325         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
326                 .build();
327         addThing(poller);
328         verifyEndpointBasicInitInteraction();
329
330         // verify registration
331         final AtomicReference<ModbusReadCallback> callbackRef = new AtomicReference<>();
332         verify(mockedModbusManager).newModbusCommunicationInterface(argThat(new TypeSafeMatcher<ModbusSlaveEndpoint>() {
333
334             @Override
335             public void describeTo(Description description) {
336                 description.appendText("correct endpoint");
337             }
338
339             @Override
340             protected boolean matchesSafely(ModbusSlaveEndpoint endpoint) {
341                 return checkEndpoint(endpoint);
342             }
343         }), any());
344         verify(comms).registerRegularPoll(argThat(new TypeSafeMatcher<ModbusReadRequestBlueprint>() {
345
346             @Override
347             public void describeTo(Description description) {
348             }
349
350             @Override
351             protected boolean matchesSafely(ModbusReadRequestBlueprint request) {
352                 return checkRequest(request, ModbusReadFunctionCode.READ_COILS);
353             }
354         }), eq(150l), eq(0L), argThat(new TypeSafeMatcher<ModbusReadCallback>() {
355
356             @Override
357             public void describeTo(Description description) {
358             }
359
360             @Override
361             protected boolean matchesSafely(ModbusReadCallback callback) {
362                 callbackRef.set(callback);
363                 return true;
364             }
365         }), notNull());
366         verifyNoMoreInteractions(mockedModbusManager);
367
368         // reset call counts for easy assertions
369         reset(mockedModbusManager);
370
371         // remove the thing
372         disposeThing(poller);
373
374         // 1) should first unregister poll task
375         verify(comms).unregisterRegularPoll(eq(pollTask));
376
377         verifyNoMoreInteractions(mockedModbusManager);
378     }
379
380     @Test
381     public void testInitializeWithNoBridge()
382             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
383         Configuration pollerConfig = new Configuration();
384         pollerConfig.put("refresh", 150L);
385         pollerConfig.put("start", 5);
386         pollerConfig.put("length", 13);
387         pollerConfig.put("type", "coil");
388         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).build();
389         addThing(poller);
390         verifyEndpointBasicInitInteraction();
391
392         assertThat(poller.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
393         assertThat(poller.getStatusInfo().getStatusDetail(), is(equalTo(ThingStatusDetail.BRIDGE_OFFLINE)));
394
395         verifyNoMoreInteractions(mockedModbusManager);
396     }
397
398     @Test
399     public void testInitializeWithOfflineBridge()
400             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
401         Configuration pollerConfig = new Configuration();
402         pollerConfig.put("refresh", 150L);
403         pollerConfig.put("start", 5);
404         pollerConfig.put("length", 13);
405         pollerConfig.put("type", "coil");
406
407         endpoint.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ""));
408         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
409                 .build();
410         addThing(poller);
411         verifyEndpointBasicInitInteraction();
412
413         assertThat(poller.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
414         assertThat(poller.getStatusInfo().getStatusDetail(), is(equalTo(ThingStatusDetail.BRIDGE_OFFLINE)));
415
416         verifyNoMoreInteractions(mockedModbusManager);
417     }
418
419     @Test
420     public void testRegistersPassedToChildDataThings()
421             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
422         PollTask pollTask = Mockito.mock(PollTask.class);
423         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
424
425         Configuration pollerConfig = new Configuration();
426         pollerConfig.put("refresh", 150L);
427         pollerConfig.put("start", 5);
428         pollerConfig.put("length", 13);
429         pollerConfig.put("type", "coil");
430         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
431                 .build();
432         addThing(poller);
433         verifyEndpointBasicInitInteraction();
434
435         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
436
437         ArgumentCaptor<ModbusReadCallback> callbackCapturer = ArgumentCaptor.forClass(ModbusReadCallback.class);
438         verify(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), callbackCapturer.capture(), notNull());
439         ModbusReadCallback readCallback = callbackCapturer.getValue();
440
441         assertNotNull(readCallback);
442
443         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
444         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
445
446         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
447         assertNotNull(thingHandler);
448
449         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
450         ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
451
452         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
453
454         // has one data child
455         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
456         readCallback.handle(result);
457         verify(child1).onReadResult(result);
458         verifyNoMoreInteractions(child1);
459         verifyNoMoreInteractions(child2);
460
461         reset(child1);
462
463         // two children (one child initialized)
464         thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
465         readCallback.handle(result);
466         verify(child1).onReadResult(result);
467         verify(child2).onReadResult(result);
468         verifyNoMoreInteractions(child1);
469         verifyNoMoreInteractions(child2);
470
471         reset(child1);
472         reset(child2);
473
474         // one child disposed
475         thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
476         readCallback.handle(result);
477         verify(child2).onReadResult(result);
478         verifyNoMoreInteractions(child1);
479         verifyNoMoreInteractions(child2);
480     }
481
482     @Test
483     public void testBitsPassedToChildDataThings()
484             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
485         PollTask pollTask = Mockito.mock(PollTask.class);
486         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
487
488         Configuration pollerConfig = new Configuration();
489         pollerConfig.put("refresh", 150L);
490         pollerConfig.put("start", 5);
491         pollerConfig.put("length", 13);
492         pollerConfig.put("type", "coil");
493         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
494                 .build();
495         addThing(poller);
496         verifyEndpointBasicInitInteraction();
497
498         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
499
500         ArgumentCaptor<ModbusReadCallback> callbackCapturer = ArgumentCaptor.forClass(ModbusReadCallback.class);
501         verify(comms).registerRegularPoll(any(), eq(150l), eq(0L), callbackCapturer.capture(), notNull());
502         ModbusReadCallback readCallback = callbackCapturer.getValue();
503
504         assertNotNull(readCallback);
505
506         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
507         BitArray bits = Mockito.mock(BitArray.class);
508
509         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
510         assertNotNull(thingHandler);
511
512         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
513         ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
514
515         AsyncModbusReadResult result = new AsyncModbusReadResult(request, bits);
516
517         // has one data child
518         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
519         readCallback.handle(result);
520         verify(child1).onReadResult(result);
521         verifyNoMoreInteractions(child1);
522         verifyNoMoreInteractions(child2);
523
524         reset(child1);
525
526         // two children (one child initialized)
527         thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
528         readCallback.handle(result);
529         verify(child1).onReadResult(result);
530         verify(child2).onReadResult(result);
531         verifyNoMoreInteractions(child1);
532         verifyNoMoreInteractions(child2);
533
534         reset(child1);
535         reset(child2);
536
537         // one child disposed
538         thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
539         readCallback.handle(result);
540         verify(child2).onReadResult(result);
541         verifyNoMoreInteractions(child1);
542         verifyNoMoreInteractions(child2);
543     }
544
545     @Test
546     public void testErrorPassedToChildDataThings()
547             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
548         PollTask pollTask = Mockito.mock(PollTask.class);
549         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
550
551         Configuration pollerConfig = new Configuration();
552         pollerConfig.put("refresh", 150L);
553         pollerConfig.put("start", 5);
554         pollerConfig.put("length", 13);
555         pollerConfig.put("type", "coil");
556         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
557                 .build();
558         addThing(poller);
559         verifyEndpointBasicInitInteraction();
560
561         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
562
563         final ArgumentCaptor<ModbusFailureCallback<ModbusReadRequestBlueprint>> callbackCapturer = ArgumentCaptor
564                 .forClass((Class) ModbusFailureCallback.class);
565         verify(comms).registerRegularPoll(any(), eq(150l), eq(0L), notNull(), callbackCapturer.capture());
566         ModbusFailureCallback<ModbusReadRequestBlueprint> readCallback = callbackCapturer.getValue();
567
568         assertNotNull(readCallback);
569
570         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
571         Exception error = Mockito.mock(Exception.class);
572
573         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
574         assertNotNull(thingHandler);
575
576         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
577         ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
578
579         AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
580                 request, error);
581
582         // has one data child
583         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
584         readCallback.handle(result);
585         verify(child1).handleReadError(result);
586         verifyNoMoreInteractions(child1);
587         verifyNoMoreInteractions(child2);
588
589         reset(child1);
590
591         // two children (one child initialized)
592         thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
593         readCallback.handle(result);
594         verify(child1).handleReadError(result);
595         verify(child2).handleReadError(result);
596         verifyNoMoreInteractions(child1);
597         verifyNoMoreInteractions(child2);
598
599         reset(child1);
600         reset(child2);
601
602         // one child disposed
603         thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
604         readCallback.handle(result);
605         verify(child2).handleReadError(result);
606         verifyNoMoreInteractions(child1);
607         verifyNoMoreInteractions(child2);
608     }
609
610     @Test
611     public void testRefresh()
612             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
613         Configuration pollerConfig = new Configuration();
614         pollerConfig.put("refresh", 0L);
615         pollerConfig.put("start", 5);
616         pollerConfig.put("length", 13);
617         pollerConfig.put("type", "coil");
618         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
619                 .build();
620         addThing(poller);
621         verifyEndpointBasicInitInteraction();
622
623         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
624
625         verify(comms, never()).submitOneTimePoll(any(), any(), any());
626         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
627         assertNotNull(thingHandler);
628         thingHandler.refresh();
629         verify(comms).submitOneTimePoll(any(), any(), any());
630     }
631
632     /**
633      * When there's no recently received data, refresh() will re-use that instead
634      *
635      * @throws IllegalArgumentException
636      * @throws IllegalAccessException
637      * @throws NoSuchFieldException
638      * @throws SecurityException
639      */
640     @Test
641     public void testRefreshWithPreviousData()
642             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
643         Configuration pollerConfig = new Configuration();
644         pollerConfig.put("refresh", 0L);
645         pollerConfig.put("start", 5);
646         pollerConfig.put("length", 13);
647         pollerConfig.put("type", "coil");
648         pollerConfig.put("cacheMillis", 10000L);
649         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
650                 .build();
651         addThing(poller);
652         verifyEndpointBasicInitInteraction();
653
654         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
655         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
656         assertNotNull(thingHandler);
657         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
658
659         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
660
661         verify(comms, never()).submitOneTimePoll(any(), any(), any());
662
663         // data is received
664         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
665         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
666         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
667         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
668         pollerReadCallback.handle(result);
669
670         // data child receives the data
671         verify(child1).onReadResult(result);
672         verifyNoMoreInteractions(child1);
673         reset(child1);
674
675         // call refresh
676         // cache is still valid, we should not have real data poll this time
677         thingHandler.refresh();
678         verify(comms, never()).submitOneTimePoll(any(), any(), any());
679
680         // data child receives the cached data
681         verify(child1).onReadResult(result);
682         verifyNoMoreInteractions(child1);
683     }
684
685     /**
686      * When there's no recently received data, refresh() will re-use that instead
687      *
688      * @throws IllegalArgumentException
689      * @throws IllegalAccessException
690      * @throws NoSuchFieldException
691      * @throws SecurityException
692      */
693     @Test
694     public void testRefreshWithPreviousDataCacheDisabled()
695             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
696         Configuration pollerConfig = new Configuration();
697         pollerConfig.put("refresh", 0L);
698         pollerConfig.put("start", 5);
699         pollerConfig.put("length", 13);
700         pollerConfig.put("type", "coil");
701         pollerConfig.put("cacheMillis", 0L);
702         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
703                 .build();
704         addThing(poller);
705         verifyEndpointBasicInitInteraction();
706
707         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
708         assertNotNull(thingHandler);
709         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
710         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
711
712         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
713
714         verify(comms, never()).submitOneTimePoll(any(), any(), any());
715
716         // data is received
717         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
718         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
719         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
720         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
721
722         pollerReadCallback.handle(result);
723
724         // data child receives the data
725         verify(child1).onReadResult(result);
726         verifyNoMoreInteractions(child1);
727         reset(child1);
728
729         // call refresh
730         // caching disabled, should poll from manager
731         thingHandler.refresh();
732         verify(comms).submitOneTimePoll(any(), any(), any());
733         verifyNoMoreInteractions(mockedModbusManager);
734
735         // data child receives the cached data
736         verifyNoMoreInteractions(child1);
737     }
738
739     /**
740      * Testing again caching, such that most recently received data is propagated to children
741      *
742      * @throws IllegalArgumentException
743      * @throws IllegalAccessException
744      * @throws NoSuchFieldException
745      * @throws SecurityException
746      * @throws InterruptedException
747      */
748     @Test
749     public void testRefreshWithPreviousData2() throws IllegalArgumentException, IllegalAccessException,
750             NoSuchFieldException, SecurityException, InterruptedException {
751         Configuration pollerConfig = new Configuration();
752         pollerConfig.put("refresh", 0L);
753         pollerConfig.put("start", 5);
754         pollerConfig.put("length", 13);
755         pollerConfig.put("type", "coil");
756         pollerConfig.put("cacheMillis", 10000L);
757         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
758                 .build();
759         addThing(poller);
760         verifyEndpointBasicInitInteraction();
761
762         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
763         assertNotNull(thingHandler);
764         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
765         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
766
767         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
768
769         verify(comms, never()).submitOneTimePoll(any(), any(), any());
770
771         // data is received
772         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
773         ModbusFailureCallback<ModbusReadRequestBlueprint> failureCallback = getPollerFailureCallback(thingHandler);
774         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
775         ModbusReadRequestBlueprint request2 = Mockito.mock(ModbusReadRequestBlueprint.class);
776         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
777         Exception error = Mockito.mock(Exception.class);
778         AsyncModbusReadResult registersResult = new AsyncModbusReadResult(request, registers);
779         AsyncModbusFailure<ModbusReadRequestBlueprint> errorResult = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
780                 request2, error);
781
782         pollerReadCallback.handle(registersResult);
783
784         // data child should receive the data
785         verify(child1).onReadResult(registersResult);
786         verifyNoMoreInteractions(child1);
787         reset(child1);
788
789         // Sleep to have time between the data
790         Thread.sleep(5L);
791
792         // error is received
793         failureCallback.handle(errorResult);
794
795         // data child should receive the error
796         verify(child1).handleReadError(errorResult);
797         verifyNoMoreInteractions(child1);
798         reset(child1);
799
800         // call refresh, should return latest data (that is, error)
801         // cache is still valid, we should not have real data poll this time
802         thingHandler.refresh();
803         verify(comms, never()).submitOneTimePoll(any(), any(), any());
804
805         // data child receives the cached error
806         verify(child1).handleReadError(errorResult);
807         verifyNoMoreInteractions(child1);
808     }
809
810     @Test
811     public void testRefreshWithOldPreviousData() throws IllegalArgumentException, IllegalAccessException,
812             NoSuchFieldException, SecurityException, InterruptedException {
813
814         Configuration pollerConfig = new Configuration();
815         pollerConfig.put("refresh", 0L);
816         pollerConfig.put("start", 5);
817         pollerConfig.put("length", 13);
818         pollerConfig.put("type", "coil");
819         pollerConfig.put("cacheMillis", 10L);
820         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
821                 .build();
822         addThing(poller);
823         verifyEndpointBasicInitInteraction();
824
825         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
826         assertNotNull(thingHandler);
827         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
828         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
829
830         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
831
832         verify(comms, never()).submitOneTimePoll(any(), any(), any());
833
834         // data is received
835         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
836         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
837         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
838         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
839
840         pollerReadCallback.handle(result);
841
842         // data child should receive the data
843         verify(child1).onReadResult(result);
844         verifyNoMoreInteractions(child1);
845         reset(child1);
846
847         // Sleep to ensure cache expiry
848         Thread.sleep(15L);
849
850         // call refresh. Since cache expired, will poll for more
851         verify(comms, never()).submitOneTimePoll(any(), any(), any());
852         thingHandler.refresh();
853         verify(comms).submitOneTimePoll(any(), any(), any());
854     }
855 }