Skip to content

Commit

Permalink
Merge pull request #577 from alandtse/dev
Browse files Browse the repository at this point in the history
chore: release 2023-04-21
  • Loading branch information
alandtse authored Apr 21, 2023
2 parents e1e286c + 970759c commit 4a32c69
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 5 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Tesla options are set via **Configuration** -> **Integrations** -> **Tesla** ->
- Seconds between polling - referred to below as the `polling_interval`.
- Wake cars on start - Whether to wake sleeping cars on Home Assistant startup. This allows a user to choose whether cars should continue to sleep (and not update information) or to wake up the cars potentially interrupting long term hibernation and increasing vampire drain.
- Polling policy - When do we actively poll the car to get updates, and when do we try to allow the car to sleep. See [the Wiki](https://github.com/alandtse/tesla/wiki/Polling-policy) for more information.
- Sync Data from TeslaMate via MQTT - Enable syncing of Data from an TeslaMate instance via MQTT, esentially enabling the Streaming API for updates. This requies MQTT to be configured in Home Assistant.

## Potential Battery impacts

Expand Down
20 changes: 20 additions & 0 deletions custom_components/tesla_custom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@

from .config_flow import CannotConnect, InvalidAuth, validate_input
from .const import (
CONF_ENABLE_TESLAMATE,
CONF_EXPIRATION,
CONF_INCLUDE_ENERGYSITES,
CONF_INCLUDE_VEHICLES,
CONF_POLLING_POLICY,
CONF_WAKE_ON_START,
DATA_LISTENER,
DEFAULT_ENABLE_TESLAMATE,
DEFAULT_POLLING_POLICY,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WAKE_ON_START,
Expand All @@ -40,6 +42,7 @@
PLATFORMS,
)
from .services import async_setup_services, async_unload_services
from .teslamate import TeslaMate
from .util import SSL_CONTEXT

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -286,11 +289,20 @@ def _async_create_close_task():
**{vin: _partial_coordinator(vins={vin}) for vin in cars},
}

teslamate = TeslaMate(hass=hass, cars=cars, coordinators=coordinators)

enable_teslamate = config_entry.options.get(
CONF_ENABLE_TESLAMATE, DEFAULT_ENABLE_TESLAMATE
)

await teslamate.enable(enable_teslamate)

hass.data[DOMAIN][config_entry.entry_id] = {
"controller": controller,
"coordinators": coordinators,
"cars": cars,
"energysites": energysites,
"teslamate": teslamate,
DATA_LISTENER: [config_entry.add_update_listener(update_listener)],
}
_LOGGER.debug("Connected to the Tesla API")
Expand All @@ -317,6 +329,8 @@ async def async_unload_entry(hass, config_entry) -> bool:
listener()
username = config_entry.title

await entry_data["teslamate"].unload()

if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
_LOGGER.debug("Unloaded entry for %s", username)
Expand Down Expand Up @@ -344,6 +358,12 @@ async def update_listener(hass, config_entry):
controller.update_interval,
)

enable_teslamate = config_entry.options.get(
CONF_ENABLE_TESLAMATE, DEFAULT_ENABLE_TESLAMATE
)

await entry_data["teslamate"].enable(enable_teslamate)


class TeslaDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Tesla data."""
Expand Down
2 changes: 1 addition & 1 deletion custom_components/tesla_custom/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(
) -> None:
"""Initialise the Tesla device."""
super().__init__(coordinator)
self._coordinator = coordinator
self._coordinator: TeslaDataUpdateCoordinator = coordinator
self._enabled_by_default: bool = True
self.hass = hass
self.type = None
Expand Down
8 changes: 8 additions & 0 deletions custom_components/tesla_custom/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
ATTR_POLLING_POLICY_ALWAYS,
ATTR_POLLING_POLICY_CONNECTED,
ATTR_POLLING_POLICY_NORMAL,
CONF_ENABLE_TESLAMATE,
CONF_EXPIRATION,
CONF_INCLUDE_ENERGYSITES,
CONF_INCLUDE_VEHICLES,
CONF_POLLING_POLICY,
CONF_WAKE_ON_START,
DEFAULT_ENABLE_TESLAMATE,
DEFAULT_POLLING_POLICY,
DEFAULT_SCAN_INTERVAL,
DEFAULT_WAKE_ON_START,
Expand Down Expand Up @@ -162,6 +164,12 @@ async def async_step_init(self, user_input=None):
ATTR_POLLING_POLICY_ALWAYS,
]
),
vol.Optional(
CONF_ENABLE_TESLAMATE,
default=self.config_entry.options.get(
CONF_ENABLE_TESLAMATE, DEFAULT_ENABLE_TESLAMATE
),
): bool,
}
)
return self.async_show_form(step_id="init", data_schema=data_schema)
Expand Down
6 changes: 6 additions & 0 deletions custom_components/tesla_custom/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
CONF_INCLUDE_ENERGYSITES = "include_energysites"
CONF_POLLING_POLICY = "polling_policy"
CONF_WAKE_ON_START = "enable_wake_on_start"
CONF_ENABLE_TESLAMATE = "enable_teslamate"
DOMAIN = "tesla_custom"
ATTRIBUTION = "Data provided by Tesla"
DATA_LISTENER = "listener"
DEFAULT_SCAN_INTERVAL = 660
DEFAULT_WAKE_ON_START = False
DEFAULT_ENABLE_TESLAMATE = False
ERROR_URL_NOT_DETECTED = "url_not_detected"
MIN_SCAN_INTERVAL = 10

Expand All @@ -25,6 +27,7 @@
"select",
"update",
"number",
"text",
]

AUTH_CALLBACK_PATH = "/auth/tesla/callback"
Expand All @@ -42,3 +45,6 @@
DISTANCE_UNITS_KM_HR = "km/hr"
SERVICE_API = "api"
SERVICE_SCAN_INTERVAL = "polling_interval"

TESLAMATE_STORAGE_VERSION = 1
TESLAMATE_STORAGE_KEY = f"{DOMAIN}_teslamate"
1 change: 1 addition & 0 deletions custom_components/tesla_custom/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"domain": "tesla_custom",
"name": "Tesla Custom Integration",
"after_dependencies": ["mqtt"],
"codeowners": ["@alandtse"],
"config_flow": true,
"dependencies": ["http"],
Expand Down
71 changes: 71 additions & 0 deletions custom_components/tesla_custom/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.util import dt
from homeassistant.util.unit_conversion import DistanceConverter
Expand Down Expand Up @@ -66,6 +67,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
entities.append(TeslaCarChargerEnergy(hass, car, coordinator))
entities.append(TeslaCarChargerPower(hass, car, coordinator))
entities.append(TeslaCarOdometer(hass, car, coordinator))
entities.append(TeslaCarShiftState(hass, car, coordinator))
entities.append(TeslaCarRange(hass, car, coordinator))
entities.append(TeslaCarTemp(hass, car, coordinator))
entities.append(TeslaCarTemp(hass, car, coordinator, inside=True))
Expand All @@ -76,6 +78,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie
)
entities.append(TeslaCarArrivalTime(hass, car, coordinator))
entities.append(TeslaCarDistanceToArrival(hass, car, coordinator))
entities.append(TeslaCarDataUpdateTime(hass, car, coordinator))

for energy_site_id, energysite in energysites.items():
coordinator = coordinators[energy_site_id]
Expand Down Expand Up @@ -291,6 +294,48 @@ def native_value(self) -> float:
return round(odometer_value, 2)


class TeslaCarShiftState(TeslaCarEntity, SensorEntity):
"""Representation of the Tesla car Shift State sensor."""

def __init__(
self,
hass: HomeAssistant,
car: TeslaCar,
coordinator: TeslaDataUpdateCoordinator,
) -> None:
"""Initialize odometer entity."""
super().__init__(hass, car, coordinator)
self.type = "shift state"
self._attr_device_class = SensorDeviceClass.ENUM
self._attr_icon = "mdi:car-shift-pattern"

@property
def native_value(self) -> float:
"""Return the shift state."""
value = self._car.shift_state

# When car is parked and off, Tesla API reports shift_state None
if value is None or value == "":
return "P"

return value

@property
def options(self) -> float:
"""Return the values for the ENUM."""
values = ["P", "D", "R", "N"]

return values

@property
def extra_state_attributes(self):
"""Return device state attributes."""

return {
"raw_state": self._car.shift_state,
}


class TeslaCarRange(TeslaCarEntity, SensorEntity):
"""Representation of the Tesla car range sensor."""

Expand Down Expand Up @@ -684,3 +729,29 @@ def native_value(self) -> float:
if self._car.active_route_miles_to_arrival is None:
return None
return round(self._car.active_route_miles_to_arrival, 2)


class TeslaCarDataUpdateTime(TeslaCarEntity, SensorEntity):
"""Representation of the TeslajsonPy Last Data Update time."""

def __init__(
self,
hass: HomeAssistant,
car: TeslaCar,
coordinator: TeslaDataUpdateCoordinator,
) -> None:
"""Initialize Last Data Update entity."""
super().__init__(hass, car, coordinator)
self.type = "data last update time"
self._attr_device_class = SensorDeviceClass.TIMESTAMP
self._attr_entity_category = EntityCategory.DIAGNOSTIC
self._attr_icon = "mdi:timer"

@property
def native_value(self) -> float:
"""Return the last data update time."""
last_time = self._coordinator.controller.get_last_update_time(vin=self._car.vin)

utc_tz = dt.get_time_zone("UTC")
date_obj = datetime.fromtimestamp(last_time, utc_tz)
return date_obj
3 changes: 2 additions & 1 deletion custom_components/tesla_custom/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"init": {
"data": {
"enable_wake_on_start": "Force cars awake on startup",
"scan_interval": "Seconds between polling"
"scan_interval": "Seconds between polling",
"enable_teslamate": "Sync Data from TeslaMate via MQTT"
}
}
}
Expand Down
Loading

0 comments on commit 4a32c69

Please sign in to comment.