From 83cf59e6a8ff9a7f6d5b1ef9e7c503f2b0f105ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 20 Mar 2024 15:40:46 +0100 Subject: [PATCH] Provide better debug capabilities for the Traccar Server integration (#113868) --- .../components/traccar_server/coordinator.py | 37 +++++++-- .../components/traccar_server/diagnostics.py | 20 ++++- .../snapshots/test_diagnostics.ambr | 80 +++++++++++++++++++ .../traccar_server/test_diagnostics.py | 41 +++++++++- 4 files changed, 167 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/traccar_server/coordinator.py b/homeassistant/components/traccar_server/coordinator.py index 8afa245f1e8..3d44b1ecede 100644 --- a/homeassistant/components/traccar_server/coordinator.py +++ b/homeassistant/components/traccar_server/coordinator.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio from datetime import datetime +from logging import DEBUG as LOG_LEVEL_DEBUG from typing import TYPE_CHECKING, Any, TypedDict from pytraccar import ( @@ -92,8 +93,18 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat self._geofences = geofences + if self.logger.isEnabledFor(LOG_LEVEL_DEBUG): + self.logger.debug("Received devices: %s", devices) + self.logger.debug("Received positions: %s", positions) + for position in positions: - if (device := get_device(position["deviceId"], devices)) is None: + device_id = position["deviceId"] + if (device := get_device(device_id, devices)) is None: + self.logger.debug( + "Device %s not found for position: %s", + device_id, + position["id"], + ) continue if ( @@ -102,9 +113,14 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat device, position ) ) is None: + self.logger.debug( + "Skipping position update %s for %s due to accuracy filter", + position["id"], + device_id, + ) continue - data[device["id"]] = { + data[device_id] = { "device": device, "geofence": get_first_geofence( geofences, @@ -122,8 +138,8 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat self._should_log_subscription_error = True update_devices = set() for device in data.get("devices") or []: - device_id = device["id"] - if device_id not in self.data: + if (device_id := device["id"]) not in self.data: + self.logger.debug("Device %s not found in data", device_id) continue if ( @@ -139,8 +155,12 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat update_devices.add(device_id) for position in data.get("positions") or []: - device_id = position["deviceId"] - if device_id not in self.data: + if (device_id := position["deviceId"]) not in self.data: + self.logger.debug( + "Device %s for position %s not found in data", + device_id, + position["id"], + ) continue if ( @@ -149,6 +169,11 @@ class TraccarServerCoordinator(DataUpdateCoordinator[TraccarServerCoordinatorDat self.data[device_id]["device"], position ) ) is None: + self.logger.debug( + "Skipping position update %s for %s due to accuracy filter", + position["id"], + device_id, + ) continue self.data[device_id]["position"] = position diff --git a/homeassistant/components/traccar_server/diagnostics.py b/homeassistant/components/traccar_server/diagnostics.py index 8e9e6490e44..ea861a9bffa 100644 --- a/homeassistant/components/traccar_server/diagnostics.py +++ b/homeassistant/components/traccar_server/diagnostics.py @@ -21,6 +21,20 @@ TO_REDACT = { } +def _entity_state( + hass: HomeAssistant, + entity: er.RegistryEntry, +) -> dict[str, Any] | None: + return ( + { + "state": state.state, + "attributes": state.attributes, + } + if (state := hass.states.get(entity.entity_id)) + else None + ) + + async def async_get_config_entry_diagnostics( hass: HomeAssistant, config_entry: ConfigEntry, @@ -43,10 +57,9 @@ async def async_get_config_entry_diagnostics( { "enity_id": entity.entity_id, "disabled": entity.disabled, - "state": {"state": state.state, "attributes": state.attributes}, + "state": _entity_state(hass, entity), } for entity in entities - if (state := hass.states.get(entity.entity_id)) is not None ], }, TO_REDACT, @@ -77,10 +90,9 @@ async def async_get_device_diagnostics( { "enity_id": entity.entity_id, "disabled": entity.disabled, - "state": {"state": state.state, "attributes": state.attributes}, + "state": _entity_state(hass, entity), } for entity in entities - if (state := hass.states.get(entity.entity_id)) is not None ], }, TO_REDACT, diff --git a/tests/components/traccar_server/snapshots/test_diagnostics.ambr b/tests/components/traccar_server/snapshots/test_diagnostics.ambr index 20d01e427ea..f8fe3cc60f7 100644 --- a/tests/components/traccar_server/snapshots/test_diagnostics.ambr +++ b/tests/components/traccar_server/snapshots/test_diagnostics.ambr @@ -99,6 +99,86 @@ 'subscription_status': 'disconnected', }) # --- +# name: test_device_diagnostics_with_disabled_entity[X-Wing] + dict({ + 'config_entry_options': dict({ + 'custom_attributes': list([ + 'custom_attr_1', + ]), + 'events': list([ + 'device_moving', + ]), + 'max_accuracy': 5.0, + 'skip_accuracy_filter_for': list([ + ]), + }), + 'coordinator_data': dict({ + '0': dict({ + 'attributes': dict({ + 'custom_attr_1': 'custom_attr_1_value', + }), + 'device': dict({ + 'attributes': dict({ + }), + 'category': 'starfighter', + 'contact': None, + 'disabled': False, + 'groupId': 0, + 'id': 0, + 'lastUpdate': '1970-01-01T00:00:00Z', + 'model': '1337', + 'name': 'X-Wing', + 'phone': None, + 'positionId': 0, + 'status': 'unknown', + 'uniqueId': 'abc123', + }), + 'geofence': dict({ + 'area': '**REDACTED**', + 'attributes': dict({ + }), + 'calendarId': 0, + 'description': "A harsh desert world orbiting twin suns in the galaxy's Outer Rim", + 'id': 0, + 'name': 'Tatooine', + }), + 'position': dict({ + 'accuracy': 3.5, + 'address': '**REDACTED**', + 'altitude': 546841384638, + 'attributes': dict({ + 'custom_attr_1': 'custom_attr_1_value', + }), + 'course': 360, + 'deviceId': 0, + 'deviceTime': '1970-01-01T00:00:00Z', + 'fixTime': '1970-01-01T00:00:00Z', + 'geofenceIds': list([ + 0, + ]), + 'id': 0, + 'latitude': '**REDACTED**', + 'longitude': '**REDACTED**', + 'network': dict({ + }), + 'outdated': True, + 'protocol': 'C-3PO', + 'serverTime': '1970-01-01T00:00:00Z', + 'speed': 4568795, + 'valid': True, + }), + }), + }), + 'entities': list([ + dict({ + 'disabled': True, + 'enity_id': 'device_tracker.x_wing', + 'state': None, + }), + ]), + 'subscription_status': 'disconnected', + }) +# --- # name: test_entry_diagnostics[entry] dict({ 'config_entry_options': dict({ diff --git a/tests/components/traccar_server/test_diagnostics.py b/tests/components/traccar_server/test_diagnostics.py index 38c35178b6f..3d112057315 100644 --- a/tests/components/traccar_server/test_diagnostics.py +++ b/tests/components/traccar_server/test_diagnostics.py @@ -6,7 +6,7 @@ from unittest.mock import AsyncMock from syrupy import SnapshotAssertion from homeassistant.core import HomeAssistant -from homeassistant.helpers import device_registry as dr +from homeassistant.helpers import device_registry as dr, entity_registry as er from .common import setup_integration @@ -63,3 +63,42 @@ async def test_device_diagnostics( ) assert result == snapshot(name=device.name) + + +async def test_device_diagnostics_with_disabled_entity( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_traccar_api_client: Generator[AsyncMock, None, None], + mock_config_entry: MockConfigEntry, + snapshot: SnapshotAssertion, + device_registry: dr.DeviceRegistry, + entity_registry: er.EntityRegistry, +) -> None: + """Test device diagnostics with disabled entity.""" + await setup_integration(hass, mock_config_entry) + + devices = dr.async_entries_for_config_entry( + hass.helpers.device_registry.async_get(hass), + mock_config_entry.entry_id, + ) + + assert len(devices) == 1 + + for device in dr.async_entries_for_config_entry( + hass.helpers.device_registry.async_get(hass), mock_config_entry.entry_id + ): + for entry in er.async_entries_for_device( + entity_registry, + device.id, + include_disabled_entities=True, + ): + entity_registry.async_update_entity( + entry.entity_id, + disabled_by=er.RegistryEntryDisabler.USER, + ) + + result = await get_diagnostics_for_device( + hass, hass_client, mock_config_entry, device=device + ) + + assert result == snapshot(name=device.name)