diff --git a/custom_components/teslafi/alarm_control_panel.py b/custom_components/teslafi/alarm_control_panel.py index a8b7af3..265545c 100644 --- a/custom_components/teslafi/alarm_control_panel.py +++ b/custom_components/teslafi/alarm_control_panel.py @@ -132,9 +132,9 @@ def _handle_coordinator_update(self) -> None: key="sentry_mode", name="Sentry Mode", entity_registry_enabled_default=False, - convert=lambda v: STATE_ALARM_ARMED_AWAY - if _convert_to_bool(v) - else STATE_ALARM_DISARMED, + convert=lambda v: ( + STATE_ALARM_ARMED_AWAY if _convert_to_bool(v) else STATE_ALARM_DISARMED + ), ), ] diff --git a/custom_components/teslafi/base.py b/custom_components/teslafi/base.py index b37f859..a498c4a 100644 --- a/custom_components/teslafi/base.py +++ b/custom_components/teslafi/base.py @@ -148,6 +148,9 @@ class TeslaFiSensorEntityDescription( icons: dict[str, str] = None """Dictionary of state -> icon""" + fix_unit: Callable[[TeslaFiVehicle, HomeAssistant], str] = lambda d, h: None + """Convert the native unit of measurement. Return None to keep the original unit.""" + @dataclass class TeslaFiNumberEntityDescription( @@ -156,7 +159,7 @@ class TeslaFiNumberEntityDescription( ): """TeslaFi Number EntityDescription""" - convert: Callable[[any], bool] = lambda v: int(v) if v else None + convert: Callable[[any], int] = lambda v: int(v) if v else None cmd: Callable[[TeslaFiCoordinator, Number], dict] = None max_value_key: str = None diff --git a/custom_components/teslafi/config_flow.py b/custom_components/teslafi/config_flow.py index 3efcd5b..c047e59 100644 --- a/custom_components/teslafi/config_flow.py +++ b/custom_components/teslafi/config_flow.py @@ -1,4 +1,5 @@ """Config flow for Bird Buddy integration.""" + from __future__ import annotations from typing import Any diff --git a/custom_components/teslafi/device_tracker.py b/custom_components/teslafi/device_tracker.py index 334a824..fc70c2e 100644 --- a/custom_components/teslafi/device_tracker.py +++ b/custom_components/teslafi/device_tracker.py @@ -1,4 +1,5 @@ """Device tracker for MyBMW vehicles.""" + from __future__ import annotations from typing import Any @@ -42,11 +43,7 @@ def __init__(self, coordinator: TeslaFiCoordinator) -> None: @property def extra_state_attributes(self) -> dict[str, Any]: """Return entity specific state attributes.""" - heading = ( - int(h) - if (h := self.coordinator.data.get("heading", None)) - else None - ) + heading = int(h) if (h := self.coordinator.data.get("heading", None)) else None cardinal = _degrees_to_cardinal(heading) if heading is not None else None return { "heading": heading, diff --git a/custom_components/teslafi/model.py b/custom_components/teslafi/model.py index 8e2e937..46376a1 100644 --- a/custom_components/teslafi/model.py +++ b/custom_components/teslafi/model.py @@ -1,8 +1,12 @@ """TeslaFi Object Models""" from collections import UserDict +from dataclasses import dataclass + from typing_extensions import deprecated +from homeassistant.const import UnitOfPressure + from .const import SHIFTER_STATES, VIN_YEARS from .util import ( _convert_to_bool, @@ -12,7 +16,6 @@ _lower_or_none, ) - NAN: float = float("NaN") CHARGER_CONNECTED_STATES = [ @@ -24,6 +27,28 @@ ] +@dataclass +class TeslaFiTirePressure: + """TeslaFi Tire Pressure Data.""" + + front_left: float | None + front_right: float | None + rear_left: float | None + rear_right: float | None + unit: str | None = None + + @staticmethod + def convert_unit(unit: str) -> str | None: + """Convert units to Home Assistant's UnitOfPressure.""" + unit_mapping = { + "kpa": UnitOfPressure.KPA, + "bar": UnitOfPressure.BAR, + "psi": UnitOfPressure.PSI, + "mmhg": UnitOfPressure.MMHG, + } + return unit_mapping.get(unit.lower(), None) if unit else None + + class TeslaFiVehicle(UserDict): """TeslaFi Vehicle Data""" @@ -196,3 +221,30 @@ def is_defrosting(self) -> bool | None: or self.get("is_rear_defroster_on") == "1" or self.get("defrost_mode", "0") != "0" ) + + @property + def tpms(self) -> TeslaFiTirePressure: + """TPMS state(s): (front-left, front-right, rear-left, rear-right).""" + return TeslaFiTirePressure( + front_left=( + float(tpms_fl) + if (tpms_fl := self.get("tpms_front_left", None)) + else None + ), + front_right=( + float(tpms_fr) + if (tpms_fr := self.get("tpms_front_right", None)) + else None + ), + rear_left=( + float(tpms_rl) + if (tpms_rl := self.get("tpms_rear_left", None)) + else None + ), + rear_right=( + float(tpms_rr) + if (tpms_rr := self.get("tpms_rear_right", None)) + else None + ), + unit=self.get("pressure", "psi"), + ) diff --git a/custom_components/teslafi/sensor.py b/custom_components/teslafi/sensor.py index 1725155..97ae17b 100644 --- a/custom_components/teslafi/sensor.py +++ b/custom_components/teslafi/sensor.py @@ -1,6 +1,7 @@ """Sensors""" -from typing_extensions import override +from typing import override + from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, @@ -16,6 +17,7 @@ UnitOfEnergy, UnitOfLength, UnitOfPower, + UnitOfPressure, UnitOfSpeed, UnitOfTemperature, UnitOfTime, @@ -26,7 +28,7 @@ from .base import TeslaFiEntity, TeslaFiSensorEntityDescription from .const import DOMAIN, SHIFTER_STATES from .coordinator import TeslaFiCoordinator - +from .model import TeslaFiTirePressure SENSORS = [ # region Generic car info @@ -187,6 +189,60 @@ ), # ... climate.py # endregion + # region TPMS + TeslaFiSensorEntityDescription( + key="tpms_front_left", + name="TPMS Front Left", + icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.PSI, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + fix_unit=lambda d, h: TeslaFiTirePressure.convert_unit(d.tpms.unit), + value=lambda d, h: d.tpms.front_left, + available=lambda u, d, h: u and d.tpms.front_left, + ), + TeslaFiSensorEntityDescription( + key="tpms_front_right", + name="TPMS Front Right", + icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.PSI, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + fix_unit=lambda d, h: TeslaFiTirePressure.convert_unit(d.tpms.unit), + value=lambda d, h: d.tpms.front_right, + available=lambda u, d, h: u and d.tpms.front_right, + ), + TeslaFiSensorEntityDescription( + key="tpms_rear_left", + name="TPMS Rear Left", + icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.PSI, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + fix_unit=lambda d, h: TeslaFiTirePressure.convert_unit(d.tpms.unit), + value=lambda d, h: d.tpms.rear_left, + available=lambda u, d, h: u and d.tpms.rear_left, + ), + TeslaFiSensorEntityDescription( + key="tpms_rear_right", + name="TPMS Rear Right", + icon="mdi:car-tire-alert", + device_class=SensorDeviceClass.PRESSURE, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPressure.PSI, + entity_category=EntityCategory.DIAGNOSTIC, + entity_registry_enabled_default=False, + fix_unit=lambda d, h: TeslaFiTirePressure.convert_unit(d.tpms.unit), + value=lambda d, h: d.tpms.rear_right, + available=lambda u, d, h: u and d.tpms.rear_right, + ), + # endregion ] @@ -195,6 +251,10 @@ class TeslaFiSensor(TeslaFiEntity[TeslaFiSensorEntityDescription], SensorEntity) def _handle_coordinator_update(self) -> None: self._attr_native_value = self._get_value() + if self.entity_description.fix_unit and ( + fixed := self.entity_description.fix_unit(self.coordinator.data, self.hass) + ): + self._attr_native_unit_of_measurement = fixed return super()._handle_coordinator_update() @property