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