From f83ee963bfc2efbb7cfdf70ef7e2da749068e6ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20S=C3=B8rensen?= Date: Wed, 24 Apr 2024 17:08:56 +0200 Subject: [PATCH] Add binary sensor entities to Traccar Server (#114719) --- .../components/traccar_server/__init__.py | 6 +- .../traccar_server/binary_sensor.py | 99 +++++++++++++++++++ .../traccar_server/device_tracker.py | 11 +-- .../components/traccar_server/icons.json | 9 ++ .../components/traccar_server/strings.json | 16 +++ .../snapshots/test_diagnostics.ambr | 51 +++++++++- 6 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 homeassistant/components/traccar_server/binary_sensor.py diff --git a/homeassistant/components/traccar_server/__init__.py b/homeassistant/components/traccar_server/__init__.py index 703df6cbfa4..c7a65d2d4a8 100644 --- a/homeassistant/components/traccar_server/__init__.py +++ b/homeassistant/components/traccar_server/__init__.py @@ -30,7 +30,11 @@ from .const import ( ) from .coordinator import TraccarServerCoordinator -PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER, Platform.SENSOR] +PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.DEVICE_TRACKER, + Platform.SENSOR, +] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/homeassistant/components/traccar_server/binary_sensor.py b/homeassistant/components/traccar_server/binary_sensor.py new file mode 100644 index 00000000000..6ee5757dcea --- /dev/null +++ b/homeassistant/components/traccar_server/binary_sensor.py @@ -0,0 +1,99 @@ +"""Support for Traccar server binary sensors.""" + +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass +from typing import Generic, Literal, TypeVar, cast + +from pytraccar import DeviceModel + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import TraccarServerCoordinator +from .entity import TraccarServerEntity + +_T = TypeVar("_T") + + +@dataclass(frozen=True, kw_only=True) +class TraccarServerBinarySensorEntityDescription( + Generic[_T], BinarySensorEntityDescription +): + """Describe Traccar Server sensor entity.""" + + data_key: Literal["position", "device", "geofence", "attributes"] + entity_registry_enabled_default = False + entity_category = EntityCategory.DIAGNOSTIC + value_fn: Callable[[_T], bool | None] + + +TRACCAR_SERVER_BINARY_SENSOR_ENTITY_DESCRIPTIONS = ( + TraccarServerBinarySensorEntityDescription[DeviceModel]( + key="attributes.motion", + data_key="position", + translation_key="motion", + device_class=BinarySensorDeviceClass.MOTION, + value_fn=lambda x: x["attributes"].get("motion", False), + ), + TraccarServerBinarySensorEntityDescription[DeviceModel]( + key="status", + data_key="device", + translation_key="status", + value_fn=lambda x: None if (s := x["status"]) == "unknown" else s == "online", + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up binary sensor entities.""" + coordinator: TraccarServerCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + TraccarServerBinarySensor( + coordinator=coordinator, + device=entry["device"], + description=cast(TraccarServerBinarySensorEntityDescription, description), + ) + for entry in coordinator.data.values() + for description in TRACCAR_SERVER_BINARY_SENSOR_ENTITY_DESCRIPTIONS + ) + + +class TraccarServerBinarySensor(TraccarServerEntity, BinarySensorEntity): + """Represent a traccar server binary sensor.""" + + _attr_has_entity_name = True + entity_description: TraccarServerBinarySensorEntityDescription + + def __init__( + self, + coordinator: TraccarServerCoordinator, + device: DeviceModel, + description: TraccarServerBinarySensorEntityDescription[_T], + ) -> None: + """Initialize the Traccar Server sensor.""" + super().__init__(coordinator, device) + self.entity_description = description + self._attr_unique_id = ( + f"{device['uniqueId']}_{description.data_key}_{description.key}" + ) + + @property + def is_on(self) -> bool | None: + """Return if the binary sensor is on or not.""" + return self.entity_description.value_fn( + getattr(self, f"traccar_{self.entity_description.data_key}") + ) diff --git a/homeassistant/components/traccar_server/device_tracker.py b/homeassistant/components/traccar_server/device_tracker.py index d15ba084dad..e7dba3ad99d 100644 --- a/homeassistant/components/traccar_server/device_tracker.py +++ b/homeassistant/components/traccar_server/device_tracker.py @@ -9,14 +9,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import ( - ATTR_CATEGORY, - ATTR_MOTION, - ATTR_STATUS, - ATTR_TRACCAR_ID, - ATTR_TRACKER, - DOMAIN, -) +from .const import ATTR_CATEGORY, ATTR_TRACCAR_ID, ATTR_TRACKER, DOMAIN from .coordinator import TraccarServerCoordinator from .entity import TraccarServerEntity @@ -46,8 +39,6 @@ class TraccarServerDeviceTracker(TraccarServerEntity, TrackerEntity): return { **self.traccar_attributes, ATTR_CATEGORY: self.traccar_device["category"], - ATTR_MOTION: self.traccar_position["attributes"].get("motion", False), - ATTR_STATUS: self.traccar_device["status"], ATTR_TRACCAR_ID: self.traccar_device["id"], ATTR_TRACKER: DOMAIN, } diff --git a/homeassistant/components/traccar_server/icons.json b/homeassistant/components/traccar_server/icons.json index 59fc663e712..a10b154fbff 100644 --- a/homeassistant/components/traccar_server/icons.json +++ b/homeassistant/components/traccar_server/icons.json @@ -1,5 +1,14 @@ { "entity": { + "binary_sensor": { + "status": { + "default": "mdi:access-point-minus", + "state": { + "off": "mdi:access-point-off", + "on": "mdi:access-point" + } + } + }, "sensor": { "altitude": { "default": "mdi:altimeter" diff --git a/homeassistant/components/traccar_server/strings.json b/homeassistant/components/traccar_server/strings.json index 41adaace77e..8bec4b112ac 100644 --- a/homeassistant/components/traccar_server/strings.json +++ b/homeassistant/components/traccar_server/strings.json @@ -43,6 +43,22 @@ } }, "entity": { + "binary_sensor": { + "motion": { + "name": "Motion", + "state": { + "off": "Stopped", + "on": "Moving" + } + }, + "status": { + "name": "Status", + "state": { + "off": "Offline", + "on": "Online" + } + } + }, "sensor": { "address": { "name": "Address" diff --git a/tests/components/traccar_server/snapshots/test_diagnostics.ambr b/tests/components/traccar_server/snapshots/test_diagnostics.ambr index 300444f10f1..89a6416c303 100644 --- a/tests/components/traccar_server/snapshots/test_diagnostics.ambr +++ b/tests/components/traccar_server/snapshots/test_diagnostics.ambr @@ -82,9 +82,7 @@ 'gps_accuracy': 3.5, 'latitude': '**REDACTED**', 'longitude': '**REDACTED**', - 'motion': False, 'source_type': 'gps', - 'status': 'online', 'traccar_id': 0, 'tracker': 'traccar_server', }), @@ -92,6 +90,29 @@ }), 'unit_of_measurement': None, }), + dict({ + 'disabled': False, + 'enity_id': 'binary_sensor.x_wing_motion', + 'state': dict({ + 'attributes': dict({ + 'device_class': 'motion', + 'friendly_name': 'X-Wing Motion', + }), + 'state': 'off', + }), + 'unit_of_measurement': None, + }), + dict({ + 'disabled': False, + 'enity_id': 'binary_sensor.x_wing_status', + 'state': dict({ + 'attributes': dict({ + 'friendly_name': 'X-Wing Status', + }), + 'state': 'on', + }), + 'unit_of_measurement': None, + }), dict({ 'disabled': False, 'enity_id': 'sensor.x_wing_battery', @@ -231,6 +252,18 @@ }), }), 'entities': list([ + dict({ + 'disabled': True, + 'enity_id': 'binary_sensor.x_wing_motion', + 'state': None, + 'unit_of_measurement': None, + }), + dict({ + 'disabled': True, + 'enity_id': 'binary_sensor.x_wing_status', + 'state': None, + 'unit_of_measurement': None, + }), dict({ 'disabled': True, 'enity_id': 'sensor.x_wing_battery', @@ -343,6 +376,18 @@ }), }), 'entities': list([ + dict({ + 'disabled': True, + 'enity_id': 'binary_sensor.x_wing_motion', + 'state': None, + 'unit_of_measurement': None, + }), + dict({ + 'disabled': True, + 'enity_id': 'binary_sensor.x_wing_status', + 'state': None, + 'unit_of_measurement': None, + }), dict({ 'disabled': True, 'enity_id': 'sensor.x_wing_battery', @@ -384,9 +429,7 @@ 'gps_accuracy': 3.5, 'latitude': '**REDACTED**', 'longitude': '**REDACTED**', - 'motion': False, 'source_type': 'gps', - 'status': 'online', 'traccar_id': 0, 'tracker': 'traccar_server', }),