2 * Copyright (c) 2010-2023 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.dsmr.internal.device;
15 import static org.junit.jupiter.api.Assertions.assertEquals;
16 import static org.junit.jupiter.api.Assertions.assertNotNull;
17 import static org.junit.jupiter.api.Assertions.assertSame;
18 import static org.junit.jupiter.api.Assertions.fail;
19 import static org.mockito.ArgumentMatchers.any;
20 import static org.mockito.ArgumentMatchers.anyInt;
21 import static org.mockito.ArgumentMatchers.anyString;
22 import static org.mockito.ArgumentMatchers.eq;
23 import static org.mockito.Mockito.doAnswer;
24 import static org.mockito.Mockito.doReturn;
25 import static org.mockito.Mockito.doThrow;
26 import static org.mockito.Mockito.when;
28 import java.io.ByteArrayInputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.TooManyListenersException;
32 import java.util.concurrent.ScheduledExecutorService;
33 import java.util.concurrent.atomic.AtomicReference;
34 import java.util.stream.Stream;
36 import org.eclipse.jdt.annotation.NonNullByDefault;
37 import org.eclipse.jdt.annotation.Nullable;
38 import org.junit.jupiter.api.BeforeEach;
39 import org.junit.jupiter.api.Test;
40 import org.junit.jupiter.api.extension.ExtendWith;
41 import org.mockito.Mock;
42 import org.mockito.junit.jupiter.MockitoExtension;
43 import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
44 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
45 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice.DeviceState;
46 import org.openhab.binding.dsmr.internal.device.connector.DSMRErrorStatus;
47 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
48 import org.openhab.binding.dsmr.internal.device.p1telegram.P1TelegramListener;
49 import org.openhab.core.io.transport.serial.PortInUseException;
50 import org.openhab.core.io.transport.serial.SerialPort;
51 import org.openhab.core.io.transport.serial.SerialPortEvent;
52 import org.openhab.core.io.transport.serial.SerialPortEventListener;
53 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
54 import org.openhab.core.io.transport.serial.SerialPortManager;
57 * Test class for {@link DSMRSerialAutoDevice}.
59 * @author Hilbrand Bouwkamp - Initial contribution
61 @ExtendWith(MockitoExtension.class)
63 public class DSMRSerialAutoDeviceTest {
65 private static final String DUMMY_PORTNAME = "/dev/dummy-serial";
66 private static final String TELEGRAM_NAME = "dsmr_50";
68 private @NonNullByDefault({}) @Mock SerialPortIdentifier mockIdentifier;
69 private @NonNullByDefault({}) @Mock ScheduledExecutorService scheduler;
70 private @NonNullByDefault({}) @Mock SerialPort mockSerialPort;
72 private final SerialPortManager serialPortManager = new SerialPortManager() {
74 public @Nullable SerialPortIdentifier getIdentifier(final String name) {
75 assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
76 return mockIdentifier;
80 public Stream<SerialPortIdentifier> getIdentifiers() {
81 return Stream.empty();
84 private @NonNullByDefault({}) SerialPortEventListener serialPortEventListener;
87 public void setUp() throws PortInUseException, TooManyListenersException {
89 serialPortEventListener = a.getArgument(0);
91 }).when(mockSerialPort).addEventListener(any());
95 public void testHandlingDataAndRestart() throws IOException, PortInUseException {
96 mockValidSerialPort();
97 final AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
98 final P1TelegramListener listener = new P1TelegramListener() {
100 public void telegramReceived(final P1Telegram telegram) {
101 telegramRef.set(telegram);
105 public void onError(final DSMRErrorStatus errorStatus, final String message) {
106 fail("No error status expected" + errorStatus);
109 try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
110 when(mockSerialPort.getInputStream()).thenReturn(inputStream);
111 final DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
112 new DSMRTelegramListener(), scheduler, 1);
114 assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
115 serialPortEventListener
116 .serialEvent(new MockSerialPortEvent(mockSerialPort, SerialPortEvent.BI, false, true));
117 assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be still in discovery state");
118 serialPortEventListener
119 .serialEvent(new MockSerialPortEvent(mockSerialPort, SerialPortEvent.DATA_AVAILABLE, false, true));
120 assertSame(DeviceState.NORMAL, device.getState(), "Expect to be in normal state");
122 assertSame(DeviceState.NORMAL, device.getState(), "Expect not to start rediscovery when in normal state");
125 assertNotNull(telegramRef.get(), "Expected to have read a telegram");
129 public void testHandleError() throws IOException, PortInUseException {
130 final AtomicReference<@Nullable DSMRErrorStatus> eventRef = new AtomicReference<>(null);
131 final P1TelegramListener listener = new P1TelegramListener() {
133 public void telegramReceived(final P1Telegram telegram) {
134 fail("No telegram expected:" + telegram);
138 public void onError(final DSMRErrorStatus errorStatus, final String message) {
139 eventRef.set(errorStatus);
142 try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
143 when(mockSerialPort.getInputStream()).thenReturn(inputStream);
144 // Trigger device to go into error stage with port in use.
145 doThrow(new PortInUseException(new Exception())).when(mockIdentifier)
146 .open(eq(DSMRBindingConstants.DSMR_PORT_NAME), anyInt());
147 DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
148 new DSMRTelegramListener(), scheduler, 1);
150 assertSame(DSMRErrorStatus.PORT_IN_USE, eventRef.get(), "Expected an error");
151 assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
152 // Trigger device to restart
153 mockValidSerialPort();
155 assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
156 // Trigger device to go into error stage with port doesn't exist.
157 mockIdentifier = null;
158 device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, new DSMRTelegramListener(),
161 assertSame(DSMRErrorStatus.PORT_DONT_EXISTS, eventRef.get(), "Expected an error");
162 assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
166 private void mockValidSerialPort() throws PortInUseException {
167 doReturn(mockSerialPort).when(mockIdentifier).open(anyString(), anyInt());
171 * Mock class implementing {@link SerialPortEvent}.
173 private static class MockSerialPortEvent implements SerialPortEvent {
174 private final int eventType;
175 private final boolean newValue;
177 public MockSerialPortEvent(final SerialPort mockSerialPort, final int eventType, final boolean oldValue,
178 final boolean newValue) {
179 this.eventType = eventType;
180 this.newValue = newValue;
184 public int getEventType() {
189 public boolean getNewValue() {