]> git.basschouten.com Git - openhab-addons.git/blob
7cd4377b9846f2a030004a413a17f4c8aa144556
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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 testInitializeWithNoBridge()
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         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).build();
386         addThing(poller);
387         verifyEndpointBasicInitInteraction();
388
389         assertThat(poller.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
390         assertThat(poller.getStatusInfo().getStatusDetail(), is(equalTo(ThingStatusDetail.BRIDGE_OFFLINE)));
391
392         verifyNoMoreInteractions(mockedModbusManager);
393     }
394
395     @Test
396     public void testInitializeWithOfflineBridge()
397             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
398         Configuration pollerConfig = new Configuration();
399         pollerConfig.put("refresh", 150L);
400         pollerConfig.put("start", 5);
401         pollerConfig.put("length", 13);
402         pollerConfig.put("type", "coil");
403
404         endpoint.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ""));
405         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
406                 .build();
407         addThing(poller);
408         verifyEndpointBasicInitInteraction();
409
410         assertThat(poller.getStatus(), is(equalTo(ThingStatus.OFFLINE)));
411         assertThat(poller.getStatusInfo().getStatusDetail(), is(equalTo(ThingStatusDetail.BRIDGE_OFFLINE)));
412
413         verifyNoMoreInteractions(mockedModbusManager);
414     }
415
416     @Test
417     public void testRegistersPassedToChildDataThings()
418             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
419         PollTask pollTask = Mockito.mock(PollTask.class);
420         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
421
422         Configuration pollerConfig = new Configuration();
423         pollerConfig.put("refresh", 150L);
424         pollerConfig.put("start", 5);
425         pollerConfig.put("length", 13);
426         pollerConfig.put("type", "coil");
427         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
428                 .build();
429         addThing(poller);
430         verifyEndpointBasicInitInteraction();
431
432         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
433
434         ArgumentCaptor<ModbusReadCallback> callbackCapturer = ArgumentCaptor.forClass(ModbusReadCallback.class);
435         verify(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), callbackCapturer.capture(), notNull());
436         ModbusReadCallback readCallback = callbackCapturer.getValue();
437
438         assertNotNull(readCallback);
439
440         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
441         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
442
443         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
444         assertNotNull(thingHandler);
445
446         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
447         ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
448
449         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
450
451         // has one data child
452         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
453         readCallback.handle(result);
454         verify(child1).onReadResult(result);
455         verifyNoMoreInteractions(child1);
456         verifyNoMoreInteractions(child2);
457
458         reset(child1);
459
460         // two children (one child initialized)
461         thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
462         readCallback.handle(result);
463         verify(child1).onReadResult(result);
464         verify(child2).onReadResult(result);
465         verifyNoMoreInteractions(child1);
466         verifyNoMoreInteractions(child2);
467
468         reset(child1);
469         reset(child2);
470
471         // one child disposed
472         thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
473         readCallback.handle(result);
474         verify(child2).onReadResult(result);
475         verifyNoMoreInteractions(child1);
476         verifyNoMoreInteractions(child2);
477     }
478
479     @Test
480     public void testBitsPassedToChildDataThings()
481             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
482         PollTask pollTask = Mockito.mock(PollTask.class);
483         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
484
485         Configuration pollerConfig = new Configuration();
486         pollerConfig.put("refresh", 150L);
487         pollerConfig.put("start", 5);
488         pollerConfig.put("length", 13);
489         pollerConfig.put("type", "coil");
490         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
491                 .build();
492         addThing(poller);
493         verifyEndpointBasicInitInteraction();
494
495         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
496
497         ArgumentCaptor<ModbusReadCallback> callbackCapturer = ArgumentCaptor.forClass(ModbusReadCallback.class);
498         verify(comms).registerRegularPoll(any(), eq(150l), eq(0L), callbackCapturer.capture(), notNull());
499         ModbusReadCallback readCallback = callbackCapturer.getValue();
500
501         assertNotNull(readCallback);
502
503         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
504         BitArray bits = Mockito.mock(BitArray.class);
505
506         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
507         assertNotNull(thingHandler);
508
509         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
510         ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
511
512         AsyncModbusReadResult result = new AsyncModbusReadResult(request, bits);
513
514         // has one data child
515         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
516         readCallback.handle(result);
517         verify(child1).onReadResult(result);
518         verifyNoMoreInteractions(child1);
519         verifyNoMoreInteractions(child2);
520
521         reset(child1);
522
523         // two children (one child initialized)
524         thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
525         readCallback.handle(result);
526         verify(child1).onReadResult(result);
527         verify(child2).onReadResult(result);
528         verifyNoMoreInteractions(child1);
529         verifyNoMoreInteractions(child2);
530
531         reset(child1);
532         reset(child2);
533
534         // one child disposed
535         thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
536         readCallback.handle(result);
537         verify(child2).onReadResult(result);
538         verifyNoMoreInteractions(child1);
539         verifyNoMoreInteractions(child2);
540     }
541
542     @Test
543     public void testErrorPassedToChildDataThings()
544             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
545         PollTask pollTask = Mockito.mock(PollTask.class);
546         doReturn(pollTask).when(comms).registerRegularPoll(notNull(), eq(150l), eq(0L), notNull(), notNull());
547
548         Configuration pollerConfig = new Configuration();
549         pollerConfig.put("refresh", 150L);
550         pollerConfig.put("start", 5);
551         pollerConfig.put("length", 13);
552         pollerConfig.put("type", "coil");
553         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
554                 .build();
555         addThing(poller);
556         verifyEndpointBasicInitInteraction();
557
558         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
559
560         final ArgumentCaptor<ModbusFailureCallback<ModbusReadRequestBlueprint>> callbackCapturer = ArgumentCaptor
561                 .forClass((Class) ModbusFailureCallback.class);
562         verify(comms).registerRegularPoll(any(), eq(150l), eq(0L), notNull(), callbackCapturer.capture());
563         ModbusFailureCallback<ModbusReadRequestBlueprint> readCallback = callbackCapturer.getValue();
564
565         assertNotNull(readCallback);
566
567         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
568         Exception error = Mockito.mock(Exception.class);
569
570         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
571         assertNotNull(thingHandler);
572
573         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
574         ModbusDataThingHandler child2 = Mockito.mock(ModbusDataThingHandler.class);
575
576         AsyncModbusFailure<ModbusReadRequestBlueprint> result = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
577                 request, error);
578
579         // has one data child
580         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
581         readCallback.handle(result);
582         verify(child1).handleReadError(result);
583         verifyNoMoreInteractions(child1);
584         verifyNoMoreInteractions(child2);
585
586         reset(child1);
587
588         // two children (one child initialized)
589         thingHandler.childHandlerInitialized(child2, Mockito.mock(Thing.class));
590         readCallback.handle(result);
591         verify(child1).handleReadError(result);
592         verify(child2).handleReadError(result);
593         verifyNoMoreInteractions(child1);
594         verifyNoMoreInteractions(child2);
595
596         reset(child1);
597         reset(child2);
598
599         // one child disposed
600         thingHandler.childHandlerDisposed(child1, Mockito.mock(Thing.class));
601         readCallback.handle(result);
602         verify(child2).handleReadError(result);
603         verifyNoMoreInteractions(child1);
604         verifyNoMoreInteractions(child2);
605     }
606
607     @Test
608     public void testRefresh()
609             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
610         Configuration pollerConfig = new Configuration();
611         pollerConfig.put("refresh", 0L);
612         pollerConfig.put("start", 5);
613         pollerConfig.put("length", 13);
614         pollerConfig.put("type", "coil");
615         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
616                 .build();
617         addThing(poller);
618         verifyEndpointBasicInitInteraction();
619
620         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
621
622         verify(comms, never()).submitOneTimePoll(any(), any(), any());
623         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
624         assertNotNull(thingHandler);
625         thingHandler.refresh();
626         verify(comms).submitOneTimePoll(any(), any(), any());
627     }
628
629     /**
630      * When there's no recently received data, refresh() will re-use that instead
631      *
632      * @throws IllegalArgumentException
633      * @throws IllegalAccessException
634      * @throws NoSuchFieldException
635      * @throws SecurityException
636      */
637     @Test
638     public void testRefreshWithPreviousData()
639             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
640         Configuration pollerConfig = new Configuration();
641         pollerConfig.put("refresh", 0L);
642         pollerConfig.put("start", 5);
643         pollerConfig.put("length", 13);
644         pollerConfig.put("type", "coil");
645         pollerConfig.put("cacheMillis", 10000L);
646         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
647                 .build();
648         addThing(poller);
649         verifyEndpointBasicInitInteraction();
650
651         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
652         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
653         assertNotNull(thingHandler);
654         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
655
656         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
657
658         verify(comms, never()).submitOneTimePoll(any(), any(), any());
659
660         // data is received
661         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
662         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
663         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
664         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
665         pollerReadCallback.handle(result);
666
667         // data child receives the data
668         verify(child1).onReadResult(result);
669         verifyNoMoreInteractions(child1);
670         reset(child1);
671
672         // call refresh
673         // cache is still valid, we should not have real data poll this time
674         thingHandler.refresh();
675         verify(comms, never()).submitOneTimePoll(any(), any(), any());
676
677         // data child receives the cached data
678         verify(child1).onReadResult(result);
679         verifyNoMoreInteractions(child1);
680     }
681
682     /**
683      * When there's no recently received data, refresh() will re-use that instead
684      *
685      * @throws IllegalArgumentException
686      * @throws IllegalAccessException
687      * @throws NoSuchFieldException
688      * @throws SecurityException
689      */
690     @Test
691     public void testRefreshWithPreviousDataCacheDisabled()
692             throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException {
693         Configuration pollerConfig = new Configuration();
694         pollerConfig.put("refresh", 0L);
695         pollerConfig.put("start", 5);
696         pollerConfig.put("length", 13);
697         pollerConfig.put("type", "coil");
698         pollerConfig.put("cacheMillis", 0L);
699         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
700                 .build();
701         addThing(poller);
702         verifyEndpointBasicInitInteraction();
703
704         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
705         assertNotNull(thingHandler);
706         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
707         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
708
709         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
710
711         verify(comms, never()).submitOneTimePoll(any(), any(), any());
712
713         // data is received
714         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
715         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
716         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
717         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
718
719         pollerReadCallback.handle(result);
720
721         // data child receives the data
722         verify(child1).onReadResult(result);
723         verifyNoMoreInteractions(child1);
724         reset(child1);
725
726         // call refresh
727         // caching disabled, should poll from manager
728         thingHandler.refresh();
729         verify(comms).submitOneTimePoll(any(), any(), any());
730         verifyNoMoreInteractions(mockedModbusManager);
731
732         // data child receives the cached data
733         verifyNoMoreInteractions(child1);
734     }
735
736     /**
737      * Testing again caching, such that most recently received data is propagated to children
738      *
739      * @throws IllegalArgumentException
740      * @throws IllegalAccessException
741      * @throws NoSuchFieldException
742      * @throws SecurityException
743      * @throws InterruptedException
744      */
745     @Test
746     public void testRefreshWithPreviousData2() throws IllegalArgumentException, IllegalAccessException,
747             NoSuchFieldException, SecurityException, InterruptedException {
748         Configuration pollerConfig = new Configuration();
749         pollerConfig.put("refresh", 0L);
750         pollerConfig.put("start", 5);
751         pollerConfig.put("length", 13);
752         pollerConfig.put("type", "coil");
753         pollerConfig.put("cacheMillis", 10000L);
754         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
755                 .build();
756         addThing(poller);
757         verifyEndpointBasicInitInteraction();
758
759         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
760         assertNotNull(thingHandler);
761         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
762         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
763
764         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
765
766         verify(comms, never()).submitOneTimePoll(any(), any(), any());
767
768         // data is received
769         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
770         ModbusFailureCallback<ModbusReadRequestBlueprint> failureCallback = getPollerFailureCallback(thingHandler);
771         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
772         ModbusReadRequestBlueprint request2 = Mockito.mock(ModbusReadRequestBlueprint.class);
773         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
774         Exception error = Mockito.mock(Exception.class);
775         AsyncModbusReadResult registersResult = new AsyncModbusReadResult(request, registers);
776         AsyncModbusFailure<ModbusReadRequestBlueprint> errorResult = new AsyncModbusFailure<ModbusReadRequestBlueprint>(
777                 request2, error);
778
779         pollerReadCallback.handle(registersResult);
780
781         // data child should receive the data
782         verify(child1).onReadResult(registersResult);
783         verifyNoMoreInteractions(child1);
784         reset(child1);
785
786         // Sleep to have time between the data
787         Thread.sleep(5L);
788
789         // error is received
790         failureCallback.handle(errorResult);
791
792         // data child should receive the error
793         verify(child1).handleReadError(errorResult);
794         verifyNoMoreInteractions(child1);
795         reset(child1);
796
797         // call refresh, should return latest data (that is, error)
798         // cache is still valid, we should not have real data poll this time
799         thingHandler.refresh();
800         verify(comms, never()).submitOneTimePoll(any(), any(), any());
801
802         // data child receives the cached error
803         verify(child1).handleReadError(errorResult);
804         verifyNoMoreInteractions(child1);
805     }
806
807     @Test
808     public void testRefreshWithOldPreviousData() throws IllegalArgumentException, IllegalAccessException,
809             NoSuchFieldException, SecurityException, InterruptedException {
810
811         Configuration pollerConfig = new Configuration();
812         pollerConfig.put("refresh", 0L);
813         pollerConfig.put("start", 5);
814         pollerConfig.put("length", 13);
815         pollerConfig.put("type", "coil");
816         pollerConfig.put("cacheMillis", 10L);
817         poller = createPollerThingBuilder("poller").withConfiguration(pollerConfig).withBridge(endpoint.getUID())
818                 .build();
819         addThing(poller);
820         verifyEndpointBasicInitInteraction();
821
822         ModbusPollerThingHandler thingHandler = (ModbusPollerThingHandler) poller.getHandler();
823         assertNotNull(thingHandler);
824         ModbusDataThingHandler child1 = Mockito.mock(ModbusDataThingHandler.class);
825         thingHandler.childHandlerInitialized(child1, Mockito.mock(Thing.class));
826
827         assertThat(poller.getStatus(), is(equalTo(ThingStatus.ONLINE)));
828
829         verify(comms, never()).submitOneTimePoll(any(), any(), any());
830
831         // data is received
832         ModbusReadCallback pollerReadCallback = getPollerCallback(thingHandler);
833         ModbusReadRequestBlueprint request = Mockito.mock(ModbusReadRequestBlueprint.class);
834         ModbusRegisterArray registers = Mockito.mock(ModbusRegisterArray.class);
835         AsyncModbusReadResult result = new AsyncModbusReadResult(request, registers);
836
837         pollerReadCallback.handle(result);
838
839         // data child should receive the data
840         verify(child1).onReadResult(result);
841         verifyNoMoreInteractions(child1);
842         reset(child1);
843
844         // Sleep to ensure cache expiry
845         Thread.sleep(15L);
846
847         // call refresh. Since cache expired, will poll for more
848         verify(comms, never()).submitOneTimePoll(any(), any(), any());
849         thingHandler.refresh();
850         verify(comms).submitOneTimePoll(any(), any(), any());
851     }
852 }