diff --git a/homeassistant/components/sofar/diagnostics.py b/homeassistant/components/sofar/diagnostics.py index 697edac6bb93..dda7daad98b9 100644 --- a/homeassistant/components/sofar/diagnostics.py +++ b/homeassistant/components/sofar/diagnostics.py @@ -3,6 +3,8 @@ from dataclasses import asdict from typing import Any +from modbus_connection import ModbusError + from homeassistant.components.diagnostics import async_redact_data from homeassistant.core import HomeAssistant @@ -19,8 +21,13 @@ async def async_get_config_entry_diagnostics( """Return diagnostics for a config entry.""" runtime_data = entry.runtime_data device = runtime_data.readings.device - raw = await device.async_read_raw() - if (holding := raw.get("holding")) is not None: + raw = masks = read_error = None + try: + raw = await device.async_read_raw() + masks = await device.async_read_masks() + except ModbusError as err: + read_error = type(err).__name__ + if raw is not None and (holding := raw.get("holding")) is not None: for address in _SERIAL_NUMBER_REGISTERS: holding.pop(address, None) @@ -32,11 +39,12 @@ async def async_get_config_entry_diagnostics( "readings_components": device.readings_components, "settings_components": device.settings_components, "active_faults": sorted(fault.key for fault in device.state.active_faults), - "address_masks": await device.async_read_masks(), + "address_masks": masks, "link": { "tuning": asdict(runtime_data.tuner.tuning), "stats": asdict(runtime_data.link.stats), }, + "read_error": read_error, "raw": raw, }, TO_REDACT, diff --git a/tests/components/sofar/snapshots/test_diagnostics.ambr b/tests/components/sofar/snapshots/test_diagnostics.ambr index f676db3dda70..3cf3301a8115 100644 --- a/tests/components/sofar/snapshots/test_diagnostics.ambr +++ b/tests/components/sofar/snapshots/test_diagnostics.ambr @@ -194,6 +194,7 @@ '4358': 0, }), }), + 'read_error': None, 'readings_components': list([ 'state', 'grid', diff --git a/tests/components/sofar/test_diagnostics.py b/tests/components/sofar/test_diagnostics.py index 82d885e0f8fe..de411d6ce81e 100644 --- a/tests/components/sofar/test_diagnostics.py +++ b/tests/components/sofar/test_diagnostics.py @@ -2,7 +2,9 @@ from unittest.mock import patch +from modbus_connection import ModbusConnectionError, ModbusError, ModbusTimeoutError from modbus_connection.mock import MockModbusConnection +import pytest from syrupy.assertion import SnapshotAssertion from syrupy.matchers import path_type @@ -83,3 +85,33 @@ async def test_diagnostics_decodes_address_masks( diag = await get_diagnostics_for_config_entry(hass, hass_client, init_integration) assert diag["address_masks"]["1024"] == 0x0001000200030004 + + +@pytest.mark.parametrize( + ("error", "error_name"), + [ + pytest.param(ModbusTimeoutError("silent"), "ModbusTimeoutError", id="timeout"), + pytest.param( + ModbusConnectionError("gone"), "ModbusConnectionError", id="link_lost" + ), + ], +) +async def test_diagnostics_inverter_unreachable( + hass: HomeAssistant, + hass_client: ClientSessionGenerator, + mock_connection: MockModbusConnection, + init_integration: MockConfigEntry, + error: ModbusError, + error_name: str, +) -> None: + """Test diagnostics still download when the inverter stops answering.""" + unit = mock_connection.for_unit(1) + unit.fail_requests(error) + unit.read_events.clear() + + diag = await get_diagnostics_for_config_entry(hass, hass_client, init_integration) + + assert diag["read_error"] == error_name + assert diag["raw"] is None + assert diag["address_masks"] is None + assert len(unit.read_events) == 1