From 1ea202a5bc467291ce626b57a20db40143aa91b3 Mon Sep 17 00:00:00 2001 From: rikroe <42204099+rikroe@users.noreply.github.com> Date: Mon, 29 May 2023 23:48:13 +0200 Subject: [PATCH] Add charging switch to BMW Connected Drive (#93737) Co-authored-by: rikroe --- .../components/bmw_connected_drive/switch.py | 19 ++++++ ...s_v4_vehicles_state_WBA00000000DEMO02.json | 10 ++- .../snapshots/test_diagnostics.ambr | 64 +++++++++++++++---- .../snapshots/test_switch.ambr | 12 ++++ .../bmw_connected_drive/test_switch.py | 2 + 5 files changed, 92 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/bmw_connected_drive/switch.py b/homeassistant/components/bmw_connected_drive/switch.py index 0ef7d8801af..41243ca9323 100644 --- a/homeassistant/components/bmw_connected_drive/switch.py +++ b/homeassistant/components/bmw_connected_drive/switch.py @@ -7,6 +7,7 @@ from typing import Any from bimmer_connected.models import MyBMWAPIError from bimmer_connected.vehicle import MyBMWVehicle +from bimmer_connected.vehicle.fuel_and_battery import ChargingState from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription from homeassistant.config_entries import ConfigEntry @@ -38,6 +39,15 @@ class BMWSwitchEntityDescription(SwitchEntityDescription, BMWRequiredKeysMixin): dynamic_options: Callable[[MyBMWVehicle], list[str]] | None = None +CHARGING_STATE_ON = { + ChargingState.CHARGING, + ChargingState.COMPLETE, + ChargingState.FULLY_CHARGED, + ChargingState.FINISHED_FULLY_CHARGED, + ChargingState.FINISHED_NOT_FULL, + ChargingState.TARGET_REACHED, +} + NUMBER_TYPES: list[BMWSwitchEntityDescription] = [ BMWSwitchEntityDescription( key="climate", @@ -48,6 +58,15 @@ NUMBER_TYPES: list[BMWSwitchEntityDescription] = [ remote_service_off=lambda v: v.remote_services.trigger_remote_air_conditioning_stop(), icon="mdi:fan", ), + BMWSwitchEntityDescription( + key="charging", + name="Charging", + is_available=lambda v: v.is_remote_charge_stop_enabled, + value_fn=lambda v: v.fuel_and_battery.charging_status in CHARGING_STATE_ON, + remote_service_on=lambda v: v.remote_services.trigger_charge_start(), + remote_service_off=lambda v: v.remote_services.trigger_charge_stop(), + icon="mdi:ev-station", + ), ] diff --git a/tests/components/bmw_connected_drive/fixtures/vehicles/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json b/tests/components/bmw_connected_drive/fixtures/vehicles/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json index 8a0be88edfe..a0974854295 100644 --- a/tests/components/bmw_connected_drive/fixtures/vehicles/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json +++ b/tests/components/bmw_connected_drive/fixtures/vehicles/G26/bmw-eadrax-vcs_v4_vehicles_state_WBA00000000DEMO02.json @@ -43,7 +43,11 @@ "lights": true, "lock": true, "remote360": true, - "remoteChargingCommands": {}, + "remoteChargingCommands": { + "chargingControl": ["START", "STOP"], + "flapControl": ["NOT_SUPPORTED"], + "plugControl": ["NOT_SUPPORTED"] + }, "remoteSoftwareUpgrade": true, "sendPoi": true, "specialThemeSupport": [], @@ -159,9 +163,9 @@ "electricChargingState": { "chargingConnectionType": "UNKNOWN", "chargingLevelPercent": 80, - "chargingStatus": "INVALID", + "chargingStatus": "CHARGING", "chargingTarget": 80, - "isChargerConnected": false, + "isChargerConnected": true, "range": 472, "remainingChargingMinutes": 10 }, diff --git a/tests/components/bmw_connected_drive/snapshots/test_diagnostics.ambr b/tests/components/bmw_connected_drive/snapshots/test_diagnostics.ambr index f5966afb32e..5befe3f0dcf 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_diagnostics.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_diagnostics.ambr @@ -289,6 +289,16 @@ 'lock': True, 'remote360': True, 'remoteChargingCommands': dict({ + 'chargingControl': list([ + 'START', + 'STOP', + ]), + 'flapControl': list([ + 'NOT_SUPPORTED', + ]), + 'plugControl': list([ + 'NOT_SUPPORTED', + ]), }), 'remoteSoftwareUpgrade': True, 'sendPoi': True, @@ -524,9 +534,9 @@ 'electricChargingState': dict({ 'chargingConnectionType': 'UNKNOWN', 'chargingLevelPercent': 80, - 'chargingStatus': 'INVALID', + 'chargingStatus': 'CHARGING', 'chargingTarget': 80, - 'isChargerConnected': False, + 'isChargerConnected': True, 'range': 472, 'remainingChargingMinutes': 10, }), @@ -778,9 +788,9 @@ 'charging_end_time': '2022-07-10T11:10:00+00:00', 'charging_start_time': None, 'charging_start_time_no_tz': None, - 'charging_status': 'NOT_CHARGING', + 'charging_status': 'CHARGING', 'charging_target': 80, - 'is_charger_connected': False, + 'is_charger_connected': True, 'remaining_battery_percent': 80, 'remaining_fuel': list([ None, @@ -804,8 +814,8 @@ 'has_electric_drivetrain': True, 'is_charging_plan_supported': True, 'is_lsc_enabled': True, - 'is_remote_charge_start_enabled': False, - 'is_remote_charge_stop_enabled': False, + 'is_remote_charge_start_enabled': True, + 'is_remote_charge_stop_enabled': True, 'is_remote_climate_start_enabled': True, 'is_remote_climate_stop_enabled': True, 'is_remote_horn_enabled': True, @@ -1687,6 +1697,16 @@ 'lock': True, 'remote360': True, 'remoteChargingCommands': dict({ + 'chargingControl': list([ + 'START', + 'STOP', + ]), + 'flapControl': list([ + 'NOT_SUPPORTED', + ]), + 'plugControl': list([ + 'NOT_SUPPORTED', + ]), }), 'remoteSoftwareUpgrade': True, 'sendPoi': True, @@ -1812,9 +1832,9 @@ 'electricChargingState': dict({ 'chargingConnectionType': 'UNKNOWN', 'chargingLevelPercent': 80, - 'chargingStatus': 'INVALID', + 'chargingStatus': 'CHARGING', 'chargingTarget': 80, - 'isChargerConnected': False, + 'isChargerConnected': True, 'range': 472, 'remainingChargingMinutes': 10, }), @@ -3191,6 +3211,16 @@ 'lock': True, 'remote360': True, 'remoteChargingCommands': dict({ + 'chargingControl': list([ + 'START', + 'STOP', + ]), + 'flapControl': list([ + 'NOT_SUPPORTED', + ]), + 'plugControl': list([ + 'NOT_SUPPORTED', + ]), }), 'remoteSoftwareUpgrade': True, 'sendPoi': True, @@ -3316,9 +3346,9 @@ 'electricChargingState': dict({ 'chargingConnectionType': 'UNKNOWN', 'chargingLevelPercent': 80, - 'chargingStatus': 'INVALID', + 'chargingStatus': 'CHARGING', 'chargingTarget': 80, - 'isChargerConnected': False, + 'isChargerConnected': True, 'range': 472, 'remainingChargingMinutes': 10, }), @@ -4024,6 +4054,16 @@ 'lock': True, 'remote360': True, 'remoteChargingCommands': dict({ + 'chargingControl': list([ + 'START', + 'STOP', + ]), + 'flapControl': list([ + 'NOT_SUPPORTED', + ]), + 'plugControl': list([ + 'NOT_SUPPORTED', + ]), }), 'remoteSoftwareUpgrade': True, 'sendPoi': True, @@ -4149,9 +4189,9 @@ 'electricChargingState': dict({ 'chargingConnectionType': 'UNKNOWN', 'chargingLevelPercent': 80, - 'chargingStatus': 'INVALID', + 'chargingStatus': 'CHARGING', 'chargingTarget': 80, - 'isChargerConnected': False, + 'isChargerConnected': True, 'range': 472, 'remainingChargingMinutes': 10, }), diff --git a/tests/components/bmw_connected_drive/snapshots/test_switch.ambr b/tests/components/bmw_connected_drive/snapshots/test_switch.ambr index 23d7ec2e833..de5a44637c3 100644 --- a/tests/components/bmw_connected_drive/snapshots/test_switch.ambr +++ b/tests/components/bmw_connected_drive/snapshots/test_switch.ambr @@ -13,5 +13,17 @@ 'last_updated': , 'state': 'off', }), + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Data provided by MyBMW', + 'friendly_name': 'i4 eDrive40 Charging', + 'icon': 'mdi:ev-station', + }), + 'context': , + 'entity_id': 'switch.i4_edrive40_charging', + 'last_changed': , + 'last_updated': , + 'state': 'on', + }), ]) # --- diff --git a/tests/components/bmw_connected_drive/test_switch.py b/tests/components/bmw_connected_drive/test_switch.py index 06c01e19689..26de4d3b6e8 100644 --- a/tests/components/bmw_connected_drive/test_switch.py +++ b/tests/components/bmw_connected_drive/test_switch.py @@ -36,6 +36,8 @@ async def test_entity_state_attrs( [ ("switch.i4_edrive40_climate", "ON"), ("switch.i4_edrive40_climate", "OFF"), + ("switch.i4_edrive40_charging", "ON"), + ("switch.i4_edrive40_charging", "OFF"), ], ) async def test_update_triggers_success(