]> git.basschouten.com Git - openhab-addons.git/blob
796a470e57c7826d6e6ca3859dd79763fda1ab32
[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         return writeString(urtsiCommand);
95     }
96
97     /**
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.
101      *
102      * @param msg the string to send
103      * @return true, if the message has been transmitted successfully, otherwise false.
104      */
105     protected synchronized boolean writeString(final String msg) {
106         logger.debug("Writing '{}' to serial port {}", msg, portId.getName());
107
108         final long earliestNextExecution = lastCommandTime + commandInterval;
109         while (earliestNextExecution > System.currentTimeMillis()) {
110             try {
111                 Thread.sleep(100);
112             } catch (InterruptedException ex) {
113                 return false;
114             }
115         }
116         try {
117             final List<Boolean> listenerResult = new ArrayList<>();
118             serialPort.addEventListener(new SerialPortEventListener() {
119                 @Override
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];
125                         try {
126                             do {
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));
131                                 }
132                                 try {
133                                     // add wait states around reading the stream, so that interrupted transmissions are
134                                     // merged
135                                     Thread.sleep(100);
136                                 } catch (InterruptedException e) {
137                                     // ignore interruption
138                                 }
139                             } while (inputStream.available() > 0);
140                             final String result = sb.toString();
141                             if (result.equals(msg)) {
142                                 listenerResult.add(true);
143                             }
144                         } catch (IOException e) {
145                             logger.debug("Error receiving data on serial port {}: {}", portId.getName(),
146                                     e.getMessage());
147                         }
148                     }
149                 }
150             });
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
158                 Thread.sleep(100);
159             }
160             return !listenerResult.isEmpty();
161         } catch (IOException | TooManyListenersException | InterruptedException e) {
162             logger.error("Error writing '{}' to serial port {}: {}", msg, portId.getName(), e.getMessage());
163         } finally {
164             serialPort.removeEventListener();
165         }
166         return false;
167     }
168
169     @Override
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;
175
176         portId = serialPortManager.getIdentifier(port);
177
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,
182                     availablePorts);
183             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, description);
184             return;
185         }
186
187         try {
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());
200         }
201     }
202
203     @Override
204     public void dispose() {
205         super.dispose();
206         if (serialPort != null) {
207             serialPort.removeEventListener();
208         }
209         if (outputStream != null) {
210             try {
211                 outputStream.close();
212             } catch (IOException e) {
213                 logger.debug("Error while closing the output stream: {}", e.getMessage());
214             }
215         }
216         if (inputStream != null) {
217             try {
218                 inputStream.close();
219             } catch (IOException e) {
220                 logger.debug("Error while closing the input stream: {}", e.getMessage());
221             }
222         }
223         if (serialPort != null) {
224             serialPort.close();
225         }
226         outputStream = null;
227         inputStream = null;
228         serialPort = null;
229     }
230 }