Validate MQTT device tracker location data before assigning (#141980)

* Validate MQTT device tracker location data before assigning

* Log warning for invalid gps_accuracy
This commit is contained in:
Jan Bouwhuis 2025-04-10 22:32:17 +02:00 committed by GitHub
parent bf0d2e9bd2
commit ea38639395
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 116 additions and 5 deletions

View File

@ -28,7 +28,12 @@ from homeassistant.helpers.typing import ConfigType, VolSchemaType
from . import subscription
from .config import MQTT_BASE_SCHEMA
from .const import CONF_JSON_ATTRS_TOPIC, CONF_PAYLOAD_RESET, CONF_STATE_TOPIC
from .const import (
CONF_JSON_ATTRS_TEMPLATE,
CONF_JSON_ATTRS_TOPIC,
CONF_PAYLOAD_RESET,
CONF_STATE_TOPIC,
)
from .entity import MqttEntity, async_setup_entity_entry_helper
from .models import MqttValueTemplate, ReceiveMessage
from .schemas import MQTT_ENTITY_COMMON_SCHEMA
@ -151,16 +156,54 @@ class MqttDeviceTracker(MqttEntity, TrackerEntity):
self, extra_state_attributes: dict[str, Any]
) -> None:
"""Extract the location from the extra state attributes."""
self._attr_latitude = extra_state_attributes.get(ATTR_LATITUDE)
self._attr_longitude = extra_state_attributes.get(ATTR_LONGITUDE)
if (
ATTR_LATITUDE in extra_state_attributes
or ATTR_LONGITUDE in extra_state_attributes
):
# Reset manual set location
latitude: float | None
longitude: float | None
gps_accuracy: int
# Reset manually set location to allow automatic zone detection
self._attr_location_name = None
if isinstance(
latitude := extra_state_attributes.get(ATTR_LATITUDE), (int, float)
) and isinstance(
longitude := extra_state_attributes.get(ATTR_LONGITUDE), (int, float)
):
self._attr_latitude = latitude
self._attr_longitude = longitude
else:
# Invalid or incomplete coordinates, reset location
self._attr_latitude = None
self._attr_longitude = None
_LOGGER.warning(
"Extra state attributes received at % and template %s "
"contain invalid or incomplete location info. Got %s",
self._config.get(CONF_JSON_ATTRS_TEMPLATE),
self._config.get(CONF_JSON_ATTRS_TOPIC),
extra_state_attributes,
)
if ATTR_GPS_ACCURACY in extra_state_attributes:
if isinstance(
gps_accuracy := extra_state_attributes[ATTR_GPS_ACCURACY],
(int, float),
):
self._attr_location_accuracy = gps_accuracy
else:
_LOGGER.warning(
"Extra state attributes received at % and template %s "
"contain invalid GPS accuracy setting, "
"gps_accuracy was set to 0 as the default. Got %s",
self._config.get(CONF_JSON_ATTRS_TEMPLATE),
self._config.get(CONF_JSON_ATTRS_TOPIC),
extra_state_attributes,
)
self._attr_location_accuracy = 0
else:
self._attr_location_accuracy = 0
self._attr_location_accuracy = extra_state_attributes.get(ATTR_GPS_ACCURACY, 0)
self._attr_extra_state_attributes = {
attribute: value
for attribute, value in extra_state_attributes.items()

View File

@ -450,14 +450,82 @@ async def test_setting_device_tracker_location_via_lat_lon_message(
assert state.attributes["latitude"] == 50.1
assert state.attributes["longitude"] == -2.1
assert state.attributes["gps_accuracy"] == 0
assert state.attributes["source_type"] == "gps"
assert state.state == STATE_NOT_HOME
# incomplete coordinates results in unknown state
async_fire_mqtt_message(hass, "attributes-topic", '{"longitude": -117.22743}')
state = hass.states.get("device_tracker.test")
assert "latitude" not in state.attributes
assert "longitude" not in state.attributes
assert state.attributes["source_type"] == "gps"
assert state.state == STATE_UNKNOWN
async_fire_mqtt_message(hass, "attributes-topic", '{"latitude":32.87336}')
state = hass.states.get("device_tracker.test")
assert "latitude" not in state.attributes
assert "longitude" not in state.attributes
assert state.attributes["source_type"] == "gps"
assert state.state == STATE_UNKNOWN
# invalid coordinates results in unknown state
async_fire_mqtt_message(
hass, "attributes-topic", '{"longitude": -117.22743, "latitude":null}'
)
state = hass.states.get("device_tracker.test")
assert "latitude" not in state.attributes
assert "longitude" not in state.attributes
assert state.attributes["source_type"] == "gps"
assert state.state == STATE_UNKNOWN
# Test number validation
async_fire_mqtt_message(
hass,
"attributes-topic",
'{"latitude": "32.87336","longitude": "-117.22743", "gps_accuracy": "1.5", "source_type": "router"}',
)
state = hass.states.get("device_tracker.test")
assert "latitude" not in state.attributes
assert "longitude" not in state.attributes
assert "gps_accuracy" not in state.attributes
# assert source_type is overridden by discovery
assert state.attributes["source_type"] == "router"
assert state.state == STATE_UNKNOWN
# Test with invalid GPS accuracy should default to 0,
# but location updates as expected
async_fire_mqtt_message(
hass,
"attributes-topic",
'{"latitude": 32.871234,"longitude": -117.21234, "gps_accuracy": "invalid", "source_type": "router"}',
)
state = hass.states.get("device_tracker.test")
assert state.state == STATE_NOT_HOME
assert state.attributes["latitude"] == 32.871234
assert state.attributes["longitude"] == -117.21234
assert state.attributes["gps_accuracy"] == 0
assert state.attributes["source_type"] == "router"
# Test with invalid latitude
async_fire_mqtt_message(
hass,
"attributes-topic",
'{"latitude": null,"longitude": "-117.22743", "gps_accuracy": 1, "source_type": "router"}',
)
state = hass.states.get("device_tracker.test")
assert "latitude" not in state.attributes
assert "longitude" not in state.attributes
assert state.state == STATE_UNKNOWN
# Test with invalid longitude
async_fire_mqtt_message(
hass,
"attributes-topic",
'{"latitude": 32.87336,"longitude": "unknown", "gps_accuracy": 1, "source_type": "router"}',
)
state = hass.states.get("device_tracker.test")
assert "latitude" not in state.attributes
assert "longitude" not in state.attributes
assert state.state == STATE_UNKNOWN