Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion custom_components/givenergy_local/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from .const import DOMAIN
from .coordinator import GivEnergyUpdateCoordinator
from .sensor import _device_kind

# LFP soft operating band. A cell that has genuinely drifted outside this for a
# sustained period warrants attention; the band is deliberately wider than the
Expand Down Expand Up @@ -116,7 +117,16 @@ def __init__(self, coordinator: GivEnergyUpdateCoordinator) -> None:
super().__init__(coordinator)
serial = coordinator.data.inverter_serial_number
self._attr_unique_id = f"{serial}_battery_out_of_spec"
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, serial)})
# Mirror sensor.py's DeviceInfo so HA derives the entity_id slug with the
# device-name prefix ("GivEnergy Inverter <serial>" → entity_id
# binary_sensor.givenergy_inverter_<serial>_battery_out_of_spec) rather
# than the bare slug it'd default to when binary_sensor sets up first
# and finds no named device record yet.
model = coordinator.data.inverter.model
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, serial)},
name=f"GivEnergy {_device_kind(model)} {serial}",
)
self._offenders: dict[str, _Offender] = {}
self._last_processed_refresh: datetime | None = None
self._evaluate()
Expand Down
18 changes: 15 additions & 3 deletions custom_components/givenergy_local/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Increment whenever the generated YAML layout changes in a meaningful way.
# __init__.py compares this against the last-generated version stored in HA's
# persistent Store and raises a Repairs issue when they diverge.
DASHBOARD_VERSION = 8
DASHBOARD_VERSION = 9


class _NoAliasDumper(yaml.SafeDumper):
Expand Down Expand Up @@ -198,6 +198,8 @@ def _overview_view(inv: str, max_power_kw: int) -> str:
name: Pause Mode
- entity: {_i(inv, "battery_temperature")}
name: Battery Temp
- entity: binary_sensor.givenergy_inverter_{inv}_battery_out_of_spec
name: Battery OOS

- type: glance
title: Today
Expand Down Expand Up @@ -735,6 +737,8 @@ def _diagnostics_view(inv: str, max_power_kw: int) -> str:
entities:
- entity: {_i(inv, "status")}
name: Inverter Status
- entity: binary_sensor.givenergy_inverter_{inv}_battery_out_of_spec
name: Battery Out Of Spec
- entity: {_i(inv, "fault_code")}
name: Fault Code
- entity: {_i(inv, "fault_messages")}
Expand Down Expand Up @@ -762,9 +766,15 @@ def _diagnostics_view(inv: str, max_power_kw: int) -> str:
title: Electrical
entities:
- entity: {_i(inv, "ac_voltage")}
name: AC Voltage
name: AC Voltage (input)
- entity: {_i(inv, "ac_frequency")}
name: AC Frequency
name: AC Frequency (input)
- entity: {_i(inv, "ac_output_voltage")}
name: AC Voltage (output)
- entity: {_i(inv, "ac_output_frequency")}
name: AC Frequency (output)
- entity: {_i(inv, "ac_output_current")}
name: AC Current (output)
- entity: {_i(inv, "battery_voltage")}
name: Battery Voltage
- entity: {_i(inv, "battery_current")}
Expand Down Expand Up @@ -799,6 +809,8 @@ def _diagnostics_view(inv: str, max_power_kw: int) -> str:
- type: entities
title: Hardware & Firmware
entities:
- entity: {_i(inv, "battery_maintenance_mode")}
name: Battery Maintenance Mode
Comment on lines +812 to +813

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The PR description states that battery_maintenance_mode is three-phase only and that skip_if_none keeps the entity absent on single-phase inverters. However, in sensor.py (line 176), the GivEnergyInverterSensorDescription for battery_maintenance_mode does not have skip_if_none=True set.\n\nWithout skip_if_none=True, this entity will still be created on single-phase inverters but will remain permanently unknown or unavailable. Please update the sensor description in sensor.py to include skip_if_none=True to align with the intended behavior.

- entity: {_i(inv, "arm_firmware_version")}
name: ARM Firmware
- entity: {_i(inv, "dsp_firmware_version")}
Expand Down
1 change: 1 addition & 0 deletions custom_components/givenergy_local/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ class GivEnergyCoordinatorSensorDescription(SensorEntityDescription):
options=[s.name.lower() for s in BatteryMaintenance],
translation_key="battery_maintenance_mode",
# Only present on three-phase inverters (HR 1124); None on single-phase.
skip_if_none=True,
value_fn=lambda inv: (
m.name.lower()
if (m := getattr(inv, "battery_maintenance_mode", None)) is not None
Expand Down
16 changes: 14 additions & 2 deletions dashboard/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ views:
name: Pause Mode
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_battery_temperature
name: Battery Temp
- entity: binary_sensor.givenergy_inverter_INVERTER_SERIAL_battery_out_of_spec
name: Battery OOS

- type: glance
title: Today
Expand Down Expand Up @@ -504,6 +506,8 @@ views:
entities:
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_status
name: Inverter Status
- entity: binary_sensor.givenergy_inverter_INVERTER_SERIAL_battery_out_of_spec
name: Battery Out Of Spec
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_fault_code
name: Fault Code
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_fault_messages
Expand Down Expand Up @@ -531,9 +535,15 @@ views:
title: Electrical
entities:
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_ac_voltage
name: AC Voltage
name: AC Voltage (input)
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_ac_frequency
name: AC Frequency
name: AC Frequency (input)
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_ac_output_voltage
name: AC Voltage (output)
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_ac_output_frequency
name: AC Frequency (output)
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_ac_output_current
name: AC Current (output)
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_battery_voltage
name: Battery Voltage
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_battery_current
Expand Down Expand Up @@ -568,6 +578,8 @@ views:
- type: entities
title: Hardware & Firmware
entities:
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_battery_maintenance_mode
name: Battery Maintenance Mode
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_arm_firmware_version
name: ARM Firmware
- entity: sensor.givenergy_inverter_INVERTER_SERIAL_dsp_firmware_version
Expand Down
26 changes: 25 additions & 1 deletion tests/test_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,31 @@ def test_dashboard_is_valid_yaml_with_expected_views():


def test_dashboard_version_is_current():
assert DASHBOARD_VERSION == 8
assert DASHBOARD_VERSION == 9


def test_battery_out_of_spec_on_status_glance_and_faults_card():
out = generate_dashboard(INV, BATS)
occurrences = out.count(f"binary_sensor.givenergy_inverter_{INV}_battery_out_of_spec")
# Once in the Overview Status glance; once in Diagnostics → Faults & Warnings.
assert occurrences >= 2, (
"battery_out_of_spec should appear in both Status glance and Faults card"
)


def test_diagnostics_electrical_has_ac_output_metrics():
out = generate_dashboard(INV, BATS)
for must in (
f"sensor.givenergy_inverter_{INV}_ac_output_voltage",
f"sensor.givenergy_inverter_{INV}_ac_output_frequency",
f"sensor.givenergy_inverter_{INV}_ac_output_current",
):
assert must in out, f"Electrical card missing {must}"


def test_diagnostics_hardware_card_includes_battery_maintenance_mode():
out = generate_dashboard(INV, BATS)
assert f"sensor.givenergy_inverter_{INV}_battery_maintenance_mode" in out


def test_battery_health_is_full_width_sections():
Expand Down
Loading