diff --git a/custom_components/givenergy_local/binary_sensor.py b/custom_components/givenergy_local/binary_sensor.py index bea64ca..ad4582a 100644 --- a/custom_components/givenergy_local/binary_sensor.py +++ b/custom_components/givenergy_local/binary_sensor.py @@ -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 @@ -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 " → entity_id + # binary_sensor.givenergy_inverter__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() diff --git a/custom_components/givenergy_local/dashboard.py b/custom_components/givenergy_local/dashboard.py index ad74b50..4c099e9 100644 --- a/custom_components/givenergy_local/dashboard.py +++ b/custom_components/givenergy_local/dashboard.py @@ -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): @@ -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 @@ -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")} @@ -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")} @@ -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 - entity: {_i(inv, "arm_firmware_version")} name: ARM Firmware - entity: {_i(inv, "dsp_firmware_version")} diff --git a/custom_components/givenergy_local/sensor.py b/custom_components/givenergy_local/sensor.py index 158e4e3..7ee2f6d 100644 --- a/custom_components/givenergy_local/sensor.py +++ b/custom_components/givenergy_local/sensor.py @@ -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 diff --git a/dashboard/template.yaml b/dashboard/template.yaml index 37b1d76..a9c188f 100644 --- a/dashboard/template.yaml +++ b/dashboard/template.yaml @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index c294b2c..3fe8165 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -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():