Skip to content

Commit f4250b7

Browse files
authored
[mqtt.homeassistant] Add missing climate properties (openhab#17659)
* Added support for humidity and preset_modes Signed-off-by: Václav Čejka <[email protected]> Signed-off-by: VasekCejka <[email protected]>
1 parent ed93eb5 commit f4250b7

File tree

2 files changed

+129
-0
lines changed
  • bundles/org.openhab.binding.mqtt.homeassistant/src
    • main/java/org/openhab/binding/mqtt/homeassistant/internal/component
    • test/java/org/openhab/binding/mqtt/homeassistant/internal/component

2 files changed

+129
-0
lines changed

bundles/org.openhab.binding.mqtt.homeassistant/src/main/java/org/openhab/binding/mqtt/homeassistant/internal/component/Climate.java

+52
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.openhab.core.library.types.StringType;
3434
import org.openhab.core.library.unit.ImperialUnits;
3535
import org.openhab.core.library.unit.SIUnits;
36+
import org.openhab.core.library.unit.Units;
3637
import org.openhab.core.types.Command;
3738
import org.openhab.core.types.State;
3839

@@ -43,20 +44,24 @@
4344
*
4445
* @author David Graeff - Initial contribution
4546
* @author Anton Kharuzhy - Implementation
47+
* @author Vaclav Cejka - added support for humidity and preset_modes
4648
*/
4749
@NonNullByDefault
4850
public class Climate extends AbstractComponent<Climate.ChannelConfiguration> {
4951
public static final String ACTION_CH_ID = "action";
5052
public static final String AUX_CH_ID = "aux";
5153
public static final String AWAY_MODE_CH_ID = "away-mode";
5254
public static final String AWAY_MODE_CH_ID_DEPRECATED = "awayMode";
55+
public static final String CURRENT_HUMIDITY_CH_ID = "current-humidity";
5356
public static final String CURRENT_TEMPERATURE_CH_ID = "current-temperature";
5457
public static final String CURRENT_TEMPERATURE_CH_ID_DEPRECATED = "currentTemperature";
5558
public static final String FAN_MODE_CH_ID = "fan-mode";
5659
public static final String FAN_MODE_CH_ID_DEPRECATED = "fanMode";
5760
public static final String HOLD_CH_ID = "hold";
5861
public static final String MODE_CH_ID = "mode";
62+
public static final String PRESET_MODE_CH_ID = "preset-mode";
5963
public static final String SWING_CH_ID = "swing";
64+
public static final String TARGET_HUMIDITY_CH_ID = "target-humidity";
6065
public static final String TEMPERATURE_CH_ID = "temperature";
6166
public static final String TEMPERATURE_HIGH_CH_ID = "temperature-high";
6267
public static final String TEMPERATURE_HIGH_CH_ID_DEPRECATED = "temperatureHigh";
@@ -120,6 +125,11 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
120125
@SerializedName("away_mode_state_topic")
121126
protected @Nullable String awayModeStateTopic;
122127

128+
@SerializedName("current_humidity_template")
129+
protected @Nullable String currentHumidityTemplate;
130+
@SerializedName("current_humidity_topic")
131+
protected @Nullable String currentHumidityTopic;
132+
123133
@SerializedName("current_temperature_template")
124134
protected @Nullable String currentTemperatureTemplate;
125135
@SerializedName("current_temperature_topic")
@@ -158,6 +168,18 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
158168
protected @Nullable String modeStateTopic;
159169
protected List<String> modes = Arrays.asList("auto", "off", "cool", "heat", "dry", "fan_only");
160170

171+
@SerializedName("preset_mode_command_template")
172+
protected @Nullable String presetModeCommandTemplate;
173+
@SerializedName("preset_mode_command_topic")
174+
protected @Nullable String presetModeCommandTopic;
175+
@SerializedName("preset_mode_state_topic")
176+
protected @Nullable String presetModeStateTopic;
177+
@SerializedName("preset_mode_value_template")
178+
protected @Nullable String presetModeStateTemplate;
179+
@SerializedName("preset_modes")
180+
protected List<String> presetModes = List.of(); // defaults heavily depend on the
181+
// type of the device
182+
161183
@SerializedName("swing_command_template")
162184
protected @Nullable String swingCommandTemplate;
163185
@SerializedName("swing_command_topic")
@@ -169,6 +191,15 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
169191
@SerializedName("swing_modes")
170192
protected List<String> swingModes = Arrays.asList("on", "off");
171193

194+
@SerializedName("target_humidity_command_template")
195+
protected @Nullable String targetHumidityCommandTemplate;
196+
@SerializedName("target_humidity_command_topic")
197+
protected @Nullable String targetHumidityCommandTopic;
198+
@SerializedName("target_humidity_state_template")
199+
protected @Nullable String targetHumidityStateTemplate;
200+
@SerializedName("target_humidity_state_topic")
201+
protected @Nullable String targetHumidityStateTopic;
202+
172203
@SerializedName("temperature_command_template")
173204
protected @Nullable String temperatureCommandTemplate;
174205
@SerializedName("temperature_command_topic")
@@ -199,6 +230,11 @@ static class ChannelConfiguration extends AbstractChannelConfiguration {
199230
@SerializedName("power_command_topic")
200231
protected @Nullable String powerCommandTopic;
201232

233+
@SerializedName("max_humidity")
234+
protected BigDecimal maxHumidity = new BigDecimal(99);
235+
@SerializedName("min_humidity")
236+
protected BigDecimal minHumidity = new BigDecimal(30);
237+
202238
protected Integer initial = 21;
203239
@SerializedName("max_temp")
204240
protected @Nullable BigDecimal maxTemp;
@@ -236,6 +272,10 @@ ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
236272
channelConfiguration.awayModeCommandTopic, channelConfiguration.awayModeStateTemplate,
237273
channelConfiguration.awayModeStateTopic, commandFilter);
238274

275+
buildOptionalChannel(CURRENT_HUMIDITY_CH_ID, ComponentChannelType.NUMBER,
276+
new NumberValue(new BigDecimal(0), new BigDecimal(100), null, Units.PERCENT), updateListener, null,
277+
null, channelConfiguration.currentHumidityTemplate, channelConfiguration.currentHumidityTopic, null);
278+
239279
buildOptionalChannel(newStyleChannels ? CURRENT_TEMPERATURE_CH_ID : CURRENT_TEMPERATURE_CH_ID_DEPRECATED,
240280
ComponentChannelType.NUMBER,
241281
new NumberValue(null, null, precision, channelConfiguration.temperatureUnit.getUnit()), updateListener,
@@ -260,11 +300,23 @@ ComponentChannelType.SWITCH, new OnOffValue(), updateListener, null,
260300
channelConfiguration.modeCommandTemplate, channelConfiguration.modeCommandTopic,
261301
channelConfiguration.modeStateTemplate, channelConfiguration.modeStateTopic, commandFilter);
262302

303+
buildOptionalChannel(PRESET_MODE_CH_ID, ComponentChannelType.STRING,
304+
new TextValue(channelConfiguration.presetModes.toArray(new String[0])), updateListener,
305+
channelConfiguration.presetModeCommandTemplate, channelConfiguration.presetModeCommandTopic,
306+
channelConfiguration.presetModeStateTemplate, channelConfiguration.presetModeStateTopic, commandFilter);
307+
263308
buildOptionalChannel(SWING_CH_ID, ComponentChannelType.STRING,
264309
new TextValue(channelConfiguration.swingModes.toArray(new String[0])), updateListener,
265310
channelConfiguration.swingCommandTemplate, channelConfiguration.swingCommandTopic,
266311
channelConfiguration.swingStateTemplate, channelConfiguration.swingStateTopic, commandFilter);
267312

313+
buildOptionalChannel(TARGET_HUMIDITY_CH_ID, ComponentChannelType.NUMBER,
314+
new NumberValue(channelConfiguration.minHumidity, channelConfiguration.maxHumidity, null,
315+
Units.PERCENT),
316+
updateListener, channelConfiguration.targetHumidityCommandTemplate,
317+
channelConfiguration.targetHumidityCommandTopic, channelConfiguration.targetHumidityStateTemplate,
318+
channelConfiguration.targetHumidityStateTopic, commandFilter);
319+
268320
buildOptionalChannel(TEMPERATURE_CH_ID, ComponentChannelType.NUMBER,
269321
new NumberValue(channelConfiguration.minTemp, channelConfiguration.maxTemp,
270322
channelConfiguration.tempStep, channelConfiguration.temperatureUnit.getUnit()),

bundles/org.openhab.binding.mqtt.homeassistant/src/test/java/org/openhab/binding/mqtt/homeassistant/internal/component/ClimateTests.java

+77
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.openhab.core.library.types.StringType;
2929
import org.openhab.core.library.unit.ImperialUnits;
3030
import org.openhab.core.library.unit.SIUnits;
31+
import org.openhab.core.library.unit.Units;
3132

3233
/**
3334
* Tests for {@link Climate}
@@ -320,6 +321,82 @@ public void testClimate() {
320321
assertPublished("zigbee2mqtt/th1/power", "OFF");
321322
}
322323

324+
@SuppressWarnings("null")
325+
@Test
326+
public void testClimateWithPresetMode() {
327+
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC),
328+
"""
329+
{
330+
"action_template": "{% set values = {None:None,'idle':'idle','heat':'heating','cool':'cooling','fan_only':'fan'} %}{{ values[value_json.running_state] }}",
331+
"action_topic": "zigbee2mqtt/th2",
332+
"current_temperature_template": "{{ value_json.local_temperature }}",
333+
"current_temperature_topic": "zigbee2mqtt/th2",
334+
"json_attributes_topic": "zigbee2mqtt/th2",
335+
"max_temp": "35",
336+
"min_temp": "5",
337+
"mode_command_topic": "zigbee2mqtt/th2/set/system_mode",
338+
"mode_state_template": "{{ value_json.system_mode }}",
339+
"mode_state_topic": "zigbee2mqtt/th2",
340+
"modes": ["auto","heat","off"],
341+
"name": "th2",
342+
"preset_mode_command_topic": "zigbee2mqtt/th2/set/preset",
343+
"preset_mode_state_topic": "zigbee2mqtt/th2",
344+
"preset_mode_value_template": "{{ value_json.preset }}",
345+
"preset_modes": ["auto","manual","off","on"],
346+
"temp_step": 0.5,
347+
"temperature_command_topic": "zigbee2mqtt/th2/set/current_heating_setpoint",
348+
"temperature_state_template": "{{ value_json.current_heating_setpoint }}",
349+
"temperature_state_topic": "zigbee2mqtt/th2",
350+
"temperature_unit": "C"
351+
}
352+
""");
353+
354+
assertThat(component.channels.size(), is(6));
355+
356+
assertChannel(component, Climate.PRESET_MODE_CH_ID, "zigbee2mqtt/th2", "zigbee2mqtt/th2/set/preset", "th2",
357+
TextValue.class);
358+
359+
publishMessage("zigbee2mqtt/th2", """
360+
{"running_state": "heat",
361+
"local_temperature": "22.2", "preset": "manual", "system_mode": "heat",
362+
"current_heating_setpoint": "24"}
363+
""");
364+
assertState(component, Climate.MODE_CH_ID, new StringType("heat"));
365+
assertState(component, Climate.TEMPERATURE_CH_ID, new QuantityType<>(24, SIUnits.CELSIUS));
366+
assertState(component, Climate.PRESET_MODE_CH_ID, new StringType("manual"));
367+
component.getChannel(Climate.PRESET_MODE_CH_ID).getState().publishValue(new StringType("on"));
368+
assertPublished("zigbee2mqtt/th2/set/preset", "on");
369+
}
370+
371+
@SuppressWarnings("null")
372+
@Test
373+
public void testClimateHumidity() {
374+
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
375+
{
376+
"current_humidity_template": "{{ value_json.humidity }}",
377+
"current_humidity_topic": "zigbee2mqtt/th2",
378+
"max_humidity": "70",
379+
"min_humidity": "30",
380+
"target_humidity_command_topic": "zigbee2mqtt/th2/set/humidity_setpoint",
381+
"target_humidity_state_template": "{{ value_json.humidity_setpoint }}",
382+
"target_humidity_state_topic": "zigbee2mqtt/th2",
383+
"name": "th2"
384+
}
385+
""");
386+
387+
assertThat(component.channels.size(), is(2));
388+
389+
assertChannel(component, Climate.CURRENT_HUMIDITY_CH_ID, "zigbee2mqtt/th2", "", "th2", NumberValue.class);
390+
assertChannel(component, Climate.TARGET_HUMIDITY_CH_ID, "zigbee2mqtt/th2",
391+
"zigbee2mqtt/th2/set/humidity_setpoint", "th2", NumberValue.class);
392+
393+
publishMessage("zigbee2mqtt/th2", """
394+
{"humidity": "55", "humidity_setpoint": "50"}\
395+
""");
396+
assertState(component, Climate.CURRENT_HUMIDITY_CH_ID, new QuantityType<>(55, Units.PERCENT));
397+
assertState(component, Climate.TARGET_HUMIDITY_CH_ID, new QuantityType<>(50, Units.PERCENT));
398+
}
399+
323400
@Override
324401
protected Set<String> getConfigTopics() {
325402
return Set.of(CONFIG_TOPIC);

0 commit comments

Comments
 (0)