Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Linky Atome."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 import logging
7 
8 from pyatome.client import AtomeClient, PyAtomeError
9 import voluptuous as vol
10 
12  PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
13  SensorDeviceClass,
14  SensorEntity,
15  SensorStateClass,
16 )
17 from homeassistant.const import (
18  CONF_NAME,
19  CONF_PASSWORD,
20  CONF_USERNAME,
21  UnitOfEnergy,
22  UnitOfPower,
23 )
24 from homeassistant.core import HomeAssistant
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
28 from homeassistant.util import Throttle
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 DEFAULT_NAME = "atome"
33 
34 LIVE_SCAN_INTERVAL = timedelta(seconds=30)
35 DAILY_SCAN_INTERVAL = timedelta(seconds=150)
36 WEEKLY_SCAN_INTERVAL = timedelta(hours=1)
37 MONTHLY_SCAN_INTERVAL = timedelta(hours=1)
38 YEARLY_SCAN_INTERVAL = timedelta(days=1)
39 
40 LIVE_NAME = "Atome Live Power"
41 DAILY_NAME = "Atome Daily"
42 WEEKLY_NAME = "Atome Weekly"
43 MONTHLY_NAME = "Atome Monthly"
44 YEARLY_NAME = "Atome Yearly"
45 
46 LIVE_TYPE = "live"
47 DAILY_TYPE = "day"
48 WEEKLY_TYPE = "week"
49 MONTHLY_TYPE = "month"
50 YEARLY_TYPE = "year"
51 
52 PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
53  {
54  vol.Required(CONF_USERNAME): cv.string,
55  vol.Required(CONF_PASSWORD): cv.string,
56  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
57  }
58 )
59 
60 
62  hass: HomeAssistant,
63  config: ConfigType,
64  add_entities: AddEntitiesCallback,
65  discovery_info: DiscoveryInfoType | None = None,
66 ) -> None:
67  """Set up the Atome sensor."""
68  username = config[CONF_USERNAME]
69  password = config[CONF_PASSWORD]
70 
71  try:
72  atome_client = AtomeClient(username, password)
73  atome_client.login()
74  except PyAtomeError as exp:
75  _LOGGER.error(exp)
76  return
77 
78  data = AtomeData(atome_client)
79 
80  sensors = []
81  sensors.append(AtomeSensor(data, LIVE_NAME, LIVE_TYPE))
82  sensors.append(AtomeSensor(data, DAILY_NAME, DAILY_TYPE))
83  sensors.append(AtomeSensor(data, WEEKLY_NAME, WEEKLY_TYPE))
84  sensors.append(AtomeSensor(data, MONTHLY_NAME, MONTHLY_TYPE))
85  sensors.append(AtomeSensor(data, YEARLY_NAME, YEARLY_TYPE))
86 
87  add_entities(sensors, True)
88 
89 
90 class AtomeData:
91  """Stores data retrieved from Neurio sensor."""
92 
93  def __init__(self, client: AtomeClient) -> None:
94  """Initialize the data."""
95  self.atome_clientatome_client = client
96  self._live_power_live_power = None
97  self._subscribed_power_subscribed_power = None
98  self._is_connected_is_connected = None
99  self._day_usage_day_usage = None
100  self._day_price_day_price = None
101  self._week_usage_week_usage = None
102  self._week_price_week_price = None
103  self._month_usage_month_usage = None
104  self._month_price_month_price = None
105  self._year_usage_year_usage = None
106  self._year_price_year_price = None
107 
108  @property
109  def live_power(self):
110  """Return latest active power value."""
111  return self._live_power_live_power
112 
113  @property
114  def subscribed_power(self):
115  """Return latest active power value."""
116  return self._subscribed_power_subscribed_power
117 
118  @property
119  def is_connected(self):
120  """Return latest active power value."""
121  return self._is_connected_is_connected
122 
123  def _retrieve_live(self):
124  values = self.atome_clientatome_client.get_live()
125  if (
126  values.get("last")
127  and values.get("subscribed")
128  and (values.get("isConnected") is not None)
129  ):
130  self._live_power_live_power = values["last"]
131  self._subscribed_power_subscribed_power = values["subscribed"]
132  self._is_connected_is_connected = values["isConnected"]
133  _LOGGER.debug(
134  "Updating Atome live data. Got: %d, isConnected: %s, subscribed: %d",
135  self._live_power_live_power,
136  self._is_connected_is_connected,
137  self._subscribed_power_subscribed_power,
138  )
139  return True
140 
141  _LOGGER.error("Live Data : Missing last value in values: %s", values)
142  return False
143 
144  @Throttle(LIVE_SCAN_INTERVAL)
145  def update_live_usage(self):
146  """Return current power value."""
147  if not self._retrieve_live_retrieve_live():
148  _LOGGER.debug("Perform Reconnect during live request")
149  self.atome_clientatome_client.login()
150  self._retrieve_live_retrieve_live()
151 
152  def _retrieve_period_usage(self, period_type):
153  """Return current daily/weekly/monthly/yearly power usage."""
154  values = self.atome_clientatome_client.get_consumption(period_type)
155  if values.get("total") and values.get("price"):
156  period_usage = values["total"] / 1000
157  period_price = values["price"]
158  _LOGGER.debug("Updating Atome %s data. Got: %d", period_type, period_usage)
159  return True, period_usage, period_price
160 
161  _LOGGER.error("%s : Missing last value in values: %s", period_type, values)
162  return False, None, None
163 
164  def _retrieve_period_usage_with_retry(self, period_type):
165  """Return current daily/weekly/monthly/yearly power usage with one retry."""
166  (
167  retrieve_success,
168  period_usage,
169  period_price,
170  ) = self._retrieve_period_usage_retrieve_period_usage(period_type)
171  if not retrieve_success:
172  _LOGGER.debug("Perform Reconnect during %s", period_type)
173  self.atome_clientatome_client.login()
174  (
175  retrieve_success,
176  period_usage,
177  period_price,
178  ) = self._retrieve_period_usage_retrieve_period_usage(period_type)
179  return (period_usage, period_price)
180 
181  @property
182  def day_usage(self):
183  """Return latest daily usage value."""
184  return self._day_usage_day_usage
185 
186  @property
187  def day_price(self):
188  """Return latest daily usage value."""
189  return self._day_price_day_price
190 
191  @Throttle(DAILY_SCAN_INTERVAL)
192  def update_day_usage(self):
193  """Return current daily power usage."""
194  (
195  self._day_usage_day_usage,
196  self._day_price_day_price,
197  ) = self._retrieve_period_usage_with_retry_retrieve_period_usage_with_retry(DAILY_TYPE)
198 
199  @property
200  def week_usage(self):
201  """Return latest weekly usage value."""
202  return self._week_usage_week_usage
203 
204  @property
205  def week_price(self):
206  """Return latest weekly usage value."""
207  return self._week_price_week_price
208 
209  @Throttle(WEEKLY_SCAN_INTERVAL)
210  def update_week_usage(self):
211  """Return current weekly power usage."""
212  (
213  self._week_usage_week_usage,
214  self._week_price_week_price,
215  ) = self._retrieve_period_usage_with_retry_retrieve_period_usage_with_retry(WEEKLY_TYPE)
216 
217  @property
218  def month_usage(self):
219  """Return latest monthly usage value."""
220  return self._month_usage_month_usage
221 
222  @property
223  def month_price(self):
224  """Return latest monthly usage value."""
225  return self._month_price_month_price
226 
227  @Throttle(MONTHLY_SCAN_INTERVAL)
229  """Return current monthly power usage."""
230  (
231  self._month_usage_month_usage,
232  self._month_price_month_price,
233  ) = self._retrieve_period_usage_with_retry_retrieve_period_usage_with_retry(MONTHLY_TYPE)
234 
235  @property
236  def year_usage(self):
237  """Return latest yearly usage value."""
238  return self._year_usage_year_usage
239 
240  @property
241  def year_price(self):
242  """Return latest yearly usage value."""
243  return self._year_price_year_price
244 
245  @Throttle(YEARLY_SCAN_INTERVAL)
246  def update_year_usage(self):
247  """Return current yearly power usage."""
248  (
249  self._year_usage_year_usage,
250  self._year_price_year_price,
251  ) = self._retrieve_period_usage_with_retry_retrieve_period_usage_with_retry(YEARLY_TYPE)
252 
253 
255  """Representation of a sensor entity for Atome."""
256 
257  def __init__(self, data, name, sensor_type):
258  """Initialize the sensor."""
259  self._attr_name_attr_name = name
260  self._data_data = data
261 
262  self._sensor_type_sensor_type = sensor_type
263 
264  if sensor_type == LIVE_TYPE:
265  self._attr_device_class_attr_device_class = SensorDeviceClass.POWER
266  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfPower.WATT
267  self._attr_state_class_attr_state_class = SensorStateClass.MEASUREMENT
268  else:
269  self._attr_device_class_attr_device_class = SensorDeviceClass.ENERGY
270  self._attr_native_unit_of_measurement_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
271  self._attr_state_class_attr_state_class = SensorStateClass.TOTAL_INCREASING
272 
273  def update(self) -> None:
274  """Update device state."""
275  update_function = getattr(self._data_data, f"update_{self._sensor_type}_usage")
276  update_function()
277 
278  if self._sensor_type_sensor_type == LIVE_TYPE:
279  self._attr_native_value_attr_native_value = self._data_data.live_power
280  self._attr_extra_state_attributes_attr_extra_state_attributes = {
281  "subscribed_power": self._data_data.subscribed_power,
282  "is_connected": self._data_data.is_connected,
283  }
284  else:
285  self._attr_native_value_attr_native_value = getattr(self._data_data, f"{self._sensor_type}_usage")
286  self._attr_extra_state_attributes_attr_extra_state_attributes = {
287  "price": getattr(self._data_data, f"{self._sensor_type}_price")
288  }
None __init__(self, AtomeClient client)
Definition: sensor.py:93
def _retrieve_period_usage(self, period_type)
Definition: sensor.py:152
def _retrieve_period_usage_with_retry(self, period_type)
Definition: sensor.py:164
def __init__(self, data, name, sensor_type)
Definition: sensor.py:257
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
Definition: sensor.py:66
def add_entities(account, async_add_entities, tracked)
Definition: sensor.py:40