-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Add atome sensor platform #26197
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
+293
−0
Merged
Add atome sensor platform #26197
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
c875b90
Atome sensor platform - provides live data from Linky energy meters w…
BaQs a79882e
Proper requirements, added code ownership
BaQs 17643dd
Do not cover atome component
BaQs 874a8ff
Proper PEP8 import, proper use of const, added missing docstring etc
BaQs 1564478
Proper PEP8 import, proper use of const, added missing docstring etc
BaQs f43aef7
Merge branches 'platform/atome' and 'platform/atome' of https://githu…
BaQs feb3676
Integrate recommendations from MartinHjelmare
BaQs bcab30f
Init shall remain as clean as possible, we don't want side effect
BaQs c8b6838
Add daily,weekly,monthly,yearly sensors. Now depends on pyatome 0.1
BaQs 9d578cd
Requirements regenerated for atome component
BaQs 543523f
Refactored the way we update sensors
BaQs bd744b5
Removed some un-necessary returns and unused variable
BaQs 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 @@ | ||
| """Support for Atome devices connected to a Linky Energy Meter.""" |
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,8 @@ | ||
| { | ||
| "domain": "atome", | ||
| "name": "Atome", | ||
| "documentation": "https://www.home-assistant.io/components/atome", | ||
| "dependencies": [], | ||
| "codeowners": ["@baqs"], | ||
| "requirements": ["pyatome==0.1.1"] | ||
| } |
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,279 @@ | ||
| """Linky Atome.""" | ||
| import logging | ||
| from datetime import timedelta | ||
|
|
||
| import voluptuous as vol | ||
| from pyatome.client import AtomeClient | ||
| from pyatome.client import PyAtomeError | ||
|
|
||
|
BaQs marked this conversation as resolved.
|
||
| from homeassistant.const import ( | ||
| CONF_PASSWORD, | ||
| CONF_USERNAME, | ||
| CONF_NAME, | ||
| DEVICE_CLASS_POWER, | ||
| POWER_WATT, | ||
| ENERGY_KILO_WATT_HOUR, | ||
| ) | ||
| from homeassistant.components.sensor import PLATFORM_SCHEMA | ||
| from homeassistant.helpers.entity import Entity | ||
| from homeassistant.util import Throttle | ||
| import homeassistant.helpers.config_validation as cv | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| DEFAULT_NAME = "atome" | ||
|
|
||
| LIVE_SCAN_INTERVAL = timedelta(seconds=30) | ||
| DAILY_SCAN_INTERVAL = timedelta(seconds=150) | ||
| WEEKLY_SCAN_INTERVAL = timedelta(hours=1) | ||
| MONTHLY_SCAN_INTERVAL = timedelta(hours=1) | ||
| YEARLY_SCAN_INTERVAL = timedelta(days=1) | ||
|
|
||
| LIVE_NAME = "Atome Live Power" | ||
| DAILY_NAME = "Atome Daily" | ||
| WEEKLY_NAME = "Atome Weekly" | ||
| MONTHLY_NAME = "Atome Monthly" | ||
| YEARLY_NAME = "Atome Yearly" | ||
|
|
||
| LIVE_TYPE = "live" | ||
| DAILY_TYPE = "day" | ||
| WEEKLY_TYPE = "week" | ||
| MONTHLY_TYPE = "month" | ||
| YEARLY_TYPE = "year" | ||
|
|
||
| ICON = "mdi:flash" | ||
|
|
||
| PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( | ||
| { | ||
| vol.Required(CONF_USERNAME): cv.string, | ||
| vol.Required(CONF_PASSWORD): cv.string, | ||
| vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
| } | ||
| ) | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_entities, discovery_info=None): | ||
| """Set up the Atome sensor.""" | ||
| username = config[CONF_USERNAME] | ||
| password = config[CONF_PASSWORD] | ||
|
|
||
| try: | ||
| atome_client = AtomeClient(username, password) | ||
| atome_client.login() | ||
| except PyAtomeError as exp: | ||
| _LOGGER.error(exp) | ||
| return | ||
|
|
||
| data = AtomeData(atome_client) | ||
|
|
||
| sensors = [] | ||
| sensors.append(AtomeSensor(data, LIVE_NAME, LIVE_TYPE)) | ||
| sensors.append(AtomeSensor(data, DAILY_NAME, DAILY_TYPE)) | ||
| sensors.append(AtomeSensor(data, WEEKLY_NAME, WEEKLY_TYPE)) | ||
| sensors.append(AtomeSensor(data, MONTHLY_NAME, MONTHLY_TYPE)) | ||
| sensors.append(AtomeSensor(data, YEARLY_NAME, YEARLY_TYPE)) | ||
|
|
||
| add_entities(sensors, True) | ||
|
|
||
|
|
||
| class AtomeData: | ||
| """Stores data retrieved from Neurio sensor.""" | ||
|
|
||
| def __init__(self, client: AtomeClient): | ||
| """Initialize the data.""" | ||
| self.atome_client = client | ||
| self._live_power = None | ||
| self._subscribed_power = None | ||
| self._is_connected = None | ||
| self._day_usage = None | ||
| self._day_price = None | ||
| self._week_usage = None | ||
| self._week_price = None | ||
| self._month_usage = None | ||
| self._month_price = None | ||
| self._year_usage = None | ||
| self._year_price = None | ||
|
|
||
| @property | ||
| def live_power(self): | ||
| """Return latest active power value.""" | ||
| return self._live_power | ||
|
|
||
| @property | ||
| def subscribed_power(self): | ||
| """Return latest active power value.""" | ||
| return self._subscribed_power | ||
|
|
||
| @property | ||
| def is_connected(self): | ||
| """Return latest active power value.""" | ||
| return self._is_connected | ||
|
|
||
| @Throttle(LIVE_SCAN_INTERVAL) | ||
| def update_live_usage(self): | ||
| """Return current power value.""" | ||
| try: | ||
| values = self.atome_client.get_live() | ||
| self._live_power = values["last"] | ||
| self._subscribed_power = values["subscribed"] | ||
| self._is_connected = values["isConnected"] | ||
| _LOGGER.debug( | ||
| "Updating Atome live data. Got: %d, isConnected: %s, subscribed: %d", | ||
| self._live_power, | ||
| self._is_connected, | ||
| self._subscribed_power, | ||
| ) | ||
|
|
||
| except KeyError as error: | ||
| _LOGGER.error("Missing last value in values: %s: %s", values, error) | ||
|
|
||
| @property | ||
| def day_usage(self): | ||
| """Return latest daily usage value.""" | ||
| return self._day_usage | ||
|
|
||
| @property | ||
| def day_price(self): | ||
| """Return latest daily usage value.""" | ||
| return self._day_price | ||
|
|
||
| @Throttle(DAILY_SCAN_INTERVAL) | ||
| def update_day_usage(self): | ||
| """Return current daily power usage.""" | ||
| try: | ||
| values = self.atome_client.get_consumption(DAILY_TYPE) | ||
| self._day_usage = values["total"] / 1000 | ||
| self._day_price = values["price"] | ||
| _LOGGER.debug("Updating Atome daily data. Got: %d.", self._day_usage) | ||
|
|
||
| except KeyError as error: | ||
| _LOGGER.error("Missing last value in values: %s: %s", values, error) | ||
|
|
||
| @property | ||
| def week_usage(self): | ||
| """Return latest weekly usage value.""" | ||
| return self._week_usage | ||
|
|
||
| @property | ||
| def week_price(self): | ||
| """Return latest weekly usage value.""" | ||
| return self._week_price | ||
|
|
||
| @Throttle(WEEKLY_SCAN_INTERVAL) | ||
| def update_week_usage(self): | ||
| """Return current weekly power usage.""" | ||
| try: | ||
| values = self.atome_client.get_consumption(WEEKLY_TYPE) | ||
| self._week_usage = values["total"] / 1000 | ||
| self._week_price = values["price"] | ||
| _LOGGER.debug("Updating Atome weekly data. Got: %d.", self._week_usage) | ||
|
|
||
| except KeyError as error: | ||
| _LOGGER.error("Missing last value in values: %s: %s", values, error) | ||
|
|
||
| @property | ||
| def month_usage(self): | ||
| """Return latest monthly usage value.""" | ||
| return self._month_usage | ||
|
|
||
| @property | ||
| def month_price(self): | ||
| """Return latest monthly usage value.""" | ||
| return self._month_price | ||
|
|
||
| @Throttle(MONTHLY_SCAN_INTERVAL) | ||
| def update_month_usage(self): | ||
| """Return current monthly power usage.""" | ||
| try: | ||
| values = self.atome_client.get_consumption(MONTHLY_TYPE) | ||
| self._month_usage = values["total"] / 1000 | ||
| self._month_price = values["price"] | ||
| _LOGGER.debug("Updating Atome monthly data. Got: %d.", self._month_usage) | ||
|
|
||
| except KeyError as error: | ||
| _LOGGER.error("Missing last value in values: %s: %s", values, error) | ||
|
|
||
| @property | ||
| def year_usage(self): | ||
| """Return latest yearly usage value.""" | ||
| return self._year_usage | ||
|
|
||
| @property | ||
| def year_price(self): | ||
| """Return latest yearly usage value.""" | ||
| return self._year_price | ||
|
|
||
| @Throttle(YEARLY_SCAN_INTERVAL) | ||
| def update_year_usage(self): | ||
| """Return current yearly power usage.""" | ||
| try: | ||
| values = self.atome_client.get_consumption(YEARLY_TYPE) | ||
| self._year_usage = values["total"] / 1000 | ||
| self._year_price = values["price"] | ||
| _LOGGER.debug("Updating Atome yearly data. Got: %d.", self._year_usage) | ||
|
|
||
| except KeyError as error: | ||
| _LOGGER.error("Missing last value in values: %s: %s", values, error) | ||
|
|
||
|
|
||
| class AtomeSensor(Entity): | ||
| """Representation of a sensor entity for Atome.""" | ||
|
|
||
| def __init__(self, data, name, sensor_type): | ||
| """Initialize the sensor.""" | ||
| self._name = name | ||
| self._data = data | ||
| self._state = None | ||
| self._attributes = {} | ||
|
|
||
| self._sensor_type = sensor_type | ||
|
|
||
| if sensor_type == LIVE_TYPE: | ||
| self._unit_of_measurement = POWER_WATT | ||
| else: | ||
| self._unit_of_measurement = ENERGY_KILO_WATT_HOUR | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the state of the sensor.""" | ||
| return self._state | ||
|
|
||
| @property | ||
| def device_state_attributes(self): | ||
| """Return the state attributes.""" | ||
| return self._attributes | ||
|
|
||
|
BaQs marked this conversation as resolved.
|
||
| @property | ||
| def unit_of_measurement(self): | ||
| """Return the unit of measurement.""" | ||
| return self._unit_of_measurement | ||
|
|
||
| @property | ||
| def icon(self): | ||
| """Icon to use in the frontend, if any.""" | ||
| return ICON | ||
|
|
||
|
BaQs marked this conversation as resolved.
|
||
| @property | ||
| def device_class(self): | ||
| """Return the device class.""" | ||
| return DEVICE_CLASS_POWER | ||
|
|
||
| def update(self): | ||
| """Update device state.""" | ||
| update_function = getattr(self._data, f"update_{self._sensor_type}_usage") | ||
| update_function() | ||
|
|
||
| if self._sensor_type == LIVE_TYPE: | ||
| self._state = self._data.live_power | ||
| self._attributes["subscribed_power"] = self._data.subscribed_power | ||
| self._attributes["is_connected"] = self._data.is_connected | ||
|
MartinHjelmare marked this conversation as resolved.
|
||
| else: | ||
| self._state = getattr(self._data, f"{self._sensor_type}_usage") | ||
| self._attributes["price"] = getattr( | ||
| self._data, f"{self._sensor_type}_price" | ||
| ) | ||
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.