Re-add "deactivate air conditioning" button to bmw_connected_drive (#94765)

This commit is contained in:
Richard Kroegel 2023-06-27 19:55:46 +02:00 committed by GitHub
parent 2092bd225d
commit f1a54a510c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 251 additions and 2 deletions

View File

@ -124,7 +124,6 @@ omit =
homeassistant/components/bluetooth_tracker/*
homeassistant/components/bmw_connected_drive/__init__.py
homeassistant/components/bmw_connected_drive/binary_sensor.py
homeassistant/components/bmw_connected_drive/button.py
homeassistant/components/bmw_connected_drive/coordinator.py
homeassistant/components/bmw_connected_drive/lock.py
homeassistant/components/bmw_connected_drive/notify.py

View File

@ -34,6 +34,7 @@ class BMWButtonEntityDescription(ButtonEntityDescription):
[MyBMWVehicle], Coroutine[Any, Any, RemoteServiceStatus]
] | None = None
account_function: Callable[[BMWDataUpdateCoordinator], Coroutine] | None = None
is_available: Callable[[MyBMWVehicle], bool] = lambda _: True
BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
@ -55,6 +56,13 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
icon="mdi:hvac",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(),
),
BMWButtonEntityDescription(
key="deactivate_air_conditioning",
icon="mdi:hvac-off",
name="Deactivate air conditioning",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled,
),
BMWButtonEntityDescription(
key="find_vehicle",
translation_key="find_vehicle",
@ -86,7 +94,7 @@ async def async_setup_entry(
[
BMWButton(coordinator, vehicle, description)
for description in BUTTON_TYPES
if not coordinator.read_only
if (not coordinator.read_only and description.is_available(vehicle))
or (coordinator.read_only and description.enabled_when_read_only)
]
)

View File

@ -1,9 +1,11 @@
"""Tests for the for the BMW Connected Drive integration."""
from pathlib import Path
from urllib.parse import urlparse
from bimmer_connected.api.authentication import MyBMWAuthentication
from bimmer_connected.const import (
REMOTE_SERVICE_POSITION_URL,
VEHICLE_CHARGING_DETAILS_URL,
VEHICLE_STATE_URL,
VEHICLES_URL,
@ -115,6 +117,18 @@ def mock_vehicles() -> respx.Router:
router.get(VEHICLE_CHARGING_DETAILS_URL).mock(
side_effect=vehicle_charging_sideeffect
)
# Get vehicle position after remote service
router.post(urlparse(REMOTE_SERVICE_POSITION_URL).netloc).mock(
httpx.Response(
200,
json=load_json_object_fixture(
FIXTURE_PATH / "remote_service" / "eventposition.json",
integration=BMW_DOMAIN,
),
)
)
return router

View File

@ -0,0 +1,12 @@
{
"positionData": {
"status": "OK",
"position": {
"latitude": 123.456,
"longitude": 34.5678,
"formattedAddress": "some_formatted_address",
"heading": 121
}
},
"errorDetails": null
}

View File

@ -0,0 +1,137 @@
# serializer version: 1
# name: test_entity_state_attrs
list([
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Flash lights',
'icon': 'mdi:car-light-alert',
}),
'context': <ANY>,
'entity_id': 'button.i4_edrive40_flash_lights',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Sound horn',
'icon': 'mdi:bullhorn',
}),
'context': <ANY>,
'entity_id': 'button.i4_edrive40_sound_horn',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Activate air conditioning',
'icon': 'mdi:hvac',
}),
'context': <ANY>,
'entity_id': 'button.i4_edrive40_activate_air_conditioning',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Deactivate air conditioning',
'icon': 'mdi:hvac-off',
}),
'context': <ANY>,
'entity_id': 'button.i4_edrive40_deactivate_air_conditioning',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Find vehicle',
'icon': 'mdi:crosshairs-question',
}),
'context': <ANY>,
'entity_id': 'button.i4_edrive40_find_vehicle',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i4 eDrive40 Refresh from cloud',
'icon': 'mdi:refresh',
}),
'context': <ANY>,
'entity_id': 'button.i4_edrive40_refresh_from_cloud',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Flash lights',
'icon': 'mdi:car-light-alert',
}),
'context': <ANY>,
'entity_id': 'button.i3_rex_flash_lights',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Sound horn',
'icon': 'mdi:bullhorn',
}),
'context': <ANY>,
'entity_id': 'button.i3_rex_sound_horn',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Activate air conditioning',
'icon': 'mdi:hvac',
}),
'context': <ANY>,
'entity_id': 'button.i3_rex_activate_air_conditioning',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Find vehicle',
'icon': 'mdi:crosshairs-question',
}),
'context': <ANY>,
'entity_id': 'button.i3_rex_find_vehicle',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
StateSnapshot({
'attributes': ReadOnlyDict({
'attribution': 'Data provided by MyBMW',
'friendly_name': 'i3 (+ REX) Refresh from cloud',
'icon': 'mdi:refresh',
}),
'context': <ANY>,
'entity_id': 'button.i3_rex_refresh_from_cloud',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
}),
])
# ---

View File

@ -0,0 +1,79 @@
"""Test BMW buttons."""
from bimmer_connected.vehicle.remote_services import RemoteServices
import pytest
import respx
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.bmw_connected_drive.coordinator import (
BMWDataUpdateCoordinator,
)
from homeassistant.core import HomeAssistant
from . import setup_mocked_integration
async def test_entity_state_attrs(
hass: HomeAssistant,
bmw_fixture: respx.Router,
snapshot: SnapshotAssertion,
) -> None:
"""Test button options and values."""
# Setup component
assert await setup_mocked_integration(hass)
# Get all button entities
assert hass.states.async_all("button") == snapshot
@pytest.mark.parametrize(
("entity_id"),
[
("button.i4_edrive40_flash_lights"),
("button.i4_edrive40_sound_horn"),
("button.i4_edrive40_activate_air_conditioning"),
("button.i4_edrive40_deactivate_air_conditioning"),
("button.i4_edrive40_find_vehicle"),
],
)
async def test_update_triggers_success(
hass: HomeAssistant,
entity_id: str,
bmw_fixture: respx.Router,
) -> None:
"""Test button press."""
# Setup component
assert await setup_mocked_integration(hass)
BMWDataUpdateCoordinator.async_update_listeners.reset_mock()
# Test
await hass.services.async_call(
"button",
"press",
blocking=True,
target={"entity_id": entity_id},
)
assert RemoteServices.trigger_remote_service.call_count == 1
assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 1
async def test_refresh_from_cloud(
hass: HomeAssistant,
bmw_fixture: respx.Router,
) -> None:
"""Test button press for deprecated service."""
# Setup component
assert await setup_mocked_integration(hass)
BMWDataUpdateCoordinator.async_update_listeners.reset_mock()
# Test
await hass.services.async_call(
"button",
"press",
blocking=True,
target={"entity_id": "button.i4_edrive40_refresh_from_cloud"},
)
assert RemoteServices.trigger_remote_service.call_count == 0
assert BMWDataUpdateCoordinator.async_update_listeners.call_count == 2