]> git.basschouten.com Git - openhab-addons.git/blob
d3e5e4a5bb0b15d48e4999c651a4aa050be69a4f
[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.upb.internal.handler;
14
15 import java.util.concurrent.CompletableFuture;
16 import java.util.concurrent.CompletionStage;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
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;
34
35 /**
36  * Bridge handler responsible for serial PIM communications.
37  *
38  * @author Marcus Better - Initial contribution
39  *
40  */
41 @NonNullByDefault
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;
47
48     private final Logger logger = LoggerFactory.getLogger(SerialPIMHandler.class);
49
50     private SerialPortManager serialPortManager;
51     private volatile @Nullable SerialIoThread receiveThread;
52     private volatile @Nullable ScheduledFuture<?> futSerialPortInit;
53
54     public SerialPIMHandler(final Bridge thing, final SerialPortManager serialPortManager) {
55         super(thing);
56         this.serialPortManager = serialPortManager;
57     }
58
59     @Override
60     public void initialize() {
61         logger.debug("Initializing Serial UPB PIM {}.", getThing().getUID());
62         super.initialize();
63
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);
69             return;
70         }
71
72         futSerialPortInit = scheduler.schedule(() -> openSerialPort(portId), SERIAL_PORT_OPEN_INIT_DELAY_MS,
73                 TimeUnit.MILLISECONDS);
74     }
75
76     @Override
77     public void dispose() {
78         final ScheduledFuture<?> futSerialPortInit = this.futSerialPortInit;
79         if (futSerialPortInit != null) {
80             futSerialPortInit.cancel(true);
81             this.futSerialPortInit = null;
82         }
83         final SerialIoThread receiveThread = this.receiveThread;
84         if (receiveThread != null) {
85             receiveThread.terminate();
86             try {
87                 receiveThread.join(1000);
88             } catch (final InterruptedException e) {
89                 // ignore
90             }
91             this.receiveThread = null;
92         }
93         logger.debug("Stopped UPB serial handler");
94         super.dispose();
95     }
96
97     private void openSerialPort(final String portId) {
98         try {
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);
103                 return;
104             }
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);
114         }
115     }
116
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);
123             return null;
124         }
125
126         final SerialPort serialPort;
127         try {
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);
132             return null;
133         }
134         try {
135             serialPort.setSerialPortParams(BAUD_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
136                     SerialPort.PARITY_NONE);
137             serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
138             try {
139                 serialPort.enableReceiveThreshold(1);
140                 serialPort.enableReceiveTimeout(SERIAL_RECEIVE_TIMEOUT_MS);
141             } catch (final UnsupportedCommOperationException e) {
142                 // ignore - not supported for RFC2217 ports
143             }
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);
148             return null;
149         }
150         logger.debug("Serial port is initialized");
151         return serialPort;
152     }
153
154     @Override
155     public CompletionStage<CmdStatus> sendPacket(final MessageBuilder msg) {
156         final SerialIoThread receiveThread = this.receiveThread;
157         if (receiveThread != null) {
158             return receiveThread.enqueue(msg.build());
159         } else {
160             return exceptionallyCompletedFuture(new IllegalStateException("I/O thread not active"));
161         }
162     }
163
164     /**
165      * Returns a new {@code CompletableFuture} that is already exceptionally completed with
166      * the given exception.
167      *
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
171      *            {@code T}
172      * @return a future that exceptionally completed with the supplied exception
173      * @throws NullPointerException if the supplied throwable is {@code null}
174      * @since 0.1.0
175      */
176     public static <T> CompletableFuture<T> exceptionallyCompletedFuture(final Throwable throwable) {
177         final CompletableFuture<T> future = new CompletableFuture<>();
178         future.completeExceptionally(throwable);
179         return future;
180     }
181 }