]> git.basschouten.com Git - openhab-addons.git/blob
cc49fe1416d4f501ca7908b1196a61e4d08e14cd
[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.dsmr.internal.device;
14
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;
27
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;
35
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;
55
56 /**
57  * Test class for {@link DSMRSerialAutoDevice}.
58  *
59  * @author Hilbrand Bouwkamp - Initial contribution
60  */
61 @ExtendWith(MockitoExtension.class)
62 @NonNullByDefault
63 public class DSMRSerialAutoDeviceTest {
64
65     private static final String DUMMY_PORTNAME = "/dev/dummy-serial";
66     private static final String TELEGRAM_NAME = "dsmr_50";
67
68     private @NonNullByDefault({}) @Mock SerialPortIdentifier mockIdentifier;
69     private @NonNullByDefault({}) @Mock ScheduledExecutorService scheduler;
70     private @NonNullByDefault({}) @Mock SerialPort mockSerialPort;
71
72     private final SerialPortManager serialPortManager = new SerialPortManager() {
73         @Override
74         public @Nullable SerialPortIdentifier getIdentifier(final String name) {
75             assertEquals(DUMMY_PORTNAME, name, "Expect the passed serial port name");
76             return mockIdentifier;
77         }
78
79         @Override
80         public Stream<SerialPortIdentifier> getIdentifiers() {
81             return Stream.empty();
82         }
83     };
84     private @NonNullByDefault({}) SerialPortEventListener serialPortEventListener;
85
86     @BeforeEach
87     public void setUp() throws PortInUseException, TooManyListenersException {
88         doAnswer(a -> {
89             serialPortEventListener = a.getArgument(0);
90             return null;
91         }).when(mockSerialPort).addEventListener(any());
92     }
93
94     @Test
95     public void testHandlingDataAndRestart() throws IOException, PortInUseException {
96         mockValidSerialPort();
97         final AtomicReference<@Nullable P1Telegram> telegramRef = new AtomicReference<>(null);
98         final P1TelegramListener listener = new P1TelegramListener() {
99             @Override
100             public void telegramReceived(final P1Telegram telegram) {
101                 telegramRef.set(telegram);
102             }
103
104             @Override
105             public void onError(final DSMRErrorStatus errorStatus, final String message) {
106                 fail("No error status expected" + errorStatus);
107             }
108         };
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);
113             device.start();
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");
121             device.restart();
122             assertSame(DeviceState.NORMAL, device.getState(), "Expect not to start rediscovery when in normal state");
123             device.stop();
124         }
125         assertNotNull(telegramRef.get(), "Expected to have read a telegram");
126     }
127
128     @Test
129     public void testHandleError() throws IOException, PortInUseException {
130         final AtomicReference<@Nullable DSMRErrorStatus> eventRef = new AtomicReference<>(null);
131         final P1TelegramListener listener = new P1TelegramListener() {
132             @Override
133             public void telegramReceived(final P1Telegram telegram) {
134                 fail("No telegram expected:" + telegram);
135             }
136
137             @Override
138             public void onError(final DSMRErrorStatus errorStatus, final String message) {
139                 eventRef.set(errorStatus);
140             }
141         };
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);
149             device.start();
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();
154             device.restart();
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(),
159                     scheduler, 1);
160             device.start();
161             assertSame(DSMRErrorStatus.PORT_DONT_EXISTS, eventRef.get(), "Expected an error");
162             assertSame(DeviceState.ERROR, device.getState(), "Expect to be in error state");
163         }
164     }
165
166     private void mockValidSerialPort() throws PortInUseException {
167         doReturn(mockSerialPort).when(mockIdentifier).open(anyString(), anyInt());
168     }
169
170     /**
171      * Mock class implementing {@link SerialPortEvent}.
172      */
173     private static class MockSerialPortEvent implements SerialPortEvent {
174         private final int eventType;
175         private final boolean newValue;
176
177         public MockSerialPortEvent(final SerialPort mockSerialPort, final int eventType, final boolean oldValue,
178                 final boolean newValue) {
179             this.eventType = eventType;
180             this.newValue = newValue;
181         }
182
183         @Override
184         public int getEventType() {
185             return eventType;
186         }
187
188         @Override
189         public boolean getNewValue() {
190             return newValue;
191         }
192     }
193 }