From 9b2a8901b17539d6b46b1c929d563686fb6d5794 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 7 Nov 2022 12:31:11 +0100 Subject: [PATCH] Adjust payload sentinel in mqtt (#81553) * Adjust payload sentinel in mqtt * Add type hints * Update sensor.py * Adjust vacuum * Add type hints * Adjust schema basic * Remove invalid hint --- .../components/mqtt/light/schema_basic.py | 53 +++++++++++-------- homeassistant/components/mqtt/models.py | 13 +++-- homeassistant/components/mqtt/sensor.py | 33 +++++++----- .../components/mqtt/vacuum/schema_legacy.py | 40 +++++++++----- 4 files changed, 87 insertions(+), 52 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index d435d4e91ad..bf2ae33ca1d 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -50,7 +50,12 @@ from ..const import ( ) from ..debug_info import log_messages from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity -from ..models import MqttCommandTemplate, MqttValueTemplate +from ..models import ( + MqttCommandTemplate, + MqttValueTemplate, + PayloadSentinel, + ReceiveMessage, +) from ..util import get_mqtt_data, valid_publish_topic, valid_subscribe_topic from .schema import MQTT_LIGHT_SCHEMA_SCHEMA @@ -450,12 +455,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @callback @log_messages(self.hass, self.entity_id) - def brightness_received(msg): + def brightness_received(msg: ReceiveMessage) -> None: """Handle new MQTT messages for the brightness.""" payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE]( - msg.payload, None + msg.payload, PayloadSentinel.DEFAULT ) - if not payload: + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug("Ignoring empty brightness message from '%s'", msg.topic) return @@ -468,8 +473,10 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): def _rgbx_received(msg, template, color_mode, convert_color): """Handle new MQTT messages for RGBW and RGBWW.""" - payload = self._value_templates[template](msg.payload, None) - if not payload: + payload = self._value_templates[template]( + msg.payload, PayloadSentinel.DEFAULT + ) + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug( "Ignoring empty %s message from '%s'", color_mode, msg.topic ) @@ -533,12 +540,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @callback @log_messages(self.hass, self.entity_id) - def color_mode_received(msg): + def color_mode_received(msg: ReceiveMessage) -> None: """Handle new MQTT messages for color mode.""" payload = self._value_templates[CONF_COLOR_MODE_VALUE_TEMPLATE]( - msg.payload, None + msg.payload, PayloadSentinel.DEFAULT ) - if not payload: + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug("Ignoring empty color mode message from '%s'", msg.topic) return @@ -549,12 +556,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @callback @log_messages(self.hass, self.entity_id) - def color_temp_received(msg): + def color_temp_received(msg: ReceiveMessage) -> None: """Handle new MQTT messages for color temperature.""" payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE]( - msg.payload, None + msg.payload, PayloadSentinel.DEFAULT ) - if not payload: + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug("Ignoring empty color temp message from '%s'", msg.topic) return @@ -567,12 +574,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @callback @log_messages(self.hass, self.entity_id) - def effect_received(msg): + def effect_received(msg: ReceiveMessage) -> None: """Handle new MQTT messages for effect.""" payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE]( - msg.payload, None + msg.payload, PayloadSentinel.DEFAULT ) - if not payload: + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug("Ignoring empty effect message from '%s'", msg.topic) return @@ -583,10 +590,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @callback @log_messages(self.hass, self.entity_id) - def hs_received(msg): + def hs_received(msg: ReceiveMessage) -> None: """Handle new MQTT messages for hs color.""" - payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](msg.payload, None) - if not payload: + payload = self._value_templates[CONF_HS_VALUE_TEMPLATE]( + msg.payload, PayloadSentinel.DEFAULT + ) + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic) return try: @@ -602,10 +611,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): @callback @log_messages(self.hass, self.entity_id) - def xy_received(msg): + def xy_received(msg: ReceiveMessage) -> None: """Handle new MQTT messages for xy color.""" - payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](msg.payload, None) - if not payload: + payload = self._value_templates[CONF_XY_VALUE_TEMPLATE]( + msg.payload, PayloadSentinel.DEFAULT + ) + if payload is PayloadSentinel.DEFAULT or not payload: _LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic) return diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index 363956cc732..a00097a6839 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Any, TypedDict, Union import attr +from homeassistant.backports.enum import StrEnum from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.helpers import template @@ -26,7 +27,13 @@ if TYPE_CHECKING: from .discovery import MQTTDiscoveryPayload from .tag import MQTTTagScanner -_SENTINEL = object() + +class PayloadSentinel(StrEnum): + """Sentinel for `async_render_with_possible_json_value`.""" + + NONE = "none" + DEFAULT = "default" + _LOGGER = logging.getLogger(__name__) @@ -189,7 +196,7 @@ class MqttValueTemplate: def async_render_with_possible_json_value( self, payload: ReceivePayloadType, - default: ReceivePayloadType | object = _SENTINEL, + default: ReceivePayloadType | PayloadSentinel = PayloadSentinel.NONE, variables: TemplateVarsType = None, ) -> ReceivePayloadType: """Render with possible json value or pass-though a received MQTT value.""" @@ -213,7 +220,7 @@ class MqttValueTemplate: ) values[ATTR_THIS] = self._template_state - if default == _SENTINEL: + if default is PayloadSentinel.NONE: _LOGGER.debug( "Rendering incoming payload '%s' with variables %s and %s", payload, diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 69077d30eee..4c6b5409962 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -45,7 +45,7 @@ from .mixins import ( async_setup_platform_helper, warn_for_legacy_schema, ) -from .models import MqttValueTemplate +from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage from .util import get_mqtt_data, valid_subscribe_topic _LOGGER = logging.getLogger(__name__) @@ -244,7 +244,7 @@ class MqttSensor(MqttEntity, RestoreSensor): """(Re)Subscribe to topics.""" topics = {} - def _update_state(msg): + def _update_state(msg: ReceiveMessage) -> None: # auto-expire enabled? expire_after = self._config.get(CONF_EXPIRE_AFTER) if expire_after is not None and expire_after > 0: @@ -262,20 +262,25 @@ class MqttSensor(MqttEntity, RestoreSensor): self.hass, self._value_is_expired, expiration_at ) - payload = self._template(msg.payload, default=self.native_value) - - if payload is not None and self.device_class in ( + payload = self._template(msg.payload, default=PayloadSentinel.DEFAULT) + if payload is PayloadSentinel.DEFAULT: + return + if self.device_class not in { SensorDeviceClass.DATE, SensorDeviceClass.TIMESTAMP, - ): - if (payload := dt_util.parse_datetime(payload)) is None: - _LOGGER.warning( - "Invalid state message '%s' from '%s'", msg.payload, msg.topic - ) - elif self.device_class == SensorDeviceClass.DATE: - payload = payload.date() - - self._attr_native_value = payload + }: + self._attr_native_value = str(payload) + return + if (payload_datetime := dt_util.parse_datetime(str(payload))) is None: + _LOGGER.warning( + "Invalid state message '%s' from '%s'", msg.payload, msg.topic + ) + self._attr_native_value = None + return + if self.device_class == SensorDeviceClass.DATE: + self._attr_native_value = payload_datetime.date() + return + self._attr_native_value = payload_datetime def _update_last_reset(msg): payload = self._last_reset_template(msg.payload) diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 09c4448fda7..a15367c3cad 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -19,7 +19,7 @@ from ..config import MQTT_BASE_SCHEMA from ..const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN from ..debug_info import log_messages from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, warn_for_legacy_schema -from ..models import MqttValueTemplate +from ..models import MqttValueTemplate, PayloadSentinel, ReceiveMessage from ..util import get_mqtt_data, valid_publish_topic from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services @@ -246,7 +246,7 @@ class MqttVacuum(MqttEntity, VacuumEntity): @callback @log_messages(self.hass, self.entity_id) - def message_received(msg): + def message_received(msg: ReceiveMessage) -> None: """Handle new MQTT message.""" if ( msg.topic == self._state_topics[CONF_BATTERY_LEVEL_TOPIC] @@ -254,8 +254,10 @@ class MqttVacuum(MqttEntity, VacuumEntity): ): battery_level = self._templates[ CONF_BATTERY_LEVEL_TEMPLATE - ].async_render_with_possible_json_value(msg.payload, None) - if battery_level: + ].async_render_with_possible_json_value( + msg.payload, PayloadSentinel.DEFAULT + ) + if battery_level and battery_level is not PayloadSentinel.DEFAULT: self._battery_level = int(battery_level) if ( @@ -264,8 +266,10 @@ class MqttVacuum(MqttEntity, VacuumEntity): ): charging = self._templates[ CONF_CHARGING_TEMPLATE - ].async_render_with_possible_json_value(msg.payload, None) - if charging: + ].async_render_with_possible_json_value( + msg.payload, PayloadSentinel.DEFAULT + ) + if charging and charging is not PayloadSentinel.DEFAULT: self._charging = cv.boolean(charging) if ( @@ -274,8 +278,10 @@ class MqttVacuum(MqttEntity, VacuumEntity): ): cleaning = self._templates[ CONF_CLEANING_TEMPLATE - ].async_render_with_possible_json_value(msg.payload, None) - if cleaning: + ].async_render_with_possible_json_value( + msg.payload, PayloadSentinel.DEFAULT + ) + if cleaning and cleaning is not PayloadSentinel.DEFAULT: self._cleaning = cv.boolean(cleaning) if ( @@ -284,8 +290,10 @@ class MqttVacuum(MqttEntity, VacuumEntity): ): docked = self._templates[ CONF_DOCKED_TEMPLATE - ].async_render_with_possible_json_value(msg.payload, None) - if docked: + ].async_render_with_possible_json_value( + msg.payload, PayloadSentinel.DEFAULT + ) + if docked and docked is not PayloadSentinel.DEFAULT: self._docked = cv.boolean(docked) if ( @@ -294,8 +302,10 @@ class MqttVacuum(MqttEntity, VacuumEntity): ): error = self._templates[ CONF_ERROR_TEMPLATE - ].async_render_with_possible_json_value(msg.payload, None) - if error is not None: + ].async_render_with_possible_json_value( + msg.payload, PayloadSentinel.DEFAULT + ) + if error is not PayloadSentinel.DEFAULT: self._error = cv.string(error) if self._docked: @@ -316,8 +326,10 @@ class MqttVacuum(MqttEntity, VacuumEntity): ): fan_speed = self._templates[ CONF_FAN_SPEED_TEMPLATE - ].async_render_with_possible_json_value(msg.payload, None) - if fan_speed: + ].async_render_with_possible_json_value( + msg.payload, PayloadSentinel.DEFAULT + ) + if fan_speed and fan_speed is not PayloadSentinel.DEFAULT: self._fan_speed = fan_speed get_mqtt_data(self.hass).state_write_requests.write_state_request(self)