mirror of
https://github.com/home-assistant/core.git
synced 2025-04-29 19:57:52 +00:00
Fix displayed units for BMW Connected Drive (#76613)
* Fix displayed units * Add tests for unit conversion * Streamline test config entry init * Refactor test to pytest fixture * Fix renamed mock Co-authored-by: rikroe <rikroe@users.noreply.github.com>
This commit is contained in:
parent
1e9ede25ad
commit
cb2799bc37
@ -15,13 +15,7 @@ from homeassistant.components.sensor import (
|
|||||||
SensorEntityDescription,
|
SensorEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import LENGTH, PERCENTAGE, VOLUME
|
||||||
LENGTH_KILOMETERS,
|
|
||||||
LENGTH_MILES,
|
|
||||||
PERCENTAGE,
|
|
||||||
VOLUME_GALLONS,
|
|
||||||
VOLUME_LITERS,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
@ -39,8 +33,7 @@ class BMWSensorEntityDescription(SensorEntityDescription):
|
|||||||
"""Describes BMW sensor entity."""
|
"""Describes BMW sensor entity."""
|
||||||
|
|
||||||
key_class: str | None = None
|
key_class: str | None = None
|
||||||
unit_metric: str | None = None
|
unit_type: str | None = None
|
||||||
unit_imperial: str | None = None
|
|
||||||
value: Callable = lambda x, y: x
|
value: Callable = lambda x, y: x
|
||||||
|
|
||||||
|
|
||||||
@ -81,56 +74,49 @@ SENSOR_TYPES: dict[str, BMWSensorEntityDescription] = {
|
|||||||
"remaining_battery_percent": BMWSensorEntityDescription(
|
"remaining_battery_percent": BMWSensorEntityDescription(
|
||||||
key="remaining_battery_percent",
|
key="remaining_battery_percent",
|
||||||
key_class="fuel_and_battery",
|
key_class="fuel_and_battery",
|
||||||
unit_metric=PERCENTAGE,
|
unit_type=PERCENTAGE,
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
device_class=SensorDeviceClass.BATTERY,
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
),
|
),
|
||||||
# --- Specific ---
|
# --- Specific ---
|
||||||
"mileage": BMWSensorEntityDescription(
|
"mileage": BMWSensorEntityDescription(
|
||||||
key="mileage",
|
key="mileage",
|
||||||
icon="mdi:speedometer",
|
icon="mdi:speedometer",
|
||||||
unit_metric=LENGTH_KILOMETERS,
|
unit_type=LENGTH,
|
||||||
unit_imperial=LENGTH_MILES,
|
|
||||||
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
||||||
),
|
),
|
||||||
"remaining_range_total": BMWSensorEntityDescription(
|
"remaining_range_total": BMWSensorEntityDescription(
|
||||||
key="remaining_range_total",
|
key="remaining_range_total",
|
||||||
key_class="fuel_and_battery",
|
key_class="fuel_and_battery",
|
||||||
icon="mdi:map-marker-distance",
|
icon="mdi:map-marker-distance",
|
||||||
unit_metric=LENGTH_KILOMETERS,
|
unit_type=LENGTH,
|
||||||
unit_imperial=LENGTH_MILES,
|
|
||||||
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
||||||
),
|
),
|
||||||
"remaining_range_electric": BMWSensorEntityDescription(
|
"remaining_range_electric": BMWSensorEntityDescription(
|
||||||
key="remaining_range_electric",
|
key="remaining_range_electric",
|
||||||
key_class="fuel_and_battery",
|
key_class="fuel_and_battery",
|
||||||
icon="mdi:map-marker-distance",
|
icon="mdi:map-marker-distance",
|
||||||
unit_metric=LENGTH_KILOMETERS,
|
unit_type=LENGTH,
|
||||||
unit_imperial=LENGTH_MILES,
|
|
||||||
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
||||||
),
|
),
|
||||||
"remaining_range_fuel": BMWSensorEntityDescription(
|
"remaining_range_fuel": BMWSensorEntityDescription(
|
||||||
key="remaining_range_fuel",
|
key="remaining_range_fuel",
|
||||||
key_class="fuel_and_battery",
|
key_class="fuel_and_battery",
|
||||||
icon="mdi:map-marker-distance",
|
icon="mdi:map-marker-distance",
|
||||||
unit_metric=LENGTH_KILOMETERS,
|
unit_type=LENGTH,
|
||||||
unit_imperial=LENGTH_MILES,
|
|
||||||
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
|
||||||
),
|
),
|
||||||
"remaining_fuel": BMWSensorEntityDescription(
|
"remaining_fuel": BMWSensorEntityDescription(
|
||||||
key="remaining_fuel",
|
key="remaining_fuel",
|
||||||
key_class="fuel_and_battery",
|
key_class="fuel_and_battery",
|
||||||
icon="mdi:gas-station",
|
icon="mdi:gas-station",
|
||||||
unit_metric=VOLUME_LITERS,
|
unit_type=VOLUME,
|
||||||
unit_imperial=VOLUME_GALLONS,
|
|
||||||
value=lambda x, hass: convert_and_round(x, hass.config.units.volume, 2),
|
value=lambda x, hass: convert_and_round(x, hass.config.units.volume, 2),
|
||||||
),
|
),
|
||||||
"remaining_fuel_percent": BMWSensorEntityDescription(
|
"remaining_fuel_percent": BMWSensorEntityDescription(
|
||||||
key="remaining_fuel_percent",
|
key="remaining_fuel_percent",
|
||||||
key_class="fuel_and_battery",
|
key_class="fuel_and_battery",
|
||||||
icon="mdi:gas-station",
|
icon="mdi:gas-station",
|
||||||
unit_metric=PERCENTAGE,
|
unit_type=PERCENTAGE,
|
||||||
unit_imperial=PERCENTAGE,
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +163,12 @@ class BMWSensor(BMWBaseEntity, SensorEntity):
|
|||||||
self._attr_name = f"{vehicle.name} {description.key}"
|
self._attr_name = f"{vehicle.name} {description.key}"
|
||||||
self._attr_unique_id = f"{vehicle.vin}-{description.key}"
|
self._attr_unique_id = f"{vehicle.vin}-{description.key}"
|
||||||
|
|
||||||
# Force metric system as BMW API apparently only returns metric values now
|
# Set the correct unit of measurement based on the unit_type
|
||||||
self._attr_native_unit_of_measurement = description.unit_metric
|
if description.unit_type:
|
||||||
|
self._attr_native_unit_of_measurement = (
|
||||||
|
coordinator.hass.config.units.as_dict().get(description.unit_type)
|
||||||
|
or description.unit_type
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
|
@ -1,17 +1,29 @@
|
|||||||
"""Tests for the for the BMW Connected Drive integration."""
|
"""Tests for the for the BMW Connected Drive integration."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from bimmer_connected.account import MyBMWAccount
|
||||||
|
from bimmer_connected.api.utils import log_to_to_file
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.bmw_connected_drive.const import (
|
from homeassistant.components.bmw_connected_drive.const import (
|
||||||
CONF_READ_ONLY,
|
CONF_READ_ONLY,
|
||||||
|
CONF_REFRESH_TOKEN,
|
||||||
DOMAIN as BMW_DOMAIN,
|
DOMAIN as BMW_DOMAIN,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry, get_fixture_path, load_fixture
|
||||||
|
|
||||||
FIXTURE_USER_INPUT = {
|
FIXTURE_USER_INPUT = {
|
||||||
CONF_USERNAME: "user@domain.com",
|
CONF_USERNAME: "user@domain.com",
|
||||||
CONF_PASSWORD: "p4ssw0rd",
|
CONF_PASSWORD: "p4ssw0rd",
|
||||||
CONF_REGION: "rest_of_world",
|
CONF_REGION: "rest_of_world",
|
||||||
}
|
}
|
||||||
|
FIXTURE_REFRESH_TOKEN = "SOME_REFRESH_TOKEN"
|
||||||
|
|
||||||
FIXTURE_CONFIG_ENTRY = {
|
FIXTURE_CONFIG_ENTRY = {
|
||||||
"entry_id": "1",
|
"entry_id": "1",
|
||||||
@ -21,8 +33,82 @@ FIXTURE_CONFIG_ENTRY = {
|
|||||||
CONF_USERNAME: FIXTURE_USER_INPUT[CONF_USERNAME],
|
CONF_USERNAME: FIXTURE_USER_INPUT[CONF_USERNAME],
|
||||||
CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD],
|
CONF_PASSWORD: FIXTURE_USER_INPUT[CONF_PASSWORD],
|
||||||
CONF_REGION: FIXTURE_USER_INPUT[CONF_REGION],
|
CONF_REGION: FIXTURE_USER_INPUT[CONF_REGION],
|
||||||
|
CONF_REFRESH_TOKEN: FIXTURE_REFRESH_TOKEN,
|
||||||
},
|
},
|
||||||
"options": {CONF_READ_ONLY: False},
|
"options": {CONF_READ_ONLY: False},
|
||||||
"source": config_entries.SOURCE_USER,
|
"source": config_entries.SOURCE_USER,
|
||||||
"unique_id": f"{FIXTURE_USER_INPUT[CONF_REGION]}-{FIXTURE_USER_INPUT[CONF_REGION]}",
|
"unique_id": f"{FIXTURE_USER_INPUT[CONF_REGION]}-{FIXTURE_USER_INPUT[CONF_REGION]}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def mock_vehicles_from_fixture(account: MyBMWAccount) -> None:
|
||||||
|
"""Load MyBMWVehicle from fixtures and add them to the account."""
|
||||||
|
|
||||||
|
fixture_path = Path(get_fixture_path("", integration=BMW_DOMAIN))
|
||||||
|
|
||||||
|
fixture_vehicles_bmw = list(fixture_path.rglob("vehicles_v2_bmw_*.json"))
|
||||||
|
fixture_vehicles_mini = list(fixture_path.rglob("vehicles_v2_mini_*.json"))
|
||||||
|
|
||||||
|
# Load vehicle base lists as provided by vehicles/v2 API
|
||||||
|
vehicles = {
|
||||||
|
"bmw": [
|
||||||
|
vehicle
|
||||||
|
for bmw_file in fixture_vehicles_bmw
|
||||||
|
for vehicle in json.loads(load_fixture(bmw_file, integration=BMW_DOMAIN))
|
||||||
|
],
|
||||||
|
"mini": [
|
||||||
|
vehicle
|
||||||
|
for mini_file in fixture_vehicles_mini
|
||||||
|
for vehicle in json.loads(load_fixture(mini_file, integration=BMW_DOMAIN))
|
||||||
|
],
|
||||||
|
}
|
||||||
|
fetched_at = utcnow()
|
||||||
|
|
||||||
|
# simulate storing fingerprints
|
||||||
|
if account.config.log_response_path:
|
||||||
|
for brand in ["bmw", "mini"]:
|
||||||
|
log_to_to_file(
|
||||||
|
json.dumps(vehicles[brand]),
|
||||||
|
account.config.log_response_path,
|
||||||
|
f"vehicles_v2_{brand}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a vehicle with base + specific state as provided by state/VIN API
|
||||||
|
for vehicle_base in [vehicle for brand in vehicles.values() for vehicle in brand]:
|
||||||
|
vehicle_state_path = (
|
||||||
|
Path("vehicles")
|
||||||
|
/ vehicle_base["attributes"]["bodyType"]
|
||||||
|
/ f"state_{vehicle_base['vin']}_0.json"
|
||||||
|
)
|
||||||
|
vehicle_state = json.loads(
|
||||||
|
load_fixture(
|
||||||
|
vehicle_state_path,
|
||||||
|
integration=BMW_DOMAIN,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
account.add_vehicle(
|
||||||
|
vehicle_base,
|
||||||
|
vehicle_state,
|
||||||
|
fetched_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
# simulate storing fingerprints
|
||||||
|
if account.config.log_response_path:
|
||||||
|
log_to_to_file(
|
||||||
|
json.dumps(vehicle_state),
|
||||||
|
account.config.log_response_path,
|
||||||
|
f"state_{vehicle_base['vin']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry:
|
||||||
|
"""Mock a fully setup config entry and all components based on fixtures."""
|
||||||
|
|
||||||
|
mock_config_entry = MockConfigEntry(**FIXTURE_CONFIG_ENTRY)
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return mock_config_entry
|
||||||
|
12
tests/components/bmw_connected_drive/conftest.py
Normal file
12
tests/components/bmw_connected_drive/conftest.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
"""Fixtures for BMW tests."""
|
||||||
|
|
||||||
|
from bimmer_connected.account import MyBMWAccount
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from . import mock_vehicles_from_fixture
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def bmw_fixture(monkeypatch):
|
||||||
|
"""Patch the vehicle fixtures into a MyBMWAccount."""
|
||||||
|
monkeypatch.setattr(MyBMWAccount, "get_vehicles", mock_vehicles_from_fixture)
|
@ -0,0 +1,206 @@
|
|||||||
|
{
|
||||||
|
"capabilities": {
|
||||||
|
"climateFunction": "AIR_CONDITIONING",
|
||||||
|
"climateNow": true,
|
||||||
|
"climateTimerTrigger": "DEPARTURE_TIMER",
|
||||||
|
"horn": true,
|
||||||
|
"isBmwChargingSupported": true,
|
||||||
|
"isCarSharingSupported": false,
|
||||||
|
"isChargeNowForBusinessSupported": false,
|
||||||
|
"isChargingHistorySupported": true,
|
||||||
|
"isChargingHospitalityEnabled": false,
|
||||||
|
"isChargingLoudnessEnabled": false,
|
||||||
|
"isChargingPlanSupported": true,
|
||||||
|
"isChargingPowerLimitEnabled": false,
|
||||||
|
"isChargingSettingsEnabled": false,
|
||||||
|
"isChargingTargetSocEnabled": false,
|
||||||
|
"isClimateTimerSupported": true,
|
||||||
|
"isCustomerEsimSupported": false,
|
||||||
|
"isDCSContractManagementSupported": true,
|
||||||
|
"isDataPrivacyEnabled": false,
|
||||||
|
"isEasyChargeEnabled": false,
|
||||||
|
"isEvGoChargingSupported": false,
|
||||||
|
"isMiniChargingSupported": false,
|
||||||
|
"isNonLscFeatureEnabled": false,
|
||||||
|
"isRemoteEngineStartSupported": false,
|
||||||
|
"isRemoteHistoryDeletionSupported": false,
|
||||||
|
"isRemoteHistorySupported": true,
|
||||||
|
"isRemoteParkingSupported": false,
|
||||||
|
"isRemoteServicesActivationRequired": false,
|
||||||
|
"isRemoteServicesBookingRequired": false,
|
||||||
|
"isScanAndChargeSupported": false,
|
||||||
|
"isSustainabilitySupported": false,
|
||||||
|
"isWifiHotspotServiceSupported": false,
|
||||||
|
"lastStateCallState": "ACTIVATED",
|
||||||
|
"lights": true,
|
||||||
|
"lock": true,
|
||||||
|
"remoteChargingCommands": {},
|
||||||
|
"sendPoi": true,
|
||||||
|
"specialThemeSupport": [],
|
||||||
|
"unlock": true,
|
||||||
|
"vehicleFinder": false,
|
||||||
|
"vehicleStateSource": "LAST_STATE_CALL"
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"chargingProfile": {
|
||||||
|
"chargingControlType": "WEEKLY_PLANNER",
|
||||||
|
"chargingMode": "DELAYED_CHARGING",
|
||||||
|
"chargingPreference": "CHARGING_WINDOW",
|
||||||
|
"chargingSettings": {
|
||||||
|
"hospitality": "NO_ACTION",
|
||||||
|
"idcc": "NO_ACTION",
|
||||||
|
"targetSoc": 100
|
||||||
|
},
|
||||||
|
"climatisationOn": false,
|
||||||
|
"departureTimes": [
|
||||||
|
{
|
||||||
|
"action": "DEACTIVATE",
|
||||||
|
"id": 1,
|
||||||
|
"timeStamp": {
|
||||||
|
"hour": 7,
|
||||||
|
"minute": 35
|
||||||
|
},
|
||||||
|
"timerWeekDays": [
|
||||||
|
"MONDAY",
|
||||||
|
"TUESDAY",
|
||||||
|
"WEDNESDAY",
|
||||||
|
"THURSDAY",
|
||||||
|
"FRIDAY"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "DEACTIVATE",
|
||||||
|
"id": 2,
|
||||||
|
"timeStamp": {
|
||||||
|
"hour": 18,
|
||||||
|
"minute": 0
|
||||||
|
},
|
||||||
|
"timerWeekDays": [
|
||||||
|
"MONDAY",
|
||||||
|
"TUESDAY",
|
||||||
|
"WEDNESDAY",
|
||||||
|
"THURSDAY",
|
||||||
|
"FRIDAY",
|
||||||
|
"SATURDAY",
|
||||||
|
"SUNDAY"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "DEACTIVATE",
|
||||||
|
"id": 3,
|
||||||
|
"timeStamp": {
|
||||||
|
"hour": 7,
|
||||||
|
"minute": 0
|
||||||
|
},
|
||||||
|
"timerWeekDays": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "DEACTIVATE",
|
||||||
|
"id": 4,
|
||||||
|
"timerWeekDays": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reductionOfChargeCurrent": {
|
||||||
|
"end": {
|
||||||
|
"hour": 1,
|
||||||
|
"minute": 30
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"hour": 18,
|
||||||
|
"minute": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkControlMessages": [],
|
||||||
|
"climateTimers": [
|
||||||
|
{
|
||||||
|
"departureTime": {
|
||||||
|
"hour": 6,
|
||||||
|
"minute": 40
|
||||||
|
},
|
||||||
|
"isWeeklyTimer": true,
|
||||||
|
"timerAction": "ACTIVATE",
|
||||||
|
"timerWeekDays": ["THURSDAY", "SUNDAY"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"departureTime": {
|
||||||
|
"hour": 12,
|
||||||
|
"minute": 50
|
||||||
|
},
|
||||||
|
"isWeeklyTimer": false,
|
||||||
|
"timerAction": "ACTIVATE",
|
||||||
|
"timerWeekDays": ["MONDAY"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"departureTime": {
|
||||||
|
"hour": 18,
|
||||||
|
"minute": 59
|
||||||
|
},
|
||||||
|
"isWeeklyTimer": true,
|
||||||
|
"timerAction": "DEACTIVATE",
|
||||||
|
"timerWeekDays": ["WEDNESDAY"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"combustionFuelLevel": {
|
||||||
|
"range": 105,
|
||||||
|
"remainingFuelLiters": 6,
|
||||||
|
"remainingFuelPercent": 65
|
||||||
|
},
|
||||||
|
"currentMileage": 137009,
|
||||||
|
"doorsState": {
|
||||||
|
"combinedSecurityState": "UNLOCKED",
|
||||||
|
"combinedState": "CLOSED",
|
||||||
|
"hood": "CLOSED",
|
||||||
|
"leftFront": "CLOSED",
|
||||||
|
"leftRear": "CLOSED",
|
||||||
|
"rightFront": "CLOSED",
|
||||||
|
"rightRear": "CLOSED",
|
||||||
|
"trunk": "CLOSED"
|
||||||
|
},
|
||||||
|
"driverPreferences": {
|
||||||
|
"lscPrivacyMode": "OFF"
|
||||||
|
},
|
||||||
|
"electricChargingState": {
|
||||||
|
"chargingConnectionType": "CONDUCTIVE",
|
||||||
|
"chargingLevelPercent": 82,
|
||||||
|
"chargingStatus": "WAITING_FOR_CHARGING",
|
||||||
|
"chargingTarget": 100,
|
||||||
|
"isChargerConnected": true,
|
||||||
|
"range": 174
|
||||||
|
},
|
||||||
|
"isLeftSteering": true,
|
||||||
|
"isLscSupported": true,
|
||||||
|
"lastFetched": "2022-06-22T14:24:23.982Z",
|
||||||
|
"lastUpdatedAt": "2022-06-22T13:58:52Z",
|
||||||
|
"range": 174,
|
||||||
|
"requiredServices": [
|
||||||
|
{
|
||||||
|
"dateTime": "2022-10-01T00:00:00.000Z",
|
||||||
|
"description": "Next service due by the specified date.",
|
||||||
|
"status": "OK",
|
||||||
|
"type": "BRAKE_FLUID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dateTime": "2023-05-01T00:00:00.000Z",
|
||||||
|
"description": "Next vehicle check due after the specified distance or date.",
|
||||||
|
"status": "OK",
|
||||||
|
"type": "VEHICLE_CHECK"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dateTime": "2023-05-01T00:00:00.000Z",
|
||||||
|
"description": "Next state inspection due by the specified date.",
|
||||||
|
"status": "OK",
|
||||||
|
"type": "VEHICLE_TUV"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"roofState": {
|
||||||
|
"roofState": "CLOSED",
|
||||||
|
"roofStateType": "SUN_ROOF"
|
||||||
|
},
|
||||||
|
"windowsState": {
|
||||||
|
"combinedState": "CLOSED",
|
||||||
|
"leftFront": "CLOSED",
|
||||||
|
"rightFront": "CLOSED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"appVehicleType": "CONNECTED",
|
||||||
|
"attributes": {
|
||||||
|
"a4aType": "USB_ONLY",
|
||||||
|
"bodyType": "I01",
|
||||||
|
"brand": "BMW_I",
|
||||||
|
"color": 4284110934,
|
||||||
|
"countryOfOrigin": "CZ",
|
||||||
|
"driveTrain": "ELECTRIC_WITH_RANGE_EXTENDER",
|
||||||
|
"driverGuideInfo": {
|
||||||
|
"androidAppScheme": "com.bmwgroup.driversguide.row",
|
||||||
|
"androidStoreUrl": "https://play.google.com/store/apps/details?id=com.bmwgroup.driversguide.row",
|
||||||
|
"iosAppScheme": "bmwdriversguide:///open",
|
||||||
|
"iosStoreUrl": "https://apps.apple.com/de/app/id714042749?mt=8"
|
||||||
|
},
|
||||||
|
"headUnitType": "NBT",
|
||||||
|
"hmiVersion": "ID4",
|
||||||
|
"lastFetched": "2022-07-10T09:25:53.104Z",
|
||||||
|
"model": "i3 (+ REX)",
|
||||||
|
"softwareVersionCurrent": {
|
||||||
|
"iStep": 510,
|
||||||
|
"puStep": {
|
||||||
|
"month": 11,
|
||||||
|
"year": 21
|
||||||
|
},
|
||||||
|
"seriesCluster": "I001"
|
||||||
|
},
|
||||||
|
"softwareVersionExFactory": {
|
||||||
|
"iStep": 502,
|
||||||
|
"puStep": {
|
||||||
|
"month": 3,
|
||||||
|
"year": 15
|
||||||
|
},
|
||||||
|
"seriesCluster": "I001"
|
||||||
|
},
|
||||||
|
"year": 2015
|
||||||
|
},
|
||||||
|
"mappingInfo": {
|
||||||
|
"isAssociated": false,
|
||||||
|
"isLmmEnabled": false,
|
||||||
|
"isPrimaryUser": true,
|
||||||
|
"mappingStatus": "CONFIRMED"
|
||||||
|
},
|
||||||
|
"vin": "WBY00000000REXI01"
|
||||||
|
}
|
||||||
|
]
|
@ -12,15 +12,11 @@ from homeassistant.components.bmw_connected_drive.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.const import CONF_USERNAME
|
from homeassistant.const import CONF_USERNAME
|
||||||
|
|
||||||
from . import FIXTURE_CONFIG_ENTRY, FIXTURE_USER_INPUT
|
from . import FIXTURE_CONFIG_ENTRY, FIXTURE_REFRESH_TOKEN, FIXTURE_USER_INPUT
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
FIXTURE_REFRESH_TOKEN = "SOME_REFRESH_TOKEN"
|
FIXTURE_COMPLETE_ENTRY = FIXTURE_CONFIG_ENTRY["data"]
|
||||||
FIXTURE_COMPLETE_ENTRY = {
|
|
||||||
**FIXTURE_USER_INPUT,
|
|
||||||
CONF_REFRESH_TOKEN: FIXTURE_REFRESH_TOKEN,
|
|
||||||
}
|
|
||||||
FIXTURE_IMPORT_ENTRY = {**FIXTURE_USER_INPUT, CONF_REFRESH_TOKEN: None}
|
FIXTURE_IMPORT_ENTRY = {**FIXTURE_USER_INPUT, CONF_REFRESH_TOKEN: None}
|
||||||
|
|
||||||
|
|
||||||
|
52
tests/components/bmw_connected_drive/test_sensor.py
Normal file
52
tests/components/bmw_connected_drive/test_sensor.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
"""Test BMW sensors."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.util.unit_system import (
|
||||||
|
IMPERIAL_SYSTEM as IMPERIAL,
|
||||||
|
METRIC_SYSTEM as METRIC,
|
||||||
|
UnitSystem,
|
||||||
|
)
|
||||||
|
|
||||||
|
from . import setup_mocked_integration
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"entity_id,unit_system,value,unit_of_measurement",
|
||||||
|
[
|
||||||
|
("sensor.i3_rex_remaining_range_total", METRIC, "279", "km"),
|
||||||
|
("sensor.i3_rex_remaining_range_total", IMPERIAL, "173.36", "mi"),
|
||||||
|
("sensor.i3_rex_mileage", METRIC, "137009", "km"),
|
||||||
|
("sensor.i3_rex_mileage", IMPERIAL, "85133.42", "mi"),
|
||||||
|
("sensor.i3_rex_remaining_battery_percent", METRIC, "82", "%"),
|
||||||
|
("sensor.i3_rex_remaining_battery_percent", IMPERIAL, "82", "%"),
|
||||||
|
("sensor.i3_rex_remaining_range_electric", METRIC, "174", "km"),
|
||||||
|
("sensor.i3_rex_remaining_range_electric", IMPERIAL, "108.12", "mi"),
|
||||||
|
("sensor.i3_rex_remaining_fuel", METRIC, "6", "L"),
|
||||||
|
("sensor.i3_rex_remaining_fuel", IMPERIAL, "1.59", "gal"),
|
||||||
|
("sensor.i3_rex_remaining_range_fuel", METRIC, "105", "km"),
|
||||||
|
("sensor.i3_rex_remaining_range_fuel", IMPERIAL, "65.24", "mi"),
|
||||||
|
("sensor.i3_rex_remaining_fuel_percent", METRIC, "65", "%"),
|
||||||
|
("sensor.i3_rex_remaining_fuel_percent", IMPERIAL, "65", "%"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_unit_conversion(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_id: str,
|
||||||
|
unit_system: UnitSystem,
|
||||||
|
value: str,
|
||||||
|
unit_of_measurement: str,
|
||||||
|
bmw_fixture,
|
||||||
|
) -> None:
|
||||||
|
"""Test conversion between metric and imperial units for sensors."""
|
||||||
|
|
||||||
|
# Set unit system
|
||||||
|
hass.config.units = unit_system
|
||||||
|
|
||||||
|
# Setup component
|
||||||
|
assert await setup_mocked_integration(hass)
|
||||||
|
|
||||||
|
# Test
|
||||||
|
entity = hass.states.get(entity_id)
|
||||||
|
assert entity.state == value
|
||||||
|
assert entity.attributes.get("unit_of_measurement") == unit_of_measurement
|
Loading…
x
Reference in New Issue
Block a user