mirror of https://github.com/home-assistant/core
Add support for changing Enphase battery backup modes (#102392)
This commit is contained in:
parent
41b59b6990
commit
013e580c02
|
@ -6,7 +6,7 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"requirements": ["pyenphase==1.12.0"],
|
||||
"requirements": ["pyenphase==1.13.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
"""Number platform for Enphase Envoy solar energy monitor."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pyenphase import EnvoyDryContactSettings
|
||||
from pyenphase import Envoy, EnvoyDryContactSettings
|
||||
from pyenphase.const import SupportedFeatures
|
||||
from pyenphase.models.tariff import EnvoyStorageSettings
|
||||
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
|
@ -12,7 +15,7 @@ from homeassistant.components.number import (
|
|||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
@ -36,6 +39,21 @@ class EnvoyRelayNumberEntityDescription(
|
|||
"""Describes an Envoy Dry Contact Relay number entity."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnvoyStorageSettingsRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[EnvoyStorageSettings], float]
|
||||
update_fn: Callable[[Envoy, float], Awaitable[dict[str, Any]]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnvoyStorageSettingsNumberEntityDescription(
|
||||
NumberEntityDescription, EnvoyStorageSettingsRequiredKeysMixin
|
||||
):
|
||||
"""Describes an Envoy storage mode number entity."""
|
||||
|
||||
|
||||
RELAY_ENTITIES = (
|
||||
EnvoyRelayNumberEntityDescription(
|
||||
key="soc_low",
|
||||
|
@ -53,6 +71,15 @@ RELAY_ENTITIES = (
|
|||
),
|
||||
)
|
||||
|
||||
STORAGE_RESERVE_SOC_ENTITY = EnvoyStorageSettingsNumberEntityDescription(
|
||||
key="reserve_soc",
|
||||
translation_key="reserve_soc",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=NumberDeviceClass.BATTERY,
|
||||
value_fn=lambda storage_settings: storage_settings.reserved_soc,
|
||||
update_fn=lambda envoy, value: envoy.set_reserve_soc(int(value)),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
@ -70,6 +97,14 @@ async def async_setup_entry(
|
|||
for entity in RELAY_ENTITIES
|
||||
for relay in envoy_data.dry_contact_settings
|
||||
)
|
||||
if (
|
||||
envoy_data.tariff
|
||||
and envoy_data.tariff.storage_settings
|
||||
and coordinator.envoy.supported_features & SupportedFeatures.ENCHARGE
|
||||
):
|
||||
entities.append(
|
||||
EnvoyStorageSettingsNumberEntity(coordinator, STORAGE_RESERVE_SOC_ENTITY)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
|
@ -114,3 +149,42 @@ class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity):
|
|||
{"id": self._relay_id, self.entity_description.key: int(value)}
|
||||
)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
|
||||
class EnvoyStorageSettingsNumberEntity(EnvoyBaseEntity, NumberEntity):
|
||||
"""Representation of an Enphase storage settings number entity."""
|
||||
|
||||
entity_description: EnvoyStorageSettingsNumberEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: EnphaseUpdateCoordinator,
|
||||
description: EnvoyStorageSettingsNumberEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Enphase relay number entity."""
|
||||
super().__init__(coordinator, description)
|
||||
self.envoy = coordinator.envoy
|
||||
assert self.data.enpower is not None
|
||||
enpower = self.data.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 native_value(self) -> float:
|
||||
"""Return the state of the storage setting entity."""
|
||||
assert self.data.tariff is not None
|
||||
assert self.data.tariff.storage_settings is not None
|
||||
return self.entity_description.value_fn(self.data.tariff.storage_settings)
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Update the storage setting."""
|
||||
await self.entity_description.update_fn(self.envoy, value)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
"""Select platform for Enphase Envoy solar energy monitor."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from collections.abc import Awaitable, Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pyenphase import Envoy, EnvoyDryContactSettings
|
||||
from pyenphase.const import SupportedFeatures
|
||||
from pyenphase.models.dry_contacts import DryContactAction, DryContactMode
|
||||
from pyenphase.models.tariff import EnvoyStorageMode, EnvoyStorageSettings
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -36,6 +38,21 @@ class EnvoyRelaySelectEntityDescription(
|
|||
"""Describes an Envoy Dry Contact Relay select entity."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnvoyStorageSettingsRequiredKeysMixin:
|
||||
"""Mixin for required keys."""
|
||||
|
||||
value_fn: Callable[[EnvoyStorageSettings], str]
|
||||
update_fn: Callable[[Envoy, str], Awaitable[dict[str, Any]]]
|
||||
|
||||
|
||||
@dataclass
|
||||
class EnvoyStorageSettingsSelectEntityDescription(
|
||||
SelectEntityDescription, EnvoyStorageSettingsRequiredKeysMixin
|
||||
):
|
||||
"""Describes an Envoy storage settings select entity."""
|
||||
|
||||
|
||||
RELAY_MODE_MAP = {
|
||||
DryContactMode.MANUAL: "standard",
|
||||
DryContactMode.STATE_OF_CHARGE: "battery",
|
||||
|
@ -51,6 +68,14 @@ REVERSE_RELAY_ACTION_MAP = {v: k for k, v in RELAY_ACTION_MAP.items()}
|
|||
MODE_OPTIONS = list(REVERSE_RELAY_MODE_MAP)
|
||||
ACTION_OPTIONS = list(REVERSE_RELAY_ACTION_MAP)
|
||||
|
||||
STORAGE_MODE_MAP = {
|
||||
EnvoyStorageMode.BACKUP: "backup",
|
||||
EnvoyStorageMode.SELF_CONSUMPTION: "self_consumption",
|
||||
EnvoyStorageMode.SAVINGS: "savings",
|
||||
}
|
||||
REVERSE_STORAGE_MODE_MAP = {v: k for k, v in STORAGE_MODE_MAP.items()}
|
||||
STORAGE_MODE_OPTIONS = list(REVERSE_STORAGE_MODE_MAP)
|
||||
|
||||
RELAY_ENTITIES = (
|
||||
EnvoyRelaySelectEntityDescription(
|
||||
key="mode",
|
||||
|
@ -101,6 +126,15 @@ RELAY_ENTITIES = (
|
|||
),
|
||||
),
|
||||
)
|
||||
STORAGE_MODE_ENTITY = EnvoyStorageSettingsSelectEntityDescription(
|
||||
key="storage_mode",
|
||||
translation_key="storage_mode",
|
||||
options=STORAGE_MODE_OPTIONS,
|
||||
value_fn=lambda storage_settings: STORAGE_MODE_MAP[storage_settings.mode],
|
||||
update_fn=lambda envoy, value: envoy.set_storage_mode(
|
||||
REVERSE_STORAGE_MODE_MAP[value]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
|
@ -119,6 +153,14 @@ async def async_setup_entry(
|
|||
for entity in RELAY_ENTITIES
|
||||
for relay in envoy_data.dry_contact_settings
|
||||
)
|
||||
if (
|
||||
envoy_data.tariff
|
||||
and envoy_data.tariff.storage_settings
|
||||
and coordinator.envoy.supported_features & SupportedFeatures.ENCHARGE
|
||||
):
|
||||
entities.append(
|
||||
EnvoyStorageSettingsSelectEntity(coordinator, STORAGE_MODE_ENTITY)
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
|
@ -164,3 +206,43 @@ class EnvoyRelaySelectEntity(EnvoyBaseEntity, SelectEntity):
|
|||
"""Update the relay."""
|
||||
await self.entity_description.update_fn(self.envoy, self.relay, option)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
|
||||
class EnvoyStorageSettingsSelectEntity(EnvoyBaseEntity, SelectEntity):
|
||||
"""Representation of an Enphase storage settings select entity."""
|
||||
|
||||
entity_description: EnvoyStorageSettingsSelectEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: EnphaseUpdateCoordinator,
|
||||
description: EnvoyStorageSettingsSelectEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the Enphase storage settings select entity."""
|
||||
super().__init__(coordinator, description)
|
||||
self.envoy = coordinator.envoy
|
||||
assert coordinator.envoy.data is not None
|
||||
assert coordinator.envoy.data.enpower is not None
|
||||
enpower = coordinator.envoy.data.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 current_option(self) -> str:
|
||||
"""Return the state of the select entity."""
|
||||
assert self.data.tariff is not None
|
||||
assert self.data.tariff.storage_settings is not None
|
||||
return self.entity_description.value_fn(self.data.tariff.storage_settings)
|
||||
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Update the relay."""
|
||||
await self.entity_description.update_fn(self.envoy, option)
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
},
|
||||
"restore_battery_level": {
|
||||
"name": "Restore battery level"
|
||||
},
|
||||
"reserve_soc": {
|
||||
"name": "Reserve battery level"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
|
@ -75,6 +78,14 @@
|
|||
"schedule": "[%key:component::enphase_envoy::entity::select::relay_grid_action::state::schedule%]",
|
||||
"none": "[%key:component::enphase_envoy::entity::select::relay_grid_action::state::none%]"
|
||||
}
|
||||
},
|
||||
"storage_mode": {
|
||||
"name": "Storage mode",
|
||||
"state": {
|
||||
"self_consumption": "Self consumption",
|
||||
"backup": "Full backup",
|
||||
"savings": "Savings mode"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
|
|
@ -1691,7 +1691,7 @@ pyedimax==0.2.1
|
|||
pyefergy==22.1.1
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.12.0
|
||||
pyenphase==1.13.0
|
||||
|
||||
# homeassistant.components.envisalink
|
||||
pyenvisalink==4.6
|
||||
|
|
|
@ -1273,7 +1273,7 @@ pyeconet==0.1.20
|
|||
pyefergy==22.1.1
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
pyenphase==1.12.0
|
||||
pyenphase==1.13.0
|
||||
|
||||
# homeassistant.components.everlights
|
||||
pyeverlights==0.1.0
|
||||
|
|
Loading…
Reference in New Issue