2 * Copyright (c) 2010-2024 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.knx.internal.client;
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;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler.CommandExtensionData;
24 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
25 import org.openhab.core.io.transport.serial.SerialPortManager;
26 import org.openhab.core.thing.ThingUID;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import tuwien.auto.calimero.Connection.BlockingMode;
31 import tuwien.auto.calimero.KNXException;
32 import tuwien.auto.calimero.link.KNXNetworkLink;
33 import tuwien.auto.calimero.link.KNXNetworkLinkFT12;
34 import tuwien.auto.calimero.link.medium.TPSettings;
35 import tuwien.auto.calimero.secure.Security;
36 import tuwien.auto.calimero.serial.FT12Connection;
39 * Serial specific {@link AbstractKNXClient} implementation.
41 * @author Simon Kaufmann - initial contribution and API.
45 public class SerialClient extends AbstractKNXClient {
47 private static final String CALIMERO_ERROR_CANNOT_OPEN_PORT = "failed to open serial port";
49 private final Logger logger = LoggerFactory.getLogger(SerialClient.class);
51 private final SerialPortManager serialPortManager;
52 private final String serialPort;
53 private final boolean useCemi;
55 public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
56 int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort, boolean useCemi,
57 SerialPortManager serialPortManager, CommandExtensionData commandExtensionData, Security openhabSecurity,
58 StatusUpdateCallback statusUpdateCallback) {
59 super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
60 commandExtensionData, openhabSecurity, statusUpdateCallback);
61 this.serialPortManager = serialPortManager;
62 this.serialPort = serialPort;
63 this.useCemi = useCemi;
67 * try automatic detection of cEMI devices via the PEI identification frame
69 * @implNote This is based on an vendor specific extension and may not work for other devices.
71 protected boolean detectCemi() throws InterruptedException {
72 final byte[] peiIdentifyReqFrame = { (byte) 0xa7 };
73 final byte peiIdentifyCon = (byte) 0xa8;
74 final byte peiWzIdentFrameLength = 11;
76 logger.trace("Checking for cEMI support");
78 try (FT12Connection serialConnection = new FT12Connection(serialPort)) {
79 final CompletableFuture<byte[]> frameListener = new CompletableFuture<>();
80 serialConnection.addConnectionListener(frameReceived -> {
81 final byte[] content = frameReceived.getFrameBytes();
82 if ((content.length > 0) && (content[0] == peiIdentifyCon)) {
83 logger.trace("Received PEI confirmation of {} bytes", content.length);
84 frameListener.complete(content);
88 serialConnection.send(peiIdentifyReqFrame, BlockingMode.NonBlocking);
89 byte[] content = frameListener.get(1, TimeUnit.SECONDS);
91 if (peiWzIdentFrameLength == content.length) {
92 // standard emi2 frame contain 9 bytes,
93 // content[1..2] physical address
94 // content[3..8] serial no
96 // Weinzierl adds 2 extra bytes, 0x0004 for capability cEMI,
97 // see "Weinzierl KNX BAOS Starter Kit, User Guide"
98 if (0 == content[9] && 4 == content[10]) {
99 logger.debug("Detected device with cEMI support");
103 } catch (final ExecutionException | TimeoutException | KNXException na) {
104 if (logger.isTraceEnabled()) {
105 logger.trace("Exception detecting cEMI: ", na);
109 logger.trace("Did not detect device with cEMI support");
114 protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException {
116 boolean useCemiL = useCemi;
118 useCemiL = detectCemi();
120 logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}{}{}", serialPort,
121 (useCemiL ? " using cEMI" : ""), ((useCemiL != useCemi) ? " (autodetected)" : ""));
122 // CEMI support by Calimero library, useful for newer serial devices like KNX RF sticks, kBerry,
123 // etc.; default is still old EMI frame format
125 return KNXNetworkLinkFT12.newCemiLink(serialPort, new TPSettings());
128 return new KNXNetworkLinkFT12(serialPort, new TPSettings());
130 } catch (NoClassDefFoundError e) {
131 throw new KNXException(
132 "The serial FT1.2 KNX connection requires the serial libraries to be available, but they could not be found!",
134 } catch (KNXException e) {
135 final String msg = e.getMessage();
136 // TODO add a test for this string match; error message might change in later version of Calimero library
137 if ((msg != null) && (msg.startsWith(CALIMERO_ERROR_CANNOT_OPEN_PORT))) {
138 String availablePorts = serialPortManager.getIdentifiers().map(SerialPortIdentifier::getName)
139 .collect(Collectors.joining("\n"));
140 if (!availablePorts.isEmpty()) {
141 availablePorts = " Available ports are:\n" + availablePorts;
143 throw new KNXException("Serial port '" + serialPort + "' could not be opened." + availablePorts);