diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 2043a9a6..8f879e69 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -32,4 +32,5 @@ Disable Debug Logging **Your Vehicle Details** Model: Year: -Type (ICE/PHEV/BEV): +Type (Gas/Hybrid/Electric): +Region (EU/US/CA/CN): diff --git a/.github/workflows/semanticTitle.yaml b/.github/workflows/semanticTitle.yaml index 0be63e9c..d6a30677 100644 --- a/.github/workflows/semanticTitle.yaml +++ b/.github/workflows/semanticTitle.yaml @@ -13,6 +13,6 @@ jobs: steps: # Please look up the latest version from # https://github.com/amannn/action-semantic-pull-request/releases - - uses: amannn/action-semantic-pull-request@v5.5.2 + - uses: amannn/action-semantic-pull-request@v5.5.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f83b4f83..bb73b6d3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ ci: autoupdate_commit_msg: "chore: pre-commit autoupdate" repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.5 + rev: v0.5.4 hooks: - id: ruff args: @@ -28,7 +28,7 @@ repos: - id: check-json exclude: (.vscode|.devcontainer) - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.16.0 hooks: - id: pyupgrade - repo: https://github.com/adrienverge/yamllint.git @@ -55,7 +55,7 @@ repos: - --keep-updates files: ^(/.+)?[^/]+\.py$ - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.11.0 hooks: - id: mypy args: [--strict, --ignore-missing-imports] diff --git a/custom_components/audiconnect/audi_account.py b/custom_components/audiconnect/audi_account.py index 9c7d6bdc..b6b71573 100644 --- a/custom_components/audiconnect/audi_account.py +++ b/custom_components/audiconnect/audi_account.py @@ -1,40 +1,35 @@ +import asyncio import logging + import voluptuous as vol -import asyncio -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, -) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.helpers.aiohttp_client import async_get_clientsession +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.util.dt import utcnow -from homeassistant.const import ( - CONF_PASSWORD, - CONF_USERNAME, -) -from .dashboard import Dashboard from .audi_connect_account import AudiConnectAccount, AudiConnectObserver from .audi_models import VehicleData - from .const import ( - DOMAIN, - CONF_VIN, + COMPONENTS, CONF_ACTION, - CONF_CLIMATE_TEMP_F, - CONF_CLIMATE_TEMP_C, CONF_CLIMATE_GLASS, CONF_CLIMATE_SEAT_FL, CONF_CLIMATE_SEAT_FR, CONF_CLIMATE_SEAT_RL, CONF_CLIMATE_SEAT_RR, + CONF_CLIMATE_TEMP_C, + CONF_CLIMATE_TEMP_F, CONF_REGION, CONF_SPIN, + CONF_VIN, + DOMAIN, SIGNAL_STATE_UPDATED, TRACKER_UPDATE, - COMPONENTS, UPDATE_SLEEP, ) +from .dashboard import Dashboard REFRESH_VEHICLE_DATA_FAILED_EVENT = "refresh_failed" REFRESH_VEHICLE_DATA_COMPLETED_EVENT = "refresh_completed" @@ -65,6 +60,14 @@ } ) +PLATFORMS: list[str] = [ + Platform.BINARY_SENSOR, + Platform.SENSOR, + Platform.DEVICE_TRACKER, + Platform.LOCK, + Platform.SWITCH, +] + SERVICE_REFRESH_CLOUD_DATA = "refresh_cloud_data" _LOGGER = logging.getLogger(__name__) @@ -119,7 +122,7 @@ def is_enabled(self, attr): # """Return true if the user has enabled the resource.""" # return attr in config[DOMAIN].get(CONF_RESOURCES, [attr]) - def discover_vehicles(self, vehicles): + async def discover_vehicles(self, vehicles): if len(vehicles) > 0: for vehicle in vehicles: vin = vehicle.vin.lower() @@ -149,30 +152,8 @@ def discover_vehicles(self, vehicles): if instrument._component == "lock": cfg_vehicle.locks.add(instrument) - self.hass.async_create_task( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, "sensor" - ) - ) - self.hass.async_create_task( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, "binary_sensor" - ) - ) - self.hass.async_create_task( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, "switch" - ) - ) - self.hass.async_create_task( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, "device_tracker" - ) - ) - self.hass.async_create_task( - self.hass.config_entries.async_forward_entry_setup( - self.config_entry, "lock" - ) + await self.hass.config_entries.async_forward_entry_setups( + self.config_entry, PLATFORMS ) async def update(self, now): @@ -188,7 +169,7 @@ async def update(self, now): ] if new_vehicles: _LOGGER.debug("Retrieved %d vehicle(s)", len(new_vehicles)) - self.discover_vehicles(new_vehicles) + await self.discover_vehicles(new_vehicles) async_dispatcher_send(self.hass, SIGNAL_STATE_UPDATED) diff --git a/custom_components/audiconnect/audi_connect_account.py b/custom_components/audiconnect/audi_connect_account.py index 2d0f66ab..98e5e0ad 100644 --- a/custom_components/audiconnect/audi_connect_account.py +++ b/custom_components/audiconnect/audi_connect_account.py @@ -183,7 +183,7 @@ async def refresh_vehicle_data(self, vin: str): ) return False except ClientResponseError as cre: - if cre.status in (403, 502): + if cre.status in (403, 404): _LOGGER.debug( "ClientResponseError with status %s while refreshing vehicle data for VIN: %s. Disabling refresh vehicle data support.", cre.status, @@ -564,7 +564,7 @@ async def update_vehicle_statusreport(self): except TimeoutError: raise except ClientResponseError as resp_exception: - if resp_exception.status in (403, 502): + if resp_exception.status in (403, 404): self.support_status_report = False else: self.log_exception_once( @@ -658,7 +658,7 @@ async def update_vehicle_position(self): ) raise except ClientResponseError as cre: - if cre.status in (403, 502): + if cre.status in (403, 404): _LOGGER.error( "POSITION: ClientResponseError with status %s for VIN: %s. Disabling vehicle position support.", cre.status, @@ -751,9 +751,9 @@ async def update_vehicle_climater(self): ) raise except ClientResponseError as cre: - if cre.status in (403, 502): + if cre.status in (403, 404): _LOGGER.debug( - "ClientResponseError with status %s while updating climater for VIN: %s. Disabling climater support.", + "CLIMATER: ClientResponseError with status %s while updating climater for VIN: %s. Disabling climater support.", cre.status, redacted_vin, ) @@ -793,7 +793,7 @@ async def update_vehicle_preheater(self): except TimeoutError: raise except ClientResponseError as resp_exception: - if resp_exception.status == 403 or resp_exception.status == 502: + if resp_exception.status in (403, 404): # _LOGGER.error( # "support_preheater set to False: {status}".format( # status=resp_exception.status @@ -893,7 +893,7 @@ async def update_vehicle_charger(self): except TimeoutError: raise except ClientResponseError as resp_exception: - if resp_exception.status == 403 or resp_exception.status == 502: + if resp_exception.status in (403, 404): # _LOGGER.error( # "support_charger set to False: {status}".format( # status=resp_exception.status @@ -965,7 +965,7 @@ async def update_vehicle_tripdata(self, kind: str): ) raise except ClientResponseError as cre: - if cre.status in (403, 502): + if cre.status in (403, 404): _LOGGER.debug( "ClientResponseError with status %s while updating trip data for VIN: %s. Disabling trip data support.", cre.status, diff --git a/custom_components/audiconnect/audi_models.py b/custom_components/audiconnect/audi_models.py index f6b1dc3b..1e8463e0 100644 --- a/custom_components/audiconnect/audi_models.py +++ b/custom_components/audiconnect/audi_models.py @@ -1,4 +1,5 @@ import logging +from .util import get_attr _LOGGER = logging.getLogger(__name__) @@ -261,6 +262,24 @@ def __init__(self, data): -1, ["climatisation", "auxiliaryHeatingStatus", "value", "climatisationState"], ) + # 2024 Q4 updated data structure for climate data + self._tryAppendStateWithTs( + data, + "climatisationState", + -1, + ["climatisation", "climatisationStatus", "value", "climatisationState"], + ) + self._tryAppendStateWithTs( + data, + "remainingClimatisationTime", + -1, + [ + "climatisation", + "climatisationStatus", + "value", + "remainingClimatisationTime_min", + ], + ) def _tryAppendStateWithTs(self, json, name, tsoff, loc): _LOGGER.debug( @@ -358,10 +377,10 @@ def _getFromJson(self, json, loc): def appendDoorState(self, data): _LOGGER.debug("APPEND DOOR: Starting to append doors...") - doors = data["access"]["accessStatus"]["value"]["doors"] - tsCarCapturedAccess = data["access"]["accessStatus"]["value"][ - "carCapturedTimestamp" - ] + doors = get_attr(data, "access.accessStatus.value.doors", []) + tsCarCapturedAccess = get_attr( + data, "access.accessStatus.value.carCapturedTimestamp" + ) _LOGGER.debug( "APPEND DOOR: Timestamp captured from car: %s", tsCarCapturedAccess ) @@ -411,10 +430,10 @@ def appendDoorState(self, data): def appendWindowState(self, data): _LOGGER.debug("APPEND WINDOW: Starting to append windows...") - windows = data["access"]["accessStatus"]["value"]["windows"] - tsCarCapturedAccess = data["access"]["accessStatus"]["value"][ - "carCapturedTimestamp" - ] + windows = get_attr(data, "access.accessStatus.value.windows", []) + tsCarCapturedAccess = get_attr( + data, "access.accessStatus.value.carCapturedTimestamp" + ) _LOGGER.debug( "APPEND WINDOW: Timestamp captured from car: %s", tsCarCapturedAccess ) diff --git a/custom_components/audiconnect/config_flow.py b/custom_components/audiconnect/config_flow.py index 2c174267..eb1ffa1d 100644 --- a/custom_components/audiconnect/config_flow.py +++ b/custom_components/audiconnect/config_flow.py @@ -20,6 +20,7 @@ MIN_UPDATE_INTERVAL, CONF_SCAN_INITIAL, CONF_SCAN_ACTIVE, + REGIONS, ) _LOGGER = logging.getLogger(__name__) @@ -52,7 +53,7 @@ async def async_step_user(self, user_input=None): self._username = user_input[CONF_USERNAME] self._password = user_input[CONF_PASSWORD] self._spin = user_input.get(CONF_SPIN) - self._region = user_input.get(CONF_REGION) + self._region = REGIONS[user_input.get(CONF_REGION)] self._scan_interval = user_input[CONF_SCAN_INTERVAL] try: @@ -94,7 +95,7 @@ async def async_step_user(self, user_input=None): data_schema[vol.Required(CONF_USERNAME, default=self._username)] = str data_schema[vol.Required(CONF_PASSWORD, default=self._password)] = str data_schema[vol.Optional(CONF_SPIN, default=self._spin)] = str - data_schema[vol.Optional(CONF_REGION, default=self._region)] = str + data_schema[vol.Required(CONF_REGION, default=self._region)] = vol.In(REGIONS) data_schema[ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_UPDATE_INTERVAL) ] = int @@ -116,7 +117,7 @@ async def async_step_import(self, user_input): region = "DE" if user_input.get(CONF_REGION): - region = user_input.get(CONF_REGION) + region = REGIONS[user_input.get(CONF_REGION)] scan_interval = DEFAULT_UPDATE_INTERVAL diff --git a/custom_components/audiconnect/const.py b/custom_components/audiconnect/const.py index 3a68d52c..08485784 100644 --- a/custom_components/audiconnect/const.py +++ b/custom_components/audiconnect/const.py @@ -77,3 +77,15 @@ "device_tracker": "device_tracker", "switch": "switch", } + +REGION_EUROPE: str = "DE" +REGION_CANADA: str = "CA" +REGION_USA: str = "US" +REGION_CHINA: str = "CN" + +REGIONS = { + 1: REGION_EUROPE, + 2: REGION_CANADA, + 3: REGION_USA, + 4: REGION_CHINA, +} diff --git a/custom_components/audiconnect/manifest.json b/custom_components/audiconnect/manifest.json index 6ea03965..db75d185 100644 --- a/custom_components/audiconnect/manifest.json +++ b/custom_components/audiconnect/manifest.json @@ -9,5 +9,5 @@ "issue_tracker": "https://github.com/audiconnect/audi_connect_ha/issues", "loggers": ["audiconnect"], "requirements": ["beautifulsoup4"], - "version": "1.9.0" + "version": "1.10.0" } diff --git a/readme.md b/readme.md index 88bfc445..64610499 100644 --- a/readme.md +++ b/readme.md @@ -44,7 +44,7 @@ Configuration is done through the Home Assistant UI. To add the integration, go to **Settings ➤ Devices & Services ➤ Integrations**, click **➕ Add Integration**, and search for "Audi Connect". -![Configuration](ha_config.png) +![image](https://github.com/user-attachments/assets/facff84e-f40f-4090-9e44-80f698385426) ### Configuration Variables @@ -62,7 +62,7 @@ To add the integration, go to **Settings ➤ Devices & Services ➤ Integrations **region** -- (string)(Optional) The region where your Audi Connect account is registered. +- (string)(Required) The region where your Audi Connect account is registered. - 'DE' for Europe (or leave unset) - 'US' for United States of America - 'CA' for Canada