Refactor BMW entity availability (#110294)

Co-authored-by: Richard <rikroe@users.noreply.github.com>
This commit is contained in:
Richard Kroegel 2024-04-08 09:26:50 +02:00 committed by GitHub
parent 487480dc88
commit 16fc935c87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 174 additions and 163 deletions

View File

@ -116,6 +116,7 @@ class BMWBinarySensorEntityDescription(BinarySensorEntityDescription):
value_fn: Callable[[MyBMWVehicle], bool]
attr_fn: Callable[[MyBMWVehicle, UnitSystem], dict[str, Any]] | None = None
is_available: Callable[[MyBMWVehicle], bool] = lambda v: v.is_lsc_enabled
SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
@ -174,12 +175,14 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
device_class=BinarySensorDeviceClass.BATTERY_CHARGING,
# device class power: On means power detected, Off means no power
value_fn=lambda v: v.fuel_and_battery.charging_status == ChargingState.CHARGING,
is_available=lambda v: v.has_electric_drivetrain,
),
BMWBinarySensorEntityDescription(
key="connection_status",
translation_key="connection_status",
device_class=BinarySensorDeviceClass.PLUG,
value_fn=lambda v: v.fuel_and_battery.is_charger_connected,
is_available=lambda v: v.has_electric_drivetrain,
),
BMWBinarySensorEntityDescription(
key="is_pre_entry_climatization_enabled",
@ -187,6 +190,7 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
value_fn=lambda v: v.charging_profile.is_pre_entry_climatization_enabled
if v.charging_profile
else False,
is_available=lambda v: v.has_electric_drivetrain,
),
)
@ -203,7 +207,7 @@ async def async_setup_entry(
BMWBinarySensor(coordinator, vehicle, description, hass.config.units)
for vehicle in coordinator.account.vehicles
for description in SENSOR_TYPES
if description.key in vehicle.available_attributes
if description.is_available(vehicle)
]
async_add_entities(entities)

View File

@ -51,7 +51,7 @@ class BMWLock(BMWBaseEntity, LockEntity):
super().__init__(coordinator, vehicle)
self._attr_unique_id = f"{vehicle.vin}-lock"
self.door_lock_state_available = DOOR_LOCK_STATE in vehicle.available_attributes
self.door_lock_state_available = vehicle.is_lsc_enabled
async def async_lock(self, **kwargs: Any) -> None:
"""Lock the car."""

View File

@ -36,6 +36,7 @@ class BMWSensorEntityDescription(SensorEntityDescription):
key_class: str | None = None
unit_type: str | None = None
value: Callable = lambda x, y: x
is_available: Callable[[MyBMWVehicle], bool] = lambda v: v.is_lsc_enabled
def convert_and_round(
@ -53,57 +54,63 @@ def convert_and_round(
return None
SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
SENSOR_TYPES: list[BMWSensorEntityDescription] = [
# --- Generic ---
"ac_current_limit": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="ac_current_limit",
translation_key="ac_current_limit",
key_class="charging_profile",
unit_type=UnitOfElectricCurrent.AMPERE,
entity_registry_enabled_default=False,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
"charging_start_time": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="charging_start_time",
translation_key="charging_start_time",
key_class="fuel_and_battery",
device_class=SensorDeviceClass.TIMESTAMP,
entity_registry_enabled_default=False,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
"charging_end_time": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="charging_end_time",
translation_key="charging_end_time",
key_class="fuel_and_battery",
device_class=SensorDeviceClass.TIMESTAMP,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
"charging_status": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="charging_status",
translation_key="charging_status",
key_class="fuel_and_battery",
value=lambda x, y: x.value,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
"charging_target": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="charging_target",
translation_key="charging_target",
key_class="fuel_and_battery",
unit_type=PERCENTAGE,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
"remaining_battery_percent": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="remaining_battery_percent",
translation_key="remaining_battery_percent",
key_class="fuel_and_battery",
unit_type=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
# --- Specific ---
"mileage": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="mileage",
translation_key="mileage",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
state_class=SensorStateClass.TOTAL_INCREASING,
),
"remaining_range_total": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="remaining_range_total",
translation_key="remaining_range_total",
key_class="fuel_and_battery",
@ -111,38 +118,42 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
state_class=SensorStateClass.MEASUREMENT,
),
"remaining_range_electric": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="remaining_range_electric",
translation_key="remaining_range_electric",
key_class="fuel_and_battery",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
state_class=SensorStateClass.MEASUREMENT,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
"remaining_range_fuel": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="remaining_range_fuel",
translation_key="remaining_range_fuel",
key_class="fuel_and_battery",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
state_class=SensorStateClass.MEASUREMENT,
is_available=lambda v: v.is_lsc_enabled and v.has_combustion_drivetrain,
),
"remaining_fuel": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="remaining_fuel",
translation_key="remaining_fuel",
key_class="fuel_and_battery",
unit_type=VOLUME,
value=lambda x, hass: convert_and_round(x, hass.config.units.volume, 2),
state_class=SensorStateClass.MEASUREMENT,
is_available=lambda v: v.is_lsc_enabled and v.has_combustion_drivetrain,
),
"remaining_fuel_percent": BMWSensorEntityDescription(
BMWSensorEntityDescription(
key="remaining_fuel_percent",
translation_key="remaining_fuel_percent",
key_class="fuel_and_battery",
unit_type=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
is_available=lambda v: v.is_lsc_enabled and v.has_combustion_drivetrain,
),
}
]
async def async_setup_entry(
@ -153,16 +164,12 @@ async def async_setup_entry(
"""Set up the MyBMW sensors from config entry."""
coordinator: BMWDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
entities: list[BMWSensor] = []
for vehicle in coordinator.account.vehicles:
entities.extend(
[
BMWSensor(coordinator, vehicle, description)
for attribute_name in vehicle.available_attributes
if (description := SENSOR_TYPES.get(attribute_name))
]
)
entities = [
BMWSensor(coordinator, vehicle, description)
for vehicle in coordinator.account.vehicles
for description in SENSOR_TYPES
if description.is_available(vehicle)
]
async_add_entities(entities)

View File

@ -1,34 +1,6 @@
# serializer version: 1
# name: test_entity_state_attrs
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Remaining range total',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_remaining_range_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '340',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -54,6 +26,19 @@
'last_updated': <ANY>,
'state': 'CHARGING',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Charging target',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_charging_target',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -69,6 +54,34 @@
'last_updated': <ANY>,
'state': '70',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Remaining range total',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_remaining_range_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '340',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -83,47 +96,6 @@
'last_updated': <ANY>,
'state': '340',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Charging target',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_charging_target',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Remaining range total',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_remaining_range_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '472',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -149,6 +121,19 @@
'last_updated': <ANY>,
'state': 'NOT_CHARGING',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Charging target',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_charging_target',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -164,6 +149,34 @@
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Remaining range total',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_remaining_range_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '472',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -181,15 +194,16 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Charging target',
'unit_of_measurement': '%',
'friendly_name': 'M340i xDrive Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_charging_target',
'entity_id': 'sensor.m340i_xdrive_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '80',
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
@ -208,16 +222,16 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'friendly_name': 'M340i xDrive Remaining range fuel',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.m340i_xdrive_mileage',
'entity_id': 'sensor.m340i_xdrive_remaining_range_fuel',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '1121',
'state': '629',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
@ -233,20 +247,6 @@
'last_updated': <ANY>,
'state': '40',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Remaining range fuel',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.m340i_xdrive_remaining_range_fuel',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '629',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -261,34 +261,6 @@
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining range total',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_range_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '279',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '137009',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -314,6 +286,19 @@
'last_updated': <ANY>,
'state': 'WAITING_FOR_CHARGING',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Charging target',
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_charging_target',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -329,6 +314,34 @@
'last_updated': <ANY>,
'state': '82',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Mileage',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_mileage',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '137009',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining range total',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_range_total',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '279',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
@ -346,15 +359,16 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Charging target',
'unit_of_measurement': '%',
'friendly_name': 'i3 (+ REX) Remaining range fuel',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_charging_target',
'entity_id': 'sensor.i3_rex_remaining_range_fuel',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '100',
'state': '105',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
@ -370,20 +384,6 @@
'last_updated': <ANY>,
'state': '6',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining range fuel',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_range_fuel',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '105',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',