From d5fc6950dcacba4493861c0cd30b86b07b4dbb62 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Sun, 28 Jan 2024 21:44:09 +0100 Subject: [PATCH] [knx] postUpdate for contact-control sends to bus (#16263) contact-control items need to send to the bus like a switch item, to trigger a state update in the external device. * Add a new profile for contact-control items * Add a profile factory and a profile advisor class * Handle postUpdate like a command and send message on KNX bus Fixes #16115. Signed-off-by: Holger Friedrich --- .../knx/internal/KNXBindingConstants.java | 4 + .../profiles/KNXContactControlProfile.java | 100 ++++++++++++++++ .../internal/profiles/KNXProfileAdvisor.java | 5 + .../internal/profiles/KNXProfileFactory.java | 108 ++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java create mode 100644 bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java index 38961a9a0ef..1c88448ad37 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/KNXBindingConstants.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; /** * The {@link KNXBindingConstants} class defines common constants, which are @@ -89,6 +90,9 @@ public class KNXBindingConstants { public static final String CHANNEL_SWITCH = "switch"; public static final String CHANNEL_SWITCH_CONTROL = "switch-control"; + public static final ChannelTypeUID CHANNEL_CONTACT_CONTROL_UID = new ChannelTypeUID(BINDING_ID, + CHANNEL_CONTACT_CONTROL); + public static final Set CONTROL_CHANNEL_TYPES = Set.of( // CHANNEL_COLOR_CONTROL, // CHANNEL_CONTACT_CONTROL, // diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java new file mode 100644 index 00000000000..7685d454882 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXContactControlProfile.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.profiles.ProfileCallback; +import org.openhab.core.thing.profiles.ProfileTypeUID; +import org.openhab.core.thing.profiles.StateProfile; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is the implementation of a specialized profile for KNX contact-control-items. + * + * In contrast to the profile {@code FOLLOW} from {@link org.openhab.core.thing.profiles.SystemProfiles} + * used for other *-control items, it sends to the bus also for contact items. + * + * @author Holger Friedrich - Initial contribution + */ +@NonNullByDefault +public class KNXContactControlProfile implements StateProfile { + + private final Logger logger = LoggerFactory.getLogger(KNXContactControlProfile.class); + private final ProfileCallback callback; + private final ThingRegistry thingRegistry; + + public KNXContactControlProfile(ProfileCallback callback, ThingRegistry thingRegistry) { + this.callback = callback; + this.thingRegistry = thingRegistry; + } + + @Override + public ProfileTypeUID getProfileTypeUID() { + return KNXProfileFactory.UID_CONTACT_CONTROL; + } + + @Override + public void onStateUpdateFromItem(State state) { + ChannelUID linkedChannelUID = callback.getItemChannelLink().getLinkedUID(); + logger.trace("onStateUpdateFromItem({}) to {}", state.toString(), linkedChannelUID); + + if (!(state instanceof Command)) { + logger.debug("The given state {} could not be transformed to a command", state); + return; + } + Command command = (Command) state; + + // this does not have effect for contact items + // callback.handleCommand(command); + // workaround is to call handleCommand of the Thing directly + @Nullable + Thing linkedThing = thingRegistry.get(linkedChannelUID.getThingUID()); + if (linkedThing != null) { + @Nullable + ThingHandler linkedThingHandler = linkedThing.getHandler(); + if (linkedThingHandler != null) { + linkedThingHandler.handleCommand(linkedChannelUID, command); + } else { + logger.warn("Failed to send to {}, no ThingHandler", linkedChannelUID); + } + } else { + logger.warn("Failed to send to {}, no linked Thing", linkedChannelUID); + } + } + + @Override + public void onCommandFromHandler(Command command) { + logger.trace("onCommandFromHandler {}", command.toString()); + callback.sendCommand(command); + } + + @Override + public void onCommandFromItem(Command command) { + logger.trace("onCommandFromItem {}", command.toString()); + // no-op + } + + @Override + public void onStateUpdateFromHandler(State state) { + logger.trace("onStateUpdateFromHandler {}", state.toString()); + // no-op + } +} diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileAdvisor.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileAdvisor.java index fe23188d6b9..73d382d1e2a 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileAdvisor.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileAdvisor.java @@ -50,6 +50,11 @@ public class KNXProfileAdvisor implements ProfileAdvisor { private ProfileTypeUID getSuggestedProfileTypeUID(ChannelTypeUID channelTypeUID) { if (isControl(channelTypeUID)) { + if (CHANNEL_CONTACT_CONTROL.equals(channelTypeUID.getId())) { + // Special handling for contact-control, as contact items do not send to bus: + // contact-control need to send out on postUpdate, as contact-control switches external device + return KNXProfileFactory.UID_CONTACT_CONTROL; + } return SystemProfiles.FOLLOW; } else { return SystemProfiles.DEFAULT; diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java new file mode 100644 index 00000000000..42ff6738428 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/profiles/KNXProfileFactory.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) 2010-2024 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 java.util.Collection; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.knx.internal.KNXBindingConstants; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ThingRegistry; +import org.openhab.core.thing.profiles.Profile; +import org.openhab.core.thing.profiles.ProfileAdvisor; +import org.openhab.core.thing.profiles.ProfileCallback; +import org.openhab.core.thing.profiles.ProfileContext; +import org.openhab.core.thing.profiles.ProfileFactory; +import org.openhab.core.thing.profiles.ProfileType; +import org.openhab.core.thing.profiles.ProfileTypeBuilder; +import org.openhab.core.thing.profiles.ProfileTypeProvider; +import org.openhab.core.thing.profiles.ProfileTypeUID; +import org.openhab.core.thing.profiles.StateProfileType; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * This class defines and provides specialized KNX profiles. + * + * @author Holger Friedrich - Initial contribution + * + */ +@NonNullByDefault +@Component +public class KNXProfileFactory implements ProfileFactory, ProfileAdvisor, ProfileTypeProvider { + + static final ProfileTypeUID UID_CONTACT_CONTROL = new ProfileTypeUID(KNXBindingConstants.BINDING_ID, + "contact-control"); + + private static final StateProfileType CONTACT_CONTROL_TYPE = ProfileTypeBuilder + .newState(UID_CONTACT_CONTROL, "contact-control").withSupportedItemTypes(CoreItemFactory.CONTACT) + .withSupportedChannelTypeUIDs(KNXBindingConstants.CHANNEL_CONTACT_CONTROL_UID).build(); + + private final ThingRegistry thingRegistry; + + @Activate + public KNXProfileFactory(@Reference ThingRegistry thingRegistry) { + this.thingRegistry = thingRegistry; + } + + @Override + public Collection getSupportedProfileTypeUIDs() { + return Stream.of(UID_CONTACT_CONTROL).collect(Collectors.toSet()); + } + + @Override + public Collection getProfileTypes(@Nullable Locale locale) { + return Stream.of(CONTACT_CONTROL_TYPE).collect(Collectors.toSet()); + } + + @Override + public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(Channel channel, @Nullable String itemType) { + return getSuggestedProfileTypeUID(channel.getChannelTypeUID(), itemType); + } + + @Override + public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(ChannelType channelType, @Nullable String itemType) { + return getSuggestedProfileTypeUID(channelType.getUID(), itemType); + } + + private @Nullable ProfileTypeUID getSuggestedProfileTypeUID(@Nullable ChannelTypeUID channelTypeUID, + @Nullable String itemType) { + if (KNXBindingConstants.CHANNEL_CONTACT_CONTROL_UID.equals(channelTypeUID) && itemType != null) { + switch (itemType) { + case CoreItemFactory.CONTACT: + return UID_CONTACT_CONTROL; + default: + return null; + } + } + return null; + } + + @Override + public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback, + ProfileContext profileContext) { + if (UID_CONTACT_CONTROL.equals(profileTypeUID)) { + return new KNXContactControlProfile(callback, thingRegistry); + } else { + return null; + } + } +}