Skip to content

Commit

Permalink
Reset energy-added when a new charge session is detected
Browse files Browse the repository at this point in the history
  • Loading branch information
jhansche committed Jul 21, 2024
1 parent 9a95f57 commit aa2b0e9
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 6 deletions.
2 changes: 2 additions & 0 deletions custom_components/teslafi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
DELAY_LOCKS = timedelta(seconds=15)
DELAY_WAKEUP = timedelta(seconds=30)

TESLAFI_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

ATTRIBUTION = "Data provided by Tesla and TeslaFi"

SHIFTER_STATES = {
Expand Down
36 changes: 31 additions & 5 deletions custom_components/teslafi/coordinator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""TeslaFi data update coordinator"""

from datetime import timedelta
from typing_extensions import override
from datetime import datetime, timedelta
from typing import override

from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

Expand All @@ -23,7 +24,8 @@ class TeslaFiCoordinator(DataUpdateCoordinator[TeslaFiVehicle]):
_vehicle: TeslaFiVehicle
data: TeslaFiVehicle

_override_next_refresh: timedelta = None
_last_charge_reset: datetime | None = None
_override_next_refresh: timedelta | None = None

def __init__(
self,
Expand All @@ -33,6 +35,7 @@ def __init__(
self._client = client
self.data = None
self._vehicle = TeslaFiVehicle({})
self._last_charge_reset = None
# TODO: implement custom Debouncer to ensure no more than 2x per min,
# as per API rate limit?
super().__init__(
Expand All @@ -55,6 +58,11 @@ def schedule_refresh_in(self, delta: timedelta):
self._override_next_refresh = delta
self._schedule_refresh()

@property
def last_charge_reset(self) -> datetime | None:
"""Last charge reset time."""
return self._last_charge_reset

@override
@callback
def _schedule_refresh(self) -> None:
Expand All @@ -78,9 +86,11 @@ async def _refresh(self) -> TeslaFiVehicle:
"""Refresh"""
current = await self._client.last_good()
LOGGER.debug("Last good: %s", current)

self._infer_charge_session(prev=self.data, current=current)

self._vehicle.update_non_empty(current)
last_remote_update = self._vehicle.get("Date")
LOGGER.debug("Remote data last updated %s", last_remote_update)
LOGGER.debug("Remote data last updated %s", self._vehicle.last_remote_update)

assert current.vin
assert self._vehicle.vin
Expand All @@ -101,3 +111,19 @@ async def _refresh(self) -> TeslaFiVehicle:
self._override_next_refresh = None

return self._vehicle

def _infer_charge_session(
self,
prev: TeslaFiVehicle,
current: TeslaFiVehicle,
):
if prev and current and prev != current:
if not prev.is_plugged_in and current.is_plugged_in:
LOGGER.info("Vehicle is newly plugged in: resetting charge session")
self._last_charge_reset = current.last_remote_update
elif (
current.charge_session_number
and prev.charge_session_number != current.charge_session_number
):
LOGGER.info("New charge session detected")
self._last_charge_reset = current.last_remote_update
24 changes: 23 additions & 1 deletion custom_components/teslafi/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

from collections import UserDict
from dataclasses import dataclass
from datetime import datetime
import logging

from typing_extensions import deprecated

from homeassistant.const import UnitOfPressure

from .const import SHIFTER_STATES, VIN_YEARS
from .const import SHIFTER_STATES, TESLAFI_DATE_FORMAT, VIN_YEARS
from .util import (
_convert_to_bool,
_int_or_none,
Expand All @@ -18,6 +20,8 @@

NAN: float = float("NaN")

LOGGER = logging.getLogger(__package__)

CHARGER_CONNECTED_STATES = [
"charging",
"complete",
Expand Down Expand Up @@ -92,6 +96,19 @@ def name(self) -> str:
or f"{self.model_year} {self.car_type} {self.vin[-4:]}"
)

@property
def last_remote_update(self) -> datetime | None:
"""Last remote update."""
try:
return (
datetime.strptime(s, TESLAFI_DATE_FORMAT)
if (s := self.get("Date", None))
else None
)
except ValueError:
LOGGER.warning("Failed to parse TeslaFi date: %s", s)
return None

@property
def car_type(self) -> str | None:
"""Car type (model). E.g. 'model3', etc."""
Expand Down Expand Up @@ -158,6 +175,11 @@ def charging_state(self) -> str | None:
"""
return _lower_or_none(self.get("charging_state", None))

@property
def charge_session_number(self) -> int | None:
"""The current charge session number."""
return n if (n := _int_or_none(self.get("chargeNumber"))) != 0 else None

@property
def is_plugged_in(self) -> bool | None:
"""Whether the vehicle is plugged in."""
Expand Down
12 changes: 12 additions & 0 deletions custom_components/teslafi/sensor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Sensors"""

from datetime import datetime
from functools import cached_property
from typing import override

from homeassistant.components.sensor import (
Expand Down Expand Up @@ -133,6 +135,7 @@
device_class=SensorDeviceClass.ENERGY,
entity_registry_visible_default=False,
state_class=SensorStateClass.TOTAL,
last_reset=None,
available=lambda u, d, h: u and d.is_plugged_in,
),
TeslaFiSensorEntityDescription(
Expand Down Expand Up @@ -255,6 +258,15 @@ def _handle_coordinator_update(self) -> None:
fixed := self.entity_description.fix_unit(self.coordinator.data, self.hass)
):
self._attr_native_unit_of_measurement = fixed

if self.entity_description.key == "charge_energy_added":
if self.entity_description.state_class == SensorStateClass.TOTAL and (
last_reset := self.coordinator.last_charge_reset
):
self._attr_last_reset = last_reset
else:
self._attr_last_reset = None

return super()._handle_coordinator_update()

@property
Expand Down

0 comments on commit aa2b0e9

Please sign in to comment.