From 0b9d02935076a7c78c7384392a30d3aa0e5bce7f Mon Sep 17 00:00:00 2001 From: Franck Nijhof Date: Fri, 7 Oct 2022 15:03:58 +0200 Subject: [PATCH] Add switch platform to LaMetric (#79759) * Add switch platform to LaMetric * Little naming tweak --- homeassistant/components/lametric/const.py | 2 +- homeassistant/components/lametric/switch.py | 102 ++++++++++++++++++++ tests/components/lametric/test_switch.py | 89 +++++++++++++++++ 3 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/lametric/switch.py create mode 100644 tests/components/lametric/test_switch.py diff --git a/homeassistant/components/lametric/const.py b/homeassistant/components/lametric/const.py index da84450e784..1ba48e0d992 100644 --- a/homeassistant/components/lametric/const.py +++ b/homeassistant/components/lametric/const.py @@ -7,7 +7,7 @@ from typing import Final from homeassistant.const import Platform DOMAIN: Final = "lametric" -PLATFORMS = [Platform.BUTTON, Platform.NUMBER] +PLATFORMS = [Platform.BUTTON, Platform.NUMBER, Platform.SWITCH] LOGGER = logging.getLogger(__package__) SCAN_INTERVAL = timedelta(seconds=30) diff --git a/homeassistant/components/lametric/switch.py b/homeassistant/components/lametric/switch.py new file mode 100644 index 00000000000..8c0acac65e6 --- /dev/null +++ b/homeassistant/components/lametric/switch.py @@ -0,0 +1,102 @@ +"""Support for LaMetric switches.""" +from __future__ import annotations + +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from typing import Any + +from demetriek import Device, LaMetricDevice + +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import LaMetricDataUpdateCoordinator +from .entity import LaMetricEntity + + +@dataclass +class LaMetricEntityDescriptionMixin: + """Mixin values for LaMetric entities.""" + + is_on_fn: Callable[[Device], bool] + set_fn: Callable[[LaMetricDevice, bool], Awaitable[Any]] + + +@dataclass +class LaMetricSwitchEntityDescription( + SwitchEntityDescription, LaMetricEntityDescriptionMixin +): + """Class describing LaMetric switch entities.""" + + available_fn: Callable[[Device], bool] = lambda device: True + + +SWITCHES = [ + LaMetricSwitchEntityDescription( + key="bluetooth", + name="Bluetooth", + icon="mdi:bluetooth", + entity_category=EntityCategory.CONFIG, + available_fn=lambda device: device.bluetooth.available, + is_on_fn=lambda device: device.bluetooth.active, + set_fn=lambda api, active: api.bluetooth(active=active), + ), +] + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up LaMetric switch based on a config entry.""" + coordinator: LaMetricDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + LaMetricSwitchEntity( + coordinator=coordinator, + description=description, + ) + for description in SWITCHES + ) + + +class LaMetricSwitchEntity(LaMetricEntity, SwitchEntity): + """Representation of a LaMetric switch.""" + + entity_description: LaMetricSwitchEntityDescription + + def __init__( + self, + coordinator: LaMetricDataUpdateCoordinator, + description: LaMetricSwitchEntityDescription, + ) -> None: + """Initiate LaMetric Switch.""" + super().__init__(coordinator) + self.entity_description = description + self._attr_unique_id = f"{coordinator.data.serial_number}-{description.key}" + + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and self.entity_description.available_fn( + self.coordinator.data + ) + + @property + def is_on(self) -> bool: + """Return state of the switch.""" + return self.entity_description.is_on_fn(self.coordinator.data) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the entity on.""" + await self.entity_description.set_fn(self.coordinator.lametric, True) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the entity off.""" + await self.entity_description.set_fn(self.coordinator.lametric, False) + await self.coordinator.async_request_refresh() diff --git a/tests/components/lametric/test_switch.py b/tests/components/lametric/test_switch.py new file mode 100644 index 00000000000..350fa1b24f8 --- /dev/null +++ b/tests/components/lametric/test_switch.py @@ -0,0 +1,89 @@ +"""Tests for the LaMetric switch platform.""" +from unittest.mock import MagicMock + +from homeassistant.components.lametric.const import DOMAIN, SCAN_INTERVAL +from homeassistant.components.switch import ( + DOMAIN as SWITCH_DOMAIN, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, +) +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + ATTR_ICON, + STATE_OFF, + STATE_UNAVAILABLE, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.helpers.entity import EntityCategory +import homeassistant.util.dt as dt_util + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_bluetooth( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_lametric: MagicMock, +) -> None: + """Test the LaMetric Bluetooth control.""" + device_registry = dr.async_get(hass) + entity_registry = er.async_get(hass) + + state = hass.states.get("switch.frenck_s_lametric_bluetooth") + assert state + assert state.attributes.get(ATTR_DEVICE_CLASS) is None + assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Frenck's LaMetric Bluetooth" + assert state.attributes.get(ATTR_ICON) == "mdi:bluetooth" + assert state.state == STATE_OFF + + entry = entity_registry.async_get(state.entity_id) + assert entry + assert entry.device_id + assert entry.entity_category is EntityCategory.CONFIG + assert entry.unique_id == "SA110405124500W00BS9-bluetooth" + + device = device_registry.async_get(entry.device_id) + assert device + assert device.configuration_url is None + assert device.connections == {(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")} + assert device.entry_type is None + assert device.hw_version is None + assert device.identifiers == {(DOMAIN, "SA110405124500W00BS9")} + assert device.manufacturer == "LaMetric Inc." + assert device.name == "Frenck's LaMetric" + assert device.sw_version == "2.2.2" + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + { + ATTR_ENTITY_ID: "switch.frenck_s_lametric_bluetooth", + }, + blocking=True, + ) + + assert len(mock_lametric.bluetooth.mock_calls) == 1 + mock_lametric.bluetooth.assert_called_once_with(active=True) + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + { + ATTR_ENTITY_ID: "switch.frenck_s_lametric_bluetooth", + }, + blocking=True, + ) + + assert len(mock_lametric.bluetooth.mock_calls) == 2 + mock_lametric.bluetooth.assert_called_with(active=False) + + mock_lametric.device.return_value.bluetooth.available = False + async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) + await hass.async_block_till_done() + + state = hass.states.get("switch.frenck_s_lametric_bluetooth") + assert state + assert state.state == STATE_UNAVAILABLE