From 4342a95be0bde6bcc7ac9471c9b01f4b41b8f96d Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Fri, 11 Aug 2023 13:31:47 -0400 Subject: [PATCH] Add Enphase switch platform and grid enable switch (#98261) * Add Enphase switch platform and grid enable switch * Update dependency * Fix docstrings * Update .coveragerc --- .coveragerc | 1 + .../components/enphase_envoy/const.py | 2 +- .../components/enphase_envoy/manifest.json | 2 +- .../components/enphase_envoy/strings.json | 5 + .../components/enphase_envoy/switch.py | 113 ++++++++++++++++++ requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 7 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/enphase_envoy/switch.py diff --git a/.coveragerc b/.coveragerc index 347e5527ee4..d36153ccca7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -306,6 +306,7 @@ omit = homeassistant/components/enphase_envoy/coordinator.py homeassistant/components/enphase_envoy/entity.py homeassistant/components/enphase_envoy/sensor.py + homeassistant/components/enphase_envoy/switch.py homeassistant/components/entur_public_transport/* homeassistant/components/environment_canada/__init__.py homeassistant/components/environment_canada/camera.py diff --git a/homeassistant/components/enphase_envoy/const.py b/homeassistant/components/enphase_envoy/const.py index d10cc0b9511..828abe8fe4c 100644 --- a/homeassistant/components/enphase_envoy/const.py +++ b/homeassistant/components/enphase_envoy/const.py @@ -5,6 +5,6 @@ from homeassistant.const import Platform DOMAIN = "enphase_envoy" -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH] INVALID_AUTH_ERRORS = (EnvoyAuthenticationError, EnvoyAuthenticationRequired) diff --git a/homeassistant/components/enphase_envoy/manifest.json b/homeassistant/components/enphase_envoy/manifest.json index 7cd107a3e67..f500ac538e7 100644 --- a/homeassistant/components/enphase_envoy/manifest.json +++ b/homeassistant/components/enphase_envoy/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/enphase_envoy", "iot_class": "local_polling", "loggers": ["pyenphase"], - "requirements": ["pyenphase==1.3.0"], + "requirements": ["pyenphase==1.4.0"], "zeroconf": [ { "type": "_enphase-envoy._tcp.local." diff --git a/homeassistant/components/enphase_envoy/strings.json b/homeassistant/components/enphase_envoy/strings.json index 915fee94e2a..f42e44d7afa 100644 --- a/homeassistant/components/enphase_envoy/strings.json +++ b/homeassistant/components/enphase_envoy/strings.json @@ -64,6 +64,11 @@ "lifetime_consumption": { "name": "Lifetime energy consumption" } + }, + "switch": { + "grid_enabled": { + "name": "Grid enabled" + } } } } diff --git a/homeassistant/components/enphase_envoy/switch.py b/homeassistant/components/enphase_envoy/switch.py new file mode 100644 index 00000000000..820b904e070 --- /dev/null +++ b/homeassistant/components/enphase_envoy/switch.py @@ -0,0 +1,113 @@ +"""Switch platform for Enphase Envoy solar energy monitor.""" +from __future__ import annotations + +from collections.abc import Callable, Coroutine +from dataclasses import dataclass +import logging +from typing import Any + +from pyenphase import Envoy, EnvoyEnpower + +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN +from .coordinator import EnphaseUpdateCoordinator +from .entity import EnvoyBaseEntity + +_LOGGER = logging.getLogger(__name__) + + +@dataclass +class EnvoyEnpowerRequiredKeysMixin: + """Mixin for required keys.""" + + value_fn: Callable[[EnvoyEnpower], bool] + turn_on_fn: Callable[[Envoy], Coroutine[Any, Any, dict[str, Any]]] + turn_off_fn: Callable[[Envoy], Coroutine[Any, Any, dict[str, Any]]] + + +@dataclass +class EnvoyEnpowerSwitchEntityDescription( + SwitchEntityDescription, EnvoyEnpowerRequiredKeysMixin +): + """Describes an Envoy Enpower switch entity.""" + + +ENPOWER_GRID_SWITCH = EnvoyEnpowerSwitchEntityDescription( + key="mains_admin_state", + translation_key="grid_enabled", + value_fn=lambda enpower: enpower.mains_admin_state == "closed", + turn_on_fn=lambda envoy: envoy.go_on_grid(), + turn_off_fn=lambda envoy: envoy.go_off_grid(), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Enphase Envoy switch platform.""" + coordinator: EnphaseUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + envoy_data = coordinator.envoy.data + assert envoy_data is not None + envoy_serial_num = config_entry.unique_id + assert envoy_serial_num is not None + entities: list[SwitchEntity] = [] + if envoy_data.enpower: + entities.extend( + [ + EnvoyEnpowerSwitchEntity( + coordinator, ENPOWER_GRID_SWITCH, envoy_data.enpower + ) + ] + ) + async_add_entities(entities) + + +class EnvoyEnpowerSwitchEntity(EnvoyBaseEntity, SwitchEntity): + """Representation of an Enphase Enpower switch entity.""" + + entity_description: EnvoyEnpowerSwitchEntityDescription + + def __init__( + self, + coordinator: EnphaseUpdateCoordinator, + description: EnvoyEnpowerSwitchEntityDescription, + enpower: EnvoyEnpower, + ) -> None: + """Initialize the Enphase Enpower switch entity.""" + super().__init__(coordinator, description) + self.envoy = coordinator.envoy + self.enpower = enpower + self._serial_number = enpower.serial_number + self._attr_unique_id = f"{self._serial_number}_{description.key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self._serial_number)}, + manufacturer="Enphase", + model="Enpower", + name=f"Enpower {self._serial_number}", + sw_version=str(enpower.firmware_version), + via_device=(DOMAIN, self.envoy_serial_num), + ) + + @property + def is_on(self) -> bool: + """Return the state of the Enpower switch.""" + enpower = self.data.enpower + assert enpower is not None + return self.entity_description.value_fn(enpower) + + async def async_turn_on(self): + """Turn on the Enpower switch.""" + await self.entity_description.turn_on_fn(self.envoy) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self): + """Turn off the Enpower switch.""" + await self.entity_description.turn_off_fn(self.envoy) + await self.coordinator.async_request_refresh() diff --git a/requirements_all.txt b/requirements_all.txt index 02503e1f7ec..4f99f44c9f1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1665,7 +1665,7 @@ pyedimax==0.2.1 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.3.0 +pyenphase==1.4.0 # homeassistant.components.envisalink pyenvisalink==4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2231346266f..cc1bc38b233 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1229,7 +1229,7 @@ pyeconet==0.1.20 pyefergy==22.1.1 # homeassistant.components.enphase_envoy -pyenphase==1.3.0 +pyenphase==1.4.0 # homeassistant.components.everlights pyeverlights==0.1.0