]> git.basschouten.com Git - openhab-addons.git/blob
4f216049ceea4c2300e7e3ba6c3e8e4ee5f2b840
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.List;
18 import java.util.Queue;
19 import java.util.Set;
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 Set.of(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(
230                             """
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.\
236                                     """);
237                 }
238             }
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);
244         }
245     }
246
247     public void sendMessage(RFXComMessage msg) {
248         try {
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());
254         }
255     }
256
257     private class MessageListener implements RFXComEventListener {
258
259         @Override
260         public void packetReceived(byte[] packet) {
261             try {
262                 RFXComMessage message = messageFactory.createMessage(packet);
263                 logger.debug("Message received: {}", message);
264
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) {
272                                 /**
273                                  * Versions before 1000 had some different behaviour, so lets encourage upgrading.
274                                  * 1001 was released in Feb 2016!
275                                  */
276                                 logger.warn(
277                                         "RFXCOM device using outdated firmware (version {}), consider flashing with more a more recent version",
278                                         msg.firmwareVersion);
279                             }
280                             thing.setProperty(Thing.PROPERTY_HARDWARE_VERSION,
281                                     msg.hardwareVersion1 + "." + msg.hardwareVersion2);
282                             thing.setProperty(Thing.PROPERTY_FIRMWARE_VERSION, Integer.toString(msg.firmwareVersion));
283
284                             if (configuration.ignoreConfig) {
285                                 logger.debug("Ignoring transceiver configuration");
286                             } else {
287                                 byte[] setMode = null;
288
289                                 if (configuration.setMode != null && !configuration.setMode.isEmpty()) {
290                                     try {
291                                         setMode = HexUtils.hexToBytes(configuration.setMode);
292                                         if (setMode.length != 14) {
293                                             logger.warn("Invalid RFXCOM transceiver mode configuration");
294                                             setMode = null;
295                                         }
296                                     } catch (IllegalArgumentException ee) {
297                                         logger.warn("Failed to parse setMode data", ee);
298                                     }
299                                 } else {
300                                     RFXComInterfaceControlMessage modeMsg = new RFXComInterfaceControlMessage(
301                                             msg.transceiverType, configuration);
302                                     setMode = modeMsg.decodeMessage();
303                                 }
304
305                                 if (setMode != null) {
306                                     if (logger.isDebugEnabled()) {
307                                         logger.debug("Setting RFXCOM mode using: {}", HexUtils.bytesToHex(setMode));
308                                     }
309                                     connector.sendMessage(setMode);
310                                 }
311                             }
312
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);
318                         }
319                     } else if (msg.subType == SubType.START_RECEIVER) {
320                         updateStatus(ThingStatus.ONLINE);
321                         logger.debug("Start TX of any queued messages");
322                         transmitQueue.send();
323                     } else {
324                         logger.debug("Interface response received: {}", msg);
325                         transmitQueue.sendNext();
326                     }
327                 } else if (message instanceof RFXComTransmitterMessage resp) {
328                     logger.debug("Transmitter response received: {}", resp);
329
330                     transmitQueue.sendNext();
331                 } else if (message instanceof RFXComDeviceMessage deviceMessage) {
332                     for (DeviceMessageListener deviceStatusListener : deviceStatusListeners) {
333                         try {
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);
338                         }
339                     }
340                 } else {
341                     logger.warn("""
342                             The received message cannot be processed, please create an \
343                             issue at the relevant tracker. Received message: {}\
344                             """, message);
345                 }
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");
352             }
353         }
354
355         @Override
356         public void errorOccurred(String error) {
357             logger.error("Error occurred: {}", error);
358             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
359         }
360     }
361
362     public boolean registerDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
363         if (deviceStatusListener == null) {
364             throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
365         }
366         return !deviceStatusListeners.contains(deviceStatusListener) && deviceStatusListeners.add(deviceStatusListener);
367     }
368
369     public boolean unregisterDeviceStatusListener(DeviceMessageListener deviceStatusListener) {
370         if (deviceStatusListener == null) {
371             throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener.");
372         }
373         return deviceStatusListeners.remove(deviceStatusListener);
374     }
375
376     public RFXComBridgeConfiguration getConfiguration() {
377         return configuration;
378     }
379 }