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.handler;
15 import java.util.concurrent.CompletableFuture;
16 import java.util.concurrent.CompletionStage;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.upb.internal.Constants;
23 import org.openhab.binding.upb.internal.message.MessageBuilder;
24 import org.openhab.core.io.transport.serial.PortInUseException;
25 import org.openhab.core.io.transport.serial.SerialPort;
26 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
27 import org.openhab.core.io.transport.serial.SerialPortManager;
28 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
29 import org.openhab.core.thing.Bridge;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * Bridge handler responsible for serial PIM communications.
38 * @author Marcus Better - Initial contribution
42 public class SerialPIMHandler extends PIMHandler {
43 private static final int SERIAL_RECEIVE_TIMEOUT_MS = 100;
44 private static final int BAUD_RATE = 4800;
45 private static final int SERIAL_PORT_OPEN_INIT_DELAY_MS = 500;
46 private static final int SERIAL_PORT_OPEN_RETRY_DELAY_MS = 30_000;
48 private final Logger logger = LoggerFactory.getLogger(SerialPIMHandler.class);
50 private SerialPortManager serialPortManager;
51 private volatile @Nullable SerialIoThread receiveThread;
52 private volatile @Nullable ScheduledFuture<?> futSerialPortInit;
54 public SerialPIMHandler(final Bridge thing, final SerialPortManager serialPortManager) {
56 this.serialPortManager = serialPortManager;
60 public void initialize() {
61 logger.debug("Initializing Serial UPB PIM {}.", getThing().getUID());
64 final String portId = (String) getConfig().get(Constants.CONFIGURATION_PORT);
65 if (portId == null || portId.isEmpty()) {
66 logger.debug("serial port is not set");
67 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
68 Constants.OFFLINE_SERIAL_PORT_NOT_SET);
72 futSerialPortInit = scheduler.schedule(() -> openSerialPort(portId), SERIAL_PORT_OPEN_INIT_DELAY_MS,
73 TimeUnit.MILLISECONDS);
77 public void dispose() {
78 final ScheduledFuture<?> futSerialPortInit = this.futSerialPortInit;
79 if (futSerialPortInit != null) {
80 futSerialPortInit.cancel(true);
81 this.futSerialPortInit = null;
83 final SerialIoThread receiveThread = this.receiveThread;
84 if (receiveThread != null) {
85 receiveThread.terminate();
87 receiveThread.join(1000);
88 } catch (final InterruptedException e) {
91 this.receiveThread = null;
93 logger.debug("Stopped UPB serial handler");
97 private void openSerialPort(final String portId) {
99 final SerialPort serialPort = tryOpenSerialPort(portId);
100 if (serialPort == null) {
101 futSerialPortInit = scheduler.schedule(() -> openSerialPort(portId), SERIAL_PORT_OPEN_RETRY_DELAY_MS,
102 TimeUnit.MILLISECONDS);
105 logger.debug("Starting receive thread");
106 final SerialIoThread receiveThread = new SerialIoThread(serialPort, this, getThing().getUID());
107 this.receiveThread = receiveThread;
108 // Once the receiver starts, it may set the PIM status to ONLINE
109 // so we must ensure all initialization is finished at that point.
110 receiveThread.start();
111 updateStatus(ThingStatus.ONLINE);
112 } catch (final RuntimeException e) {
113 logger.warn("failed to open serial port", e);
117 private @Nullable SerialPort tryOpenSerialPort(final String portId) {
118 logger.debug("opening serial port {}", portId);
119 final SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(portId);
120 if (portIdentifier == null) {
121 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
122 Constants.OFFLINE_SERIAL_EXISTS);
126 final SerialPort serialPort;
128 serialPort = portIdentifier.open("org.openhab.binding.upb", 1000);
129 } catch (final PortInUseException e) {
130 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
131 Constants.OFFLINE_SERIAL_INUSE);
135 serialPort.setSerialPortParams(BAUD_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
136 SerialPort.PARITY_NONE);
137 serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
139 serialPort.enableReceiveThreshold(1);
140 serialPort.enableReceiveTimeout(SERIAL_RECEIVE_TIMEOUT_MS);
141 } catch (final UnsupportedCommOperationException e) {
142 // ignore - not supported for RFC2217 ports
144 } catch (final UnsupportedCommOperationException e) {
145 logger.debug("cannot open serial port", e);
146 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
147 Constants.OFFLINE_SERIAL_UNSUPPORTED);
150 logger.debug("Serial port is initialized");
155 public CompletionStage<CmdStatus> sendPacket(final MessageBuilder msg) {
156 final SerialIoThread receiveThread = this.receiveThread;
157 if (receiveThread != null) {
158 return receiveThread.enqueue(msg.build());
160 return exceptionallyCompletedFuture(new IllegalStateException("I/O thread not active"));
165 * Returns a new {@code CompletableFuture} that is already exceptionally completed with
166 * the given exception.
168 * @param throwable the exception
169 * @param <T> an arbitrary type for the returned future; can be anything since the future
170 * will be exceptionally completed and thus there will never be a value of type
172 * @return a future that exceptionally completed with the supplied exception
173 * @throws NullPointerException if the supplied throwable is {@code null}
176 public static <T> CompletableFuture<T> exceptionallyCompletedFuture(final Throwable throwable) {
177 final CompletableFuture<T> future = new CompletableFuture<>();
178 future.completeExceptionally(throwable);