[mqtt.homeassistant] Use a single channel for all scenes on a device (#18262)
It accepts either object ID, or scene name (assuming the latter doesn't conflict with the former). Signed-off-by: Cody Cutrer <cody@cutrer.us>pull/17817/merge
parent
a41c6d4b4f
commit
1856aa9b3c
|
@ -43,6 +43,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChanne
|
|||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Availability;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AvailabilityMode;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.Device;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
|
@ -416,4 +417,45 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
|||
private ChannelGroupTypeUID getChannelGroupTypeUID(String prefix) {
|
||||
return new ChannelGroupTypeUID(MqttBindingConstants.BINDING_ID, prefix + "_" + uniqueId);
|
||||
}
|
||||
|
||||
public boolean mergeable(AbstractComponent<?> other) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Configuration mergeChannelConfiguration(ComponentChannel channel, AbstractComponent<C> other) {
|
||||
Configuration currentConfiguration = channel.getChannel().getConfiguration();
|
||||
Configuration newConfiguration = new Configuration();
|
||||
newConfiguration.put("component", currentConfiguration.get("component"));
|
||||
newConfiguration.put("nodeid", currentConfiguration.get("nodeid"));
|
||||
Object objectIdObject = currentConfiguration.get("objectid");
|
||||
if (objectIdObject instanceof String objectIdString) {
|
||||
if (!objectIdString.equals(other.getHaID().objectID)) {
|
||||
newConfiguration.put("objectid", List.of(objectIdString, other.getHaID().objectID));
|
||||
}
|
||||
} else if (objectIdObject instanceof List<?> objectIdList) {
|
||||
newConfiguration.put("objectid", Stream.concat(objectIdList.stream(), Stream.of(other.getHaID().objectID))
|
||||
.sorted().distinct().toList());
|
||||
}
|
||||
Object configObject = currentConfiguration.get("config");
|
||||
if (configObject instanceof String configString) {
|
||||
if (!configString.equals(other.getChannelConfigurationJson())) {
|
||||
newConfiguration.put("config", List.of(configString, other.getChannelConfigurationJson()));
|
||||
}
|
||||
} else if (configObject instanceof List<?> configList) {
|
||||
newConfiguration.put("config",
|
||||
Stream.concat(configList.stream(), Stream.of(other.getChannelConfigurationJson())).sorted()
|
||||
.distinct().toList());
|
||||
}
|
||||
return newConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take another component of the same type, and merge it so that only one (set of)
|
||||
* channel(s) exist on the Thing.
|
||||
*
|
||||
* @return if the component was stopped, and thus needs restarted
|
||||
*/
|
||||
public boolean merge(AbstractComponent<?> other) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
*/
|
||||
package org.openhab.binding.mqtt.homeassistant.internal.component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
@ -91,44 +91,40 @@ public class DeviceTrigger extends AbstractComponent<DeviceTrigger.ChannelConfig
|
|||
.stateTopic(channelConfiguration.topic, channelConfiguration.getValueTemplate()).trigger(true).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mergeable(AbstractComponent<?> other) {
|
||||
if (other instanceof DeviceTrigger newTrigger
|
||||
&& newTrigger.getChannelConfiguration().getSubtype().equals(getChannelConfiguration().getSubtype())
|
||||
&& newTrigger.getChannelConfiguration().getTopic().equals(getChannelConfiguration().getTopic())
|
||||
&& getHaID().nodeID.equals(newTrigger.getHaID().nodeID)) {
|
||||
String newTriggerValueTemplate = newTrigger.getChannelConfiguration().getValueTemplate();
|
||||
String oldTriggerValueTemplate = getChannelConfiguration().getValueTemplate();
|
||||
if ((newTriggerValueTemplate == null && oldTriggerValueTemplate == null)
|
||||
|| (newTriggerValueTemplate != null & oldTriggerValueTemplate != null
|
||||
&& newTriggerValueTemplate.equals(oldTriggerValueTemplate))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take another DeviceTrigger (presumably whose subtype, topic, and value template match),
|
||||
* and adjust this component's channel to accept the payload that trigger allows.
|
||||
*
|
||||
* @return if the component was stopped, and thus needs restarted
|
||||
*/
|
||||
@Override
|
||||
public boolean merge(AbstractComponent<?> other) {
|
||||
DeviceTrigger newTrigger = (DeviceTrigger) other;
|
||||
ComponentChannel channel = Objects.requireNonNull(channels.get(componentId));
|
||||
Configuration newConfiguration = mergeChannelConfiguration(channel, newTrigger);
|
||||
|
||||
public boolean merge(DeviceTrigger other) {
|
||||
ComponentChannel channel = channels.get(componentId);
|
||||
TextValue value = (TextValue) channel.getState().getCache();
|
||||
Set<String> payloads = value.getStates();
|
||||
// Append objectid/config to channel configuration
|
||||
Configuration currentConfiguration = channel.getChannel().getConfiguration();
|
||||
Configuration newConfiguration = new Configuration();
|
||||
newConfiguration.put("component", currentConfiguration.get("component"));
|
||||
newConfiguration.put("nodeid", currentConfiguration.get("nodeid"));
|
||||
Object objectIdObject = currentConfiguration.get("objectid");
|
||||
if (objectIdObject instanceof String objectIdString) {
|
||||
if (!objectIdString.equals(other.getHaID().objectID)) {
|
||||
newConfiguration.put("objectid", List.of(objectIdString, other.getHaID().objectID));
|
||||
}
|
||||
} else if (objectIdObject instanceof List<?> objectIdList) {
|
||||
newConfiguration.put("objectid", Stream.concat(objectIdList.stream(), Stream.of(other.getHaID().objectID))
|
||||
.sorted().distinct().toList());
|
||||
}
|
||||
Object configObject = currentConfiguration.get("config");
|
||||
if (configObject instanceof String configString) {
|
||||
if (!configString.equals(other.getChannelConfigurationJson())) {
|
||||
newConfiguration.put("config", List.of(configString, other.getChannelConfigurationJson()));
|
||||
}
|
||||
} else if (configObject instanceof List<?> configList) {
|
||||
newConfiguration.put("config",
|
||||
Stream.concat(configList.stream(), Stream.of(other.getChannelConfigurationJson())).sorted()
|
||||
.distinct().toList());
|
||||
}
|
||||
|
||||
// Append payload to allowed values
|
||||
String otherPayload = other.getChannelConfiguration().payload;
|
||||
String otherPayload = newTrigger.getChannelConfiguration().payload;
|
||||
if (payloads == null || otherPayload == null) {
|
||||
// Need to accept anything
|
||||
value = new TextValue();
|
||||
|
|
|
@ -12,12 +12,24 @@
|
|||
*/
|
||||
package org.openhab.binding.mqtt.homeassistant.internal.component;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mqtt.generic.ChannelState;
|
||||
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.CommandDescriptionBuilder;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
|
@ -30,6 +42,29 @@ import com.google.gson.annotations.SerializedName;
|
|||
public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
|
||||
public static final String SCENE_CHANNEL_ID = "scene";
|
||||
|
||||
// A command that has already been processed and routed to the correct Value,
|
||||
// and should be immediately published. This will be the payloadOn value from
|
||||
// the configuration
|
||||
private static class SceneCommand extends StringType {
|
||||
SceneCommand(String value) {
|
||||
super(value);
|
||||
}
|
||||
}
|
||||
|
||||
// A value that can provide a proper CommandDescription with values and labels
|
||||
class SceneValue extends TextValue {
|
||||
SceneValue() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandDescriptionBuilder createCommandDescription() {
|
||||
CommandDescriptionBuilder builder = super.createCommandDescription();
|
||||
objectIdToScene.forEach((k, v) -> builder.withCommandOption(new CommandOption(k, v.getName())));
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration class for MQTT component
|
||||
*/
|
||||
|
@ -39,23 +74,106 @@ public class Scene extends AbstractComponent<Scene.ChannelConfiguration> {
|
|||
}
|
||||
|
||||
@SerializedName("command_topic")
|
||||
protected @Nullable String commandTopic;
|
||||
protected String commandTopic = "";
|
||||
|
||||
@SerializedName("payload_on")
|
||||
protected String payloadOn = "ON";
|
||||
}
|
||||
|
||||
// Keeps track of discrete command topics, and one SceneValue that uses that topic
|
||||
private final Map<String, ChannelState> topicsToChannelStates = new HashMap<>();
|
||||
private final Map<String, ChannelConfiguration> objectIdToScene = new TreeMap<>();
|
||||
private final Map<String, ChannelConfiguration> labelToScene = new HashMap<>();
|
||||
|
||||
private final SceneValue value = new SceneValue();
|
||||
private ComponentChannel channel;
|
||||
|
||||
public Scene(ComponentFactory.ComponentConfiguration componentConfiguration) {
|
||||
super(componentConfiguration, ChannelConfiguration.class);
|
||||
|
||||
TextValue value = new TextValue(new String[] { channelConfiguration.payloadOn });
|
||||
if (channelConfiguration.commandTopic.isEmpty()) {
|
||||
throw new ConfigurationException("command_topic is required");
|
||||
}
|
||||
|
||||
buildChannel(SCENE_CHANNEL_ID, ComponentChannelType.STRING, value, getName(),
|
||||
// Name the channel with a constant, not the component ID
|
||||
// So that we only end up with a single channel for all scenes
|
||||
componentId = SCENE_CHANNEL_ID;
|
||||
groupId = null;
|
||||
|
||||
channel = buildChannel(SCENE_CHANNEL_ID, ComponentChannelType.STRING, value, getName(),
|
||||
componentConfiguration.getUpdateListener())
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||
.commandFilter(this::handleCommand).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||
topicsToChannelStates.put(channelConfiguration.commandTopic, channel.getState());
|
||||
addScene(this);
|
||||
}
|
||||
|
||||
finalizeChannels();
|
||||
ComponentChannel getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
private void addScene(Scene scene) {
|
||||
ChannelConfiguration channelConfiguration = scene.getChannelConfiguration();
|
||||
objectIdToScene.put(scene.getHaID().objectID, channelConfiguration);
|
||||
labelToScene.put(channelConfiguration.getName(), channelConfiguration);
|
||||
|
||||
if (!topicsToChannelStates.containsKey(channelConfiguration.commandTopic)) {
|
||||
hiddenChannels.add(scene.getChannel());
|
||||
topicsToChannelStates.put(channelConfiguration.commandTopic, scene.getChannel().getState());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleCommand(Command command) {
|
||||
// This command has already been processed by the rest of this method,
|
||||
// so just return immediately.
|
||||
if (command instanceof SceneCommand) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String valueStr = command.toString();
|
||||
ChannelConfiguration sceneConfig = objectIdToScene.get(valueStr);
|
||||
if (sceneConfig == null) {
|
||||
sceneConfig = labelToScene.get(command.toString());
|
||||
}
|
||||
if (sceneConfig == null) {
|
||||
throw new IllegalArgumentException("Value " + valueStr + " not within range");
|
||||
}
|
||||
|
||||
ChannelState state = Objects.requireNonNull(topicsToChannelStates.get(sceneConfig.commandTopic));
|
||||
// This will end up calling this same method, so be sure no further processing is done
|
||||
state.publishValue(new SceneCommand(sceneConfig.payloadOn));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Scene";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mergeable(AbstractComponent<?> other) {
|
||||
return other instanceof Scene;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean merge(AbstractComponent<?> other) {
|
||||
Scene newScene = (Scene) other;
|
||||
Configuration newConfiguration = mergeChannelConfiguration(channel, newScene);
|
||||
|
||||
addScene(newScene);
|
||||
|
||||
// Recreate the channel so that the configuration will have all the scenes
|
||||
stop();
|
||||
channel = buildChannel(SCENE_CHANNEL_ID, ComponentChannelType.STRING, value, "Scene",
|
||||
componentConfiguration.getUpdateListener())
|
||||
.withConfiguration(newConfiguration)
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.commandFilter(this::handleCommand).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||
// New ChannelState created; need to make sure we're referencing the correct one
|
||||
topicsToChannelStates.put(channelConfiguration.commandTopic, channel.getState());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public abstract class AbstractChannelConfiguration {
|
|||
public static final char PARENT_TOPIC_PLACEHOLDER = '~';
|
||||
private static final String DEFAULT_THING_NAME = "Home Assistant Device";
|
||||
|
||||
protected @Nullable String name;
|
||||
protected String name;
|
||||
|
||||
protected String icon = "";
|
||||
protected int qos; // defaults to 0 according to HA specification
|
||||
|
@ -136,7 +136,7 @@ public abstract class AbstractChannelConfiguration {
|
|||
return properties;
|
||||
}
|
||||
|
||||
public @Nullable String getName() {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.openhab.binding.mqtt.homeassistant.internal.HandlerConfiguration;
|
|||
import org.openhab.binding.mqtt.homeassistant.internal.HomeAssistantChannelLinkageChecker;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.component.AbstractComponent;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.component.ComponentFactory;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.component.DeviceTrigger;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.component.Update;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.config.ChannelConfigurationTypeAdapterFactory;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
|
||||
|
@ -482,33 +481,19 @@ public class HomeAssistantThingHandler extends AbstractMQTTThingHandler
|
|||
private boolean addComponent(AbstractComponent<?> component) {
|
||||
AbstractComponent<?> existing = haComponents.get(component.getComponentId());
|
||||
if (existing != null) {
|
||||
// DeviceTriggers that are for the same subtype, topic, and value template
|
||||
// can be coalesced together
|
||||
if (component instanceof DeviceTrigger newTrigger && existing instanceof DeviceTrigger oldTrigger
|
||||
&& newTrigger.getChannelConfiguration().getSubtype()
|
||||
.equals(oldTrigger.getChannelConfiguration().getSubtype())
|
||||
&& newTrigger.getChannelConfiguration().getTopic()
|
||||
.equals(oldTrigger.getChannelConfiguration().getTopic())
|
||||
&& oldTrigger.getHaID().nodeID.equals(newTrigger.getHaID().nodeID)) {
|
||||
String newTriggerValueTemplate = newTrigger.getChannelConfiguration().getValueTemplate();
|
||||
String oldTriggerValueTemplate = oldTrigger.getChannelConfiguration().getValueTemplate();
|
||||
if ((newTriggerValueTemplate == null && oldTriggerValueTemplate == null)
|
||||
|| (newTriggerValueTemplate != null & oldTriggerValueTemplate != null
|
||||
&& newTriggerValueTemplate.equals(oldTriggerValueTemplate))) {
|
||||
// Adjust the set of valid values
|
||||
MqttBrokerConnection connection = this.connection;
|
||||
|
||||
if (oldTrigger.merge(newTrigger) && connection != null) {
|
||||
// Make sure to re-start if this did something, and it was stopped
|
||||
oldTrigger.start(connection, scheduler, 0).exceptionally(e -> {
|
||||
logger.warn("Failed to start component {}", oldTrigger.getHaID(), e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
haComponentsByUniqueId.put(component.getUniqueId(), component);
|
||||
haComponentsByHaId.put(component.getHaID(), component);
|
||||
return false;
|
||||
// Check for components that merge together
|
||||
if (component.mergeable(existing)) {
|
||||
MqttBrokerConnection connection = this.connection;
|
||||
if (existing.merge(component) && connection != null) {
|
||||
// Make sure to re-start if this did something, and it was stopped
|
||||
existing.start(connection, scheduler, 0).exceptionally(e -> {
|
||||
logger.warn("Failed to start component {}", existing.getHaID(), e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
haComponentsByUniqueId.put(component.getUniqueId(), component);
|
||||
haComponentsByHaId.put(component.getHaID(), component);
|
||||
return false;
|
||||
}
|
||||
|
||||
// rename the conflict
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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 static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mqtt.generic.values.Value;
|
||||
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
|
||||
/**
|
||||
* Tests for {@link DeviceTrigger}
|
||||
*
|
||||
* @author Cody Cutrer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SceneTests extends AbstractComponentTests {
|
||||
public static final String CONFIG_TOPIC_1 = "scene/12345_14/scene_1";
|
||||
public static final String CONFIG_TOPIC_2 = "scene/12345_14/scene_2";
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void test() throws InterruptedException {
|
||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC_1), """
|
||||
{
|
||||
"command_topic": "zigbee2mqtt/Theater Room Lights/set",
|
||||
"name": "House",
|
||||
"object_id": "theater_room_lights_1_house",
|
||||
"payload_on": "{ \\"scene_recall\\": 1 }",
|
||||
"unique_id": "14_scene_1_zigbee2mqtt"
|
||||
}
|
||||
""");
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("Scene"));
|
||||
|
||||
assertChannel(component, Scene.SCENE_CHANNEL_ID, "", "zigbee2mqtt/Theater Room Lights/set", "Scene",
|
||||
Scene.SceneValue.class);
|
||||
linkAllChannels(component);
|
||||
|
||||
component.getChannel(Scene.SCENE_CHANNEL_ID).getState().publishValue(new StringType("scene_1"));
|
||||
assertPublished("zigbee2mqtt/Theater Room Lights/set", "{ \"scene_recall\": 1 }");
|
||||
|
||||
component.getChannel(Scene.SCENE_CHANNEL_ID).getState().publishValue(new StringType("House"));
|
||||
assertPublished("zigbee2mqtt/Theater Room Lights/set", "{ \"scene_recall\": 1 }", 2);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testMerge() throws InterruptedException {
|
||||
var component1 = (Scene) discoverComponent(configTopicToMqtt(CONFIG_TOPIC_1), """
|
||||
{
|
||||
"command_topic": "zigbee2mqtt/Theater Room Lights/set",
|
||||
"name": "House",
|
||||
"object_id": "theater_room_lights_1_house",
|
||||
"payload_on": "{ \\"scene_recall\\": 1 }",
|
||||
"unique_id": "14_scene_1_zigbee2mqtt"
|
||||
}
|
||||
""");
|
||||
discoverComponent(configTopicToMqtt(CONFIG_TOPIC_2), """
|
||||
{
|
||||
"command_topic": "zigbee2mqtt/Theater Room Lights/set",
|
||||
"name": "Menu",
|
||||
"object_id": "theater_room_lights_2_menu",
|
||||
"payload_on": "{ \\"scene_recall\\": 2 }",
|
||||
"unique_id": "14_scene_2_zigbee2mqtt"
|
||||
}
|
||||
""");
|
||||
|
||||
assertThat(component1.channels.size(), is(1));
|
||||
|
||||
ComponentChannel channel = Objects.requireNonNull(component1.getChannel(Scene.SCENE_CHANNEL_ID));
|
||||
Value value = channel.getState().getCache();
|
||||
List<CommandOption> options = value.createCommandDescription().build().getCommandOptions();
|
||||
assertThat(options.size(), is(2));
|
||||
assertThat(options.get(0).getCommand(), is("scene_1"));
|
||||
assertThat(options.get(1).getCommand(), is("scene_2"));
|
||||
Configuration channelConfig = channel.getChannel().getConfiguration();
|
||||
Object config = channelConfig.get("config");
|
||||
assertNotNull(config);
|
||||
assertThat(config.getClass(), is(ArrayList.class));
|
||||
List<?> configList = (List<?>) config;
|
||||
assertThat(configList.size(), is(2));
|
||||
|
||||
linkAllChannels(component1);
|
||||
|
||||
component1.getChannel(Scene.SCENE_CHANNEL_ID).getState().publishValue(new StringType("House"));
|
||||
assertPublished("zigbee2mqtt/Theater Room Lights/set", "{ \"scene_recall\": 1 }");
|
||||
|
||||
component1.getChannel(Scene.SCENE_CHANNEL_ID).getState().publishValue(new StringType("scene_2"));
|
||||
assertPublished("zigbee2mqtt/Theater Room Lights/set", "{ \"scene_recall\": 2 }");
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testMultipleTopics() throws InterruptedException {
|
||||
var component1 = (Scene) discoverComponent(configTopicToMqtt(CONFIG_TOPIC_1), """
|
||||
{
|
||||
"command_topic": "zigbee2mqtt/Theater Room Lights/set",
|
||||
"name": "House",
|
||||
"object_id": "theater_room_lights_1_house",
|
||||
"payload_on": "{ \\"scene_recall\\": 1 }",
|
||||
"unique_id": "14_scene_1_zigbee2mqtt"
|
||||
}
|
||||
""");
|
||||
discoverComponent(configTopicToMqtt(CONFIG_TOPIC_2), """
|
||||
{
|
||||
"command_topic": "zigbee2mqtt/Theater Room Lights 2/set",
|
||||
"name": "Menu",
|
||||
"object_id": "theater_room_lights_2_menu",
|
||||
"payload_on": "{ \\"scene_recall\\": 2 }",
|
||||
"unique_id": "14_scene_2_zigbee2mqtt"
|
||||
}
|
||||
""");
|
||||
|
||||
assertThat(component1.channels.size(), is(1));
|
||||
|
||||
ComponentChannel channel = Objects.requireNonNull(component1.getChannel(Scene.SCENE_CHANNEL_ID));
|
||||
Value value = channel.getState().getCache();
|
||||
List<CommandOption> options = value.createCommandDescription().build().getCommandOptions();
|
||||
assertThat(options.size(), is(2));
|
||||
assertThat(options.get(0).getCommand(), is("scene_1"));
|
||||
assertThat(options.get(1).getCommand(), is("scene_2"));
|
||||
Configuration channelConfig = channel.getChannel().getConfiguration();
|
||||
Object config = channelConfig.get("config");
|
||||
assertNotNull(config);
|
||||
assertThat(config.getClass(), is(ArrayList.class));
|
||||
List<?> configList = (List<?>) config;
|
||||
assertThat(configList.size(), is(2));
|
||||
|
||||
linkAllChannels(component1);
|
||||
|
||||
component1.getChannel(Scene.SCENE_CHANNEL_ID).getState().publishValue(new StringType("House"));
|
||||
assertPublished("zigbee2mqtt/Theater Room Lights/set", "{ \"scene_recall\": 1 }");
|
||||
|
||||
component1.getChannel(Scene.SCENE_CHANNEL_ID).getState().publishValue(new StringType("scene_2"));
|
||||
assertPublished("zigbee2mqtt/Theater Room Lights 2/set", "{ \"scene_recall\": 2 }");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getConfigTopics() {
|
||||
return Set.of(CONFIG_TOPIC_1, CONFIG_TOPIC_2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue