[mqtt.generic] Add optional stopCommandTopic for rollershutters (#17158)

* [mqtt.generic] Add optional stopCommandTopic for rollershutters

Signed-off-by: Cody Cutrer <cody@cutrer.us>
pull/17185/head
Cody Cutrer 2024-07-30 12:10:50 -06:00 committed by GitHub
parent cc6a202034
commit 7baec4d651
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 60 additions and 1 deletions

View File

@ -199,6 +199,7 @@ The channel expects values on the corresponding MQTT topic to be in this format
- **on**: An optional string (like "Open") that is recognized as `UP` state. - **on**: An optional string (like "Open") that is recognized as `UP` state.
- **off**: An optional string (like "Close") that is recognized as `DOWN` state. - **off**: An optional string (like "Close") that is recognized as `DOWN` state.
- **stop**: An optional string (like "Stop") that is recognized as `STOP` state. - **stop**: An optional string (like "Stop") that is recognized as `STOP` state.
- **stopCommandTopic**: An optional topic to send `STOP` commands to. If not set, `STOP` commands are sent to the main **commandTopic**.
Internally `UP` is converted to 0%, `DOWN` to 100%. Internally `UP` is converted to 0%, `DOWN` to 100%.
If strings are defined for these values, they are used for sending commands to the broker, too. If strings are defined for these values, they are used for sending commands to the broker, too.

View File

@ -32,6 +32,7 @@ public class ChannelConfig {
/** This is either a state topic or a trigger topic, depending on {@link #trigger}. */ /** This is either a state topic or a trigger topic, depending on {@link #trigger}. */
public String stateTopic = ""; public String stateTopic = "";
public String commandTopic = ""; public String commandTopic = "";
public String stopCommandTopic = "";
/** /**
* If true, the channel state is not updated on a new message. * If true, the channel state is not updated on a new message.

View File

@ -59,6 +59,13 @@ public class ChannelConfigBuilder {
return this; return this;
} }
public ChannelConfigBuilder withStopCommandTopic(@Nullable String topic) {
if (topic != null) {
config.stopCommandTopic = topic;
}
return this;
}
public ChannelConfigBuilder withRetain(boolean retain) { public ChannelConfigBuilder withRetain(boolean retain) {
config.retained = retain; config.retained = retain;
return this; return this;

View File

@ -30,6 +30,7 @@ import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber; import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
@ -420,7 +421,13 @@ public class ChannelState implements MqttMessageSubscriber {
int qos = (config.qos != null) ? config.qos : connection.getQos(); int qos = (config.qos != null) ? config.qos : connection.getQos();
return connection.publish(config.commandTopic, commandString.getBytes(), qos, config.retained); String commandTopic;
if (command.equals(StopMoveType.STOP) && !config.stopCommandTopic.isEmpty()) {
commandTopic = config.stopCommandTopic;
} else {
commandTopic = config.commandTopic;
}
return connection.publish(commandTopic, commandString.getBytes(), qos, config.retained);
} }
/** /**

View File

@ -22,6 +22,11 @@
<description>An MQTT topic that this thing will send a command to. If not set, this will be a read-only <description>An MQTT topic that this thing will send a command to. If not set, this will be a read-only
rollershutter.</description> rollershutter.</description>
</parameter> </parameter>
<parameter name="stopCommandTopic" type="text">
<label>MQTT Stop Command Topic</label>
<description>An MQTT topic that this thing will send a STOP command to. If not set, it will send STOP commands to the
main commandTopic.</description>
</parameter>
<parameter name="transformationPattern" type="text" groupName="transformations"> <parameter name="transformationPattern" type="text" groupName="transformations">
<label>Incoming Value Transformations</label> <label>Incoming Value Transformations</label>
<description><![CDATA[ <description><![CDATA[

View File

@ -156,6 +156,8 @@ thing-type.config.mqtt.rollershutter_channel.stateTopic.label = MQTT State Topic
thing-type.config.mqtt.rollershutter_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. thing-type.config.mqtt.rollershutter_channel.stateTopic.description = An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel.
thing-type.config.mqtt.rollershutter_channel.stop.label = Stop Command thing-type.config.mqtt.rollershutter_channel.stop.label = Stop Command
thing-type.config.mqtt.rollershutter_channel.stop.description = A string (like "STOP") that is sent when commanding the rollershutter to stop. thing-type.config.mqtt.rollershutter_channel.stop.description = A string (like "STOP") that is sent when commanding the rollershutter to stop.
thing-type.config.mqtt.rollershutter_channel.stopCommandTopic.label = MQTT Stop Command Topic
thing-type.config.mqtt.rollershutter_channel.stopCommandTopic.description = An MQTT topic that this thing will send a STOP command to. If not set, it will send STOP commands to the main commandTopic.
thing-type.config.mqtt.rollershutter_channel.transformExtentsToString.label = Transform Commands at Extents to String thing-type.config.mqtt.rollershutter_channel.transformExtentsToString.label = Transform Commands at Extents to String
thing-type.config.mqtt.rollershutter_channel.transformExtentsToString.description = If a command is 0 or 100, send that as UP or DOWN commands instead. Useful if your device doesn't support going to a specific position - only opening or closing, but you have front ends (say HomeKit) or rules that will only send percentage commands instead of UP/DOWN. thing-type.config.mqtt.rollershutter_channel.transformExtentsToString.description = If a command is 0 or 100, send that as UP or DOWN commands instead. Useful if your device doesn't support going to a specific position - only opening or closing, but you have front ends (say HomeKit) or rules that will only send percentage commands instead of UP/DOWN.
thing-type.config.mqtt.rollershutter_channel.transformationPattern.label = Incoming Value Transformations thing-type.config.mqtt.rollershutter_channel.transformationPattern.label = Incoming Value Transformations

View File

@ -52,6 +52,7 @@ import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.library.types.HSBType; import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.RawType; import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StopMoveType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units; import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
@ -134,6 +135,41 @@ public class ChannelStateTests {
verify(connectionMock).unsubscribe(eq("state"), eq(c)); verify(connectionMock).unsubscribe(eq("state"), eq(c));
} }
@Test
public void publishStopTest() throws Exception {
ChannelConfig config = ChannelConfigBuilder.create("state", "command").build();
config.stop = "STOP";
ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
c.start(connectionMock, scheduler, 0).get(50, TimeUnit.MILLISECONDS);
verify(connectionMock).subscribe(eq("state"), eq(c));
c.publishValue(StopMoveType.STOP).get();
verify(connectionMock).publish(eq("command"), argThat(p -> Arrays.equals(p, "STOP".getBytes())), anyInt(),
eq(false));
c.stop().get();
verify(connectionMock).unsubscribe(eq("state"), eq(c));
}
@Test
public void publishStopSeparateTopicTest() throws Exception {
ChannelConfig config = ChannelConfigBuilder.create("state", "command").withStopCommandTopic("stopCommand")
.build();
config.stop = "STOP";
ChannelState c = spy(new ChannelState(config, channelUIDMock, textValue, channelStateUpdateListenerMock));
c.start(connectionMock, scheduler, 0).get(50, TimeUnit.MILLISECONDS);
verify(connectionMock).subscribe(eq("state"), eq(c));
c.publishValue(StopMoveType.STOP).get();
verify(connectionMock).publish(eq("stopCommand"), argThat(p -> Arrays.equals(p, "STOP".getBytes())), anyInt(),
eq(false));
c.stop().get();
verify(connectionMock).unsubscribe(eq("state"), eq(c));
}
@Test @Test
public void receiveWildcardTest() throws Exception { public void receiveWildcardTest() throws Exception {
ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(), ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(),