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