-
-
Notifications
You must be signed in to change notification settings - Fork 37.6k
Add device_tracker platform to Volvo integration #153437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f0ebb86
db55103
a9bc681
42cfa88
3c66d08
0941657
e2d5b68
97a1acc
eacfa42
89f5426
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| """Volvo device tracker.""" | ||
|
|
||
| from dataclasses import dataclass | ||
|
|
||
| from volvocarsapi.models import VolvoCarsApiBaseModel, VolvoCarsLocation | ||
|
|
||
| from homeassistant.components.device_tracker.config_entry import ( | ||
| TrackerEntity, | ||
| TrackerEntityDescription, | ||
| ) | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback | ||
|
|
||
| from .coordinator import VolvoConfigEntry | ||
| from .entity import VolvoEntity, VolvoEntityDescription | ||
|
|
||
| PARALLEL_UPDATES = 0 | ||
|
|
||
|
|
||
| @dataclass(frozen=True, kw_only=True) | ||
| class VolvoTrackerDescription(VolvoEntityDescription, TrackerEntityDescription): | ||
| """Describes a Volvo Cars tracker entity.""" | ||
|
|
||
|
|
||
| _DESCRIPTIONS: tuple[VolvoTrackerDescription, ...] = ( | ||
| VolvoTrackerDescription( | ||
| key="location", | ||
| api_field="location", | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| async def async_setup_entry( | ||
| _: HomeAssistant, | ||
| entry: VolvoConfigEntry, | ||
| async_add_entities: AddConfigEntryEntitiesCallback, | ||
| ) -> None: | ||
| """Set up tracker.""" | ||
|
|
||
| coordinators = entry.runtime_data.interval_coordinators | ||
| async_add_entities( | ||
| VolvoDeviceTracker(coordinator, description) | ||
|
thomasddn marked this conversation as resolved.
|
||
| for coordinator in coordinators | ||
| for description in _DESCRIPTIONS | ||
| if description.api_field in coordinator.data | ||
| ) | ||
|
|
||
|
|
||
| class VolvoDeviceTracker(VolvoEntity, TrackerEntity): | ||
| """Volvo tracker.""" | ||
|
|
||
| entity_description: VolvoTrackerDescription | ||
|
|
||
| def _update_state(self, api_field: VolvoCarsApiBaseModel | None) -> None: | ||
| assert isinstance(api_field, VolvoCarsLocation) | ||
|
|
||
| if api_field.geometry.coordinates and len(api_field.geometry.coordinates) > 1: | ||
| self._attr_longitude = api_field.geometry.coordinates[0] | ||
| self._attr_latitude = api_field.geometry.coordinates[1] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,10 +5,11 @@ | |
| from collections.abc import Callable | ||
| from dataclasses import dataclass | ||
| import logging | ||
| from typing import Any, cast | ||
| from typing import cast | ||
|
|
||
| from volvocarsapi.models import ( | ||
| VolvoCarsApiBaseModel, | ||
| VolvoCarsLocation, | ||
| VolvoCarsValue, | ||
| VolvoCarsValueField, | ||
| VolvoCarsValueStatusField, | ||
|
|
@@ -21,6 +22,7 @@ | |
| SensorStateClass, | ||
| ) | ||
| from homeassistant.const import ( | ||
| DEGREE, | ||
| PERCENTAGE, | ||
| EntityCategory, | ||
| UnitOfElectricCurrent, | ||
|
|
@@ -34,6 +36,7 @@ | |
| ) | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback | ||
| from homeassistant.helpers.typing import StateType | ||
|
|
||
| from .const import API_NONE_VALUE, DATA_BATTERY_CAPACITY | ||
| from .coordinator import VolvoConfigEntry | ||
|
|
@@ -47,34 +50,40 @@ | |
| class VolvoSensorDescription(VolvoEntityDescription, SensorEntityDescription): | ||
| """Describes a Volvo sensor entity.""" | ||
|
|
||
| value_fn: Callable[[VolvoCarsValue], Any] | None = None | ||
| value_fn: Callable[[VolvoCarsApiBaseModel], StateType] | None = None | ||
|
|
||
|
|
||
| def _availability_status(field: VolvoCarsValue) -> str: | ||
| def _availability_status(field: VolvoCarsApiBaseModel) -> str: | ||
| reason = field.get("unavailable_reason") | ||
| return reason if reason else str(field.value) | ||
|
|
||
| if reason: | ||
| return str(reason) | ||
|
|
||
| def _calculate_time_to_service(field: VolvoCarsValue) -> int: | ||
| value = int(field.value) | ||
| if isinstance(field, VolvoCarsValue): | ||
| return str(field.value) | ||
|
|
||
| return "" | ||
|
|
||
| # Always express value in days | ||
| if isinstance(field, VolvoCarsValueField) and field.unit == "months": | ||
| return value * 30 | ||
|
|
||
| return value | ||
| def _calculate_time_to_service(field: VolvoCarsApiBaseModel) -> int: | ||
| if not isinstance(field, VolvoCarsValueField): | ||
| return 0 | ||
|
|
||
| value = int(field.value) | ||
| # Always express value in days | ||
| return value * 30 if field.unit == "months" else value | ||
|
|
||
|
|
||
| def _charging_power_value(field: VolvoCarsValue) -> int: | ||
| def _charging_power_value(field: VolvoCarsApiBaseModel) -> int: | ||
| return ( | ||
| field.value | ||
| if isinstance(field, VolvoCarsValueStatusField) and isinstance(field.value, int) | ||
| else 0 | ||
| ) | ||
|
|
||
|
|
||
| def _charging_power_status_value(field: VolvoCarsValue) -> str | None: | ||
| status = cast(str, field.value) | ||
| def _charging_power_status_value(field: VolvoCarsApiBaseModel) -> str | None: | ||
| status = cast(str, field.value) if isinstance(field, VolvoCarsValue) else "" | ||
|
|
||
| if status.lower() in _CHARGING_POWER_STATUS_OPTIONS: | ||
| return status | ||
|
|
@@ -86,6 +95,10 @@ def _charging_power_status_value(field: VolvoCarsValue) -> str | None: | |
| return None | ||
|
|
||
|
|
||
| def _direction_value(field: VolvoCarsApiBaseModel) -> str | None: | ||
| return field.properties.heading if isinstance(field, VolvoCarsLocation) else None | ||
|
|
||
|
|
||
| _CHARGING_POWER_STATUS_OPTIONS = [ | ||
| "fault", | ||
| "power_available_but_not_activated", | ||
|
|
@@ -245,6 +258,14 @@ def _charging_power_status_value(field: VolvoCarsValue) -> str | None: | |
| "none", | ||
| ], | ||
| ), | ||
| # location endpoint | ||
| VolvoSensorDescription( | ||
| key="direction", | ||
| api_field="location", | ||
| native_unit_of_measurement=DEGREE, | ||
| suggested_display_precision=0, | ||
| value_fn=_direction_value, | ||
| ), | ||
| # statistics endpoint | ||
| # We're not using `electricRange` from the energy state endpoint because | ||
| # the official app seems to use `distanceToEmptyBattery`. | ||
|
|
@@ -380,13 +401,12 @@ def _update_state(self, api_field: VolvoCarsApiBaseModel | None) -> None: | |
| self._attr_native_value = None | ||
| return | ||
|
|
||
| assert isinstance(api_field, VolvoCarsValue) | ||
| native_value = None | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a bit trouble to understand why you assign this variable, whilst it will be outputted later on directly to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm using a variable, because in the statements below, the value could still be transformed before it is assigned to I could move the whole block to a method if you'd prefer that. |
||
|
|
||
| native_value = ( | ||
| api_field.value | ||
| if self.entity_description.value_fn is None | ||
| else self.entity_description.value_fn(api_field) | ||
| ) | ||
| if self.entity_description.value_fn: | ||
| native_value = self.entity_description.value_fn(api_field) | ||
| elif isinstance(api_field, VolvoCarsValue): | ||
| native_value = api_field.value | ||
|
|
||
| if self.device_class == SensorDeviceClass.ENUM and native_value: | ||
| # Entities having an "unknown" value should report None as the state | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.