2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.smartmeter;
15 import static org.mockito.ArgumentMatchers.any;
16 import static org.mockito.Mockito.*;
18 import java.io.IOException;
19 import java.time.Duration;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.TimeoutException;
22 import java.util.function.Supplier;
24 import javax.measure.Quantity;
26 import org.eclipse.jdt.annotation.NonNull;
27 import org.junit.jupiter.api.Test;
28 import org.mockito.ArgumentMatchers;
29 import org.mockito.Mockito;
30 import org.openhab.binding.smartmeter.connectors.ConnectorBase;
31 import org.openhab.binding.smartmeter.connectors.IMeterReaderConnector;
32 import org.openhab.binding.smartmeter.internal.MeterDevice;
33 import org.openhab.binding.smartmeter.internal.MeterValue;
34 import org.openhab.binding.smartmeter.internal.MeterValueListener;
35 import org.openhab.binding.smartmeter.internal.helper.ProtocolMode;
36 import org.openhab.core.io.transport.serial.SerialPortManager;
38 import io.reactivex.disposables.Disposable;
39 import io.reactivex.functions.Consumer;
40 import io.reactivex.plugins.RxJavaPlugins;
44 * @author Matthias Steigenberger - Initial contribution
47 public class TestMeterReading {
50 public void testContinousReading() throws Exception {
51 final Duration period = Duration.ofSeconds(1);
52 final int executionCount = 5;
53 MockMeterReaderConnector connector = getMockedConnector(false, () -> new Object());
54 MeterDevice<Object> meter = getMeterDevice(connector);
55 MeterValueListener changeListener = Mockito.mock(MeterValueListener.class);
56 meter.addValueChangeListener(changeListener);
57 long executionTime = period.toMillis() * executionCount;
58 Disposable disposable = meter.readValues(executionTime, Executors.newScheduledThreadPool(1), period);
60 verify(changeListener, after(executionTime + period.toMillis() / 2 + 50).never()).errorOccurred(any());
61 verify(changeListener, times(executionCount)).valueChanged(any());
68 public void testRetryHandling() {
69 final Duration period = Duration.ofSeconds(1);
70 MockMeterReaderConnector connector = spy(getMockedConnector(true, () -> {
71 throw new IllegalArgumentException();
73 MeterDevice<Object> meter = getMeterDevice(connector);
74 MeterValueListener changeListener = Mockito.mock(MeterValueListener.class);
75 meter.addValueChangeListener(changeListener);
76 Disposable disposable = meter.readValues(5000, Executors.newScheduledThreadPool(1), period);
78 verify(changeListener, after(
79 period.toMillis() + 2 * period.toMillis() * ConnectorBase.NUMBER_OF_RETRIES + period.toMillis() / 2)
80 .times(1)).errorOccurred(any());
81 verify(connector, times(ConnectorBase.NUMBER_OF_RETRIES)).retryHook(ArgumentMatchers.anyInt());
88 public void testTimeoutHandling() {
89 final Duration period = Duration.ofSeconds(2);
90 final int timeout = 5000;
91 MockMeterReaderConnector connector = spy(getMockedConnector(true, () -> {
93 Thread.sleep(timeout);
94 } catch (InterruptedException e) {
98 MeterDevice<Object> meter = getMeterDevice(connector);
99 MeterValueListener changeListener = Mockito.mock(MeterValueListener.class);
100 meter.addValueChangeListener(changeListener);
101 Disposable disposable = meter.readValues(timeout / 2, Executors.newScheduledThreadPool(2), period);
103 verify(changeListener, timeout(timeout)).errorOccurred(any(TimeoutException.class));
105 disposable.dispose();
110 public void shouldNotReportToFallbackException() {
111 final Duration period = Duration.ofSeconds(2);
112 final int timeout = 5000;
113 MockMeterReaderConnector connector = spy(getMockedConnector(true, () -> {
115 Thread.sleep(timeout);
116 } catch (InterruptedException e) {
118 throw new RuntimeException(new IOException("fucked up"));
120 MeterDevice<Object> meter = getMeterDevice(connector);
121 Consumer<Throwable> errorHandler = mock(Consumer.class);
122 RxJavaPlugins.setErrorHandler(errorHandler);
123 MeterValueListener changeListener = Mockito.mock(MeterValueListener.class);
124 meter.addValueChangeListener(changeListener);
125 Disposable disposable = meter.readValues(timeout / 2, Executors.newScheduledThreadPool(2), period);
127 verify(changeListener, timeout(timeout)).errorOccurred(any(TimeoutException.class));
128 verifyNoMoreInteractions(errorHandler);
130 disposable.dispose();
134 MockMeterReaderConnector getMockedConnector(boolean applyRetry, Supplier<Object> readNextSupplier) {
135 return new MockMeterReaderConnector("Test port", applyRetry, readNextSupplier);
138 MeterDevice<Object> getMeterDevice(ConnectorBase<Object> connector) {
139 return new MeterDevice<>(() -> mock(SerialPortManager.class), "id", "port", null, 9600, 0, ProtocolMode.SML) {
142 protected @NonNull IMeterReaderConnector<Object> createConnector(
143 @NonNull Supplier<@NonNull SerialPortManager> serialPortManagerSupplier, @NonNull String serialPort,
144 int baudrate, int baudrateChangeDelay, @NonNull ProtocolMode protocolMode) {
149 protected <Q extends @NonNull Quantity<Q>> void populateValueCache(Object smlFile) {
150 addObisCache(new MeterValue("123", "333", null));