From c78cae44836d3edad11f98eb4a4e75602723b4a1 Mon Sep 17 00:00:00 2001 From: Jan Bouwhuis Date: Tue, 7 Feb 2023 11:23:23 +0100 Subject: [PATCH] Fix handling `None` or empty value for numeric MQTT sensor (#87004) * Allow `None` for numeric sensor, ignore empty val * Add test case with omitting a value * Use _numeric_state_expected property * Only respect None if numeric state is expected --- homeassistant/components/mqtt/sensor.py | 20 +++++---- tests/components/mqtt/test_sensor.py | 55 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index 1a8c59ff86e..df51dd60a15 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -38,7 +38,7 @@ from homeassistant.util import dt as dt_util from . import subscription from .config import MQTT_RO_SCHEMA -from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC +from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, PAYLOAD_NONE from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, @@ -272,13 +272,19 @@ class MqttSensor(MqttEntity, RestoreSensor): payload = self._template(msg.payload, PayloadSentinel.DEFAULT) if payload is PayloadSentinel.DEFAULT: return - if self.device_class not in { - SensorDeviceClass.DATE, - SensorDeviceClass.TIMESTAMP, - }: - self._attr_native_value = str(payload) + new_value = str(payload) + if self._numeric_state_expected: + if new_value == "": + _LOGGER.debug("Ignore empty state from '%s'", msg.topic) + elif new_value == PAYLOAD_NONE: + self._attr_native_value = None + else: + self._attr_native_value = new_value return - if (payload_datetime := dt_util.parse_datetime(str(payload))) is None: + if self.device_class is None: + self._attr_native_value = new_value + return + if (payload_datetime := dt_util.parse_datetime(new_value)) is None: _LOGGER.warning( "Invalid state message '%s' from '%s'", msg.payload, msg.topic ) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index ad32773ac5d..fcdbd2ce687 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -169,6 +169,61 @@ async def test_setting_sensor_native_value_handling_via_mqtt_message( assert log == ("Invalid state message" in caplog.text) +async def test_setting_numeric_sensor_native_value_handling_via_mqtt_message( + hass: ha.HomeAssistant, + mqtt_mock_entry_with_yaml_config, +) -> None: + """Test the setting of a numeric sensor value via MQTT.""" + assert await async_setup_component( + hass, + mqtt.DOMAIN, + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": "test", + "state_topic": "test-topic", + "value_template": "{{ value_json.power }}", + "device_class": "power", + "unit_of_measurement": "W", + } + } + }, + ) + await hass.async_block_till_done() + await mqtt_mock_entry_with_yaml_config() + + # float value + async_fire_mqtt_message(hass, "test-topic", '{ "power": 45.3, "current": 5.24 }') + state = hass.states.get("sensor.test") + assert state.attributes.get("device_class") == "power" + assert state.state == "45.3" + + # null value, native value should be None + async_fire_mqtt_message(hass, "test-topic", '{ "power": null, "current": 5.34 }') + state = hass.states.get("sensor.test") + assert state.state == "unknown" + + # int value + async_fire_mqtt_message(hass, "test-topic", '{ "power": 20, "current": 5.34 }') + state = hass.states.get("sensor.test") + assert state.state == "20" + + # int value + async_fire_mqtt_message(hass, "test-topic", '{ "power": "21", "current": 5.34 }') + state = hass.states.get("sensor.test") + assert state.state == "21" + + # ignore empty value, native sensor value should not change + async_fire_mqtt_message(hass, "test-topic", '{ "power": "", "current": 5.34 }') + state = hass.states.get("sensor.test") + assert state.state == "21" + + # omitting value, causing it to be ignored, native sensor value should not change (template warning will be logged though) + async_fire_mqtt_message(hass, "test-topic", '{ "current": 5.34 }') + state = hass.states.get("sensor.test") + assert state.state == "21" + + async def test_setting_sensor_value_expires_availability_topic( hass, mqtt_mock_entry_with_yaml_config, caplog ):