2 * Copyright (c) 2010-2023 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.List;
18 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 Set.of(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) {
231 Automatically Discovered RFXCOM bridges use FTDI chip driver (D2XX).\
232 Reason for this error normally is related to operating system native FTDI drivers,\
233 which prevent D2XX driver to open device.\
234 To solve this problem, uninstall OS FTDI native drivers or add manually universal bridge 'RFXCOM USB Transceiver',\
235 which use normal serial port driver rather than D2XX.\
239 } catch (Exception e) {
240 logger.error("Connection to RFXCOM transceiver failed", e);
241 } catch (UnsatisfiedLinkError e) {
242 logger.error("Error occurred when trying to load native library for OS '{}' version '{}', processor '{}'",
243 System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"), e);
247 public void sendMessage(RFXComMessage msg) {
249 RFXComBaseMessage baseMsg = (RFXComBaseMessage) msg;
250 transmitQueue.enqueue(baseMsg);
251 } catch (IOException e) {
252 logger.error("I/O Error", e);
253 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
257 private class MessageListener implements RFXComEventListener {
260 public void packetReceived(byte[] packet) {
262 RFXComMessage message = messageFactory.createMessage(packet);
263 logger.debug("Message received: {}", message);
265 if (message instanceof RFXComInterfaceMessage msg) {
266 if (msg.subType == SubType.RESPONSE) {
267 if (msg.command == Commands.GET_STATUS) {
268 logger.debug("RFXCOM transceiver/receiver type: {}, hw version: {}.{}, fw version: {}",
269 msg.transceiverType, msg.hardwareVersion1, msg.hardwareVersion2,
270 msg.firmwareVersion);
271 if (msg.firmwareVersion < 1000) {
273 * Versions before 1000 had some different behaviour, so lets encourage upgrading.
274 * 1001 was released in Feb 2016!
277 "RFXCOM device using outdated firmware (version {}), consider flashing with more a more recent version",
278 msg.firmwareVersion);
280 thing.setProperty(Thing.PROPERTY_HARDWARE_VERSION,
281 msg.hardwareVersion1 + "." + msg.hardwareVersion2);
282 thing.setProperty(Thing.PROPERTY_FIRMWARE_VERSION, Integer.toString(msg.firmwareVersion));
284 if (configuration.ignoreConfig) {
285 logger.debug("Ignoring transceiver configuration");
287 byte[] setMode = null;
289 if (configuration.setMode != null && !configuration.setMode.isEmpty()) {
291 setMode = HexUtils.hexToBytes(configuration.setMode);
292 if (setMode.length != 14) {
293 logger.warn("Invalid RFXCOM transceiver mode configuration");
296 } catch (IllegalArgumentException ee) {
297 logger.warn("Failed to parse setMode data", ee);
300 RFXComInterfaceControlMessage modeMsg = new RFXComInterfaceControlMessage(
301 msg.transceiverType, configuration);
302 setMode = modeMsg.decodeMessage();
305 if (setMode != null) {
306 if (logger.isDebugEnabled()) {
307 logger.debug("Setting RFXCOM mode using: {}", HexUtils.bytesToHex(setMode));
309 connector.sendMessage(setMode);
313 // No need to wait for a response to any set mode. We start
314 // regardless of whether it fails and the RFXCOM's buffer
315 // is big enough to queue up the command.
316 logger.debug("Start receiver");
317 connector.sendMessage(RFXComInterfaceMessage.CMD_START_RECEIVER);
319 } else if (msg.subType == SubType.START_RECEIVER) {
320 updateStatus(ThingStatus.ONLINE);
321 logger.debug("Start TX of any queued messages");
322 transmitQueue.send();
324 logger.debug("Interface response received: {}", msg);
325 transmitQueue.sendNext();
327 } else if (message instanceof RFXComTransmitterMessage resp) {
328 logger.debug("Transmitter response received: {}", resp);
330 transmitQueue.sendNext();
331 } else if (message instanceof RFXComDeviceMessage deviceMessage) {
332 for (DeviceMessageListener deviceStatusListener : deviceStatusListeners) {
334 deviceStatusListener.onDeviceMessageReceived(getThing().getUID(), deviceMessage);
335 } catch (Exception e) {
336 // catch all exceptions give all handlers a fair chance of handling the messages
337 logger.error("An exception occurred while calling the DeviceStatusListener", e);
342 The received message cannot be processed, please create an \
343 issue at the relevant tracker. Received message: {}\
346 } catch (RFXComMessageNotImplementedException e) {
347 logger.debug("Message not supported, data: {}", HexUtils.bytesToHex(packet));
348 } catch (RFXComException e) {
349 logger.error("Error occurred during packet receiving, data: {}", HexUtils.bytesToHex(packet), e);
350 } catch (IOException e) {
351 errorOccurred("I/O error");
356 public void errorOccurred(String error) {
357 logger.error("Error occurred: {}", error);
358 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
362 public boolean registerDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
363 if (deviceStatusListener == null) {
364 throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
366 return !deviceStatusListeners.contains(deviceStatusListener) && deviceStatusListeners.add(deviceStatusListener);
369 public boolean unregisterDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
370 if (deviceStatusListener == null) {
371 throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
373 return deviceStatusListeners.remove(deviceStatusListener);
376 public RFXComBridgeConfiguration getConfiguration() {
377 return configuration;