Percentage and preset mode support for MQTT fan (#47944)

* git push --all origin

* Fix percentage to ordered list conversion

* Tests for mqtt fan and fixes

* Improve tests and error handling base config

* Additional tests

* Tests completed, small fixes

* Allow preset mode and percentages combined

* Remove raise in setup and update tests

* Alignment with fan entity mode

* Fix pylint for len-as-condition

* Remove python binary cache file from PR

* Additional tests on async_turn_on and fix

* Added comments for deprecation of speeds

* Schema checks before init

* Optimize pre schema checks

* Correct schema checks

* Update homeassistant/components/mqtt/abbreviations.py

Comment speeds for mqtt fan are deprecated not needed here

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Comment speeds for mqtt fan are deprecated not needed here

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Comment speeds for mqtt fan are deprecated not needed here

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Comment speeds for mqtt fan are deprecated not needed here

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Comment speeds for mqtt fan are deprecated not needed here

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Warnings for exceptions - testing speed_range

* Update homeassistant/components/mqtt/abbreviations.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

Co-authored-by: Erik Montnemery <erik@montnemery.com>

* Update homeassistant/components/mqtt/fan.py

* Save with black

Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
Jan Bouwhuis 2021-03-26 08:37:47 +01:00 committed by GitHub
parent da2fecb312
commit 5b17aaf9d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 1725 additions and 157 deletions

View File

@ -88,6 +88,9 @@ ABBREVIATIONS = {
"osc_cmd_t": "oscillation_command_topic", "osc_cmd_t": "oscillation_command_topic",
"osc_stat_t": "oscillation_state_topic", "osc_stat_t": "oscillation_state_topic",
"osc_val_tpl": "oscillation_value_template", "osc_val_tpl": "oscillation_value_template",
"pct_cmd_t": "percentage_command_topic",
"pct_stat_t": "percentage_state_topic",
"pct_val_tpl": "percentage_value_template",
"pl": "payload", "pl": "payload",
"pl_arm_away": "payload_arm_away", "pl_arm_away": "payload_arm_away",
"pl_arm_home": "payload_arm_home", "pl_arm_home": "payload_arm_home",
@ -124,6 +127,10 @@ ABBREVIATIONS = {
"pow_cmd_t": "power_command_topic", "pow_cmd_t": "power_command_topic",
"pow_stat_t": "power_state_topic", "pow_stat_t": "power_state_topic",
"pow_stat_tpl": "power_state_template", "pow_stat_tpl": "power_state_template",
"pr_mode_cmd_t": "preset_mode_command_topic",
"pr_mode_stat_t": "preset_mode_state_topic",
"pr_mode_val_tpl": "preset_mode_value_template",
"pr_modes": "preset_modes",
"r_tpl": "red_template", "r_tpl": "red_template",
"ret": "retain", "ret": "retain",
"rgb_cmd_tpl": "rgb_command_template", "rgb_cmd_tpl": "rgb_command_template",
@ -139,6 +146,8 @@ ABBREVIATIONS = {
"pos_tpl": "position_template", "pos_tpl": "position_template",
"spd_cmd_t": "speed_command_topic", "spd_cmd_t": "speed_command_topic",
"spd_stat_t": "speed_state_topic", "spd_stat_t": "speed_state_topic",
"spd_rng_min": "speed_range_min",
"spd_rng_max": "speed_range_max",
"spd_val_tpl": "speed_value_template", "spd_val_tpl": "speed_value_template",
"spds": "speeds", "spds": "speeds",
"src_type": "source_type", "src_type": "source_type",

View File

@ -1,18 +1,23 @@
"""Support for MQTT fans.""" """Support for MQTT fans."""
import functools import functools
import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components import fan from homeassistant.components import fan
from homeassistant.components.fan import ( from homeassistant.components.fan import (
ATTR_PERCENTAGE,
ATTR_PRESET_MODE,
ATTR_SPEED, ATTR_SPEED,
SPEED_HIGH, SPEED_HIGH,
SPEED_LOW, SPEED_LOW,
SPEED_MEDIUM, SPEED_MEDIUM,
SPEED_OFF, SPEED_OFF,
SUPPORT_OSCILLATE, SUPPORT_OSCILLATE,
SUPPORT_PRESET_MODE,
SUPPORT_SET_SPEED, SUPPORT_SET_SPEED,
FanEntity, FanEntity,
speed_list_without_preset_modes,
) )
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_NAME,
@ -25,6 +30,12 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util.percentage import (
ordered_list_item_to_percentage,
percentage_to_ordered_list_item,
percentage_to_ranged_value,
ranged_value_to_percentage,
)
from . import ( from . import (
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
@ -40,6 +51,15 @@ from .debug_info import log_messages
from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper from .mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, async_setup_entry_helper
CONF_STATE_VALUE_TEMPLATE = "state_value_template" CONF_STATE_VALUE_TEMPLATE = "state_value_template"
CONF_PERCENTAGE_STATE_TOPIC = "percentage_state_topic"
CONF_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic"
CONF_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template"
CONF_SPEED_RANGE_MIN = "speed_range_min"
CONF_SPEED_RANGE_MAX = "speed_range_max"
CONF_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic"
CONF_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic"
CONF_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template"
CONF_PRESET_MODES_LIST = "preset_modes"
CONF_SPEED_STATE_TOPIC = "speed_state_topic" CONF_SPEED_STATE_TOPIC = "speed_state_topic"
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
CONF_SPEED_VALUE_TEMPLATE = "speed_value_template" CONF_SPEED_VALUE_TEMPLATE = "speed_value_template"
@ -58,19 +78,71 @@ DEFAULT_NAME = "MQTT Fan"
DEFAULT_PAYLOAD_ON = "ON" DEFAULT_PAYLOAD_ON = "ON"
DEFAULT_PAYLOAD_OFF = "OFF" DEFAULT_PAYLOAD_OFF = "OFF"
DEFAULT_OPTIMISTIC = False DEFAULT_OPTIMISTIC = False
DEFAULT_SPEED_RANGE_MIN = 1
DEFAULT_SPEED_RANGE_MAX = 100
OSCILLATE_ON_PAYLOAD = "oscillate_on" OSCILLATE_ON_PAYLOAD = "oscillate_on"
OSCILLATE_OFF_PAYLOAD = "oscillate_off" OSCILLATE_OFF_PAYLOAD = "oscillate_off"
OSCILLATION = "oscillation" OSCILLATION = "oscillation"
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( _LOGGER = logging.getLogger(__name__)
def valid_fan_speed_configuration(config):
"""Validate that the fan speed configuration is valid, throws if it isn't."""
if config.get(CONF_SPEED_COMMAND_TOPIC) and not speed_list_without_preset_modes(
config.get(CONF_SPEED_LIST)
):
raise ValueError("No valid speeds configured")
return config
def valid_speed_range_configuration(config):
"""Validate that the fan speed_range configuration is valid, throws if it isn't."""
if config.get(CONF_SPEED_RANGE_MIN) == 0:
raise ValueError("speed_range_min must be > 0")
if config.get(CONF_SPEED_RANGE_MIN) >= config.get(CONF_SPEED_RANGE_MAX):
raise ValueError("speed_range_max must be > speed_range_min")
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,
# are deprecated, support will be removed after a quarter (2021.7)
cv.deprecated(CONF_PAYLOAD_HIGH_SPEED),
cv.deprecated(CONF_PAYLOAD_LOW_SPEED),
cv.deprecated(CONF_PAYLOAD_MEDIUM_SPEED),
cv.deprecated(CONF_SPEED_LIST),
cv.deprecated(CONF_SPEED_COMMAND_TOPIC),
cv.deprecated(CONF_SPEED_STATE_TOPIC),
cv.deprecated(CONF_SPEED_VALUE_TEMPLATE),
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_OSCILLATION_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_OSCILLATION_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_PERCENTAGE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_PERCENTAGE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PERCENTAGE_VALUE_TEMPLATE): cv.template,
# CONF_PRESET_MODE_COMMAND_TOPIC and CONF_PRESET_MODES_LIST must be used together
vol.Inclusive(
CONF_PRESET_MODE_COMMAND_TOPIC, "preset_modes"
): mqtt.valid_publish_topic,
vol.Inclusive(
CONF_PRESET_MODES_LIST, "preset_modes", default=[]
): cv.ensure_list,
vol.Optional(CONF_PRESET_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PRESET_MODE_VALUE_TEMPLATE): cv.template,
vol.Optional(
CONF_SPEED_RANGE_MIN, default=DEFAULT_SPEED_RANGE_MIN
): cv.positive_int,
vol.Optional(
CONF_SPEED_RANGE_MAX, default=DEFAULT_SPEED_RANGE_MAX
): cv.positive_int,
vol.Optional(CONF_PAYLOAD_HIGH_SPEED, default=SPEED_HIGH): 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_LOW_SPEED, default=SPEED_LOW): cv.string,
vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string, vol.Optional(CONF_PAYLOAD_MEDIUM_SPEED, default=SPEED_MEDIUM): cv.string,
@ -92,7 +164,10 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_SPEED_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
} }
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema),
valid_fan_speed_configuration,
valid_speed_range_configuration,
)
async def async_setup_platform( async def async_setup_platform(
@ -124,7 +199,10 @@ class MqttFan(MqttEntity, FanEntity):
def __init__(self, hass, config, config_entry, discovery_data): def __init__(self, hass, config, config_entry, discovery_data):
"""Initialize the MQTT fan.""" """Initialize the MQTT fan."""
self._state = False self._state = False
# self._speed will be removed after a quarter (2021.7)
self._speed = None self._speed = None
self._percentage = None
self._preset_mode = None
self._oscillation = None self._oscillation = None
self._supported_features = 0 self._supported_features = 0
@ -133,6 +211,8 @@ class MqttFan(MqttEntity, FanEntity):
self._templates = None self._templates = None
self._optimistic = None self._optimistic = None
self._optimistic_oscillation = None self._optimistic_oscillation = None
self._optimistic_percentage = None
self._optimistic_preset_mode = None
self._optimistic_speed = None self._optimistic_speed = None
MqttEntity.__init__(self, hass, config, config_entry, discovery_data) MqttEntity.__init__(self, hass, config, config_entry, discovery_data)
@ -144,11 +224,19 @@ class MqttFan(MqttEntity, FanEntity):
def _setup_from_config(self, config): def _setup_from_config(self, config):
"""(Re)Setup the entity.""" """(Re)Setup the entity."""
self._speed_range = (
config.get(CONF_SPEED_RANGE_MIN),
config.get(CONF_SPEED_RANGE_MAX),
)
self._topic = { self._topic = {
key: config.get(key) key: config.get(key)
for key in ( for key in (
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_PERCENTAGE_STATE_TOPIC,
CONF_PERCENTAGE_COMMAND_TOPIC,
CONF_PRESET_MODE_STATE_TOPIC,
CONF_PRESET_MODE_COMMAND_TOPIC,
CONF_SPEED_STATE_TOPIC, CONF_SPEED_STATE_TOPIC,
CONF_SPEED_COMMAND_TOPIC, CONF_SPEED_COMMAND_TOPIC,
CONF_OSCILLATION_STATE_TOPIC, CONF_OSCILLATION_STATE_TOPIC,
@ -157,6 +245,9 @@ class MqttFan(MqttEntity, FanEntity):
} }
self._templates = { self._templates = {
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE), CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
ATTR_PERCENTAGE: config.get(CONF_PERCENTAGE_VALUE_TEMPLATE),
ATTR_PRESET_MODE: config.get(CONF_PRESET_MODE_VALUE_TEMPLATE),
# ATTR_SPEED is deprecated in the schema, support will be removed after a quarter (2021.7)
ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE), ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE),
OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE), OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE),
} }
@ -165,16 +256,53 @@ class MqttFan(MqttEntity, FanEntity):
"STATE_OFF": config[CONF_PAYLOAD_OFF], "STATE_OFF": config[CONF_PAYLOAD_OFF],
"OSCILLATE_ON_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_ON], "OSCILLATE_ON_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_ON],
"OSCILLATE_OFF_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_OFF], "OSCILLATE_OFF_PAYLOAD": config[CONF_PAYLOAD_OSCILLATION_OFF],
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
"SPEED_LOW": config[CONF_PAYLOAD_LOW_SPEED], "SPEED_LOW": config[CONF_PAYLOAD_LOW_SPEED],
"SPEED_MEDIUM": config[CONF_PAYLOAD_MEDIUM_SPEED], "SPEED_MEDIUM": config[CONF_PAYLOAD_MEDIUM_SPEED],
"SPEED_HIGH": config[CONF_PAYLOAD_HIGH_SPEED], "SPEED_HIGH": config[CONF_PAYLOAD_HIGH_SPEED],
"SPEED_OFF": config[CONF_PAYLOAD_OFF_SPEED], "SPEED_OFF": config[CONF_PAYLOAD_OFF_SPEED],
} }
# 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
if self._feature_legacy_speeds:
self._legacy_speeds_list = config[CONF_SPEED_LIST]
self._legacy_speeds_list_no_off = speed_list_without_preset_modes(
self._legacy_speeds_list
)
else:
self._legacy_speeds_list = []
self._feature_percentage = CONF_PERCENTAGE_COMMAND_TOPIC in config
self._feature_preset_mode = CONF_PRESET_MODE_COMMAND_TOPIC in config
if self._feature_preset_mode:
self._speeds_list = speed_list_without_preset_modes(
self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST]
)
self._preset_modes = (
self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST]
)
else:
self._speeds_list = speed_list_without_preset_modes(
self._legacy_speeds_list
)
self._preset_modes = []
if not self._speeds_list or self._feature_percentage:
self._speed_count = 100
else:
self._speed_count = len(self._speeds_list)
optimistic = config[CONF_OPTIMISTIC] optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
self._optimistic_oscillation = ( self._optimistic_oscillation = (
optimistic or self._topic[CONF_OSCILLATION_STATE_TOPIC] is None optimistic or self._topic[CONF_OSCILLATION_STATE_TOPIC] is None
) )
self._optimistic_percentage = (
optimistic or self._topic[CONF_PERCENTAGE_STATE_TOPIC] is None
)
self._optimistic_preset_mode = (
optimistic or self._topic[CONF_PRESET_MODE_STATE_TOPIC] is None
)
self._optimistic_speed = ( self._optimistic_speed = (
optimistic or self._topic[CONF_SPEED_STATE_TOPIC] is None optimistic or self._topic[CONF_SPEED_STATE_TOPIC] is None
) )
@ -184,9 +312,14 @@ class MqttFan(MqttEntity, FanEntity):
self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None
and SUPPORT_OSCILLATE and SUPPORT_OSCILLATE
) )
self._supported_features |= ( if self._feature_preset_mode and self._speeds_list:
self._topic[CONF_SPEED_COMMAND_TOPIC] is not None and SUPPORT_SET_SPEED self._supported_features |= SUPPORT_SET_SPEED
) if self._feature_percentage:
self._supported_features |= SUPPORT_SET_SPEED
if self._feature_legacy_speeds:
self._supported_features |= SUPPORT_SET_SPEED
if self._feature_preset_mode:
self._supported_features |= SUPPORT_PRESET_MODE
for key, tpl in list(self._templates.items()): for key, tpl in list(self._templates.items()):
if tpl is None: if tpl is None:
@ -217,19 +350,103 @@ class MqttFan(MqttEntity, FanEntity):
"qos": self._config[CONF_QOS], "qos": self._config[CONF_QOS],
} }
@callback
@log_messages(self.hass, self.entity_id)
def percentage_received(msg):
"""Handle new received MQTT message for the percentage."""
numeric_val_str = self._templates[ATTR_PERCENTAGE](msg.payload)
try:
percentage = ranged_value_to_percentage(
self._speed_range, int(numeric_val_str)
)
except ValueError:
_LOGGER.warning(
"'%s' received on topic %s is not a valid speed within the speed range",
msg.payload,
msg.topic,
)
return
if percentage < 0 or percentage > 100:
_LOGGER.warning(
"'%s' received on topic %s is not a valid speed within the speed range",
msg.payload,
msg.topic,
)
return
self._percentage = percentage
self.async_write_ha_state()
if self._topic[CONF_PERCENTAGE_STATE_TOPIC] is not None:
topics[CONF_PERCENTAGE_STATE_TOPIC] = {
"topic": self._topic[CONF_PERCENTAGE_STATE_TOPIC],
"msg_callback": percentage_received,
"qos": self._config[CONF_QOS],
}
self._percentage = None
@callback
@log_messages(self.hass, self.entity_id)
def preset_mode_received(msg):
"""Handle new received MQTT message for preset mode."""
preset_mode = self._templates[ATTR_PRESET_MODE](msg.payload)
if preset_mode not in self.preset_modes:
_LOGGER.warning(
"'%s' received on topic %s is not a valid preset mode",
msg.payload,
msg.topic,
)
return
self._preset_mode = preset_mode
if not self._implemented_percentage and (preset_mode in self.speed_list):
self._percentage = ordered_list_item_to_percentage(
self.speed_list, preset_mode
)
self.async_write_ha_state()
if self._topic[CONF_PRESET_MODE_STATE_TOPIC] is not None:
topics[CONF_PRESET_MODE_STATE_TOPIC] = {
"topic": self._topic[CONF_PRESET_MODE_STATE_TOPIC],
"msg_callback": preset_mode_received,
"qos": self._config[CONF_QOS],
}
self._preset_mode = None
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
@callback @callback
@log_messages(self.hass, self.entity_id) @log_messages(self.hass, self.entity_id)
def speed_received(msg): def speed_received(msg):
"""Handle new received MQTT message for the speed.""" """Handle new received MQTT message for the speed."""
payload = self._templates[ATTR_SPEED](msg.payload) speed_payload = self._templates[ATTR_SPEED](msg.payload)
if payload == self._payload["SPEED_LOW"]: if speed_payload == self._payload["SPEED_LOW"]:
self._speed = SPEED_LOW speed = SPEED_LOW
elif payload == self._payload["SPEED_MEDIUM"]: elif speed_payload == self._payload["SPEED_MEDIUM"]:
self._speed = SPEED_MEDIUM speed = SPEED_MEDIUM
elif payload == self._payload["SPEED_HIGH"]: elif speed_payload == self._payload["SPEED_HIGH"]:
self._speed = SPEED_HIGH speed = SPEED_HIGH
elif payload == self._payload["SPEED_OFF"]: elif speed_payload == self._payload["SPEED_OFF"]:
self._speed = SPEED_OFF speed = SPEED_OFF
else:
speed = None
if speed and speed in self._legacy_speeds_list:
self._speed = speed
else:
_LOGGER.warning(
"'%s' received on topic %s is not a valid speed",
msg.payload,
msg.topic,
)
return
if not self._implemented_percentage:
if speed in self._speeds_list:
self._percentage = ordered_list_item_to_percentage(
self._speeds_list, speed
)
elif speed == SPEED_OFF:
self._percentage = 0
self.async_write_ha_state() self.async_write_ha_state()
if self._topic[CONF_SPEED_STATE_TOPIC] is not None: if self._topic[CONF_SPEED_STATE_TOPIC] is not None:
@ -273,10 +490,42 @@ class MqttFan(MqttEntity, FanEntity):
"""Return true if device is on.""" """Return true if device is on."""
return self._state return self._state
@property
def _implemented_percentage(self):
"""Return true if percentage has been implemented."""
return self._feature_percentage
@property
def _implemented_preset_mode(self):
"""Return true if preset_mode has been implemented."""
return self._feature_preset_mode
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
@property
def _implemented_speed(self):
"""Return true if speed has been implemented."""
return self._feature_legacy_speeds
@property
def percentage(self):
"""Return the current percentage."""
return self._percentage
@property
def preset_mode(self):
"""Return the current preset _mode."""
return self._preset_mode
@property
def preset_modes(self) -> list:
"""Get the list of available preset modes."""
return self._preset_modes
# The speed_list property is deprecated in the schema, support will be removed after a quarter (2021.7)
@property @property
def speed_list(self) -> list: def speed_list(self) -> list:
"""Get the list of available speeds.""" """Get the list of available speeds."""
return self._config[CONF_SPEED_LIST] return self._speeds_list
@property @property
def supported_features(self) -> int: def supported_features(self) -> int:
@ -288,18 +537,17 @@ class MqttFan(MqttEntity, FanEntity):
"""Return the current speed.""" """Return the current speed."""
return self._speed return self._speed
@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports or 100 if percentage is supported."""
return self._speed_count
@property @property
def oscillating(self): def oscillating(self):
"""Return the oscillation state.""" """Return the oscillation state."""
return self._oscillation return self._oscillation
# # The speed attribute deprecated in the schema, support will be removed after a quarter (2021.7)
# The fan entity model has changed to use percentages and preset_modes
# instead of speeds.
#
# Please review
# https://developers.home-assistant.io/docs/core/entity/fan/
#
async def async_turn_on( async def async_turn_on(
self, self,
speed: str = None, speed: str = None,
@ -318,7 +566,12 @@ class MqttFan(MqttEntity, FanEntity):
self._config[CONF_QOS], self._config[CONF_QOS],
self._config[CONF_RETAIN], self._config[CONF_RETAIN],
) )
if speed: if percentage:
await self.async_set_percentage(percentage)
if preset_mode:
await self.async_set_preset_mode(preset_mode)
# The speed attribute deprecated in the schema, support will be removed after a quarter (2021.7)
if speed and not percentage and not preset_mode:
await self.async_set_speed(speed) await self.async_set_speed(speed)
if self._optimistic: if self._optimistic:
self._state = True self._state = True
@ -340,31 +593,110 @@ class MqttFan(MqttEntity, FanEntity):
self._state = False self._state = False
self.async_write_ha_state() self.async_write_ha_state()
async def async_set_speed(self, speed: str) -> None: async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed of the fan. """Set the percentage of the fan.
This method is a coroutine. This method is a coroutine.
""" """
if speed == SPEED_LOW: percentage_payload = int(
mqtt_payload = self._payload["SPEED_LOW"] percentage_to_ranged_value(self._speed_range, percentage)
elif speed == SPEED_MEDIUM: )
mqtt_payload = self._payload["SPEED_MEDIUM"] if self._implemented_preset_mode:
elif speed == SPEED_HIGH: if percentage:
mqtt_payload = self._payload["SPEED_HIGH"] await self.async_set_preset_mode(
elif speed == SPEED_OFF: preset_mode=percentage_to_ordered_list_item(
mqtt_payload = self._payload["SPEED_OFF"] self.speed_list, percentage
else: )
raise ValueError(f"{speed} is not a valid fan speed") )
# Legacy are deprecated in the schema, support will be removed after a quarter (2021.7)
elif self._feature_legacy_speeds and (
SPEED_OFF in self._legacy_speeds_list
):
await self.async_set_preset_mode(SPEED_OFF)
# Legacy are deprecated in the schema, support will be removed after a quarter (2021.7)
elif self._feature_legacy_speeds:
if percentage:
await self.async_set_speed(
percentage_to_ordered_list_item(
self._legacy_speeds_list_no_off,
percentage,
)
)
elif SPEED_OFF in self._legacy_speeds_list:
await self.async_set_speed(SPEED_OFF)
if self._implemented_percentage:
mqtt.async_publish(
self.hass,
self._topic[CONF_PERCENTAGE_COMMAND_TOPIC],
percentage_payload,
self._config[CONF_QOS],
self._config[CONF_RETAIN],
)
if self._optimistic_percentage:
self._percentage = percentage
self.async_write_ha_state()
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan.
This method is a coroutine.
"""
if preset_mode not in self.preset_modes:
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
return
# Legacy are deprecated in the schema, support will be removed after a quarter (2021.7)
if preset_mode in self._legacy_speeds_list:
await self.async_set_speed(speed=preset_mode)
if not self._implemented_percentage and preset_mode in self.speed_list:
self._percentage = ordered_list_item_to_percentage(
self.speed_list, preset_mode
)
mqtt_payload = preset_mode
mqtt.async_publish( mqtt.async_publish(
self.hass, self.hass,
self._topic[CONF_SPEED_COMMAND_TOPIC], self._topic[CONF_PRESET_MODE_COMMAND_TOPIC],
mqtt_payload, mqtt_payload,
self._config[CONF_QOS], self._config[CONF_QOS],
self._config[CONF_RETAIN], self._config[CONF_RETAIN],
) )
if self._optimistic_speed: if self._optimistic_preset_mode:
self._preset_mode = preset_mode
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:
"""Set the speed of the fan.
This method is a coroutine.
"""
speed_payload = None
if self._feature_legacy_speeds:
if speed == SPEED_LOW:
speed_payload = self._payload["SPEED_LOW"]
elif speed == SPEED_MEDIUM:
speed_payload = self._payload["SPEED_MEDIUM"]
elif speed == SPEED_HIGH:
speed_payload = self._payload["SPEED_HIGH"]
elif speed == SPEED_OFF:
speed_payload = self._payload["SPEED_OFF"]
else:
_LOGGER.warning("'%s'is not a valid speed", speed)
return
if speed_payload:
mqtt.async_publish(
self.hass,
self._topic[CONF_SPEED_COMMAND_TOPIC],
speed_payload,
self._config[CONF_QOS],
self._config[CONF_RETAIN],
)
if self._optimistic_speed and speed_payload:
self._speed = speed self._speed = speed
self.async_write_ha_state() self.async_write_ha_state()

File diff suppressed because it is too large Load Diff