Skip to content

Commit

Permalink
Adjust BMW enum sensors translations (#118754)
Browse files Browse the repository at this point in the history
Co-authored-by: Richard <rikroe@users.noreply.github.com>
  • Loading branch information
rikroe and rikroe authored Jun 8, 2024
1 parent a2504da commit 43343ea
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 21 deletions.
7 changes: 0 additions & 7 deletions homeassistant/components/bmw_connected_drive/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,3 @@
"north_america": 600,
"rest_of_world": 300,
}

CLIMATE_ACTIVITY_STATE: list[str] = [
"cooling",
"heating",
"inactive",
"standby",
]
12 changes: 6 additions & 6 deletions homeassistant/components/bmw_connected_drive/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ class BMWSelectEntityDescription(SelectEntityDescription):
dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None


SELECT_TYPES: dict[str, BMWSelectEntityDescription] = {
"ac_limit": BMWSelectEntityDescription(
SELECT_TYPES: tuple[BMWSelectEntityDescription, ...] = (
BMWSelectEntityDescription(
key="ac_limit",
translation_key="ac_limit",
is_available=lambda v: v.is_remote_set_ac_limit_enabled,
Expand All @@ -48,17 +48,17 @@ class BMWSelectEntityDescription(SelectEntityDescription):
),
unit_of_measurement=UnitOfElectricCurrent.AMPERE,
),
"charging_mode": BMWSelectEntityDescription(
BMWSelectEntityDescription(
key="charging_mode",
translation_key="charging_mode",
is_available=lambda v: v.is_charging_plan_supported,
options=[c.value.lower() for c in ChargingMode if c != ChargingMode.UNKNOWN],
current_option=lambda v: str(v.charging_profile.charging_mode.value).lower(), # type: ignore[union-attr]
current_option=lambda v: v.charging_profile.charging_mode.value.lower(), # type: ignore[union-attr]
remote_service=lambda v, o: v.remote_services.trigger_charging_profile_update(
charging_mode=ChargingMode(o)
),
),
}
)


async def async_setup_entry(
Expand All @@ -76,7 +76,7 @@ async def async_setup_entry(
entities.extend(
[
BMWSelect(coordinator, vehicle, description)
for description in SELECT_TYPES.values()
for description in SELECT_TYPES
if description.is_available(vehicle)
]
)
Expand Down
12 changes: 10 additions & 2 deletions homeassistant/components/bmw_connected_drive/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from bimmer_connected.models import StrEnum, ValueWithUnit
from bimmer_connected.vehicle import MyBMWVehicle
from bimmer_connected.vehicle.climate import ClimateActivityState
from bimmer_connected.vehicle.fuel_and_battery import ChargingState

from homeassistant.components.sensor import (
SensorDeviceClass,
Expand All @@ -29,7 +31,7 @@
from homeassistant.util import dt as dt_util

from . import BMWBaseEntity
from .const import CLIMATE_ACTIVITY_STATE, DOMAIN
from .const import DOMAIN
from .coordinator import BMWDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -73,6 +75,8 @@ class BMWSensorEntityDescription(SensorEntityDescription):
key="charging_status",
translation_key="charging_status",
key_class="fuel_and_battery",
device_class=SensorDeviceClass.ENUM,
options=[s.value.lower() for s in ChargingState if s != ChargingState.UNKNOWN],
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
BMWSensorEntityDescription(
Expand Down Expand Up @@ -155,7 +159,11 @@ class BMWSensorEntityDescription(SensorEntityDescription):
translation_key="climate_status",
key_class="climate",
device_class=SensorDeviceClass.ENUM,
options=CLIMATE_ACTIVITY_STATE,
options=[
s.value.lower()
for s in ClimateActivityState
if s != ClimateActivityState.UNKNOWN
],
is_available=lambda v: v.is_remote_climate_stop_enabled,
),
]
Expand Down
102 changes: 96 additions & 6 deletions tests/components/bmw_connected_drive/snapshots/test_sensor.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,22 @@
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'capabilities': dict({
'options': list([
'default',
'charging',
'error',
'complete',
'fully_charged',
'finished_fully_charged',
'finished_not_full',
'invalid',
'not_charging',
'plugged_in',
'waiting_for_charging',
'target_reached',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
Expand All @@ -169,7 +184,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Charging status',
'platform': 'bmw_connected_drive',
Expand All @@ -184,7 +199,22 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'enum',
'friendly_name': 'i3 (+ REX) Charging status',
'options': list([
'default',
'charging',
'error',
'complete',
'fully_charged',
'finished_fully_charged',
'finished_not_full',
'invalid',
'not_charging',
'plugged_in',
'waiting_for_charging',
'target_reached',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_charging_status',
Expand Down Expand Up @@ -783,7 +813,22 @@
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'capabilities': dict({
'options': list([
'default',
'charging',
'error',
'complete',
'fully_charged',
'finished_fully_charged',
'finished_not_full',
'invalid',
'not_charging',
'plugged_in',
'waiting_for_charging',
'target_reached',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
Expand All @@ -800,7 +845,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Charging status',
'platform': 'bmw_connected_drive',
Expand All @@ -815,7 +860,22 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'enum',
'friendly_name': 'i4 eDrive40 Charging status',
'options': list([
'default',
'charging',
'error',
'complete',
'fully_charged',
'finished_fully_charged',
'finished_not_full',
'invalid',
'not_charging',
'plugged_in',
'waiting_for_charging',
'target_reached',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_charging_status',
Expand Down Expand Up @@ -1311,7 +1371,22 @@
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'capabilities': dict({
'options': list([
'default',
'charging',
'error',
'complete',
'fully_charged',
'finished_fully_charged',
'finished_not_full',
'invalid',
'not_charging',
'plugged_in',
'waiting_for_charging',
'target_reached',
]),
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
Expand All @@ -1328,7 +1403,7 @@
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Charging status',
'platform': 'bmw_connected_drive',
Expand All @@ -1343,7 +1418,22 @@
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'enum',
'friendly_name': 'iX xDrive50 Charging status',
'options': list([
'default',
'charging',
'error',
'complete',
'fully_charged',
'finished_fully_charged',
'finished_not_full',
'invalid',
'not_charging',
'plugged_in',
'waiting_for_charging',
'target_reached',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_charging_status',
Expand Down
29 changes: 29 additions & 0 deletions tests/components/bmw_connected_drive/test_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
import respx
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
from homeassistant.components.bmw_connected_drive.select import SELECT_TYPES
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.translation import async_get_translations

from . import check_remote_service_call, setup_mocked_integration

Expand Down Expand Up @@ -152,3 +155,29 @@ async def test_service_call_fail(
target={"entity_id": entity_id},
)
assert hass.states.get(entity_id).state == old_value


@pytest.mark.usefixtures("bmw_fixture")
async def test_entity_option_translations(
hass: HomeAssistant,
) -> None:
"""Ensure all enum sensor values are translated."""

# Setup component to load translations
assert await setup_mocked_integration(hass)

prefix = f"component.{BMW_DOMAIN}.entity.{Platform.SELECT.value}"

translations = await async_get_translations(hass, "en", "entity", [BMW_DOMAIN])
translation_states = {
k for k in translations if k.startswith(prefix) and ".state." in k
}

sensor_options = {
f"{prefix}.{entity_description.translation_key}.state.{option}"
for entity_description in SELECT_TYPES
if entity_description.options
for option in entity_description.options
}

assert sensor_options == translation_states
30 changes: 30 additions & 0 deletions tests/components/bmw_connected_drive/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
from homeassistant.components.bmw_connected_drive.sensor import SENSOR_TYPES
from homeassistant.components.sensor.const import SensorDeviceClass
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.translation import async_get_translations
from homeassistant.util.unit_system import (
METRIC_SYSTEM as METRIC,
US_CUSTOMARY_SYSTEM as IMPERIAL,
Expand Down Expand Up @@ -77,3 +81,29 @@ async def test_unit_conversion(
entity = hass.states.get(entity_id)
assert entity.state == value
assert entity.attributes.get("unit_of_measurement") == unit_of_measurement


@pytest.mark.usefixtures("bmw_fixture")
async def test_entity_option_translations(
hass: HomeAssistant,
) -> None:
"""Ensure all enum sensor values are translated."""

# Setup component to load translations
assert await setup_mocked_integration(hass)

prefix = f"component.{BMW_DOMAIN}.entity.{Platform.SENSOR.value}"

translations = await async_get_translations(hass, "en", "entity", [BMW_DOMAIN])
translation_states = {
k for k in translations if k.startswith(prefix) and ".state." in k
}

sensor_options = {
f"{prefix}.{entity_description.translation_key}.state.{option}"
for entity_description in SENSOR_TYPES
if entity_description.device_class == SensorDeviceClass.ENUM
for option in entity_description.options
}

assert sensor_options == translation_states

0 comments on commit 43343ea

Please sign in to comment.