[mqtt.homeassistant] Improve Cover support (#15875)

* [mqtt.homeassistant] improve Cover support

 * Add support for covers that report position
 * Handle when command and state values for OPEN/CLOSE/STOP
   differ (as they do by default)
 * Expose the full cover state, since it can have tell you
   if the cover is moving or not
 * Handle covers that have a position only, but not a state

* add constants to clarify up/down values

* Be sure to parse percents from strings in RollshutterValue

---------

Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
Cody Cutrer
2023-12-11 11:11:27 -07:00
committed by GitHub
parent 0aab1f5818
commit 73559be058
6 changed files with 358 additions and 80 deletions

View File

@@ -36,9 +36,46 @@ import org.openhab.core.types.Command;
*/
@NonNullByDefault
public class RollershutterValue extends Value {
private final @Nullable String upString;
private final @Nullable String downString;
private final String stopString;
// openHAB interprets open rollershutters as 0, and closed as 100
private static final String UP_VALUE = "0";
private static final String DOWN_VALUE = "100";
// other devices may interpret it the opposite, so we need to be able
// to invert it
private static final String INVERTED_UP_VALUE = DOWN_VALUE;
private static final String INVERTED_DOWN_VALUE = UP_VALUE;
private final @Nullable String upCommandString;
private final @Nullable String downCommandString;
private final @Nullable String stopCommandString;
private final @Nullable String upStateString;
private final @Nullable String downStateString;
private final boolean inverted;
private final boolean transformExtentsToString;
/**
* Creates a new rollershutter value.
*
* @param upCommandString The UP command string.
* @param downCommandString The DOWN command string.
* @param stopCommandString The STOP command string.
* @param upStateString The UP value string. This will be compared to MQTT messages.
* @param downStateString The DOWN value string. This will be compared to MQTT messages.
* @param inverted Whether to invert 0-100/100-0
* @param transformExtentsToString Whether 0/100 will be sent as UP/DOWN
*/
public RollershutterValue(@Nullable String upCommandString, @Nullable String downCommandString,
@Nullable String stopCommandString, @Nullable String upStateString, @Nullable String downStateString,
boolean inverted, boolean transformExtentsToString) {
super(CoreItemFactory.ROLLERSHUTTER,
List.of(UpDownType.class, StopMoveType.class, PercentType.class, StringType.class));
this.upCommandString = upCommandString;
this.downCommandString = downCommandString;
this.stopCommandString = stopCommandString;
this.upStateString = upStateString;
this.downStateString = downStateString;
this.inverted = inverted;
this.transformExtentsToString = transformExtentsToString;
}
/**
* Creates a new rollershutter value.
@@ -48,17 +85,13 @@ public class RollershutterValue extends Value {
* @param stopString The STOP value string. This will be compared to MQTT messages.
*/
public RollershutterValue(@Nullable String upString, @Nullable String downString, @Nullable String stopString) {
super(CoreItemFactory.ROLLERSHUTTER,
List.of(UpDownType.class, StopMoveType.class, PercentType.class, StringType.class));
this.upString = upString;
this.downString = downString;
this.stopString = stopString == null ? StopMoveType.STOP.name() : stopString;
this(upString, downString, stopString, upString, downString, false, true);
}
@Override
public Command parseCommand(Command command) throws IllegalArgumentException {
private Command parseType(Command command, @Nullable String upString, @Nullable String downString)
throws IllegalArgumentException {
if (command instanceof StopMoveType) {
if (command == StopMoveType.STOP) {
if (command == StopMoveType.STOP && stopCommandString != null) {
return command;
} else {
throw new IllegalArgumentException(command.toString() + " is not a valid command for MQTT.");
@@ -68,12 +101,14 @@ public class RollershutterValue extends Value {
if (upString != null) {
return command;
} else {
// Do not handle inversion here. See parseCommand below
return PercentType.ZERO;
}
} else {
if (downString != null) {
return command;
} else {
// Do not handle inversion here. See parseCommand below
return PercentType.HUNDRED;
}
}
@@ -85,43 +120,70 @@ public class RollershutterValue extends Value {
return UpDownType.UP;
} else if (updatedValue.equals(downString)) {
return UpDownType.DOWN;
} else if (updatedValue.equals(stopString)) {
} else if (updatedValue.equals(stopCommandString)) {
return StopMoveType.STOP;
} else {
return PercentType.valueOf(updatedValue);
}
}
throw new IllegalStateException("Cannot call parseCommand() with " + command.toString());
}
@Override
public Command parseCommand(Command command) throws IllegalArgumentException {
// Do not handle inversion in this code path. parseCommand might be called
// multiple times when sending a command TO an MQTT topic. The inversion is
// handled _only_ in getMQTTpublishValue
return parseType(command, upCommandString, downCommandString);
}
@Override
public Command parseMessage(Command command) throws IllegalArgumentException {
command = parseType(command, upStateString, downStateString);
if (inverted && command instanceof PercentType percentType) {
return new PercentType(100 - percentType.intValue());
}
return command;
}
@Override
public String getMQTTpublishValue(Command command, @Nullable String pattern) {
final String upString = this.upString;
final String downString = this.downString;
final String stopString = this.stopString;
return getMQTTpublishValue(command, transformExtentsToString);
}
public String getMQTTpublishValue(Command command, boolean transformExtentsToString) {
final String upCommandString = this.upCommandString;
final String downCommandString = this.downCommandString;
final String stopCommandString = this.stopCommandString;
if (command == UpDownType.UP) {
if (upString != null) {
return upString;
if (upCommandString != null) {
return upCommandString;
} else {
return ((UpDownType) command).name();
return (inverted ? INVERTED_UP_VALUE : UP_VALUE);
}
} else if (command == UpDownType.DOWN) {
if (downString != null) {
return downString;
if (downCommandString != null) {
return downCommandString;
} else {
return ((UpDownType) command).name();
return (inverted ? INVERTED_DOWN_VALUE : DOWN_VALUE);
}
} else if (command == StopMoveType.STOP) {
if (stopString != null) {
return stopString;
if (stopCommandString != null) {
return stopCommandString;
} else {
return ((StopMoveType) command).name();
}
} else if (command instanceof PercentType percentage) {
if (command.equals(PercentType.HUNDRED) && downString != null) {
return downString;
} else if (command.equals(PercentType.ZERO) && upString != null) {
return upString;
if (transformExtentsToString && command.equals(PercentType.HUNDRED) && downCommandString != null) {
return downCommandString;
} else if (transformExtentsToString && command.equals(PercentType.ZERO) && upCommandString != null) {
return upCommandString;
} else {
return String.valueOf(percentage.intValue());
int value = percentage.intValue();
if (inverted) {
value = 100 - value;
}
return String.valueOf(value);
}
} else {
throw new IllegalArgumentException("Invalid command type for Rollershutter item");

View File

@@ -227,6 +227,39 @@ public class ValueTests {
// Test formatting 0/100
assertThat(v.getMQTTpublishValue(PercentType.ZERO, null), is("fancyON"));
assertThat(v.getMQTTpublishValue(PercentType.HUNDRED, null), is("fancyOff"));
// Test parsing from MQTT
assertThat(v.parseMessage(new StringType("fancyON")), is(UpDownType.UP));
assertThat(v.parseMessage(new StringType("fancyOff")), is(UpDownType.DOWN));
}
@Test
public void rollershutterUpdateWithDiscreteCommandAndStateStrings() {
RollershutterValue v = new RollershutterValue("OPEN", "CLOSE", "STOP", "open", "closed", false, true);
// Test with UP/DOWN/STOP command
assertThat(v.parseCommand(UpDownType.UP), is(UpDownType.UP));
assertThat(v.getMQTTpublishValue(UpDownType.UP, null), is("OPEN"));
assertThat(v.parseCommand(UpDownType.DOWN), is(UpDownType.DOWN));
assertThat(v.getMQTTpublishValue(UpDownType.DOWN, null), is("CLOSE"));
assertThat(v.parseCommand(StopMoveType.STOP), is(StopMoveType.STOP));
assertThat(v.getMQTTpublishValue(StopMoveType.STOP, null), is("STOP"));
// Test with custom string
assertThat(v.parseCommand(new StringType("OPEN")), is(UpDownType.UP));
assertThat(v.parseCommand(new StringType("CLOSE")), is(UpDownType.DOWN));
// Test with exact percent
Command command = new PercentType(27);
assertThat(v.parseCommand((Command) command), is(command));
assertThat(v.getMQTTpublishValue(command, null), is("27"));
// Test formatting 0/100
assertThat(v.getMQTTpublishValue(PercentType.ZERO, null), is("OPEN"));
assertThat(v.getMQTTpublishValue(PercentType.HUNDRED, null), is("CLOSE"));
// Test parsing from MQTT
assertThat(v.parseMessage(new StringType("open")), is(UpDownType.UP));
assertThat(v.parseMessage(new StringType("closed")), is(UpDownType.DOWN));
}
@Test