mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Add diagnostic information to DSMR (#122041)
* Add diagnostic information to DSMR Switches to runtime_data to get access to the last telegram received. * Correct import of domain * Apply suggestions from code review Co-authored-by: G Johansson <goran.johansson@shiftit.se> --------- Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
parent
41d75e159b
commit
42610f4e09
@ -2,18 +2,32 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from asyncio import CancelledError
|
||||
from asyncio import CancelledError, Task
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from dsmr_parser.objects import Telegram
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import CONF_DSMR_VERSION, DATA_TASK, DOMAIN, PLATFORMS
|
||||
from .const import CONF_DSMR_VERSION, PLATFORMS
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
@dataclass
|
||||
class DsmrState:
|
||||
"""State of integration."""
|
||||
|
||||
task: Task | None = None
|
||||
telegram: Telegram | None = None
|
||||
|
||||
|
||||
type DsmrConfigEntry = ConfigEntry[DsmrState]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: DsmrConfigEntry) -> bool:
|
||||
"""Set up DSMR from a config entry."""
|
||||
|
||||
@callback
|
||||
@ -25,32 +39,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await er.async_migrate_entries(hass, entry.entry_id, _async_migrate_entity_entry)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = {}
|
||||
|
||||
entry.runtime_data = DsmrState()
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
entry.async_on_unload(entry.add_update_listener(async_update_options))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: DsmrConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
task = hass.data[DOMAIN][entry.entry_id][DATA_TASK]
|
||||
|
||||
# Cancel the reconnect task
|
||||
task.cancel()
|
||||
with suppress(CancelledError):
|
||||
await task
|
||||
if task := entry.runtime_data.task:
|
||||
task.cancel()
|
||||
with suppress(CancelledError):
|
||||
await task
|
||||
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
async def async_update_options(hass: HomeAssistant, entry: DsmrConfigEntry) -> None:
|
||||
"""Update options."""
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
@ -23,8 +23,6 @@ DEFAULT_PRECISION = 3
|
||||
DEFAULT_RECONNECT_INTERVAL = 30
|
||||
DEFAULT_TIME_BETWEEN_UPDATE = 30
|
||||
|
||||
DATA_TASK = "task"
|
||||
|
||||
DEVICE_NAME_ELECTRICITY = "Electricity Meter"
|
||||
DEVICE_NAME_GAS = "Gas Meter"
|
||||
DEVICE_NAME_WATER = "Water Meter"
|
||||
|
28
homeassistant/components/dsmr/diagnostics.py
Normal file
28
homeassistant/components/dsmr/diagnostics.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""Diagnostics support for DSMR."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.util.json import json_loads
|
||||
|
||||
from . import DsmrConfigEntry
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: DsmrConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
|
||||
return {
|
||||
"entry": {
|
||||
"data": {
|
||||
**config_entry.data,
|
||||
},
|
||||
"unique_id": config_entry.unique_id,
|
||||
},
|
||||
"data": json_loads(config_entry.runtime_data.telegram.to_json())
|
||||
if config_entry.runtime_data.telegram
|
||||
else None,
|
||||
}
|
@ -46,12 +46,12 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import DsmrConfigEntry
|
||||
from .const import (
|
||||
CONF_DSMR_VERSION,
|
||||
CONF_SERIAL_ID,
|
||||
CONF_SERIAL_ID_GAS,
|
||||
CONF_TIME_BETWEEN_UPDATE,
|
||||
DATA_TASK,
|
||||
DEFAULT_PRECISION,
|
||||
DEFAULT_RECONNECT_INTERVAL,
|
||||
DEFAULT_TIME_BETWEEN_UPDATE,
|
||||
@ -514,7 +514,7 @@ def create_mbus_entities(
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
hass: HomeAssistant, entry: DsmrConfigEntry, async_add_entities: AddEntitiesCallback
|
||||
) -> None:
|
||||
"""Set up the DSMR sensor."""
|
||||
dsmr_version = entry.data[CONF_DSMR_VERSION]
|
||||
@ -567,6 +567,8 @@ async def async_setup_entry(
|
||||
for entity in entities:
|
||||
entity.update_data(telegram)
|
||||
|
||||
entry.runtime_data.telegram = telegram
|
||||
|
||||
if not initialized and telegram:
|
||||
initialized = True
|
||||
async_dispatcher_send(
|
||||
@ -695,7 +697,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
# Save the task to be able to cancel it when unloading
|
||||
hass.data[DOMAIN][entry.entry_id][DATA_TASK] = task
|
||||
entry.runtime_data.task = task
|
||||
|
||||
|
||||
class DSMREntity(SensorEntity):
|
||||
|
29
tests/components/dsmr/snapshots/test_diagnostics.ambr
Normal file
29
tests/components/dsmr/snapshots/test_diagnostics.ambr
Normal file
@ -0,0 +1,29 @@
|
||||
# serializer version: 1
|
||||
# name: test_diagnostics
|
||||
dict({
|
||||
'data': dict({
|
||||
'CURRENT_ELECTRICITY_USAGE': dict({
|
||||
'unit': 'W',
|
||||
'value': 0.0,
|
||||
}),
|
||||
'ELECTRICITY_ACTIVE_TARIFF': dict({
|
||||
'unit': '',
|
||||
'value': '0001',
|
||||
}),
|
||||
'GAS_METER_READING': dict({
|
||||
'datetime': '2019-03-03T19:43:33+00:00',
|
||||
'unit': 'm³',
|
||||
'value': 745.695,
|
||||
}),
|
||||
}),
|
||||
'entry': dict({
|
||||
'data': dict({
|
||||
'dsmr_version': '2.2',
|
||||
'port': '/dev/ttyUSB0',
|
||||
'serial_id': '1234',
|
||||
'serial_id_gas': '5678',
|
||||
}),
|
||||
'unique_id': '/dev/ttyUSB0',
|
||||
}),
|
||||
})
|
||||
# ---
|
@ -10,7 +10,8 @@ import serial
|
||||
import serial.tools.list_ports
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.dsmr import DOMAIN, config_flow
|
||||
from homeassistant.components.dsmr import config_flow
|
||||
from homeassistant.components.dsmr.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
|
82
tests/components/dsmr/test_diagnostics.py
Normal file
82
tests/components/dsmr/test_diagnostics.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""Test DSMR diagnostics."""
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from dsmr_parser.obis_references import (
|
||||
CURRENT_ELECTRICITY_USAGE,
|
||||
ELECTRICITY_ACTIVE_TARIFF,
|
||||
GAS_METER_READING,
|
||||
)
|
||||
from dsmr_parser.objects import CosemObject, MBusObject, Telegram
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
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,
|
||||
dsmr_connection_fixture: tuple[MagicMock, MagicMock, MagicMock],
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test diagnostics."""
|
||||
(connection_factory, transport, protocol) = dsmr_connection_fixture
|
||||
|
||||
entry_data = {
|
||||
"port": "/dev/ttyUSB0",
|
||||
"dsmr_version": "2.2",
|
||||
"serial_id": "1234",
|
||||
"serial_id_gas": "5678",
|
||||
}
|
||||
entry_options = {
|
||||
"time_between_update": 0,
|
||||
}
|
||||
|
||||
telegram = Telegram()
|
||||
telegram.add(
|
||||
CURRENT_ELECTRICITY_USAGE,
|
||||
CosemObject(
|
||||
(0, 0),
|
||||
[{"value": Decimal("0.0"), "unit": "W"}],
|
||||
),
|
||||
"CURRENT_ELECTRICITY_USAGE",
|
||||
)
|
||||
telegram.add(
|
||||
ELECTRICITY_ACTIVE_TARIFF,
|
||||
CosemObject((0, 0), [{"value": "0001", "unit": ""}]),
|
||||
"ELECTRICITY_ACTIVE_TARIFF",
|
||||
)
|
||||
telegram.add(
|
||||
GAS_METER_READING,
|
||||
MBusObject(
|
||||
(0, 0),
|
||||
[
|
||||
{"value": datetime.datetime.fromtimestamp(1551642213)},
|
||||
{"value": Decimal(745.695), "unit": "m³"},
|
||||
],
|
||||
),
|
||||
"GAS_METER_READING",
|
||||
)
|
||||
|
||||
mock_entry = MockConfigEntry(
|
||||
domain="dsmr", unique_id="/dev/ttyUSB0", data=entry_data, options=entry_options
|
||||
)
|
||||
|
||||
mock_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
telegram_callback = connection_factory.call_args_list[0][0][2]
|
||||
|
||||
# simulate a telegram pushed from the smartmeter and parsed by dsmr_parser
|
||||
telegram_callback(telegram)
|
||||
|
||||
result = await get_diagnostics_for_config_entry(hass, hass_client, mock_entry)
|
||||
assert result == snapshot
|
Loading…
x
Reference in New Issue
Block a user