2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.knx.internal.channel;
15 import static java.util.stream.Collectors.*;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashSet;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Objects;
23 import java.util.Optional;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.knx.internal.KNXTypeMapper;
31 import org.openhab.binding.knx.internal.client.InboundSpec;
32 import org.openhab.binding.knx.internal.client.OutboundSpec;
33 import org.openhab.core.config.core.Configuration;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.library.types.QuantityType;
36 import org.openhab.core.types.Type;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
40 import tuwien.auto.calimero.GroupAddress;
41 import tuwien.auto.calimero.KNXFormatException;
44 * Meta-data abstraction for the KNX channel configurations.
46 * @author Simon Kaufmann - initial contribution and API.
50 public abstract class KNXChannelType {
52 private static final Pattern PATTERN = Pattern.compile(
53 "^((?<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}))*)$");
55 private static final Pattern PATTERN_LISTEN = Pattern
56 .compile("\\+((?<read>\\<)?(?<GA>[0-9]{1,5}(/[0-9]{1,4}){0,2}))");
58 private final Logger logger = LoggerFactory.getLogger(KNXChannelType.class);
59 private final Set<String> channelTypeIDs;
61 KNXChannelType(String... channelTypeIDs) {
62 this.channelTypeIDs = new HashSet<>(Arrays.asList(channelTypeIDs));
65 final Set<String> getChannelIDs() {
66 return channelTypeIDs;
70 protected final ChannelConfiguration parse(@Nullable String fancy) {
74 Matcher matcher = PATTERN.matcher(fancy.replace(" ", ""));
76 if (matcher.matches()) {
78 String input = matcher.group("listenGAs");
79 Matcher m2 = PATTERN_LISTEN.matcher(input);
80 List<GroupAddressConfiguration> listenGAs = new LinkedList<>();
82 listenGAs.add(new GroupAddressConfiguration(m2.group("GA"), m2.group("read") != null));
86 GroupAddressConfiguration mainGA = new GroupAddressConfiguration(matcher.group("mainGA"),
87 matcher.group("read") != null);
89 return new ChannelConfiguration(matcher.group("dpt"), mainGA, listenGAs);
94 protected abstract Set<String> getAllGAKeys();
96 public final Set<GroupAddress> getListenAddresses(Configuration channelConfiguration) {
97 Set<GroupAddress> ret = new HashSet<>();
98 for (String key : getAllGAKeys()) {
99 ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
101 ret.addAll(conf.getListenGAs().stream().map(this::toGroupAddress).collect(toSet()));
107 public final Set<GroupAddress> getReadAddresses(Configuration channelConfiguration) {
108 Set<GroupAddress> ret = new HashSet<>();
109 for (String key : getAllGAKeys()) {
110 ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
112 ret.addAll(conf.getReadGAs().stream().map(this::toGroupAddress).collect(toSet()));
118 public final Set<GroupAddress> getWriteAddresses(Configuration channelConfiguration) {
119 Set<GroupAddress> ret = new HashSet<>();
120 for (String key : getAllGAKeys()) {
121 ChannelConfiguration conf = parse((String) channelConfiguration.get(key));
123 GroupAddress ga = toGroupAddress(conf.getMainGA());
132 private @Nullable GroupAddress toGroupAddress(GroupAddressConfiguration ga) {
134 return new GroupAddress(ga.getGA());
135 } catch (KNXFormatException e) {
136 logger.warn("Could not parse group address '{}'", ga.getGA());
141 protected final Set<GroupAddress> getAddresses(@Nullable Configuration configuration, Iterable<String> addresses)
142 throws KNXFormatException {
143 Set<GroupAddress> ret = new HashSet<>();
144 for (String address : addresses) {
145 if (configuration != null && configuration.get(address) != null) {
146 ret.add(new GroupAddress((String) configuration.get(address)));
152 protected final boolean isEquals(@Nullable Configuration configuration, String address, GroupAddress groupAddress)
153 throws KNXFormatException {
154 if (configuration != null && configuration.get(address) != null) {
155 return Objects.equals(new GroupAddress((String) configuration.get(address)), groupAddress);
160 protected final Set<String> asSet(String... values) {
161 return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(values)));
164 public final @Nullable OutboundSpec getCommandSpec(Configuration configuration, KNXTypeMapper typeHelper,
165 Type command) throws KNXFormatException {
166 logger.trace("getCommandSpec testing Keys '{}' for command '{}'", getAllGAKeys(), command);
167 for (String key : getAllGAKeys()) {
168 ChannelConfiguration config = parse((String) configuration.get(key));
169 if (config != null) {
170 String dpt = config.getDPT();
172 dpt = getDefaultDPT(key);
174 Class<? extends Type> expectedTypeClass = typeHelper.toTypeClass(dpt);
175 if (expectedTypeClass != null) {
176 if (expectedTypeClass.isInstance(command)
177 || ((expectedTypeClass == DecimalType.class) && (command instanceof QuantityType))) {
179 "getCommandSpec key '{}' uses expectedTypeClass '{}' which isInstance for command '{}' and dpt '{}'",
180 key, expectedTypeClass, command, dpt);
181 return new WriteSpecImpl(config, dpt, command);
186 logger.trace("getCommandSpec no Spec found!");
190 public final List<InboundSpec> getReadSpec(Configuration configuration) throws KNXFormatException {
191 return getAllGAKeys().stream()
192 .map(key -> new ReadRequestSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key)))
193 .filter(spec -> !spec.getGroupAddresses().isEmpty()).collect(toList());
196 public final @Nullable InboundSpec getListenSpec(Configuration configuration, GroupAddress groupAddress) {
197 Optional<ListenSpecImpl> result = getAllGAKeys().stream()
198 .map(key -> new ListenSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key)))
199 .filter(spec -> !spec.getGroupAddresses().isEmpty())
200 .filter(spec -> spec.getGroupAddresses().contains(groupAddress)).findFirst();
201 return result.isPresent() ? result.get() : null;
204 protected abstract String getDefaultDPT(String gaConfigKey);
206 public final @Nullable OutboundSpec getResponseSpec(Configuration configuration, GroupAddress groupAddress,
207 Type type) throws KNXFormatException {
208 Optional<ReadResponseSpecImpl> result = getAllGAKeys().stream()
209 .map(key -> new ReadResponseSpecImpl(parse((String) configuration.get(key)), getDefaultDPT(key), type))
210 .filter(spec -> groupAddress.equals(spec.getGroupAddress())).findFirst();
211 return result.isPresent() ? result.get() : null;
215 public String toString() {
216 return channelTypeIDs.toString();