Catch exceptions and add logging when writing states on MQTT entities (#89091)
* Catch exceptions when writing states * Do not use wrapper for logging and adjust tests * Catch logging directly on async_write_ha_state() * Update homeassistant/components/mqtt/models.py Co-authored-by: Erik Montnemery <erik@montnemery.com> * Fix test --------- Co-authored-by: Erik Montnemery <erik@montnemery.com>
This commit is contained in:
parent
0c042e8f72
commit
5ee383456f
4 changed files with 84 additions and 13 deletions
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Reference in a new issue