2 * Copyright (c) 2010-2022 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.rfxcom.internal.handler;
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;
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;
60 * {@link RFXComBridgeHandler} is the handler for a RFXCOM transceivers. All
61 * {@link RFXComHandler}s use the {@link RFXComBridgeHandler} to execute the
64 * @author Pauli Anttila - Initial contribution
66 public class RFXComBridgeHandler extends BaseBridgeHandler {
67 private Logger logger = LoggerFactory.getLogger(RFXComBridgeHandler.class);
69 private RFXComConnectorInterface connector = null;
70 private MessageListener eventListener = new MessageListener();
72 private List<DeviceMessageListener> deviceStatusListeners = new CopyOnWriteArrayList<>();
74 private RFXComBridgeConfiguration configuration = null;
75 private ScheduledFuture<?> connectorTask;
77 private SerialPortManager serialPortManager;
79 private RFXComMessageFactory messageFactory;
81 private class TransmitQueue {
82 private Queue<RFXComBaseMessage> queue = new LinkedBlockingQueue<>();
84 public synchronized void enqueue(RFXComBaseMessage msg) throws IOException {
85 boolean wasEmpty = queue.isEmpty();
86 if (queue.offer(msg)) {
91 logger.error("Transmit queue overflow. Lost message: {}", msg);
95 public synchronized void sendNext() throws IOException {
100 public synchronized void send() throws IOException {
101 while (!queue.isEmpty()) {
102 RFXComBaseMessage msg = queue.peek();
105 byte[] data = msg.decodeMessage();
106 if (logger.isDebugEnabled()) {
107 logger.debug("Transmitting bytes '{}' for message '{}'", HexUtils.bytesToHex(data), msg);
109 connector.sendMessage(data);
111 } catch (RFXComException rfxe) {
112 logger.error("Error during send of {}", msg, rfxe);
119 private TransmitQueue transmitQueue = new TransmitQueue();
121 public RFXComBridgeHandler(@NonNull Bridge br, SerialPortManager serialPortManager) {
123 this.serialPortManager = serialPortManager;
124 this.messageFactory = RFXComMessageFactoryImpl.INSTANCE;
127 public RFXComBridgeHandler(@NonNull Bridge br, SerialPortManager serialPortManager,
128 RFXComMessageFactory messageFactory) {
130 this.serialPortManager = serialPortManager;
131 this.messageFactory = messageFactory;
135 public Collection<Class<? extends ThingHandlerService>> getServices() {
136 return Collections.singleton(RFXComDeviceDiscoveryService.class);
140 public void handleCommand(ChannelUID channelUID, Command command) {
141 logger.debug("Bridge commands not supported.");
145 public synchronized void dispose() {
146 logger.debug("Handler disposed.");
148 for (DeviceMessageListener deviceStatusListener : deviceStatusListeners) {
149 unregisterDeviceStatusListener(deviceStatusListener);
152 if (connector != null) {
153 connector.removeEventListener(eventListener);
154 connector.disconnect();
158 if (connectorTask != null && !connectorTask.isCancelled()) {
159 connectorTask.cancel(true);
160 connectorTask = null;
167 public void initialize() {
168 logger.debug("Initializing RFXCOM bridge handler");
169 updateStatus(ThingStatus.OFFLINE);
171 configuration = getConfigAs(RFXComBridgeConfiguration.class);
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");
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) {
186 }, 0, 60, TimeUnit.SECONDS);
190 private synchronized void connect() {
191 logger.debug("Connecting to RFXCOM transceiver");
194 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
195 if (configuration.serialPort != null) {
196 if (connector == null) {
197 connector = new RFXComSerialConnector(serialPortManager, readerThreadName);
199 } else if (configuration.bridgeId != null) {
200 if (connector == null) {
201 connector = new RFXComJD2XXConnector(readerThreadName);
203 } else if (configuration.host != null) {
204 if (connector == null) {
205 connector = new RFXComTcpConnector(readerThreadName);
209 if (connector != null) {
210 connector.disconnect();
211 connector.connect(configuration);
213 logger.debug("Reset controller");
214 connector.sendMessage(RFXComInterfaceMessage.CMD_RESET);
216 // controller does not response immediately after reset,
220 connector.addEventListener(eventListener);
222 logger.debug("Get status of controller");
223 connector.sendMessage(RFXComInterfaceMessage.CMD_GET_STATUS);
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.");
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);
244 public void sendMessage(RFXComMessage msg) {
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());
254 private class MessageListener implements RFXComEventListener {
257 public void packetReceived(byte[] packet) {
259 RFXComMessage message = messageFactory.createMessage(packet);
260 logger.debug("Message received: {}", message);
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) {
271 * Versions before 1000 had some different behaviour, so lets encourage upgrading.
272 * 1001 was released in Feb 2016!
275 "RFXCOM device using outdated firmware (version {}), consider flashing with more a more recent version",
276 msg.firmwareVersion);
278 thing.setProperty(Thing.PROPERTY_HARDWARE_VERSION,
279 msg.hardwareVersion1 + "." + msg.hardwareVersion2);
280 thing.setProperty(Thing.PROPERTY_FIRMWARE_VERSION, Integer.toString(msg.firmwareVersion));
282 if (configuration.ignoreConfig) {
283 logger.debug("Ignoring transceiver configuration");
285 byte[] setMode = null;
287 if (configuration.setMode != null && !configuration.setMode.isEmpty()) {
289 setMode = HexUtils.hexToBytes(configuration.setMode);
290 if (setMode.length != 14) {
291 logger.warn("Invalid RFXCOM transceiver mode configuration");
294 } catch (IllegalArgumentException ee) {
295 logger.warn("Failed to parse setMode data", ee);
298 RFXComInterfaceControlMessage modeMsg = new RFXComInterfaceControlMessage(
299 msg.transceiverType, configuration);
300 setMode = modeMsg.decodeMessage();
303 if (setMode != null) {
304 if (logger.isDebugEnabled()) {
305 logger.debug("Setting RFXCOM mode using: {}", HexUtils.bytesToHex(setMode));
307 connector.sendMessage(setMode);
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);
317 } else if (msg.subType == SubType.START_RECEIVER) {
318 updateStatus(ThingStatus.ONLINE);
319 logger.debug("Start TX of any queued messages");
320 transmitQueue.send();
322 logger.debug("Interface response received: {}", msg);
323 transmitQueue.sendNext();
325 } else if (message instanceof RFXComTransmitterMessage) {
326 RFXComTransmitterMessage resp = (RFXComTransmitterMessage) message;
328 logger.debug("Transmitter response received: {}", resp);
330 transmitQueue.sendNext();
331 } else if (message instanceof RFXComDeviceMessage) {
332 for (DeviceMessageListener deviceStatusListener : deviceStatusListeners) {
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);
342 logger.warn("The received message cannot be processed, please create an "
343 + "issue at the relevant tracker. Received message: {}", message);
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");
355 public void errorOccurred(String error) {
356 logger.error("Error occurred: {}", error);
357 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
361 public boolean registerDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
362 if (deviceStatusListener == null) {
363 throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
365 return !deviceStatusListeners.contains(deviceStatusListener) && deviceStatusListeners.add(deviceStatusListener);
368 public boolean unregisterDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
369 if (deviceStatusListener == null) {
370 throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
372 return deviceStatusListeners.remove(deviceStatusListener);
375 public RFXComBridgeConfiguration getConfiguration() {
376 return configuration;