Add long-term statistics to BMW sensors (#99506)

* Add long-term statistics to BMW ConnectedDrive sensors

* Add sensor test snapshot

---------

Co-authored-by: rikroe <rikroe@users.noreply.github.com>
This commit is contained in:
Richard Kroegel 2023-09-02 23:44:28 +02:00 committed by GitHub
parent f4f78cf000
commit bec36d3914
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 422 additions and 0 deletions

View file

@ -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,
),
}

View file

@ -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': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_remaining_range_total',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '340',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Mileage',
'icon': 'mdi:speedometer',
'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_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'timestamp',
'friendly_name': 'iX xDrive50 Charging end time',
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_charging_end_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.ix_xdrive50_charging_status',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'CHARGING',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'battery',
'friendly_name': 'iX xDrive50 Remaining battery percent',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_remaining_battery_percent',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '70',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'iX xDrive50 Remaining range electric',
'icon': 'mdi:map-marker-distance',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.ix_xdrive50_remaining_range_electric',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.ix_xdrive50_charging_target',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Remaining range total',
'icon': 'mdi:map-marker-distance',
'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_updated': <ANY>,
'state': '472',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Mileage',
'icon': 'mdi:speedometer',
'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_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'timestamp',
'friendly_name': 'i4 eDrive40 Charging end time',
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_charging_end_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.i4_edrive40_charging_status',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'NOT_CHARGING',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'battery',
'friendly_name': 'i4 eDrive40 Remaining battery percent',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_remaining_battery_percent',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Remaining range electric',
'icon': 'mdi:map-marker-distance',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i4_edrive40_remaining_range_electric',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.i4_edrive40_charging_target',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Remaining range total',
'icon': 'mdi:map-marker-distance',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.m340i_xdrive_remaining_range_total',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '629',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Mileage',
'icon': 'mdi:speedometer',
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.m340i_xdrive_mileage',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '1121',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Remaining fuel',
'icon': 'mdi:gas-station',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
'context': <ANY>,
'entity_id': 'sensor.m340i_xdrive_remaining_fuel',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '40',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Remaining range fuel',
'icon': 'mdi:map-marker-distance',
'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_updated': <ANY>,
'state': '629',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'M340i xDrive Remaining fuel percent',
'icon': 'mdi:gas-station',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.m340i_xdrive_remaining_fuel_percent',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '80',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining range total',
'icon': 'mdi:map-marker-distance',
'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_updated': <ANY>,
'state': '279',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Mileage',
'icon': 'mdi:speedometer',
'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_updated': <ANY>,
'state': '137009',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'timestamp',
'friendly_name': 'i3 (+ REX) Charging end time',
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_charging_end_time',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Charging status',
'icon': 'mdi:ev-station',
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_charging_status',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'WAITING_FOR_CHARGING',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'device_class': 'battery',
'friendly_name': 'i3 (+ REX) Remaining battery percent',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_battery_percent',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '82',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining range electric',
'icon': 'mdi:map-marker-distance',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfLength.KILOMETERS: 'km'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_range_electric',
'last_changed': <ANY>,
'last_updated': <ANY>,
'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': <ANY>,
'entity_id': 'sensor.i3_rex_charging_target',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '100',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining fuel',
'icon': 'mdi:gas-station',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfVolume.LITERS: 'L'>,
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_fuel',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': '6',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining range fuel',
'icon': 'mdi:map-marker-distance',
'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_updated': <ANY>,
'state': '105',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Remaining fuel percent',
'icon': 'mdi:gas-station',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.i3_rex_remaining_fuel_percent',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
])
# ---

View file

@ -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"),
[