From 92535277be30a52022933a73921c3cc312092135 Mon Sep 17 00:00:00 2001 From: Charles Garwood Date: Tue, 15 Aug 2023 14:08:11 -0400 Subject: [PATCH] Add number platform & battery setpoint entities to Enphase integration (#98427) Co-authored-by: J. Nick Koston --- .coveragerc | 1 + .../components/enphase_envoy/const.py | 8 +- .../components/enphase_envoy/number.py | 116 ++++++++++++++++++ .../components/enphase_envoy/strings.json | 8 ++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/enphase_envoy/number.py diff --git a/.coveragerc b/.coveragerc index 014dc2f0f39..9930cbaf0b5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -305,6 +305,7 @@ omit = homeassistant/components/enphase_envoy/binary_sensor.py homeassistant/components/enphase_envoy/coordinator.py homeassistant/components/enphase_envoy/entity.py + homeassistant/components/enphase_envoy/number.py homeassistant/components/enphase_envoy/select.py homeassistant/components/enphase_envoy/sensor.py homeassistant/components/enphase_envoy/switch.py diff --git a/homeassistant/components/enphase_envoy/const.py b/homeassistant/components/enphase_envoy/const.py index d1c6618502e..c5656a65b6f 100644 --- a/homeassistant/components/enphase_envoy/const.py +++ b/homeassistant/components/enphase_envoy/const.py @@ -5,6 +5,12 @@ from homeassistant.const import Platform DOMAIN = "enphase_envoy" -PLATFORMS = [Platform.BINARY_SENSOR, Platform.SELECT, Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.NUMBER, + Platform.SELECT, + Platform.SENSOR, + Platform.SWITCH, +] INVALID_AUTH_ERRORS = (EnvoyAuthenticationError, EnvoyAuthenticationRequired) diff --git a/homeassistant/components/enphase_envoy/number.py b/homeassistant/components/enphase_envoy/number.py new file mode 100644 index 00000000000..50d4de18f12 --- /dev/null +++ b/homeassistant/components/enphase_envoy/number.py @@ -0,0 +1,116 @@ +"""Number platform for Enphase Envoy solar energy monitor.""" +from __future__ import annotations + +from collections.abc import Callable +from dataclasses import dataclass + +from pyenphase import EnvoyDryContactSettings + +from homeassistant.components.number import ( + NumberDeviceClass, + NumberEntity, + NumberEntityDescription, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory +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 + + +@dataclass +class EnvoyRelayRequiredKeysMixin: + """Mixin for required keys.""" + + value_fn: Callable[[EnvoyDryContactSettings], float] + + +@dataclass +class EnvoyRelayNumberEntityDescription( + NumberEntityDescription, EnvoyRelayRequiredKeysMixin +): + """Describes an Envoy Dry Contact Relay number entity.""" + + +RELAY_ENTITIES = ( + EnvoyRelayNumberEntityDescription( + key="soc_low", + translation_key="cutoff_battery_level", + device_class=NumberDeviceClass.BATTERY, + entity_category=EntityCategory.CONFIG, + value_fn=lambda relay: relay.soc_low, + ), + EnvoyRelayNumberEntityDescription( + key="soc_high", + translation_key="restore_battery_level", + device_class=NumberDeviceClass.BATTERY, + entity_category=EntityCategory.CONFIG, + value_fn=lambda relay: relay.soc_high, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Enphase Envoy number platform.""" + coordinator: EnphaseUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id] + envoy_data = coordinator.envoy.data + assert envoy_data is not None + entities: list[NumberEntity] = [] + if envoy_data.dry_contact_settings: + entities.extend( + EnvoyRelayNumberEntity(coordinator, entity, relay) + for entity in RELAY_ENTITIES + for relay in envoy_data.dry_contact_settings + ) + async_add_entities(entities) + + +class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity): + """Representation of an Enphase Enpower number entity.""" + + entity_description: EnvoyRelayNumberEntityDescription + + def __init__( + self, + coordinator: EnphaseUpdateCoordinator, + description: EnvoyRelayNumberEntityDescription, + relay_id: str, + ) -> None: + """Initialize the Enphase relay number entity.""" + super().__init__(coordinator, description) + self.envoy = coordinator.envoy + enpower = self.data.enpower + assert enpower is not None + serial_number = enpower.serial_number + self._relay_id = relay_id + self._attr_unique_id = f"{serial_number}_relay_{relay_id}_{description.key}" + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, relay_id)}, + manufacturer="Enphase", + model="Dry contact relay", + name=self.data.dry_contact_settings[relay_id].load_name, + sw_version=str(enpower.firmware_version), + via_device=(DOMAIN, serial_number), + ) + + @property + def native_value(self) -> float: + """Return the state of the relay entity.""" + return self.entity_description.value_fn( + self.data.dry_contact_settings[self._relay_id] + ) + + async def async_set_native_value(self, value: float) -> None: + """Update the relay.""" + await self.envoy.update_dry_contact( + {"id": self._relay_id, self.entity_description.key: int(value)} + ) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/enphase_envoy/strings.json b/homeassistant/components/enphase_envoy/strings.json index 0f292dfa8e3..f023bc7d114 100644 --- a/homeassistant/components/enphase_envoy/strings.json +++ b/homeassistant/components/enphase_envoy/strings.json @@ -39,6 +39,14 @@ "name": "Relay status" } }, + "number": { + "cutoff_battery_level": { + "name": "Cutoff battery level" + }, + "restore_battery_level": { + "name": "Restore battery level" + } + }, "select": { "relay_mode": { "name": "Mode",