Mqtt fan feature for resetting current speed percentage
or preset_mode
(#50565)
* Mqtt fan resetting speed percentage or preset_mode * tests reset payload is working with val templates * Remove duplicate line for CONF_PAYLOAD_HIGH_SPEED
This commit is contained in:
parent
5da0191fe3
commit
9abf43f95f
3 changed files with 78 additions and 7 deletions
|
@ -119,6 +119,8 @@ ABBREVIATIONS = {
|
|||
"pl_osc_off": "payload_oscillation_off",
|
||||
"pl_osc_on": "payload_oscillation_on",
|
||||
"pl_paus": "payload_pause",
|
||||
"pl_rst_pct": "payload_reset_percentage",
|
||||
"pl_rst_pr_mode": "payload_reset_preset_mode",
|
||||
"pl_stop": "payload_stop",
|
||||
"pl_strt": "payload_start",
|
||||
"pl_stpa": "payload_start_pause",
|
||||
|
|
|
@ -59,6 +59,7 @@ CONF_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"
|
|||
CONF_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"
|
||||
CONF_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"
|
||||
CONF_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template"
|
||||
CONF_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage"
|
||||
CONF_SPEED_RANGE_MIN = "speed_range_min"
|
||||
CONF_SPEED_RANGE_MAX = "speed_range_max"
|
||||
CONF_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"
|
||||
|
@ -66,6 +67,7 @@ CONF_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"
|
|||
CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"
|
||||
CONF_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template"
|
||||
CONF_PRESET_MODES_LIST = "preset_modes"
|
||||
CONF_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode"
|
||||
CONF_SPEED_STATE_TOPIC = "speed_state_topic"
|
||||
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
|
||||
CONF_SPEED_VALUE_TEMPLATE = "speed_value_template"
|
||||
|
@ -84,6 +86,7 @@ CONF_SPEED_LIST = "speeds"
|
|||
DEFAULT_NAME = "MQTT Fan"
|
||||
DEFAULT_PAYLOAD_ON = "ON"
|
||||
DEFAULT_PAYLOAD_OFF = "OFF"
|
||||
DEFAULT_PAYLOAD_RESET = "None"
|
||||
DEFAULT_OPTIMISTIC = False
|
||||
DEFAULT_SPEED_RANGE_MIN = 1
|
||||
DEFAULT_SPEED_RANGE_MAX = 100
|
||||
|
@ -113,6 +116,13 @@ def valid_speed_range_configuration(config):
|
|||
return config
|
||||
|
||||
|
||||
def valid_preset_mode_configuration(config):
|
||||
"""Validate that the preset mode reset payload is not one of the preset modes."""
|
||||
if config.get(CONF_PAYLOAD_RESET_PRESET_MODE) in config.get(CONF_PRESET_MODES_LIST):
|
||||
raise ValueError("preset_modes must not contain payload_reset_preset_mode")
|
||||
return config
|
||||
|
||||
|
||||
PLATFORM_SCHEMA = vol.All(
|
||||
# CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_STATE_TOPIC, CONF_STATE_VALUE_TEMPLATE, CONF_SPEED_LIST and
|
||||
# Speeds SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH SPEED_OFF,
|
||||
|
@ -153,6 +163,12 @@ PLATFORM_SCHEMA = vol.All(
|
|||
vol.Optional(
|
||||
CONF_SPEED_RANGE_MAX, default=DEFAULT_SPEED_RANGE_MAX
|
||||
): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_PAYLOAD_RESET_PERCENTAGE, default=DEFAULT_PAYLOAD_RESET
|
||||
): cv.string,
|
||||
vol.Optional(
|
||||
CONF_PAYLOAD_RESET_PRESET_MODE, default=DEFAULT_PAYLOAD_RESET
|
||||
): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_LOW_SPEED, default=SPEED_LOW): cv.string,
|
||||
vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string,
|
||||
|
@ -177,6 +193,7 @@ PLATFORM_SCHEMA = vol.All(
|
|||
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema),
|
||||
valid_fan_speed_configuration,
|
||||
valid_speed_range_configuration,
|
||||
valid_preset_mode_configuration,
|
||||
)
|
||||
|
||||
|
||||
|
@ -281,6 +298,8 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
"SPEED_MEDIUM": config[CONF_PAYLOAD_MEDIUM_SPEED],
|
||||
"SPEED_HIGH": config[CONF_PAYLOAD_HIGH_SPEED],
|
||||
"SPEED_OFF": config[CONF_PAYLOAD_OFF_SPEED],
|
||||
"PERCENTAGE_RESET": config[CONF_PAYLOAD_RESET_PERCENTAGE],
|
||||
"PRESET_MODE_RESET": config[CONF_PAYLOAD_RESET_PRESET_MODE],
|
||||
}
|
||||
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
|
||||
self._feature_legacy_speeds = not self._topic[CONF_SPEED_COMMAND_TOPIC] is None
|
||||
|
@ -364,20 +383,27 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
@log_messages(self.hass, self.entity_id)
|
||||
def percentage_received(msg):
|
||||
"""Handle new received MQTT message for the percentage."""
|
||||
numeric_val_str = self._value_templates[ATTR_PERCENTAGE](msg.payload)
|
||||
if not numeric_val_str:
|
||||
rendered_percentage_payload = self._value_templates[ATTR_PERCENTAGE](
|
||||
msg.payload
|
||||
)
|
||||
if not rendered_percentage_payload:
|
||||
_LOGGER.debug("Ignoring empty speed from '%s'", msg.topic)
|
||||
return
|
||||
if rendered_percentage_payload == self._payload["PERCENTAGE_RESET"]:
|
||||
self._percentage = None
|
||||
self._speed = None
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
try:
|
||||
percentage = ranged_value_to_percentage(
|
||||
self._speed_range, int(numeric_val_str)
|
||||
self._speed_range, int(rendered_percentage_payload)
|
||||
)
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
"'%s' received on topic %s. '%s' is not a valid speed within the speed range",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
numeric_val_str,
|
||||
rendered_percentage_payload,
|
||||
)
|
||||
return
|
||||
if percentage < 0 or percentage > 100:
|
||||
|
@ -385,7 +411,7 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
"'%s' received on topic %s. '%s' is not a valid speed within the speed range",
|
||||
msg.payload,
|
||||
msg.topic,
|
||||
numeric_val_str,
|
||||
rendered_percentage_payload,
|
||||
)
|
||||
return
|
||||
self._percentage = percentage
|
||||
|
@ -404,6 +430,10 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
def preset_mode_received(msg):
|
||||
"""Handle new received MQTT message for preset mode."""
|
||||
preset_mode = self._value_templates[ATTR_PRESET_MODE](msg.payload)
|
||||
if preset_mode == self._payload["PRESET_MODE_RESET"]:
|
||||
self._preset_mode = None
|
||||
self.async_write_ha_state()
|
||||
return
|
||||
if not preset_mode:
|
||||
_LOGGER.debug("Ignoring empty preset_mode from '%s'", msg.topic)
|
||||
return
|
||||
|
@ -658,7 +688,7 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
|
||||
if self._optimistic_preset_mode:
|
||||
self._preset_mode = preset_mode
|
||||
self.async_write_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
# async_set_speed is deprecated, support will be removed after a quarter (2021.7)
|
||||
async def async_set_speed(self, speed: str) -> None:
|
||||
|
@ -691,7 +721,7 @@ class MqttFan(MqttEntity, FanEntity):
|
|||
|
||||
if self._optimistic_speed and speed_payload:
|
||||
self._speed = speed
|
||||
self.async_write_ha_state()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_oscillate(self, oscillating: bool) -> None:
|
||||
"""Set oscillation.
|
||||
|
|
|
@ -100,6 +100,8 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog):
|
|||
"payload_low_speed": "speed_lOw",
|
||||
"payload_medium_speed": "speed_mEdium",
|
||||
"payload_high_speed": "speed_High",
|
||||
"payload_reset_percentage": "rEset_percentage",
|
||||
"payload_reset_preset_mode": "rEset_preset_mode",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -168,6 +170,10 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog):
|
|||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get("preset_mode") == "silent"
|
||||
|
||||
async_fire_mqtt_message(hass, "preset-mode-state-topic", "rEset_preset_mode")
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get("preset_mode") is None
|
||||
|
||||
async_fire_mqtt_message(hass, "preset-mode-state-topic", "ModeUnknown")
|
||||
assert "not a valid preset mode" in caplog.text
|
||||
caplog.clear()
|
||||
|
@ -191,6 +197,11 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog):
|
|||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get("speed") == fan.SPEED_OFF
|
||||
|
||||
async_fire_mqtt_message(hass, "percentage-state-topic", "rEset_percentage")
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) is None
|
||||
assert state.attributes.get(fan.ATTR_SPEED) is None
|
||||
|
||||
async_fire_mqtt_message(hass, "speed-state-topic", "speed_very_high")
|
||||
assert "not a valid speed" in caplog.text
|
||||
caplog.clear()
|
||||
|
@ -408,6 +419,10 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap
|
|||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
|
||||
|
||||
async_fire_mqtt_message(hass, "percentage-state-topic", '{"val": "None"}')
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get(fan.ATTR_PERCENTAGE) is None
|
||||
|
||||
async_fire_mqtt_message(hass, "percentage-state-topic", '{"otherval": 100}')
|
||||
assert "Ignoring empty speed from" in caplog.text
|
||||
caplog.clear()
|
||||
|
@ -428,6 +443,10 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap
|
|||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get("preset_mode") == "silent"
|
||||
|
||||
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "None"}')
|
||||
state = hass.states.get("fan.test")
|
||||
assert state.attributes.get("preset_mode") is None
|
||||
|
||||
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"otherval": 100}')
|
||||
assert "Ignoring empty preset_mode from" in caplog.text
|
||||
caplog.clear()
|
||||
|
@ -1895,6 +1914,21 @@ async def test_supported_features(hass, mqtt_mock):
|
|||
"speed_range_min": 0,
|
||||
"speed_range_max": 40,
|
||||
},
|
||||
{
|
||||
"platform": "mqtt",
|
||||
"name": "test7reset_payload_in_preset_modes_a",
|
||||
"command_topic": "command-topic",
|
||||
"preset_mode_command_topic": "preset-mode-command-topic",
|
||||
"preset_modes": ["auto", "smart", "normal", "None"],
|
||||
},
|
||||
{
|
||||
"platform": "mqtt",
|
||||
"name": "test7reset_payload_in_preset_modes_b",
|
||||
"command_topic": "command-topic",
|
||||
"preset_mode_command_topic": "preset-mode-command-topic",
|
||||
"preset_modes": ["whoosh", "silent", "auto", "None"],
|
||||
"payload_reset_preset_mode": "normal",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
|
@ -1962,6 +1996,11 @@ async def test_supported_features(hass, mqtt_mock):
|
|||
state = hass.states.get("fan.test6spd_range_c")
|
||||
assert state is None
|
||||
|
||||
state = hass.states.get("fan.test7reset_payload_in_preset_modes_a")
|
||||
assert state is None
|
||||
state = hass.states.get("fan.test7reset_payload_in_preset_modes_b")
|
||||
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE
|
||||
|
||||
|
||||
async def test_availability_when_connection_lost(hass, mqtt_mock):
|
||||
"""Test availability after MQTT disconnection."""
|
||||
|
|
Loading…
Add table
Reference in a new issue