diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java index f502f45fd..0e2c16e57 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/ComponentFactory.java @@ -65,6 +65,8 @@ public class ComponentFactory { return new Fan(componentConfiguration); case "climate": return new Climate(componentConfiguration); + case "device_automation": + return new DeviceTrigger(componentConfiguration); case "light": return Light.create(componentConfiguration); case "lock": diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java new file mode 100644 index 000000000..438191198 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTrigger.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2023 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.mqtt.homeassistant.internal.component; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.mqtt.generic.values.TextValue; +import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; +import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException; + +import com.google.gson.annotations.SerializedName; + +/** + * A MQTT Device Trigger, following the https://www.home-assistant.io/integrations/device_trigger.mqtt/ specification. + * + * @author Cody Cutrer - Initial contribution + */ +@NonNullByDefault +public class DeviceTrigger extends AbstractComponent { + /** + * Configuration class for MQTT component + */ + static class ChannelConfiguration extends AbstractChannelConfiguration { + ChannelConfiguration() { + super("MQTT Device Trigger"); + } + + @SerializedName("automation_type") + protected String automationType = "trigger"; + protected String topic = ""; + protected String type = ""; + protected String subtype = ""; + + protected @Nullable String payload; + } + + public DeviceTrigger(ComponentFactory.ComponentConfiguration componentConfiguration) { + super(componentConfiguration, ChannelConfiguration.class); + + if (!channelConfiguration.automationType.equals("trigger")) { + throw new ConfigurationException("Component:DeviceTrigger must have automation_type 'trigger'"); + } + if (channelConfiguration.type.isBlank()) { + throw new ConfigurationException("Component:DeviceTrigger must have a type"); + } + if (channelConfiguration.subtype.isBlank()) { + throw new ConfigurationException("Component:DeviceTrigger must have a subtype"); + } + + TextValue value; + String payload = channelConfiguration.payload; + if (payload != null) { + value = new TextValue(new String[] { payload }); + } else { + value = new TextValue(); + } + + buildChannel(channelConfiguration.type, value, channelConfiguration.getName(), + componentConfiguration.getUpdateListener()) + .stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true) + .build(); + } +} diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java index 533c98854..c14dee7af 100644 --- a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/AbstractComponentTests.java @@ -176,6 +176,20 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests assertThat(component.getChannel(channelId).getState().getCache().getChannelState(), is(state)); } + protected void spyOnChannelUpdates(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component, + String channelId) { + // It's already thingHandler, but not the spy version + component.getChannel(channelId).getState().setChannelStateUpdateListener(thingHandler); + } + + /** + * Assert a channel triggers + */ + protected void assertTriggered(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component, + String channelId, String trigger) { + verify(thingHandler).triggerChannel(eq(component.getChannel(channelId).getChannelUID()), eq(trigger)); + } + /** * Assert that given payload was published exact-once on given topic. * diff --git a/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTriggerTests.java b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTriggerTests.java new file mode 100644 index 000000000..4c49cde86 --- /dev/null +++ b/bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/DeviceTriggerTests.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2023 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.mqtt.homeassistant.internal.component; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.mqtt.generic.values.TextValue; + +/** + * Tests for {@link DeviceTrigger} + * + * @author Cody Cutrer - Initial contribution + */ +@NonNullByDefault +public class DeviceTriggerTests extends AbstractComponentTests { + public static final String CONFIG_TOPIC = "device_automation/0x8cf681fffe2fd2a6"; + + @SuppressWarnings("null") + @Test + public void test() throws InterruptedException { + var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """ + { + "automation_type": "trigger", + "device": { + "configuration_url": "#/device/0x8cf681fffe2fd2a6/info", + "identifiers": [ + "zigbee2mqtt_0x8cf681fffe2fd2a6" + ], + "manufacturer": "IKEA", + "model": "TRADFRI shortcut button (E1812)", + "name": "Charge Now Button", + "sw_version": "2.3.015" + }, + "payload": "on", + "subtype": "on", + "topic": "zigbee2mqtt/Charge Now Button/action", + "type": "action" + } + """); + + assertThat(component.channels.size(), is(1)); + assertThat(component.getName(), is("MQTT Device Trigger")); + + assertChannel(component, "action", "zigbee2mqtt/Charge Now Button/action", "", "MQTT Device Trigger", + TextValue.class); + + spyOnChannelUpdates(component, "action"); + publishMessage("zigbee2mqtt/Charge Now Button/action", "on"); + assertTriggered(component, "action", "on"); + } + + @Override + protected Set getConfigTopics() { + return Set.of(CONFIG_TOPIC); + } +}