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 boolean executedSuccessfully = writeString(urtsiCommand);
95 return executedSuccessfully;
99 * Sends a string to the serial port of this device.
100 * The writing of the msg is executed synchronized, so it's guaranteed that the device doesn't get
101 * multiple messages concurrently.
103 * @param msg the string to send
104 * @return true, if the message has been transmitted successfully, otherwise false.
106 protected synchronized boolean writeString(final String msg) {
107 logger.debug("Writing '{}' to serial port {}", msg, portId.getName());
109 final long earliestNextExecution = lastCommandTime + commandInterval;
110 while (earliestNextExecution > System.currentTimeMillis()) {
113 } catch (InterruptedException ex) {
118 final List<Boolean> listenerResult = new ArrayList<>();
119 serialPort.addEventListener(new SerialPortEventListener() {
121 public void serialEvent(SerialPortEvent event) {
122 if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
123 // we get here if data has been received
124 final StringBuilder sb = new StringBuilder();
125 final byte[] readBuffer = new byte[20];
128 // read data from serial device
129 while (inputStream.available() > 0) {
130 final int bytes = inputStream.read(readBuffer);
131 sb.append(new String(readBuffer, 0, bytes));
134 // add wait states around reading the stream, so that interrupted transmissions are
137 } catch (InterruptedException e) {
138 // ignore interruption
140 } while (inputStream.available() > 0);
141 final String result = sb.toString();
142 if (result.equals(msg)) {
143 listenerResult.add(true);
145 } catch (IOException e) {
146 logger.debug("Error receiving data on serial port {}: {}", portId.getName(),
152 serialPort.notifyOnDataAvailable(true);
153 outputStream.write(msg.getBytes());
154 outputStream.flush();
155 lastCommandTime = System.currentTimeMillis();
156 final long timeout = lastCommandTime + 1000;
157 while (listenerResult.isEmpty() && System.currentTimeMillis() < timeout) {
158 // Waiting for response
161 return !listenerResult.isEmpty();
162 } catch (IOException | TooManyListenersException | InterruptedException e) {
163 logger.error("Error writing '{}' to serial port {}: {}", msg, portId.getName(), e.getMessage());
165 serialPort.removeEventListener();
171 public void initialize() {
172 address = getThing().getProperties().get("address");
173 UrtsiDeviceConfig urtsiDeviceConfig = getConfigAs(UrtsiDeviceConfig.class);
174 commandInterval = urtsiDeviceConfig.commandInterval;
175 String port = urtsiDeviceConfig.port;
177 portId = serialPortManager.getIdentifier(port);
179 if (portId == null) {
180 String availablePorts = serialPortManager.getIdentifiers().map(id -> id.getName())
181 .collect(Collectors.joining(System.lineSeparator()));
182 String description = String.format("Serial port '%s' could not be found. Available ports are:%n%s", port,
184 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
189 serialPort = portId.open("openHAB", 2000);
190 serialPort.setSerialPortParams(BAUD, DATABITS, STOPBIT, PARITY);
191 inputStream = serialPort.getInputStream();
192 outputStream = serialPort.getOutputStream();
193 updateStatus(ThingStatus.ONLINE);
194 } catch (IOException e) {
195 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error: " + e.getMessage());
196 } catch (PortInUseException e) {
197 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port already used: " + port);
198 } catch (UnsupportedCommOperationException e) {
199 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
200 "Unsupported operation on port '" + port + "': " + e.getMessage());
205 public void dispose() {
207 if (serialPort != null) {
208 serialPort.removeEventListener();
210 if (outputStream != null) {
212 outputStream.close();
213 } catch (IOException e) {
214 logger.debug("Error while closing the output stream: {}", e.getMessage());
217 if (inputStream != null) {
220 } catch (IOException e) {
221 logger.debug("Error while closing the input stream: {}", e.getMessage());
224 if (serialPort != null) {