From 4ccdcf3a9c1d5ed00d452f8c4d25f990350e0553 Mon Sep 17 00:00:00 2001 From: Gerard Date: Wed, 8 Mar 2023 20:26:20 +0100 Subject: [PATCH 1/6] Add new attributes and remote services for EV https://github.com/bimmerconnected/bimmer_connected/releases/tag/0.12.3 https://github.com/bimmerconnected/bimmer_connected/releases/tag/0.13.0 --- custom_components/bmw_connected_drive/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/bmw_connected_drive/__init__.py b/custom_components/bmw_connected_drive/__init__.py index a47f2be..e919430 100644 --- a/custom_components/bmw_connected_drive/__init__.py +++ b/custom_components/bmw_connected_drive/__init__.py @@ -41,6 +41,7 @@ Platform.DEVICE_TRACKER, Platform.LOCK, Platform.NOTIFY, + Platform.SELECT, Platform.SENSOR, ] From d92022c3a4d1664951412cf11849a2f9d41912d4 Mon Sep 17 00:00:00 2001 From: Gerard Date: Wed, 8 Mar 2023 20:27:15 +0100 Subject: [PATCH 2/6] Update binary_sensor.py --- custom_components/bmw_connected_drive/binary_sensor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/bmw_connected_drive/binary_sensor.py b/custom_components/bmw_connected_drive/binary_sensor.py index df25efb..0de1d74 100644 --- a/custom_components/bmw_connected_drive/binary_sensor.py +++ b/custom_components/bmw_connected_drive/binary_sensor.py @@ -189,6 +189,12 @@ class BMWBinarySensorEntityDescription( icon="mdi:car-electric", value_fn=lambda v: v.fuel_and_battery.is_charger_connected, ), + BMWBinarySensorEntityDescription( + key="is_pre_entry_climatization_enabled", + name="Pre entry climatization", + icon="mdi:car-seat-heater", + value_fn=lambda v: v.charging_profile.is_pre_entry_climatization_enabled, + ), ) From 8389ab240db04d5828ed3586c187b3eae5fae0f5 Mon Sep 17 00:00:00 2001 From: Gerard Date: Wed, 8 Mar 2023 20:27:54 +0100 Subject: [PATCH 3/6] Update sensor.py --- .../bmw_connected_drive/sensor.py | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/custom_components/bmw_connected_drive/sensor.py b/custom_components/bmw_connected_drive/sensor.py index c797de9..2b76fbe 100644 --- a/custom_components/bmw_connected_drive/sensor.py +++ b/custom_components/bmw_connected_drive/sensor.py @@ -15,7 +15,7 @@ SensorEntityDescription, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import LENGTH, PERCENTAGE, VOLUME +from homeassistant.const import LENGTH, PERCENTAGE, VOLUME, UnitOfElectricCurrent from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType @@ -80,6 +80,35 @@ def convert_and_round( unit_type=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, ), + "ac_current_limit": BMWSensorEntityDescription( + key="ac_current_limit", + name="AC current limit", + key_class="charging_profile", + unit_type=UnitOfElectricCurrent.AMPERE, + icon="mdi:current-ac", + entity_registry_enabled_default=False, + ), + "charging_target": BMWSensorEntityDescription( + key="charging_target", + name="Charging target", + key_class="fuel_and_battery", + unit_type=PERCENTAGE, + icon="mdi:battery-charging-high", + ), + "charging_mode": BMWSensorEntityDescription( + key="charging_mode", + name="Charging mode", + key_class="charging_profile", + icon="mdi:ev-station", + value=lambda x, y: x.value, + ), + "charging_preferences": BMWSensorEntityDescription( + key="charging_preferences", + name="Charging preferences", + key_class="charging_profile", + icon="mdi:ev-station", + value=lambda x, y: x.value, + ), # --- Specific --- "mileage": BMWSensorEntityDescription( key="mileage", From be79eb9a5911b42a23cfa351a5e717418db3b94a Mon Sep 17 00:00:00 2001 From: Gerard Date: Wed, 8 Mar 2023 20:28:27 +0100 Subject: [PATCH 4/6] Add select.py --- .../bmw_connected_drive/select.py | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 custom_components/bmw_connected_drive/select.py diff --git a/custom_components/bmw_connected_drive/select.py b/custom_components/bmw_connected_drive/select.py new file mode 100644 index 0000000..09f5bfc --- /dev/null +++ b/custom_components/bmw_connected_drive/select.py @@ -0,0 +1,140 @@ +"""Select platform for BMW.""" +from collections.abc import Callable +from dataclasses import dataclass +import logging +from typing import Any + +from bimmer_connected.vehicle import MyBMWVehicle +from bimmer_connected.vehicle.charging_profile import ChargingMode + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE, UnitOfElectricCurrent +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from . import BMWBaseEntity +from .const import DOMAIN +from .coordinator import BMWDataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + + +@dataclass +class BMWRequiredKeysMixin: + """Mixin for required keys.""" + + current_option: Callable[[MyBMWVehicle], Any] + remote_service: Callable[[MyBMWVehicle, Any], Any] + + +@dataclass +class BMWSelectEntityDescription(SelectEntityDescription, BMWRequiredKeysMixin): + """Describes BMW sensor entity.""" + + is_available: Callable = lambda x: False + dynamic_options: Callable | None = None + + +NUMBER_TYPES: dict[str, BMWSelectEntityDescription] = { + # --- Generic --- + "target_soc": BMWSelectEntityDescription( + key="target_soc", + name="Target SoC", + is_available=lambda x: x.is_remote_set_target_soc_enabled, + options=[str(i * 5 + 20) for i in range(17)], + current_option=lambda x: str(x.fuel_and_battery.charging_target), + remote_service=lambda v, x: v.remote_services.trigger_charging_settings_update( + target_soc=int(x) + ), + icon="mdi:battery-charging-medium", + unit_of_measurement=PERCENTAGE, + ), + "ac_limit": BMWSelectEntityDescription( + key="ac_limit", + name="AC Charging Limit", + is_available=lambda x: x.is_remote_set_ac_limit_enabled, + dynamic_options=lambda x: [ + str(lim) for lim in x.charging_profile.ac_available_limits + ], + current_option=lambda x: str(x.charging_profile.ac_current_limit), # type: ignore[union-attr] + remote_service=lambda v, x: v.remote_services.trigger_charging_settings_update( + ac_limit=int(x) + ), + icon="mdi:current-ac", + unit_of_measurement=UnitOfElectricCurrent.AMPERE, + ), + "charging_mode": BMWSelectEntityDescription( + key="charging_mode", + name="Charging Mode", + is_available=lambda x: x.is_charging_plan_supported, + options=[c.value for c in ChargingMode if c != ChargingMode.UNKNOWN], + current_option=lambda x: x.charging_profile.charging_mode.value, # type: ignore[union-attr] + remote_service=lambda v, x: v.remote_services.trigger_charging_profile_update( + charging_mode=ChargingMode(x) + ), + icon="mdi:vector-point-select", + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the MyBMW lock from config entry.""" + coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + entities: list[BMWSelect] = [] + + for vehicle in coordinator.account.vehicles: + if not coordinator.read_only: + entities.extend( + [ + BMWSelect(coordinator, vehicle, description) + for description in NUMBER_TYPES.values() + if description.is_available(vehicle) + ] + ) + async_add_entities(entities) + + +class BMWSelect(BMWBaseEntity, SelectEntity): + """Representation of BMW select entity.""" + + entity_description: BMWSelectEntityDescription + + def __init__( + self, + coordinator: BMWDataUpdateCoordinator, + vehicle: MyBMWVehicle, + description: BMWSelectEntityDescription, + ) -> None: + """Initialize an BMW select.""" + super().__init__(coordinator, vehicle) + self.entity_description = description + self._attr_unique_id = f"{vehicle.vin}-{description.key}" + self._attr_options = description.options or ( + description.dynamic_options(vehicle) if description.dynamic_options else [] + ) + self._attr_current_option = description.current_option(vehicle) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + _LOGGER.debug( + "Updating select '%s' of %s", self.entity_description.key, self.vehicle.name + ) + self._attr_current_option = self.entity_description.current_option(self.vehicle) + super()._handle_coordinator_update() + + async def async_select_option(self, option: str) -> None: + """Update to the vehicle.""" + _LOGGER.debug( + "Executing '%s' on vehicle '%s' to value '%s'", + self.entity_description.key, + self.vehicle.vin, + option, + ) + await self.entity_description.remote_service(self.vehicle, option) From b10a50c8f049f5b366dc7dcfe5ac37e3068f7362 Mon Sep 17 00:00:00 2001 From: Gerard Date: Wed, 8 Mar 2023 20:29:18 +0100 Subject: [PATCH 5/6] Update manifest.json --- custom_components/bmw_connected_drive/manifest.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/bmw_connected_drive/manifest.json b/custom_components/bmw_connected_drive/manifest.json index a68e25b..de41954 100644 --- a/custom_components/bmw_connected_drive/manifest.json +++ b/custom_components/bmw_connected_drive/manifest.json @@ -1,11 +1,11 @@ { "domain": "bmw_connected_drive", "name": "BMW Connected Drive", - "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", - "requirements": ["bimmer_connected==0.12.0"], "codeowners": ["@gerard33", "@rikroe"], "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive", "iot_class": "cloud_polling", - "version": "2023.2.2-custom", - "loggers": ["bimmer_connected"] + "loggers": ["bimmer_connected"], + "requirements": ["bimmer_connected==0.13.0"], + "version": "2023.3-custom" } From aa77979d1524eed7b73110804ae38056d887d4b7 Mon Sep 17 00:00:00 2001 From: Gerard Date: Thu, 9 Mar 2023 20:31:29 +0100 Subject: [PATCH 6/6] Remove `charging_preferences` --- custom_components/bmw_connected_drive/sensor.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/custom_components/bmw_connected_drive/sensor.py b/custom_components/bmw_connected_drive/sensor.py index 2b76fbe..eb14980 100644 --- a/custom_components/bmw_connected_drive/sensor.py +++ b/custom_components/bmw_connected_drive/sensor.py @@ -102,13 +102,6 @@ def convert_and_round( icon="mdi:ev-station", value=lambda x, y: x.value, ), - "charging_preferences": BMWSensorEntityDescription( - key="charging_preferences", - name="Charging preferences", - key_class="charging_profile", - icon="mdi:ev-station", - value=lambda x, y: x.value, - ), # --- Specific --- "mileage": BMWSensorEntityDescription( key="mileage",