added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.knx-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-knx" description="KNX Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.knx/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal;
import static java.util.stream.Collectors.toSet;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Stream;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link KNXBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Karel Goderis - Initial contribution
*/
public class KNXBindingConstants {
public static final String BINDING_ID = "knx";
// Thing Type UIDs
public static final ThingTypeUID THING_TYPE_IP_BRIDGE = new ThingTypeUID(BINDING_ID, "ip");
public static final ThingTypeUID THING_TYPE_SERIAL_BRIDGE = new ThingTypeUID(BINDING_ID, "serial");
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
// Property IDs
public static final String FIRMWARE_TYPE = "firmwaretype";
public static final String FIRMWARE_VERSION = "firmwareversion";
public static final String FIRMWARE_SUBVERSION = "firmwaresubversion";
public static final String MANUFACTURER_NAME = "manfacturername";
public static final String MANUFACTURER_SERIAL_NO = "manfacturerserialnumber";
public static final String MANUFACTURER_HARDWARE_TYPE = "manfacturerhardwaretype";
public static final String MANUFACTURER_FIRMWARE_REVISION = "manfacturerfirmwarerevision";
// Thing Configuration parameters
public static final String IP_ADDRESS = "ipAddress";
public static final String IP_CONNECTION_TYPE = "type";
public static final String LOCAL_IP = "localIp";
public static final String LOCAL_SOURCE_ADDRESS = "localSourceAddr";
public static final String PORT_NUMBER = "portNumber";
public static final String SERIAL_PORT = "serialPort";
// The default multicast ip address (see <a
// href="http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xml">iana</a> EIBnet/IP
public static final String DEFAULT_MULTICAST_IP = "224.0.23.12";
// Channel Type IDs
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_CONTROL = "color-control";
public static final String CHANNEL_CONTACT = "contact";
public static final String CHANNEL_CONTACT_CONTROL = "contact-control";
public static final String CHANNEL_DATETIME = "datetime";
public static final String CHANNEL_DATETIME_CONTROL = "datetime-control";
public static final String CHANNEL_DIMMER = "dimmer";
public static final String CHANNEL_DIMMER_CONTROL = "dimmer-control";
public static final String CHANNEL_NUMBER = "number";
public static final String CHANNEL_NUMBER_CONTROL = "number-control";
public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
public static final String CHANNEL_ROLLERSHUTTER_CONTROL = "rollershutter-control";
public static final String CHANNEL_STRING = "string";
public static final String CHANNEL_STRING_CONTROL = "string-control";
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_SWITCH_CONTROL = "switch-control";
public static final Set<String> CONTROL_CHANNEL_TYPES = Collections.unmodifiableSet(Stream.of(CHANNEL_COLOR_CONTROL, //
CHANNEL_CONTACT_CONTROL, //
CHANNEL_DATETIME_CONTROL, //
CHANNEL_DIMMER_CONTROL, //
CHANNEL_NUMBER_CONTROL, //
CHANNEL_ROLLERSHUTTER_CONTROL, //
CHANNEL_STRING_CONTROL, //
CHANNEL_SWITCH_CONTROL //
).collect(toSet()));
public static final String CHANNEL_RESET = "reset";
// Channel Configuration parameters
public static final String GA = "ga";
public static final String HSB_GA = "hsb";
public static final String INCREASE_DECREASE_GA = "increaseDecrease";
public static final String POSITION_GA = "position";
public static final String REPEAT_FREQUENCY = "frequency";
public static final String STOP_MOVE_GA = "stopMove";
public static final String SWITCH_GA = "switch";
public static final String UP_DOWN_GA = "upDown";
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Type;
import tuwien.auto.calimero.datapoint.Datapoint;
import tuwien.auto.calimero.process.ProcessEvent;
/**
* This interface must be implemented by classes that provide a type mapping
* between openHAB and KNX.
* When a command or status update is sent to an item on the openHAB event bus,
* it must be clear, in which format it must be sent to KNX and vice versa.
*
* @author Kai Kreuzer
*
*/
@NonNullByDefault
public interface KNXTypeMapper {
/**
* maps an openHAB command/state to a string value which correspond to its datapoint in KNX
*
* @param type a command or state
* @param dpt the corresponding datapoint type
* @return datapoint value as a string
*/
@Nullable
public String toDPTValue(Type type, @Nullable String dpt);
/**
* maps a datapoint value to an openHAB command or state
*
* @param datapoint the source datapoint
* @param data the datapoint value as an ASDU byte array (see <code>{@link ProcessEvent}.getASDU()</code>)
* @return a command or state of openHAB
*/
@Nullable
public Type toType(Datapoint datapoint, byte[] data);
@Nullable
public Class<? extends Type> toTypeClass(@Nullable String dpt);
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.InboundSpec;
import org.openhab.binding.knx.internal.client.OutboundSpec;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.KNXFormatException;
/**
* Base class for telegram meta-data
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public abstract class AbstractSpec {
private String dpt;
protected AbstractSpec(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT) {
if (channelConfiguration != null) {
String configuredDPT = channelConfiguration.getDPT();
this.dpt = configuredDPT != null ? configuredDPT : defaultDPT;
} else {
this.dpt = defaultDPT;
}
}
/**
* Helper method to convert a {@link GroupAddressConfiguration} into a {@link GroupAddress}.
*
* @param ga the group address configuration
* @return a group address object
*/
protected final GroupAddress toGroupAddress(GroupAddressConfiguration ga) {
try {
return new GroupAddress(ga.getGA());
} catch (KNXFormatException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Return the data point type.
* <p>
* See {@link InboundSpec#getDPT()} and {@link OutboundSpec#getDPT()}.
*
* @return the data point type.
*/
public final String getDPT() {
return dpt;
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toList;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Data structure representing the content of a channel's group address configuration.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class ChannelConfiguration {
private final @Nullable String dpt;
private final GroupAddressConfiguration mainGA;
private final List<GroupAddressConfiguration> listenGAs;
public ChannelConfiguration(@Nullable String dpt, GroupAddressConfiguration mainGA,
List<GroupAddressConfiguration> listenGAs) {
this.dpt = dpt;
this.mainGA = mainGA;
this.listenGAs = listenGAs;
}
public @Nullable String getDPT() {
return dpt;
}
public GroupAddressConfiguration getMainGA() {
return mainGA;
}
public List<GroupAddressConfiguration> getListenGAs() {
return Stream.concat(Stream.of(mainGA), listenGAs.stream()).collect(toList());
}
public List<GroupAddressConfiguration> getReadGAs() {
return getListenGAs().stream().filter(ga -> ga.isRead()).collect(toList());
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Data structure representing a single group address configuration within a channel configuration parameter.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class GroupAddressConfiguration {
private final String ga;
private final boolean read;
public GroupAddressConfiguration(String ga, boolean read) {
super();
this.ga = ga;
this.read = read;
}
/**
* The group address.
*
* @return the group address.
*/
public String getGA() {
return ga;
}
/**
* Denotes whether the group address is marked to be actively read from.
*
* @return {@code true} if read requests should be issued to this address
*/
public boolean isRead() {
return read;
}
}

View File

@@ -0,0 +1,215 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.KNXTypeMapper;
import org.openhab.binding.knx.internal.client.InboundSpec;
import org.openhab.binding.knx.internal.client.OutboundSpec;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.types.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.KNXFormatException;
/**
* Meta-data abstraction for the KNX channel configurations.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public abstract class KNXChannelType {
private static final Pattern PATTERN = Pattern.compile(
"^((?<dpt>[0-9]{1,3}\\.[0-9]{3,4}):)?(?<read>\\<)?(?<mainGA>[0-9]{1,5}(/[0-9]{1,4}){0,2})(?<listenGAs>(\\+(\\<?[0-9]{1,5}(/[0-9]{1,4}){0,2}))*)$");
private static final Pattern PATTERN_LISTEN = Pattern
.compile("\\+((?<read>\\<)?(?<GA>[0-9]{1,5}(/[0-9]{1,4}){0,2}))");
private final Logger logger = LoggerFactory.getLogger(KNXChannelType.class);
private final Set<String> channelTypeIDs;
KNXChannelType(String... channelTypeIDs) {
this.channelTypeIDs = new HashSet<>(Arrays.asList(channelTypeIDs));
}
final Set<String> getChannelIDs() {
return channelTypeIDs;
}
@Nullable
protected final ChannelConfiguration parse(@Nullable String fancy) {
if (fancy == null) {
return null;
}
Matcher matcher = PATTERN.matcher(fancy.replace(" ", ""));
if (matcher.matches()) {
// Listen GAs
String input = matcher.group("listenGAs");
Matcher m2 = PATTERN_LISTEN.matcher(input);
List<GroupAddressConfiguration> listenGAs = new LinkedList<>();
while (m2.find()) {
listenGAs.add(new GroupAddressConfiguration(m2.group("GA"), m2.group("read") != null));
}
// Main GA
GroupAddressConfiguration mainGA = new GroupAddressConfiguration(matcher.group("mainGA"),
matcher.group("read") != null);
return new ChannelConfiguration(matcher.group("dpt"), mainGA, listenGAs);
}
return null;
}
protected abstract Set<String> getAllGAKeys();
public final Set<GroupAddress> getListenAddresses(Configuration channelConfiguration) {
Set<GroupAddress> ret = new HashSet<>();
for (String key : getAllGAKeys()) {
ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
if (conf != null) {
ret.addAll(conf.getListenGAs().stream().map(this::toGroupAddress).collect(toSet()));
}
}
return ret;
}
public final Set<GroupAddress> getReadAddresses(Configuration channelConfiguration) {
Set<GroupAddress> ret = new HashSet<>();
for (String key : getAllGAKeys()) {
ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
if (conf != null) {
ret.addAll(conf.getReadGAs().stream().map(this::toGroupAddress).collect(toSet()));
}
}
return ret;
}
public final Set<GroupAddress> getWriteAddresses(Configuration channelConfiguration) {
Set<GroupAddress> ret = new HashSet<>();
for (String key : getAllGAKeys()) {
ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
if (conf != null) {
GroupAddress ga = toGroupAddress(conf.getMainGA());
if (ga != null) {
ret.add(ga);
}
}
}
return ret;
}
private @Nullable GroupAddress toGroupAddress(GroupAddressConfiguration ga) {
try {
return new GroupAddress(ga.getGA());
} catch (KNXFormatException e) {
logger.warn("Could not parse group address '{}'", ga.getGA());
}
return null;
}
protected final Set<GroupAddress> getAddresses(@Nullable Configuration configuration, Iterable<String> addresses)
throws KNXFormatException {
Set<GroupAddress> ret = new HashSet<>();
for (String address : addresses) {
if (configuration != null && configuration.get(address) != null) {
ret.add(new GroupAddress((String) configuration.get(address)));
}
}
return ret;
}
protected final boolean isEquals(@Nullable Configuration configuration, String address, GroupAddress groupAddress)
throws KNXFormatException {
if (configuration != null && configuration.get(address) != null) {
return Objects.equals(new GroupAddress((String) configuration.get(address)), groupAddress);
}
return false;
}
protected final Set<String> asSet(String... values) {
return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(values)));
}
public final @Nullable OutboundSpec getCommandSpec(Configuration configuration, KNXTypeMapper typeHelper,
Type command) throws KNXFormatException {
logger.trace("getCommandSpec testing Keys '{}' for command '{}'", getAllGAKeys(), command);
for (String key : getAllGAKeys()) {
ChannelConfiguration config = parse((String) configuration.get(key));
if (config != null) {
String dpt = config.getDPT();
if (dpt == null) {
dpt = getDefaultDPT(key);
}
Class<? extends Type> expectedTypeClass = typeHelper.toTypeClass(dpt);
if (expectedTypeClass != null) {
if (expectedTypeClass.isInstance(command)) {
logger.trace(
"getCommandSpec key '{}' uses expectedTypeClass '{}' witch isInstance for command '{}' and dpt '{}'",
key, expectedTypeClass, command, dpt);
return new WriteSpecImpl(config, dpt, command);
}
}
}
}
logger.trace("getCommandSpec no Spec found!");
return null;
}
public final List<InboundSpec> getReadSpec(Configuration configuration) throws KNXFormatException {
return getAllGAKeys().stream()
.map(key -> new ReadRequestSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key)))
.filter(spec -> !spec.getGroupAddresses().isEmpty()).collect(toList());
}
public final @Nullable InboundSpec getListenSpec(Configuration configuration, GroupAddress groupAddress) {
Optional<ListenSpecImpl> result = getAllGAKeys().stream()
.map(key -> new ListenSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key)))
.filter(spec -> !spec.getGroupAddresses().isEmpty())
.filter(spec -> spec.getGroupAddresses().contains(groupAddress)).findFirst();
return result.isPresent() ? result.get() : null;
}
protected abstract String getDefaultDPT(String gaConfigKey);
public final @Nullable OutboundSpec getResponseSpec(Configuration configuration, GroupAddress groupAddress,
Type type) throws KNXFormatException {
Optional<ReadResponseSpecImpl> result = getAllGAKeys().stream()
.map(key -> new ReadResponseSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key), type))
.filter(spec -> groupAddress.equals(spec.getGroupAddress())).findFirst();
return result.isPresent() ? result.get() : null;
}
@Override
public String toString() {
return channelTypeIDs.toString();
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toSet;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* Helper class to find the matching {@link KNXChannelType} for any given {@link ChannelTypeUID}.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public final class KNXChannelTypes {
private static final Set<KNXChannelType> TYPES = Collections.unmodifiableSet(Stream.of(//
new TypeColor(), //
new TypeContact(), //
new TypeDateTime(), //
new TypeDimmer(), //
new TypeNumber(), //
new TypeRollershutter(), //
new TypeString(), //
new TypeSwitch() //
).collect(toSet()));
private KNXChannelTypes() {
// prevent instantiation
}
public static KNXChannelType getType(@Nullable ChannelTypeUID channelTypeUID) throws IllegalArgumentException {
Objects.requireNonNull(channelTypeUID);
for (KNXChannelType c : TYPES) {
if (c.getChannelIDs().contains(channelTypeUID.getId())) {
return c;
}
}
throw new IllegalArgumentException(channelTypeUID.getId() + " is not a valid value channel type ID");
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.InboundSpec;
import tuwien.auto.calimero.GroupAddress;
/**
* Listen meta-data.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
public class ListenSpecImpl extends AbstractSpec implements InboundSpec {
private final List<GroupAddress> listenAddresses;
public ListenSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT) {
super(channelConfiguration, defaultDPT);
if (channelConfiguration != null) {
this.listenAddresses = channelConfiguration.getListenGAs().stream().map(this::toGroupAddress)
.collect(toList());
} else {
this.listenAddresses = Collections.emptyList();
}
}
public List<GroupAddress> getGroupAddresses() {
return listenAddresses;
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toList;
import java.util.Collections;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.InboundSpec;
import tuwien.auto.calimero.GroupAddress;
/**
* Read meta-data.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class ReadRequestSpecImpl extends AbstractSpec implements InboundSpec {
private final List<GroupAddress> readAddresses;
public ReadRequestSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT) {
super(channelConfiguration, defaultDPT);
if (channelConfiguration != null) {
this.readAddresses = channelConfiguration.getReadGAs().stream().map(this::toGroupAddress).collect(toList());
} else {
this.readAddresses = Collections.emptyList();
}
}
@Override
public List<GroupAddress> getGroupAddresses() {
return readAddresses;
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.OutboundSpec;
import org.openhab.core.types.Type;
import tuwien.auto.calimero.GroupAddress;
/**
* Response meta-data
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class ReadResponseSpecImpl extends AbstractSpec implements OutboundSpec {
private final @Nullable GroupAddress groupAddress;
private final Type type;
public ReadResponseSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT, Type state) {
super(channelConfiguration, defaultDPT);
if (channelConfiguration != null) {
this.groupAddress = toGroupAddress(channelConfiguration.getMainGA());
} else {
this.groupAddress = null;
}
this.type = state;
}
@Override
public @Nullable GroupAddress getGroupAddress() {
return groupAddress;
}
@Override
public Type getType() {
return type;
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toSet;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
import tuwien.auto.calimero.dptxlator.DPTXlatorRGB;
/**
* color channel type description
*
* @author Helmut Lehmeyer - initial contribution
*
*/
@NonNullByDefault
class TypeColor extends KNXChannelType {
TypeColor() {
super(CHANNEL_COLOR, CHANNEL_COLOR_CONTROL);
}
@Override
protected Set<String> getAllGAKeys() {
return Stream.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA, HSB_GA).collect(toSet());
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
if (gaConfigKey.equals(HSB_GA)) {
return DPTXlatorRGB.DPT_RGB.getID();
}
if (gaConfigKey.equals(INCREASE_DECREASE_GA)) {
return DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID();
}
if (gaConfigKey.equals(SWITCH_GA)) {
return DPTXlatorBoolean.DPT_SWITCH.getID();
}
if (gaConfigKey.equals(POSITION_GA)) {
return DPTXlator8BitUnsigned.DPT_SCALING.getID();
}
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
/**
* contact channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeContact extends KNXChannelType {
TypeContact() {
super(CHANNEL_CONTACT, CHANNEL_CONTACT_CONTROL);
}
@Override
protected Set<String> getAllGAKeys() {
return Collections.singleton(GA);
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
return DPTXlatorBoolean.DPT_OPENCLOSE.getID();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlatorDateTime;
/**
* datetime channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeDateTime extends KNXChannelType {
TypeDateTime() {
super(CHANNEL_DATETIME, CHANNEL_DATETIME_CONTROL);
}
@Override
protected Set<String> getAllGAKeys() {
return Collections.singleton(GA);
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
return DPTXlatorDateTime.DPT_DATE_TIME.getID();
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toSet;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlator3BitControlled;
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
/**
* dimmer channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeDimmer extends KNXChannelType {
TypeDimmer() {
super(CHANNEL_DIMMER, CHANNEL_DIMMER_CONTROL);
}
@Override
protected Set<String> getAllGAKeys() {
return Stream.of(SWITCH_GA, POSITION_GA, INCREASE_DECREASE_GA).collect(toSet());
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
if (Objects.equals(gaConfigKey, INCREASE_DECREASE_GA)) {
return DPTXlator3BitControlled.DPT_CONTROL_DIMMING.getID();
}
if (Objects.equals(gaConfigKey, SWITCH_GA)) {
return DPTXlatorBoolean.DPT_SWITCH.getID();
}
if (Objects.equals(gaConfigKey, POSITION_GA)) {
return DPTXlator8BitUnsigned.DPT_SCALING.getID();
}
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* number channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeNumber extends KNXChannelType {
TypeNumber() {
super(CHANNEL_NUMBER, CHANNEL_NUMBER_CONTROL);
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
return "9.001";
}
@Override
protected Set<String> getAllGAKeys() {
return Collections.singleton(GA);
}
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static java.util.stream.Collectors.toSet;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlator8BitUnsigned;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
/**
* rollershutter channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeRollershutter extends KNXChannelType {
TypeRollershutter() {
super(CHANNEL_ROLLERSHUTTER, CHANNEL_ROLLERSHUTTER_CONTROL);
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
if (Objects.equals(gaConfigKey, UP_DOWN_GA)) {
return DPTXlatorBoolean.DPT_UPDOWN.getID();
}
if (Objects.equals(gaConfigKey, STOP_MOVE_GA)) {
return DPTXlatorBoolean.DPT_START.getID();
}
if (Objects.equals(gaConfigKey, POSITION_GA)) {
return DPTXlator8BitUnsigned.DPT_SCALING.getID();
}
throw new IllegalArgumentException("GA configuration '" + gaConfigKey + "' is not supported");
}
@Override
protected Set<String> getAllGAKeys() {
return Stream.of(UP_DOWN_GA, STOP_MOVE_GA, POSITION_GA).collect(toSet());
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlatorString;
/**
* string channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeString extends KNXChannelType {
TypeString() {
super(CHANNEL_STRING, CHANNEL_STRING_CONTROL);
}
@Override
protected Set<String> getAllGAKeys() {
return Collections.singleton(GA);
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
return DPTXlatorString.DPT_STRING_8859_1.getID();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.dptxlator.DPTXlatorBoolean;
/**
* switch channel type description
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
class TypeSwitch extends KNXChannelType {
TypeSwitch() {
super(CHANNEL_SWITCH, CHANNEL_SWITCH_CONTROL);
}
@Override
protected Set<String> getAllGAKeys() {
return Collections.singleton(GA);
}
@Override
protected String getDefaultDPT(String gaConfigKey) {
return DPTXlatorBoolean.DPT_SWITCH.getID();
}
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.OutboundSpec;
import org.openhab.core.types.Type;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.KNXFormatException;
/**
* Command meta-data
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
public class WriteSpecImpl extends AbstractSpec implements OutboundSpec {
private final Type type;
private final @Nullable GroupAddress groupAddress;
public WriteSpecImpl(@Nullable ChannelConfiguration channelConfiguration, String defaultDPT, Type type)
throws KNXFormatException {
super(channelConfiguration, defaultDPT);
if (channelConfiguration != null) {
this.groupAddress = new GroupAddress(channelConfiguration.getMainGA().getGA());
} else {
this.groupAddress = null;
}
this.type = type;
}
@Override
public Type getType() {
return type;
}
@Override
public @Nullable GroupAddress getGroupAddress() {
return groupAddress;
}
}

View File

@@ -0,0 +1,473 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.KNXTypeMapper;
import org.openhab.binding.knx.internal.dpt.KNXCoreTypeMapper;
import org.openhab.binding.knx.internal.handler.GroupAddressListener;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.types.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.CloseEvent;
import tuwien.auto.calimero.DetachEvent;
import tuwien.auto.calimero.FrameEvent;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.datapoint.CommandDP;
import tuwien.auto.calimero.datapoint.Datapoint;
import tuwien.auto.calimero.device.ProcessCommunicationResponder;
import tuwien.auto.calimero.link.KNXNetworkLink;
import tuwien.auto.calimero.link.NetworkLinkListener;
import tuwien.auto.calimero.mgmt.Destination;
import tuwien.auto.calimero.mgmt.ManagementClient;
import tuwien.auto.calimero.mgmt.ManagementClientImpl;
import tuwien.auto.calimero.mgmt.ManagementProcedures;
import tuwien.auto.calimero.mgmt.ManagementProceduresImpl;
import tuwien.auto.calimero.process.ProcessCommunicationBase;
import tuwien.auto.calimero.process.ProcessCommunicator;
import tuwien.auto.calimero.process.ProcessCommunicatorImpl;
import tuwien.auto.calimero.process.ProcessEvent;
import tuwien.auto.calimero.process.ProcessListener;
/**
* KNX Client which encapsulates the communication with the KNX bus via the calimero libary.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClient {
private static final int MAX_SEND_ATTEMPTS = 2;
private final Logger logger = LoggerFactory.getLogger(AbstractKNXClient.class);
private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper();
private final ThingUID thingUID;
private final int responseTimeout;
private final int readingPause;
private final int autoReconnectPeriod;
private final int readRetriesLimit;
private final StatusUpdateCallback statusUpdateCallback;
private final ScheduledExecutorService knxScheduler;
private @Nullable ProcessCommunicator processCommunicator;
private @Nullable ProcessCommunicationResponder responseCommunicator;
private @Nullable ManagementProcedures managementProcedures;
private @Nullable ManagementClient managementClient;
private @Nullable KNXNetworkLink link;
private @Nullable DeviceInfoClient deviceInfoClient;
private @Nullable ScheduledFuture<?> busJob;
private @Nullable ScheduledFuture<?> connectJob;
private final Set<GroupAddressListener> groupAddressListeners = new CopyOnWriteArraySet<>();
private final LinkedBlockingQueue<ReadDatapoint> readDatapoints = new LinkedBlockingQueue<>();
@FunctionalInterface
private interface ListenerNotification {
void apply(BusMessageListener listener, IndividualAddress source, GroupAddress destination, byte[] asdu);
}
@NonNullByDefault({})
private final ProcessListener processListener = new ProcessListener() {
@Override
public void detached(DetachEvent e) {
logger.debug("The KNX network link was detached from the process communicator");
}
@Override
public void groupWrite(ProcessEvent e) {
processEvent("Group Write", e, (listener, source, destination, asdu) -> {
listener.onGroupWrite(AbstractKNXClient.this, source, destination, asdu);
});
}
@Override
public void groupReadRequest(ProcessEvent e) {
processEvent("Group Read Request", e, (listener, source, destination, asdu) -> {
listener.onGroupRead(AbstractKNXClient.this, source, destination, asdu);
});
}
@Override
public void groupReadResponse(ProcessEvent e) {
processEvent("Group Read Response", e, (listener, source, destination, asdu) -> {
listener.onGroupReadResponse(AbstractKNXClient.this, source, destination, asdu);
});
}
};
public AbstractKNXClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
int readRetriesLimit, ScheduledExecutorService knxScheduler, StatusUpdateCallback statusUpdateCallback) {
this.autoReconnectPeriod = autoReconnectPeriod;
this.thingUID = thingUID;
this.responseTimeout = responseTimeout;
this.readingPause = readingPause;
this.readRetriesLimit = readRetriesLimit;
this.knxScheduler = knxScheduler;
this.statusUpdateCallback = statusUpdateCallback;
}
public void initialize() {
if (!scheduleReconnectJob()) {
connect();
}
}
private boolean scheduleReconnectJob() {
if (autoReconnectPeriod > 0) {
connectJob = knxScheduler.schedule(this::connect, autoReconnectPeriod, TimeUnit.SECONDS);
return true;
} else {
return false;
}
}
private void cancelReconnectJob() {
ScheduledFuture<?> currentReconnectJob = connectJob;
if (currentReconnectJob != null) {
currentReconnectJob.cancel(true);
connectJob = null;
}
}
protected abstract KNXNetworkLink establishConnection() throws KNXException, InterruptedException;
private synchronized boolean connectIfNotAutomatic() {
if (!isConnected()) {
return connectJob != null ? false : connect();
}
return true;
}
private synchronized boolean connect() {
if (isConnected()) {
return true;
}
try {
releaseConnection();
logger.debug("Bridge {} is connecting to the KNX bus", thingUID);
KNXNetworkLink link = establishConnection();
this.link = link;
managementProcedures = new ManagementProceduresImpl(link);
ManagementClient managementClient = new ManagementClientImpl(link);
managementClient.setResponseTimeout(responseTimeout);
this.managementClient = managementClient;
deviceInfoClient = new DeviceInfoClientImpl(managementClient);
ProcessCommunicator processCommunicator = new ProcessCommunicatorImpl(link);
processCommunicator.setResponseTimeout(responseTimeout);
processCommunicator.addProcessListener(processListener);
this.processCommunicator = processCommunicator;
ProcessCommunicationResponder responseCommunicator = new ProcessCommunicationResponder(link);
this.responseCommunicator = responseCommunicator;
link.addLinkListener(this);
busJob = knxScheduler.scheduleWithFixedDelay(() -> readNextQueuedDatapoint(), 0, readingPause,
TimeUnit.MILLISECONDS);
statusUpdateCallback.updateStatus(ThingStatus.ONLINE);
connectJob = null;
return true;
} catch (KNXException | InterruptedException e) {
logger.debug("Error connecting to the bus: {}", e.getMessage(), e);
disconnect(e);
scheduleReconnectJob();
return false;
}
}
private void disconnect(@Nullable Exception e) {
releaseConnection();
if (e != null) {
statusUpdateCallback.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
e.getLocalizedMessage());
} else {
statusUpdateCallback.updateStatus(ThingStatus.OFFLINE);
}
}
@SuppressWarnings("null")
private void releaseConnection() {
logger.debug("Bridge {} is disconnecting from the KNX bus", thingUID);
readDatapoints.clear();
busJob = nullify(busJob, j -> j.cancel(true));
deviceInfoClient = null;
managementProcedures = nullify(managementProcedures, mp -> mp.detach());
managementClient = nullify(managementClient, mc -> mc.detach());
link = nullify(link, l -> l.close());
processCommunicator = nullify(processCommunicator, pc -> {
pc.removeProcessListener(processListener);
pc.detach();
});
responseCommunicator = nullify(responseCommunicator, rc -> {
rc.removeProcessListener(processListener);
rc.detach();
});
}
private <T> T nullify(T target, @Nullable Consumer<T> lastWill) {
if (target != null && lastWill != null) {
lastWill.accept(target);
}
return null;
}
private void processEvent(String task, ProcessEvent event, ListenerNotification action) {
GroupAddress destination = event.getDestination();
IndividualAddress source = event.getSourceAddr();
byte[] asdu = event.getASDU();
logger.trace("Received a {} telegram from '{}' to '{}' with value '{}'", task, source, destination, asdu);
for (GroupAddressListener listener : groupAddressListeners) {
if (listener.listensTo(destination)) {
knxScheduler.schedule(() -> action.apply(listener, source, destination, asdu), 0, TimeUnit.SECONDS);
}
}
}
/**
* Transforms a {@link Type} into a datapoint type value for the KNX bus.
*
* @param type the {@link Type} to transform
* @param dpt the datapoint type to which should be converted
* @return the corresponding KNX datapoint type value as a string
*/
@Nullable
private String toDPTValue(Type type, String dpt) {
return typeHelper.toDPTValue(type, dpt);
}
@SuppressWarnings("null")
private void readNextQueuedDatapoint() {
if (!connectIfNotAutomatic()) {
return;
}
ProcessCommunicator processCommunicator = this.processCommunicator;
if (processCommunicator == null) {
return;
}
ReadDatapoint datapoint = readDatapoints.poll();
if (datapoint != null) {
datapoint.incrementRetries();
try {
logger.trace("Sending a Group Read Request telegram for {}", datapoint.getDatapoint().getMainAddress());
processCommunicator.read(datapoint.getDatapoint());
} catch (KNXException e) {
if (datapoint.getRetries() < datapoint.getLimit()) {
readDatapoints.add(datapoint);
logger.debug("Could not read value for datapoint {}: {}. Going to retry.",
datapoint.getDatapoint().getMainAddress(), e.getMessage());
} else {
logger.warn("Giving up reading datapoint {}, the number of maximum retries ({}) is reached.",
datapoint.getDatapoint().getMainAddress(), datapoint.getLimit());
}
} catch (InterruptedException e) {
logger.debug("Interrupted sending KNX read request");
return;
}
}
}
public void dispose() {
cancelReconnectJob();
disconnect(null);
}
@Override
public void linkClosed(@Nullable CloseEvent closeEvent) {
KNXNetworkLink link = this.link;
if (link == null || closeEvent == null) {
return;
}
if (!link.isOpen() && CloseEvent.USER_REQUEST != closeEvent.getInitiator()) {
statusUpdateCallback.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
closeEvent.getReason());
logger.debug("KNX link has been lost (reason: {} on object {})", closeEvent.getReason(),
closeEvent.getSource().toString());
scheduleReconnectJob();
}
}
@Override
public void indication(@Nullable FrameEvent e) {
// no-op
}
@Override
public void confirmation(@Nullable FrameEvent e) {
// no-op
}
@Override
public final synchronized boolean isReachable(@Nullable IndividualAddress address) throws KNXException {
ManagementProcedures managementProcedures = this.managementProcedures;
if (managementProcedures == null || address == null) {
return false;
}
try {
return managementProcedures.isAddressOccupied(address);
} catch (InterruptedException e) {
logger.debug("Interrupted pinging KNX device '{}'", address);
}
return false;
}
@Override
public final synchronized void restartNetworkDevice(@Nullable IndividualAddress address) {
ManagementClient managementClient = this.managementClient;
if (address == null || managementClient == null) {
return;
}
Destination destination = null;
try {
destination = managementClient.createDestination(address, true);
managementClient.restart(destination);
} catch (KNXException e) {
logger.warn("Could not reset device with address '{}': {}", address, e.getMessage());
} catch (InterruptedException e) { // ignored as in Calimero pre-2.4.0
} finally {
if (destination != null) {
destination.destroy();
}
}
}
@Override
public void readDatapoint(Datapoint datapoint) {
synchronized (this) {
ReadDatapoint retryDatapoint = new ReadDatapoint(datapoint, readRetriesLimit);
if (!readDatapoints.contains(retryDatapoint)) {
readDatapoints.add(retryDatapoint);
}
}
}
@Override
public final boolean registerGroupAddressListener(GroupAddressListener listener) {
return groupAddressListeners.add(listener);
}
@Override
public final boolean unregisterGroupAddressListener(GroupAddressListener listener) {
return groupAddressListeners.remove(listener);
}
@Override
public boolean isConnected() {
return link != null && link.isOpen();
}
@Override
public DeviceInfoClient getDeviceInfoClient() {
DeviceInfoClient deviceInfoClient = this.deviceInfoClient;
if (deviceInfoClient != null) {
return deviceInfoClient;
} else {
throw new IllegalStateException();
}
}
@Override
public void writeToKNX(OutboundSpec commandSpec) throws KNXException {
ProcessCommunicator processCommunicator = this.processCommunicator;
KNXNetworkLink link = this.link;
if (processCommunicator == null || link == null) {
logger.debug("Cannot write to the KNX bus (processCommuicator: {}, link: {})",
processCommunicator == null ? "Not OK" : "OK",
link == null ? "Not OK" : (link.isOpen() ? "Open" : "Closed"));
return;
}
GroupAddress groupAddress = commandSpec.getGroupAddress();
logger.trace("writeToKNX groupAddress '{}', commandSpec '{}'", groupAddress, commandSpec);
if (groupAddress != null) {
sendToKNX(processCommunicator, link, groupAddress, commandSpec.getDPT(), commandSpec.getType());
}
}
@Override
public void respondToKNX(OutboundSpec responseSpec) throws KNXException {
ProcessCommunicationResponder responseCommunicator = this.responseCommunicator;
KNXNetworkLink link = this.link;
if (responseCommunicator == null || link == null) {
logger.debug("Cannot write to the KNX bus (responseCommunicator: {}, link: {})",
responseCommunicator == null ? "Not OK" : "OK",
link == null ? "Not OK" : (link.isOpen() ? "Open" : "Closed"));
return;
}
GroupAddress groupAddress = responseSpec.getGroupAddress();
logger.trace("respondToKNX groupAddress '{}', responseSpec '{}'", groupAddress, responseSpec);
if (groupAddress != null) {
sendToKNX(responseCommunicator, link, groupAddress, responseSpec.getDPT(), responseSpec.getType());
}
}
private void sendToKNX(ProcessCommunicationBase communicator, KNXNetworkLink link, GroupAddress groupAddress,
String dpt, Type type) throws KNXException {
if (!connectIfNotAutomatic()) {
return;
}
Datapoint datapoint = new CommandDP(groupAddress, thingUID.toString(), 0, dpt);
String mappedValue = toDPTValue(type, dpt);
logger.trace("sendToKNX mappedValue: '{}' groupAddress: '{}'", mappedValue, groupAddress);
if (mappedValue == null) {
logger.debug("Value '{}' cannot be mapped to datapoint '{}'", type, datapoint);
return;
}
for (int i = 0; i < MAX_SEND_ATTEMPTS; i++) {
try {
communicator.write(datapoint, mappedValue);
logger.debug("Wrote value '{}' to datapoint '{}' ({}. attempt).", type, datapoint, i);
break;
} catch (KNXException e) {
if (i < MAX_SEND_ATTEMPTS - 1) {
logger.debug("Value '{}' could not be sent to the KNX bus using datapoint '{}': {}. Will retry.",
type, datapoint, e.getLocalizedMessage());
} else {
logger.warn("Value '{}' could not be sent to the KNX bus using datapoint '{}': {}. Giving up now.",
type, datapoint, e.getLocalizedMessage());
throw e;
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.IndividualAddress;
/**
* Callback interface for KNX bus messages
*
* @author Simon Kaufmann - Initial contribution and API
*/
@NonNullByDefault
public interface BusMessageListener {
/**
* Called when the KNX bridge receives a group write telegram
*
* @param bridge
* @param destination
* @param asdu
*/
public void onGroupWrite(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
/**
* Called when the KNX bridge receives a group read telegram
*
* @param bridge
* @param destination
* @param asdu
*/
public void onGroupRead(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu);
/**
* Called when the KNX bridge receives a group read response telegram
*
* @param bridge
* @param destination
* @param asdu
*/
public void onGroupReadResponse(AbstractKNXClient client, IndividualAddress source, GroupAddress destination,
byte[] asdu);
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.knxnetip.KNXnetIPConnection;
import tuwien.auto.calimero.link.KNXNetworkLinkIP;
import tuwien.auto.calimero.link.medium.KNXMediumSettings;
/**
* Subclass of {@link KNXNetworkLinkIP} which exposes the protected constructor in order to work-around
* https://github.com/calimero-project/calimero-core/issues/57
*
* @author Simon Kaufmann - initial contribution and API
*
*/
public class CustomKNXNetworkLinkIP extends KNXNetworkLinkIP {
public static final int TUNNELING = KNXNetworkLinkIP.TUNNELING;
public static final int ROUTING = KNXNetworkLinkIP.ROUTING;
CustomKNXNetworkLinkIP(final int serviceMode, KNXnetIPConnection conn, KNXMediumSettings settings)
throws KNXException, InterruptedException {
super(serviceMode, conn, settings);
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import tuwien.auto.calimero.IndividualAddress;
/**
* Client to retrieve further information about KNX devices.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
@NonNullByDefault
public interface DeviceInfoClient {
byte @Nullable [] readDeviceDescription(IndividualAddress address, int descType, boolean authenticate,
long timeout);
byte @Nullable [] readDeviceMemory(IndividualAddress address, int startAddress, int bytes, boolean authenticate,
long timeout);
byte @Nullable [] readDeviceProperties(IndividualAddress address, final int interfaceObjectIndex,
final int propertyId, final int start, final int elements, boolean authenticate, long timeout);
public boolean isConnected();
}

View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import java.nio.ByteBuffer;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.mgmt.Destination;
import tuwien.auto.calimero.mgmt.ManagementClient;
/**
* Client for retrieving additional device descriptions.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class DeviceInfoClientImpl implements DeviceInfoClient {
private final Logger logger = LoggerFactory.getLogger(DeviceInfoClientImpl.class);
private final ManagementClient managementClient;
DeviceInfoClientImpl(ManagementClient managementClient) {
this.managementClient = managementClient;
}
@FunctionalInterface
private interface ReadFunction<T, R> {
@Nullable
R apply(T t) throws KNXException, InterruptedException;
}
private byte @Nullable [] readFromManagementClient(String task, long timeout, IndividualAddress address,
ReadFunction<Destination, byte[]> function) {
final long start = System.nanoTime();
while ((System.nanoTime() - start) < TimeUnit.MILLISECONDS.toNanos(timeout)) {
Destination destination = null;
try {
logger.trace("Going to {} of {} ", task, address);
destination = managementClient.createDestination(address, true);
byte[] result = function.apply(destination);
logger.trace("Finished to {} of {}, result: {}", task, address, result == null ? null : result.length);
return result;
} catch (KNXException e) {
logger.debug("Could not {} of {}: {}", task, address, e.getMessage());
} catch (InterruptedException e) {
logger.trace("Interrupted to {}", task);
return null;
} finally {
if (destination != null) {
destination.destroy();
}
}
}
return null;
}
private void authorize(boolean authenticate, Destination destination) throws KNXException, InterruptedException {
if (authenticate) {
managementClient.authorize(destination, (ByteBuffer.allocate(4)).put((byte) 0xFF).put((byte) 0xFF)
.put((byte) 0xFF).put((byte) 0xFF).array());
}
}
@Override
public synchronized byte @Nullable [] readDeviceDescription(IndividualAddress address, int descType,
boolean authenticate, long timeout) {
String task = "read the device description";
return readFromManagementClient(task, timeout, address, destination -> {
authorize(authenticate, destination);
return managementClient.readDeviceDesc(destination, descType);
});
}
@Override
public synchronized byte @Nullable [] readDeviceMemory(IndividualAddress address, int startAddress, int bytes,
boolean authenticate, long timeout) {
String task = MessageFormat.format("read {0} bytes at memory location {1}", bytes, startAddress);
return readFromManagementClient(task, timeout, address, destination -> {
authorize(authenticate, destination);
return managementClient.readMemory(destination, startAddress, bytes);
});
}
@Override
public synchronized byte @Nullable [] readDeviceProperties(IndividualAddress address,
final int interfaceObjectIndex, final int propertyId, final int start, final int elements,
boolean authenticate, long timeout) {
String task = MessageFormat.format("read device property {0} at index {1}", propertyId, interfaceObjectIndex);
return readFromManagementClient(task, timeout, address, destination -> {
authorize(authenticate, destination);
return managementClient.readProperty(destination, interfaceObjectIndex, propertyId, start, elements);
});
}
@Override
public boolean isConnected() {
return managementClient.isOpen();
}
}

View File

@@ -0,0 +1,172 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import static org.openhab.binding.knx.internal.handler.DeviceConstants.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.handler.Firmware;
import org.openhab.binding.knx.internal.handler.Manufacturer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.DataUnitBuilder;
import tuwien.auto.calimero.DeviceDescriptor;
import tuwien.auto.calimero.DeviceDescriptor.DD0;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.mgmt.PropertyAccess.PID;
/**
* Client dedicated to read device specific information using the {@link DeviceInfoClient}.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class DeviceInspector {
private static final long OPERATION_TIMEOUT = 5000;
private static final long OPERATION_INTERVAL = 2000;
private final Logger logger = LoggerFactory.getLogger(DeviceInspector.class);
private final DeviceInfoClient client;
private final IndividualAddress address;
public static class Result {
private final Map<String, String> properties;
private final Set<GroupAddress> groupAddresses;
public Result(Map<String, String> properties, Set<GroupAddress> groupAddresses) {
super();
this.properties = properties;
this.groupAddresses = groupAddresses;
}
public Map<String, String> getProperties() {
return properties;
}
public Set<GroupAddress> getGroupAddresses() {
return groupAddresses;
}
}
public DeviceInspector(DeviceInfoClient client, IndividualAddress address) {
this.client = client;
this.address = address;
}
private DeviceInfoClient getClient() {
return client;
}
@Nullable
public Result readDeviceInfo() {
if (!getClient().isConnected()) {
return null;
}
logger.debug("Fetching device information for address {}", address);
Map<String, String> properties = new HashMap<>();
properties.putAll(readDeviceDescription(address));
properties.putAll(readDeviceProperties(address));
return new Result(properties, Collections.emptySet());
}
private Map<String, String> readDeviceProperties(IndividualAddress address) {
Map<String, String> ret = new HashMap<>();
try {
Thread.sleep(OPERATION_INTERVAL);
// check if there is a Device Object in the KNX device
byte[] elements = getClient().readDeviceProperties(address, DEVICE_OBJECT, PID.OBJECT_TYPE, 0, 1, false,
OPERATION_TIMEOUT);
if ((elements == null ? 0 : toUnsigned(elements)) == 1) {
Thread.sleep(OPERATION_INTERVAL);
String ManufacturerID = Manufacturer.getName(toUnsigned(getClient().readDeviceProperties(address,
DEVICE_OBJECT, PID.MANUFACTURER_ID, 1, 1, false, OPERATION_TIMEOUT)));
Thread.sleep(OPERATION_INTERVAL);
String serialNo = toHex(getClient().readDeviceProperties(address, DEVICE_OBJECT, PID.SERIAL_NUMBER, 1,
1, false, OPERATION_TIMEOUT), "");
Thread.sleep(OPERATION_INTERVAL);
String hardwareType = toHex(getClient().readDeviceProperties(address, DEVICE_OBJECT, HARDWARE_TYPE, 1,
1, false, OPERATION_TIMEOUT), " ");
Thread.sleep(OPERATION_INTERVAL);
String firmwareRevision = Integer.toString(toUnsigned(getClient().readDeviceProperties(address,
DEVICE_OBJECT, PID.FIRMWARE_REVISION, 1, 1, false, OPERATION_TIMEOUT)));
ret.put(MANUFACTURER_NAME, ManufacturerID);
if (serialNo != null) {
ret.put(MANUFACTURER_SERIAL_NO, serialNo);
}
if (hardwareType != null) {
ret.put(MANUFACTURER_HARDWARE_TYPE, hardwareType);
}
ret.put(MANUFACTURER_FIRMWARE_REVISION, firmwareRevision);
logger.debug("Identified device {} as a {}, type {}, revision {}, serial number {}", address,
ManufacturerID, hardwareType, firmwareRevision, serialNo);
} else {
logger.debug("The KNX device with address {} does not expose a Device Object", address);
}
} catch (InterruptedException e) {
logger.debug("Interrupted while fetching the device description for a device '{}' : {}", address,
e.getMessage());
}
return ret;
}
private @Nullable String toHex(byte @Nullable [] input, String separator) {
return input == null ? null : DataUnitBuilder.toHex(input, separator);
}
private Map<String, String> readDeviceDescription(IndividualAddress address) {
Map<String, String> ret = new HashMap<>();
byte[] data = getClient().readDeviceDescription(address, 0, false, OPERATION_TIMEOUT);
if (data != null) {
final DD0 dd = DeviceDescriptor.DD0.from(data);
ret.put(FIRMWARE_TYPE, Firmware.getName(dd.firmwareType()));
ret.put(FIRMWARE_VERSION, Firmware.getName(dd.firmwareVersion()));
ret.put(FIRMWARE_SUBVERSION, Firmware.getName(dd.firmwareSubcode()));
logger.debug("The device with address {} is of type {}, version {}, subversion {}", address,
Firmware.getName(dd.firmwareType()), Firmware.getName(dd.firmwareVersion()),
Firmware.getName(dd.firmwareSubcode()));
} else {
logger.debug("The KNX device with address {} does not expose a Device Descriptor", address);
}
return ret;
}
private int toUnsigned(final byte @Nullable [] data) {
if (data == null) {
return 0;
}
int value = data[0] & 0xff;
if (data.length == 1) {
return value;
}
value = value << 8 | data[1] & 0xff;
if (data.length == 2) {
return value;
}
value = value << 16 | data[2] & 0xff << 8 | data[3] & 0xff;
return value;
}
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXIllegalArgumentException;
import tuwien.auto.calimero.knxnetip.KNXnetIPConnection;
import tuwien.auto.calimero.knxnetip.KNXnetIPRouting;
import tuwien.auto.calimero.knxnetip.KNXnetIPTunnel;
import tuwien.auto.calimero.knxnetip.KNXnetIPTunnel.TunnelingLayer;
import tuwien.auto.calimero.link.KNXNetworkLink;
import tuwien.auto.calimero.link.KNXNetworkLinkIP;
import tuwien.auto.calimero.link.medium.KNXMediumSettings;
import tuwien.auto.calimero.link.medium.TPSettings;
/**
* IP specific {@link AbstractKNXClient} implementation.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public class IPClient extends AbstractKNXClient {
private final Logger logger = LoggerFactory.getLogger(IPClient.class);
private static final String MODE_ROUTER = "ROUTER";
private static final String MODE_TUNNEL = "TUNNEL";
private final int ipConnectionType;
private final String ip;
private final String localSource;
private final int port;
@Nullable
private final InetSocketAddress localEndPoint;
private final boolean useNAT;
public IPClient(int ipConnectionType, String ip, String localSource, int port,
@Nullable InetSocketAddress localEndPoint, boolean useNAT, int autoReconnectPeriod, ThingUID thingUID,
int responseTimeout, int readingPause, int readRetriesLimit, ScheduledExecutorService knxScheduler,
StatusUpdateCallback statusUpdateCallback) {
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
statusUpdateCallback);
this.ipConnectionType = ipConnectionType;
this.ip = ip;
this.localSource = localSource;
this.port = port;
this.localEndPoint = localEndPoint;
this.useNAT = useNAT;
}
@Override
protected KNXNetworkLink establishConnection() throws KNXException, InterruptedException {
logger.debug("Establishing connection to KNX bus on {}:{} in mode {}.", ip, port, connectionTypeToString());
TPSettings settings = new TPSettings(new IndividualAddress(localSource));
return createKNXNetworkLinkIP(ipConnectionType, localEndPoint, new InetSocketAddress(ip, port), useNAT,
settings);
}
private String connectionTypeToString() {
return ipConnectionType == CustomKNXNetworkLinkIP.ROUTING ? MODE_ROUTER : MODE_TUNNEL;
}
private KNXNetworkLinkIP createKNXNetworkLinkIP(int serviceMode, @Nullable InetSocketAddress localEP,
@Nullable InetSocketAddress remoteEP, boolean useNAT, KNXMediumSettings settings)
throws KNXException, InterruptedException {
// creating the connection here as a workaround for
// https://github.com/calimero-project/calimero-core/issues/57
KNXnetIPConnection conn = getConnection(serviceMode, localEP, remoteEP, useNAT);
return new CustomKNXNetworkLinkIP(serviceMode, conn, settings);
}
private KNXnetIPConnection getConnection(int serviceMode, @Nullable InetSocketAddress localEP,
@Nullable InetSocketAddress remoteEP, boolean useNAT) throws KNXException, InterruptedException {
KNXnetIPConnection conn;
switch (serviceMode) {
case CustomKNXNetworkLinkIP.TUNNELING:
InetSocketAddress local = localEP;
if (local == null) {
try {
local = new InetSocketAddress(InetAddress.getLocalHost(), 0);
} catch (final UnknownHostException e) {
throw new KNXException("no local host available");
}
}
conn = new KNXnetIPTunnel(TunnelingLayer.LinkLayer, local, remoteEP, useNAT);
break;
case CustomKNXNetworkLinkIP.ROUTING:
NetworkInterface netIf = null;
if (localEP != null && !localEP.isUnresolved()) {
try {
netIf = NetworkInterface.getByInetAddress(localEP.getAddress());
} catch (final SocketException e) {
throw new KNXException("error getting network interface: " + e.getMessage());
}
}
final InetAddress mcast = remoteEP != null ? remoteEP.getAddress() : null;
conn = new KNXnetIPRouting(netIf, mcast);
break;
default:
throw new KNXIllegalArgumentException("unknown service mode");
}
return conn;
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import tuwien.auto.calimero.GroupAddress;
/**
* Describes the relevant parameters for reading from/listening to the KNX bus.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
@NonNullByDefault
public interface InboundSpec {
/**
* Get the datapoint type.
*
* @return the datapoint type
*/
String getDPT();
/**
* Get the affected group addresses.
*
* @return a list of group addresses.
*/
List<GroupAddress> getGroupAddresses();
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.handler.GroupAddressListener;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.datapoint.Datapoint;
/**
* Client for communicating with the KNX bus.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
@NonNullByDefault
public interface KNXClient {
/**
* Check whether the client is connected
*
* @return {@code true} if the client currently is connected
*/
boolean isConnected();
/**
* Determines whether the supplied address is occupied by a device in the KNX network or not.
*
* @param address the individual address to check
* @return {@code true} if the address is occupied
* @throws KNXException on network or send errors
*/
boolean isReachable(@Nullable IndividualAddress address) throws KNXException;
/**
* Get the {@link DeviceInfoClient} which allows further device inspection.
*
* @return the device infor client
* @throws IllegalStateException in case the client is not connected
*/
DeviceInfoClient getDeviceInfoClient();
/**
* Initiates a basic restart of the device with the given address.
*
* @param address the individual address of the device
*/
void restartNetworkDevice(@Nullable IndividualAddress address);
/**
* Register the given listener to be informed on KNX bus traffic.
*
* @param listener the listener
* @return {@code true} if it wasn't registered before
*/
boolean registerGroupAddressListener(GroupAddressListener listener);
/**
* Remove the given listener.
*
* @param listener the listener
* @return {@code true} if it was successfully removed
*/
boolean unregisterGroupAddressListener(GroupAddressListener listener);
/**
* Schedule the given data point for asynchronous reading.
*
* @param datapoint the datapoint
*/
void readDatapoint(Datapoint datapoint);
/**
* Write a command to the KNX bus.
*
* @param commandSpec the outbound spec
* @throws KNXException if any problem with the communication arises.
*/
void writeToKNX(OutboundSpec commandSpec) throws KNXException;
/**
* Send a state as a read-response to the KNX bus.
*
* @param responseSpec the outbound spec
* @throws KNXException if any problem with the communication arises.
*/
void respondToKNX(OutboundSpec responseSpec) throws KNXException;
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.handler.GroupAddressListener;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.datapoint.Datapoint;
/**
*
* @author Simon Kaufmann - initial contribution and API
*
*/
@NonNullByDefault
public class NoOpClient implements KNXClient {
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isReachable(@Nullable IndividualAddress address) throws KNXException {
return false;
}
@Override
public DeviceInfoClient getDeviceInfoClient() {
throw new IllegalStateException("KNX client not properly configured");
}
@Override
public void restartNetworkDevice(@Nullable IndividualAddress address) {
throw new IllegalStateException("KNX client not properly configured");
}
@Override
public boolean registerGroupAddressListener(GroupAddressListener listener) {
return false;
}
@Override
public boolean unregisterGroupAddressListener(GroupAddressListener listener) {
return false;
}
@Override
public void readDatapoint(Datapoint datapoint) {
}
@Override
public void writeToKNX(OutboundSpec commandSpec) throws KNXException {
}
@Override
public void respondToKNX(OutboundSpec responseSpec) throws KNXException {
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Type;
import tuwien.auto.calimero.GroupAddress;
/**
* Describes the relevant parameters for writing to the KNX bus.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
@NonNullByDefault
public interface OutboundSpec {
/**
* Get the datapoint type.
*
* @return the datapoint type
*/
String getDPT();
/**
* The group address to be used.
*
* @return the group address
*/
@Nullable
GroupAddress getGroupAddress();
/**
* The command or state to be sent.
*
* @return the command/state
*/
Type getType();
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import tuwien.auto.calimero.datapoint.Datapoint;
/**
* Information about a data point which is queued to be read from the KNX bus.
*
* @author Karel Goderis - Initial contribution
*/
public class ReadDatapoint {
private final Datapoint datapoint;
private int retries;
private final int limit;
public ReadDatapoint(Datapoint datapoint, int limit) {
this.datapoint = datapoint;
this.retries = 0;
this.limit = limit;
}
public Datapoint getDatapoint() {
return datapoint;
}
public int getRetries() {
return retries;
}
public void incrementRetries() {
this.retries++;
}
public int getLimit() {
return limit;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((datapoint.getMainAddress() == null) ? 0 : datapoint.getMainAddress().hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ReadDatapoint other = (ReadDatapoint) obj;
if (datapoint == null) {
if (other.datapoint != null) {
return false;
}
} else if (!datapoint.getMainAddress().equals(other.datapoint.getMainAddress())) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import java.util.Enumeration;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNull;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import gnu.io.CommPortIdentifier;
import gnu.io.RXTXVersion;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.link.KNXNetworkLink;
import tuwien.auto.calimero.link.KNXNetworkLinkFT12;
import tuwien.auto.calimero.link.medium.TPSettings;
/**
* Serial specific {@link AbstractKNXClient} implementation.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
public class SerialClient extends AbstractKNXClient {
private final Logger logger = LoggerFactory.getLogger(SerialClient.class);
private final String serialPort;
public SerialClient(int autoReconnectPeriod, ThingUID thingUID, int responseTimeout, int readingPause,
int readRetriesLimit, ScheduledExecutorService knxScheduler, String serialPort,
StatusUpdateCallback statusUpdateCallback) {
super(autoReconnectPeriod, thingUID, responseTimeout, readingPause, readRetriesLimit, knxScheduler,
statusUpdateCallback);
this.serialPort = serialPort;
}
@Override
protected @NonNull KNXNetworkLink establishConnection() throws KNXException, InterruptedException {
try {
RXTXVersion.getVersion();
logger.debug("Establishing connection to KNX bus through FT1.2 on serial port {}.", serialPort);
return new KNXNetworkLinkFT12(serialPort, new TPSettings());
} catch (NoClassDefFoundError e) {
throw new KNXException(
"The serial FT1.2 KNX connection requires the RXTX libraries to be available, but they could not be found!",
e);
} catch (KNXException e) {
if (e.getMessage().startsWith("can not open serial port")) {
StringBuilder sb = new StringBuilder("Available ports are:\n");
Enumeration<?> portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier id = (CommPortIdentifier) portList.nextElement();
if (id.getPortType() == CommPortIdentifier.PORT_SERIAL) {
sb.append(id.getName());
sb.append("\n");
}
}
sb.deleteCharAt(sb.length() - 1);
throw new KNXException("Serial port '" + serialPort + "' could not be opened. " + sb.toString());
} else {
throw e;
}
}
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.client;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
/**
* Callback interface which enables the KNXClient implementations to update the thing status.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@NonNullByDefault
public interface StatusUpdateCallback {
/**
* see BaseThingHandler
*
* @param status
*/
void updateStatus(ThingStatus status);
/**
* see BaseThingHandler
*
* @param status
*/
void updateStatus(ThingStatus status, ThingStatusDetail thingStatusDetail, String message);
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.config;
import java.math.BigDecimal;
import org.openhab.binding.knx.internal.handler.KNXBridgeBaseThingHandler;
/**
* {@link KNXBridgeBaseThingHandler} configuration
*
* @author Simon Kaufmann - initial contribution and API
*
*/
public class BridgeConfiguration {
private int autoReconnectPeriod;
private BigDecimal readingPause;
private BigDecimal readRetriesLimit;
private BigDecimal responseTimeout;
public int getAutoReconnectPeriod() {
return autoReconnectPeriod;
}
public BigDecimal getReadingPause() {
return readingPause;
}
public BigDecimal getReadRetriesLimit() {
return readRetriesLimit;
}
public BigDecimal getResponseTimeout() {
return responseTimeout;
}
public void setAutoReconnectPeriod(int period) {
autoReconnectPeriod = period;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.config;
import java.math.BigDecimal;
/**
* Configuration object for the device thing handler.
*
* @author Karel Goderis - Initial contribution
* @author Simon Kaufmann - refactoring & cleanup
*/
public class DeviceConfig {
private String address;
private boolean fetch;
private BigDecimal pingInterval;
private BigDecimal readInterval;
public String getAddress() {
return address;
}
public Boolean getFetch() {
return fetch;
}
public BigDecimal getPingInterval() {
return pingInterval;
}
public BigDecimal getReadInterval() {
return readInterval;
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.config;
import java.math.BigDecimal;
/**
* IP Bridge handler configuration object.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
public class IPBridgeConfiguration extends BridgeConfiguration {
private boolean useNAT;
private String type;
private String ipAddress;
private BigDecimal portNumber;
private String localIp;
private String localSourceAddr;
public Boolean getUseNAT() {
return useNAT;
}
public String getType() {
return type;
}
public String getIpAddress() {
return ipAddress;
}
public BigDecimal getPortNumber() {
return portNumber;
}
public String getLocalIp() {
return localIp;
}
public String getLocalSourceAddr() {
return localSourceAddr;
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.config;
/**
* Serial Bridge configuration object.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
public class SerialBridgeConfiguration extends BridgeConfiguration {
private String serialPort;
public String getSerialPort() {
return serialPort;
}
}

View File

@@ -0,0 +1,108 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.factory;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.util.Arrays;
import java.util.Collection;
import org.openhab.binding.knx.internal.handler.DeviceThingHandler;
import org.openhab.binding.knx.internal.handler.IPBridgeThingHandler;
import org.openhab.binding.knx.internal.handler.SerialBridgeThingHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link KNXHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Simon Kaufmann - Initial contribution and API
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.knx")
public class KNXHandlerFactory extends BaseThingHandlerFactory {
public static final Collection<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Arrays.asList(THING_TYPE_DEVICE,
THING_TYPE_IP_BRIDGE, THING_TYPE_SERIAL_BRIDGE);
private NetworkAddressService networkAddressService;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
public Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID,
ThingUID bridgeUID) {
if (THING_TYPE_IP_BRIDGE.equals(thingTypeUID)) {
ThingUID IPBridgeUID = getIPBridgeThingUID(thingTypeUID, thingUID, configuration);
return super.createThing(thingTypeUID, configuration, IPBridgeUID, null);
}
if (THING_TYPE_SERIAL_BRIDGE.equals(thingTypeUID)) {
ThingUID serialBridgeUID = getSerialBridgeThingUID(thingTypeUID, thingUID, configuration);
return super.createThing(thingTypeUID, configuration, serialBridgeUID, null);
}
if (THING_TYPE_DEVICE.equals(thingTypeUID)) {
return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
}
throw new IllegalArgumentException("The thing type " + thingTypeUID + " is not supported by the KNX binding.");
}
@Override
protected ThingHandler createHandler(Thing thing) {
if (thing.getThingTypeUID().equals(THING_TYPE_IP_BRIDGE)) {
return new IPBridgeThingHandler((Bridge) thing, networkAddressService);
} else if (thing.getThingTypeUID().equals(THING_TYPE_SERIAL_BRIDGE)) {
return new SerialBridgeThingHandler((Bridge) thing);
} else if (thing.getThingTypeUID().equals(THING_TYPE_DEVICE)) {
return new DeviceThingHandler(thing);
}
return null;
}
private ThingUID getIPBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID, Configuration configuration) {
if (thingUID != null) {
return thingUID;
}
String ipAddress = (String) configuration.get(IP_ADDRESS);
return new ThingUID(thingTypeUID, ipAddress);
}
private ThingUID getSerialBridgeThingUID(ThingTypeUID thingTypeUID, ThingUID thingUID,
Configuration configuration) {
if (thingUID != null) {
return thingUID;
}
String serialPort = (String) configuration.get(SERIAL_PORT);
return new ThingUID(thingTypeUID, serialPort);
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
}

View File

@@ -0,0 +1,220 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.DeviceInspector;
import org.openhab.binding.knx.internal.client.DeviceInspector.Result;
import org.openhab.binding.knx.internal.client.KNXClient;
import org.openhab.binding.knx.internal.config.DeviceConfig;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXFormatException;
/**
* Base class for KNX thing handlers.
*
* @author Simon Kaufmann - initial contribution and API
*
*/
@NonNullByDefault
public abstract class AbstractKNXThingHandler extends BaseThingHandler implements GroupAddressListener {
private static final int INITIAL_PING_DELAY = 5;
private final Logger logger = LoggerFactory.getLogger(AbstractKNXThingHandler.class);
protected @Nullable IndividualAddress address;
private @Nullable Future<?> descriptionJob;
private boolean filledDescription = false;
private final Random random = new Random();
private @Nullable ScheduledFuture<?> pollingJob;
public AbstractKNXThingHandler(Thing thing) {
super(thing);
}
protected final ScheduledExecutorService getScheduler() {
return getBridgeHandler().getScheduler();
}
protected final ScheduledExecutorService getBackgroundScheduler() {
return getBridgeHandler().getBackgroundScheduler();
}
protected final KNXBridgeBaseThingHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge != null) {
KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
if (handler != null) {
return handler;
}
}
throw new IllegalStateException("The bridge must not be null and must be initialized");
}
protected final KNXClient getClient() {
return getBridgeHandler().getClient();
}
protected final boolean describeDevice(@Nullable IndividualAddress address) {
if (address == null) {
return false;
}
DeviceInspector inspector = new DeviceInspector(getClient().getDeviceInfoClient(), address);
Result result = inspector.readDeviceInfo();
if (result != null) {
Map<String, String> properties = editProperties();
properties.putAll(result.getProperties());
updateProperties(properties);
return true;
}
return false;
}
protected final String asduToHex(byte[] asdu) {
final char[] hexCode = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder(2 + asdu.length * 2);
sb.append("0x");
for (byte b : asdu) {
sb.append(hexCode[(b >> 4) & 0xF]);
sb.append(hexCode[(b & 0xF)]);
}
return sb.toString();
}
protected final void restart() {
if (address != null) {
getClient().restartNetworkDevice(address);
}
}
@Override
public void bridgeStatusChanged(@NonNull ThingStatusInfo bridgeStatusInfo) {
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
attachToClient();
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
detachFromClient();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
@Override
public void initialize() {
attachToClient();
}
@Override
public void dispose() {
detachFromClient();
}
protected abstract void scheduleReadJobs();
protected abstract void cancelReadFutures();
private void pollDeviceStatus() {
try {
if (address != null && getClient().isConnected()) {
logger.debug("Polling individual address '{}'", address);
boolean isReachable = getClient().isReachable(address);
if (isReachable) {
updateStatus(ThingStatus.ONLINE);
DeviceConfig config = getConfigAs(DeviceConfig.class);
if (!filledDescription && config.getFetch()) {
Future<?> descriptionJob = this.descriptionJob;
if (descriptionJob == null || descriptionJob.isCancelled()) {
long initialDelay = Math.round(config.getPingInterval().longValue() * random.nextFloat());
this.descriptionJob = getBackgroundScheduler().schedule(() -> {
filledDescription = describeDevice(address);
}, initialDelay, TimeUnit.SECONDS);
}
}
} else {
updateStatus(ThingStatus.OFFLINE);
}
}
} catch (KNXException e) {
logger.debug("An error occurred while testing the reachability of a thing '{}'", getThing().getUID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage());
}
}
protected void attachToClient() {
if (!getClient().isConnected()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
DeviceConfig config = getConfigAs(DeviceConfig.class);
try {
if (config.getAddress() != null && !config.getAddress().isEmpty()) {
updateStatus(ThingStatus.UNKNOWN);
address = new IndividualAddress(config.getAddress());
long pingInterval = config.getPingInterval().longValue();
long initialPingDelay = Math.round(INITIAL_PING_DELAY * random.nextFloat());
ScheduledFuture<?> pollingJob = this.pollingJob;
if ((pollingJob == null || pollingJob.isCancelled())) {
logger.debug("'{}' will be polled every {}s", getThing().getUID(), pingInterval);
this.pollingJob = getBackgroundScheduler().scheduleWithFixedDelay(() -> pollDeviceStatus(),
initialPingDelay, pingInterval, TimeUnit.SECONDS);
}
} else {
updateStatus(ThingStatus.ONLINE);
}
} catch (KNXFormatException e) {
logger.debug("An exception occurred while setting the individual address '{}'", config.getAddress(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
}
getClient().registerGroupAddressListener(this);
scheduleReadJobs();
}
protected void detachFromClient() {
if (pollingJob != null) {
pollingJob.cancel(true);
pollingJob = null;
}
if (descriptionJob != null) {
descriptionJob.cancel(true);
descriptionJob = null;
}
cancelReadFutures();
Bridge bridge = getBridge();
if (bridge != null) {
KNXBridgeBaseThingHandler handler = (KNXBridgeBaseThingHandler) bridge.getHandler();
if (handler != null) {
handler.getClient().unregisterGroupAddressListener(this);
}
}
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
/**
* @author Karel Goderis - Initial contribution
*/
public class DeviceConstants {
private DeviceConstants() {
// prevent instantiation
}
// Memory addresses for device information
public static final int MEM_DOA = 0x0102; // length 2
public static final int MEM_MANUFACTURERID = 0x0104;
public static final int MEM_DEVICETYPE = 0x0105; // length 2
public static final int MEM_VERSION = 0x0107;
public static final int MEM_PEI = 0x0109;
public static final int MEM_RUNERROR = 0x010d;
public static final int MEM_GROUPOBJECTABLEPTR = 0x0112;
public static final int MEM_PROGRAMPTR = 0x0114;
public static final int MEM_GROUPADDRESSTABLE = 0x0116; // max. length 233
// Interface Object indexes
public static final int DEVICE_OBJECT = 0; // Device Object
public static final int ADDRESS_TABLE_OBJECT = 1; // Addresstable Object
public static final int ASSOCIATION_TABLE_OBJECT = 2; // Associationtable Object
public static final int APPLICATION_PROGRAM_TABLE = 3; // Application Program Object
public static final int INTERFACE_PROGRAM_OBJECT = 4; // Interface Program Object
public static final int GROUPOBJECT_OBJECT = 9; // Group Object Object
public static final int KNXNET_IP_OBJECT = 11; // KNXnet/IP Parameter Object
// Property IDs for device information;
public static final int HARDWARE_TYPE = 78;
}

View File

@@ -0,0 +1,435 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.KNXBindingConstants;
import org.openhab.binding.knx.internal.KNXTypeMapper;
import org.openhab.binding.knx.internal.channel.KNXChannelType;
import org.openhab.binding.knx.internal.channel.KNXChannelTypes;
import org.openhab.binding.knx.internal.client.AbstractKNXClient;
import org.openhab.binding.knx.internal.client.InboundSpec;
import org.openhab.binding.knx.internal.client.OutboundSpec;
import org.openhab.binding.knx.internal.config.DeviceConfig;
import org.openhab.binding.knx.internal.dpt.KNXCoreTypeMapper;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.Type;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tuwien.auto.calimero.GroupAddress;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.KNXException;
import tuwien.auto.calimero.KNXFormatException;
import tuwien.auto.calimero.datapoint.CommandDP;
import tuwien.auto.calimero.datapoint.Datapoint;
/**
* The {@link DeviceThingHandler} is responsible for handling commands and state updates sent to and received from the
* bus and updating the channels correspondingly.
*
* @author Simon Kaufmann - Initial contribution and API
*/
@NonNullByDefault
public class DeviceThingHandler extends AbstractKNXThingHandler {
private final Logger logger = LoggerFactory.getLogger(DeviceThingHandler.class);
private final KNXTypeMapper typeHelper = new KNXCoreTypeMapper();
private final Set<GroupAddress> groupAddresses = new HashSet<>();
private final Set<GroupAddress> groupAddressesWriteBlockedOnce = new HashSet<>();
private final Set<OutboundSpec> groupAddressesRespondingSpec = new HashSet<>();
private final Map<GroupAddress, @Nullable ScheduledFuture<?>> readFutures = new HashMap<>();
private final Map<ChannelUID, @Nullable ScheduledFuture<?>> channelFutures = new HashMap<>();
private int readInterval;
public DeviceThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
super.initialize();
DeviceConfig config = getConfigAs(DeviceConfig.class);
readInterval = config.getReadInterval().intValue();
initializeGroupAddresses();
}
private void initializeGroupAddresses() {
forAllChannels((selector, channelConfiguration) -> {
groupAddresses.addAll(selector.getReadAddresses(channelConfiguration));
groupAddresses.addAll(selector.getWriteAddresses(channelConfiguration));
groupAddresses.addAll(selector.getListenAddresses(channelConfiguration));
});
}
@Override
public void dispose() {
cancelChannelFutures();
freeGroupAdresses();
super.dispose();
}
private void cancelChannelFutures() {
for (ScheduledFuture<?> future : channelFutures.values()) {
if (future != null && !future.isDone()) {
future.cancel(true);
}
}
channelFutures.clear();
}
private void freeGroupAdresses() {
groupAddresses.clear();
groupAddressesWriteBlockedOnce.clear();
groupAddressesRespondingSpec.clear();
}
@Override
protected void cancelReadFutures() {
for (ScheduledFuture<?> future : readFutures.values()) {
if (future != null && !future.isDone()) {
future.cancel(true);
}
}
readFutures.clear();
}
@FunctionalInterface
private interface ChannelFunction {
void apply(KNXChannelType channelType, Configuration configuration) throws KNXException;
}
private void withKNXType(ChannelUID channelUID, ChannelFunction function) {
Channel channel = getThing().getChannel(channelUID.getId());
if (channel == null) {
logger.warn("Channel '{}' does not exist", channelUID);
return;
}
withKNXType(channel, function);
}
private void withKNXType(Channel channel, ChannelFunction function) {
try {
KNXChannelType selector = getKNXChannelType(channel);
function.apply(selector, channel.getConfiguration());
} catch (KNXException e) {
logger.warn("An error occurred on channel {}: {}", channel.getUID(), e.getMessage(), e);
}
}
private void forAllChannels(ChannelFunction function) {
for (Channel channel : getThing().getChannels()) {
withKNXType(channel, function);
}
}
@Override
public void channelLinked(ChannelUID channelUID) {
if (!isControl(channelUID)) {
withKNXType(channelUID, (selector, configuration) -> {
scheduleRead(selector, configuration);
});
}
}
@Override
protected void scheduleReadJobs() {
cancelReadFutures();
for (Channel channel : getThing().getChannels()) {
if (isLinked(channel.getUID().getId()) && !isControl(channel.getUID())) {
withKNXType(channel, (selector, configuration) -> {
scheduleRead(selector, configuration);
});
}
}
}
private void scheduleRead(KNXChannelType selector, Configuration configuration) throws KNXFormatException {
List<InboundSpec> readSpecs = selector.getReadSpec(configuration);
for (InboundSpec readSpec : readSpecs) {
for (GroupAddress groupAddress : readSpec.getGroupAddresses()) {
scheduleReadJob(groupAddress, readSpec.getDPT());
}
}
}
private void scheduleReadJob(GroupAddress groupAddress, String dpt) {
if (readInterval > 0) {
ScheduledFuture<?> future = readFutures.get(groupAddress);
if (future == null || future.isDone() || future.isCancelled()) {
future = getScheduler().scheduleWithFixedDelay(() -> readDatapoint(groupAddress, dpt), 0, readInterval,
TimeUnit.SECONDS);
readFutures.put(groupAddress, future);
}
} else {
getScheduler().submit(() -> readDatapoint(groupAddress, dpt));
}
}
private void readDatapoint(GroupAddress groupAddress, String dpt) {
if (getClient().isConnected()) {
if (!isDPTSupported(dpt)) {
logger.warn("DPT '{}' is not supported by the KNX binding", dpt);
return;
}
Datapoint datapoint = new CommandDP(groupAddress, getThing().getUID().toString(), 0, dpt);
getClient().readDatapoint(datapoint);
}
}
@Override
public boolean listensTo(GroupAddress destination) {
return groupAddresses.contains(destination);
}
/** KNXIO remember controls, removeIf may be null */
@SuppressWarnings("null")
private void rememberRespondingSpec(OutboundSpec commandSpec, boolean add) {
GroupAddress ga = commandSpec.getGroupAddress();
groupAddressesRespondingSpec.removeIf(spec -> spec.getGroupAddress().equals(ga));
if (add) {
groupAddressesRespondingSpec.add(commandSpec);
}
logger.trace("rememberRespondingSpec handled commandSpec for '{}' size '{}' added '{}'", ga,
groupAddressesRespondingSpec.size(), add);
}
/** Handling commands triggered from openHAB */
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Handling command '{}' for channel '{}'", command, channelUID);
if (command instanceof RefreshType && !isControl(channelUID)) {
logger.debug("Refreshing channel '{}'", channelUID);
withKNXType(channelUID, (selector, configuration) -> {
scheduleRead(selector, configuration);
});
} else {
switch (channelUID.getId()) {
case CHANNEL_RESET:
if (address != null) {
restart();
}
break;
default:
withKNXType(channelUID, (selector, channelConfiguration) -> {
OutboundSpec commandSpec = selector.getCommandSpec(channelConfiguration, typeHelper, command);
// only send GroupValueWrite to KNX if GA is not blocked once
if (commandSpec != null
&& !groupAddressesWriteBlockedOnce.remove(commandSpec.getGroupAddress())) {
getClient().writeToKNX(commandSpec);
if (isControl(channelUID)) {
rememberRespondingSpec(commandSpec, true);
}
} else {
logger.debug(
"None of the configured GAs on channel '{}' could handle the command '{}' of type '{}'",
channelUID, command, command.getClass().getSimpleName());
}
});
break;
}
}
}
private boolean isControl(ChannelUID channelUID) {
ChannelTypeUID channelTypeUID = getChannelTypeUID(channelUID);
return CONTROL_CHANNEL_TYPES.contains(channelTypeUID.getId());
}
private ChannelTypeUID getChannelTypeUID(ChannelUID channelUID) {
Channel channel = getThing().getChannel(channelUID.getId());
Objects.requireNonNull(channel);
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
Objects.requireNonNull(channelTypeUID);
return channelTypeUID;
}
/** KNXIO */
private void sendGroupValueResponse(Channel channel, GroupAddress destination) {
Set<GroupAddress> rsa = getKNXChannelType(channel).getWriteAddresses(channel.getConfiguration());
if (!rsa.isEmpty()) {
logger.trace("onGroupRead size '{}'", rsa.size());
withKNXType(channel, (selector, configuration) -> {
Optional<OutboundSpec> os = groupAddressesRespondingSpec.stream().filter(spec -> {
GroupAddress groupAddress = spec.getGroupAddress();
if (groupAddress != null) {
return groupAddress.equals(destination);
}
return false;
}).findFirst();
if (os.isPresent()) {
logger.trace("onGroupRead respondToKNX '{}'", os.get().getGroupAddress());
/** KNXIO: sending real "GroupValueResponse" to the KNX bus. */
getClient().respondToKNX(os.get());
}
});
}
}
/**
* KNXIO, extended with the ability to respond on "GroupValueRead" telegrams with "GroupValueResponse" telegram
*/
@Override
public void onGroupRead(AbstractKNXClient client, IndividualAddress source, GroupAddress destination, byte[] asdu) {
logger.trace("onGroupRead Thing '{}' received a GroupValueRead telegram from '{}' for destination '{}'",
getThing().getUID(), source, destination);
for (Channel channel : getThing().getChannels()) {
if (isControl(channel.getUID())) {
withKNXType(channel, (selector, configuration) -> {
OutboundSpec responseSpec = selector.getResponseSpec(configuration, destination,
RefreshType.REFRESH);
if (responseSpec != null) {
logger.trace("onGroupRead isControl -> postCommand");
// This event should be sent to KNX as GroupValueResponse immediately.
sendGroupValueResponse(channel, destination);
// Send REFRESH to openHAB to get this event for scripting with postCommand
// and remember to ignore/block this REFRESH to be sent back to KNX as GroupValueWrite after
// postCommand is done!
groupAddressesWriteBlockedOnce.add(destination);
postCommand(channel.getUID().getId(), RefreshType.REFRESH);
}
});
}
}
}
@Override
public void onGroupReadResponse(AbstractKNXClient client, IndividualAddress source, GroupAddress destination,
byte[] asdu) {
// GroupValueResponses are treated the same as GroupValueWrite telegrams
logger.trace("onGroupReadResponse Thing '{}' processes a GroupValueResponse telegram for destination '{}'",
getThing().getUID(), destination);
onGroupWrite(client, source, destination, asdu);
}
/**
* KNXIO, here value changes are set, coming from KNX OR openHAB.
*/
@Override
public void onGroupWrite(AbstractKNXClient client, IndividualAddress source, GroupAddress destination,
byte[] asdu) {
logger.debug("onGroupWrite Thing '{}' received a GroupValueWrite telegram from '{}' for destination '{}'",
getThing().getUID(), source, destination);
for (Channel channel : getThing().getChannels()) {
withKNXType(channel, (selector, configuration) -> {
InboundSpec listenSpec = selector.getListenSpec(configuration, destination);
if (listenSpec != null) {
logger.trace(
"onGroupWrite Thing '{}' processes a GroupValueWrite telegram for destination '{}' for channel '{}'",
getThing().getUID(), destination, channel.getUID());
/**
* Remember current KNXIO outboundSpec only if it is a control channel.
*/
if (isControl(channel.getUID())) {
logger.trace("onGroupWrite isControl");
Type type = typeHelper.toType(
new CommandDP(destination, getThing().getUID().toString(), 0, listenSpec.getDPT()),
asdu);
if (type != null) {
OutboundSpec commandSpec = selector.getCommandSpec(configuration, typeHelper, type);
if (commandSpec != null) {
rememberRespondingSpec(commandSpec, true);
}
}
}
processDataReceived(destination, asdu, listenSpec, channel.getUID());
}
});
}
}
private void processDataReceived(GroupAddress destination, byte[] asdu, InboundSpec listenSpec,
ChannelUID channelUID) {
if (!isDPTSupported(listenSpec.getDPT())) {
logger.warn("DPT '{}' is not supported by the KNX binding.", listenSpec.getDPT());
return;
}
Datapoint datapoint = new CommandDP(destination, getThing().getUID().toString(), 0, listenSpec.getDPT());
Type type = typeHelper.toType(datapoint, asdu);
if (type != null) {
if (isControl(channelUID)) {
Channel channel = getThing().getChannel(channelUID.getId());
Object repeat = channel != null ? channel.getConfiguration().get(KNXBindingConstants.REPEAT_FREQUENCY)
: null;
int frequency = repeat != null ? ((BigDecimal) repeat).intValue() : 0;
if (KNXBindingConstants.CHANNEL_DIMMER_CONTROL.equals(getChannelTypeUID(channelUID).getId())
&& (type instanceof UnDefType || type instanceof IncreaseDecreaseType) && frequency > 0) {
// continuous dimming by the binding
if (UnDefType.UNDEF.equals(type)) {
ScheduledFuture<?> future = channelFutures.remove(channelUID);
if (future != null) {
future.cancel(false);
}
} else if (type instanceof IncreaseDecreaseType) {
ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> {
postCommand(channelUID, (Command) type);
}, 0, frequency, TimeUnit.MILLISECONDS);
ScheduledFuture<?> previousFuture = channelFutures.put(channelUID, future);
if (previousFuture != null) {
previousFuture.cancel(true);
}
}
} else {
if (type instanceof Command) {
logger.trace("processDataReceived postCommand new value '{}' for GA '{}'", asdu, address);
postCommand(channelUID, (Command) type);
}
}
} else {
if (type instanceof State) {
updateState(channelUID, (State) type);
}
}
} else {
String s = asduToHex(asdu);
logger.warn(
"Ignoring KNX bus data: couldn't transform to any Type (destination='{}', datapoint='{}', data='{}')",
destination, datapoint, s);
}
}
private boolean isDPTSupported(@Nullable String dpt) {
return typeHelper.toTypeClass(dpt) != null;
}
private KNXChannelType getKNXChannelType(Channel channel) {
return KNXChannelTypes.getType(channel.getChannelTypeUID());
}
}

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
/**
* Enumeration containing the firmware types.
*
* @author Karel Goderis - Initial contribution
*/
public enum Firmware {
F0(0, "BCU 1, BCU 2, BIM M113"),
F1(1, "Unidirectional devices"),
F3(3, "Property based device management"),
F7(7, "BIM M112"),
F8(8, "IR Decoder, TP1 legacy"),
F9(9, "Repeater, Coupler");
private int code;
private String name;
private Firmware(int code, String name) {
this.code = code;
this.name = name;
}
@Override
public String toString() {
return name;
}
public static String getName(int code) {
for (Firmware c : Firmware.values()) {
if (c.code == code) {
return c.name;
}
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.knx.internal.client.BusMessageListener;
import tuwien.auto.calimero.GroupAddress;
/**
* The {@link GroupAddressListener} is an interface that needs to be
* implemented by classes that want to listen to Group Addresses
* on the KNX bus
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public interface GroupAddressListener extends BusMessageListener {
/**
* Called to verify if the GroupAddressListener has an interest in the given GroupAddress
*
* @param destination
*/
public boolean listensTo(GroupAddress destination);
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import java.net.InetSocketAddress;
import java.text.MessageFormat;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.KNXBindingConstants;
import org.openhab.binding.knx.internal.client.CustomKNXNetworkLinkIP;
import org.openhab.binding.knx.internal.client.IPClient;
import org.openhab.binding.knx.internal.client.KNXClient;
import org.openhab.binding.knx.internal.client.NoOpClient;
import org.openhab.binding.knx.internal.config.IPBridgeConfiguration;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link IPBridgeThingHandler} is responsible for handling commands, which are
* sent to one of the channels. It implements a KNX/IP Gateway, that either acts a a
* conduit for other {@link DeviceThingHandler}s, or for Channels that are
* directly defined on the bridge
*
* @author Karel Goderis - Initial contribution
* @author Simon Kaufmann - Refactoring & cleanup
*/
@NonNullByDefault
public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler {
private static final String MODE_ROUTER = "ROUTER";
private static final String MODE_TUNNEL = "TUNNEL";
private final Logger logger = LoggerFactory.getLogger(IPBridgeThingHandler.class);
private @Nullable IPClient client;
private final NetworkAddressService networkAddressService;
public IPBridgeThingHandler(Bridge bridge, NetworkAddressService networkAddressService) {
super(bridge);
this.networkAddressService = networkAddressService;
}
@Override
public void initialize() {
IPBridgeConfiguration config = getConfigAs(IPBridgeConfiguration.class);
int autoReconnectPeriod = config.getAutoReconnectPeriod();
if (autoReconnectPeriod != 0 && autoReconnectPeriod < 30) {
logger.info("autoReconnectPeriod for {} set to {}s, allowed range is 0 (never) or >30", thing.getUID(),
autoReconnectPeriod);
autoReconnectPeriod = 30;
config.setAutoReconnectPeriod(autoReconnectPeriod);
}
String localSource = config.getLocalSourceAddr();
String connectionTypeString = config.getType();
int port = config.getPortNumber().intValue();
String ip = config.getIpAddress();
InetSocketAddress localEndPoint = null;
boolean useNAT = false;
int ipConnectionType;
if (MODE_TUNNEL.equalsIgnoreCase(connectionTypeString)) {
useNAT = config.getUseNAT() != null ? config.getUseNAT() : false;
ipConnectionType = CustomKNXNetworkLinkIP.TUNNELING;
} else if (MODE_ROUTER.equalsIgnoreCase(connectionTypeString)) {
useNAT = false;
if (ip == null || ip.isEmpty()) {
ip = KNXBindingConstants.DEFAULT_MULTICAST_IP;
}
ipConnectionType = CustomKNXNetworkLinkIP.ROUTING;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
MessageFormat.format("Unknown IP connection type {0}. Known types are either 'TUNNEL' or 'ROUTER'",
connectionTypeString));
return;
}
if (ip == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"The 'ipAddress' of the gateway must be configured in 'TUNNEL' mode");
return;
}
if (config.getLocalIp() != null && !config.getLocalIp().isEmpty()) {
localEndPoint = new InetSocketAddress(config.getLocalIp(), 0);
} else {
localEndPoint = new InetSocketAddress(networkAddressService.getPrimaryIpv4HostAddress(), 0);
}
updateStatus(ThingStatus.UNKNOWN);
client = new IPClient(ipConnectionType, ip, localSource, port, localEndPoint, useNAT, autoReconnectPeriod,
thing.getUID(), config.getResponseTimeout().intValue(), config.getReadingPause().intValue(),
config.getReadRetriesLimit().intValue(), getScheduler(), this);
client.initialize();
}
@Override
public void dispose() {
super.dispose();
if (client != null) {
client.dispose();
client = null;
}
}
@Override
protected KNXClient getClient() {
KNXClient ret = client;
if (ret == null) {
return new NoOpClient();
}
return ret;
}
}

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.knx.internal.client.KNXClient;
import org.openhab.binding.knx.internal.client.StatusUpdateCallback;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import tuwien.auto.calimero.IndividualAddress;
import tuwien.auto.calimero.mgmt.Destination;
/**
* The {@link KNXBridgeBaseThingHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Simon Kaufmann - Initial contribution and API
*/
@NonNullByDefault
public abstract class KNXBridgeBaseThingHandler extends BaseBridgeHandler implements StatusUpdateCallback {
protected ConcurrentHashMap<IndividualAddress, Destination> destinations = new ConcurrentHashMap<>();
private final ScheduledExecutorService knxScheduler = ThreadPoolManager.getScheduledPool("knx");
private final ScheduledExecutorService backgroundScheduler = Executors.newSingleThreadScheduledExecutor();
public KNXBridgeBaseThingHandler(Bridge bridge) {
super(bridge);
}
protected abstract KNXClient getClient();
@Override
public void handleUpdate(ChannelUID channelUID, State newState) {
// Nothing to do here
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to do here
}
public ScheduledExecutorService getScheduler() {
return knxScheduler;
}
public ScheduledExecutorService getBackgroundScheduler() {
return backgroundScheduler;
}
@Override
public void updateStatus(ThingStatus status) {
super.updateStatus(status);
}
@Override
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
super.updateStatus(status, statusDetail, description);
}
}

View File

@@ -0,0 +1,347 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Enumeration containing all the KNX device manufactureres.
*
* @author Karel Goderis - Initial contribution
*/
@NonNullByDefault
public enum Manufacturer {
M1(1, "Siemens"),
M2(2, "ABB"),
M4(4, "Albrecht Jung"),
M5(5, "Bticino"),
M6(6, "Berker"),
M7(7, "Busch-Jaeger Elektro"),
M8(8, "GIRA Giersiepen"),
M9(9, "Hager Electro"),
M10(10, "INSTA ELEKTRO"),
M11(11, "LEGRAND Appareillage électrique"),
M12(12, "Merten"),
M14(14, "ABB SpA SACE Division"),
M22(22, "Siedle & Söhne"),
M24(24, "Eberle"),
M25(25, "GEWISS"),
M27(27, "Albert Ackermann"),
M28(28, "Schupa GmbH"),
M29(29, "ABB SCHWEIZ"),
M30(30, "Feller"),
M32(32, "DEHN & SÖHNE"),
M33(33, "CRABTREE"),
M36(36, "Paul Hochköpper"),
M37(37, "Altenburger Electronic"),
M41(41, "Grässlin"),
M42(42, "Simon"),
M44(44, "VIMAR"),
M45(45, "Moeller Gebäudeautomation KG"),
M46(46, "Eltako"),
M49(49, "Bosch-Siemens Haushaltsgeräte"),
M52(52, "RITTO GmbH&Co.KG"),
M53(53, "Power Controls"),
M55(55, "ZUMTOBEL"),
M57(57, "Phoenix Contact"),
M61(61, "WAGO Kontakttechnik"),
M66(66, "Wieland Electric"),
M67(67, "Hermann Kleinhuis"),
M69(69, "Stiebel Eltron"),
M71(71, "Tehalit"),
M72(72, "Theben AG"),
M73(73, "Wilhelm Rutenbeck"),
M75(75, "Winkhaus"),
M76(76, "Robert Bosch"),
M78(78, "Somfy"),
M80(80, "Woertz"),
M81(81, "Viessmann Werke"),
M82(82, "Theodor HEIMEIER Metallwerk"),
M83(83, "Joh. Vaillant"),
M85(85, "AMP Deutschland"),
M89(89, "Bosch Thermotechnik GmbH"),
M90(90, "SEF - ECOTEC"),
M92(92, "DORMA GmbH + Co. KG"),
M93(93, "WindowMaster A/S"),
M94(94, "Walther Werke"),
M95(95, "ORAS"),
M97(97, "Dätwyler"),
M98(98, "Electrak"),
M99(99, "Techem"),
M100(100, "Schneider Electric Industries SAS"),
M101(101, "WHD Wilhelm Huber + Söhne"),
M102(102, "Bischoff Elektronik"),
M104(104, "JEPAZ"),
M105(105, "RTS Automation"),
M106(106, "EIBMARKT GmbH"),
M107(107, "WAREMA electronic GmbH"),
M108(108, "Eelectron"),
M109(109, "Belden Wire & Cable B.V."),
M110(110, "Becker-Antriebe GmbH"),
M111(111, "J.Stehle+Söhne GmbH"),
M112(112, "AGFEO"),
M113(113, "Zennio"),
M114(114, "TAPKO Technologies"),
M115(115, "HDL"),
M116(116, "Uponor"),
M117(117, "se Lightmanagement AG"),
M118(118, "Arcus-eds"),
M119(119, "Intesis"),
M120(120, "Herholdt Controls srl"),
M121(121, "Zublin AG"),
M122(122, "Durable Technologies"),
M123(123, "Innoteam"),
M124(124, "ise GmbH"),
M125(125, "TEAM FOR TRONICS"),
M126(126, "CIAT"),
M127(127, "Remeha BV"),
M128(128, "ESYLUX"),
M129(129, "BASALTE"),
M130(130, "Vestamatic"),
M131(131, "MDT technologies"),
M132(132, "Warendorfer Küchen GmbH"),
M133(133, "Video-Star"),
M134(134, "Sitek"),
M135(135, "CONTROLtronic"),
M136(136, "function Technology"),
M137(137, "AMX"),
M138(138, "ELDAT"),
M139(139, "VIKO"),
M140(140, "Pulse Technologies"),
M141(141, "Crestron"),
M142(142, "STEINEL professional"),
M143(143, "BILTON LED Lighting"),
M144(144, "denro AG"),
M145(145, "GePro"),
M146(146, "preussen automation"),
M147(147, "Zoppas Industries"),
M148(148, "MACTECH"),
M149(149, "TECHNO-TREND"),
M150(150, "FS Cables"),
M151(151, "Delta Dore"),
M152(152, "Eissound"),
M153(153, "Cisco"),
M154(154, "Dinuy"),
M155(155, "iKNiX"),
M156(156, "Rademacher Geräte-Elektronik GmbH & Co. KG"),
M157(157, "EGi Electroacustica General Iberica"),
M158(158, "Ingenium"),
M159(159, "ElabNET"),
M160(160, "Blumotix"),
M161(161, "Hunter Douglas"),
M162(162, "APRICUM"),
M163(163, "TIANSU Automation"),
M164(164, "Bubendorff"),
M165(165, "MBS GmbH"),
M166(166, "Enertex Bayern GmbH"),
M167(167, "BMS"),
M168(168, "Sinapsi"),
M169(169, "Embedded Systems SIA"),
M170(170, "KNX1"),
M171(171, "Tokka"),
M172(172, "NanoSense"),
M173(173, "PEAR Automation GmbH"),
M174(174, "DGA"),
M175(175, "Lutron"),
M176(176, "AIRZONE ALTRA"),
M177(177, "Lithoss Design Switches"),
M178(178, "3ATEL"),
M179(179, "Philips Controls"),
M180(180, "VELUX A/S"),
M181(181, "LOYTEC"),
M182(182, "SBS S.p.A."),
M183(183, "SIRLAN Technologies"),
M184(184, "Bleu Comm' Azur"),
M185(185, "IT GmbH"),
M186(186, "RENSON"),
M187(187, "HEP Group"),
M188(188, "Balmart"),
M189(189, "GFS GmbH"),
M190(190, "Schenker Storen AG"),
M191(191, "Algodue Elettronica S.r.L."),
M192(192, "Newron System"),
M193(193, "maintronic"),
M194(194, "Vantage"),
M195(195, "Foresis"),
M196(196, "Research & Production Association SEM"),
M197(197, "Weinzierl Engineering GmbH"),
M198(198, "Möhlenhoff Wärmetechnik GmbH"),
M199(199, "PKC-GROUP Oyj"),
M200(200, "B.E.G."),
M201(201, "Elsner Elektronik GmbH"),
M202(202, "Siemens Building Technologies (HK/China) Ltd."),
M204(204, "Eutrac"),
M205(205, "Gustav Hensel GmbH & Co. KG"),
M206(206, "GARO AB"),
M207(207, "Waldmann Lichttechnik"),
M208(208, "SCHÜCO"),
M209(209, "EMU"),
M210(210, "JNet Systems AG"),
M214(214, "O.Y.L. Electronics"),
M215(215, "Galax System"),
M216(216, "Disch"),
M217(217, "Aucoteam"),
M218(218, "Luxmate Controls"),
M219(219, "Danfoss"),
M220(220, "AST GmbH"),
M222(222, "WILA Leuchten"),
M223(223, "b+b Automations- und Steuerungstechnik"),
M225(225, "Lingg & Janke"),
M227(227, "Sauter"),
M228(228, "SIMU"),
M232(232, "Theben HTS AG"),
M233(233, "Amann GmbH"),
M234(234, "BERG Energiekontrollsysteme GmbH"),
M235(235, "Hüppe Form Sonnenschutzsysteme GmbH"),
M237(237, "Oventrop KG"),
M238(238, "Griesser AG"),
M239(239, "IPAS GmbH"),
M240(240, "elero GmbH"),
M241(241, "Ardan Production and Industrial Controls Ltd."),
M242(242, "Metec Meßtechnik GmbH"),
M244(244, "ELKA-Elektronik GmbH"),
M245(245, "ELEKTROANLAGEN D. NAGEL"),
M246(246, "Tridonic Bauelemente GmbH"),
M248(248, "Stengler Gesellschaft"),
M249(249, "Schneider Electric (MG)"),
M250(250, "KNX Association"),
M251(251, "VIVO"),
M252(252, "Hugo Müller GmbH & Co KG"),
M253(253, "Siemens HVAC"),
M254(254, "APT"),
M256(256, "HighDom"),
M257(257, "Top Services"),
M258(258, "ambiHome"),
M259(259, "DATEC electronic AG"),
M260(260, "ABUS Security-Center"),
M261(261, "Lite-Puter"),
M262(262, "Tantron Electronic"),
M263(263, "Yönnet"),
M264(264, "DKX Tech"),
M265(265, "Viatron"),
M266(266, "Nautibus"),
M268(268, "Longchuang"),
M269(269, "Air-On AG"),
M270(270, "ib-company GmbH"),
M271(271, "SATION"),
M272(272, "Agentilo GmbH"),
M273(273, "Makel Elektrik"),
M274(274, "Helios Ventilatoren"),
M275(275, "Otto Solutions Pte Ltd"),
M276(276, "Airmaster"),
M277(277, "HEINEMANN GmbH"),
M278(278, "LDS"),
M279(279, "ASIN"),
M280(280, "Bridges"),
M281(281, "ARBONIA"),
M282(282, "KERMI"),
M283(283, "PROLUX"),
M284(284, "ClicHome"),
M285(285, "COMMAX"),
M286(286, "EAE"),
M287(287, "Tense"),
M288(288, "Seyoung Electronics"),
M289(289, "Lifedomus"),
M290(290, "EUROtronic Technology GmbH"),
M291(291, "tci"),
M292(292, "Rishun Electronic"),
M293(293, "Zipato"),
M294(294, "cm-security GmbH & Co KG"),
M295(295, "Qing Cables"),
M296(296, "LABIO"),
M297(297, "Coster Tecnologie Elettroniche S.p.A."),
M298(298, "E.G.E"),
M299(299, "NETxAutomation"),
M300(300, "tecalor"),
M301(301, "Urmet Electronics (Huizhou) Ltd."),
M302(302, "Peiying Building Control"),
M303(303, "BPT S.p.A. a Socio Unico"),
M304(304, "Kanontec - KanonBUS"),
M305(305, "ISER Tech"),
M306(306, "Fineline"),
M307(307, "CP Electronics Ltd"),
M308(308, "Servodan A/S"),
M309(309, "Simon"),
M310(310, "GM modular pvt. Ltd."),
M311(311, "FU CHENG Intelligence"),
M312(312, "NexKon"),
M313(313, "FEEL s.r.l"),
M314(314, "Not Assigned"),
M315(315, "Shenzhen Fanhai Sanjiang Electronics Co., Ltd."),
M316(316, "Jiuzhou Greeble"),
M317(317, "Aumüller Aumatic GmbH"),
M318(318, "Etman Electric"),
M319(319, "EMT Controls"),
M320(320, "ZidaTech AG"),
M321(321, "IDGS bvba"),
M322(322, "dakanimo"),
M323(323, "Trebor Automation AB"),
M324(324, "Satel sp. z o.o."),
M325(325, "Russound, Inc."),
M326(326, "Midea Heating & Ventilating Equipment CO LTD"),
M327(327, "Consorzio Terranuova"),
M328(328, "Wolf Heiztechnik GmbH"),
M329(329, "SONTEC"),
M330(330, "Belcom Cables Ltd."),
M331(331, "Guangzhou SeaWin Electrical Technologies Co., Ltd."),
M332(332, "Acrel"),
M333(333, "Franke Aquarotter GmbH"),
M334(334, "Orion Systems"),
M335(335, "Schrack Technik GmbH"),
M336(336, "INSPRID"),
M337(337, "Sunricher"),
M338(338, "Menred automation system(shanghai) Co.,Ltd."),
M339(339, "Aurex"),
M340(340, "Josef Barthelme GmbH & Co. KG"),
M341(341, "Architecture Numerique"),
M342(342, "UP GROUP"),
M343(343, "Teknos-Avinno"),
M344(344, "Ningbo Dooya Mechanic & Electronic Technology"),
M345(345, "Thermokon Sensortechnik GmbH"),
M346(346, "BELIMO Automation AG"),
M347(347, "Zehnder Group International AG"),
M348(348, "sks Kinkel Elektronik"),
M349(349, "ECE Wurmitzer GmbH"),
M350(350, "LARS"),
M351(351, "URC"),
M352(352, "LightControl"),
M353(353, "ShenZhen YM"),
M354(354, "MEAN WELL Enterprises Co. Ltd."),
M355(355, "OSix"),
M356(356, "AYPRO Technology"),
M357(357, "Hefei Ecolite Software"),
M358(358, "Enno"),
M359(359, "Ohosure");
private int code;
private String name;
private Manufacturer(int code, String name) {
this.code = code;
this.name = name;
}
@Override
public String toString() {
return name;
}
public static String getName(int code) {
for (Manufacturer c : Manufacturer.values()) {
if (c.code == code) {
return c.name;
}
}
return "Unknown";
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.knx.internal.client.AbstractKNXClient;
import org.openhab.binding.knx.internal.client.SerialClient;
import org.openhab.binding.knx.internal.config.SerialBridgeConfiguration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
/**
* The {@link IPBridgeThingHandler} is responsible for handling commands, which are
* sent to one of the channels. It implements a KNX Serial/USB Gateway, that either acts a a
* conduit for other {@link DeviceThingHandler}s, or for Channels that are
* directly defined on the bridge
*
* @author Karel Goderis - Initial contribution
* @author Simon Kaufmann - Refactoring & cleanup
*/
@NonNullByDefault
public class SerialBridgeThingHandler extends KNXBridgeBaseThingHandler {
private final SerialClient client;
public SerialBridgeThingHandler(Bridge bridge) {
super(bridge);
SerialBridgeConfiguration config = getConfigAs(SerialBridgeConfiguration.class);
client = new SerialClient(config.getAutoReconnectPeriod(), thing.getUID(),
config.getResponseTimeout().intValue(), config.getReadingPause().intValue(),
config.getReadRetriesLimit().intValue(), getScheduler(), config.getSerialPort(), this);
}
@Override
public void initialize() {
updateStatus(ThingStatus.UNKNOWN);
client.initialize();
}
@Override
public void dispose() {
super.dispose();
client.dispose();
}
@Override
protected AbstractKNXClient getClient() {
return client;
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.profiles;
import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.profiles.ProfileAdvisor;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.thing.profiles.SystemProfiles;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Component;
/**
* Advisor, provider and factory for the specialized KNX profiles.
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
@Component
@NonNullByDefault
public class KNXProfileAdvisor implements ProfileAdvisor {
@Override
public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(Channel channel, @Nullable String itemType) {
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null || !channelTypeUID.getBindingId().equals(BINDING_ID)) {
return null;
}
return getSuggestedProfieTypeUID(channelTypeUID);
}
@Override
public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(ChannelType channelType, @Nullable String itemType) {
return getSuggestedProfieTypeUID(channelType.getUID());
}
private ProfileTypeUID getSuggestedProfieTypeUID(ChannelTypeUID channelTypeUID) {
if (isControl(channelTypeUID)) {
return SystemProfiles.FOLLOW;
} else {
return SystemProfiles.DEFAULT;
}
}
public static boolean isControl(ChannelTypeUID channelTypeUID) {
return CONTROL_CHANNEL_TYPES.contains(channelTypeUID.getId());
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="knx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>KNX Binding</name>
<description>This binding supports connecting to a KNX bus</description>
</binding:binding>

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<!-- Dimmer -->
<config-description uri="channel-type:knx:dimmer">
<parameter name="switch" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to toggle the dimmer on or off</description>
<required>false</required>
</parameter>
<parameter name="position" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to set the absolute position of the dimmer</description>
<required>false</required>
</parameter>
<parameter name="increaseDecrease" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to increase or decrease the dimmer</description>
<required>false</required>
</parameter>
</config-description>
<config-description uri="channel-type:knx:dimmer-control">
<parameter name="switch" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to toggle the dimmer on or off</description>
<required>false</required>
</parameter>
<parameter name="position" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to set the absolute position of the dimmer</description>
<required>false</required>
</parameter>
<parameter name="increaseDecrease" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to increase or decrease the dimmer</description>
<required>false</required>
</parameter>
<parameter name="frequency" type="integer">
<label>Frequency</label>
<description>Increase/decrease send frequency if dimming should be handled by the binding (in ms) - set to 0 if the
KNX device sends them repeatedly itself</description>
<default>0</default>
</parameter>
</config-description>
<!-- Color -->
<config-description uri="channel-type:knx:color">
<parameter name="hsb" type="text">
<label>Color Value</label>
<description>The group address(es) in Group Address Notation for the color value</description>
<required>false</required>
</parameter>
<parameter name="switch" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to toggle the color on or off</description>
<required>false</required>
</parameter>
<parameter name="position" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to set the absolute position of the color</description>
<required>false</required>
</parameter>
<parameter name="increaseDecrease" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to increase or decrease the color</description>
<required>false</required>
</parameter>
</config-description>
<config-description uri="channel-type:knx:color-control">
<parameter name="hsb" type="text">
<label>Color Value</label>
<description>The group address(es) in Group Address Notation for the color value</description>
<required>false</required>
</parameter>
<parameter name="switch" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to toggle the color on or off</description>
<required>false</required>
</parameter>
<parameter name="position" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to set the absolute position of the color</description>
<required>false</required>
</parameter>
<parameter name="increaseDecrease" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to increase or decrease the color</description>
<required>false</required>
</parameter>
<parameter name="frequency" type="integer">
<label>Frequency</label>
<description>Increase/decrease send frequency if color should be handled by the binding (in ms) - set to 0 if the KNX
device sends them repeatedly itself</description>
<default>0</default>
</parameter>
</config-description>
<!-- Rollershutter -->
<config-description uri="channel-type:knx:rollershutter">
<parameter name="upDown" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to move the shutter in the DOWN or UP direction</description>
<required>false</required>
</parameter>
<parameter name="stopMove" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to start (MOVE) or STOP shutter movement</description>
<required>false</required>
</parameter>
<parameter name="position" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation to set the absolute position of the shutter, in %</description>
<required>false</required>
</parameter>
</config-description>
<!-- Generic single-GA -->
<config-description uri="channel-type:knx:single">
<parameter name="ga" type="text">
<label>Address</label>
<description>The group address(es) in Group Address Notation</description>
<required>true</required>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="knx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="device"
extensible="switch, switch-control, dimmer, dimmer-control, rollershutter, rollershutter-control, color, color-control, contact, contact-control, number, number-control, string, string-control, datetime, datetime-control">
<supported-bridge-type-refs>
<bridge-type-ref id="ip"/>
<bridge-type-ref id="serial"/>
</supported-bridge-type-refs>
<label>KNX Device</label>
<description>An addressable basic KNX device</description>
<config-description>
<parameter name="address" type="text">
<label>Address</label>
<description>The individual address in x.y.z notation</description>
</parameter>
<parameter name="fetch" type="boolean">
<label>Fetch</label>
<description>Read out the device parameters and address/communication object tables</description>
<default>false</default>
</parameter>
<parameter name="pingInterval" type="integer">
<label>Interval</label>
<description>Interval (in seconds) between attempts to poll the device status</description>
<default>600</default>
</parameter>
<parameter name="readInterval" type="integer">
<label>Interval</label>
<description>Interval (in seconds) between attempts to read the status group addresses on the bus</description>
<default>0</default>
</parameter>
</config-description>
</thing-type>
<!-- Switch -->
<channel-type id="switch">
<item-type>Switch</item-type>
<label>Switch</label>
<description>A channel to manage a generic Group Addresses with a DPT compatible with Switch Items</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<channel-type id="switch-control">
<item-type>Switch</item-type>
<label>Switch Control</label>
<description>Control a switch item via KNX (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<!-- Dimmer -->
<channel-type id="dimmer">
<item-type>Dimmer</item-type>
<label>Dimmer</label>
<description>A channel to control a dimmer</description>
<config-description-ref uri="channel-type:knx:dimmer"/>
</channel-type>
<channel-type id="dimmer-control">
<item-type>Dimmer</item-type>
<label>Dimmer Control</label>
<description>Control a dimmer item via KNX (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:dimmer-control"/>
</channel-type>
<!-- Rollershutter -->
<channel-type id="rollershutter">
<item-type>Rollershutter</item-type>
<label>Rollershutter</label>
<description>A channel to control a rollershutter</description>
<config-description-ref uri="channel-type:knx:rollershutter"/>
</channel-type>
<channel-type id="rollershutter-control">
<item-type>Rollershutter</item-type>
<label>Rollershutter Control</label>
<description>Control a rollershutter item (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:rollershutter"/>
</channel-type>
<!-- Color -->
<channel-type id="color">
<item-type>Color</item-type>
<label>Color</label>
<description>A channel to control color information (RGB)</description>
<config-description-ref uri="channel-type:knx:color"/>
</channel-type>
<channel-type id="color-control">
<item-type>Color</item-type>
<label>Color Control</label>
<description>Control a color item (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:color-control"/>
</channel-type>
<!-- Contact -->
<channel-type id="contact">
<item-type>Contact</item-type>
<label>Contact</label>
<description>A channel to manage a generic Group Addresses with a DPT compatible with Contact Items</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<channel-type id="contact-control">
<item-type>Contact</item-type>
<label>Contact Control</label>
<description>Control a contact item (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<!-- Generic Number -->
<channel-type id="number">
<item-type>Number</item-type>
<label>Number</label>
<description>A channel to manage a generic Group Addresses with a DPT compatible with Number Items</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<channel-type id="number-control">
<item-type>Number</item-type>
<label>Number Control</label>
<description>Control a number item (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<!-- Generic String -->
<channel-type id="string">
<item-type>String</item-type>
<label>String</label>
<description>A channel to manage a generic Group Addresses with a DPT compatible with String Items</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<channel-type id="string-control">
<item-type>String</item-type>
<label>String Control</label>
<description>Control a string item (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<!-- Generic Date/Time -->
<channel-type id="datetime">
<item-type>DateTime</item-type>
<label>DateTime</label>
<description>A channel to manage a generic Group Addresses with a DPT compatible with DateTime Items</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
<channel-type id="datetime-control">
<item-type>DateTime Control</item-type>
<label>DateTime</label>
<description>Control a date/time item (i.e. the status is not owned by KNX)</description>
<config-description-ref uri="channel-type:knx:single"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="knx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Bridge Thing Type -->
<bridge-type id="ip">
<label>KNX/IP Gateway</label>
<description>This is a KNX IP interface or router</description>
<config-description>
<parameter name="type" type="text">
<label>IP Connection Type</label>
<description>The ip connection type for connecting to the KNX bus. Could be either TUNNEL or ROUTER</description>
<required>true</required>
<options>
<option value="TUNNEL">Tunnel</option>
<option value="ROUTER">Router</option>
</options>
</parameter>
<parameter name="ipAddress" type="text">
<label>Network Address</label>
<description>Network address of the KNX/IP gateway</description>
<context>network-address</context>
</parameter>
<parameter name="portNumber" type="integer">
<description>Port number of the KNX/IP gateway</description>
<required>false</required> <!-- Only required in TUNNEL mode -->
<label>Port</label>
<default>3671</default>
</parameter>
<parameter name="localIp" type="text">
<label>Local Network Address</label>
<description>Network address of the local host to be used to set up the connection to the KNX/IP gateway</description>
<context>network-address</context>
</parameter>
<parameter name="localSourceAddr" type="text">
<label>Local Device Address</label>
<description>The Physical Address (Individual Address) in x.y.z notation for identification of this KNX/IP gateway
within the KNX bus</description>
<default>0.0.0</default>
</parameter>
<parameter name="useNAT" type="boolean">
<label>Use NAT</label>
<description>Set to "true" when having network address translation between this server and the gateway</description>
<default>false</default>
</parameter>
<parameter name="readingPause" type="integer">
<label>Reading Pause</label>
<description>Time in milliseconds of how long should be paused between two read requests to the bus during
initialization</description>
<default>50</default>
</parameter>
<parameter name="responseTimeout" type="integer">
<label>Response Timeout</label>
<description>Seconds to wait for a response from the KNX bus</description>
<default>10</default>
</parameter>
<parameter name="readRetriesLimit" type="integer">
<label>Read Retries Limit</label>
<description>Limits the read retries while initialization from the KNX bus</description>
<default>3</default>
</parameter>
<parameter name="autoReconnectPeriod" type="integer">
<label>Auto Reconnect Period</label>
<description>Seconds between connection retries when KNX link has been lost, 0 means never retry, minimum 30s</description>
<default>60</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="knx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Bridge Thing Type -->
<bridge-type id="serial">
<label>KNX FT1.2 Interface</label>
<description>This is a serial interface for accessing the KNX bus</description>
<config-description>
<parameter name="serialPort" type="text">
<context>serial-port </context>
<label>Serial Port</label>
<description>The serial port to use for connecting to the KNX bus</description>
<required>true</required>
</parameter>
<parameter name="readingPause" type="integer">
<label>Reading Pause</label>
<description>Time in milliseconds of how long should be paused between two read requests to the bus during
initialization</description>
<required>true</required>
<default>50</default>
</parameter>
<parameter name="responseTimeout" type="integer">
<label>Response Timeout</label>
<description>Seconds to wait for a response from the KNX bus</description>
<required>true</required>
<default>10</default>
</parameter>
<parameter name="readRetriesLimit" type="integer">
<label>Read Retries Limit</label>
<description>Limits the read retries while initialization from the KNX bus</description>
<required>true</required>
<default>3</default>
</parameter>
<parameter name="autoReconnectPeriod" type="integer">
<label>Auto Reconnect Period</label>
<description>Seconds between connect retries when KNX link has been lost, 0 means never retry</description>
<required>true</required>
<default>0</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,113 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.channel;
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.junit.Before;
import org.junit.Test;
/**
*
* @author Simon Kaufmann - initial contribution and API.
*
*/
public class KNXChannelTypeTest {
private KNXChannelType ct;
@Before
public void setup() {
ct = new MyKNXChannelType("");
}
@Test
public void testParse_withDPT_multiple_withRead() {
ChannelConfiguration res = ct.parse("5.001:<1/3/22+0/3/22+<0/8/15");
assertEquals("5.001", res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA());
assertTrue(res.getMainGA().isRead());
assertEquals(3, res.getListenGAs().size());
assertEquals(2, res.getReadGAs().size());
}
@Test
public void testParse_withDPT_multiple_withoutRead() {
ChannelConfiguration res = ct.parse("5.001:1/3/22+0/3/22+0/8/15");
assertEquals("5.001", res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA());
assertFalse(res.getMainGA().isRead());
assertEquals(3, res.getListenGAs().size());
assertEquals(0, res.getReadGAs().size());
}
@Test
public void testParse_withoutDPT_single_withoutRead() {
ChannelConfiguration res = ct.parse("1/3/22");
assertNull(res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA());
assertFalse(res.getMainGA().isRead());
assertEquals(1, res.getListenGAs().size());
assertEquals(0, res.getReadGAs().size());
}
@Test
public void testParse_withoutDPT_single_witRead() {
ChannelConfiguration res = ct.parse("<1/3/22");
assertNull(res.getDPT());
assertEquals("1/3/22", res.getMainGA().getGA());
assertTrue(res.getMainGA().isRead());
assertEquals(1, res.getListenGAs().size());
assertEquals(1, res.getReadGAs().size());
}
@Test
public void testParse_twoLevel() {
ChannelConfiguration res = ct.parse("5.001:<3/1024+<4/1025");
assertEquals("3/1024", res.getMainGA().getGA());
assertEquals(2, res.getListenGAs().size());
assertEquals(2, res.getReadGAs().size());
}
@Test
public void testParse_freeLevel() {
ChannelConfiguration res = ct.parse("5.001:<4610+<4611");
assertEquals("4610", res.getMainGA().getGA());
assertEquals(2, res.getListenGAs().size());
assertEquals(2, res.getReadGAs().size());
}
private static class MyKNXChannelType extends KNXChannelType {
public MyKNXChannelType(String channelTypeID) {
super(channelTypeID);
}
@Override
protected @NonNull Set<@NonNull String> getAllGAKeys() {
return Collections.emptySet();
}
@Override
protected @NonNull String getDefaultDPT(@NonNull String gaConfigKey) {
return "";
}
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.knx.internal.dpt;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.openhab.core.library.types.DecimalType;
/**
*
* @author Simon Kaufmann - initial contribution and API
*
*/
public class KNXCoreTypeMapperTest {
@Test
public void testToDPTValue_trailingZeroesStrippedOff() {
assertEquals("3", new KNXCoreTypeMapper().toDPTValue(new DecimalType("3"), "17.001"));
assertEquals("3", new KNXCoreTypeMapper().toDPTValue(new DecimalType("3.0"), "17.001"));
}
}