Bump pytraccar to 1.0.0 (#75671)

This commit is contained in:
Joakim Sørensen 2022-07-26 00:45:01 +02:00 committed by GitHub
parent ea354f3d5f
commit e87c2b9e25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 103 deletions

View file

@ -1,11 +1,19 @@
"""Support for Traccar device tracking.""" """Support for Traccar device tracking."""
from __future__ import annotations from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from pytraccar.api import API from pytraccar import (
ApiClient,
DeviceModel,
GeofenceModel,
PositionModel,
TraccarAuthenticationException,
TraccarException,
)
from stringcase import camelcase from stringcase import camelcase
import voluptuous as vol import voluptuous as vol
@ -170,17 +178,13 @@ async def async_setup_scanner(
discovery_info: DiscoveryInfoType | None = None, discovery_info: DiscoveryInfoType | None = None,
) -> bool: ) -> bool:
"""Validate the configuration and return a Traccar scanner.""" """Validate the configuration and return a Traccar scanner."""
api = ApiClient(
session = async_get_clientsession(hass, config[CONF_VERIFY_SSL]) host=config[CONF_HOST],
port=config[CONF_PORT],
api = API( ssl=config[CONF_SSL],
hass.loop, username=config[CONF_USERNAME],
session, password=config[CONF_PASSWORD],
config[CONF_USERNAME], client_session=async_get_clientsession(hass, config[CONF_VERIFY_SSL]),
config[CONF_PASSWORD],
config[CONF_HOST],
config[CONF_PORT],
config[CONF_SSL],
) )
scanner = TraccarScanner( scanner = TraccarScanner(
@ -202,15 +206,15 @@ class TraccarScanner:
def __init__( def __init__(
self, self,
api, api: ApiClient,
hass, hass: HomeAssistant,
async_see, async_see: Callable[..., Awaitable[None]],
scan_interval, scan_interval: timedelta,
max_accuracy, max_accuracy: int,
skip_accuracy_on, skip_accuracy_on: bool,
custom_attributes, custom_attributes: list[str],
event_types, event_types: list[str],
): ) -> None:
"""Initialize.""" """Initialize."""
if EVENT_ALL_EVENTS in event_types: if EVENT_ALL_EVENTS in event_types:
@ -220,15 +224,18 @@ class TraccarScanner:
self._scan_interval = scan_interval self._scan_interval = scan_interval
self._async_see = async_see self._async_see = async_see
self._api = api self._api = api
self.connected = False
self._hass = hass self._hass = hass
self._max_accuracy = max_accuracy self._max_accuracy = max_accuracy
self._skip_accuracy_on = skip_accuracy_on self._skip_accuracy_on = skip_accuracy_on
self._devices: list[DeviceModel] = []
self._positions: list[PositionModel] = []
self._geofences: list[GeofenceModel] = []
async def async_init(self): async def async_init(self):
"""Further initialize connection to Traccar.""" """Further initialize connection to Traccar."""
await self._api.test_connection() try:
if self._api.connected and not self._api.authenticated: await self._api.get_server()
except TraccarAuthenticationException:
_LOGGER.error("Authentication for Traccar failed") _LOGGER.error("Authentication for Traccar failed")
return False return False
@ -238,57 +245,63 @@ class TraccarScanner:
async def _async_update(self, now=None): async def _async_update(self, now=None):
"""Update info from Traccar.""" """Update info from Traccar."""
if not self.connected:
_LOGGER.debug("Testing connection to Traccar")
await self._api.test_connection()
self.connected = self._api.connected
if self.connected:
_LOGGER.info("Connection to Traccar restored")
else:
return
_LOGGER.debug("Updating device data") _LOGGER.debug("Updating device data")
await self._api.get_device_info(self._custom_attributes) try:
(self._devices, self._positions, self._geofences,) = await asyncio.gather(
self._api.get_devices(),
self._api.get_positions(),
self._api.get_geofences(),
)
except TraccarException as ex:
_LOGGER.error("Error while updating device data: %s", ex)
return
self._hass.async_create_task(self.import_device_data()) self._hass.async_create_task(self.import_device_data())
if self._event_types: if self._event_types:
self._hass.async_create_task(self.import_events()) self._hass.async_create_task(self.import_events())
self.connected = self._api.connected
async def import_device_data(self): async def import_device_data(self):
"""Import device data from Traccar.""" """Import device data from Traccar."""
for device_unique_id in self._api.device_info: for position in self._positions:
device_info = self._api.device_info[device_unique_id] device = next(
device = None (dev for dev in self._devices if dev.id == position.device_id), None
attr = {} )
if not device:
continue
attr = {
ATTR_TRACKER: "traccar",
ATTR_ADDRESS: position.address,
ATTR_SPEED: position.speed,
ATTR_ALTITUDE: position.altitude,
ATTR_MOTION: position.attributes.get("motion", False),
ATTR_TRACCAR_ID: device.id,
ATTR_GEOFENCE: next(
(
geofence.name
for geofence in self._geofences
if geofence.id in (device.geofence_ids or [])
),
None,
),
ATTR_CATEGORY: device.category,
ATTR_STATUS: device.status,
}
skip_accuracy_filter = False skip_accuracy_filter = False
attr[ATTR_TRACKER] = "traccar"
if device_info.get("address") is not None:
attr[ATTR_ADDRESS] = device_info["address"]
if device_info.get("geofence") is not None:
attr[ATTR_GEOFENCE] = device_info["geofence"]
if device_info.get("category") is not None:
attr[ATTR_CATEGORY] = device_info["category"]
if device_info.get("speed") is not None:
attr[ATTR_SPEED] = device_info["speed"]
if device_info.get("motion") is not None:
attr[ATTR_MOTION] = device_info["motion"]
if device_info.get("traccar_id") is not None:
attr[ATTR_TRACCAR_ID] = device_info["traccar_id"]
for dev in self._api.devices:
if dev["id"] == device_info["traccar_id"]:
device = dev
break
if device is not None and device.get("status") is not None:
attr[ATTR_STATUS] = device["status"]
for custom_attr in self._custom_attributes: for custom_attr in self._custom_attributes:
if device_info.get(custom_attr) is not None: if device.attributes.get(custom_attr) is not None:
attr[custom_attr] = device_info[custom_attr] attr[custom_attr] = position.attributes[custom_attr]
if custom_attr in self._skip_accuracy_on:
skip_accuracy_filter = True
if position.attributes.get(custom_attr) is not None:
attr[custom_attr] = position.attributes[custom_attr]
if custom_attr in self._skip_accuracy_on: if custom_attr in self._skip_accuracy_on:
skip_accuracy_filter = True skip_accuracy_filter = True
accuracy = 0.0 accuracy = position.accuracy or 0.0
if device_info.get("accuracy") is not None:
accuracy = device_info["accuracy"]
if ( if (
not skip_accuracy_filter not skip_accuracy_filter
and self._max_accuracy > 0 and self._max_accuracy > 0
@ -302,42 +315,39 @@ class TraccarScanner:
continue continue
await self._async_see( await self._async_see(
dev_id=slugify(device_info["device_id"]), dev_id=slugify(device.name),
gps=(device_info.get("latitude"), device_info.get("longitude")), gps=(position.latitude, position.longitude),
gps_accuracy=accuracy, gps_accuracy=accuracy,
battery=device_info.get("battery"), battery=position.attributes.get("batteryLevel", -1),
attributes=attr, attributes=attr,
) )
async def import_events(self): async def import_events(self):
"""Import events from Traccar.""" """Import events from Traccar."""
device_ids = [device["id"] for device in self._api.devices] start_intervel = datetime.utcnow()
end_interval = datetime.utcnow() events = await self._api.get_reports_events(
start_interval = end_interval - self._scan_interval devices=[device.id for device in self._devices],
events = await self._api.get_events( start_time=start_intervel,
device_ids=device_ids, end_time=start_intervel - self._scan_interval,
from_time=start_interval,
to_time=end_interval,
event_types=self._event_types.keys(), event_types=self._event_types.keys(),
) )
if events is not None: if events is not None:
for event in events: for event in events:
device_name = next( self._hass.bus.async_fire(
f"traccar_{self._event_types.get(event.type)}",
{
"device_traccar_id": event.device_id,
"device_name": next(
( (
dev.get("name") dev.name
for dev in self._api.devices for dev in self._devices
if dev.get("id") == event["deviceId"] if dev.id == event.device_id
), ),
None, None,
) ),
self._hass.bus.async_fire( "type": event.type,
f"traccar_{self._event_types.get(event['type'])}", "serverTime": event.event_time,
{ "attributes": event.attributes,
"device_traccar_id": event["deviceId"],
"device_name": device_name,
"type": event["type"],
"serverTime": event.get("eventTime") or event.get("serverTime"),
"attributes": event["attributes"],
}, },
) )

View file

@ -3,7 +3,7 @@
"name": "Traccar", "name": "Traccar",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/traccar", "documentation": "https://www.home-assistant.io/integrations/traccar",
"requirements": ["pytraccar==0.10.0", "stringcase==1.2.0"], "requirements": ["pytraccar==1.0.0", "stringcase==1.2.0"],
"dependencies": ["webhook"], "dependencies": ["webhook"],
"codeowners": ["@ludeeus"], "codeowners": ["@ludeeus"],
"iot_class": "local_polling", "iot_class": "local_polling",

View file

@ -1996,7 +1996,7 @@ pytomorrowio==0.3.4
pytouchline==0.7 pytouchline==0.7
# homeassistant.components.traccar # homeassistant.components.traccar
pytraccar==0.10.0 pytraccar==1.0.0
# homeassistant.components.tradfri # homeassistant.components.tradfri
pytradfri[async]==9.0.0 pytradfri[async]==9.0.0

View file

@ -1340,7 +1340,7 @@ pytile==2022.02.0
pytomorrowio==0.3.4 pytomorrowio==0.3.4
# homeassistant.components.traccar # homeassistant.components.traccar
pytraccar==0.10.0 pytraccar==1.0.0
# homeassistant.components.tradfri # homeassistant.components.tradfri
pytradfri[async]==9.0.0 pytradfri[async]==9.0.0

View file

@ -2,6 +2,8 @@
from datetime import datetime from datetime import datetime
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
from pytraccar import ReportsEventeModel
from homeassistant.components.device_tracker.const import DOMAIN from homeassistant.components.device_tracker.const import DOMAIN
from homeassistant.components.traccar.device_tracker import ( from homeassistant.components.traccar.device_tracker import (
PLATFORM_SCHEMA as TRACCAR_PLATFORM_SCHEMA, PLATFORM_SCHEMA as TRACCAR_PLATFORM_SCHEMA,
@ -35,26 +37,39 @@ async def test_import_events_catch_all(hass):
device = {"id": 1, "name": "abc123"} device = {"id": 1, "name": "abc123"}
api_mock = AsyncMock() api_mock = AsyncMock()
api_mock.devices = [device] api_mock.devices = [device]
api_mock.get_events.return_value = [ api_mock.get_reports_events.return_value = [
{ ReportsEventeModel(
**{
"id": 1,
"positionId": 1,
"geofenceId": 1,
"maintenanceId": 1,
"deviceId": device["id"], "deviceId": device["id"],
"type": "ignitionOn", "type": "ignitionOn",
"serverTime": datetime.utcnow(), "eventTime": datetime.utcnow().isoformat(),
"attributes": {}, "attributes": {},
}, }
{ ),
ReportsEventeModel(
**{
"id": 2,
"positionId": 2,
"geofenceId": 1,
"maintenanceId": 1,
"deviceId": device["id"], "deviceId": device["id"],
"type": "ignitionOff", "type": "ignitionOff",
"serverTime": datetime.utcnow(), "eventTime": datetime.utcnow().isoformat(),
"attributes": {}, "attributes": {},
}, }
),
] ]
events_ignition_on = async_capture_events(hass, "traccar_ignition_on") events_ignition_on = async_capture_events(hass, "traccar_ignition_on")
events_ignition_off = async_capture_events(hass, "traccar_ignition_off") events_ignition_off = async_capture_events(hass, "traccar_ignition_off")
with patch( with patch(
"homeassistant.components.traccar.device_tracker.API", return_value=api_mock "homeassistant.components.traccar.device_tracker.ApiClient",
return_value=api_mock,
): ):
assert await async_setup_component(hass, DOMAIN, conf_dict) assert await async_setup_component(hass, DOMAIN, conf_dict)