From bec36d39145a5e0a81b37196bf5f6b34891e9509 Mon Sep 17 00:00:00 2001 From: Richard Kroegel <42204099+rikroe@users.noreply.github.com> Date: Sat, 2 Sep 2023 23:44:28 +0200 Subject: [PATCH] Add long-term statistics to BMW sensors (#99506) * Add long-term statistics to BMW ConnectedDrive sensors * Add sensor test snapshot --------- Co-authored-by: rikroe --- .../components/bmw_connected_drive/sensor.py | 8 + .../snapshots/test_sensor.ambr | 396 ++++++++++++++++++ .../bmw_connected_drive/test_sensor.py | 18 + 3 files changed, 422 insertions(+) create mode 100644 tests/components/bmw_connected_drive/snapshots/test_sensor.ambr diff --git a/homeassistant/components/bmw_connected_drive/sensor.py b/homeassistant/components/bmw_connected_drive/sensor.py index 8f5b4fb8608..62854badb20 100644 --- a/homeassistant/components/bmw_connected_drive/sensor.py +++ b/homeassistant/components/bmw_connected_drive/sensor.py @@ -13,6 +13,7 @@ from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import LENGTH, PERCENTAGE, VOLUME, UnitOfElectricCurrent @@ -94,6 +95,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { key_class="fuel_and_battery", unit_type=PERCENTAGE, device_class=SensorDeviceClass.BATTERY, + state_class=SensorStateClass.MEASUREMENT, ), # --- Specific --- "mileage": BMWSensorEntityDescription( @@ -102,6 +104,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { icon="mdi:speedometer", 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( key="remaining_range_total", @@ -110,6 +113,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { icon="mdi:map-marker-distance", unit_type=LENGTH, value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2), + state_class=SensorStateClass.MEASUREMENT, ), "remaining_range_electric": BMWSensorEntityDescription( key="remaining_range_electric", @@ -118,6 +122,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { icon="mdi:map-marker-distance", unit_type=LENGTH, value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2), + state_class=SensorStateClass.MEASUREMENT, ), "remaining_range_fuel": BMWSensorEntityDescription( key="remaining_range_fuel", @@ -126,6 +131,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { icon="mdi:map-marker-distance", unit_type=LENGTH, value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2), + state_class=SensorStateClass.MEASUREMENT, ), "remaining_fuel": BMWSensorEntityDescription( key="remaining_fuel", @@ -134,6 +140,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { icon="mdi:gas-station", unit_type=VOLUME, value=lambda x, hass: convert_and_round(x, hass.config.units.volume, 2), + state_class=SensorStateClass.MEASUREMENT, ), "remaining_fuel_percent": BMWSensorEntityDescription( key="remaining_fuel_percent", @@ -141,6 +148,7 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = { key_class="fuel_and_battery", icon="mdi:gas-station", unit_type=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, ), } diff --git a/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr b/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr new file mode 100644 index 00000000000..d64bdb32597 --- /dev/null +++ b/tests/components/bmw_connected_drive/snapshots/test_sensor.ambr @@ -0,0 +1,396 @@ +# serializer version: 1 +# name: test_entity_state_attrs + list([ + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'iX xDrive50 Remaining range total', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_remaining_range_total', + 'last_changed': , + 'last_updated': , + 'state': '340', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'iX xDrive50 Mileage', + 'icon': 'mdi:speedometer', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_mileage', + 'last_changed': , + 'last_updated': , + 'state': '1121', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'device_class': 'timestamp', + 'friendly_name': 'iX xDrive50 Charging end time', + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_charging_end_time', + 'last_changed': , + 'last_updated': , + 'state': '2023-06-22T10:40:00+00:00', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'iX xDrive50 Charging status', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_charging_status', + 'last_changed': , + 'last_updated': , + 'state': 'CHARGING', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'device_class': 'battery', + 'friendly_name': 'iX xDrive50 Remaining battery percent', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_remaining_battery_percent', + 'last_changed': , + 'last_updated': , + 'state': '70', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'iX xDrive50 Remaining range electric', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_remaining_range_electric', + 'last_changed': , + 'last_updated': , + 'state': '340', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'iX xDrive50 Charging target', + 'icon': 'mdi:battery-charging-high', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.ix_xdrive50_charging_target', + 'last_changed': , + 'last_updated': , + 'state': '80', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i4 eDrive40 Remaining range total', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_remaining_range_total', + 'last_changed': , + 'last_updated': , + 'state': '472', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i4 eDrive40 Mileage', + 'icon': 'mdi:speedometer', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_mileage', + 'last_changed': , + 'last_updated': , + 'state': '1121', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'device_class': 'timestamp', + 'friendly_name': 'i4 eDrive40 Charging end time', + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_charging_end_time', + 'last_changed': , + 'last_updated': , + 'state': '2023-06-22T10:40:00+00:00', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i4 eDrive40 Charging status', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_charging_status', + 'last_changed': , + 'last_updated': , + 'state': 'NOT_CHARGING', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'device_class': 'battery', + 'friendly_name': 'i4 eDrive40 Remaining battery percent', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_remaining_battery_percent', + 'last_changed': , + 'last_updated': , + 'state': '80', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i4 eDrive40 Remaining range electric', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_remaining_range_electric', + 'last_changed': , + 'last_updated': , + 'state': '472', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i4 eDrive40 Charging target', + 'icon': 'mdi:battery-charging-high', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.i4_edrive40_charging_target', + 'last_changed': , + 'last_updated': , + 'state': '80', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'M340i xDrive Remaining range total', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.m340i_xdrive_remaining_range_total', + 'last_changed': , + 'last_updated': , + 'state': '629', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'M340i xDrive Mileage', + 'icon': 'mdi:speedometer', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.m340i_xdrive_mileage', + 'last_changed': , + 'last_updated': , + 'state': '1121', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'M340i xDrive Remaining fuel', + 'icon': 'mdi:gas-station', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.m340i_xdrive_remaining_fuel', + 'last_changed': , + 'last_updated': , + 'state': '40', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'M340i xDrive Remaining range fuel', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.m340i_xdrive_remaining_range_fuel', + 'last_changed': , + 'last_updated': , + 'state': '629', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'M340i xDrive Remaining fuel percent', + 'icon': 'mdi:gas-station', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.m340i_xdrive_remaining_fuel_percent', + 'last_changed': , + 'last_updated': , + 'state': '80', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Remaining range total', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i3_rex_remaining_range_total', + 'last_changed': , + 'last_updated': , + 'state': '279', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Mileage', + 'icon': 'mdi:speedometer', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i3_rex_mileage', + 'last_changed': , + 'last_updated': , + 'state': '137009', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'device_class': 'timestamp', + 'friendly_name': 'i3 (+ REX) Charging end time', + }), + 'context': , + 'entity_id': 'sensor.i3_rex_charging_end_time', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Charging status', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'sensor.i3_rex_charging_status', + 'last_changed': , + 'last_updated': , + 'state': 'WAITING_FOR_CHARGING', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'device_class': 'battery', + 'friendly_name': 'i3 (+ REX) Remaining battery percent', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.i3_rex_remaining_battery_percent', + 'last_changed': , + 'last_updated': , + 'state': '82', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Remaining range electric', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i3_rex_remaining_range_electric', + 'last_changed': , + 'last_updated': , + 'state': '174', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Charging target', + 'icon': 'mdi:battery-charging-high', + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.i3_rex_charging_target', + 'last_changed': , + 'last_updated': , + 'state': '100', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Remaining fuel', + 'icon': 'mdi:gas-station', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i3_rex_remaining_fuel', + 'last_changed': , + 'last_updated': , + 'state': '6', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Remaining range fuel', + 'icon': 'mdi:map-marker-distance', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.i3_rex_remaining_range_fuel', + 'last_changed': , + 'last_updated': , + 'state': '105', + }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i3 (+ REX) Remaining fuel percent', + 'icon': 'mdi:gas-station', + 'state_class': , + 'unit_of_measurement': '%', + }), + 'context': , + 'entity_id': 'sensor.i3_rex_remaining_fuel_percent', + 'last_changed': , + 'last_updated': , + 'state': 'unknown', + }), + ]) +# --- diff --git a/tests/components/bmw_connected_drive/test_sensor.py b/tests/components/bmw_connected_drive/test_sensor.py index 95b1145d9d6..c6cb12cf047 100644 --- a/tests/components/bmw_connected_drive/test_sensor.py +++ b/tests/components/bmw_connected_drive/test_sensor.py @@ -1,5 +1,8 @@ """Test BMW sensors.""" +from freezegun import freeze_time import pytest +import respx +from syrupy.assertion import SnapshotAssertion from homeassistant.core import HomeAssistant from homeassistant.util.unit_system import ( @@ -11,6 +14,21 @@ from homeassistant.util.unit_system import ( from . import setup_mocked_integration +@freeze_time("2023-06-22 10:30:00+00:00") +async def test_entity_state_attrs( + hass: HomeAssistant, + bmw_fixture: respx.Router, + snapshot: SnapshotAssertion, +) -> None: + """Test sensor options and values..""" + + # Setup component + assert await setup_mocked_integration(hass) + + # Get all select entities + assert hass.states.async_all("sensor") == snapshot + + @pytest.mark.parametrize( ("entity_id", "unit_system", "value", "unit_of_measurement"), [