mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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",
|
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pyenphase"],
|
"loggers": ["pyenphase"],
|
||||||
"requirements": ["pyenphase==1.12.0"],
|
"requirements": ["pyenphase==1.13.0"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_enphase-envoy._tcp.local."
|
"type": "_enphase-envoy._tcp.local."
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
"""Number platform for Enphase Envoy solar energy monitor."""
|
"""Number platform for Enphase Envoy solar energy monitor."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Awaitable, Callable
|
||||||
from dataclasses import dataclass
|
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 (
|
from homeassistant.components.number import (
|
||||||
NumberDeviceClass,
|
NumberDeviceClass,
|
||||||
@ -12,7 +15,7 @@ from homeassistant.components.number import (
|
|||||||
NumberEntityDescription,
|
NumberEntityDescription,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import EntityCategory
|
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -36,6 +39,21 @@ class EnvoyRelayNumberEntityDescription(
|
|||||||
"""Describes an Envoy Dry Contact Relay number entity."""
|
"""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 = (
|
RELAY_ENTITIES = (
|
||||||
EnvoyRelayNumberEntityDescription(
|
EnvoyRelayNumberEntityDescription(
|
||||||
key="soc_low",
|
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(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -70,6 +97,14 @@ async def async_setup_entry(
|
|||||||
for entity in RELAY_ENTITIES
|
for entity in RELAY_ENTITIES
|
||||||
for relay in envoy_data.dry_contact_settings
|
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)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -114,3 +149,42 @@ class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity):
|
|||||||
{"id": self._relay_id, self.entity_description.key: int(value)}
|
{"id": self._relay_id, self.entity_description.key: int(value)}
|
||||||
)
|
)
|
||||||
await self.coordinator.async_request_refresh()
|
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."""
|
"""Select platform for Enphase Envoy solar energy monitor."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable, Coroutine
|
from collections.abc import Awaitable, Callable, Coroutine
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyenphase import Envoy, EnvoyDryContactSettings
|
from pyenphase import Envoy, EnvoyDryContactSettings
|
||||||
|
from pyenphase.const import SupportedFeatures
|
||||||
from pyenphase.models.dry_contacts import DryContactAction, DryContactMode
|
from pyenphase.models.dry_contacts import DryContactAction, DryContactMode
|
||||||
|
from pyenphase.models.tariff import EnvoyStorageMode, EnvoyStorageSettings
|
||||||
|
|
||||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -36,6 +38,21 @@ class EnvoyRelaySelectEntityDescription(
|
|||||||
"""Describes an Envoy Dry Contact Relay select entity."""
|
"""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 = {
|
RELAY_MODE_MAP = {
|
||||||
DryContactMode.MANUAL: "standard",
|
DryContactMode.MANUAL: "standard",
|
||||||
DryContactMode.STATE_OF_CHARGE: "battery",
|
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)
|
MODE_OPTIONS = list(REVERSE_RELAY_MODE_MAP)
|
||||||
ACTION_OPTIONS = list(REVERSE_RELAY_ACTION_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 = (
|
RELAY_ENTITIES = (
|
||||||
EnvoyRelaySelectEntityDescription(
|
EnvoyRelaySelectEntityDescription(
|
||||||
key="mode",
|
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(
|
async def async_setup_entry(
|
||||||
@ -119,6 +153,14 @@ async def async_setup_entry(
|
|||||||
for entity in RELAY_ENTITIES
|
for entity in RELAY_ENTITIES
|
||||||
for relay in envoy_data.dry_contact_settings
|
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)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
@ -164,3 +206,43 @@ class EnvoyRelaySelectEntity(EnvoyBaseEntity, SelectEntity):
|
|||||||
"""Update the relay."""
|
"""Update the relay."""
|
||||||
await self.entity_description.update_fn(self.envoy, self.relay, option)
|
await self.entity_description.update_fn(self.envoy, self.relay, option)
|
||||||
await self.coordinator.async_request_refresh()
|
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": {
|
"restore_battery_level": {
|
||||||
"name": "Restore battery level"
|
"name": "Restore battery level"
|
||||||
|
},
|
||||||
|
"reserve_soc": {
|
||||||
|
"name": "Reserve battery level"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
@ -75,6 +78,14 @@
|
|||||||
"schedule": "[%key:component::enphase_envoy::entity::select::relay_grid_action::state::schedule%]",
|
"schedule": "[%key:component::enphase_envoy::entity::select::relay_grid_action::state::schedule%]",
|
||||||
"none": "[%key:component::enphase_envoy::entity::select::relay_grid_action::state::none%]"
|
"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": {
|
"sensor": {
|
||||||
|
@ -1691,7 +1691,7 @@ pyedimax==0.2.1
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.12.0
|
pyenphase==1.13.0
|
||||||
|
|
||||||
# homeassistant.components.envisalink
|
# homeassistant.components.envisalink
|
||||||
pyenvisalink==4.6
|
pyenvisalink==4.6
|
||||||
|
@ -1273,7 +1273,7 @@ pyeconet==0.1.20
|
|||||||
pyefergy==22.1.1
|
pyefergy==22.1.1
|
||||||
|
|
||||||
# homeassistant.components.enphase_envoy
|
# homeassistant.components.enphase_envoy
|
||||||
pyenphase==1.12.0
|
pyenphase==1.13.0
|
||||||
|
|
||||||
# homeassistant.components.everlights
|
# homeassistant.components.everlights
|
||||||
pyeverlights==0.1.0
|
pyeverlights==0.1.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user