Skip to content

Commit

Permalink
[mqtt.homeassistant] fix multi-speed fans (#17813)
Browse files Browse the repository at this point in the history
* fix step math so that the state description represents the step
   scaled to 0-100%

Signed-off-by: Cody Cutrer <cody@cutrer.us>
  • Loading branch information
ccutrer authored Dec 3, 2024
1 parent d0ea14f commit 156e691
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 30 deletions.
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

0 comments on commit 156e691

Please sign in to comment.