Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ omit =
homeassistant/components/ecobee/weather.py
homeassistant/components/econet/*
homeassistant/components/ecovacs/*
homeassistant/components/edl21/*
homeassistant/components/eddystone_temperature/sensor.py
homeassistant/components/edimax/switch.py
homeassistant/components/egardia/*
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ homeassistant/components/doorbird/* @oblogic7
homeassistant/components/dweet/* @fabaff
homeassistant/components/ecobee/* @marthoc
homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edl21/* @mtdcr
homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64
homeassistant/components/elv/* @majuss
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/edl21/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""The edl21 component."""
12 changes: 12 additions & 0 deletions homeassistant/components/edl21/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"domain": "edl21",
"name": "EDL21",
"documentation": "https://www.home-assistant.io/integrations/edl21",
"requirements": [
"pysml==0.0.2"
],
"dependencies": [],
"codeowners": [
"@mtdcr"
]
}
152 changes: 152 additions & 0 deletions homeassistant/components/edl21/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""Support for EDL21 Smart Meters."""

from datetime import timedelta
import logging

from sml import SmlGetListResponse
from sml.asyncio import SmlProtocol
import voluptuous as vol

from homeassistant.components.sensor import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import Optional
from homeassistant.util import Throttle

_LOGGER = logging.getLogger(__name__)

DOMAIN = "edl21"
CONF_SERIAL_PORT = "serial_port"
ICON_POWER = "mdi:flash"
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_SERIAL_PORT): cv.string})


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the EDL21 sensor."""
hass.data[DOMAIN] = EDL21(hass, config, async_add_entities)
await hass.data[DOMAIN].connect()


class EDL21:
"""EDL21 handles telegrams sent by a compatible smart meter."""

def __init__(self, hass, config, async_add_entities) -> None:
"""Initialize an EDL21 object."""
self._cache = {}
self._hass = hass
self._async_add_entities = async_add_entities
self._proto = SmlProtocol(config[CONF_SERIAL_PORT])
self._proto.add_listener(self.event, ["SmlGetListResponse"])

async def connect(self):
"""Connect to an EDL21 reader."""
await self._proto.connect(self._hass.loop)

def event(self, message_body) -> None:
"""Handle events from pysml."""
assert isinstance(message_body, SmlGetListResponse)

new_devices = []
Comment thread
mtdcr marked this conversation as resolved.
Outdated
for telegram in message_body.get("valList", []):
obis = telegram.get("objName")
if not obis:
continue

device = self._cache.get(obis)
Comment thread
mtdcr marked this conversation as resolved.
Outdated
if not device:
device = self._cache[obis] = EDL21Entity(obis, telegram)
new_devices.append(device)
elif device.update_telegram(telegram):
self._hass.async_create_task(device.async_update_ha_state())
Comment thread
mtdcr marked this conversation as resolved.
Outdated

if new_devices:
self._hass.async_add_job(
Comment thread
mtdcr marked this conversation as resolved.
Outdated
self._async_add_entities(new_devices, update_before_add=True)
)


class EDL21Entity(Entity):
"""Entity reading values from EDL21 telegram."""

# OBIS format: A-B:C.D.E*F
_OBIS_NAMES = {
Comment thread
mtdcr marked this conversation as resolved.
# A=1: Electricity
# C=0: General purpose objects
"1-0:0.0.9*255": "Electricity ID",
# C=1: Active power +
# D=8: Time integral 1
# E=0: Total
"1-0:1.8.0*255": "Positive active energy total",
# E=1: Rate 1
"1-0:1.8.1*255": "Positive active energy in tariff T1",
# E=2: Rate 2
"1-0:1.8.2*255": "Positive active energy in tariff T2",
# D=17: Time integral 7
# E=0: Total
"1-0:1.17.0*255": "Last signed positive active energy total",
# C=15: Active power absolute
# D=7: Instantaneous value
# E=0: Total
"1-0:15.7.0*255": "Absolute active instantaneous power",
# C=16: Active power sum
# D=7: Instantaneous value
# E=0: Total
"1-0:16.7.0*255": "Sum active instantaneous power",
# A=129: Manufacturer specific
"129-129:199.130.3*255": "Manufacturer",
"129-129:199.130.5*255": "Public Key",
}

def __init__(self, obis, telegram):
"""Initialize an EDL21Entity."""
self._obis = obis
self._telegram = telegram

@property
def should_poll(self) -> bool:
"""Do not poll."""
return False

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._obis

@property
def name(self) -> Optional[str]:
"""Return a name."""
return self._OBIS_NAMES.get(self._obis)

@property
def state(self) -> str:
"""Return the value of the last received telegram."""
return self._telegram.get("value")

@property
def device_state_attributes(self):
"""Enumerate supported attributes."""
attr = self._telegram.copy()
for key in ("objName", "unit", "value"):
if key in attr:
del attr[key]
return attr
Comment thread
mtdcr marked this conversation as resolved.
Outdated

@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._telegram.get("unit")

@property
def icon(self):
"""Return an icon."""
return ICON_POWER

@Throttle(MIN_TIME_BETWEEN_UPDATES)
Comment thread
balloob marked this conversation as resolved.
Outdated
def update_telegram(self, telegram: dict) -> bool:
"""Update attributes from last received telegram for this object."""
if self._telegram == telegram:
return False
self._telegram = telegram
return True
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,9 @@ pysmartthings==0.6.9
# homeassistant.components.smarty
pysmarty==0.8

# homeassistant.components.edl21
pysml==0.0.2

# homeassistant.components.snmp
pysnmp==4.4.12

Expand Down