added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
10
bundles/org.openhab.binding.knx/src/main/feature/feature.xml
Normal file
10
bundles/org.openhab.binding.knx/src/main/feature/feature.xml
Normal 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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user