DSMR: Refactor sensor creation, added typing to sensors (#52153)
* DSMR: Refactor sensor creation, added typing to sensors * Log from package level * Apply suggestions from code review Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
04c9665241
commit
75c3daa45f
4 changed files with 249 additions and 151 deletions
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
@ -29,10 +28,9 @@ from .const import (
|
||||||
CONF_TIME_BETWEEN_UPDATE,
|
CONF_TIME_BETWEEN_UPDATE,
|
||||||
DEFAULT_TIME_BETWEEN_UPDATE,
|
DEFAULT_TIME_BETWEEN_UPDATE,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
LOGGER,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CONF_MANUAL_PATH = "Enter Manually"
|
CONF_MANUAL_PATH = "Enter Manually"
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,7 +90,7 @@ class DSMRConnection:
|
||||||
try:
|
try:
|
||||||
transport, protocol = await asyncio.create_task(reader_factory())
|
transport, protocol = await asyncio.create_task(reader_factory())
|
||||||
except (serial.serialutil.SerialException, OSError):
|
except (serial.serialutil.SerialException, OSError):
|
||||||
_LOGGER.exception("Error connecting to DSMR")
|
LOGGER.exception("Error connecting to DSMR")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if transport:
|
if transport:
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
"""Constants for the DSMR integration."""
|
"""Constants for the DSMR integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from dsmr_parser import obis_references
|
||||||
|
|
||||||
|
from .models import DSMRSensor
|
||||||
|
|
||||||
DOMAIN = "dsmr"
|
DOMAIN = "dsmr"
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
PLATFORMS = ["sensor"]
|
PLATFORMS = ["sensor"]
|
||||||
|
|
||||||
CONF_DSMR_VERSION = "dsmr_version"
|
CONF_DSMR_VERSION = "dsmr_version"
|
||||||
|
@ -28,3 +37,160 @@ ICON_GAS = "mdi:fire"
|
||||||
ICON_POWER = "mdi:flash"
|
ICON_POWER = "mdi:flash"
|
||||||
ICON_POWER_FAILURE = "mdi:flash-off"
|
ICON_POWER_FAILURE = "mdi:flash-off"
|
||||||
ICON_SWELL_SAG = "mdi:pulse"
|
ICON_SWELL_SAG = "mdi:pulse"
|
||||||
|
|
||||||
|
|
||||||
|
SENSORS: list[DSMRSensor] = [
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Consumption",
|
||||||
|
obis_reference=obis_references.CURRENT_ELECTRICITY_USAGE,
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Production",
|
||||||
|
obis_reference=obis_references.CURRENT_ELECTRICITY_DELIVERY,
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Tariff",
|
||||||
|
obis_reference=obis_references.ELECTRICITY_ACTIVE_TARIFF,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Consumption (tarif 1)",
|
||||||
|
obis_reference=obis_references.ELECTRICITY_USED_TARIFF_1,
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Consumption (tarif 2)",
|
||||||
|
obis_reference=obis_references.ELECTRICITY_USED_TARIFF_2,
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Production (tarif 1)",
|
||||||
|
obis_reference=obis_references.ELECTRICITY_DELIVERED_TARIFF_1,
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Production (tarif 2)",
|
||||||
|
obis_reference=obis_references.ELECTRICITY_DELIVERED_TARIFF_2,
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Consumption Phase L1",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Consumption Phase L2",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Consumption Phase L3",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Production Phase L1",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Production Phase L2",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Power Production Phase L3",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Short Power Failure Count",
|
||||||
|
obis_reference=obis_references.SHORT_POWER_FAILURE_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Long Power Failure Count",
|
||||||
|
obis_reference=obis_references.LONG_POWER_FAILURE_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Sags Phase L1",
|
||||||
|
obis_reference=obis_references.VOLTAGE_SAG_L1_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Sags Phase L2",
|
||||||
|
obis_reference=obis_references.VOLTAGE_SAG_L2_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Sags Phase L3",
|
||||||
|
obis_reference=obis_references.VOLTAGE_SAG_L3_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Swells Phase L1",
|
||||||
|
obis_reference=obis_references.VOLTAGE_SWELL_L1_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Swells Phase L2",
|
||||||
|
obis_reference=obis_references.VOLTAGE_SWELL_L2_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Swells Phase L3",
|
||||||
|
obis_reference=obis_references.VOLTAGE_SWELL_L3_COUNT,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Phase L1",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_VOLTAGE_L1,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Phase L2",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_VOLTAGE_L2,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Voltage Phase L3",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_VOLTAGE_L3,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Current Phase L1",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_CURRENT_L1,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Current Phase L2",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_CURRENT_L2,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Current Phase L3",
|
||||||
|
obis_reference=obis_references.INSTANTANEOUS_CURRENT_L3,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Consumption (total)",
|
||||||
|
obis_reference=obis_references.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL,
|
||||||
|
dsmr_versions={"5L"},
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Production (total)",
|
||||||
|
obis_reference=obis_references.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL,
|
||||||
|
dsmr_versions={"5L"},
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Energy Consumption (total)",
|
||||||
|
obis_reference=obis_references.ELECTRICITY_IMPORTED_TOTAL,
|
||||||
|
dsmr_versions={"2.2", "4", "5", "5B"},
|
||||||
|
force_update=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Gas Consumption",
|
||||||
|
obis_reference=obis_references.HOURLY_GAS_METER_READING,
|
||||||
|
dsmr_versions={"4", "5", "5L"},
|
||||||
|
force_update=True,
|
||||||
|
is_gas=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Gas Consumption",
|
||||||
|
obis_reference=obis_references.BELGIUM_HOURLY_GAS_METER_READING,
|
||||||
|
dsmr_versions={"5B"},
|
||||||
|
force_update=True,
|
||||||
|
is_gas=True,
|
||||||
|
),
|
||||||
|
DSMRSensor(
|
||||||
|
name="Gas Consumption",
|
||||||
|
obis_reference=obis_references.GAS_METER_READING,
|
||||||
|
dsmr_versions={"2.2"},
|
||||||
|
force_update=True,
|
||||||
|
is_gas=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
16
homeassistant/components/dsmr/models.py
Normal file
16
homeassistant/components/dsmr/models.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"""Models for the DSMR integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DSMRSensor:
|
||||||
|
"""Represents an DSMR Sensor."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
obis_reference: str
|
||||||
|
|
||||||
|
dsmr_versions: set[str] | None = None
|
||||||
|
force_update: bool = False
|
||||||
|
is_gas: bool = False
|
|
@ -6,10 +6,11 @@ from asyncio import CancelledError
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
from typing import Any
|
||||||
|
|
||||||
from dsmr_parser import obis_references as obis_ref
|
from dsmr_parser import obis_references as obis_ref
|
||||||
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
|
from dsmr_parser.clients.protocol import create_dsmr_reader, create_tcp_dsmr_reader
|
||||||
|
from dsmr_parser.objects import DSMRObject
|
||||||
import serial
|
import serial
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
@ -18,6 +19,8 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
||||||
from homeassistant.core import CoreState, HomeAssistant, callback
|
from homeassistant.core import CoreState, HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.typing import ConfigType, StateType
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -40,9 +43,10 @@ from .const import (
|
||||||
ICON_POWER,
|
ICON_POWER,
|
||||||
ICON_POWER_FAILURE,
|
ICON_POWER_FAILURE,
|
||||||
ICON_SWELL_SAG,
|
ICON_SWELL_SAG,
|
||||||
|
LOGGER,
|
||||||
|
SENSORS,
|
||||||
)
|
)
|
||||||
|
from .models import DSMRSensor
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
@ -57,7 +61,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
async def async_setup_platform(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigType,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
discovery_info: dict[str, Any] | None = None,
|
||||||
|
) -> None:
|
||||||
"""Import the platform into a config entry."""
|
"""Import the platform into a config entry."""
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
|
@ -67,139 +76,37 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the DSMR sensor."""
|
"""Set up the DSMR sensor."""
|
||||||
config = entry.data
|
dsmr_version = entry.data[CONF_DSMR_VERSION]
|
||||||
options = entry.options
|
entities = [
|
||||||
|
DSMREntity(sensor, entry)
|
||||||
dsmr_version = config[CONF_DSMR_VERSION]
|
for sensor in SENSORS
|
||||||
|
if (sensor.dsmr_versions is None or dsmr_version in sensor.dsmr_versions)
|
||||||
# Define list of name,obis,force_update mappings to generate entities
|
and (not sensor.is_gas or CONF_SERIAL_ID_GAS in entry.data)
|
||||||
obis_mapping = [
|
|
||||||
["Power Consumption", obis_ref.CURRENT_ELECTRICITY_USAGE, True],
|
|
||||||
["Power Production", obis_ref.CURRENT_ELECTRICITY_DELIVERY, True],
|
|
||||||
["Power Tariff", obis_ref.ELECTRICITY_ACTIVE_TARIFF, False],
|
|
||||||
["Energy Consumption (tarif 1)", obis_ref.ELECTRICITY_USED_TARIFF_1, True],
|
|
||||||
["Energy Consumption (tarif 2)", obis_ref.ELECTRICITY_USED_TARIFF_2, True],
|
|
||||||
["Energy Production (tarif 1)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_1, True],
|
|
||||||
["Energy Production (tarif 2)", obis_ref.ELECTRICITY_DELIVERED_TARIFF_2, True],
|
|
||||||
[
|
|
||||||
"Power Consumption Phase L1",
|
|
||||||
obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_POSITIVE,
|
|
||||||
False,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Power Consumption Phase L2",
|
|
||||||
obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_POSITIVE,
|
|
||||||
False,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Power Consumption Phase L3",
|
|
||||||
obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_POSITIVE,
|
|
||||||
False,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Power Production Phase L1",
|
|
||||||
obis_ref.INSTANTANEOUS_ACTIVE_POWER_L1_NEGATIVE,
|
|
||||||
False,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Power Production Phase L2",
|
|
||||||
obis_ref.INSTANTANEOUS_ACTIVE_POWER_L2_NEGATIVE,
|
|
||||||
False,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Power Production Phase L3",
|
|
||||||
obis_ref.INSTANTANEOUS_ACTIVE_POWER_L3_NEGATIVE,
|
|
||||||
False,
|
|
||||||
],
|
|
||||||
["Short Power Failure Count", obis_ref.SHORT_POWER_FAILURE_COUNT, False],
|
|
||||||
["Long Power Failure Count", obis_ref.LONG_POWER_FAILURE_COUNT, False],
|
|
||||||
["Voltage Sags Phase L1", obis_ref.VOLTAGE_SAG_L1_COUNT, False],
|
|
||||||
["Voltage Sags Phase L2", obis_ref.VOLTAGE_SAG_L2_COUNT, False],
|
|
||||||
["Voltage Sags Phase L3", obis_ref.VOLTAGE_SAG_L3_COUNT, False],
|
|
||||||
["Voltage Swells Phase L1", obis_ref.VOLTAGE_SWELL_L1_COUNT, False],
|
|
||||||
["Voltage Swells Phase L2", obis_ref.VOLTAGE_SWELL_L2_COUNT, False],
|
|
||||||
["Voltage Swells Phase L3", obis_ref.VOLTAGE_SWELL_L3_COUNT, False],
|
|
||||||
["Voltage Phase L1", obis_ref.INSTANTANEOUS_VOLTAGE_L1, False],
|
|
||||||
["Voltage Phase L2", obis_ref.INSTANTANEOUS_VOLTAGE_L2, False],
|
|
||||||
["Voltage Phase L3", obis_ref.INSTANTANEOUS_VOLTAGE_L3, False],
|
|
||||||
["Current Phase L1", obis_ref.INSTANTANEOUS_CURRENT_L1, False],
|
|
||||||
["Current Phase L2", obis_ref.INSTANTANEOUS_CURRENT_L2, False],
|
|
||||||
["Current Phase L3", obis_ref.INSTANTANEOUS_CURRENT_L3, False],
|
|
||||||
]
|
]
|
||||||
|
async_add_entities(entities)
|
||||||
if dsmr_version == "5L":
|
|
||||||
obis_mapping.extend(
|
|
||||||
[
|
|
||||||
[
|
|
||||||
"Energy Consumption (total)",
|
|
||||||
obis_ref.LUXEMBOURG_ELECTRICITY_USED_TARIFF_GLOBAL,
|
|
||||||
True,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Energy Production (total)",
|
|
||||||
obis_ref.LUXEMBOURG_ELECTRICITY_DELIVERED_TARIFF_GLOBAL,
|
|
||||||
True,
|
|
||||||
],
|
|
||||||
]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
obis_mapping.extend(
|
|
||||||
[["Energy Consumption (total)", obis_ref.ELECTRICITY_IMPORTED_TOTAL, True]]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate device entities
|
|
||||||
devices = [
|
|
||||||
DSMREntity(
|
|
||||||
name, DEVICE_NAME_ENERGY, config[CONF_SERIAL_ID], obis, config, force_update
|
|
||||||
)
|
|
||||||
for name, obis, force_update in obis_mapping
|
|
||||||
]
|
|
||||||
|
|
||||||
# Protocol version specific obis
|
|
||||||
if CONF_SERIAL_ID_GAS in config:
|
|
||||||
if dsmr_version in ("4", "5", "5L"):
|
|
||||||
gas_obis = obis_ref.HOURLY_GAS_METER_READING
|
|
||||||
elif dsmr_version in ("5B",):
|
|
||||||
gas_obis = obis_ref.BELGIUM_HOURLY_GAS_METER_READING
|
|
||||||
else:
|
|
||||||
gas_obis = obis_ref.GAS_METER_READING
|
|
||||||
|
|
||||||
# Add gas meter reading
|
|
||||||
devices += [
|
|
||||||
DSMREntity(
|
|
||||||
"Gas Consumption",
|
|
||||||
DEVICE_NAME_GAS,
|
|
||||||
config[CONF_SERIAL_ID_GAS],
|
|
||||||
gas_obis,
|
|
||||||
config,
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
async_add_entities(devices)
|
|
||||||
|
|
||||||
min_time_between_updates = timedelta(
|
min_time_between_updates = timedelta(
|
||||||
seconds=options.get(CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE)
|
seconds=entry.options.get(CONF_TIME_BETWEEN_UPDATE, DEFAULT_TIME_BETWEEN_UPDATE)
|
||||||
)
|
)
|
||||||
|
|
||||||
@Throttle(min_time_between_updates)
|
@Throttle(min_time_between_updates)
|
||||||
def update_entities_telegram(telegram):
|
def update_entities_telegram(telegram: dict[str, DSMRObject]):
|
||||||
"""Update entities with latest telegram and trigger state update."""
|
"""Update entities with latest telegram and trigger state update."""
|
||||||
# Make all device entities aware of new telegram
|
# Make all device entities aware of new telegram
|
||||||
for device in devices:
|
for entity in entities:
|
||||||
device.update_data(telegram)
|
entity.update_data(telegram)
|
||||||
|
|
||||||
# Creates an asyncio.Protocol factory for reading DSMR telegrams from
|
# Creates an asyncio.Protocol factory for reading DSMR telegrams from
|
||||||
# serial and calls update_entities_telegram to update entities on arrival
|
# serial and calls update_entities_telegram to update entities on arrival
|
||||||
if CONF_HOST in config:
|
if CONF_HOST in entry.data:
|
||||||
reader_factory = partial(
|
reader_factory = partial(
|
||||||
create_tcp_dsmr_reader,
|
create_tcp_dsmr_reader,
|
||||||
config[CONF_HOST],
|
entry.data[CONF_HOST],
|
||||||
config[CONF_PORT],
|
entry.data[CONF_PORT],
|
||||||
config[CONF_DSMR_VERSION],
|
entry.data[CONF_DSMR_VERSION],
|
||||||
update_entities_telegram,
|
update_entities_telegram,
|
||||||
loop=hass.loop,
|
loop=hass.loop,
|
||||||
keep_alive_interval=60,
|
keep_alive_interval=60,
|
||||||
|
@ -207,13 +114,13 @@ async def async_setup_entry(
|
||||||
else:
|
else:
|
||||||
reader_factory = partial(
|
reader_factory = partial(
|
||||||
create_dsmr_reader,
|
create_dsmr_reader,
|
||||||
config[CONF_PORT],
|
entry.data[CONF_PORT],
|
||||||
config[CONF_DSMR_VERSION],
|
entry.data[CONF_DSMR_VERSION],
|
||||||
update_entities_telegram,
|
update_entities_telegram,
|
||||||
loop=hass.loop,
|
loop=hass.loop,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def connect_and_reconnect():
|
async def connect_and_reconnect() -> None:
|
||||||
"""Connect to DSMR and keep reconnecting until Home Assistant stops."""
|
"""Connect to DSMR and keep reconnecting until Home Assistant stops."""
|
||||||
stop_listener = None
|
stop_listener = None
|
||||||
transport = None
|
transport = None
|
||||||
|
@ -245,12 +152,12 @@ async def async_setup_entry(
|
||||||
update_entities_telegram({})
|
update_entities_telegram({})
|
||||||
|
|
||||||
# throttle reconnect attempts
|
# throttle reconnect attempts
|
||||||
await asyncio.sleep(config[CONF_RECONNECT_INTERVAL])
|
await asyncio.sleep(entry.data[CONF_RECONNECT_INTERVAL])
|
||||||
|
|
||||||
except (serial.serialutil.SerialException, OSError):
|
except (serial.serialutil.SerialException, OSError):
|
||||||
# Log any error while establishing connection and drop to retry
|
# Log any error while establishing connection and drop to retry
|
||||||
# connection wait
|
# connection wait
|
||||||
_LOGGER.exception("Error connecting to DSMR")
|
LOGGER.exception("Error connecting to DSMR")
|
||||||
transport = None
|
transport = None
|
||||||
protocol = None
|
protocol = None
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
|
@ -277,40 +184,48 @@ class DSMREntity(SensorEntity):
|
||||||
|
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, name, device_name, device_serial, obis, config, force_update):
|
def __init__(self, sensor: DSMRSensor, entry: ConfigEntry) -> None:
|
||||||
"""Initialize entity."""
|
"""Initialize entity."""
|
||||||
self._obis = obis
|
self._sensor = sensor
|
||||||
self._config = config
|
self._entry = entry
|
||||||
self.telegram = {}
|
self.telegram: dict[str, DSMRObject] = {}
|
||||||
|
|
||||||
self._attr_name = name
|
device_serial = entry.data[CONF_SERIAL_ID]
|
||||||
self._attr_force_update = force_update
|
device_name = DEVICE_NAME_ENERGY
|
||||||
self._attr_unique_id = f"{device_serial}_{name}".replace(" ", "_")
|
if sensor.is_gas:
|
||||||
|
device_serial = entry.data[CONF_SERIAL_ID_GAS]
|
||||||
|
device_name = DEVICE_NAME_GAS
|
||||||
|
|
||||||
|
self._attr_name = sensor.name
|
||||||
|
self._attr_force_update = sensor.force_update
|
||||||
|
self._attr_unique_id = f"{device_serial}_{sensor.name}".replace(" ", "_")
|
||||||
self._attr_device_info = {
|
self._attr_device_info = {
|
||||||
"identifiers": {(DOMAIN, device_serial)},
|
"identifiers": {(DOMAIN, device_serial)},
|
||||||
"name": device_name,
|
"name": device_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_data(self, telegram):
|
def update_data(self, telegram: dict[str, DSMRObject]) -> None:
|
||||||
"""Update data."""
|
"""Update data."""
|
||||||
self.telegram = telegram
|
self.telegram = telegram
|
||||||
if self.hass and self._obis in self.telegram:
|
if self.hass and self._sensor.obis_reference in self.telegram:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
def get_dsmr_object_attr(self, attribute):
|
def get_dsmr_object_attr(self, attribute: str) -> str | None:
|
||||||
"""Read attribute from last received telegram for this DSMR object."""
|
"""Read attribute from last received telegram for this DSMR object."""
|
||||||
# Make sure telegram contains an object for this entities obis
|
# Make sure telegram contains an object for this entities obis
|
||||||
if self._obis not in self.telegram:
|
if self._sensor.obis_reference not in self.telegram:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Get the attribute value if the object has it
|
# Get the attribute value if the object has it
|
||||||
dsmr_object = self.telegram[self._obis]
|
dsmr_object = self.telegram[self._sensor.obis_reference]
|
||||||
return getattr(dsmr_object, attribute, None)
|
return getattr(dsmr_object, attribute, None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def icon(self):
|
def icon(self) -> str | None:
|
||||||
"""Icon to use in the frontend, if any."""
|
"""Icon to use in the frontend, if any."""
|
||||||
|
if not self.name:
|
||||||
|
return None
|
||||||
if "Sags" in self.name or "Swells" in self.name:
|
if "Sags" in self.name or "Swells" in self.name:
|
||||||
return ICON_SWELL_SAG
|
return ICON_SWELL_SAG
|
||||||
if "Failure" in self.name:
|
if "Failure" in self.name:
|
||||||
|
@ -319,18 +234,21 @@ class DSMREntity(SensorEntity):
|
||||||
return ICON_POWER
|
return ICON_POWER
|
||||||
if "Gas" in self.name:
|
if "Gas" in self.name:
|
||||||
return ICON_GAS
|
return ICON_GAS
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self) -> StateType:
|
||||||
"""Return the state of sensor, if available, translate if needed."""
|
"""Return the state of sensor, if available, translate if needed."""
|
||||||
value = self.get_dsmr_object_attr("value")
|
value = self.get_dsmr_object_attr("value")
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
if self._obis == obis_ref.ELECTRICITY_ACTIVE_TARIFF:
|
if self._sensor.obis_reference == obis_ref.ELECTRICITY_ACTIVE_TARIFF:
|
||||||
return self.translate_tariff(value, self._config[CONF_DSMR_VERSION])
|
return self.translate_tariff(value, self._entry.data[CONF_DSMR_VERSION])
|
||||||
|
|
||||||
with suppress(TypeError):
|
with suppress(TypeError):
|
||||||
value = round(
|
value = round(
|
||||||
float(value), self._config.get(CONF_PRECISION, DEFAULT_PRECISION)
|
float(value), self._entry.data.get(CONF_PRECISION, DEFAULT_PRECISION)
|
||||||
)
|
)
|
||||||
|
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
@ -339,16 +257,16 @@ class DSMREntity(SensorEntity):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self) -> str | None:
|
||||||
"""Return the unit of measurement of this entity, if any."""
|
"""Return the unit of measurement of this entity, if any."""
|
||||||
return self.get_dsmr_object_attr("unit")
|
return self.get_dsmr_object_attr("unit")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def translate_tariff(value, dsmr_version):
|
def translate_tariff(value: str, dsmr_version: str) -> str | None:
|
||||||
"""Convert 2/1 to normal/low depending on DSMR version."""
|
"""Convert 2/1 to normal/low depending on DSMR version."""
|
||||||
# DSMR V5B: Note: In Belgium values are swapped:
|
# DSMR V5B: Note: In Belgium values are swapped:
|
||||||
# Rate code 2 is used for low rate and rate code 1 is used for normal rate.
|
# Rate code 2 is used for low rate and rate code 1 is used for normal rate.
|
||||||
if dsmr_version in ("5B",):
|
if dsmr_version == "5B":
|
||||||
if value == "0001":
|
if value == "0001":
|
||||||
value = "0002"
|
value = "0002"
|
||||||
elif value == "0002":
|
elif value == "0002":
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue