Adjust BMW enum sensors translations (#118754)
Co-authored-by: Richard <rikroe@users.noreply.github.com>pull/119147/head
parent
a2504dafbc
commit
43343ea44a
|
@ -28,10 +28,3 @@ SCAN_INTERVALS = {
|
||||||
"north_america": 600,
|
"north_america": 600,
|
||||||
"rest_of_world": 300,
|
"rest_of_world": 300,
|
||||||
}
|
}
|
||||||
|
|
||||||
CLIMATE_ACTIVITY_STATE: list[str] = [
|
|
||||||
"cooling",
|
|
||||||
"heating",
|
|
||||||
"inactive",
|
|
||||||
"standby",
|
|
||||||
]
|
|
||||||
|
|
|
@ -33,8 +33,8 @@ class BMWSelectEntityDescription(SelectEntityDescription):
|
||||||
dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None
|
dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None
|
||||||
|
|
||||||
|
|
||||||
SELECT_TYPES: dict[str, BMWSelectEntityDescription] = {
|
SELECT_TYPES: tuple[BMWSelectEntityDescription, ...] = (
|
||||||
"ac_limit": BMWSelectEntityDescription(
|
BMWSelectEntityDescription(
|
||||||
key="ac_limit",
|
key="ac_limit",
|
||||||
translation_key="ac_limit",
|
translation_key="ac_limit",
|
||||||
is_available=lambda v: v.is_remote_set_ac_limit_enabled,
|
is_available=lambda v: v.is_remote_set_ac_limit_enabled,
|
||||||
|
@ -48,17 +48,17 @@ SELECT_TYPES: dict[str, BMWSelectEntityDescription] = {
|
||||||
),
|
),
|
||||||
unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
),
|
),
|
||||||
"charging_mode": BMWSelectEntityDescription(
|
BMWSelectEntityDescription(
|
||||||
key="charging_mode",
|
key="charging_mode",
|
||||||
translation_key="charging_mode",
|
translation_key="charging_mode",
|
||||||
is_available=lambda v: v.is_charging_plan_supported,
|
is_available=lambda v: v.is_charging_plan_supported,
|
||||||
options=[c.value.lower() for c in ChargingMode if c != ChargingMode.UNKNOWN],
|
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(
|
remote_service=lambda v, o: v.remote_services.trigger_charging_profile_update(
|
||||||
charging_mode=ChargingMode(o)
|
charging_mode=ChargingMode(o)
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
|
@ -76,7 +76,7 @@ async def async_setup_entry(
|
||||||
entities.extend(
|
entities.extend(
|
||||||
[
|
[
|
||||||
BMWSelect(coordinator, vehicle, description)
|
BMWSelect(coordinator, vehicle, description)
|
||||||
for description in SELECT_TYPES.values()
|
for description in SELECT_TYPES
|
||||||
if description.is_available(vehicle)
|
if description.is_available(vehicle)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,6 +9,8 @@ import logging
|
||||||
|
|
||||||
from bimmer_connected.models import StrEnum, ValueWithUnit
|
from bimmer_connected.models import StrEnum, ValueWithUnit
|
||||||
from bimmer_connected.vehicle import MyBMWVehicle
|
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 (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
|
@ -29,7 +31,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import BMWBaseEntity
|
from . import BMWBaseEntity
|
||||||
from .const import CLIMATE_ACTIVITY_STATE, DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import BMWDataUpdateCoordinator
|
from .coordinator import BMWDataUpdateCoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -73,6 +75,8 @@ SENSOR_TYPES: list[BMWSensorEntityDescription] = [
|
||||||
key="charging_status",
|
key="charging_status",
|
||||||
translation_key="charging_status",
|
translation_key="charging_status",
|
||||||
key_class="fuel_and_battery",
|
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,
|
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
|
||||||
),
|
),
|
||||||
BMWSensorEntityDescription(
|
BMWSensorEntityDescription(
|
||||||
|
@ -155,7 +159,11 @@ SENSOR_TYPES: list[BMWSensorEntityDescription] = [
|
||||||
translation_key="climate_status",
|
translation_key="climate_status",
|
||||||
key_class="climate",
|
key_class="climate",
|
||||||
device_class=SensorDeviceClass.ENUM,
|
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,
|
is_available=lambda v: v.is_remote_climate_stop_enabled,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -152,7 +152,22 @@
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'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>,
|
'config_entry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
'device_id': <ANY>,
|
'device_id': <ANY>,
|
||||||
|
@ -169,7 +184,7 @@
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'Charging status',
|
'original_name': 'Charging status',
|
||||||
'platform': 'bmw_connected_drive',
|
'platform': 'bmw_connected_drive',
|
||||||
|
@ -184,7 +199,22 @@
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'attribution': 'Data provided by MyBMW',
|
'attribution': 'Data provided by MyBMW',
|
||||||
|
'device_class': 'enum',
|
||||||
'friendly_name': 'i3 (+ REX) Charging status',
|
'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>,
|
'context': <ANY>,
|
||||||
'entity_id': 'sensor.i3_rex_charging_status',
|
'entity_id': 'sensor.i3_rex_charging_status',
|
||||||
|
@ -783,7 +813,22 @@
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'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>,
|
'config_entry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
'device_id': <ANY>,
|
'device_id': <ANY>,
|
||||||
|
@ -800,7 +845,7 @@
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'Charging status',
|
'original_name': 'Charging status',
|
||||||
'platform': 'bmw_connected_drive',
|
'platform': 'bmw_connected_drive',
|
||||||
|
@ -815,7 +860,22 @@
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'attribution': 'Data provided by MyBMW',
|
'attribution': 'Data provided by MyBMW',
|
||||||
|
'device_class': 'enum',
|
||||||
'friendly_name': 'i4 eDrive40 Charging status',
|
'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>,
|
'context': <ANY>,
|
||||||
'entity_id': 'sensor.i4_edrive40_charging_status',
|
'entity_id': 'sensor.i4_edrive40_charging_status',
|
||||||
|
@ -1311,7 +1371,22 @@
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'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>,
|
'config_entry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
'device_id': <ANY>,
|
'device_id': <ANY>,
|
||||||
|
@ -1328,7 +1403,7 @@
|
||||||
'name': None,
|
'name': None,
|
||||||
'options': dict({
|
'options': dict({
|
||||||
}),
|
}),
|
||||||
'original_device_class': None,
|
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'Charging status',
|
'original_name': 'Charging status',
|
||||||
'platform': 'bmw_connected_drive',
|
'platform': 'bmw_connected_drive',
|
||||||
|
@ -1343,7 +1418,22 @@
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'attribution': 'Data provided by MyBMW',
|
'attribution': 'Data provided by MyBMW',
|
||||||
|
'device_class': 'enum',
|
||||||
'friendly_name': 'iX xDrive50 Charging status',
|
'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>,
|
'context': <ANY>,
|
||||||
'entity_id': 'sensor.ix_xdrive50_charging_status',
|
'entity_id': 'sensor.ix_xdrive50_charging_status',
|
||||||
|
|
|
@ -8,10 +8,13 @@ import pytest
|
||||||
import respx
|
import respx
|
||||||
from syrupy.assertion import SnapshotAssertion
|
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.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||||
from homeassistant.helpers import entity_registry as er
|
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
|
from . import check_remote_service_call, setup_mocked_integration
|
||||||
|
|
||||||
|
@ -152,3 +155,29 @@ async def test_service_call_fail(
|
||||||
target={"entity_id": entity_id},
|
target={"entity_id": entity_id},
|
||||||
)
|
)
|
||||||
assert hass.states.get(entity_id).state == old_value
|
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
|
||||||
|
|
|
@ -5,9 +5,13 @@ from unittest.mock import patch
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
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.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
from homeassistant.helpers.translation import async_get_translations
|
||||||
from homeassistant.util.unit_system import (
|
from homeassistant.util.unit_system import (
|
||||||
METRIC_SYSTEM as METRIC,
|
METRIC_SYSTEM as METRIC,
|
||||||
US_CUSTOMARY_SYSTEM as IMPERIAL,
|
US_CUSTOMARY_SYSTEM as IMPERIAL,
|
||||||
|
@ -77,3 +81,29 @@ async def test_unit_conversion(
|
||||||
entity = hass.states.get(entity_id)
|
entity = hass.states.get(entity_id)
|
||||||
assert entity.state == value
|
assert entity.state == value
|
||||||
assert entity.attributes.get("unit_of_measurement") == unit_of_measurement
|
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
|
||||||
|
|
Loading…
Reference in New Issue