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
This commit is contained in:
epenet 2022-11-07 12:31:11 +01:00 committed by GitHub
parent d1fd141e8c
commit 9b2a8901b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 87 additions and 52 deletions

View File

@ -50,7 +50,12 @@ from ..const import (
) )
from ..debug_info import log_messages from ..debug_info import log_messages
from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity 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 ..util import get_mqtt_data, valid_publish_topic, valid_subscribe_topic
from .schema import MQTT_LIGHT_SCHEMA_SCHEMA from .schema import MQTT_LIGHT_SCHEMA_SCHEMA
@ -450,12 +455,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @log_messages(self.hass, self.entity_id)
def brightness_received(msg): def brightness_received(msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for the brightness.""" """Handle new MQTT messages for the brightness."""
payload = self._value_templates[CONF_BRIGHTNESS_VALUE_TEMPLATE]( 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) _LOGGER.debug("Ignoring empty brightness message from '%s'", msg.topic)
return return
@ -468,8 +473,10 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
def _rgbx_received(msg, template, color_mode, convert_color): def _rgbx_received(msg, template, color_mode, convert_color):
"""Handle new MQTT messages for RGBW and RGBWW.""" """Handle new MQTT messages for RGBW and RGBWW."""
payload = self._value_templates[template](msg.payload, None) payload = self._value_templates[template](
if not payload: msg.payload, PayloadSentinel.DEFAULT
)
if payload is PayloadSentinel.DEFAULT or not payload:
_LOGGER.debug( _LOGGER.debug(
"Ignoring empty %s message from '%s'", color_mode, msg.topic "Ignoring empty %s message from '%s'", color_mode, msg.topic
) )
@ -533,12 +540,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @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.""" """Handle new MQTT messages for color mode."""
payload = self._value_templates[CONF_COLOR_MODE_VALUE_TEMPLATE]( 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) _LOGGER.debug("Ignoring empty color mode message from '%s'", msg.topic)
return return
@ -549,12 +556,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @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.""" """Handle new MQTT messages for color temperature."""
payload = self._value_templates[CONF_COLOR_TEMP_VALUE_TEMPLATE]( 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) _LOGGER.debug("Ignoring empty color temp message from '%s'", msg.topic)
return return
@ -567,12 +574,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @log_messages(self.hass, self.entity_id)
def effect_received(msg): def effect_received(msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for effect.""" """Handle new MQTT messages for effect."""
payload = self._value_templates[CONF_EFFECT_VALUE_TEMPLATE]( 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) _LOGGER.debug("Ignoring empty effect message from '%s'", msg.topic)
return return
@ -583,10 +590,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @log_messages(self.hass, self.entity_id)
def hs_received(msg): def hs_received(msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for hs color.""" """Handle new MQTT messages for hs color."""
payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](msg.payload, None) payload = self._value_templates[CONF_HS_VALUE_TEMPLATE](
if not payload: msg.payload, PayloadSentinel.DEFAULT
)
if payload is PayloadSentinel.DEFAULT or not payload:
_LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic) _LOGGER.debug("Ignoring empty hs message from '%s'", msg.topic)
return return
try: try:
@ -602,10 +611,12 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @log_messages(self.hass, self.entity_id)
def xy_received(msg): def xy_received(msg: ReceiveMessage) -> None:
"""Handle new MQTT messages for xy color.""" """Handle new MQTT messages for xy color."""
payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](msg.payload, None) payload = self._value_templates[CONF_XY_VALUE_TEMPLATE](
if not payload: msg.payload, PayloadSentinel.DEFAULT
)
if payload is PayloadSentinel.DEFAULT or not payload:
_LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic) _LOGGER.debug("Ignoring empty xy-color message from '%s'", msg.topic)
return return

View File

@ -12,6 +12,7 @@ from typing import TYPE_CHECKING, Any, TypedDict, Union
import attr import attr
from homeassistant.backports.enum import StrEnum
from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME from homeassistant.const import ATTR_ENTITY_ID, ATTR_NAME
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers import template from homeassistant.helpers import template
@ -26,7 +27,13 @@ if TYPE_CHECKING:
from .discovery import MQTTDiscoveryPayload from .discovery import MQTTDiscoveryPayload
from .tag import MQTTTagScanner 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__) _LOGGER = logging.getLogger(__name__)
@ -189,7 +196,7 @@ class MqttValueTemplate:
def async_render_with_possible_json_value( def async_render_with_possible_json_value(
self, self,
payload: ReceivePayloadType, payload: ReceivePayloadType,
default: ReceivePayloadType | object = _SENTINEL, default: ReceivePayloadType | PayloadSentinel = PayloadSentinel.NONE,
variables: TemplateVarsType = None, variables: TemplateVarsType = None,
) -> ReceivePayloadType: ) -> ReceivePayloadType:
"""Render with possible json value or pass-though a received MQTT value.""" """Render with possible json value or pass-though a received MQTT value."""
@ -213,7 +220,7 @@ class MqttValueTemplate:
) )
values[ATTR_THIS] = self._template_state values[ATTR_THIS] = self._template_state
if default == _SENTINEL: if default is PayloadSentinel.NONE:
_LOGGER.debug( _LOGGER.debug(
"Rendering incoming payload '%s' with variables %s and %s", "Rendering incoming payload '%s' with variables %s and %s",
payload, payload,

View File

@ -45,7 +45,7 @@ from .mixins import (
async_setup_platform_helper, async_setup_platform_helper,
warn_for_legacy_schema, warn_for_legacy_schema,
) )
from .models import MqttValueTemplate from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage
from .util import get_mqtt_data, valid_subscribe_topic from .util import get_mqtt_data, valid_subscribe_topic
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -244,7 +244,7 @@ class MqttSensor(MqttEntity, RestoreSensor):
"""(Re)Subscribe to topics.""" """(Re)Subscribe to topics."""
topics = {} topics = {}
def _update_state(msg): def _update_state(msg: ReceiveMessage) -> None:
# auto-expire enabled? # auto-expire enabled?
expire_after = self._config.get(CONF_EXPIRE_AFTER) expire_after = self._config.get(CONF_EXPIRE_AFTER)
if expire_after is not None and expire_after > 0: 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 self.hass, self._value_is_expired, expiration_at
) )
payload = self._template(msg.payload, default=self.native_value) payload = self._template(msg.payload, default=PayloadSentinel.DEFAULT)
if payload is PayloadSentinel.DEFAULT:
if payload is not None and self.device_class in ( return
if self.device_class not in {
SensorDeviceClass.DATE, SensorDeviceClass.DATE,
SensorDeviceClass.TIMESTAMP, SensorDeviceClass.TIMESTAMP,
): }:
if (payload := dt_util.parse_datetime(payload)) is None: self._attr_native_value = str(payload)
_LOGGER.warning( return
"Invalid state message '%s' from '%s'", msg.payload, msg.topic if (payload_datetime := dt_util.parse_datetime(str(payload))) is None:
) _LOGGER.warning(
elif self.device_class == SensorDeviceClass.DATE: "Invalid state message '%s' from '%s'", msg.payload, msg.topic
payload = payload.date() )
self._attr_native_value = None
self._attr_native_value = payload 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): def _update_last_reset(msg):
payload = self._last_reset_template(msg.payload) payload = self._last_reset_template(msg.payload)

View File

@ -19,7 +19,7 @@ from ..config import MQTT_BASE_SCHEMA
from ..const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN from ..const import CONF_COMMAND_TOPIC, CONF_ENCODING, CONF_QOS, CONF_RETAIN
from ..debug_info import log_messages from ..debug_info import log_messages
from ..mixins import MQTT_ENTITY_COMMON_SCHEMA, MqttEntity, warn_for_legacy_schema 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 ..util import get_mqtt_data, valid_publish_topic
from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED from .const import MQTT_VACUUM_ATTRIBUTES_BLOCKED
from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services
@ -246,7 +246,7 @@ class MqttVacuum(MqttEntity, VacuumEntity):
@callback @callback
@log_messages(self.hass, self.entity_id) @log_messages(self.hass, self.entity_id)
def message_received(msg): def message_received(msg: ReceiveMessage) -> None:
"""Handle new MQTT message.""" """Handle new MQTT message."""
if ( if (
msg.topic == self._state_topics[CONF_BATTERY_LEVEL_TOPIC] msg.topic == self._state_topics[CONF_BATTERY_LEVEL_TOPIC]
@ -254,8 +254,10 @@ class MqttVacuum(MqttEntity, VacuumEntity):
): ):
battery_level = self._templates[ battery_level = self._templates[
CONF_BATTERY_LEVEL_TEMPLATE CONF_BATTERY_LEVEL_TEMPLATE
].async_render_with_possible_json_value(msg.payload, None) ].async_render_with_possible_json_value(
if battery_level: msg.payload, PayloadSentinel.DEFAULT
)
if battery_level and battery_level is not PayloadSentinel.DEFAULT:
self._battery_level = int(battery_level) self._battery_level = int(battery_level)
if ( if (
@ -264,8 +266,10 @@ class MqttVacuum(MqttEntity, VacuumEntity):
): ):
charging = self._templates[ charging = self._templates[
CONF_CHARGING_TEMPLATE CONF_CHARGING_TEMPLATE
].async_render_with_possible_json_value(msg.payload, None) ].async_render_with_possible_json_value(
if charging: msg.payload, PayloadSentinel.DEFAULT
)
if charging and charging is not PayloadSentinel.DEFAULT:
self._charging = cv.boolean(charging) self._charging = cv.boolean(charging)
if ( if (
@ -274,8 +278,10 @@ class MqttVacuum(MqttEntity, VacuumEntity):
): ):
cleaning = self._templates[ cleaning = self._templates[
CONF_CLEANING_TEMPLATE CONF_CLEANING_TEMPLATE
].async_render_with_possible_json_value(msg.payload, None) ].async_render_with_possible_json_value(
if cleaning: msg.payload, PayloadSentinel.DEFAULT
)
if cleaning and cleaning is not PayloadSentinel.DEFAULT:
self._cleaning = cv.boolean(cleaning) self._cleaning = cv.boolean(cleaning)
if ( if (
@ -284,8 +290,10 @@ class MqttVacuum(MqttEntity, VacuumEntity):
): ):
docked = self._templates[ docked = self._templates[
CONF_DOCKED_TEMPLATE CONF_DOCKED_TEMPLATE
].async_render_with_possible_json_value(msg.payload, None) ].async_render_with_possible_json_value(
if docked: msg.payload, PayloadSentinel.DEFAULT
)
if docked and docked is not PayloadSentinel.DEFAULT:
self._docked = cv.boolean(docked) self._docked = cv.boolean(docked)
if ( if (
@ -294,8 +302,10 @@ class MqttVacuum(MqttEntity, VacuumEntity):
): ):
error = self._templates[ error = self._templates[
CONF_ERROR_TEMPLATE CONF_ERROR_TEMPLATE
].async_render_with_possible_json_value(msg.payload, None) ].async_render_with_possible_json_value(
if error is not None: msg.payload, PayloadSentinel.DEFAULT
)
if error is not PayloadSentinel.DEFAULT:
self._error = cv.string(error) self._error = cv.string(error)
if self._docked: if self._docked:
@ -316,8 +326,10 @@ class MqttVacuum(MqttEntity, VacuumEntity):
): ):
fan_speed = self._templates[ fan_speed = self._templates[
CONF_FAN_SPEED_TEMPLATE CONF_FAN_SPEED_TEMPLATE
].async_render_with_possible_json_value(msg.payload, None) ].async_render_with_possible_json_value(
if fan_speed: msg.payload, PayloadSentinel.DEFAULT
)
if fan_speed and fan_speed is not PayloadSentinel.DEFAULT:
self._fan_speed = fan_speed self._fan_speed = fan_speed
get_mqtt_data(self.hass).state_write_requests.write_state_request(self) get_mqtt_data(self.hass).state_write_requests.write_state_request(self)