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.upb.internal;
15 import static java.nio.charset.StandardCharsets.US_ASCII;
16 import static org.junit.jupiter.api.Assertions.assertEquals;
17 import static org.junit.jupiter.api.Assertions.assertFalse;
18 import static org.junit.jupiter.api.Assertions.assertTrue;
19 import static org.mockito.Mockito.mock;
20 import static org.mockito.Mockito.when;
22 import java.io.IOException;
23 import java.io.InputStreamReader;
24 import java.io.OutputStreamWriter;
25 import java.io.PipedInputStream;
26 import java.io.PipedOutputStream;
27 import java.util.concurrent.BlockingQueue;
28 import java.util.concurrent.CompletableFuture;
29 import java.util.concurrent.CompletionStage;
30 import java.util.concurrent.LinkedBlockingQueue;
32 import org.junit.jupiter.api.AfterEach;
33 import org.junit.jupiter.api.BeforeEach;
34 import org.junit.jupiter.api.Test;
35 import org.mockito.Mock;
36 import org.openhab.binding.upb.internal.handler.MessageListener;
37 import org.openhab.binding.upb.internal.handler.SerialIoThread;
38 import org.openhab.binding.upb.internal.handler.UPBIoHandler.CmdStatus;
39 import org.openhab.binding.upb.internal.message.Command;
40 import org.openhab.binding.upb.internal.message.MessageBuilder;
41 import org.openhab.binding.upb.internal.message.UPBMessage;
42 import org.openhab.core.io.transport.serial.SerialPort;
43 import org.openhab.core.thing.ThingUID;
46 * @author Marcus Better - Initial contribution
48 public class SerialIoThreadTest {
50 private static final String ENABLE_MESSAGE_MODE_CMD = "\u001770028E\n";
52 private final ThingUID thingUID = new ThingUID("a", "b", "c");
53 private final Listener msgListener = new Listener();
54 private final PipedOutputStream in = new PipedOutputStream();
55 private final OutputStreamWriter inbound = new OutputStreamWriter(in, US_ASCII);
56 private final PipedOutputStream out = new PipedOutputStream();
58 private @Mock SerialPort serialPort;
59 private SerialIoThread thread;
60 private InputStreamReader outbound;
61 final char[] buf = new char[256];
64 public void setup() throws IOException {
65 serialPort = mock(SerialPort.class);
66 outbound = new InputStreamReader(new PipedInputStream(out), US_ASCII);
67 when(serialPort.getInputStream()).thenReturn(new PipedInputStream(in));
68 when(serialPort.getOutputStream()).thenReturn(out);
69 thread = new SerialIoThread(serialPort, msgListener, thingUID);
74 public void cleanup() {
79 public void testName() {
80 assertEquals("OH-binding-a:b:c-serial-reader", thread.getName());
81 assertTrue(thread.isDaemon());
85 public void receive() throws Exception {
86 writeInbound("PU8905FA011220FFFF47\r");
87 final UPBMessage msg = msgListener.readInbound();
88 assertEquals(Command.ACTIVATE, msg.getCommand());
89 assertEquals(1, msg.getDestination());
90 writeInbound("PU8905FA011221FFFF48\r");
91 final UPBMessage msg2 = msgListener.readInbound();
92 assertEquals(Command.DEACTIVATE, msg2.getCommand());
93 verifyMessageModeCmd();
97 public void send() throws Exception {
98 final String msg = MessageBuilder.forCommand(Command.GOTO).args((byte) 10).network((byte) 2)
99 .destination((byte) 5).build();
100 final CompletionStage<CmdStatus> fut = thread.enqueue(msg);
101 verifyMessageModeCmd();
102 final int n = outbound.read(buf);
103 assertEquals("\u001408100205FF220AB6\r", new String(buf, 0, n));
105 final CmdStatus res = fut.toCompletableFuture().join();
106 assertEquals(CmdStatus.ACK, res);
110 public void resend() throws Exception {
111 final String msg = MessageBuilder.forCommand(Command.GOTO).args((byte) 10).network((byte) 2)
112 .destination((byte) 5).build();
113 final CompletableFuture<CmdStatus> fut = thread.enqueue(msg).toCompletableFuture();
114 verifyMessageModeCmd();
115 int n = outbound.read(buf);
116 assertEquals("\u001408100205FF220AB6\r", new String(buf, 0, n));
120 n = outbound.read(buf);
121 assertEquals("\u001408100205FF220AB6\r", new String(buf, 0, n));
122 assertFalse(fut.isDone());
124 final CmdStatus res = fut.join();
125 assertEquals(CmdStatus.ACK, res);
129 public void resendMaxAttempts() throws Exception {
130 final String msg = MessageBuilder.forCommand(Command.GOTO).args((byte) 10).network((byte) 2)
131 .destination((byte) 5).build();
132 final CompletableFuture<CmdStatus> fut = thread.enqueue(msg).toCompletableFuture();
133 verifyMessageModeCmd();
134 int n = outbound.read(buf);
135 assertEquals("\u001408100205FF220AB6\r", new String(buf, 0, n));
139 n = outbound.read(buf);
140 assertEquals("\u001408100205FF220AB6\r", new String(buf, 0, n));
141 assertFalse(fut.isDone());
142 // no response - wait for ack timeout
145 n = outbound.read(buf);
146 assertEquals("\u001408100205FF220AB6\r", new String(buf, 0, n));
147 assertFalse(fut.isDone());
149 final CmdStatus res = fut.join();
150 assertEquals(CmdStatus.NAK, res);
153 private void ack() throws IOException {
154 writeInbound("PK\r");
157 private void nak() throws IOException {
158 writeInbound("PN\r");
161 private void writeInbound(String s) throws IOException {
166 private void verifyMessageModeCmd() throws IOException {
167 final int n = outbound.read(buf, 0, ENABLE_MESSAGE_MODE_CMD.length());
168 assertEquals(ENABLE_MESSAGE_MODE_CMD, new String(buf, 0, n));
171 private static class Listener implements MessageListener {
173 private final BlockingQueue<UPBMessage> messages = new LinkedBlockingQueue<>();
176 public void incomingMessage(final UPBMessage msg) {
181 public void onError(final Throwable t) {
184 public UPBMessage readInbound() {
186 return messages.take();
187 } catch (InterruptedException e) {