Add switch platform to Tessie (#106153)

* Add switch platform

* Make functions mandatory

* Underscores

* Improvements
This commit is contained in:
Brett Adams 2023-12-21 15:34:52 +10:00 committed by GitHub
parent 7c5824b4f3
commit e2cf4244ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 192 additions and 1 deletions

View File

@ -14,7 +14,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import TessieDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]
_LOGGER = logging.getLogger(__name__)

View File

@ -160,6 +160,23 @@
"vehicle_state_tpms_soft_warning_rr": {
"name": "Tire pressure warning rear right"
}
},
"switch": {
"charge_state_charge_enable_request": {
"name": "Charge"
},
"climate_state_defrost_mode": {
"name": "Defrost mode"
},
"vehicle_state_sentry_mode": {
"name": "Sentry mode"
},
"vehicle_state_valet_mode": {
"name": "Valet mode"
},
"climate_state_steering_wheel_heater": {
"name": "Steering wheel heater"
}
}
}
}

View File

@ -0,0 +1,121 @@
"""Switch platform for Tessie integration."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any
from tessie_api import (
disable_sentry_mode,
disable_valet_mode,
enable_sentry_mode,
enable_valet_mode,
start_charging,
start_defrost,
start_steering_wheel_heater,
stop_charging,
stop_defrost,
stop_steering_wheel_heater,
)
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
SwitchEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import TessieDataUpdateCoordinator
from .entity import TessieEntity
@dataclass(frozen=True, kw_only=True)
class TessieSwitchEntityDescription(SwitchEntityDescription):
"""Describes Tessie Switch entity."""
on_func: Callable
off_func: Callable
device_class: SwitchDeviceClass = SwitchDeviceClass.SWITCH
DESCRIPTIONS: tuple[TessieSwitchEntityDescription, ...] = (
TessieSwitchEntityDescription(
key="charge_state_charge_enable_request",
on_func=start_charging,
off_func=stop_charging,
icon="mdi:ev-station",
),
TessieSwitchEntityDescription(
key="climate_state_defrost_mode",
on_func=start_defrost,
off_func=stop_defrost,
icon="mdi:snowflake",
),
TessieSwitchEntityDescription(
key="vehicle_state_sentry_mode",
on_func=enable_sentry_mode,
off_func=disable_sentry_mode,
icon="mdi:shield-car",
),
TessieSwitchEntityDescription(
key="vehicle_state_valet_mode",
on_func=enable_valet_mode,
off_func=disable_valet_mode,
icon="mdi:car-key",
),
TessieSwitchEntityDescription(
key="climate_state_steering_wheel_heater",
on_func=start_steering_wheel_heater,
off_func=stop_steering_wheel_heater,
icon="mdi:steering",
),
)
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Tessie Switch platform from a config entry."""
coordinators = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[
TessieSwitchEntity(coordinator, description)
for coordinator in coordinators
for description in DESCRIPTIONS
if description.key in coordinator.data
]
)
class TessieSwitchEntity(TessieEntity, SwitchEntity):
"""Base class for Tessie Switch."""
entity_description: TessieSwitchEntityDescription
def __init__(
self,
coordinator: TessieDataUpdateCoordinator,
description: TessieSwitchEntityDescription,
) -> None:
"""Initialize the Switch."""
super().__init__(coordinator, description.key)
self.entity_description = description
@property
def is_on(self) -> bool:
"""Return the state of the Switch."""
return self._value
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the Switch."""
await self.run(self.entity_description.on_func)
self.set((self.entity_description.key, True))
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the Switch."""
await self.run(self.entity_description.off_func)
self.set((self.entity_description.key, False))

View File

@ -0,0 +1,53 @@
"""Test the Tessie switch platform."""
from unittest.mock import patch
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.components.tessie.switch import DESCRIPTIONS
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON
from homeassistant.core import HomeAssistant
from .common import TEST_VEHICLE_STATE_ONLINE, setup_platform
async def test_switches(hass: HomeAssistant) -> None:
"""Tests that the switches are correct."""
assert len(hass.states.async_all("switch")) == 0
await setup_platform(hass)
assert len(hass.states.async_all("switch")) == len(DESCRIPTIONS)
assert (hass.states.get("switch.test_charge").state == STATE_ON) == (
TEST_VEHICLE_STATE_ONLINE["charge_state"]["charge_enable_request"]
)
assert (hass.states.get("switch.test_sentry_mode").state == STATE_ON) == (
TEST_VEHICLE_STATE_ONLINE["vehicle_state"]["sentry_mode"]
)
with patch(
"homeassistant.components.tessie.entity.TessieEntity.run",
return_value=True,
) as mock_run:
# Test Switch On
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ["switch.test_charge"]},
blocking=True,
)
mock_run.assert_called_once()
mock_run.reset_mock()
# Test Switch Off
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: ["switch.test_charge"]},
blocking=True,
)
mock_run.assert_called_once()