From c393b3981700a984c6b6dad3248e354e0dceca06 Mon Sep 17 00:00:00 2001 From: Michael Benz Date: Mon, 3 Apr 2023 14:18:40 +0000 Subject: [PATCH 1/7] Initial comit of TeslaMate connection --- custom_components/tesla_custom/__init__.py | 20 ++ custom_components/tesla_custom/config_flow.py | 8 + custom_components/tesla_custom/const.py | 6 + custom_components/tesla_custom/manifest.json | 21 +- custom_components/tesla_custom/strings.json | 5 +- custom_components/tesla_custom/teslamate.py | 186 ++++++++++++++++++ custom_components/tesla_custom/text.py | 62 ++++++ 7 files changed, 301 insertions(+), 7 deletions(-) create mode 100644 custom_components/tesla_custom/teslamate.py create mode 100644 custom_components/tesla_custom/text.py diff --git a/custom_components/tesla_custom/__init__.py b/custom_components/tesla_custom/__init__.py index 2e3b8d7e..50fe2319 100644 --- a/custom_components/tesla_custom/__init__.py +++ b/custom_components/tesla_custom/__init__.py @@ -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, @@ -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__) @@ -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") @@ -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) @@ -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.""" diff --git a/custom_components/tesla_custom/config_flow.py b/custom_components/tesla_custom/config_flow.py index 5418d319..22ae454c 100644 --- a/custom_components/tesla_custom/config_flow.py +++ b/custom_components/tesla_custom/config_flow.py @@ -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, @@ -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) diff --git a/custom_components/tesla_custom/const.py b/custom_components/tesla_custom/const.py index 74ea5d37..11cdf13d 100644 --- a/custom_components/tesla_custom/const.py +++ b/custom_components/tesla_custom/const.py @@ -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 @@ -25,6 +27,7 @@ "select", "update", "number", + "text", ] AUTH_CALLBACK_PATH = "/auth/tesla/callback" @@ -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" diff --git a/custom_components/tesla_custom/manifest.json b/custom_components/tesla_custom/manifest.json index ea9bdbd6..a966cd51 100644 --- a/custom_components/tesla_custom/manifest.json +++ b/custom_components/tesla_custom/manifest.json @@ -1,9 +1,16 @@ { "domain": "tesla_custom", "name": "Tesla Custom Integration", - "codeowners": ["@alandtse"], + "codeowners": [ + "@alandtse" + ], "config_flow": true, - "dependencies": ["http"], + "dependencies": [ + "http" + ], + "after_dependencies": [ + "mqtt" + ], "dhcp": [ { "hostname": "tesla_*", @@ -21,7 +28,11 @@ "documentation": "https://github.com/alandtse/tesla/wiki", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/alandtse/tesla/issues", - "loggers": ["teslajsonpy"], - "requirements": ["teslajsonpy==3.8.0"], + "loggers": [ + "teslajsonpy" + ], + "requirements": [ + "teslajsonpy==3.8.0" + ], "version": "3.11.0" -} +} \ No newline at end of file diff --git a/custom_components/tesla_custom/strings.json b/custom_components/tesla_custom/strings.json index 8e5f0af8..6f8f3e7c 100644 --- a/custom_components/tesla_custom/strings.json +++ b/custom_components/tesla_custom/strings.json @@ -29,9 +29,10 @@ "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" } } } } -} +} \ No newline at end of file diff --git a/custom_components/tesla_custom/teslamate.py b/custom_components/tesla_custom/teslamate.py new file mode 100644 index 00000000..ea676d01 --- /dev/null +++ b/custom_components/tesla_custom/teslamate.py @@ -0,0 +1,186 @@ +"""TelsmaMate Module. + +This listens to Teslamate MQTT topics, and updates their entites +with the latest data. +""" + +import asyncio +import logging +from typing import TYPE_CHECKING + +from homeassistant.components.mqtt.models import ReceiveMessage +from homeassistant.components.mqtt.subscription import ( + async_prepare_subscribe_topics, + async_subscribe_topics, + async_unsubscribe_topics, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.storage import Store +from teslajsonpy.car import TeslaCar + +from .const import TESLAMATE_STORAGE_KEY, TESLAMATE_STORAGE_VERSION + +if TYPE_CHECKING: + from . import TeslaDataUpdateCoordinator + +logger = logging.getLogger(__name__) + +MAP_DRIVE_STATE = { + "latitude": ("latitude", float), + "longitude": ("longitude", float), + "shift_state": ("shift_state", str), + "speed": ("speed", int), + "heading": ("heading", int), +} + +MAP_CLIMATE_STATE = { + "is_climate_on": ("is_climate_on", bool), + "inside_temp": ("inside_temp", float), + "outside_temp": ("outside_temp", float), +} + +MAP_VEHICLE_STATE = { + "tpms_pressure_fl": ("tpms_pressure_fl", float), + "tpms_pressure_fr": ("tpms_pressure_fr", float), + "tpms_pressure_rl": ("tpms_pressure_rl", float), + "tpms_pressure_rr": ("tpms_pressure_rr", float), +} + + +class TeslaMate: + def __init__( + self, + hass: HomeAssistant, + coordinators: list["TeslaDataUpdateCoordinator"], + cars: dict[str, TeslaCar], + ): + self.cars = cars + self.hass = hass + self.coordinators = coordinators + + self.watchers = [] + + self._sub_state = None + self._store = Store[dict[str, str]]( + hass, TESLAMATE_STORAGE_VERSION, TESLAMATE_STORAGE_KEY + ) + + async def unload(self): + """Unload any MQTT watchers.""" + self._sub_state = async_unsubscribe_topics(self.hass, self._sub_state) + return True + + async def set_car_id(self, vin, teslamate_id): + """Set the TeslaMate Car ID.""" + if (data := await self._store.async_load()) is None: + data = {} + + if "car_map" not in data: + data["car_map"] = {} + + data["car_map"][vin] = teslamate_id + + await self._store.async_save(data) + + async def get_car_id(self, vin) -> str | None: + """Get the TeslaMate Car ID.""" + if (data := await self._store.async_load()) is None: + data = {} + + if "car_map" not in data: + data["car_map"] = {} + + result = data["car_map"].get(vin) + + return result + + async def enable(self, enable=True): + """Start Listening to MQTT topics.""" + + if enable is False: + return await self.unload() + + for vin in self.cars: + car = self.cars[vin] + teslamate_id = await self.get_car_id(vin=vin) + + if teslamate_id is not None: + await self.watch_car(car=car, teslamate_id=teslamate_id) + + return True + + async def watch_car(self, car: TeslaCar, teslamate_id: str): + """Set up MQTT watchers for a car.""" + + topics = {} + + def msg_recieved(msg: ReceiveMessage): + return asyncio.run_coroutine_threadsafe( + self.async_handle_new_data(car, msg), self.hass.loop + ).result() + + topics["car_data"] = { + "topic": f"teslamate/cars/{teslamate_id}/#", + "msg_callback": msg_recieved, + "qos": 0, + } + + self._sub_state = async_prepare_subscribe_topics( + self.hass, self._sub_state, topics + ) + + await async_subscribe_topics(self.hass, self._sub_state) + + async def async_handle_new_data(self, car: TeslaCar, msg: ReceiveMessage): + """Update Car Data from MQTT msg.""" + + mqtt_attr = msg.topic.split("/")[-1] + coordinator = self.coordinators[car.vin] + + if mqtt_attr in MAP_DRIVE_STATE: + logger.info("Setting %s from MQTT", mqtt_attr) + attr, cast = MAP_DRIVE_STATE[mqtt_attr] + self.update_drive_state(car, attr, cast(msg.payload)) + coordinator.async_update_listeners() + + elif mqtt_attr in MAP_VEHICLE_STATE: + logger.info("Setting %s from MQTT", mqtt_attr) + attr, cast = MAP_VEHICLE_STATE[mqtt_attr] + self.update_vehicle_state(car, attr, cast(msg.payload)) + coordinator.async_update_listeners() + + elif mqtt_attr in MAP_CLIMATE_STATE: + logger.info("Setting %s from MQTT", mqtt_attr) + attr, cast = MAP_CLIMATE_STATE[mqtt_attr] + self.update_climate_state(car, attr, cast(msg.payload)) + coordinator.async_update_listeners() + + @staticmethod + def update_drive_state(car, attr, value): + """Update Drive State Safely.""" + + if "drive_state" not in car._vehicle_data: + car._vehicle_data["drive_state"] = {} + + drive_state = car._vehicle_data["drive_state"] + drive_state[attr] = value + + @staticmethod + def update_vehicle_state(car, attr, value): + """Update Vehicle State Safely.""" + + if "vehicle_state" not in car._vehicle_data: + car._vehicle_data["vehicle_state"] = {} + + vehicle_state = car._vehicle_data["vehicle_state"] + vehicle_state[attr] = value + + @staticmethod + def update_climate_state(car, attr, value): + """Update Climate State Safely.""" + + if "climate_state" not in car._vehicle_data: + car._vehicle_data["climate_state"] = {} + + climate_state = car._vehicle_data["climate_state"] + climate_state[attr] = value diff --git a/custom_components/tesla_custom/text.py b/custom_components/tesla_custom/text.py new file mode 100644 index 00000000..72b283d1 --- /dev/null +++ b/custom_components/tesla_custom/text.py @@ -0,0 +1,62 @@ +"""Support for Tesla numbers.""" +from homeassistant.components.text import TextEntity, TextMode +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from teslajsonpy.car import TeslaCar + +from . import TeslaDataUpdateCoordinator +from .base import TeslaCarEntity +from .const import DOMAIN +from .teslamate import TeslaMate + + +async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entities): + """Set up the Tesla numbers by config_entry.""" + entry_data = hass.data[DOMAIN][config_entry.entry_id] + coordinators = entry_data["coordinators"] + cars = entry_data["cars"] + teslamate = entry_data["teslamate"] + entities = [] + + for vin, car in cars.items(): + coordinator = coordinators[vin] + entities.append(TeslaCarTeslaMateID(hass, car, coordinator, teslamate)) + + async_add_entities(entities, update_before_add=True) + + +class TeslaCarTeslaMateID(TeslaCarEntity, TextEntity): + """Representation of a Tesla car charge limit number.""" + + def __init__( + self, + hass: HomeAssistant, + car: TeslaCar, + coordinator: TeslaDataUpdateCoordinator, + teslamate: TeslaMate, + ) -> None: + """Initialize charge limit entity.""" + super().__init__(hass, car, coordinator) + self.type = "teslamate id" + self._attr_icon = "mdi:ev-station" + self._attr_mode = TextMode.TEXT + self._enabled_by_default = False + self._attr_entity_category = EntityCategory.CONFIG + + self.teslsmate = teslamate + self._state = None + + async def async_set_value(self, value: str) -> None: + """Update charge limit.""" + await self.teslsmate.set_car_id(self._car.vin, value) + await self.async_update_ha_state() + + async def async_update(self) -> None: + """Update the entity.""" + # Ignore manual update requests if the entity is disabled + self._state = await self.teslsmate.get_car_id(self._car.vin) + + @property + def native_value(self) -> str: + """Return charge limit.""" + return self._state From 394af8904a541e7d2d1d03797137e0e2315e0505 Mon Sep 17 00:00:00 2001 From: Michael Benz Date: Wed, 5 Apr 2023 10:53:09 +0000 Subject: [PATCH 2/7] More productionising of code. --- custom_components/tesla_custom/teslamate.py | 48 ++++++++++++++++--- custom_components/tesla_custom/text.py | 1 + .../tesla_custom/translations/en.json | 5 +- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/custom_components/tesla_custom/teslamate.py b/custom_components/tesla_custom/teslamate.py index ea676d01..1823ee31 100644 --- a/custom_components/tesla_custom/teslamate.py +++ b/custom_components/tesla_custom/teslamate.py @@ -14,6 +14,9 @@ async_subscribe_topics, async_unsubscribe_topics, ) +from homeassistant.components.mqtt import ( + mqtt_config_entry_enabled, +) from homeassistant.core import HomeAssistant from homeassistant.helpers.storage import Store from teslajsonpy.car import TeslaCar @@ -57,6 +60,7 @@ def __init__( self.cars = cars self.hass = hass self.coordinators = coordinators + self._enabled = False self.watchers = [] @@ -67,9 +71,23 @@ def __init__( async def unload(self): """Unload any MQTT watchers.""" - self._sub_state = async_unsubscribe_topics(self.hass, self._sub_state) + self._enabled = False + + if not mqtt_config_entry_enabled(self.hass): + logger.warning( + "Cannot unsub from TeslaMate as MQTT has not been configured." + ) + return None + else: + await self._unsub_mqtt() + return True + async def _unsub_mqtt(self): + """Unsub from MQTT topics.""" + logger.info("Un-subbing from MQTT Topics.") + self._sub_state = async_unsubscribe_topics(self.hass, self._sub_state) + async def set_car_id(self, vin, teslamate_id): """Set the TeslaMate Car ID.""" if (data := await self._store.async_load()) is None: @@ -100,16 +118,33 @@ async def enable(self, enable=True): if enable is False: return await self.unload() + self._enabled = True + return await self.watch_cars() + + async def watch_cars(self): + """Start listening to MQTT for updates.""" + + if self._enabled is False: + logger.info("Can't watch cars. teslaMate is not enabled.") + return None + + if not mqtt_config_entry_enabled(self.hass): + logger.warning("Cannot enable TeslaMate as MQTT has not been configured.") + return None + + logger.info("Setting up MQTT subs for Teslamate") + + # We'll unsub from all topics before we create new ones. + await self._unsub_mqtt() + for vin in self.cars: car = self.cars[vin] teslamate_id = await self.get_car_id(vin=vin) if teslamate_id is not None: - await self.watch_car(car=car, teslamate_id=teslamate_id) - - return True + await self._watch_car(car=car, teslamate_id=teslamate_id) - async def watch_car(self, car: TeslaCar, teslamate_id: str): + async def _watch_car(self, car: TeslaCar, teslamate_id: str): """Set up MQTT watchers for a car.""" topics = {} @@ -119,7 +154,8 @@ def msg_recieved(msg: ReceiveMessage): self.async_handle_new_data(car, msg), self.hass.loop ).result() - topics["car_data"] = { + sub_id = f"teslamate_{car.vin}" + topics[sub_id] = { "topic": f"teslamate/cars/{teslamate_id}/#", "msg_callback": msg_recieved, "qos": 0, diff --git a/custom_components/tesla_custom/text.py b/custom_components/tesla_custom/text.py index 72b283d1..5205127d 100644 --- a/custom_components/tesla_custom/text.py +++ b/custom_components/tesla_custom/text.py @@ -49,6 +49,7 @@ def __init__( async def async_set_value(self, value: str) -> None: """Update charge limit.""" await self.teslsmate.set_car_id(self._car.vin, value) + await self.teslsmate.watch_cars() await self.async_update_ha_state() async def async_update(self) -> None: diff --git a/custom_components/tesla_custom/translations/en.json b/custom_components/tesla_custom/translations/en.json index 8e5f0af8..6f8f3e7c 100644 --- a/custom_components/tesla_custom/translations/en.json +++ b/custom_components/tesla_custom/translations/en.json @@ -29,9 +29,10 @@ "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" } } } } -} +} \ No newline at end of file From 61e0104758f11a7816e1a097888fb0df57ce9e09 Mon Sep 17 00:00:00 2001 From: Michael Benz Date: Wed, 5 Apr 2023 10:56:44 +0000 Subject: [PATCH 3/7] Update readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 47365ac8..3744c7f2 100644 --- a/README.md +++ b/README.md @@ -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 From a0af6c6afe6cdd1d5ec6cff6e6274ef52d4a6c19 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 11:10:52 +0000 Subject: [PATCH 4/7] style: auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- custom_components/tesla_custom/manifest.json | 22 +++++-------------- custom_components/tesla_custom/strings.json | 2 +- custom_components/tesla_custom/teslamate.py | 4 +--- .../tesla_custom/translations/en.json | 2 +- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/custom_components/tesla_custom/manifest.json b/custom_components/tesla_custom/manifest.json index a966cd51..dee58602 100644 --- a/custom_components/tesla_custom/manifest.json +++ b/custom_components/tesla_custom/manifest.json @@ -1,16 +1,10 @@ { "domain": "tesla_custom", "name": "Tesla Custom Integration", - "codeowners": [ - "@alandtse" - ], + "codeowners": ["@alandtse"], "config_flow": true, - "dependencies": [ - "http" - ], - "after_dependencies": [ - "mqtt" - ], + "dependencies": ["http"], + "after_dependencies": ["mqtt"], "dhcp": [ { "hostname": "tesla_*", @@ -28,11 +22,7 @@ "documentation": "https://github.com/alandtse/tesla/wiki", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/alandtse/tesla/issues", - "loggers": [ - "teslajsonpy" - ], - "requirements": [ - "teslajsonpy==3.8.0" - ], + "loggers": ["teslajsonpy"], + "requirements": ["teslajsonpy==3.8.0"], "version": "3.11.0" -} \ No newline at end of file +} diff --git a/custom_components/tesla_custom/strings.json b/custom_components/tesla_custom/strings.json index 6f8f3e7c..da991e0a 100644 --- a/custom_components/tesla_custom/strings.json +++ b/custom_components/tesla_custom/strings.json @@ -35,4 +35,4 @@ } } } -} \ No newline at end of file +} diff --git a/custom_components/tesla_custom/teslamate.py b/custom_components/tesla_custom/teslamate.py index 1823ee31..b11bbb4b 100644 --- a/custom_components/tesla_custom/teslamate.py +++ b/custom_components/tesla_custom/teslamate.py @@ -8,15 +8,13 @@ import logging from typing import TYPE_CHECKING +from homeassistant.components.mqtt import mqtt_config_entry_enabled from homeassistant.components.mqtt.models import ReceiveMessage from homeassistant.components.mqtt.subscription import ( async_prepare_subscribe_topics, async_subscribe_topics, async_unsubscribe_topics, ) -from homeassistant.components.mqtt import ( - mqtt_config_entry_enabled, -) from homeassistant.core import HomeAssistant from homeassistant.helpers.storage import Store from teslajsonpy.car import TeslaCar diff --git a/custom_components/tesla_custom/translations/en.json b/custom_components/tesla_custom/translations/en.json index 6f8f3e7c..da991e0a 100644 --- a/custom_components/tesla_custom/translations/en.json +++ b/custom_components/tesla_custom/translations/en.json @@ -35,4 +35,4 @@ } } } -} \ No newline at end of file +} From 087f09901cf9d818a9d6ba3724d2818f7b7130b9 Mon Sep 17 00:00:00 2001 From: Michael Benz Date: Wed, 5 Apr 2023 11:31:11 +0000 Subject: [PATCH 5/7] fix pre-commit issuesl --- custom_components/tesla_custom/teslamate.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/custom_components/tesla_custom/teslamate.py b/custom_components/tesla_custom/teslamate.py index b11bbb4b..909ed2a5 100644 --- a/custom_components/tesla_custom/teslamate.py +++ b/custom_components/tesla_custom/teslamate.py @@ -49,12 +49,18 @@ class TeslaMate: + """TeslaMate Connector. + + Manages connnections to MQTT topics exposed by TeslaMate. + """ + def __init__( self, hass: HomeAssistant, coordinators: list["TeslaDataUpdateCoordinator"], cars: dict[str, TeslaCar], ): + """Init Class.""" self.cars = cars self.hass = hass self.coordinators = coordinators @@ -71,13 +77,12 @@ async def unload(self): """Unload any MQTT watchers.""" self._enabled = False - if not mqtt_config_entry_enabled(self.hass): + if mqtt_config_entry_enabled(self.hass): + await self._unsub_mqtt() + else: logger.warning( "Cannot unsub from TeslaMate as MQTT has not been configured." ) - return None - else: - await self._unsub_mqtt() return True @@ -192,6 +197,7 @@ async def async_handle_new_data(self, car: TeslaCar, msg: ReceiveMessage): @staticmethod def update_drive_state(car, attr, value): """Update Drive State Safely.""" + # pylint: disable=protected-access if "drive_state" not in car._vehicle_data: car._vehicle_data["drive_state"] = {} @@ -202,6 +208,7 @@ def update_drive_state(car, attr, value): @staticmethod def update_vehicle_state(car, attr, value): """Update Vehicle State Safely.""" + # pylint: disable=protected-access if "vehicle_state" not in car._vehicle_data: car._vehicle_data["vehicle_state"] = {} @@ -212,6 +219,7 @@ def update_vehicle_state(car, attr, value): @staticmethod def update_climate_state(car, attr, value): """Update Climate State Safely.""" + # pylint: disable=protected-access if "climate_state" not in car._vehicle_data: car._vehicle_data["climate_state"] = {} From 68e0530b64f70b28e29c63f4dfe65a9257e48fac Mon Sep 17 00:00:00 2001 From: Michael Benz Date: Wed, 5 Apr 2023 11:37:29 +0000 Subject: [PATCH 6/7] Fix Tests. --- tests/test_config_flow.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index 8d348734..726a5e34 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -17,11 +17,13 @@ from custom_components.tesla_custom.const import ( ATTR_POLLING_POLICY_CONNECTED, + 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, @@ -241,6 +243,7 @@ async def test_option_flow(hass): CONF_SCAN_INTERVAL: 350, CONF_WAKE_ON_START: True, CONF_POLLING_POLICY: ATTR_POLLING_POLICY_CONNECTED, + CONF_ENABLE_TESLAMATE: True, }, ) assert result["type"] == "create_entry" @@ -248,6 +251,7 @@ async def test_option_flow(hass): CONF_SCAN_INTERVAL: 350, CONF_WAKE_ON_START: True, CONF_POLLING_POLICY: ATTR_POLLING_POLICY_CONNECTED, + CONF_ENABLE_TESLAMATE: True, } @@ -269,6 +273,7 @@ async def test_option_flow_defaults(hass): CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL, CONF_WAKE_ON_START: DEFAULT_WAKE_ON_START, CONF_POLLING_POLICY: DEFAULT_POLLING_POLICY, + CONF_ENABLE_TESLAMATE: DEFAULT_ENABLE_TESLAMATE, } @@ -290,4 +295,5 @@ async def test_option_flow_input_floor(hass): CONF_SCAN_INTERVAL: MIN_SCAN_INTERVAL, CONF_WAKE_ON_START: DEFAULT_WAKE_ON_START, CONF_POLLING_POLICY: DEFAULT_POLLING_POLICY, + CONF_ENABLE_TESLAMATE: DEFAULT_ENABLE_TESLAMATE, } From 8f412c37b34ac494b77fdb2774c66ef60efc8c5a Mon Sep 17 00:00:00 2001 From: Michael Benz Date: Wed, 5 Apr 2023 21:42:29 +1000 Subject: [PATCH 7/7] Fix manifest file for hassfest check --- custom_components/tesla_custom/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/tesla_custom/manifest.json b/custom_components/tesla_custom/manifest.json index dee58602..b50517d6 100644 --- a/custom_components/tesla_custom/manifest.json +++ b/custom_components/tesla_custom/manifest.json @@ -1,10 +1,10 @@ { "domain": "tesla_custom", "name": "Tesla Custom Integration", + "after_dependencies": ["mqtt"], "codeowners": ["@alandtse"], "config_flow": true, "dependencies": ["http"], - "after_dependencies": ["mqtt"], "dhcp": [ { "hostname": "tesla_*",