]> git.basschouten.com Git - openhab-addons.git/blob
f0ba662b180232edb2b1c4125c18aa0bc92d40e0
[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.urtsi.internal.handler;
14
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;
22
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;
42
43 /**
44  * The {@link UrtsiDeviceHandler} is responsible for handling commands, which are
45  * sent to one of the channels.
46  *
47  * @author Oliver Libutzki - Initial contribution
48  */
49 public class UrtsiDeviceHandler extends BaseBridgeHandler {
50
51     private final Logger logger = LoggerFactory.getLogger(UrtsiDeviceHandler.class);
52
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;
57
58     private int commandInterval;
59     private String address;
60
61     private long lastCommandTime;
62
63     private SerialPortIdentifier portId;
64     private SerialPort serialPort;
65     private final SerialPortManager serialPortManager;
66     private OutputStream outputStream;
67     private InputStream inputStream;
68
69     public UrtsiDeviceHandler(Bridge bridge, SerialPortManager serialPortManager) {
70         super(bridge);
71         this.serialPortManager = serialPortManager;
72     }
73
74     @Override
75     public void handleCommand(ChannelUID channelUID, Command command) {
76         // the bridge does not have any channels
77     }
78
79     /**
80      * Executes the given {@link RtsCommand} for the given {@link Thing} (RTS Device).
81      *
82      * @param rtsDevice the RTS Device which is the receiver of the command.
83      * @param rtsCommand the command to be executed
84      * @return
85      */
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) {
90             return false;
91         }
92         String urtsiCommand = new StringBuilder(address).append(mappedChannel).append(rtsCommand.getActionKey())
93                 .toString();
94         boolean executedSuccessfully = writeString(urtsiCommand);
95         return executedSuccessfully;
96     }
97
98     /**
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.
102      *
103      * @param msg the string to send
104      * @return true, if the message has been transmitted successfully, otherwise false.
105      */
106     protected synchronized boolean writeString(final String msg) {
107         logger.debug("Writing '{}' to serial port {}", msg, portId.getName());
108
109         final long earliestNextExecution = lastCommandTime + commandInterval;
110         while (earliestNextExecution > System.currentTimeMillis()) {
111             try {
112                 Thread.sleep(100);
113             } catch (InterruptedException ex) {
114                 return false;
115             }
116         }
117         try {
118             final List<Boolean> listenerResult = new ArrayList<>();
119             serialPort.addEventListener(new SerialPortEventListener() {
120                 @Override
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];
126                         try {
127                             do {
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));
132                                 }
133                                 try {
134                                     // add wait states around reading the stream, so that interrupted transmissions are
135                                     // merged
136                                     Thread.sleep(100);
137                                 } catch (InterruptedException e) {
138                                     // ignore interruption
139                                 }
140                             } while (inputStream.available() > 0);
141                             final String result = sb.toString();
142                             if (result.equals(msg)) {
143                                 listenerResult.add(true);
144                             }
145                         } catch (IOException e) {
146                             logger.debug("Error receiving data on serial port {}: {}", portId.getName(),
147                                     e.getMessage());
148                         }
149                     }
150                 }
151             });
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
159                 Thread.sleep(100);
160             }
161             return !listenerResult.isEmpty();
162         } catch (IOException | TooManyListenersException | InterruptedException e) {
163             logger.error("Error writing '{}' to serial port {}: {}", msg, portId.getName(), e.getMessage());
164         } finally {
165             serialPort.removeEventListener();
166         }
167         return false;
168     }
169
170     @Override
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;
176
177         portId = serialPortManager.getIdentifier(port);
178
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,
183                     availablePorts);
184             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
185             return;
186         }
187
188         try {
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());
201         }
202     }
203
204     @Override
205     public void dispose() {
206         super.dispose();
207         if (serialPort != null) {
208             serialPort.removeEventListener();
209         }
210         if (outputStream != null) {
211             try {
212                 outputStream.close();
213             } catch (IOException e) {
214                 logger.debug("Error while closing the output stream: {}", e.getMessage());
215             }
216         }
217         if (inputStream != null) {
218             try {
219                 inputStream.close();
220             } catch (IOException e) {
221                 logger.debug("Error while closing the input stream: {}", e.getMessage());
222             }
223         }
224         if (serialPort != null) {
225             serialPort.close();
226         }
227         outputStream = null;
228         inputStream = null;
229         serialPort = null;
230     }
231 }