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.urtsi.internal.handler;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.io.OutputStream;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.TooManyListenersException;
21 import java.util.stream.Collectors;
23 import org.openhab.binding.urtsi.internal.config.RtsDeviceConfig;
24 import org.openhab.binding.urtsi.internal.config.UrtsiDeviceConfig;
25 import org.openhab.binding.urtsi.internal.mapping.UrtsiChannelMapping;
26 import org.openhab.core.io.transport.serial.PortInUseException;
27 import org.openhab.core.io.transport.serial.SerialPort;
28 import org.openhab.core.io.transport.serial.SerialPortEvent;
29 import org.openhab.core.io.transport.serial.SerialPortEventListener;
30 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
31 import org.openhab.core.io.transport.serial.SerialPortManager;
32 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.Thing;
36 import org.openhab.core.thing.ThingStatus;
37 import org.openhab.core.thing.ThingStatusDetail;
38 import org.openhab.core.thing.binding.BaseBridgeHandler;
39 import org.openhab.core.types.Command;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
44 * The {@link UrtsiDeviceHandler} is responsible for handling commands, which are
45 * sent to one of the channels.
47 * @author Oliver Libutzki - Initial contribution
49 public class UrtsiDeviceHandler extends BaseBridgeHandler {
51 private final Logger logger = LoggerFactory.getLogger(UrtsiDeviceHandler.class);
53 private static final int BAUD = 9600;
54 private static final int DATABITS = SerialPort.DATABITS_8;
55 private static final int STOPBIT = SerialPort.STOPBITS_1;
56 private static final int PARITY = SerialPort.PARITY_NONE;
58 private int commandInterval;
59 private String address;
61 private long lastCommandTime;
63 private SerialPortIdentifier portId;
64 private SerialPort serialPort;
65 private final SerialPortManager serialPortManager;
66 private OutputStream outputStream;
67 private InputStream inputStream;
69 public UrtsiDeviceHandler(Bridge bridge, SerialPortManager serialPortManager) {
71 this.serialPortManager = serialPortManager;
75 public void handleCommand(ChannelUID channelUID, Command command) {
76 // the bridge does not have any channels
80 * Executes the given {@link RtsCommand} for the given {@link Thing} (RTS Device).
82 * @param rtsDevice the RTS Device which is the receiver of the command.
83 * @param rtsCommand the command to be executed
86 public boolean executeRtsCommand(Thing rtsDevice, RtsCommand rtsCommand) {
87 RtsDeviceConfig rtsDeviceConfig = rtsDevice.getConfiguration().as(RtsDeviceConfig.class);
88 String mappedChannel = UrtsiChannelMapping.getMappedChannel(rtsDeviceConfig.channel);
89 if (mappedChannel == null) {
92 String urtsiCommand = new StringBuilder(address).append(mappedChannel).append(rtsCommand.getActionKey())
94 return writeString(urtsiCommand);
98 * Sends a string to the serial port of this device.
99 * The writing of the msg is executed synchronized, so it's guaranteed that the device doesn't get
100 * multiple messages concurrently.
102 * @param msg the string to send
103 * @return true, if the message has been transmitted successfully, otherwise false.
105 protected synchronized boolean writeString(final String msg) {
106 logger.debug("Writing '{}' to serial port {}", msg, portId.getName());
108 final long earliestNextExecution = lastCommandTime + commandInterval;
109 while (earliestNextExecution > System.currentTimeMillis()) {
112 } catch (InterruptedException ex) {
117 final List<Boolean> listenerResult = new ArrayList<>();
118 serialPort.addEventListener(new SerialPortEventListener() {
120 public void serialEvent(SerialPortEvent event) {
121 if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
122 // we get here if data has been received
123 final StringBuilder sb = new StringBuilder();
124 final byte[] readBuffer = new byte[20];
127 // read data from serial device
128 while (inputStream.available() > 0) {
129 final int bytes = inputStream.read(readBuffer);
130 sb.append(new String(readBuffer, 0, bytes));
133 // add wait states around reading the stream, so that interrupted transmissions are
136 } catch (InterruptedException e) {
137 // ignore interruption
139 } while (inputStream.available() > 0);
140 final String result = sb.toString();
141 if (result.equals(msg)) {
142 listenerResult.add(true);
144 } catch (IOException e) {
145 logger.debug("Error receiving data on serial port {}: {}", portId.getName(),
151 serialPort.notifyOnDataAvailable(true);
152 outputStream.write(msg.getBytes());
153 outputStream.flush();
154 lastCommandTime = System.currentTimeMillis();
155 final long timeout = lastCommandTime + 1000;
156 while (listenerResult.isEmpty() && System.currentTimeMillis() < timeout) {
157 // Waiting for response
160 return !listenerResult.isEmpty();
161 } catch (IOException | TooManyListenersException | InterruptedException e) {
162 logger.error("Error writing '{}' to serial port {}: {}", msg, portId.getName(), e.getMessage());
164 serialPort.removeEventListener();
170 public void initialize() {
171 address = getThing().getProperties().get("address");
172 UrtsiDeviceConfig urtsiDeviceConfig = getConfigAs(UrtsiDeviceConfig.class);
173 commandInterval = urtsiDeviceConfig.commandInterval;
174 String port = urtsiDeviceConfig.port;
176 portId = serialPortManager.getIdentifier(port);
178 if (portId == null) {
179 String availablePorts = serialPortManager.getIdentifiers().map(id -> id.getName())
180 .collect(Collectors.joining(System.lineSeparator()));
181 String description = String.format("Serial port '%s' could not be found. Available ports are:%n%s", port,
183 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
188 serialPort = portId.open("openHAB", 2000);
189 serialPort.setSerialPortParams(BAUD, DATABITS, STOPBIT, PARITY);
190 inputStream = serialPort.getInputStream();
191 outputStream = serialPort.getOutputStream();
192 updateStatus(ThingStatus.ONLINE);
193 } catch (IOException e) {
194 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error: " + e.getMessage());
195 } catch (PortInUseException e) {
196 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port already used: " + port);
197 } catch (UnsupportedCommOperationException e) {
198 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
199 "Unsupported operation on port '" + port + "': " + e.getMessage());
204 public void dispose() {
206 if (serialPort != null) {
207 serialPort.removeEventListener();
209 if (outputStream != null) {
211 outputStream.close();
212 } catch (IOException e) {
213 logger.debug("Error while closing the output stream: {}", e.getMessage());
216 if (inputStream != null) {
219 } catch (IOException e) {
220 logger.debug("Error while closing the input stream: {}", e.getMessage());
223 if (serialPort != null) {