2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.knx.internal.channel;
15 import static java.util.stream.Collectors.toList;
16 import static org.openhab.binding.knx.internal.KNXBindingConstants.*;
18 import java.util.HashMap;
19 import java.util.HashSet;
20 import java.util.List;
22 import java.util.Objects;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.knx.internal.client.InboundSpec;
28 import org.openhab.binding.knx.internal.client.OutboundSpec;
29 import org.openhab.binding.knx.internal.dpt.DPTUtil;
30 import org.openhab.core.config.core.Configuration;
31 import org.openhab.core.thing.Channel;
32 import org.openhab.core.thing.ChannelUID;
33 import org.openhab.core.types.State;
34 import org.openhab.core.types.Type;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 import tuwien.auto.calimero.GroupAddress;
41 * Meta-data abstraction for the KNX channel configurations.
43 * @author Simon Kaufmann - initial contribution and API
44 * @author Jan N. Klug - refactored from type definition to channel instance
48 public abstract class KNXChannel {
49 private final Logger logger = LoggerFactory.getLogger(KNXChannel.class);
50 private final Set<String> gaKeys;
52 private final Map<String, GroupAddressConfiguration> groupAddressConfigurations = new HashMap<>();
53 private final Set<GroupAddress> listenAddresses = new HashSet<>();
54 private final Set<GroupAddress> writeAddresses = new HashSet<>();
55 private final String channelType;
56 private final ChannelUID channelUID;
57 private final boolean isControl;
58 private final Class<? extends Type> preferredType;
60 KNXChannel(List<Class<? extends Type>> acceptedTypes, Channel channel) {
61 this(Set.of(GA), acceptedTypes, channel);
64 KNXChannel(Set<String> gaKeys, List<Class<? extends Type>> acceptedTypes, Channel channel) {
66 this.preferredType = acceptedTypes.get(0);
68 // this is safe because we already checked the presence of the ChannelTypeUID before
69 this.channelType = Objects.requireNonNull(channel.getChannelTypeUID()).getId();
70 this.channelUID = channel.getUID();
71 this.isControl = CONTROL_CHANNEL_TYPES.contains(channelType);
73 // build map of ChannelConfigurations and GA lists
74 Configuration configuration = channel.getConfiguration();
75 gaKeys.forEach(key -> {
76 GroupAddressConfiguration groupAddressConfiguration = GroupAddressConfiguration
77 .parse(configuration.get(key));
78 if (groupAddressConfiguration != null) {
79 // check DPT configuration (if set) is compatible with item
80 String dpt = groupAddressConfiguration.getDPT();
82 Set<Class<? extends Type>> types = DPTUtil.getAllowedTypes(dpt);
83 if (acceptedTypes.stream().noneMatch(types::contains)) {
84 logger.warn("Configured DPT '{}' is incompatible with accepted types '{}' for channel '{}'",
85 dpt, acceptedTypes, channelUID);
88 groupAddressConfigurations.put(key, groupAddressConfiguration);
89 // store address configuration for re-use
90 listenAddresses.addAll(groupAddressConfiguration.getListenGAs());
91 writeAddresses.add(groupAddressConfiguration.getMainGA());
96 public String getChannelType() {
100 public ChannelUID getChannelUID() {
104 public boolean isControl() {
108 public Class<? extends Type> preferredType() {
109 return preferredType;
112 public final Set<GroupAddress> getAllGroupAddresses() {
113 return listenAddresses;
116 public final Set<GroupAddress> getWriteAddresses() {
117 return writeAddresses;
120 public final @Nullable OutboundSpec getCommandSpec(Type command) {
121 logger.trace("getCommandSpec checking keys '{}' for command '{}' ({})", gaKeys, command, command.getClass());
122 for (Map.Entry<String, GroupAddressConfiguration> entry : groupAddressConfigurations.entrySet()) {
123 String dpt = Objects.requireNonNullElse(entry.getValue().getDPT(), getDefaultDPT(entry.getKey()));
124 Set<Class<? extends Type>> expectedTypeClasses = DPTUtil.getAllowedTypes(dpt);
125 // find the first matching type that is assignable from the command
126 for (Class<? extends Type> expectedTypeClass : expectedTypeClasses) {
127 if (expectedTypeClass.equals(command.getClass())) {
128 logger.trace("getCommandSpec command class matches expected type class");
129 return new WriteSpecImpl(entry.getValue(), dpt, command);
130 } else if (command instanceof State state && State.class.isAssignableFrom(expectedTypeClass)) {
131 if (state.as(expectedTypeClass.asSubclass(State.class)) != null) {
132 logger.trace("getCommandSpec command class is a sub-class of the expected type class");
133 Class<? extends State> expectedTypeAsStateClass = expectedTypeClass.asSubclass(State.class);
134 State convertedState = state.as(expectedTypeAsStateClass);
135 if (convertedState != null) {
136 return new WriteSpecImpl(entry.getValue(), dpt, convertedState);
142 logger.trace("getCommandSpec no Spec found!");
146 public final List<InboundSpec> getReadSpec() {
147 return groupAddressConfigurations.entrySet().stream()
148 .map(entry -> new ReadRequestSpecImpl(entry.getValue(), getDefaultDPT(entry.getKey())))
149 .filter(spec -> !spec.getGroupAddresses().isEmpty()).collect(toList());
152 public final @Nullable InboundSpec getListenSpec(GroupAddress groupAddress) {
153 return groupAddressConfigurations.entrySet().stream()
154 .map(entry -> new ListenSpecImpl(entry.getValue(), getDefaultDPT(entry.getKey())))
155 .filter(spec -> spec.getGroupAddresses().contains(groupAddress)).findFirst().orElse(null);
158 public final @Nullable OutboundSpec getResponseSpec(GroupAddress groupAddress, Type value) {
159 return groupAddressConfigurations.entrySet().stream()
160 .map(entry -> new ReadResponseSpecImpl(entry.getValue(), getDefaultDPT(entry.getKey()), value))
161 .filter(spec -> spec.matchesDestination(groupAddress)).findFirst().orElse(null);
164 protected abstract String getDefaultDPT(String gaConfigKey);