mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add home battery storage entities for enphase_envoy (#114015)
This commit is contained in:
parent
817d931df0
commit
205c457a77
@ -89,8 +89,10 @@ async def async_get_config_entry_diagnostics(
|
||||
"system_production_phases": envoy_data.system_production_phases,
|
||||
"ctmeter_production": envoy_data.ctmeter_production,
|
||||
"ctmeter_consumption": envoy_data.ctmeter_consumption,
|
||||
"ctmeter_storage": envoy_data.ctmeter_storage,
|
||||
"ctmeter_production_phases": envoy_data.ctmeter_production_phases,
|
||||
"ctmeter_consumption_phases": envoy_data.ctmeter_consumption_phases,
|
||||
"ctmeter_storage_phases": envoy_data.ctmeter_storage_phases,
|
||||
"dry_contact_status": envoy_data.dry_contact_status,
|
||||
"dry_contact_settings": envoy_data.dry_contact_settings,
|
||||
"inverters": envoy_data.inverters,
|
||||
@ -108,6 +110,7 @@ async def async_get_config_entry_diagnostics(
|
||||
"ct_count": envoy.ct_meter_count,
|
||||
"ct_consumption_meter": envoy.consumption_meter_type,
|
||||
"ct_production_meter": envoy.production_meter_type,
|
||||
"ct_storage_meter": envoy.storage_meter_type,
|
||||
}
|
||||
|
||||
diagnostic_data: dict[str, Any] = {
|
||||
|
@ -364,6 +364,87 @@ CT_PRODUCTION_PHASE_SENSORS = {
|
||||
for phase in range(3)
|
||||
}
|
||||
|
||||
CT_STORAGE_SENSORS = (
|
||||
EnvoyCTSensorEntityDescription(
|
||||
key="lifetime_battery_discharged",
|
||||
translation_key="lifetime_battery_discharged",
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
|
||||
suggested_display_precision=3,
|
||||
value_fn=lambda ct: ct.energy_delivered,
|
||||
on_phase=None,
|
||||
),
|
||||
EnvoyCTSensorEntityDescription(
|
||||
key="lifetime_battery_charged",
|
||||
translation_key="lifetime_battery_charged",
|
||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
suggested_unit_of_measurement=UnitOfEnergy.MEGA_WATT_HOUR,
|
||||
suggested_display_precision=3,
|
||||
value_fn=lambda ct: ct.energy_received,
|
||||
on_phase=None,
|
||||
),
|
||||
EnvoyCTSensorEntityDescription(
|
||||
key="battery_discharge",
|
||||
translation_key="battery_discharge",
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.POWER,
|
||||
suggested_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||
suggested_display_precision=3,
|
||||
value_fn=lambda ct: ct.active_power,
|
||||
on_phase=None,
|
||||
),
|
||||
EnvoyCTSensorEntityDescription(
|
||||
key="storage_voltage",
|
||||
translation_key="storage_ct_voltage",
|
||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.VOLTAGE,
|
||||
suggested_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||
suggested_display_precision=1,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda ct: ct.voltage,
|
||||
on_phase=None,
|
||||
),
|
||||
EnvoyCTSensorEntityDescription(
|
||||
key="storage_ct_metering_status",
|
||||
translation_key="storage_ct_metering_status",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=list(CtMeterStatus),
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda ct: ct.metering_status,
|
||||
on_phase=None,
|
||||
),
|
||||
EnvoyCTSensorEntityDescription(
|
||||
key="storage_ct_status_flags",
|
||||
translation_key="storage_ct_status_flags",
|
||||
state_class=None,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda ct: 0 if ct.status_flags is None else len(ct.status_flags),
|
||||
on_phase=None,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
CT_STORAGE_PHASE_SENSORS = {
|
||||
(on_phase := PHASENAMES[phase]): [
|
||||
replace(
|
||||
sensor,
|
||||
key=f"{sensor.key}_l{phase + 1}",
|
||||
translation_key=f"{sensor.translation_key}_phase",
|
||||
entity_registry_enabled_default=False,
|
||||
on_phase=on_phase,
|
||||
translation_placeholders={"phase_name": f"l{phase + 1}"},
|
||||
)
|
||||
for sensor in list(CT_STORAGE_SENSORS)
|
||||
]
|
||||
for phase in range(3)
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class EnvoyEnchargeSensorEntityDescription(SensorEntityDescription):
|
||||
@ -560,6 +641,21 @@ async def async_setup_entry(
|
||||
for description in CT_PRODUCTION_PHASE_SENSORS[use_phase]
|
||||
if phase.measurement_type == CtType.PRODUCTION
|
||||
)
|
||||
# Add storage CT entities
|
||||
if ctmeter := envoy_data.ctmeter_storage:
|
||||
entities.extend(
|
||||
EnvoyStorageCTEntity(coordinator, description)
|
||||
for description in CT_STORAGE_SENSORS
|
||||
if ctmeter.measurement_type == CtType.STORAGE
|
||||
)
|
||||
# For each storage ct phase reported add storage ct entities
|
||||
if phase_data := envoy_data.ctmeter_storage_phases:
|
||||
entities.extend(
|
||||
EnvoyStorageCTPhaseEntity(coordinator, description)
|
||||
for use_phase, phase in phase_data.items()
|
||||
for description in CT_STORAGE_PHASE_SENSORS[use_phase]
|
||||
if phase.measurement_type == CtType.STORAGE
|
||||
)
|
||||
|
||||
if envoy_data.inverters:
|
||||
entities.extend(
|
||||
@ -758,6 +854,40 @@ class EnvoyProductionCTPhaseEntity(EnvoySystemSensorEntity):
|
||||
)
|
||||
|
||||
|
||||
class EnvoyStorageCTEntity(EnvoySystemSensorEntity):
|
||||
"""Envoy net storage CT entity."""
|
||||
|
||||
entity_description: EnvoyCTSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_value(
|
||||
self,
|
||||
) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
|
||||
"""Return the state of the CT sensor."""
|
||||
if (ctmeter := self.data.ctmeter_storage) is None:
|
||||
return None
|
||||
return self.entity_description.value_fn(ctmeter)
|
||||
|
||||
|
||||
class EnvoyStorageCTPhaseEntity(EnvoySystemSensorEntity):
|
||||
"""Envoy net storage CT phase entity."""
|
||||
|
||||
entity_description: EnvoyCTSensorEntityDescription
|
||||
|
||||
@property
|
||||
def native_value(
|
||||
self,
|
||||
) -> int | float | str | CtType | CtMeterStatus | CtStatusFlags | None:
|
||||
"""Return the state of the CT phase sensor."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.entity_description.on_phase
|
||||
if (ctmeter := self.data.ctmeter_storage_phases) is None:
|
||||
return None
|
||||
return self.entity_description.value_fn(
|
||||
ctmeter[self.entity_description.on_phase]
|
||||
)
|
||||
|
||||
|
||||
class EnvoyInverterEntity(EnvoySensorBaseEntity):
|
||||
"""Envoy inverter entity."""
|
||||
|
||||
|
@ -170,6 +170,24 @@
|
||||
"production_ct_status_flags": {
|
||||
"name": "Meter status flags active production CT"
|
||||
},
|
||||
"lifetime_battery_discharged": {
|
||||
"name": "Lifetime battery energy discharged"
|
||||
},
|
||||
"lifetime_battery_charged": {
|
||||
"name": "Lifetime battery energy charged"
|
||||
},
|
||||
"battery_discharge": {
|
||||
"name": "Current battery discharge"
|
||||
},
|
||||
"storage_ct_voltage": {
|
||||
"name": "Voltage storage CT"
|
||||
},
|
||||
"storage_ct_metering_status": {
|
||||
"name": "Metering status storage CT"
|
||||
},
|
||||
"storage_ct_status_flags": {
|
||||
"name": "Meter status flags active storage CT"
|
||||
},
|
||||
"lifetime_net_consumption_phase": {
|
||||
"name": "Lifetime net energy consumption {phase_name}"
|
||||
},
|
||||
@ -197,6 +215,24 @@
|
||||
"production_ct_status_flags_phase": {
|
||||
"name": "Meter status flags active production CT {phase_name}"
|
||||
},
|
||||
"lifetime_battery_discharged_phase": {
|
||||
"name": "Lifetime battery energy discharged {phase_name}"
|
||||
},
|
||||
"lifetime_battery_charged_phase": {
|
||||
"name": "Lifetime battery energy charged {phase_name}"
|
||||
},
|
||||
"battery_discharge_phase": {
|
||||
"name": "Current battery discharge {phase_name}"
|
||||
},
|
||||
"storage_ct_voltage_phase": {
|
||||
"name": "Voltage storage CT {phase_name}"
|
||||
},
|
||||
"storage_ct_metering_status_phase": {
|
||||
"name": "Metering status storage CT {phase_name}"
|
||||
},
|
||||
"storage_ct_status_flags_phase": {
|
||||
"name": "Meter status flags active storage CT {phase_name}"
|
||||
},
|
||||
"reserve_soc": {
|
||||
"name": "Reserve battery level"
|
||||
},
|
||||
|
@ -55,15 +55,18 @@ def config_fixture():
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_envoy")
|
||||
def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth):
|
||||
def mock_envoy_fixture(
|
||||
serial_number,
|
||||
mock_authenticate,
|
||||
mock_setup,
|
||||
mock_auth,
|
||||
):
|
||||
"""Define a mocked Envoy fixture."""
|
||||
mock_envoy = Mock(spec=Envoy)
|
||||
mock_envoy.serial_number = serial_number
|
||||
mock_envoy.firmware = "7.1.2"
|
||||
mock_envoy.part_number = "123456789"
|
||||
mock_envoy.envoy_model = (
|
||||
"Envoy, phases: 3, phase mode: three, net-consumption CT, production CT"
|
||||
)
|
||||
mock_envoy.envoy_model = "Envoy, phases: 3, phase mode: three, net-consumption CT, production CT, storage CT"
|
||||
mock_envoy.authenticate = mock_authenticate
|
||||
mock_envoy.setup = mock_setup
|
||||
mock_envoy.auth = mock_auth
|
||||
@ -78,9 +81,10 @@ def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth):
|
||||
mock_envoy.phase_mode = EnvoyPhaseMode.THREE
|
||||
mock_envoy.phase_count = 3
|
||||
mock_envoy.active_phase_count = 3
|
||||
mock_envoy.ct_meter_count = 2
|
||||
mock_envoy.ct_meter_count = 3
|
||||
mock_envoy.consumption_meter_type = CtType.NET_CONSUMPTION
|
||||
mock_envoy.production_meter_type = CtType.PRODUCTION
|
||||
mock_envoy.storage_meter_type = CtType.STORAGE
|
||||
mock_envoy.data = EnvoyData(
|
||||
system_consumption=EnvoySystemConsumption(
|
||||
watt_hours_last_7_days=1234,
|
||||
@ -167,6 +171,21 @@ def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth):
|
||||
metering_status=CtMeterStatus.NORMAL,
|
||||
status_flags=[],
|
||||
),
|
||||
ctmeter_storage=EnvoyMeterData(
|
||||
eid="100000030",
|
||||
timestamp=1708006120,
|
||||
energy_delivered=31234,
|
||||
energy_received=32345,
|
||||
active_power=103,
|
||||
power_factor=0.23,
|
||||
voltage=113,
|
||||
current=0.4,
|
||||
frequency=50.3,
|
||||
state=CtState.ENABLED,
|
||||
measurement_type=CtType.STORAGE,
|
||||
metering_status=CtMeterStatus.NORMAL,
|
||||
status_flags=[],
|
||||
),
|
||||
ctmeter_production_phases={
|
||||
PhaseNames.PHASE_1: EnvoyMeterData(
|
||||
eid="100000011",
|
||||
@ -261,6 +280,53 @@ def mock_envoy_fixture(serial_number, mock_authenticate, mock_setup, mock_auth):
|
||||
status_flags=[],
|
||||
),
|
||||
},
|
||||
ctmeter_storage_phases={
|
||||
PhaseNames.PHASE_1: EnvoyMeterData(
|
||||
eid="100000031",
|
||||
timestamp=1708006121,
|
||||
energy_delivered=312341,
|
||||
energy_received=323451,
|
||||
active_power=22,
|
||||
power_factor=0.32,
|
||||
voltage=113,
|
||||
current=0.4,
|
||||
frequency=50.3,
|
||||
state=CtState.ENABLED,
|
||||
measurement_type=CtType.STORAGE,
|
||||
metering_status=CtMeterStatus.NORMAL,
|
||||
status_flags=[],
|
||||
),
|
||||
PhaseNames.PHASE_2: EnvoyMeterData(
|
||||
eid="100000032",
|
||||
timestamp=1708006122,
|
||||
energy_delivered=312342,
|
||||
energy_received=323452,
|
||||
active_power=33,
|
||||
power_factor=0.23,
|
||||
voltage=112,
|
||||
current=0.3,
|
||||
frequency=50.2,
|
||||
state=CtState.ENABLED,
|
||||
measurement_type=CtType.STORAGE,
|
||||
metering_status=CtMeterStatus.NORMAL,
|
||||
status_flags=[],
|
||||
),
|
||||
PhaseNames.PHASE_3: EnvoyMeterData(
|
||||
eid="100000033",
|
||||
timestamp=1708006123,
|
||||
energy_delivered=312343,
|
||||
energy_received=323453,
|
||||
active_power=53,
|
||||
power_factor=0.24,
|
||||
voltage=112,
|
||||
current=0.3,
|
||||
frequency=50.2,
|
||||
state=CtState.ENABLED,
|
||||
measurement_type=CtType.STORAGE,
|
||||
metering_status=CtMeterStatus.NORMAL,
|
||||
status_flags=[],
|
||||
),
|
||||
},
|
||||
inverters={
|
||||
"1": EnvoyInverter(
|
||||
serial_number="1",
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user