Add diagnostics to KACO Modbus (#181771)

This commit is contained in:
g4bri3lDev
2026-09-29 19:41:54 +02:00
committed by GitHub
parent fe11885cae
commit ccc1aa2f87
4 changed files with 370 additions and 1 deletions
@@ -0,0 +1,46 @@
"""Diagnostics for KACO Modbus."""
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant
from .coordinator import KacoConfigEntry
TO_REDACT = {"serial_number"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: KacoConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry.
The raw register map is included because it loads straight into
``modbus_connection.mock``, which reproduces a reported problem without
the hardware. It covers the polled components only, so the serial number
model 1 holds in the clear is not in it.
"""
coordinator = entry.runtime_data
device = coordinator.device
info = device.info
assert info is not None
return async_redact_data(
{
"manufacturer": info.manufacturer,
"model": info.model,
"firmware": info.firmware,
"options": info.options,
"serial_number": info.serial_number,
"base_address": device.base_address,
"models": sorted(device.models or ()),
"strings": len(device.strings),
"updated": sorted(coordinator.data.updated),
"failed": {
component: str(error)
for component, error in coordinator.data.failed.items()
},
"raw": await device.async_read_raw(),
},
TO_REDACT,
)
@@ -52,7 +52,7 @@ rules:
test-coverage: done
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: There is no discovery to carry updated network information.
@@ -0,0 +1,282 @@
# serializer version: 1
# name: test_diagnostics
dict({
'base_address': 40000,
'failed': dict({
}),
'firmware': 'V5.53',
'manufacturer': 'KACO new energy',
'model': 'blueplanet 8.6 TL3 INT',
'models': list([
1,
103,
113,
120,
121,
122,
123,
126,
129,
130,
132,
135,
136,
160,
64204,
]),
'options': '086L32',
'raw': dict({
'holding': dict({
'40070': 103,
'40071': 50,
'40072': 484,
'40073': 164,
'40074': 162,
'40075': 157,
'40076': 65534,
'40077': 65535,
'40078': 65535,
'40079': 65535,
'40080': 2265,
'40081': 2282,
'40082': 2278,
'40083': 65535,
'40084': 100,
'40085': 1,
'40086': 49944,
'40087': 65533,
'40088': 99,
'40089': 1,
'40090': 0,
'40091': 1,
'40092': 1000,
'40093': 65533,
'40094': 185,
'40095': 63009,
'40096': 0,
'40097': 230,
'40098': 65534,
'40099': 4437,
'40100': 65535,
'40101': 102,
'40102': 1,
'40103': 469,
'40104': 32768,
'40105': 32768,
'40106': 32768,
'40107': 65535,
'40108': 4,
'40109': 4,
'40110': 0,
'40111': 0,
'40112': 0,
'40113': 0,
'40114': 65535,
'40115': 65535,
'40116': 65535,
'40117': 65535,
'40118': 65535,
'40119': 65535,
'40120': 65535,
'40121': 65535,
'40184': 120,
'40185': 26,
'40186': 4,
'40187': 860,
'40188': 1,
'40189': 860,
'40190': 1,
'40191': 860,
'40192': 32768,
'40193': 32768,
'40194': 860,
'40195': 1,
'40196': 65535,
'40197': 32768,
'40198': 50880,
'40199': 32768,
'40200': 32768,
'40201': 50880,
'40202': 65533,
'40203': 65535,
'40204': 32768,
'40205': 65535,
'40206': 32768,
'40207': 65535,
'40208': 32768,
'40209': 65535,
'40210': 32768,
'40212': 121,
'40213': 30,
'40214': 860,
'40215': 2300,
'40216': 32768,
'40217': 65535,
'40218': 65535,
'40219': 860,
'40220': 32768,
'40221': 32768,
'40222': 32768,
'40223': 32768,
'40224': 65535,
'40225': 32768,
'40226': 32768,
'40227': 32768,
'40228': 32768,
'40229': 65535,
'40230': 65535,
'40231': 65535,
'40232': 65535,
'40233': 65535,
'40234': 1,
'40235': 65535,
'40236': 32768,
'40237': 32768,
'40238': 1,
'40239': 32768,
'40240': 32768,
'40241': 32768,
'40242': 32768,
'40243': 32768,
'40244': 122,
'40245': 44,
'40246': 7,
'40247': 65535,
'40248': 1,
'40249': 0,
'40250': 0,
'40251': 0,
'40252': 0,
'40253': 0,
'40254': 0,
'40255': 0,
'40256': 0,
'40257': 0,
'40258': 0,
'40259': 0,
'40260': 0,
'40261': 0,
'40262': 0,
'40263': 0,
'40264': 0,
'40265': 0,
'40266': 0,
'40267': 0,
'40268': 0,
'40269': 0,
'40270': 0,
'40271': 0,
'40272': 0,
'40273': 32768,
'40274': 32768,
'40275': 65535,
'40276': 32768,
'40277': 65535,
'40278': 65535,
'40279': 65535,
'40280': 65535,
'40281': 19265,
'40282': 17231,
'40283': 8302,
'40284': 101,
'40285': 12830,
'40286': 1609,
'40287': 65535,
'40288': 65535,
'40289': 32768,
'40290': 123,
'40291': 24,
'40292': 65535,
'40293': 65535,
'40294': 1,
'40295': 1000,
'40296': 65535,
'40297': 300,
'40298': 65535,
'40299': 0,
'40300': 1000,
'40301': 65535,
'40302': 300,
'40303': 65535,
'40304': 0,
'40305': 0,
'40306': 0,
'40307': 32768,
'40308': 65535,
'40309': 300,
'40310': 65535,
'40311': 1,
'40312': 0,
'40313': 65535,
'40314': 65533,
'40315': 65535,
'40316': 126,
'40317': 226,
'40318': 1,
'40319': 0,
'40320': 65535,
'40321': 65535,
'40322': 65535,
'40323': 4,
'40324': 10,
'40858': 160,
'40859': 48,
'40860': 65534,
'40861': 65535,
'40862': 1,
'40863': 32768,
'40864': 0,
'40865': 0,
'40866': 2,
'40867': 65535,
'40868': 0,
'40869': 19792,
'40870': 20564,
'40871': 8240,
'40872': 0,
'40873': 0,
'40874': 0,
'40875': 0,
'40876': 0,
'40877': 102,
'40878': 3551,
'40879': 36,
'40880': 0,
'40881': 0,
'40882': 27275,
'40883': 15290,
'40884': 45,
'40885': 65535,
'40886': 65535,
'40887': 65535,
'40888': 1,
'40889': 19792,
'40890': 20564,
'40891': 8241,
'40892': 0,
'40893': 0,
'40894': 0,
'40895': 0,
'40896': 0,
'40897': 122,
'40898': 5330,
'40899': 65,
'40900': 0,
'40901': 0,
'40902': 27275,
'40903': 15290,
'40904': 0,
'40905': 65535,
'40906': 65535,
'40907': 65535,
}),
}),
'serial_number': '**REDACTED**',
'strings': 2,
'updated': list([
'inverter',
'mppt',
'status',
]),
})
# ---
@@ -0,0 +1,41 @@
"""Test the KACO Modbus diagnostics."""
import json
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
from . import MOCK_SERIAL
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
init_integration: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test generating diagnostics for a config entry."""
diag = await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
assert diag == snapshot
async def test_diagnostics_does_not_leak_the_serial_number(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
init_integration: MockConfigEntry,
) -> None:
"""Test the serial is redacted, and absent from the raw registers.
Model 1 carries it in the clear, so a raw map that reached back over that
block would hand it to anyone who decoded the dump.
"""
diag = await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
assert diag["serial_number"] == "**REDACTED**"
assert MOCK_SERIAL not in json.dumps(diag)