1 """Support for EDL21 Smart Meters."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
6 from datetime
import timedelta
9 from sml
import SmlGetListResponse
10 from sml.asyncio
import SmlProtocol
15 SensorEntityDescription,
21 UnitOfElectricCurrent,
22 UnitOfElectricPotential,
30 async_dispatcher_connect,
31 async_dispatcher_send,
41 SIGNAL_EDL21_TELEGRAM,
47 SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
54 translation_key=
"ownership_id",
55 entity_registry_enabled_default=
False,
60 translation_key=
"electricity_id",
65 translation_key=
"configuration_program_version_number",
69 translation_key=
"firmware_version_number",
76 translation_key=
"positive_active_instantaneous_power",
77 state_class=SensorStateClass.MEASUREMENT,
78 device_class=SensorDeviceClass.POWER,
85 translation_key=
"positive_active_energy_total",
86 state_class=SensorStateClass.TOTAL_INCREASING,
87 device_class=SensorDeviceClass.ENERGY,
92 translation_key=
"positive_active_energy_tariff_t1",
93 state_class=SensorStateClass.TOTAL_INCREASING,
94 device_class=SensorDeviceClass.ENERGY,
99 translation_key=
"positive_active_energy_tariff_t2",
100 state_class=SensorStateClass.TOTAL_INCREASING,
101 device_class=SensorDeviceClass.ENERGY,
106 key=
"1-0:1.17.0*255",
107 translation_key=
"last_signed_positive_active_energy_total",
114 translation_key=
"negative_active_energy_total",
115 state_class=SensorStateClass.TOTAL_INCREASING,
116 device_class=SensorDeviceClass.ENERGY,
121 translation_key=
"negative_active_energy_tariff_t1",
122 state_class=SensorStateClass.TOTAL_INCREASING,
123 device_class=SensorDeviceClass.ENERGY,
128 translation_key=
"negative_active_energy_tariff_t2",
129 state_class=SensorStateClass.TOTAL_INCREASING,
130 device_class=SensorDeviceClass.ENERGY,
136 key=
"1-0:14.7.0*255",
137 translation_key=
"supply_frequency",
143 key=
"1-0:15.7.0*255",
144 translation_key=
"absolute_active_instantaneous_power",
145 state_class=SensorStateClass.MEASUREMENT,
146 device_class=SensorDeviceClass.POWER,
152 key=
"1-0:16.7.0*255",
153 translation_key=
"sum_active_instantaneous_power",
154 state_class=SensorStateClass.MEASUREMENT,
155 device_class=SensorDeviceClass.POWER,
161 key=
"1-0:31.7.0*255",
162 translation_key=
"l1_active_instantaneous_amperage",
163 state_class=SensorStateClass.MEASUREMENT,
164 device_class=SensorDeviceClass.CURRENT,
170 key=
"1-0:32.7.0*255",
171 translation_key=
"l1_active_instantaneous_voltage",
172 state_class=SensorStateClass.MEASUREMENT,
173 device_class=SensorDeviceClass.VOLTAGE,
179 key=
"1-0:36.7.0*255",
180 translation_key=
"l1_active_instantaneous_power",
181 state_class=SensorStateClass.MEASUREMENT,
182 device_class=SensorDeviceClass.POWER,
188 key=
"1-0:51.7.0*255",
189 translation_key=
"l2_active_instantaneous_amperage",
190 state_class=SensorStateClass.MEASUREMENT,
191 device_class=SensorDeviceClass.CURRENT,
197 key=
"1-0:52.7.0*255",
198 translation_key=
"l2_active_instantaneous_voltage",
199 state_class=SensorStateClass.MEASUREMENT,
200 device_class=SensorDeviceClass.VOLTAGE,
206 key=
"1-0:56.7.0*255",
207 translation_key=
"l2_active_instantaneous_power",
208 state_class=SensorStateClass.MEASUREMENT,
209 device_class=SensorDeviceClass.POWER,
215 key=
"1-0:71.7.0*255",
216 translation_key=
"l3_active_instantaneous_amperage",
217 state_class=SensorStateClass.MEASUREMENT,
218 device_class=SensorDeviceClass.CURRENT,
224 key=
"1-0:72.7.0*255",
225 translation_key=
"l3_active_instantaneous_voltage",
226 state_class=SensorStateClass.MEASUREMENT,
227 device_class=SensorDeviceClass.VOLTAGE,
233 key=
"1-0:76.7.0*255",
234 translation_key=
"l3_active_instantaneous_power",
235 state_class=SensorStateClass.MEASUREMENT,
236 device_class=SensorDeviceClass.POWER,
246 key=
"1-0:81.7.1*255",
247 translation_key=
"u_l2_u_l1_phase_angle",
250 key=
"1-0:81.7.2*255",
251 translation_key=
"u_l3_u_l1_phase_angle",
254 key=
"1-0:81.7.4*255",
255 translation_key=
"u_l1_i_l1_phase_angle",
258 key=
"1-0:81.7.15*255",
259 translation_key=
"u_l2_i_l2_phase_angle",
262 key=
"1-0:81.7.26*255",
263 translation_key=
"u_l3_i_l3_phase_angle",
267 key=
"1-0:96.1.0*255",
268 translation_key=
"metering_point_id_1",
271 key=
"1-0:96.5.0*255",
272 translation_key=
"internal_operating_status",
276 SENSORS = {desc.key: desc
for desc
in SENSOR_TYPES}
278 SENSOR_UNIT_MAPPING = {
279 "Wh": UnitOfEnergy.WATT_HOUR,
280 "kWh": UnitOfEnergy.KILO_WATT_HOUR,
281 "W": UnitOfPower.WATT,
282 "A": UnitOfElectricCurrent.AMPERE,
283 "V": UnitOfElectricPotential.VOLT,
285 "Hz": UnitOfFrequency.HERTZ,
291 config_entry: ConfigEntry,
292 async_add_entities: AddEntitiesCallback,
294 """Set up the EDL21 sensor."""
295 hass.data[DOMAIN] =
EDL21(hass, config_entry.data, async_add_entities)
296 await hass.data[DOMAIN].connect()
300 """EDL21 handles telegrams sent by a compatible smart meter."""
312 "129-129:199.130.3*255",
313 "129-129:199.130.5*255",
319 config: Mapping[str, Any],
320 async_add_entities: AddEntitiesCallback,
322 """Initialize an EDL21 object."""
323 self._registered_obis: set[tuple[str, str]] = set()
327 self.
_proto_proto = SmlProtocol(config[CONF_SERIAL_PORT])
328 self.
_proto_proto.add_listener(self.
eventevent, [
"SmlGetListResponse"])
330 "Initialized EDL21 on %s",
331 config[CONF_SERIAL_PORT],
335 """Connect to an EDL21 reader."""
338 def event(self, message_body) -> None:
339 """Handle events from pysml."""
340 assert isinstance(message_body, SmlGetListResponse)
341 LOGGER.debug(
"Received sml message on %s: %s", self.
_serial_port_serial_port, message_body)
343 electricity_id = message_body[
"serverId"]
345 if electricity_id
is None:
347 "No electricity id found in sml message on %s", self.
_serial_port_serial_port
350 electricity_id = electricity_id.replace(
" ",
"")
352 new_entities: list[EDL21Entity] = []
353 for telegram
in message_body.get(
"valList", []):
354 if not (obis := telegram.get(
"objName")):
357 if (electricity_id, obis)
in self._registered_obis:
359 self.
_hass_hass, SIGNAL_EDL21_TELEGRAM, electricity_id, telegram
362 entity_description = SENSORS.get(obis)
363 if entity_description:
372 self._registered_obis.
add((electricity_id, obis))
375 "Unhandled sensor %s detected. Please report at %s",
377 "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue+label%3A%22integration%3A+edl21%22",
386 """Entity reading values from EDL21 telegram."""
388 _attr_should_poll =
False
389 _attr_has_entity_name =
True
391 def __init__(self, electricity_id, obis, entity_description, telegram):
392 """Initialize an EDL21Entity."""
403 name=DEFAULT_DEVICE_NAME,
407 """Run when entity about to be added to hass."""
410 def handle_telegram(electricity_id, telegram):
411 """Update attributes from last received telegram for this object."""
414 if self.
_obis_obis != telegram.get(
"objName"):
428 self.
hasshass, SIGNAL_EDL21_TELEGRAM, handle_telegram
432 """Run when entity will be removed from hass."""
438 """Return the value of the last received telegram."""
443 """Return the unit of measurement."""
444 if (unit := self.
_telegram_telegram.
get(
"unit"))
is None or unit == 0:
447 return SENSOR_UNIT_MAPPING[unit]
str|None native_unit_of_measurement(self)
None async_added_to_hass(self)
def __init__(self, electricity_id, obis, entity_description, telegram)
None async_will_remove_from_hass(self)
None event(self, message_body)
None __init__(self, HomeAssistant hass, Mapping[str, Any] config, AddEntitiesCallback async_add_entities)
dictionary _OBIS_BLACKLIST
None async_write_ha_state(self)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)