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.*;
16 import static org.mockito.ArgumentMatchers.*;
17 import static org.mockito.Mockito.*;
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.util.TooManyListenersException;
23 import java.util.concurrent.ScheduledExecutorService;
24 import java.util.concurrent.atomic.AtomicReference;
25 import java.util.stream.Stream;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.junit.jupiter.api.BeforeEach;
30 import org.junit.jupiter.api.Test;
31 import org.junit.jupiter.api.extension.ExtendWith;
32 import org.mockito.Mock;
33 import org.mockito.junit.jupiter.MockitoExtension;
34 import org.openhab.binding.dsmr.internal.DSMRBindingConstants;
35 import org.openhab.binding.dsmr.internal.TelegramReaderUtil;
36 import org.openhab.binding.dsmr.internal.device.DSMRSerialAutoDevice.DeviceState;
37 import org.openhab.binding.dsmr.internal.device.connector.DSMRConnectorErrorEvent;
38 import org.openhab.binding.dsmr.internal.device.p1telegram.P1Telegram;
39 import org.openhab.core.io.transport.serial.PortInUseException;
40 import org.openhab.core.io.transport.serial.SerialPort;
41 import org.openhab.core.io.transport.serial.SerialPortEvent;
42 import org.openhab.core.io.transport.serial.SerialPortEventListener;
43 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
44 import org.openhab.core.io.transport.serial.SerialPortManager;
47 * Test class for {@link DSMRSerialAutoDevice}.
49 * @author Hilbrand Bouwkamp - Initial contribution
51 @ExtendWith(MockitoExtension.class)
53 public class DSMRSerialAutoDeviceTest {
55 private static final String DUMMY_PORTNAME = "/dev/dummy-serial";
56 private static final String TELEGRAM_NAME = "dsmr_50";
58 private @NonNullByDefault({}) @Mock SerialPortIdentifier mockIdentifier;
59 private @NonNullByDefault({}) @Mock ScheduledExecutorService scheduler;
60 private @NonNullByDefault({}) @Mock SerialPort mockSerialPort;
62 private final SerialPortManager serialPortManager = new SerialPortManager() {
64 public @Nullable SerialPortIdentifier getIdentifier(String name) {
65 assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
66 return mockIdentifier;
70 public Stream<SerialPortIdentifier> getIdentifiers() {
71 return Stream.empty();
74 private @NonNullByDefault({}) SerialPortEventListener serialPortEventListener;
77 public void setUp() throws PortInUseException, TooManyListenersException {
79 serialPortEventListener = a.getArgument(0);
81 }).when(mockSerialPort).addEventListener(any());
85 public void testHandlingDataAndRestart() throws IOException, PortInUseException {
86 mockValidSerialPort();
87 AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
88 DSMREventListener listener = new DSMREventListener() {
90 public void handleTelegramReceived(P1Telegram telegram) {
91 telegramRef.set(telegram);
95 public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
96 fail("No handleErrorEvent Expected" + connectorErrorEvent);
99 try (InputStream inputStream = new ByteArrayInputStream(TelegramReaderUtil.readRawTelegram(TELEGRAM_NAME))) {
100 when(mockSerialPort.getInputStream()).thenReturn(inputStream);
101 DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
102 new DSMRTelegramListener(), scheduler, 1);
104 assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
105 serialPortEventListener
106 .serialEvent(new MockSerialPortEvent(mockSerialPort, SerialPortEvent.BI, false, true));
107 assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be still in discovery state");
108 serialPortEventListener
109 .serialEvent(new MockSerialPortEvent(mockSerialPort, SerialPortEvent.DATA_AVAILABLE, false, true));
110 assertSame(DeviceState.NORMAL, device.getState(), "Expect to be in normal state");
112 assertSame(DeviceState.NORMAL, device.getState(), "Expect not to start rediscovery when in normal state");
115 assertNotNull(telegramRef.get(), "Expected to have read a telegram");
119 public void testHandleError() throws IOException, PortInUseException {
120 AtomicReference<@Nullable DSMRConnectorErrorEvent> eventRef = new AtomicReference<>(null);
121 DSMREventListener listener = new DSMREventListener() {
123 public void handleTelegramReceived(P1Telegram telegram) {
124 fail("No telegram expected:" + telegram);
128 public void handleErrorEvent(DSMRConnectorErrorEvent connectorErrorEvent) {
129 eventRef.set(connectorErrorEvent);
132 try (InputStream inputStream = new ByteArrayInputStream(new byte[] {})) {
133 when(mockSerialPort.getInputStream()).thenReturn(inputStream);
134 // Trigger device to go into error stage with port in use.
135 doThrow(new PortInUseException(new Exception())).when(mockIdentifier)
136 .open(eq(DSMRBindingConstants.DSMR_PORT_NAME), anyInt());
137 DSMRSerialAutoDevice device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener,
138 new DSMRTelegramListener(), scheduler, 1);
140 assertSame(DSMRConnectorErrorEvent.IN_USE, eventRef.get(), "Expected an error");
141 assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
142 // Trigger device to restart
143 mockValidSerialPort();
145 assertSame(DeviceState.DISCOVER_SETTINGS, device.getState(), "Expect to be starting discovery state");
146 // Trigger device to go into error stage with port doesn't exist.
147 mockIdentifier = null;
148 device = new DSMRSerialAutoDevice(serialPortManager, DUMMY_PORTNAME, listener, new DSMRTelegramListener(),
151 assertSame(DSMRConnectorErrorEvent.DONT_EXISTS, eventRef.get(), "Expected an error");
152 assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
156 private void mockValidSerialPort() throws PortInUseException {
157 doReturn(mockSerialPort).when(mockIdentifier).open(anyString(), anyInt());
161 * Mock class implementing {@link SerialPortEvent}.
163 private static class MockSerialPortEvent implements SerialPortEvent {
164 private final int eventType;
165 private final boolean newValue;
167 public MockSerialPortEvent(SerialPort mockSerialPort, int eventType, boolean oldValue, boolean newValue) {
168 this.eventType = eventType;
169 this.newValue = newValue;
173 public int getEventType() {
178 public boolean getNewValue() {