-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Add edl21 component for SML-based smart meters #27962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 7 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
7236e08
Add edl21 component for SML-based smart meters
mtdcr d91b687
edl21: Remove unused variable
mtdcr 5a13763
[edl21] Add 1 minute throttle to the sensor
DavidMStraub d117a17
Merge pull request #1 from DavidMStraub/edl21_throttle
mtdcr 035fbd0
Update homeassistant/components/edl21/manifest.json
mtdcr b7e219f
edl21: Move imports to top
mtdcr 5202313
edl21: Remove special case for STATE_UNKNOWN, which replicated defaul…
mtdcr 1561b9e
edl21: Implement blacklist for and warn about unhandled OBIS values
mtdcr ce7976a
edl21: Add filter to issues URL
mtdcr fe24c73
edl21: Make blacklist global
mtdcr 77e3642
edl21: Rename device to entity
mtdcr 9f9786d
edl21: Don't schedule async_add_entities
mtdcr 1309988
edl21: Use dispatcher, implement own throttling mechanism
mtdcr 152b485
edl21: Simplify keeping track of known obis
mtdcr 2dbbb4f
edl21: Use whitelist for state attributes
mtdcr 4840a27
edl21: Remove dispatcher on shutdown
mtdcr 45a0022
edl21: Convert state attributes to snakecase
mtdcr 4221c3f
edl21: Annotate handle_telegram with @callback
mtdcr feb2a67
edl21: Call async_write_ha_state instead of schedule_update_ha_state
mtdcr File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """The edl21 component.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 = [] | ||
| for telegram in message_body.get("valList", []): | ||
| obis = telegram.get("objName") | ||
| if not obis: | ||
| continue | ||
|
|
||
| device = self._cache.get(obis) | ||
|
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()) | ||
|
mtdcr marked this conversation as resolved.
Outdated
|
||
|
|
||
| if new_devices: | ||
| self._hass.async_add_job( | ||
|
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 = { | ||
|
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 | ||
|
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) | ||
|
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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.