forked from openhab/openhab-addons
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[mqtt.homeassistant] Add support for Event component (openhab#17599)
Signed-off-by: Cody Cutrer <cody@cutrer.us>
- Loading branch information
Showing
7 changed files
with
226 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
...istant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Event.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
/** | ||
* 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.mqtt.homeassistant.internal.component; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import org.eclipse.jdt.annotation.NonNullByDefault; | ||
import org.eclipse.jdt.annotation.Nullable; | ||
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener; | ||
import org.openhab.binding.mqtt.generic.values.TextValue; | ||
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType; | ||
import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantChannelTransformation; | ||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration; | ||
import org.openhab.core.thing.ChannelUID; | ||
import org.openhab.core.types.Command; | ||
import org.openhab.core.types.State; | ||
|
||
import com.google.gson.annotations.SerializedName; | ||
|
||
/** | ||
* A MQTT Event, following the https://www.home-assistant.io/integrations/event.mqttspecification. | ||
* | ||
* @author Cody Cutrer - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class Event extends AbstractComponent<Event.ChannelConfiguration> implements ChannelStateUpdateListener { | ||
public static final String EVENT_TYPE_CHANNEL_ID = "event-type"; | ||
public static final String JSON_ATTRIBUTES_CHANNEL_ID = "json-attributes"; | ||
private static final String EVENT_TYPE_TRANFORMATION = "{{ value_json.event_type }}"; | ||
|
||
/** | ||
* Configuration class for MQTT component | ||
*/ | ||
public static class ChannelConfiguration extends AbstractChannelConfiguration { | ||
ChannelConfiguration() { | ||
super("MQTT Event"); | ||
} | ||
|
||
@SerializedName("state_topic") | ||
protected String stateTopic = ""; | ||
|
||
@SerializedName("event_types") | ||
protected List<String> eventTypes = new ArrayList(); | ||
|
||
@SerializedName("json_attributes_topic") | ||
protected @Nullable String jsonAttributesTopic; | ||
|
||
@SerializedName("json_attributes_template") | ||
protected @Nullable String jsonAttributesTemplate; | ||
} | ||
|
||
private final HomeAssistantChannelTransformation transformation; | ||
|
||
public Event(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) { | ||
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels); | ||
|
||
transformation = new HomeAssistantChannelTransformation(getJinjava(), this, ""); | ||
|
||
buildChannel(EVENT_TYPE_CHANNEL_ID, ComponentChannelType.TRIGGER, new TextValue(), getName(), this) | ||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate()).trigger(true) | ||
.build(); | ||
|
||
if (channelConfiguration.jsonAttributesTopic != null) { | ||
// It's unclear from the documentation if the JSON attributes value is expected | ||
// to be the same as the main topic, and thus would always have an event_type | ||
// attribute (and thus could possibly be shared with multiple components). | ||
// If that were the case, we would need to intercept events, and check that they | ||
// had an event_type that is in channelConfiguration.eventTypes. If/when that | ||
// becomes an issue, change `channelStateUpdateListener` to `this`, and handle | ||
// the filtering below. | ||
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.TRIGGER, new TextValue(), getName(), | ||
componentConfiguration.getUpdateListener()) | ||
.stateTopic(channelConfiguration.jsonAttributesTopic, channelConfiguration.jsonAttributesTemplate) | ||
.trigger(true).build(); | ||
} | ||
|
||
finalizeChannels(); | ||
} | ||
|
||
@Override | ||
public void triggerChannel(ChannelUID channel, String event) { | ||
String eventType = transformation.apply(EVENT_TYPE_TRANFORMATION, event).orElse(null); | ||
if (eventType == null) { | ||
// Warning logged from inside the transformation | ||
return; | ||
} | ||
// The TextValue allows anything, because it receives the full JSON, and | ||
// we don't check the actual event_type against valid event_types until here | ||
if (!channelConfiguration.eventTypes.contains(eventType)) { | ||
return; | ||
} | ||
|
||
componentConfiguration.getUpdateListener().triggerChannel(channel, eventType); | ||
} | ||
|
||
@Override | ||
public void updateChannelState(ChannelUID channel, State state) { | ||
// N/A (only trigger channels) | ||
} | ||
|
||
@Override | ||
public void postChannelCommand(ChannelUID channel, Command command) { | ||
// N/A (only trigger channels) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
...t/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/EventTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/** | ||
* 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.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 Event} | ||
* | ||
* @author Cody Cutrer - Initial contribution | ||
*/ | ||
@NonNullByDefault | ||
public class EventTests extends AbstractComponentTests { | ||
public static final String CONFIG_TOPIC = "event/doorbell/action"; | ||
|
||
@SuppressWarnings("null") | ||
@Test | ||
public void test() throws InterruptedException { | ||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """ | ||
{ | ||
"event_types": [ | ||
"press", | ||
"release" | ||
], | ||
"state_topic": "zigbee2mqtt/doorbell/action" | ||
} | ||
"""); | ||
|
||
assertThat(component.channels.size(), is(1)); | ||
assertThat(component.getName(), is("MQTT Event")); | ||
|
||
assertChannel(component, "event-type", "zigbee2mqtt/doorbell/action", "", "MQTT Event", TextValue.class); | ||
|
||
publishMessage("zigbee2mqtt/doorbell/action", "{ \"event_type\": \"press\" }"); | ||
assertTriggered(component, "event-type", "press"); | ||
|
||
publishMessage("zigbee2mqtt/doorbell/action", "{ \"event_type\": \"release\" }"); | ||
assertTriggered(component, "event-type", "release"); | ||
|
||
publishMessage("zigbee2mqtt/doorbell/action", "{ \"event_type\": \"else\" }"); | ||
assertNotTriggered(component, "event-type", "else"); | ||
} | ||
|
||
@SuppressWarnings("null") | ||
@Test | ||
public void testJsonAttributes() throws InterruptedException { | ||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """ | ||
{ | ||
"event_types": [ | ||
"press", | ||
"release" | ||
], | ||
"state_topic": "zigbee2mqtt/doorbell/action", | ||
"json_attributes_topic": "zigbee2mqtt/doorbell/action" | ||
} | ||
"""); | ||
|
||
assertThat(component.channels.size(), is(2)); | ||
assertThat(component.getName(), is("MQTT Event")); | ||
|
||
assertChannel(component, "event-type", "zigbee2mqtt/doorbell/action", "", "MQTT Event", TextValue.class); | ||
assertChannel(component, "json-attributes", "zigbee2mqtt/doorbell/action", "", "MQTT Event", TextValue.class); | ||
|
||
publishMessage("zigbee2mqtt/doorbell/action", "{ \"event_type\": \"press\" }"); | ||
assertTriggered(component, "json-attributes", "{ \"event_type\": \"press\" }"); | ||
} | ||
|
||
@Override | ||
protected Set<String> getConfigTopics() { | ||
return Set.of(CONFIG_TOPIC); | ||
} | ||
} |