]> git.basschouten.com Git - openhab-addons.git/blob
07076a029fbd864fe3923eb384c8e50ceda69fda
[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.lutron.internal.hw;
14
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;
22 import java.util.Collections;
23 import java.util.TooManyListenersException;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
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;
44
45 /**
46  *
47  * This is the main handler for HomeWorks RS232 Processors.
48  *
49  * @author Andrew Shilliday - Initial contribution
50  *
51  */
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");
56
57     private String serialPortName;
58     private int baudRate;
59     private Boolean updateTime;
60     private ScheduledFuture<?> updateTimeJob;
61
62     private HwDiscoveryService discoveryService;
63
64     private final SerialPortManager serialPortManager;
65     private SerialPort serialPort;
66     private OutputStreamWriter serialOutput;
67     private BufferedReader serialInput;
68
69     public HwSerialBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
70         super(bridge);
71         this.serialPortManager = serialPortManager;
72     }
73
74     @Override
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;
82         } else {
83             baudRate = configuration.getBaudRate().intValue();
84         }
85
86         if (serialPortName == null) {
87             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port not specified");
88             return;
89         }
90
91         logger.debug("Lutron HomeWorks RS232 Bridge Handler Initializing.");
92         logger.debug("   Serial Port: {},", serialPortName);
93         logger.debug("   Baud:        {},", baudRate);
94
95         scheduler.execute(() -> openConnection());
96     }
97
98     public void setDiscoveryService(HwDiscoveryService discoveryService) {
99         this.discoveryService = discoveryService;
100     }
101
102     private void openConnection() {
103         SerialPortIdentifier portIdentifier = serialPortManager.getIdentifier(serialPortName);
104         if (portIdentifier == null) {
105             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Invalid port: " + serialPortName);
106             return;
107         }
108
109         try {
110             logger.info("Connecting to Lutron HomeWorks Processor using {}.", serialPortName);
111             serialPort = portIdentifier.open(this.getClass().getName(), 2000);
112
113             logger.debug("Connection established using {}.  Configuring IO parameters. ", serialPortName);
114
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"));
121
122             serialPort.addEventListener(this);
123             serialPort.notifyOnDataAvailable(true);
124
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
131
132             updateStatus(ThingStatus.ONLINE);
133
134             if (updateTime) {
135                 startUpdateProcessorTimeJob();
136             }
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.");
144         }
145     }
146
147     private void startUpdateProcessorTimeJob() {
148         if (updateTimeJob != null) {
149             logger.debug("Canceling old scheduled job");
150             updateTimeJob.cancel(false);
151             updateTimeJob = null;
152         }
153
154         updateTimeJob = scheduler.scheduleWithFixedDelay(() -> updateProcessorTime(), 0, 1, TimeUnit.DAYS);
155     }
156
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);
162
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;
168             }
169             return;
170         }
171
172         sendCommand("SD, " + dateString);
173         sendCommand("ST, " + timeString);
174     }
175
176     @Override
177     public Collection<Class<? extends ThingHandlerService>> getServices() {
178         return Collections.singleton(HwDiscoveryService.class);
179     }
180
181     @Override
182     public void handleCommand(ChannelUID channelUID, Command command) {
183         logger.debug("Unexpected command for HomeWorks Bridge: {} - {}", channelUID, command);
184     }
185
186     private void handleIncomingMessage(String line) {
187         if (line == null || line.isEmpty()) {
188             return;
189         }
190
191         logger.debug("Received message from HomeWorks processor: {}", line);
192         String[] data = line.replaceAll("\\s", "").toUpperCase().split(",");
193         if ("DL".equals(data[0])) {
194             try {
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);
200                 } else {
201                     handler.handleLevelChange(level);
202                 }
203             } catch (RuntimeException e) {
204                 logger.error("Error parsing incoming message", e);
205             }
206         }
207     }
208
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())) {
214                     return handler;
215                 }
216             }
217         }
218         return null;
219     }
220
221     /**
222      * Receives Serial Port Events and reads Serial Port Data.
223      *
224      * @param serialPortEvent
225      */
226     @Override
227     public void serialEvent(SerialPortEvent serialPortEvent) {
228         if (serialPortEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
229             try {
230                 while (true) {
231                     String messageLine = serialInput.readLine();
232                     if (messageLine == null) {
233                         break;
234                     }
235                     handleIncomingMessage(messageLine);
236                 }
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");
240             }
241         }
242     }
243
244     public void sendCommand(String command) {
245         try {
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.");
252         }
253     }
254
255     @Override
256     public void dispose() {
257         logger.info("HomeWorks bridge being disposed.");
258         if (serialPort != null) {
259             serialPort.close();
260         }
261
262         serialPort = null;
263         serialInput = null;
264         serialOutput = null;
265
266         if (updateTimeJob != null) {
267             updateTimeJob.cancel(false);
268         }
269
270         logger.debug("Finished disposing bridge.");
271     }
272 }