mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-25 14:55:55 +01:00
[mqtt.homeassistant] Implement optimistic components with AutoUpdatePolicy.RECOMMEND (#17520)
Signed-off-by: Cody Cutrer <cody@cutrer.us> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
8d517be36b
commit
f137c07dad
@ -225,6 +225,18 @@ public class ComponentChannel {
|
||||
return this;
|
||||
}
|
||||
|
||||
// If the component explicitly specifies optimistic, or it's missing a state topic
|
||||
// put it in optimistic mode (which, in openHAB parlance, means to auto-update the
|
||||
// item).
|
||||
public Builder inferOptimistic(@Nullable Boolean optimistic) {
|
||||
String localStateTopic = stateTopic;
|
||||
if (optimistic == null && (localStateTopic == null || localStateTopic.isBlank())
|
||||
|| optimistic != null && optimistic == true) {
|
||||
this.autoUpdatePolicy = AutoUpdatePolicy.RECOMMEND;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ComponentChannel build() {
|
||||
return build(true);
|
||||
}
|
||||
|
@ -94,6 +94,8 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
|
||||
super("MQTT HVAC");
|
||||
}
|
||||
|
||||
protected @Nullable Boolean optimistic;
|
||||
|
||||
@SerializedName("action_template")
|
||||
protected @Nullable String actionTemplate;
|
||||
@SerializedName("action_topic")
|
||||
@ -297,7 +299,7 @@ public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
|
||||
.stateTopic(stateTopic, stateTemplate, channelConfiguration.getValueTemplate())
|
||||
.commandTopic(commandTopic, channelConfiguration.isRetain(), channelConfiguration.getQos(),
|
||||
commandTemplate)
|
||||
.commandFilter(commandFilter).build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).commandFilter(commandFilter).build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChanne
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@ -48,6 +49,8 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
|
||||
super("MQTT Cover");
|
||||
}
|
||||
|
||||
protected @Nullable Boolean optimistic;
|
||||
|
||||
@SerializedName("state_topic")
|
||||
protected @Nullable String stateTopic;
|
||||
@SerializedName("command_topic")
|
||||
@ -88,6 +91,12 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
|
||||
public Cover(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
|
||||
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
|
||||
|
||||
boolean optimistic = false;
|
||||
Boolean localOptimistic = channelConfiguration.optimistic;
|
||||
if (localOptimistic != null && localOptimistic == true
|
||||
|| channelConfiguration.stateTopic == null && channelConfiguration.positionTopic == null) {
|
||||
optimistic = true;
|
||||
}
|
||||
String stateTopic = channelConfiguration.stateTopic;
|
||||
|
||||
// State can indicate additional information than just
|
||||
@ -149,7 +158,7 @@ public class Cover extends AbstractComponent<Cover.ChannelConfiguration> {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).build();
|
||||
}).withAutoUpdatePolicy(optimistic ? AutoUpdatePolicy.RECOMMEND : null).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
@ -60,13 +61,14 @@ public class DefaultSchemaLight extends Light {
|
||||
|
||||
@Override
|
||||
protected void buildChannels() {
|
||||
AutoUpdatePolicy autoUpdatePolicy = optimistic ? AutoUpdatePolicy.RECOMMEND : null;
|
||||
ComponentChannel localOnOffChannel;
|
||||
localOnOffChannel = onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, ComponentChannelType.SWITCH, onOffValue,
|
||||
"On/Off State", this)
|
||||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.stateValueTemplate)
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.commandFilter(this::handleRawOnOffCommand).build(false);
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).commandFilter(this::handleRawOnOffCommand).build(false);
|
||||
|
||||
@Nullable
|
||||
ComponentChannel localBrightnessChannel = null;
|
||||
@ -76,7 +78,8 @@ public class DefaultSchemaLight extends Light {
|
||||
.stateTopic(channelConfiguration.brightnessStateTopic, channelConfiguration.brightnessValueTemplate)
|
||||
.commandTopic(channelConfiguration.brightnessCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.withFormat("%.0f").commandFilter(this::handleBrightnessCommand).build(false);
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).withFormat("%.0f")
|
||||
.commandFilter(this::handleBrightnessCommand).build(false);
|
||||
}
|
||||
|
||||
if (channelConfiguration.whiteCommandTopic != null) {
|
||||
@ -84,14 +87,14 @@ public class DefaultSchemaLight extends Light {
|
||||
"Go directly to white of a specific brightness", this)
|
||||
.commandTopic(channelConfiguration.whiteCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.isAdvanced(true).build();
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).isAdvanced(true).build();
|
||||
}
|
||||
|
||||
if (channelConfiguration.colorModeStateTopic != null) {
|
||||
buildChannel(COLOR_MODE_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "Current color mode",
|
||||
this)
|
||||
.stateTopic(channelConfiguration.colorModeStateTopic, channelConfiguration.colorModeValueTemplate)
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
}
|
||||
|
||||
if (channelConfiguration.colorTempStateTopic != null || channelConfiguration.colorTempCommandTopic != null) {
|
||||
@ -99,7 +102,7 @@ public class DefaultSchemaLight extends Light {
|
||||
.stateTopic(channelConfiguration.colorTempStateTopic, channelConfiguration.colorTempValueTemplate)
|
||||
.commandTopic(channelConfiguration.colorTempCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
}
|
||||
|
||||
if (effectValue != null
|
||||
@ -109,7 +112,7 @@ public class DefaultSchemaLight extends Light {
|
||||
.stateTopic(channelConfiguration.effectStateTopic, channelConfiguration.effectValueTemplate)
|
||||
.commandTopic(channelConfiguration.effectCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
}
|
||||
|
||||
boolean hasColorChannel = false;
|
||||
@ -170,7 +173,7 @@ public class DefaultSchemaLight extends Light {
|
||||
}
|
||||
colorChannel = buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this)
|
||||
.commandTopic(DUMMY_TOPIC, channelConfiguration.isRetain(), channelConfiguration.getQos())
|
||||
.commandFilter(this::handleColorCommand).build();
|
||||
.commandFilter(this::handleColorCommand).withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
} else if (localBrightnessChannel != null) {
|
||||
hiddenChannels.add(localOnOffChannel);
|
||||
channels.put(BRIGHTNESS_CHANNEL_ID, localBrightnessChannel);
|
||||
|
@ -57,6 +57,8 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
super("MQTT Fan");
|
||||
}
|
||||
|
||||
protected @Nullable Boolean optimistic;
|
||||
|
||||
@SerializedName("state_topic")
|
||||
protected @Nullable String stateTopic;
|
||||
@SerializedName("command_template")
|
||||
@ -136,6 +138,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
|
||||
.inferOptimistic(channelConfiguration.optimistic)
|
||||
.build(channelConfiguration.percentageCommandTopic == null);
|
||||
|
||||
rawSpeedState = UnDefType.NULL;
|
||||
@ -152,7 +155,8 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
.stateTopic(channelConfiguration.percentageStateTopic, channelConfiguration.percentageValueTemplate)
|
||||
.commandTopic(channelConfiguration.percentageCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.percentageCommandTemplate)
|
||||
.commandFilter(this::handlePercentageCommand).build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).commandFilter(this::handlePercentageCommand)
|
||||
.build();
|
||||
} else {
|
||||
primaryChannel = onOffChannel;
|
||||
speedChannel = null;
|
||||
@ -167,7 +171,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
.stateTopic(channelConfiguration.presetModeStateTopic, channelConfiguration.presetModeValueTemplate)
|
||||
.commandTopic(channelConfiguration.presetModeCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.presetModeCommandTemplate)
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
}
|
||||
|
||||
if (channelConfiguration.oscillationCommandTopic != null) {
|
||||
@ -179,7 +183,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
channelConfiguration.oscillationValueTemplate)
|
||||
.commandTopic(channelConfiguration.oscillationCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.oscillationCommandTemplate)
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
}
|
||||
|
||||
if (channelConfiguration.directionCommandTopic != null) {
|
||||
@ -189,7 +193,7 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
|
||||
.stateTopic(channelConfiguration.directionStateTopic, channelConfiguration.directionValueTemplate)
|
||||
.commandTopic(channelConfiguration.directionCommandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.directionCommandTemplate)
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
}
|
||||
finalizeChannels();
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
@ -79,6 +80,7 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
@Override
|
||||
protected void buildChannels() {
|
||||
boolean hasColorChannel = false;
|
||||
AutoUpdatePolicy autoUpdatePolicy = optimistic ? AutoUpdatePolicy.RECOMMEND : null;
|
||||
List<LightColorMode> supportedColorModes = channelConfiguration.supportedColorModes;
|
||||
if (supportedColorModes != null) {
|
||||
if (LightColorMode.hasColorChannel(supportedColorModes)) {
|
||||
@ -88,13 +90,14 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
if (supportedColorModes.contains(LightColorMode.COLOR_MODE_COLOR_TEMP)) {
|
||||
buildChannel(COLOR_TEMP_CHANNEL_ID, ComponentChannelType.NUMBER, colorTempValue, "Color Temperature",
|
||||
this).commandTopic(DUMMY_TOPIC, true, 1)
|
||||
.commandFilter(command -> handleColorTempCommand(command)).build();
|
||||
.commandFilter(command -> handleColorTempCommand(command))
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
|
||||
if (hasColorChannel) {
|
||||
colorModeValue = new TextValue(
|
||||
supportedColorModes.stream().map(LightColorMode::serializedName).toArray(String[]::new));
|
||||
buildChannel(COLOR_MODE_CHANNEL_ID, ComponentChannelType.STRING, colorModeValue, "Color Mode", this)
|
||||
.isAdvanced(true).build();
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).isAdvanced(true).build();
|
||||
|
||||
}
|
||||
}
|
||||
@ -102,19 +105,23 @@ public class JSONSchemaLight extends AbstractRawSchemaLight {
|
||||
|
||||
if (hasColorChannel) {
|
||||
colorChannel = buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this)
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand)
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
} else if (channelConfiguration.brightness) {
|
||||
brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, ComponentChannelType.DIMMER, brightnessValue,
|
||||
"Brightness", this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
|
||||
"Brightness", this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand)
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
} else {
|
||||
onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, ComponentChannelType.SWITCH, onOffValue, "On/Off State",
|
||||
this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand).build();
|
||||
this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(this::handleCommand)
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
}
|
||||
|
||||
if (effectValue != null) {
|
||||
buildChannel(EFFECT_CHANNEL_ID, ComponentChannelType.STRING, Objects.requireNonNull(effectValue),
|
||||
"Lighting Effect", this).commandTopic(DUMMY_TOPIC, true, 1)
|
||||
.commandFilter(command -> handleEffectCommand(command)).build();
|
||||
.commandFilter(command -> handleEffectCommand(command)).withAutoUpdatePolicy(autoUpdatePolicy)
|
||||
.build();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mqtt.generic.values.NumberValue;
|
||||
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.types.util.UnitUtils;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
@ -73,13 +72,6 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
|
||||
public Number(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
|
||||
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
|
||||
|
||||
boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic
|
||||
: channelConfiguration.stateTopic.isBlank();
|
||||
|
||||
if (optimistic && !channelConfiguration.stateTopic.isBlank()) {
|
||||
throw new ConfigurationException("Component:Number does not support forced optimistic mode");
|
||||
}
|
||||
|
||||
NumberValue value = new NumberValue(channelConfiguration.min, channelConfiguration.max,
|
||||
channelConfiguration.step, UnitUtils.parseUnit(channelConfiguration.unitOfMeasurement));
|
||||
|
||||
@ -88,7 +80,7 @@ public class Number extends AbstractComponent<Number.ChannelConfiguration> {
|
||||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||
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 com.google.gson.annotations.SerializedName;
|
||||
|
||||
@ -58,13 +57,6 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
|
||||
public Select(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
|
||||
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
|
||||
|
||||
boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic
|
||||
: channelConfiguration.stateTopic.isBlank();
|
||||
|
||||
if (optimistic && !channelConfiguration.stateTopic.isBlank()) {
|
||||
throw new ConfigurationException("Component:Select does not support forced optimistic mode");
|
||||
}
|
||||
|
||||
TextValue value = new TextValue(channelConfiguration.options);
|
||||
|
||||
buildChannel(SELECT_CHANNEL_ID, ComponentChannelType.STRING, value, getName(),
|
||||
@ -72,7 +64,7 @@ public class Select extends AbstractComponent<Select.ChannelConfiguration> {
|
||||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.mqtt.generic.values.OnOffValue;
|
||||
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 com.google.gson.annotations.SerializedName;
|
||||
|
||||
@ -63,13 +62,6 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
|
||||
public Switch(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
|
||||
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
|
||||
|
||||
boolean optimistic = channelConfiguration.optimistic != null ? channelConfiguration.optimistic
|
||||
: channelConfiguration.stateTopic.isBlank();
|
||||
|
||||
if (optimistic && !channelConfiguration.stateTopic.isBlank()) {
|
||||
throw new ConfigurationException("Component:Switch does not support forced optimistic mode");
|
||||
}
|
||||
|
||||
OnOffValue value = new OnOffValue(channelConfiguration.stateOn, channelConfiguration.stateOff,
|
||||
channelConfiguration.payloadOn, channelConfiguration.payloadOff);
|
||||
|
||||
@ -78,7 +70,7 @@ public class Switch extends AbstractComponent<Switch.ChannelConfiguration> {
|
||||
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
|
||||
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
|
||||
channelConfiguration.getQos())
|
||||
.build();
|
||||
.inferOptimistic(channelConfiguration.optimistic).build();
|
||||
finalizeChannels();
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
@ -76,6 +77,7 @@ public class TemplateSchemaLight extends AbstractRawSchemaLight {
|
||||
|
||||
@Override
|
||||
protected void buildChannels() {
|
||||
AutoUpdatePolicy autoUpdatePolicy = optimistic ? AutoUpdatePolicy.RECOMMEND : null;
|
||||
if (channelConfiguration.commandOnTemplate == null || channelConfiguration.commandOffTemplate == null) {
|
||||
throw new UnsupportedComponentException("Template schema light component '" + getHaID()
|
||||
+ "' does not define command_on_template or command_off_template!");
|
||||
@ -87,25 +89,28 @@ public class TemplateSchemaLight extends AbstractRawSchemaLight {
|
||||
if (channelConfiguration.redTemplate != null && channelConfiguration.greenTemplate != null
|
||||
&& channelConfiguration.blueTemplate != null) {
|
||||
colorChannel = buildChannel(COLOR_CHANNEL_ID, ComponentChannelType.COLOR, colorValue, "Color", this)
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleCommand(command)).build();
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleCommand(command))
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
} else if (channelConfiguration.brightnessTemplate != null) {
|
||||
brightnessChannel = buildChannel(BRIGHTNESS_CHANNEL_ID, ComponentChannelType.DIMMER, brightnessValue,
|
||||
"Brightness", this).commandTopic(DUMMY_TOPIC, true, 1)
|
||||
.commandFilter(command -> handleCommand(command)).build();
|
||||
.commandFilter(command -> handleCommand(command)).withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
} else {
|
||||
onOffChannel = buildChannel(ON_OFF_CHANNEL_ID, ComponentChannelType.SWITCH, onOffValue, "On/Off State",
|
||||
this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleCommand(command)).build();
|
||||
this).commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleCommand(command))
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
}
|
||||
|
||||
if (channelConfiguration.colorTempTemplate != null) {
|
||||
buildChannel(COLOR_TEMP_CHANNEL_ID, ComponentChannelType.NUMBER, colorTempValue, "Color Temperature", this)
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleColorTempCommand(command))
|
||||
.build();
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
}
|
||||
TextValue localEffectValue = effectValue;
|
||||
if (channelConfiguration.effectTemplate != null && localEffectValue != null) {
|
||||
buildChannel(EFFECT_CHANNEL_ID, ComponentChannelType.STRING, localEffectValue, "Effect", this)
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleEffectCommand(command)).build();
|
||||
.commandTopic(DUMMY_TOPIC, true, 1).commandFilter(command -> handleEffectCommand(command))
|
||||
.withAutoUpdatePolicy(autoUpdatePolicy).build();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
@ -167,6 +168,43 @@ public abstract class AbstractComponentTests extends AbstractHomeAssistantTests
|
||||
assertThat(stateChannel.getState().getCache(), is(instanceOf(valueClass)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert channel topics, label and value class
|
||||
*
|
||||
* @param component component
|
||||
* @param channelId channel
|
||||
* @param stateTopic state topic or empty string
|
||||
* @param commandTopic command topic or empty string
|
||||
* @param label label
|
||||
* @param valueClass value class
|
||||
* @param autoUpdatePolicy Auto Update Policy
|
||||
*/
|
||||
protected static void assertChannel(AbstractComponent<@NonNull ? extends AbstractChannelConfiguration> component,
|
||||
String channelId, String stateTopic, String commandTopic, String label, Class<? extends Value> valueClass,
|
||||
@Nullable AutoUpdatePolicy autoUpdatePolicy) {
|
||||
var stateChannel = Objects.requireNonNull(component.getChannel(channelId));
|
||||
assertChannel(stateChannel, stateTopic, commandTopic, label, valueClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert channel topics, label and value class
|
||||
*
|
||||
* @param stateChannel channel
|
||||
* @param stateTopic state topic or empty string
|
||||
* @param commandTopic command topic or empty string
|
||||
* @param label label
|
||||
* @param valueClass value class
|
||||
* @param autoUpdatePolicy Auto Update Policy
|
||||
*/
|
||||
protected static void assertChannel(ComponentChannel stateChannel, String stateTopic, String commandTopic,
|
||||
String label, Class<? extends Value> valueClass, @Nullable AutoUpdatePolicy autoUpdatePolicy) {
|
||||
assertThat(stateChannel.getChannel().getLabel(), is(label));
|
||||
assertThat(stateChannel.getState().getStateTopic(), is(stateTopic));
|
||||
assertThat(stateChannel.getState().getCommandTopic(), is(commandTopic));
|
||||
assertThat(stateChannel.getState().getCache(), is(instanceOf(valueClass)));
|
||||
assertThat(stateChannel.getChannel().getAutoUpdatePolicy(), is(autoUpdatePolicy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert channel state
|
||||
*
|
||||
|
@ -27,6 +27,7 @@ import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
@ -72,7 +73,7 @@ public class FanTests extends AbstractComponentTests {
|
||||
assertThat(component.getName(), is("fan"));
|
||||
|
||||
assertChannel(component, Fan.SWITCH_CHANNEL_ID, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
|
||||
"On/Off State", OnOffValue.class);
|
||||
"On/Off State", OnOffValue.class, null);
|
||||
|
||||
publishMessage("zigbee2mqtt/fan/state", "ON_");
|
||||
assertState(component, Fan.SWITCH_CHANNEL_ID, OnOffType.ON);
|
||||
@ -89,6 +90,117 @@ public class FanTests extends AbstractComponentTests {
|
||||
assertPublished("zigbee2mqtt/fan/set/state", "ON_");
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInferredOptimistic() throws InterruptedException {
|
||||
// @formatter:off
|
||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||
"""
|
||||
{ \
|
||||
"availability": [ \
|
||||
{ \
|
||||
"topic": "zigbee2mqtt/bridge/state" \
|
||||
} \
|
||||
], \
|
||||
"device": { \
|
||||
"identifiers": [ \
|
||||
"zigbee2mqtt_0x0000000000000000" \
|
||||
], \
|
||||
"manufacturer": "Fans inc", \
|
||||
"model": "Fan", \
|
||||
"name": "FanBlower", \
|
||||
"sw_version": "Zigbee2MQTT 1.18.2" \
|
||||
}, \
|
||||
"name": "fan", \
|
||||
"payload_off": "OFF_", \
|
||||
"payload_on": "ON_", \
|
||||
"command_topic": "zigbee2mqtt/fan/set/state"
|
||||
}\
|
||||
""");
|
||||
// @formatter:on
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("fan"));
|
||||
|
||||
assertChannel(component, Fan.SWITCH_CHANNEL_ID, "", "zigbee2mqtt/fan/set/state", "On/Off State",
|
||||
OnOffValue.class, AutoUpdatePolicy.RECOMMEND);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testForcedOptimistic() throws InterruptedException {
|
||||
// @formatter:off
|
||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||
"""
|
||||
{ \
|
||||
"availability": [ \
|
||||
{ \
|
||||
"topic": "zigbee2mqtt/bridge/state" \
|
||||
} \
|
||||
], \
|
||||
"device": { \
|
||||
"identifiers": [ \
|
||||
"zigbee2mqtt_0x0000000000000000" \
|
||||
], \
|
||||
"manufacturer": "Fans inc", \
|
||||
"model": "Fan", \
|
||||
"name": "FanBlower", \
|
||||
"sw_version": "Zigbee2MQTT 1.18.2" \
|
||||
}, \
|
||||
"name": "fan", \
|
||||
"payload_off": "OFF_", \
|
||||
"payload_on": "ON_", \
|
||||
"state_topic": "zigbee2mqtt/fan/state", \
|
||||
"command_topic": "zigbee2mqtt/fan/set/state", \
|
||||
"optimistic": true \
|
||||
}\
|
||||
""");
|
||||
// @formatter:on
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("fan"));
|
||||
|
||||
assertChannel(component, Fan.SWITCH_CHANNEL_ID, "zigbee2mqtt/fan/state", "zigbee2mqtt/fan/set/state",
|
||||
"On/Off State", OnOffValue.class, AutoUpdatePolicy.RECOMMEND);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInferredOptimisticWithPosition() throws InterruptedException {
|
||||
// @formatter:off
|
||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
|
||||
"""
|
||||
{ \
|
||||
"availability": [ \
|
||||
{ \
|
||||
"topic": "zigbee2mqtt/bridge/state" \
|
||||
} \
|
||||
], \
|
||||
"device": { \
|
||||
"identifiers": [ \
|
||||
"zigbee2mqtt_0x0000000000000000" \
|
||||
], \
|
||||
"manufacturer": "Fans inc", \
|
||||
"model": "Fan", \
|
||||
"name": "FanBlower", \
|
||||
"sw_version": "Zigbee2MQTT 1.18.2" \
|
||||
}, \
|
||||
"name": "fan", \
|
||||
"payload_off": "OFF_", \
|
||||
"payload_on": "ON_", \
|
||||
"command_topic": "zigbee2mqtt/fan/set/state", \
|
||||
"percentage_command_topic": "bedroom_fan/speed/percentage" \
|
||||
}\
|
||||
""");
|
||||
// @formatter:on
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("fan"));
|
||||
|
||||
assertChannel(component, Fan.SPEED_CHANNEL_ID, "", "bedroom_fan/speed/percentage", "Speed",
|
||||
PercentageValue.class, AutoUpdatePolicy.RECOMMEND);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testCommandTemplate() throws InterruptedException {
|
||||
|
@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.mqtt.generic.values.NumberValue;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||
|
||||
/**
|
||||
* Tests for {@link Number}
|
||||
@ -62,7 +63,7 @@ public class NumberTests extends AbstractComponentTests {
|
||||
assertThat(component.getName(), is("BWA Link Hot Tub Pump 1"));
|
||||
|
||||
assertChannel(component, Number.NUMBER_CHANNEL_ID, "homie/bwa/spa/pump1", "homie/bwa/spa/pump1/set",
|
||||
"BWA Link Hot Tub Pump 1", NumberValue.class);
|
||||
"BWA Link Hot Tub Pump 1", NumberValue.class, null);
|
||||
|
||||
publishMessage("homie/bwa/spa/pump1", "1");
|
||||
assertState(component, Number.NUMBER_CHANNEL_ID, new DecimalType(1));
|
||||
@ -73,6 +74,74 @@ public class NumberTests extends AbstractComponentTests {
|
||||
assertPublished("homie/bwa/spa/pump1/set", "1");
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInferredOptimistic() throws InterruptedException {
|
||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
|
||||
{
|
||||
"name": "BWA Link Hot Tub Pump 1",
|
||||
"availability_topic": "homie/bwa/$state",
|
||||
"payload_available": "ready",
|
||||
"payload_not_available": "lost",
|
||||
"qos": 1,
|
||||
"icon": "mdi:chart-bubble",
|
||||
"device": {
|
||||
"manufacturer": "Balboa Water Group",
|
||||
"sw_version": "2.1.3",
|
||||
"model": "BFBP20",
|
||||
"name": "BWA Link",
|
||||
"identifiers": "bwa"
|
||||
},
|
||||
"command_topic": "homie/bwa/spa/pump1/set",
|
||||
"command_template": "{{ value | round(0) }}",
|
||||
"min": 0,
|
||||
"max": 2,
|
||||
"unique_id": "bwa_spa_pump1"
|
||||
}
|
||||
""");
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("BWA Link Hot Tub Pump 1"));
|
||||
|
||||
assertChannel(component, Number.NUMBER_CHANNEL_ID, "", "homie/bwa/spa/pump1/set", "BWA Link Hot Tub Pump 1",
|
||||
NumberValue.class, AutoUpdatePolicy.RECOMMEND);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testForcedOptimistic() throws InterruptedException {
|
||||
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
|
||||
{
|
||||
"name": "BWA Link Hot Tub Pump 1",
|
||||
"availability_topic": "homie/bwa/$state",
|
||||
"payload_available": "ready",
|
||||
"payload_not_available": "lost",
|
||||
"qos": 1,
|
||||
"icon": "mdi:chart-bubble",
|
||||
"device": {
|
||||
"manufacturer": "Balboa Water Group",
|
||||
"sw_version": "2.1.3",
|
||||
"model": "BFBP20",
|
||||
"name": "BWA Link",
|
||||
"identifiers": "bwa"
|
||||
},
|
||||
"state_topic": "homie/bwa/spa/pump1",
|
||||
"command_topic": "homie/bwa/spa/pump1/set",
|
||||
"command_template": "{{ value | round(0) }}",
|
||||
"min": 0,
|
||||
"max": 2,
|
||||
"unique_id": "bwa_spa_pump1",
|
||||
"optimistic": true
|
||||
}
|
||||
""");
|
||||
|
||||
assertThat(component.channels.size(), is(1));
|
||||
assertThat(component.getName(), is("BWA Link Hot Tub Pump 1"));
|
||||
|
||||
assertChannel(component, Number.NUMBER_CHANNEL_ID, "homie/bwa/spa/pump1", "homie/bwa/spa/pump1/set",
|
||||
"BWA Link Hot Tub Pump 1", NumberValue.class, AutoUpdatePolicy.RECOMMEND);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getConfigTopics() {
|
||||
return Set.of(CONFIG_TOPIC);
|
||||
|
Loading…
Reference in New Issue
Block a user