Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Sensor to collect the reference daily prices of electricity ('PVPC') in Spain."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from datetime import datetime
7 import logging
8 from typing import Any
9 
10 from aiopvpc.const import KEY_INJECTION, KEY_MAG, KEY_OMIE, KEY_PVPC
11 
13  SensorEntity,
14  SensorEntityDescription,
15  SensorStateClass,
16 )
17 from homeassistant.config_entries import ConfigEntry
18 from homeassistant.const import CURRENCY_EURO, UnitOfEnergy
19 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.event import async_track_time_change
23 from homeassistant.helpers.typing import StateType
24 from homeassistant.helpers.update_coordinator import CoordinatorEntity
25 
26 from .const import DOMAIN
27 from .coordinator import ElecPricesDataUpdateCoordinator
28 from .helpers import make_sensor_unique_id
29 
30 _LOGGER = logging.getLogger(__name__)
31 PARALLEL_UPDATES = 1
32 SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
34  key=KEY_PVPC,
35  icon="mdi:currency-eur",
36  native_unit_of_measurement=f"{CURRENCY_EURO}/{UnitOfEnergy.KILO_WATT_HOUR}",
37  state_class=SensorStateClass.MEASUREMENT,
38  suggested_display_precision=5,
39  name="PVPC",
40  ),
42  key=KEY_INJECTION,
43  icon="mdi:transmission-tower-export",
44  native_unit_of_measurement=f"{CURRENCY_EURO}/{UnitOfEnergy.KILO_WATT_HOUR}",
45  state_class=SensorStateClass.MEASUREMENT,
46  suggested_display_precision=5,
47  name="Injection Price",
48  ),
50  key=KEY_MAG,
51  icon="mdi:bank-transfer",
52  native_unit_of_measurement=f"{CURRENCY_EURO}/{UnitOfEnergy.KILO_WATT_HOUR}",
53  state_class=SensorStateClass.MEASUREMENT,
54  suggested_display_precision=5,
55  name="MAG tax",
56  entity_registry_enabled_default=False,
57  ),
59  key=KEY_OMIE,
60  icon="mdi:shopping",
61  native_unit_of_measurement=f"{CURRENCY_EURO}/{UnitOfEnergy.KILO_WATT_HOUR}",
62  state_class=SensorStateClass.MEASUREMENT,
63  suggested_display_precision=5,
64  name="OMIE Price",
65  entity_registry_enabled_default=False,
66  ),
67 )
68 _PRICE_SENSOR_ATTRIBUTES_MAP = {
69  "data_id": "data_id",
70  "name": "data_name",
71  "tariff": "tariff",
72  "period": "period",
73  "available_power": "available_power",
74  "next_period": "next_period",
75  "hours_to_next_period": "hours_to_next_period",
76  "next_better_price": "next_better_price",
77  "hours_to_better_price": "hours_to_better_price",
78  "num_better_prices_ahead": "num_better_prices_ahead",
79  "price_position": "price_position",
80  "price_ratio": "price_ratio",
81  "max_price": "max_price",
82  "max_price_at": "max_price_at",
83  "min_price": "min_price",
84  "min_price_at": "min_price_at",
85  "next_best_at": "next_best_at",
86  "price_00h": "price_00h",
87  "price_01h": "price_01h",
88  "price_02h": "price_02h",
89  "price_02h_d": "price_02h_d", # only on DST day change with 25h
90  "price_03h": "price_03h",
91  "price_04h": "price_04h",
92  "price_05h": "price_05h",
93  "price_06h": "price_06h",
94  "price_07h": "price_07h",
95  "price_08h": "price_08h",
96  "price_09h": "price_09h",
97  "price_10h": "price_10h",
98  "price_11h": "price_11h",
99  "price_12h": "price_12h",
100  "price_13h": "price_13h",
101  "price_14h": "price_14h",
102  "price_15h": "price_15h",
103  "price_16h": "price_16h",
104  "price_17h": "price_17h",
105  "price_18h": "price_18h",
106  "price_19h": "price_19h",
107  "price_20h": "price_20h",
108  "price_21h": "price_21h",
109  "price_22h": "price_22h",
110  "price_23h": "price_23h",
111  # only seen in the evening
112  "next_better_price (next day)": "next_better_price (next day)",
113  "hours_to_better_price (next day)": "hours_to_better_price (next day)",
114  "num_better_prices_ahead (next day)": "num_better_prices_ahead (next day)",
115  "price_position (next day)": "price_position (next day)",
116  "price_ratio (next day)": "price_ratio (next day)",
117  "max_price (next day)": "max_price (next day)",
118  "max_price_at (next day)": "max_price_at (next day)",
119  "min_price (next day)": "min_price (next day)",
120  "min_price_at (next day)": "min_price_at (next day)",
121  "next_best_at (next day)": "next_best_at (next day)",
122  "price_next_day_00h": "price_next_day_00h",
123  "price_next_day_01h": "price_next_day_01h",
124  "price_next_day_02h": "price_next_day_02h",
125  "price_next_day_02h_d": "price_next_day_02h_d",
126  "price_next_day_03h": "price_next_day_03h",
127  "price_next_day_04h": "price_next_day_04h",
128  "price_next_day_05h": "price_next_day_05h",
129  "price_next_day_06h": "price_next_day_06h",
130  "price_next_day_07h": "price_next_day_07h",
131  "price_next_day_08h": "price_next_day_08h",
132  "price_next_day_09h": "price_next_day_09h",
133  "price_next_day_10h": "price_next_day_10h",
134  "price_next_day_11h": "price_next_day_11h",
135  "price_next_day_12h": "price_next_day_12h",
136  "price_next_day_13h": "price_next_day_13h",
137  "price_next_day_14h": "price_next_day_14h",
138  "price_next_day_15h": "price_next_day_15h",
139  "price_next_day_16h": "price_next_day_16h",
140  "price_next_day_17h": "price_next_day_17h",
141  "price_next_day_18h": "price_next_day_18h",
142  "price_next_day_19h": "price_next_day_19h",
143  "price_next_day_20h": "price_next_day_20h",
144  "price_next_day_21h": "price_next_day_21h",
145  "price_next_day_22h": "price_next_day_22h",
146  "price_next_day_23h": "price_next_day_23h",
147 }
148 
149 
151  hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
152 ) -> None:
153  """Set up the electricity price sensor from config_entry."""
154  coordinator: ElecPricesDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
155  sensors = [ElecPriceSensor(coordinator, SENSOR_TYPES[0], entry.unique_id)]
156  if coordinator.api.using_private_api:
157  sensors.extend(
158  ElecPriceSensor(coordinator, sensor_desc, entry.unique_id)
159  for sensor_desc in SENSOR_TYPES[1:]
160  )
161  async_add_entities(sensors)
162 
163 
164 class ElecPriceSensor(CoordinatorEntity[ElecPricesDataUpdateCoordinator], SensorEntity):
165  """Class to hold the prices of electricity as a sensor."""
166 
167  _attr_has_entity_name = True
168 
169  def __init__(
170  self,
171  coordinator: ElecPricesDataUpdateCoordinator,
172  description: SensorEntityDescription,
173  unique_id: str | None,
174  ) -> None:
175  """Initialize ESIOS sensor."""
176  super().__init__(coordinator)
177  self.entity_descriptionentity_description = description
178  self._attr_attribution_attr_attribution = coordinator.api.attribution
179  self._attr_unique_id_attr_unique_id = make_sensor_unique_id(unique_id, description.key)
180  self._attr_device_info_attr_device_info = DeviceInfo(
181  configuration_url="https://api.esios.ree.es",
182  entry_type=DeviceEntryType.SERVICE,
183  identifiers={(DOMAIN, coordinator.entry_id)},
184  manufacturer="REE",
185  name="ESIOS",
186  )
187 
188  @property
189  def available(self) -> bool:
190  """Return if entity is available."""
191  return self.coordinator.data.availability.get(
192  self.entity_descriptionentity_description.key, False
193  )
194 
195  async def async_added_to_hass(self) -> None:
196  """Handle entity which will be added."""
197  await super().async_added_to_hass()
198  # Enable API downloads for this sensor
199  self.coordinator.api.update_active_sensors(self.entity_descriptionentity_description.key, True)
200  self.async_on_removeasync_on_remove(
201  lambda: self.coordinator.api.update_active_sensors(
202  self.entity_descriptionentity_description.key, False
203  )
204  )
205 
206  # Update 'state' value in hour changes
207  self.async_on_removeasync_on_remove(
209  self.hasshasshass, self.update_current_priceupdate_current_price, second=[0], minute=[0]
210  )
211  )
212  _LOGGER.debug(
213  "Setup of ESIOS sensor %s (%s, unique_id: %s)",
214  self.entity_descriptionentity_description.key,
215  self.entity_identity_id,
216  self._attr_unique_id_attr_unique_id,
217  )
218 
219  @callback
220  def update_current_price(self, now: datetime) -> None:
221  """Update the sensor state, by selecting the current price for this hour."""
222  self.coordinator.api.process_state_and_attributes(
223  self.coordinator.data, self.entity_descriptionentity_description.key, now
224  )
225  self.async_write_ha_stateasync_write_ha_state()
226 
227  @property
228  def native_value(self) -> StateType:
229  """Return the state of the sensor."""
230  return self.coordinator.api.states.get(self.entity_descriptionentity_description.key)
231 
232  @property
233  def extra_state_attributes(self) -> Mapping[str, Any]:
234  """Return the state attributes."""
235  sensor_attributes = self.coordinator.api.sensor_attributes.get(
236  self.entity_descriptionentity_description.key, {}
237  )
238  return {
239  _PRICE_SENSOR_ATTRIBUTES_MAP[key]: value
240  for key, value in sensor_attributes.items()
241  if key in _PRICE_SENSOR_ATTRIBUTES_MAP
242  }
None __init__(self, ElecPricesDataUpdateCoordinator coordinator, SensorEntityDescription description, str|None unique_id)
Definition: sensor.py:174
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
str make_sensor_unique_id(str|None config_entry_id, str sensor_key)
Definition: helpers.py:43
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:152
CALLBACK_TYPE async_track_time_change(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, Any|None hour=None, Any|None minute=None, Any|None second=None)
Definition: event.py:1904