Improve error handling and logging on MQTT update entity state updates when template rederings fails (#141960)

This commit is contained in:
Jan Bouwhuis 2025-04-01 19:22:32 +02:00 committed by Franck Nijhof
parent 2b9c903429
commit fa3832fbd7
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
2 changed files with 79 additions and 2 deletions

View File

@ -26,7 +26,7 @@ from . import subscription
from .config import DEFAULT_RETAIN, MQTT_RO_SCHEMA from .config import DEFAULT_RETAIN, MQTT_RO_SCHEMA
from .const import CONF_COMMAND_TOPIC, CONF_RETAIN, CONF_STATE_TOPIC, PAYLOAD_EMPTY_JSON from .const import CONF_COMMAND_TOPIC, CONF_RETAIN, CONF_STATE_TOPIC, PAYLOAD_EMPTY_JSON
from .entity import MqttEntity, async_setup_entity_entry_helper from .entity import MqttEntity, async_setup_entity_entry_helper
from .models import MqttValueTemplate, ReceiveMessage from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage
from .schemas import MQTT_ENTITY_COMMON_SCHEMA from .schemas import MQTT_ENTITY_COMMON_SCHEMA
from .util import valid_publish_topic, valid_subscribe_topic from .util import valid_publish_topic, valid_subscribe_topic
@ -136,7 +136,18 @@ class MqttUpdate(MqttEntity, UpdateEntity, RestoreEntity):
@callback @callback
def _handle_state_message_received(self, msg: ReceiveMessage) -> None: def _handle_state_message_received(self, msg: ReceiveMessage) -> None:
"""Handle receiving state message via MQTT.""" """Handle receiving state message via MQTT."""
payload = self._templates[CONF_VALUE_TEMPLATE](msg.payload) payload = self._templates[CONF_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
)
if payload is PayloadSentinel.DEFAULT:
_LOGGER.warning(
"Unable to process payload '%s' for topic %s, with value template '%s'",
msg.payload,
msg.topic,
self._config.get(CONF_VALUE_TEMPLATE),
)
return
if not payload or payload == PAYLOAD_EMPTY_JSON: if not payload or payload == PAYLOAD_EMPTY_JSON:
_LOGGER.debug( _LOGGER.debug(

View File

@ -1,6 +1,7 @@
"""The tests for mqtt update component.""" """The tests for mqtt update component."""
import json import json
from typing import Any
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
@ -225,6 +226,71 @@ async def test_value_template(
assert state.attributes.get("latest_version") == "2.0.0" assert state.attributes.get("latest_version") == "2.0.0"
@pytest.mark.parametrize(
"hass_config",
[
{
mqtt.DOMAIN: {
update.DOMAIN: {
"state_topic": "test/update",
"value_template": (
"{\"latest_version\":\"{{ value_json['update']['latest_version'] }}\","
"\"installed_version\":\"{{ value_json['update']['installed_version'] }}\","
"\"update_percentage\":{{ value_json['update'].get('progress', 'null') }}}"
),
"name": "Test Update",
}
}
}
],
)
async def test_errornous_value_template(
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test that it fetches the given payload with a template or handles the exception."""
state_topic = "test/update"
await mqtt_mock_entry()
# Simulate a template redendering error with payload
# without "update" mapping
example_payload: dict[str, Any] = {
"child_lock": "UNLOCK",
"current": 0.02,
"energy": 212.92,
"indicator_mode": "off/on",
"linkquality": 65,
"power": 0,
"power_outage_memory": "off",
"state": "ON",
"voltage": 232,
}
async_fire_mqtt_message(hass, state_topic, json.dumps(example_payload))
await hass.async_block_till_done()
assert hass.states.get("update.test_update") is not None
assert "Unable to process payload '" in caplog.text
# Add update info
example_payload["update"] = {
"latest_version": "2.0.0",
"installed_version": "1.9.0",
"progress": 20,
}
async_fire_mqtt_message(hass, state_topic, json.dumps(example_payload))
await hass.async_block_till_done()
state = hass.states.get("update.test_update")
assert state is not None
assert state.state == STATE_ON
assert state.attributes.get("installed_version") == "1.9.0"
assert state.attributes.get("latest_version") == "2.0.0"
assert state.attributes.get("update_percentage") == 20
@pytest.mark.parametrize( @pytest.mark.parametrize(
"hass_config", "hass_config",
[ [