Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mqtt.homeassistant] Fix multi-speed fans #17813

Merged
merged 1 commit into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ public class PercentageValue extends Value {
private final BigDecimal stepPercent;
private final @Nullable String onValue;
private final @Nullable String offValue;
private final @Nullable String formatOverride;

public PercentageValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step,
@Nullable String onValue, @Nullable String offValue) {
@Nullable String onValue, @Nullable String offValue, @Nullable String formatOverride) {
super(CoreItemFactory.DIMMER, List.of(DecimalType.class, QuantityType.class, IncreaseDecreaseType.class,
OnOffType.class, UpDownType.class, StringType.class));
this.onValue = onValue;
Expand All @@ -69,6 +70,7 @@ public PercentageValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Null
this.span = this.max.subtract(this.min);
this.step = step == null ? BigDecimal.ONE : step;
this.stepPercent = this.step.multiply(HUNDRED).divide(this.span, MathContext.DECIMAL128);
this.formatOverride = formatOverride;
}

@Override
Expand Down Expand Up @@ -135,7 +137,10 @@ public Command parseCommand(Command command) throws IllegalArgumentException {

@Override
public String getMQTTpublishValue(Command command, @Nullable String pattern) {
String formatPattern = pattern;
String formatPattern = this.formatOverride;
if (formatPattern == null) {
formatPattern = pattern;
}
if (formatPattern == null) {
formatPattern = "%s";
}
Expand Down Expand Up @@ -170,7 +175,7 @@ public String getMQTTpublishValue(Command command, @Nullable String pattern) {

@Override
public StateDescriptionFragmentBuilder createStateDescription(boolean readOnly) {
return super.createStateDescription(readOnly).withMaximum(HUNDRED).withMinimum(BigDecimal.ZERO).withStep(step)
.withPattern("%.0f %%");
return super.createStateDescription(readOnly).withMaximum(HUNDRED).withMinimum(BigDecimal.ZERO)
.withStep(stepPercent).withPattern("%.0f %%");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public static Value createValueState(ChannelConfig config, String channelTypeID)
value = new NumberValue(config.min, config.max, config.step, UnitUtils.parseUnit(config.unit));
break;
case MqttBindingConstants.DIMMER:
value = new PercentageValue(config.min, config.max, config.step, config.on, config.off);
value = new PercentageValue(config.min, config.max, config.step, config.on, config.off, null);
break;
case MqttBindingConstants.COLOR_HSB:
value = new ColorValue(ColorMode.HSB, config.on, config.off, config.onBrightness);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public void receiveDecimalAsPercentageUnitTest() {
@Test
public void receivePercentageTest() {
PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
null);
null, null);
ChannelState c = spy(new ChannelState(config, channelUIDMock, value, channelStateUpdateListenerMock));
c.start(connectionMock, mock(ScheduledExecutorService.class), 100);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static org.junit.jupiter.api.Assertions.*;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Objects;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand Down Expand Up @@ -99,7 +100,7 @@ public void illegalNumberCommand() {

@Test
public void illegalPercentCommand() {
PercentageValue v = new PercentageValue(null, null, null, null, null);
PercentageValue v = new PercentageValue(null, null, null, null, null, null);
assertThrows(IllegalStateException.class, () -> v.parseCommand(new StringType("demo")));
}

Expand All @@ -111,7 +112,7 @@ public void illegalOnOffCommand() {

@Test
public void illegalPercentUpdate() {
PercentageValue v = new PercentageValue(null, null, null, null, null);
PercentageValue v = new PercentageValue(null, null, null, null, null, null);
assertThrows(IllegalArgumentException.class, () -> v.parseCommand(new DecimalType(101.0)));
}

Expand Down Expand Up @@ -304,7 +305,9 @@ public void rollershutterUpdateWithOutStrings() {
@Test
public void percentCalc() {
PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0), null,
null);
null, null);
assertThat(v.createStateDescription(false).build().getStep(), is(new BigDecimal(1)));

assertThat(v.parseCommand(new DecimalType("110.0")), is(PercentType.HUNDRED));
assertThat(v.getMQTTpublishValue(PercentType.HUNDRED, null), is("110"));
assertThat(v.parseCommand(new DecimalType(10.0)), is(PercentType.ZERO));
Expand All @@ -316,9 +319,20 @@ public void percentCalc() {
assertThat(v.getMQTTpublishValue(OnOffType.OFF, null), is("10"));
}

@Test
public void percentFormatOverride() {
PercentageValue v = new PercentageValue(BigDecimal.ZERO, new BigDecimal(3.0), null, null, null, "%.0f");
assertThat(v.createStateDescription(false).build().getStep(),
is(new BigDecimal(100).divide(new BigDecimal(3), MathContext.DECIMAL128)));
assertThat(v.getMQTTpublishValue(PercentType.HUNDRED, null), is("3"));
assertThat(v.getMQTTpublishValue(PercentType.valueOf("67"), null), is("2"));
assertThat(v.getMQTTpublishValue(PercentType.valueOf("33"), null), is("1"));
assertThat(v.getMQTTpublishValue(PercentType.ZERO, null), is("0"));
}

@Test
public void percentMQTTValue() {
PercentageValue v = new PercentageValue(null, null, null, null, null);
PercentageValue v = new PercentageValue(null, null, null, null, null, null);
assertThat(v.parseCommand(new DecimalType("10.10000")), is(new PercentType("10.1")));
assertThat(v.getMQTTpublishValue(new PercentType("10.1"), null), is("10.1"));
Command command;
Expand All @@ -333,7 +347,7 @@ public void percentMQTTValue() {
@Test
public void percentCustomOnOff() {
PercentageValue v = new PercentageValue(new BigDecimal("0.0"), new BigDecimal("100.0"), new BigDecimal("1.0"),
"on", "off");
"on", "off", null);
assertThat(v.parseCommand(new StringType("on")), is(OnOffType.ON));
assertThat(v.getMQTTpublishValue(OnOffType.ON, "%s"), is("on"));
assertThat(v.parseCommand(new StringType("off")), is(OnOffType.OFF));
Expand All @@ -343,7 +357,7 @@ public void percentCustomOnOff() {
@Test
public void decimalCalc() {
PercentageValue v = new PercentageValue(new BigDecimal("0.1"), new BigDecimal("1.0"), new BigDecimal("0.1"),
null, null);
null, null, null);
assertThat(v.parseCommand(new DecimalType(1.0)), is(PercentType.HUNDRED));
assertThat(v.parseCommand(new DecimalType(0.1)), is(PercentType.ZERO));
PercentType command = (PercentType) v.parseCommand(new DecimalType(0.2));
Expand All @@ -353,7 +367,7 @@ public void decimalCalc() {
@Test
public void increaseDecreaseCalc() {
PercentageValue v = new PercentageValue(new BigDecimal("1.0"), new BigDecimal("11.0"), new BigDecimal("0.5"),
null, null);
null, null, null);

// Normal operation.
PercentType command = (PercentType) v.parseCommand(new DecimalType("6.0"));
Expand Down Expand Up @@ -382,7 +396,7 @@ public void increaseDecreaseCalc() {
@Test
public void upDownCalc() {
PercentageValue v = new PercentageValue(new BigDecimal("1.0"), new BigDecimal("11.0"), new BigDecimal("0.5"),
null, null);
null, null, null);

// Normal operation.
PercentType command = (PercentType) v.parseCommand(new DecimalType("6.0"));
Expand Down Expand Up @@ -411,7 +425,7 @@ public void upDownCalc() {
@Test
public void percentCalcInvalid() {
PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0), null,
null);
null, null);
assertThrows(IllegalArgumentException.class, () -> v.parseCommand(new DecimalType(9.0)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public class Fan extends AbstractComponent<Fan.ChannelConfiguration> implements
public static final String OSCILLATION_CHANNEL_ID = "oscillation";
public static final String DIRECTION_CHANNEL_ID = "direction";

private static final BigDecimal BIG_DECIMAL_HUNDRED = new BigDecimal(100);
private static final String FORMAT_INTEGER = "%.0f";

/**
* Configuration class for MQTT component
*/
Expand All @@ -60,6 +63,8 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {

protected @Nullable Boolean optimistic;

@SerializedName("state_value_template")
protected @Nullable String stateValueTemplate;
@SerializedName("state_topic")
protected @Nullable String stateTopic;
@SerializedName("command_template")
Expand Down Expand Up @@ -136,18 +141,17 @@ public Fan(ComponentFactory.ComponentConfiguration componentConfiguration, boole
: this;
onOffChannel = buildChannel(newStyleChannels ? SWITCH_CHANNEL_ID : SWITCH_CHANNEL_ID_DEPRECATED,
ComponentChannelType.SWITCH, onOffValue, "On/Off State", onOffListener)
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
.stateTopic(channelConfiguration.stateTopic, channelConfiguration.stateValueTemplate)
.commandTopic(channelConfiguration.commandTopic, channelConfiguration.isRetain(),
channelConfiguration.getQos(), channelConfiguration.commandTemplate)
.inferOptimistic(channelConfiguration.optimistic)
.build(channelConfiguration.percentageCommandTopic == null);

rawSpeedState = UnDefType.NULL;

int speeds = Math.min(channelConfiguration.speedRangeMax, 100) - Math.max(channelConfiguration.speedRangeMin, 1)
+ 1;
speedValue = new PercentageValue(BigDecimal.ZERO, BigDecimal.valueOf(100), BigDecimal.valueOf(100.0d / speeds),
channelConfiguration.payloadOn, channelConfiguration.payloadOff);
speedValue = new PercentageValue(BigDecimal.valueOf(channelConfiguration.speedRangeMin - 1),
BigDecimal.valueOf(channelConfiguration.speedRangeMax), null, channelConfiguration.payloadOn,
channelConfiguration.payloadOff, FORMAT_INTEGER);

if (channelConfiguration.percentageCommandTopic != null) {
hiddenChannels.add(onOffChannel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public abstract class Light extends AbstractComponent<Light.ChannelConfiguration
protected static final String ON_COMMAND_TYPE_BRIGHTNESS = "brightness";
protected static final String ON_COMMAND_TYPE_LAST = "last";

protected static final String FORMAT_INTEGER = "%.0f";

/**
* Configuration class for MQTT component
*/
Expand Down Expand Up @@ -276,7 +278,7 @@ protected Light(ComponentFactory.ComponentConfiguration builder, boolean newStyl

onOffValue = new OnOffValue(channelConfiguration.payloadOn, channelConfiguration.payloadOff);
brightnessValue = new PercentageValue(null, new BigDecimal(channelConfiguration.brightnessScale), null, null,
null);
null, FORMAT_INTEGER);
@Nullable
List<String> effectList = channelConfiguration.effectList;
if (effectList != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ protected void buildChannels() {
}

onOffValue = new OnOffValue("on", "off");
brightnessValue = new PercentageValue(null, new BigDecimal(255), null, null, null);
brightnessValue = new PercentageValue(null, new BigDecimal(255), null, null, null, FORMAT_INTEGER);

if (channelConfiguration.redTemplate != null && channelConfiguration.greenTemplate != null
&& channelConfiguration.blueTemplate != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ ComponentChannelType.STRING, new TextValue(), updateListener, null,
if (supportedFeatures.contains(FEATURE_BATTERY)) {
buildOptionalChannel(newStyleChannels ? BATTERY_LEVEL_CH_ID : BATTERY_LEVEL_CH_ID_DEPRECATED,
ComponentChannelType.DIMMER,
new PercentageValue(BigDecimal.ZERO, BigDecimal.valueOf(100), BigDecimal.ONE, null, null),
new PercentageValue(BigDecimal.ZERO, BigDecimal.valueOf(100), BigDecimal.ONE, null, null, null),
updateListener, null, null, "{{ value_json.battery_level }}", channelConfiguration.stateTopic);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public class Valve extends AbstractComponent<Valve.ChannelConfiguration> impleme
private static final String POSITION_KEY = "position";
private static final String STATE_KEY = "state";

private static final String FORMAT_INTEGER = "%.0f";

private final Logger logger = LoggerFactory.getLogger(Valve.class);

/**
Expand Down Expand Up @@ -121,7 +123,7 @@ public Valve(ComponentFactory.ComponentConfiguration componentConfiguration, boo
onOffValue = new OnOffValue(channelConfiguration.stateOpen, channelConfiguration.stateClosed,
channelConfiguration.payloadOpen, channelConfiguration.payloadClose);
positionValue = new PercentageValue(BigDecimal.valueOf(channelConfiguration.positionClosed),
BigDecimal.valueOf(channelConfiguration.positionOpen), null, null, null);
BigDecimal.valueOf(channelConfiguration.positionOpen), null, null, null, FORMAT_INTEGER);

if (channelConfiguration.reportsPosition) {
buildChannel(VALVE_CHANNEL_ID, ComponentChannelType.DIMMER, positionValue, getName(), this)
Expand Down
Loading