]> git.basschouten.com Git - openhab-addons.git/blob
2adc3db362784617edf83d19e96ac8c7f79c1f08
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.rfxcom.internal.handler;
14
15 import java.io.IOException;
16 import java.util.List;
17 import java.util.Queue;
18 import java.util.concurrent.CopyOnWriteArrayList;
19 import java.util.concurrent.LinkedBlockingQueue;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.openhab.binding.rfxcom.internal.DeviceMessageListener;
25 import org.openhab.binding.rfxcom.internal.config.RFXComBridgeConfiguration;
26 import org.openhab.binding.rfxcom.internal.connector.RFXComConnectorInterface;
27 import org.openhab.binding.rfxcom.internal.connector.RFXComEventListener;
28 import org.openhab.binding.rfxcom.internal.connector.RFXComJD2XXConnector;
29 import org.openhab.binding.rfxcom.internal.connector.RFXComSerialConnector;
30 import org.openhab.binding.rfxcom.internal.connector.RFXComTcpConnector;
31 import org.openhab.binding.rfxcom.internal.exceptions.RFXComException;
32 import org.openhab.binding.rfxcom.internal.exceptions.RFXComMessageNotImplementedException;
33 import org.openhab.binding.rfxcom.internal.messages.RFXComBaseMessage;
34 import org.openhab.binding.rfxcom.internal.messages.RFXComDeviceMessage;
35 import org.openhab.binding.rfxcom.internal.messages.RFXComInterfaceControlMessage;
36 import org.openhab.binding.rfxcom.internal.messages.RFXComInterfaceMessage;
37 import org.openhab.binding.rfxcom.internal.messages.RFXComInterfaceMessage.Commands;
38 import org.openhab.binding.rfxcom.internal.messages.RFXComInterfaceMessage.SubType;
39 import org.openhab.binding.rfxcom.internal.messages.RFXComMessage;
40 import org.openhab.binding.rfxcom.internal.messages.RFXComMessageFactory;
41 import org.openhab.binding.rfxcom.internal.messages.RFXComTransmitterMessage;
42 import org.openhab.core.io.transport.serial.SerialPortManager;
43 import org.openhab.core.thing.Bridge;
44 import org.openhab.core.thing.ChannelUID;
45 import org.openhab.core.thing.Thing;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingStatusDetail;
48 import org.openhab.core.thing.binding.BaseBridgeHandler;
49 import org.openhab.core.types.Command;
50 import org.openhab.core.util.HexUtils;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
53
54 /**
55  * {@link RFXComBridgeHandler} is the handler for a RFXCOM transceivers. All
56  * {@link RFXComHandler}s use the {@link RFXComBridgeHandler} to execute the
57  * actual commands.
58  *
59  * @author Pauli Anttila - Initial contribution
60  */
61 public class RFXComBridgeHandler extends BaseBridgeHandler {
62     private Logger logger = LoggerFactory.getLogger(RFXComBridgeHandler.class);
63
64     private RFXComConnectorInterface connector = null;
65     private MessageListener eventListener = new MessageListener();
66
67     private List<DeviceMessageListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
68
69     private RFXComBridgeConfiguration configuration = null;
70     private ScheduledFuture<?> connectorTask;
71
72     private SerialPortManager serialPortManager;
73
74     private class TransmitQueue {
75         private Queue<RFXComBaseMessage> queue = new LinkedBlockingQueue<>();
76
77         public synchronized void enqueue(RFXComBaseMessage msg) throws IOException {
78             boolean wasEmpty = queue.isEmpty();
79             if (queue.offer(msg)) {
80                 if (wasEmpty) {
81                     send();
82                 }
83             } else {
84                 logger.error("Transmit queue overflow. Lost message: {}", msg);
85             }
86         }
87
88         public synchronized void sendNext() throws IOException {
89             queue.poll();
90             send();
91         }
92
93         public synchronized void send() throws IOException {
94             while (!queue.isEmpty()) {
95                 RFXComBaseMessage msg = queue.peek();
96
97                 try {
98                     logger.debug("Transmitting message '{}'", msg);
99                     byte[] data = msg.decodeMessage();
100                     connector.sendMessage(data);
101                     break;
102                 } catch (RFXComException rfxe) {
103                     logger.error("Error during send of {}", msg, rfxe);
104                     queue.poll();
105                 }
106             }
107         }
108     }
109
110     private TransmitQueue transmitQueue = new TransmitQueue();
111
112     public RFXComBridgeHandler(@NonNull Bridge br, SerialPortManager serialPortManager) {
113         super(br);
114         this.serialPortManager = serialPortManager;
115     }
116
117     @Override
118     public void handleCommand(ChannelUID channelUID, Command command) {
119         logger.debug("Bridge commands not supported.");
120     }
121
122     @Override
123     public synchronized void dispose() {
124         logger.debug("Handler disposed.");
125
126         for (DeviceMessageListener deviceStatusListener : deviceStatusListeners) {
127             unregisterDeviceStatusListener(deviceStatusListener);
128         }
129
130         if (connector != null) {
131             connector.removeEventListener(eventListener);
132             connector.disconnect();
133             connector = null;
134         }
135
136         if (connectorTask != null && !connectorTask.isCancelled()) {
137             connectorTask.cancel(true);
138             connectorTask = null;
139         }
140
141         super.dispose();
142     }
143
144     @Override
145     public void initialize() {
146         logger.debug("Initializing RFXCOM bridge handler");
147         updateStatus(ThingStatus.OFFLINE);
148
149         configuration = getConfigAs(RFXComBridgeConfiguration.class);
150
151         if (configuration.serialPort != null && configuration.serialPort.startsWith("rfc2217")) {
152             logger.debug("Please use the Transceiver over TCP/IP bridge type for a serial over IP connection.");
153             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
154                     "Please use the Transceiver over TCP/IP bridge type for a serial over IP connection.");
155             return;
156         }
157
158         if (connectorTask == null || connectorTask.isCancelled()) {
159             connectorTask = scheduler.scheduleWithFixedDelay(() -> {
160                 logger.trace("Checking RFXCOM transceiver connection, thing status = {}", thing.getStatus());
161                 if (thing.getStatus() != ThingStatus.ONLINE) {
162                     connect();
163                 }
164             }, 0, 60, TimeUnit.SECONDS);
165         }
166     }
167
168     private synchronized void connect() {
169         logger.debug("Connecting to RFXCOM transceiver");
170
171         try {
172             String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
173             if (configuration.serialPort != null) {
174                 if (connector == null) {
175                     connector = new RFXComSerialConnector(serialPortManager, readerThreadName);
176                 }
177             } else if (configuration.bridgeId != null) {
178                 if (connector == null) {
179                     connector = new RFXComJD2XXConnector(readerThreadName);
180                 }
181             } else if (configuration.host != null) {
182                 if (connector == null) {
183                     connector = new RFXComTcpConnector(readerThreadName);
184                 }
185             }
186
187             if (connector != null) {
188                 connector.disconnect();
189                 connector.connect(configuration);
190
191                 logger.debug("Reset controller");
192                 connector.sendMessage(RFXComMessageFactory.CMD_RESET);
193
194                 // controller does not response immediately after reset,
195                 // so wait a while
196                 Thread.sleep(300);
197
198                 connector.addEventListener(eventListener);
199
200                 logger.debug("Get status of controller");
201                 connector.sendMessage(RFXComMessageFactory.CMD_GET_STATUS);
202             }
203         } catch (IOException e) {
204             logger.error("Connection to RFXCOM transceiver failed", e);
205             if ("device not opened (3)".equalsIgnoreCase(e.getMessage())) {
206                 if (connector instanceof RFXComJD2XXConnector) {
207                     logger.info("Automatically Discovered RFXCOM bridges use FTDI chip driver (D2XX)."
208                             + " Reason for this error normally is related to operating system native FTDI drivers,"
209                             + " which prevent D2XX driver to open device."
210                             + " To solve this problem, uninstall OS FTDI native drivers or add manually universal bridge 'RFXCOM USB Transceiver',"
211                             + " which use normal serial port driver rather than D2XX.");
212                 }
213             }
214         } catch (Exception e) {
215             logger.error("Connection to RFXCOM transceiver failed", e);
216         } catch (UnsatisfiedLinkError e) {
217             logger.error("Error occurred when trying to load native library for OS '{}' version '{}', processor '{}'",
218                     System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"), e);
219         }
220     }
221
222     public void sendMessage(RFXComMessage msg) {
223         try {
224             RFXComBaseMessage baseMsg = (RFXComBaseMessage) msg;
225             transmitQueue.enqueue(baseMsg);
226         } catch (IOException e) {
227             logger.error("I/O Error", e);
228             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
229         }
230     }
231
232     private class MessageListener implements RFXComEventListener {
233
234         @Override
235         public void packetReceived(byte[] packet) {
236             try {
237                 RFXComMessage message = RFXComMessageFactory.createMessage(packet);
238                 logger.debug("Message received: {}", message);
239
240                 if (message instanceof RFXComInterfaceMessage) {
241                     RFXComInterfaceMessage msg = (RFXComInterfaceMessage) message;
242                     if (msg.subType == SubType.RESPONSE) {
243                         if (msg.command == Commands.GET_STATUS) {
244                             logger.debug("RFXCOM transceiver/receiver type: {}, hw version: {}.{}, fw version: {}",
245                                     msg.transceiverType, msg.hardwareVersion1, msg.hardwareVersion2,
246                                     msg.firmwareVersion);
247                             thing.setProperty(Thing.PROPERTY_HARDWARE_VERSION,
248                                     msg.hardwareVersion1 + "." + msg.hardwareVersion2);
249                             thing.setProperty(Thing.PROPERTY_FIRMWARE_VERSION, Integer.toString(msg.firmwareVersion));
250
251                             if (configuration.ignoreConfig) {
252                                 logger.debug("Ignoring transceiver configuration");
253                             } else {
254                                 byte[] setMode = null;
255
256                                 if (configuration.setMode != null && !configuration.setMode.isEmpty()) {
257                                     try {
258                                         setMode = HexUtils.hexToBytes(configuration.setMode);
259                                         if (setMode.length != 14) {
260                                             logger.warn("Invalid RFXCOM transceiver mode configuration");
261                                             setMode = null;
262                                         }
263                                     } catch (IllegalArgumentException ee) {
264                                         logger.warn("Failed to parse setMode data", ee);
265                                     }
266                                 } else {
267                                     RFXComInterfaceControlMessage modeMsg = new RFXComInterfaceControlMessage(
268                                             msg.transceiverType, configuration);
269                                     setMode = modeMsg.decodeMessage();
270                                 }
271
272                                 if (setMode != null) {
273                                     if (logger.isDebugEnabled()) {
274                                         logger.debug("Setting RFXCOM mode using: {}", HexUtils.bytesToHex(setMode));
275                                     }
276                                     connector.sendMessage(setMode);
277                                 }
278                             }
279
280                             // No need to wait for a response to any set mode. We start
281                             // regardless of whether it fails and the RFXCOM's buffer
282                             // is big enough to queue up the command.
283                             logger.debug("Start receiver");
284                             connector.sendMessage(RFXComMessageFactory.CMD_START_RECEIVER);
285                         }
286                     } else if (msg.subType == SubType.START_RECEIVER) {
287                         updateStatus(ThingStatus.ONLINE);
288                         logger.debug("Start TX of any queued messages");
289                         transmitQueue.send();
290                     } else {
291                         logger.debug("Interface response received: {}", msg);
292                         transmitQueue.sendNext();
293                     }
294                 } else if (message instanceof RFXComTransmitterMessage) {
295                     RFXComTransmitterMessage resp = (RFXComTransmitterMessage) message;
296
297                     logger.debug("Transmitter response received: {}", resp);
298
299                     transmitQueue.sendNext();
300                 } else if (message instanceof RFXComDeviceMessage) {
301                     for (DeviceMessageListener deviceStatusListener : deviceStatusListeners) {
302                         try {
303                             deviceStatusListener.onDeviceMessageReceived(getThing().getUID(),
304                                     (RFXComDeviceMessage) message);
305                         } catch (Exception e) {
306                             // catch all exceptions give all handlers a fair chance of handling the messages
307                             logger.error("An exception occurred while calling the DeviceStatusListener", e);
308                         }
309                     }
310                 } else {
311                     logger.warn("The received message cannot be processed, please create an "
312                             + "issue at the relevant tracker. Received message: {}", message);
313                 }
314             } catch (RFXComMessageNotImplementedException e) {
315                 logger.debug("Message not supported, data: {}", HexUtils.bytesToHex(packet));
316             } catch (RFXComException e) {
317                 logger.error("Error occurred during packet receiving, data: {}", HexUtils.bytesToHex(packet), e);
318             } catch (IOException e) {
319                 errorOccurred("I/O error");
320             }
321         }
322
323         @Override
324         public void errorOccurred(String error) {
325             logger.error("Error occurred: {}", error);
326             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
327         }
328     }
329
330     public boolean registerDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
331         if (deviceStatusListener == null) {
332             throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
333         }
334         return !deviceStatusListeners.contains(deviceStatusListener) && deviceStatusListeners.add(deviceStatusListener);
335     }
336
337     public boolean unregisterDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
338         if (deviceStatusListener == null) {
339             throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
340         }
341         return deviceStatusListeners.remove(deviceStatusListener);
342     }
343
344     public RFXComBridgeConfiguration getConfiguration() {
345         return configuration;
346     }
347 }