]> git.basschouten.com Git - openhab-addons.git/blob
20e269f8300f685a6fc36a4497aa64cfd78a7d15
[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.knx.internal.client;
14
15 import java.util.concurrent.CompletableFuture;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.concurrent.TimeUnit;
19 import java.util.concurrent.TimeoutException;
20 import java.util.stream.Collectors;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
24 import org.openhab.core.io.transport.serial.SerialPortManager;
25 import org.openhab.core.thing.ThingUID;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import tuwien.auto.calimero.Connection.BlockingMode;
30 import tuwien.auto.calimero.KNXException;
31 import tuwien.auto.calimero.link.KNXNetworkLink;
32 import tuwien.auto.calimero.link.KNXNetworkLinkFT12;
33 import tuwien.auto.calimero.link.medium.TPSettings;
34 import tuwien.auto.calimero.serial.FT12Connection;
35
36 /**
37  * Serial specific {@link AbstractKNXClient} implementation.
38  *
39  * @author Simon Kaufmann - initial contribution and API.
40  *
41  */
42 @NonNullByDefault
43 public class SerialClient extends AbstractKNXClient {
44
45     private static final String CALIMERO_ERROR_CANNOT_OPEN_PORT = "failed to open serial port";
46
47     private final Logger logger = LoggerFactory.getLogger(SerialClient.class);
48
49     private final SerialPortManager serialPortManager;
50     private final String serialPort;
51     private final boolean useCemi;
52
53     public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
54             int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi,
55             SerialPortManager serialPortManager, StatusUpdateCallback statusUpdateCallback) {
56         super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
57                 statusUpdateCallback);
58         this.serialPortManager = serialPortManager;
59         this.serialPort = serialPort;
60         this.useCemi = useCemi;
61     }
62
63     /**
64      * try autodetection of cEMI devices via the PEI identification frame
65      *
66      * @implNote This is based on an vendor specific extension and may not work for other devices.
67      */
68     protected boolean detectCemi() throws InterruptedException {
69         final byte[] peiIdentifyReqFrame = { (byte) 0xa7 };
70         final byte peiIdentifyCon = (byte) 0xa8;
71         final byte peiWzIdentFrameLength = 11;
72
73         logger.trace("Checking for cEMI support");
74
75         try (FT12Connection serialConnection = new FT12Connection(serialPort)) {
76             final CompletableFuture<byte[]> frameListener = new CompletableFuture<byte[]>();
77             serialConnection.addConnectionListener(frameReceived -> {
78                 final byte[] content = frameReceived.getFrameBytes();
79                 if ((content.length > 0) && (content[0] == peiIdentifyCon)) {
80                     logger.trace("Received PEI confirmation of {} bytes", content.length);
81                     frameListener.complete(content);
82                 }
83             });
84
85             serialConnection.send(peiIdentifyReqFrame, BlockingMode.NonBlocking);
86             byte[] content = frameListener.get(1, TimeUnit.SECONDS);
87
88             if (peiWzIdentFrameLength == content.length) {
89                 // standard emi2 frame contain 9 bytes,
90                 // content[1..2] physical address
91                 // content[3..8] serial no
92                 //
93                 // Weinzierl adds 2 extra bytes, 0x0004 for capablity cEMI,
94                 // see "Weinzierl KNX BAOS Starter Kit, User Guide"
95                 if (0 == content[9] && 4 == content[10]) {
96                     logger.debug("Detected device with cEMI support");
97                     return true;
98                 }
99             }
100         } catch (final ExecutionException | TimeoutException | KNXException na) {
101             if (logger.isTraceEnabled()) {
102                 logger.trace("Exception detecting cEMI: ", na);
103             }
104         }
105
106         logger.trace("Did not detect device with cEMI support");
107         return false;
108     }
109
110     @Override
111     protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException {
112         try {
113             boolean useCemiL = useCemi;
114             if (!useCemiL) {
115                 useCemiL = detectCemi();
116             }
117             logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}{}{}", serialPort,
118                     (useCemiL ? " using cEMI" : ""), ((useCemiL != useCemi) ? " (autodetected)" : ""));
119             // CEMI support by Calimero library, userful for newer serial devices like KNX RF sticks, kBerry,
120             // etc.; default is still old EMI frame format
121             if (useCemiL) {
122                 return KNXNetworkLinkFT12.newCemiLink(serialPort, new TPSettings());
123             }
124
125             return new KNXNetworkLinkFT12(serialPort, new TPSettings());
126
127         } catch (NoClassDefFoundError e) {
128             throw new KNXException(
129                     "The serial FT1.2 KNX connection requires the serial libraries to be available, but they could not be found!",
130                     e);
131         } catch (KNXException e) {
132             final String msg = e.getMessage();
133             // TODO add a test for this string match; error message might change in later version of Calimero library
134             if ((msg != null) && (msg.startsWith(CALIMERO_ERROR_CANNOT_OPEN_PORT))) {
135                 String availablePorts = serialPortManager.getIdentifiers().map(SerialPortIdentifier::getName)
136                         .collect(Collectors.joining("\n"));
137                 if (!availablePorts.isEmpty()) {
138                     availablePorts = " Available ports are:\n" + availablePorts;
139                 }
140                 throw new KNXException("Serial port '" + serialPort + "' could not be opened." + availablePorts);
141             } else {
142                 throw e;
143             }
144         }
145     }
146 }