Add Envoy enpower sensors (#98086)

This commit is contained in:
Charles Garwood 2023-08-08 23:05:52 -04:00 committed by GitHub
parent d975e93abc
commit ce6b759b70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 225 additions and 21 deletions

View File

@ -8,7 +8,9 @@ import logging
from pyenphase import ( from pyenphase import (
EnvoyData, EnvoyData,
EnvoyEncharge, EnvoyEncharge,
EnvoyEnpower,
) )
from pyenphase.models.dry_contacts import DryContactStatus
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass, BinarySensorDeviceClass,
@ -66,6 +68,47 @@ ENCHARGE_SENSORS = (
), ),
) )
RELAY_STATUS_SENSOR = BinarySensorEntityDescription(
key="relay_status", icon="mdi:power-plug", has_entity_name=True
)
@dataclass
class EnvoyEnpowerRequiredKeysMixin:
"""Mixin for required keys."""
value_fn: Callable[[EnvoyEnpower], bool]
@dataclass
class EnvoyEnpowerBinarySensorEntityDescription(
BinarySensorEntityDescription, EnvoyEnpowerRequiredKeysMixin
):
"""Describes an Envoy Enpower binary sensor entity."""
ENPOWER_SENSORS = (
EnvoyEnpowerBinarySensorEntityDescription(
key="communicating",
translation_key="communicating",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda enpower: enpower.communicating,
),
EnvoyEnpowerBinarySensorEntityDescription(
key="operating",
translation_key="operating",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda enpower: enpower.operating,
),
EnvoyEnpowerBinarySensorEntityDescription(
key="mains_oper_state",
translation_key="grid_status",
icon="mdi:transmission-tower",
value_fn=lambda enpower: enpower.mains_oper_state == "closed",
),
)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
@ -86,15 +129,50 @@ async def async_setup_entry(
for encharge in envoy_data.encharge_inventory for encharge in envoy_data.encharge_inventory
) )
if envoy_data.enpower:
entities.extend(
EnvoyEnpowerBinarySensorEntity(coordinator, description)
for description in ENPOWER_SENSORS
)
if envoy_data.dry_contact_status:
entities.extend(
EnvoyRelayBinarySensorEntity(coordinator, RELAY_STATUS_SENSOR, relay)
for relay in envoy_data.dry_contact_status
)
async_add_entities(entities) async_add_entities(entities)
class EnvoyEnchargeBinarySensorEntity( class EnvoyBaseBinarySensorEntity(
CoordinatorEntity[EnphaseUpdateCoordinator], BinarySensorEntity CoordinatorEntity[EnphaseUpdateCoordinator], BinarySensorEntity
): ):
"""Defines a base envoy binary_sensor entity.""" """Defines a base envoy binary_sensor entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(
self,
coordinator: EnphaseUpdateCoordinator,
description: BinarySensorEntityDescription,
) -> None:
"""Init the Enphase base binary_sensor entity."""
self.entity_description = description
serial_number = coordinator.envoy.serial_number
assert serial_number is not None
self.envoy_serial_num = serial_number
super().__init__(coordinator)
@property
def data(self) -> EnvoyData:
"""Return envoy data."""
data = self.coordinator.envoy.data
assert data is not None
return data
class EnvoyEnchargeBinarySensorEntity(EnvoyBaseBinarySensorEntity):
"""Defines an Encharge binary_sensor entity."""
entity_description: EnvoyEnchargeBinarySensorEntityDescription entity_description: EnvoyEnchargeBinarySensorEntityDescription
def __init__( def __init__(
@ -104,13 +182,7 @@ class EnvoyEnchargeBinarySensorEntity(
serial_number: str, serial_number: str,
) -> None: ) -> None:
"""Init the Encharge base entity.""" """Init the Encharge base entity."""
self.entity_description = description super().__init__(coordinator, description)
self.coordinator = coordinator
assert serial_number is not None
self.envoy_serial_num = coordinator.envoy.serial_number
assert self.envoy_serial_num is not None
self._serial_number = serial_number self._serial_number = serial_number
self._attr_unique_id = f"{serial_number}_{description.key}" self._attr_unique_id = f"{serial_number}_{description.key}"
encharge_inventory = self.data.encharge_inventory encharge_inventory = self.data.encharge_inventory
@ -124,18 +196,76 @@ class EnvoyEnchargeBinarySensorEntity(
via_device=(DOMAIN, self.envoy_serial_num), via_device=(DOMAIN, self.envoy_serial_num),
) )
super().__init__(coordinator)
@property
def data(self) -> EnvoyData:
"""Return envoy data."""
data = self.coordinator.envoy.data
assert data is not None
return data
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return the state of the Encharge binary_sensor.""" """Return the state of the Encharge binary_sensor."""
encharge_inventory = self.data.encharge_inventory encharge_inventory = self.data.encharge_inventory
assert encharge_inventory is not None assert encharge_inventory is not None
return self.entity_description.value_fn(encharge_inventory[self._serial_number]) return self.entity_description.value_fn(encharge_inventory[self._serial_number])
class EnvoyEnpowerBinarySensorEntity(EnvoyBaseBinarySensorEntity):
"""Defines an Enpower binary_sensor entity."""
entity_description: EnvoyEnpowerBinarySensorEntityDescription
def __init__(
self,
coordinator: EnphaseUpdateCoordinator,
description: EnvoyEnpowerBinarySensorEntityDescription,
) -> None:
"""Init the Enpower base entity."""
super().__init__(coordinator, description)
enpower = self.data.enpower
assert enpower is not None
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 binary_sensor."""
enpower = self.data.enpower
assert enpower is not None
return self.entity_description.value_fn(enpower)
class EnvoyRelayBinarySensorEntity(EnvoyBaseBinarySensorEntity):
"""Defines an Enpower dry contact binary_sensor entity."""
def __init__(
self,
coordinator: EnphaseUpdateCoordinator,
description: BinarySensorEntityDescription,
relay: str,
) -> None:
"""Init the Enpower base entity."""
super().__init__(coordinator, description)
enpower = self.data.enpower
assert enpower is not None
self.relay = self.data.dry_contact_status[relay]
self._serial_number = enpower.serial_number
self._attr_unique_id = f"{self._serial_number}_relay_{self.relay.id}"
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),
)
self._attr_name = (
f"{self.data.dry_contact_settings[self.relay.id].load_name} Relay"
)
@property
def is_on(self) -> bool:
"""Return the state of the Enpower binary_sensor."""
return self.relay.status == DryContactStatus.CLOSED

View File

@ -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.1.3"], "requirements": ["pyenphase==1.2.1"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_enphase-envoy._tcp.local." "type": "_enphase-envoy._tcp.local."

View File

@ -10,6 +10,7 @@ from pyenphase import (
EnvoyData, EnvoyData,
EnvoyEncharge, EnvoyEncharge,
EnvoyEnchargePower, EnvoyEnchargePower,
EnvoyEnpower,
EnvoyInverter, EnvoyInverter,
EnvoySystemConsumption, EnvoySystemConsumption,
EnvoySystemProduction, EnvoySystemProduction,
@ -259,6 +260,36 @@ ENCHARGE_POWER_SENSORS = (
) )
@dataclass
class EnvoyEnpowerRequiredKeysMixin:
"""Mixin for required keys."""
value_fn: Callable[[EnvoyEnpower], datetime.datetime | int | float]
@dataclass
class EnvoyEnpowerSensorEntityDescription(
SensorEntityDescription, EnvoyEnpowerRequiredKeysMixin
):
"""Describes an Envoy Encharge sensor entity."""
ENPOWER_SENSORS = (
EnvoyEnpowerSensorEntityDescription(
key="temperature",
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
device_class=SensorDeviceClass.TEMPERATURE,
value_fn=lambda enpower: enpower.temperature,
),
EnvoyEnpowerSensorEntityDescription(
key=LAST_REPORTED_KEY,
translation_key=LAST_REPORTED_KEY,
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda enpower: dt_util.utc_from_timestamp(enpower.last_report_date),
),
)
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
@ -300,6 +331,11 @@ async def async_setup_entry(
for description in ENCHARGE_POWER_SENSORS for description in ENCHARGE_POWER_SENSORS
for encharge in envoy_data.encharge_power for encharge in envoy_data.encharge_power
) )
if envoy_data.enpower:
entities.extend(
EnvoyEnpowerEntity(coordinator, description)
for description in ENPOWER_SENSORS
)
async_add_entities(entities) async_add_entities(entities)
@ -469,3 +505,38 @@ class EnvoyEnchargePowerEntity(EnvoyEnchargeEntity):
encharge_power = self.data.encharge_power encharge_power = self.data.encharge_power
assert encharge_power is not None assert encharge_power is not None
return self.entity_description.value_fn(encharge_power[self._serial_number]) return self.entity_description.value_fn(encharge_power[self._serial_number])
class EnvoyEnpowerEntity(EnvoyBaseEntity, SensorEntity):
"""Envoy Enpower sensor entity."""
_attr_has_entity_name = True
entity_description: EnvoyEnpowerSensorEntityDescription
def __init__(
self,
coordinator: EnphaseUpdateCoordinator,
description: EnvoyEnpowerSensorEntityDescription,
) -> None:
"""Initialize Enpower entity."""
super().__init__(coordinator, description)
assert coordinator.envoy.data is not None
enpower_data = coordinator.envoy.data.enpower
assert enpower_data is not None
self._serial_number = enpower_data.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_data.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
)
@property
def native_value(self) -> datetime.datetime | int | float | None:
"""Return the state of the power sensors."""
enpower = self.data.enpower
assert enpower is not None
return self.entity_description.value_fn(enpower)

View File

@ -27,10 +27,13 @@
"name": "Communicating" "name": "Communicating"
}, },
"dc_switch": { "dc_switch": {
"name": "DC Switch" "name": "DC switch"
}, },
"operating": { "operating": {
"name": "Operating" "name": "Operating"
},
"grid_status": {
"name": "Grid status"
} }
}, },
"sensor": { "sensor": {

View File

@ -1662,7 +1662,7 @@ pyedimax==0.2.1
pyefergy==22.1.1 pyefergy==22.1.1
# homeassistant.components.enphase_envoy # homeassistant.components.enphase_envoy
pyenphase==1.1.3 pyenphase==1.2.1
# homeassistant.components.envisalink # homeassistant.components.envisalink
pyenvisalink==4.6 pyenvisalink==4.6

View File

@ -1229,7 +1229,7 @@ pyeconet==0.1.20
pyefergy==22.1.1 pyefergy==22.1.1
# homeassistant.components.enphase_envoy # homeassistant.components.enphase_envoy
pyenphase==1.1.3 pyenphase==1.2.1
# homeassistant.components.everlights # homeassistant.components.everlights
pyeverlights==0.1.0 pyeverlights==0.1.0