Skip to content

Commit

Permalink
Fix handling None or empty value for numeric MQTT sensor (home-assi…
Browse files Browse the repository at this point in the history
…stant#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
  • Loading branch information
jbouwh authored Feb 7, 2023
1 parent 42008c5 commit c78cae4
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 7 deletions.
20 changes: 13 additions & 7 deletions homeassistant/components/mqtt/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

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,
Expand Down Expand Up @@ -272,13 +272,19 @@ def _update_state(msg: ReceiveMessage) -> None:
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
)
Expand Down
55 changes: 55 additions & 0 deletions tests/components/mqtt/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down

0 comments on commit c78cae4

Please sign in to comment.