diff --git a/homeassistant/components/mqtt/client.py b/homeassistant/components/mqtt/client.py index ad89a35ec0a..e717da5144c 100644 --- a/homeassistant/components/mqtt/client.py +++ b/homeassistant/components/mqtt/client.py @@ -719,7 +719,7 @@ class MQTT: timestamp, ), ) - self._mqtt_data.state_write_requests.process_write_state_requests() + self._mqtt_data.state_write_requests.process_write_state_requests(msg) def _mqtt_on_callback( self, diff --git a/homeassistant/components/mqtt/models.py b/homeassistant/components/mqtt/models.py index a88fb97b833..84735c55e08 100644 --- a/homeassistant/components/mqtt/models.py +++ b/homeassistant/components/mqtt/models.py @@ -21,6 +21,8 @@ from homeassistant.helpers.service_info.mqtt import ReceivePayloadType from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, TemplateVarsType if TYPE_CHECKING: + from paho.mqtt.client import MQTTMessage + from .client import MQTT, Subscription from .debug_info import TimestampedPublishMessage from .device_trigger import Trigger @@ -260,11 +262,21 @@ class EntityTopicState: self.subscribe_calls: dict[str, Entity] = {} @callback - def process_write_state_requests(self) -> None: + def process_write_state_requests(self, msg: MQTTMessage) -> None: """Process the write state requests.""" while self.subscribe_calls: _, entity = self.subscribe_calls.popitem() - entity.async_write_ha_state() + try: + entity.async_write_ha_state() + except Exception: # pylint: disable=broad-except + _LOGGER.error( + "Exception raised when updating state of %s, topic: " + "'%s' with payload: %s", + entity.entity_id, + msg.topic, + msg.payload, + exc_info=True, + ) @callback def write_state_request(self, entity: Entity) -> None: diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 47f8743f502..8c6ee21c932 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -817,6 +817,50 @@ def test_entity_device_info_schema() -> None: ) +@pytest.mark.parametrize( + "hass_config", + [ + { + mqtt.DOMAIN: { + "sensor": [ + { + "name": "test-sensor", + "unique_id": "test-sensor", + "state_topic": "test/state", + } + ] + } + } + ], +) +async def test_handle_logging_on_writing_the_entity_state( + hass: HomeAssistant, + mock_hass_config: None, + mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test on log handling when an error occurs writing the state.""" + await mqtt_mock_entry_no_yaml_config() + await hass.async_block_till_done() + async_fire_mqtt_message(hass, "test/state", b"initial_state") + await hass.async_block_till_done() + + state = hass.states.get("sensor.test_sensor") + assert state is not None + assert state.state == "initial_state" + with patch( + "homeassistant.helpers.entity.Entity.async_write_ha_state", + side_effect=ValueError("Invalid value for sensor"), + ): + async_fire_mqtt_message(hass, "test/state", b"payload causing errors") + await hass.async_block_till_done() + state = hass.states.get("sensor.test_sensor") + assert state is not None + assert state.state == "initial_state" + assert "Invalid value for sensor" in caplog.text + assert "Exception raised when updating state of" in caplog.text + + async def test_receiving_non_utf8_message_gets_logged( hass: HomeAssistant, mqtt_mock_entry_no_yaml_config: MqttMockHAClientGenerator, diff --git a/tests/components/mqtt/test_text.py b/tests/components/mqtt/test_text.py index 93b605d4a33..a5209e3f5fd 100644 --- a/tests/components/mqtt/test_text.py +++ b/tests/components/mqtt/test_text.py @@ -116,7 +116,9 @@ async def test_controlling_state_via_topic( async def test_controlling_validation_state_via_topic( - hass: HomeAssistant, mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator + hass: HomeAssistant, + mqtt_mock_entry_with_yaml_config: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, ) -> None: """Test the validation of a received state.""" assert await async_setup_component( @@ -148,26 +150,39 @@ async def test_controlling_validation_state_via_topic( assert state.state == "yes" # test pattern error - with pytest.raises(ValueError): - async_fire_mqtt_message(hass, "state-topic", "other") - await hass.async_block_till_done() + caplog.clear() + async_fire_mqtt_message(hass, "state-topic", "other") + await hass.async_block_till_done() + assert ( + "ValueError: Entity text.test provides state other which does not match expected pattern (y|n)" + in caplog.text + ) state = hass.states.get("text.test") assert state.state == "yes" # test text size to large - with pytest.raises(ValueError): - async_fire_mqtt_message(hass, "state-topic", "yesyesyesyes") - await hass.async_block_till_done() + caplog.clear() + async_fire_mqtt_message(hass, "state-topic", "yesyesyesyes") + await hass.async_block_till_done() + assert ( + "ValueError: Entity text.test provides state yesyesyesyes which is too long (maximum length 10)" + in caplog.text + ) state = hass.states.get("text.test") assert state.state == "yes" # test text size to small - with pytest.raises(ValueError): - async_fire_mqtt_message(hass, "state-topic", "y") - await hass.async_block_till_done() + caplog.clear() + async_fire_mqtt_message(hass, "state-topic", "y") + await hass.async_block_till_done() + assert ( + "ValueError: Entity text.test provides state y which is too short (minimum length 2)" + in caplog.text + ) state = hass.states.get("text.test") assert state.state == "yes" + # test with valid text async_fire_mqtt_message(hass, "state-topic", "no") await hass.async_block_till_done() state = hass.states.get("text.test")