Skip to content

Commit

Permalink
Covers
Browse files Browse the repository at this point in the history
  • Loading branch information
Bre77 committed Dec 11, 2024
1 parent d4644fa commit c1be677
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 110 deletions.
4 changes: 2 additions & 2 deletions custom_components/teslemetry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@

PLATFORMS: Final = [
Platform.BINARY_SENSOR,
#Platform.BUTTON,
Platform.BUTTON,
#Platform.COVER,
#Platform.CLIMATE,
Platform.CLIMATE,
#Platform.DEVICE_TRACKER,
#Platform.LOCK,
#Platform.MEDIA_PLAYER,
Expand Down
45 changes: 26 additions & 19 deletions custom_components/teslemetry/climate.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Climate platform for Teslemetry integration."""

from typing import Any, cast
from itertools import chain

from tesla_fleet_api.const import Scope, CabinOverheatProtectionTemp
from teslemetry_stream import Signal
Expand All @@ -23,33 +24,39 @@
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.restore_state import RestoreEntity

from .const import TeslemetryClimateSide, TeslemetryTimestamp
from .const import TeslemetryClimateSide
from .entity import TeslemetryVehicleComplexStreamEntity, TeslemetryVehicleEntity
from .models import TeslemetryVehicleData

DEFAULT_MIN_TEMP = 15
DEFAULT_MAX_TEMP = 28


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Teslemetry Climate platform from a config entry."""

entities = []
for vehicle in entry.runtime_data.vehicles:
if True or vehicle.api.pre2021 or vehicle.firmware < "2024.44.25":
# Vehicle cannot use streaming
entities.append(TeslemetryPollingClimateEntity(
vehicle, TeslemetryClimateSide.DRIVER, entry.runtime_data.scopes
))
entities.append(TeslemetryCabinOverheatProtectionEntity(vehicle, entry.runtime_data.scopes))
else:
entities.append(TeslemetryStreamingClimateEntity(
async_add_entities(
chain((
TeslemetryPollingClimateEntity(
vehicle, TeslemetryClimateSide.DRIVER, entry.runtime_data.scopes
))
entities.append(TeslemetryCabinOverheatProtectionEntity(vehicle, entry.runtime_data.scopes))
)
if True or vehicle.api.pre2021 or vehicle.firmware < "2024.44.25"
else TeslemetryStreamingClimateEntity(
vehicle, TeslemetryClimateSide.DRIVER, entry.runtime_data.scopes
)
for vehicle in entry.runtime_data.vehicles
),(
TeslemetryPollingCabinOverheatProtectionEntity(
vehicle, entry.runtime_data.scopes
)
if vehicle.api.pre2021 or vehicle.firmware < "2024.44.25"
else TeslemetryStreamingCabinOverheatProtectionEntity(
vehicle, entry.runtime_data.scopes
)
for vehicle in entry.runtime_data.vehicles
))
)

DEFAULT_MIN_TEMP = 15
DEFAULT_MAX_TEMP = 28

class TeslemetryClimateEntity(ClimateEntity):
"""Vehicle Climate Control."""
Expand Down Expand Up @@ -344,7 +351,7 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
self._attr_hvac_mode = hvac_mode
self.async_write_ha_state()

class TeslemetryCabinOverheatProtectionPollingEntity(TeslemetryVehicleEntity, TeslemetryCabinOverheatProtectionEntity):
class TeslemetryPollingCabinOverheatProtectionEntity(TeslemetryVehicleEntity, TeslemetryCabinOverheatProtectionEntity):
"""Vehicle Cabin Overheat Protection."""

def __init__(
Expand Down Expand Up @@ -387,7 +394,7 @@ def _async_update_attrs(self) -> None:
self._attr_current_temperature = self.get("climate_state_inside_temp")


class TeslemetryCabinOverheatProtectionPollingEntity(TeslemetryVehicleComplexStreamEntity, TeslemetryCabinOverheatProtectionEntity):
class TeslemetryStreamingCabinOverheatProtectionEntity(TeslemetryVehicleComplexStreamEntity, TeslemetryCabinOverheatProtectionEntity):
"""Vehicle Cabin Overheat Protection."""

def __init__(
Expand Down
167 changes: 107 additions & 60 deletions custom_components/teslemetry/cover.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity

from .const import TeslemetryCoverStates, TeslemetryTimestamp
from .entity import TeslemetryVehicleEntity
from .entity import TeslemetryVehicleEntity, TeslemetryVehicleStreamEntity, TeslemetryVehicleComplexStreamEntity
from .models import TeslemetryVehicleData
from .helpers import auto_type

Expand All @@ -33,44 +32,46 @@ async def async_setup_entry(

async_add_entities(
klass(vehicle, entry.runtime_data.scopes)
for vehicle in entry.runtime_data.vehicles
for (klass) in (
TeslemetryWindowEntity,
TeslemetryPollingWindowEntity if vehicle.pre2021 else TeslemetryStreamingWindowEntity,
TeslemetryChargePortEntity,
TeslemetryFrontTrunkEntity,
TeslemetryRearTrunkEntity,
TeslemetrySunroofEntity,
)
for vehicle in entry.runtime_data.vehicles
)


class CoverRestoreEntity(CoverEntity, RestoreEntity):
"""Base class for cover entities that need to restore state."""

_attr_is_closed: bool | None = None

async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
if (state := await self.async_get_last_state()) is not None and not self.coordinator.updated_once:
if (state.state == "open"):
self._attr_is_closed = False
elif (state.state == "closed"):
self._attr_is_closed = True
self._attr_current_cover_position = state.attributes.get("current_cover_position")
)


class TeslemetryWindowEntity(TeslemetryVehicleEntity, CoverRestoreEntity):
class TeslemetryWindowEntity(CoverEntity):
"""Cover entity for windows."""

_attr_device_class = CoverDeviceClass.WINDOW
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE

async def async_open_cover(self, **kwargs: Any) -> None:
"""Vent windows."""
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await self.handle_command(self.api.window_control(command=WindowCommand.VENT))
self._attr_is_closed = False
self.async_write_ha_state()

async def async_close_cover(self, **kwargs: Any) -> None:
"""Close windows."""
self.raise_for_scope(Scope.VEHICLE_CMDS)
await self.wake_up_if_asleep()
await self.handle_command(self.api.window_control(command=WindowCommand.CLOSE))
self._attr_is_closed = True
self.async_write_ha_state()

class TeslemetryPollingWindowEntity(TeslemetryVehicleEntity, TeslemetryWindowEntity):
"""Polling cover entity for windows."""

def __init__(self, data: TeslemetryVehicleData, scopes: list[Scope]) -> None:
"""Initialize the sensor."""
super().__init__(
data, "windows", timestamp_key=TeslemetryTimestamp.VEHICLE_STATE
)
super().__init__(data, "windows")
self.scoped = Scope.VEHICLE_CMDS in scopes
if not self.scoped:
self._attr_supported_features = CoverEntityFeature(0)
Expand All @@ -82,38 +83,105 @@ def _async_update_attrs(self) -> None:
rd = self.get("vehicle_state_rd_window")
rp = self.get("vehicle_state_rp_window")

# Any open set to open
if OPEN in (fd, fp, rd, rp):
self._attr_is_closed = False
# All closed set to closed
elif CLOSED == fd == fp == rd == rp:
self._attr_is_closed = True
# Otherwise, set to unknown
elif None in (fd, fp, rd, rp):
self._attr_is_closed = None
else:
self._attr_is_closed = True

class TeslemetryStreamingWindowEntity(TeslemetryVehicleComplexStreamEntity, TeslemetryWindowEntity, RestoreEntity):
"""Streaming cover entity for windows."""

fd: bool | None = None
fp: bool | None = None
rd: bool | None = None
rp: bool | None = None

def __init__(self, data: TeslemetryVehicleData, scopes: list[Scope]) -> None:
"""Initialize the sensor."""
super().__init__(data, "windows", [
Signal.FD_WINDOW,
Signal.FP_WINDOW,
Signal.RD_WINDOW,
Signal.RP_WINDOW,
])
self.scoped = Scope.VEHICLE_CMDS in scopes
if not self.scoped:
self._attr_supported_features = CoverEntityFeature(0)

async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""
await super().async_added_to_hass()
if (state := await self.async_get_last_state()) is not None:
if (state.state == "open"):
self._attr_is_closed = False
elif (state.state == "closed"):
self._attr_is_closed = True
#self._attr_current_cover_position = state.attributes.get("current_cover_position")

def _async_data_from_stream(self, data) -> None:
"""Update the entity attributes."""
if value := data.get(Signal.FD_WINDOW):
self.fd = value == "WindowStateOpen"
if value := data.get(Signal.FP_WINDOW):
self.fp = value == "WindowStateOpen"
if value := data.get(Signal.RD_WINDOW):
self.rd = value == "WindowStateOpen"
if value := data.get(Signal.RP_WINDOW):
self.rp = value == "WindowStateOpen"

if True in (fd, fp, rd, rp):
self._attr_is_closed = False
elif None in (fd, fp, rd, rp):
self._attr_is_closed = None
else:
self._attr_is_closed = True

class TeslemetryChargePortEntity(CoverEntity):
"""Cover entity for the charge port."""

_attr_device_class = CoverDeviceClass.DOOR
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE

async def async_open_cover(self, **kwargs: Any) -> None:
"""Vent windows."""
self.raise_for_scope(Scope.VEHICLE_CMDS)
"""Open windows."""
self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)
await self.wake_up_if_asleep()
await self.handle_command(self.api.window_control(command=WindowCommand.VENT))
await self.handle_command(self.api.charge_port_door_open())
self._attr_is_closed = False
self.async_write_ha_state()

async def async_close_cover(self, **kwargs: Any) -> None:
"""Close windows."""
self.raise_for_scope(Scope.VEHICLE_CMDS)
self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)
await self.wake_up_if_asleep()
await self.handle_command(self.api.window_control(command=WindowCommand.CLOSE))
await self.handle_command(self.api.charge_port_door_close())
self._attr_is_closed = True
self.async_write_ha_state()

class TeslemetryPollingChargePortLatch(TeslemetryVehicleEntity, TeslemetryChargePortEntity):
"""Polling cover entity for the charge port."""
def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
"""Initialize the sensor."""
self.scoped = any(
scope in scopes
for scope in [Scope.VEHICLE_CMDS, Scope.VEHICLE_CHARGING_CMDS]
)
if not self.scoped:
self._attr_supported_features = CoverEntityFeature(0)

class TeslemetryChargePortEntity(TeslemetryVehicleEntity, CoverRestoreEntity):
"""Cover entity for the charge port."""
super().__init__(
vehicle,
"charge_state_charge_port_door_open",
)

_attr_device_class = CoverDeviceClass.DOOR
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
def _async_value_from_stream(self, value) -> None:
"""Update the value of the entity."""
self._attr_is_closed = not auto_type(value)

class TeslemetryStreamingChargePortLatch(TeslemetryVehicleStreamEntity, TeslemetryChargePortEntity):
"""Streaming cover entity for the charge port."""

def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
"""Initialize the sensor."""
Expand All @@ -127,34 +195,13 @@ def __init__(self, vehicle: TeslemetryVehicleData, scopes: list[Scope]) -> None:
super().__init__(
vehicle,
"charge_state_charge_port_door_open",
timestamp_key=TeslemetryTimestamp.CHARGE_STATE,
streaming_key=Signal.CHARGE_PORT,
streaming_key=Signal.CHARGE_PORT_LATCH,
)

def _async_update_attrs(self) -> None:
"""Update the entity attributes."""
self._attr_is_closed = not self._value

def _async_value_from_stream(self, value) -> None:
"""Update the value of the entity."""
self._attr_is_closed = not auto_type(value)

async def async_open_cover(self, **kwargs: Any) -> None:
"""Open windows."""
self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)
await self.wake_up_if_asleep()
await self.handle_command(self.api.charge_port_door_open())
self._attr_is_closed = False
self.async_write_ha_state()

async def async_close_cover(self, **kwargs: Any) -> None:
"""Close windows."""
self.raise_for_scope(Scope.VEHICLE_CHARGING_CMDS)
await self.wake_up_if_asleep()
await self.handle_command(self.api.charge_port_door_close())
self._attr_is_closed = True
self.async_write_ha_state()


class TeslemetryFrontTrunkEntity(TeslemetryVehicleEntity, CoverRestoreEntity):
"""Cover entity for the front trunk."""
Expand Down
Loading

0 comments on commit c1be677

Please sign in to comment.