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.lutron.internal.hw;
15 import java.io.BufferedReader;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.io.OutputStreamWriter;
19 import java.time.LocalDate;
20 import java.time.format.DateTimeFormatter;
21 import java.util.Collection;
23 import java.util.TooManyListenersException;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
27 import org.openhab.core.io.transport.serial.PortInUseException;
28 import org.openhab.core.io.transport.serial.SerialPort;
29 import org.openhab.core.io.transport.serial.SerialPortEvent;
30 import org.openhab.core.io.transport.serial.SerialPortEventListener;
31 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
32 import org.openhab.core.io.transport.serial.SerialPortManager;
33 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
34 import org.openhab.core.thing.Bridge;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.binding.BaseBridgeHandler;
40 import org.openhab.core.thing.binding.ThingHandlerService;
41 import org.openhab.core.types.Command;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
47 * This is the main handler for HomeWorks RS232 Processors.
49 * @author Andrew Shilliday - Initial contribution
52 public class HwSerialBridgeHandler extends BaseBridgeHandler implements SerialPortEventListener {
53 private final Logger logger = LoggerFactory.getLogger(HwSerialBridgeHandler.class);
54 private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM/dd/yyyy");
55 private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
57 private String serialPortName;
59 private Boolean updateTime;
60 private ScheduledFuture<?> updateTimeJob;
62 private HwDiscoveryService discoveryService;
64 private final SerialPortManager serialPortManager;
65 private SerialPort serialPort;
66 private OutputStreamWriter serialOutput;
67 private BufferedReader serialInput;
69 public HwSerialBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
71 this.serialPortManager = serialPortManager;
75 public void initialize() {
76 logger.debug("Initializing the Lutron HomeWorks RS232 bridge handler");
77 HwSerialBridgeConfig configuration = getConfigAs(HwSerialBridgeConfig.class);
78 serialPortName = configuration.getSerialPort();
79 updateTime = configuration.getUpdateTime();
80 if (configuration.getBaudRate() == null) {
81 baudRate = HwSerialBridgeConfig.DEFAULT_BAUD;
83 baudRate = configuration.getBaudRate().intValue();
86 if (serialPortName == null) {
87 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port not specified");
91 logger.debug("Lutron HomeWorks RS232 Bridge Handler Initializing.");
92 logger.debug(" Serial Port: {},", serialPortName);
93 logger.debug(" Baud: {},", baudRate);
95 scheduler.execute(() -> openConnection());
98 public void setDiscoveryService(HwDiscoveryService discoveryService) {
99 this.discoveryService = discoveryService;
102 private void openConnection() {
103 SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
104 if (portIdentifier == null) {
105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Invalid port: " + serialPortName);
110 logger.info("Connecting to Lutron HomeWorks Processor using {}.", serialPortName);
111 serialPort = portIdentifier.open(this.getClass().getName(), 2000);
113 logger.debug("Connection established using {}. Configuring IO parameters. ", serialPortName);
115 int db = SerialPort.DATABITS_8, sb = SerialPort.STOPBITS_1, p = SerialPort.PARITY_NONE;
116 serialPort.setSerialPortParams(baudRate, db, sb, p);
117 serialPort.enableReceiveThreshold(1);
118 serialPort.disableReceiveTimeout();
119 serialOutput = new OutputStreamWriter(serialPort.getOutputStream(), "US-ASCII");
120 serialInput = new BufferedReader(new InputStreamReader(serialPort.getInputStream(), "US-ASCII"));
122 serialPort.addEventListener(this);
123 serialPort.notifyOnDataAvailable(true);
125 logger.debug("Sending monitoring commands.");
126 sendCommand("PROMPTOFF");
127 sendCommand("KBMOFF");
128 sendCommand("KLMOFF");
129 sendCommand("GSMOFF");
130 sendCommand("DLMON"); // Turn on dimmer monitoring
132 updateStatus(ThingStatus.ONLINE);
135 startUpdateProcessorTimeJob();
137 } catch (PortInUseException portInUseException) {
138 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Port in use: " + serialPortName);
139 } catch (UnsupportedCommOperationException | IOException e) {
140 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication error");
141 } catch (TooManyListenersException e) {
142 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
143 "Too many listeners to serial port.");
147 private void startUpdateProcessorTimeJob() {
148 if (updateTimeJob != null) {
149 logger.debug("Canceling old scheduled job");
150 updateTimeJob.cancel(false);
151 updateTimeJob = null;
154 updateTimeJob = scheduler.scheduleWithFixedDelay(() -> updateProcessorTime(), 0, 1, TimeUnit.DAYS);
157 private void updateProcessorTime() {
158 LocalDate date = LocalDate.now();
159 String dateString = date.format(dateFormat);
160 String timeString = date.format(timeFormat);
161 logger.debug("Updating HomeWorks processor date and time to {} {}", dateString, timeString);
163 if (!this.getBridge().getStatus().equals(ThingStatus.ONLINE)) {
164 logger.warn("HomeWorks Bridge is offline and cannot update time on HomeWorks processor.");
165 if (updateTimeJob != null) {
166 updateTimeJob.cancel(false);
167 updateTimeJob = null;
172 sendCommand("SD, " + dateString);
173 sendCommand("ST, " + timeString);
177 public Collection<Class<? extends ThingHandlerService>> getServices() {
178 return Set.of(HwDiscoveryService.class);
182 public void handleCommand(ChannelUID channelUID, Command command) {
183 logger.debug("Unexpected command for HomeWorks Bridge: {} - {}", channelUID, command);
186 private void handleIncomingMessage(String line) {
187 if (line == null || line.isEmpty()) {
191 logger.debug("Received message from HomeWorks processor: {}", line);
192 String[] data = line.replaceAll("\\s", "").toUpperCase().split(",");
193 if ("DL".equals(data[0])) {
195 String address = data[1];
196 Integer level = Integer.parseInt(data[2]);
197 HwDimmerHandler handler = findHandler(address);
198 if (handler == null) {
199 discoveryService.declareUnknownDimmer(address);
201 handler.handleLevelChange(level);
203 } catch (RuntimeException e) {
204 logger.error("Error parsing incoming message", e);
209 private HwDimmerHandler findHandler(String address) {
210 for (Thing thing : getThing().getThings()) {
211 if (thing.getHandler() instanceof HwDimmerHandler) {
212 HwDimmerHandler handler = (HwDimmerHandler) thing.getHandler();
213 if (address.equals(handler.getAddress())) {
222 * Receives Serial Port Events and reads Serial Port Data.
224 * @param serialPortEvent
227 public void serialEvent(SerialPortEvent serialPortEvent) {
228 if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
231 String messageLine = serialInput.readLine();
232 if (messageLine == null) {
235 handleIncomingMessage(messageLine);
237 } catch (IOException e) {
238 logger.debug("Error reading from serial port: {}", e.getMessage(), e);
239 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error reading from port");
244 public void sendCommand(String command) {
246 logger.debug("HomeWorks bridge sending command: {}", command);
247 serialOutput.write(command + "\r");
248 serialOutput.flush();
249 } catch (IOException e) {
250 logger.debug("Error writing to serial port: {}", e.getMessage(), e);
251 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Error writing to port.");
256 public void dispose() {
257 logger.info("HomeWorks bridge being disposed.");
258 if (serialPort != null) {
266 if (updateTimeJob != null) {
267 updateTimeJob.cancel(false);
270 logger.debug("Finished disposing bridge.");