Skip to content

Commit

Permalink
Merge pull request #56 from bimmerconnected/new-ev-attributes
Browse files Browse the repository at this point in the history
Add new attributes and remote services for EV
  • Loading branch information
gerard33 authored Mar 12, 2023
2 parents 385baa8 + aa77979 commit e2b1749
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 5 deletions.
1 change: 1 addition & 0 deletions custom_components/bmw_connected_drive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
Platform.DEVICE_TRACKER,
Platform.LOCK,
Platform.NOTIFY,
Platform.SELECT,
Platform.SENSOR,
]

Expand Down
6 changes: 6 additions & 0 deletions custom_components/bmw_connected_drive/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
)


Expand Down
8 changes: 4 additions & 4 deletions custom_components/bmw_connected_drive/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
140 changes: 140 additions & 0 deletions custom_components/bmw_connected_drive/select.py
Original file line number Diff line number Diff line change
@@ -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)
24 changes: 23 additions & 1 deletion custom_components/bmw_connected_drive/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -80,6 +80,28 @@ 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,
),
# --- Specific ---
"mileage": BMWSensorEntityDescription(
key="mileage",
Expand Down

0 comments on commit e2b1749

Please sign in to comment.