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