1 """Support for Tibber sensors."""
3 from __future__
import annotations
5 from collections.abc
import Callable
7 from datetime
import timedelta
9 from random
import randrange
10 from typing
import Any
18 SensorEntityDescription,
23 EVENT_HOMEASSISTANT_STOP,
25 SIGNAL_STRENGTH_DECIBELS,
27 UnitOfElectricCurrent,
28 UnitOfElectricPotential,
40 DataUpdateCoordinator,
44 from .const
import DOMAIN
as TIBBER_DOMAIN, MANUFACTURER
45 from .coordinator
import TibberDataCoordinator
47 _LOGGER = logging.getLogger(__name__)
49 ICON =
"mdi:currency-usd"
53 TWENTY_MINUTES = 20 * 60
55 RT_SENSORS_UNIQUE_ID_MIGRATION = {
56 "accumulated_consumption_last_hour":
"accumulated consumption current hour",
57 "accumulated_production_last_hour":
"accumulated production current hour",
58 "current_l1":
"current L1",
59 "current_l2":
"current L2",
60 "current_l3":
"current L3",
61 "estimated_hour_consumption":
"Estimated consumption current hour",
64 RT_SENSORS_UNIQUE_ID_MIGRATION_SIMPLE = {
66 "accumulated_consumption",
68 "accumulated_production",
71 "last_meter_consumption",
72 "last_meter_production",
84 RT_SENSORS: tuple[SensorEntityDescription, ...] = (
87 translation_key=
"average_power",
88 device_class=SensorDeviceClass.POWER,
89 native_unit_of_measurement=UnitOfPower.WATT,
93 translation_key=
"power",
94 device_class=SensorDeviceClass.POWER,
95 state_class=SensorStateClass.MEASUREMENT,
96 native_unit_of_measurement=UnitOfPower.WATT,
99 key=
"powerProduction",
100 translation_key=
"power_production",
101 device_class=SensorDeviceClass.POWER,
102 state_class=SensorStateClass.MEASUREMENT,
103 native_unit_of_measurement=UnitOfPower.WATT,
107 translation_key=
"min_power",
108 device_class=SensorDeviceClass.POWER,
109 native_unit_of_measurement=UnitOfPower.WATT,
113 translation_key=
"max_power",
114 device_class=SensorDeviceClass.POWER,
115 native_unit_of_measurement=UnitOfPower.WATT,
118 key=
"accumulatedConsumption",
119 translation_key=
"accumulated_consumption",
120 device_class=SensorDeviceClass.ENERGY,
121 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
122 state_class=SensorStateClass.TOTAL,
125 key=
"accumulatedConsumptionLastHour",
126 translation_key=
"accumulated_consumption_last_hour",
127 device_class=SensorDeviceClass.ENERGY,
128 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
129 state_class=SensorStateClass.TOTAL_INCREASING,
132 key=
"estimatedHourConsumption",
133 translation_key=
"estimated_hour_consumption",
134 device_class=SensorDeviceClass.ENERGY,
135 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
138 key=
"accumulatedProduction",
139 translation_key=
"accumulated_production",
140 device_class=SensorDeviceClass.ENERGY,
141 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
142 state_class=SensorStateClass.TOTAL,
145 key=
"accumulatedProductionLastHour",
146 translation_key=
"accumulated_production_last_hour",
147 device_class=SensorDeviceClass.ENERGY,
148 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
149 state_class=SensorStateClass.TOTAL_INCREASING,
152 key=
"lastMeterConsumption",
153 translation_key=
"last_meter_consumption",
154 device_class=SensorDeviceClass.ENERGY,
155 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
156 state_class=SensorStateClass.TOTAL_INCREASING,
159 key=
"lastMeterProduction",
160 translation_key=
"last_meter_production",
161 device_class=SensorDeviceClass.ENERGY,
162 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
163 state_class=SensorStateClass.TOTAL_INCREASING,
167 translation_key=
"voltage_phase1",
168 device_class=SensorDeviceClass.VOLTAGE,
169 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
170 state_class=SensorStateClass.MEASUREMENT,
174 translation_key=
"voltage_phase2",
175 device_class=SensorDeviceClass.VOLTAGE,
176 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
177 state_class=SensorStateClass.MEASUREMENT,
181 translation_key=
"voltage_phase3",
182 device_class=SensorDeviceClass.VOLTAGE,
183 native_unit_of_measurement=UnitOfElectricPotential.VOLT,
184 state_class=SensorStateClass.MEASUREMENT,
188 translation_key=
"current_l1",
189 device_class=SensorDeviceClass.CURRENT,
190 native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
191 state_class=SensorStateClass.MEASUREMENT,
195 translation_key=
"current_l2",
196 device_class=SensorDeviceClass.CURRENT,
197 native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
198 state_class=SensorStateClass.MEASUREMENT,
202 translation_key=
"current_l3",
203 device_class=SensorDeviceClass.CURRENT,
204 native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
205 state_class=SensorStateClass.MEASUREMENT,
208 key=
"signalStrength",
209 translation_key=
"signal_strength",
210 device_class=SensorDeviceClass.SIGNAL_STRENGTH,
211 native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
212 state_class=SensorStateClass.MEASUREMENT,
213 entity_category=EntityCategory.DIAGNOSTIC,
216 key=
"accumulatedReward",
217 translation_key=
"accumulated_reward",
218 device_class=SensorDeviceClass.MONETARY,
219 state_class=SensorStateClass.TOTAL,
222 key=
"accumulatedCost",
223 translation_key=
"accumulated_cost",
224 device_class=SensorDeviceClass.MONETARY,
225 state_class=SensorStateClass.TOTAL,
229 translation_key=
"power_factor",
230 device_class=SensorDeviceClass.POWER_FACTOR,
231 native_unit_of_measurement=PERCENTAGE,
232 state_class=SensorStateClass.MEASUREMENT,
236 SENSORS: tuple[SensorEntityDescription, ...] = (
239 translation_key=
"month_cost",
240 device_class=SensorDeviceClass.MONETARY,
244 translation_key=
"peak_hour",
245 device_class=SensorDeviceClass.ENERGY,
246 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
249 key=
"peak_hour_time",
250 translation_key=
"peak_hour_time",
251 device_class=SensorDeviceClass.TIMESTAMP,
255 translation_key=
"month_cons",
256 device_class=SensorDeviceClass.ENERGY,
257 native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
258 state_class=SensorStateClass.TOTAL_INCREASING,
264 hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
266 """Set up the Tibber sensor."""
268 tibber_connection = hass.data[TIBBER_DOMAIN]
270 entity_registry = er.async_get(hass)
271 device_registry = dr.async_get(hass)
273 coordinator: TibberDataCoordinator |
None =
None
274 entities: list[TibberSensor] = []
275 for home
in tibber_connection.get_homes(only_active=
False):
277 await home.update_info()
278 except TimeoutError
as err:
279 _LOGGER.error(
"Timeout connecting to Tibber home: %s ", err)
280 raise PlatformNotReady
from err
281 except aiohttp.ClientError
as err:
282 _LOGGER.error(
"Error connecting to Tibber home: %s ", err)
283 raise PlatformNotReady
from err
285 if home.has_active_subscription:
287 if coordinator
is None:
291 for entity_description
in SENSORS
294 if home.has_real_time_consumption:
296 async_add_entities, home, entity_registry
298 await home.rt_subscribe(
300 entity_creator.add_sensors, home, hass
301 ).async_set_updated_data
305 old_id = home.info[
"viewer"][
"home"][
"meteringPointData"][
"consumptionEan"]
310 old_entity_id = entity_registry.async_get_entity_id(
311 "sensor", TIBBER_DOMAIN, old_id
313 if old_entity_id
is not None:
314 entity_registry.async_update_entity(
315 old_entity_id, new_unique_id=home.home_id
319 device_entry = device_registry.async_get_device(
320 identifiers={(TIBBER_DOMAIN, old_id)}
322 if device_entry
and entry.entry_id
in device_entry.config_entries:
323 device_registry.async_update_device(
324 device_entry.id, new_identifiers={(TIBBER_DOMAIN, home.home_id)}
331 """Representation of a generic Tibber sensor."""
333 _attr_has_entity_name =
True
336 self, *args: Any, tibber_home: tibber.TibberHome, **kwargs: Any
338 """Initialize the sensor."""
341 self.
_home_name_home_name = tibber_home.info[
"viewer"][
"home"][
"appNickname"]
343 self.
_home_name_home_name = tibber_home.info[
"viewer"][
"home"][
"address"].
get(
346 self._device_name: str |
None =
None
347 self._model: str |
None =
None
351 """Return the device_info of the device."""
353 identifiers={(TIBBER_DOMAIN, self.
_tibber_home_tibber_home.home_id)},
354 name=self._device_name,
355 manufacturer=MANUFACTURER,
357 if self._model
is not None:
358 device_info[
"model"] = self._model
363 """Representation of a Tibber sensor for el price."""
365 _attr_state_class = SensorStateClass.MEASUREMENT
366 _attr_translation_key =
"electricity_price"
368 def __init__(self, tibber_home: tibber.TibberHome) ->
None:
369 """Initialize the sensor."""
370 super().
__init__(tibber_home=tibber_home)
371 self._last_updated: datetime.datetime |
None =
None
376 "app_nickname":
None,
377 "grid_company":
None,
378 "estimated_annual_consumption":
None,
386 "intraday_price_ranking":
None,
395 """Get the latest data and updates the states."""
399 or (self.
_tibber_home_tibber_home.last_data_timestamp - now).total_seconds()
403 _LOGGER.debug(
"Asking for new data")
408 and self._last_updated
409 and self._last_updated.hour == now.hour
414 res = self.
_tibber_home_tibber_home.current_price_data()
415 self._attr_native_value, price_level, self._last_updated, price_rank = res
419 attrs = self.
_tibber_home_tibber_home.current_attributes()
421 self.
_attr_available_attr_available = self._attr_native_value
is not None
424 @Throttle(MIN_TIME_BETWEEN_UPDATES)
426 _LOGGER.debug(
"Fetching data")
428 await self.
_tibber_home_tibber_home.update_info_and_price_info()
429 except (TimeoutError, aiohttp.ClientError):
431 data = self.
_tibber_home_tibber_home.info[
"viewer"][
"home"]
438 ][
"estimatedAnnualConsumption"]
442 """Representation of a Tibber sensor."""
446 tibber_home: tibber.TibberHome,
447 coordinator: TibberDataCoordinator,
448 entity_description: SensorEntityDescription,
450 """Initialize the sensor."""
451 super().
__init__(coordinator=coordinator, tibber_home=tibber_home)
455 f
"{self._tibber_home.home_id}_{self.entity_description.key}"
457 if entity_description.key ==
"month_cost":
464 """Return the value of the sensor."""
469 """Representation of a Tibber sensor for real time consumption."""
473 tibber_home: tibber.TibberHome,
474 description: SensorEntityDescription,
475 initial_state: float,
476 coordinator: TibberRtDataCoordinator,
478 """Initialize the sensor."""
479 super().
__init__(coordinator=coordinator, tibber_home=tibber_home)
485 self.
_attr_unique_id_attr_unique_id = f
"{self._tibber_home.home_id}_rt_{description.key}"
487 if description.key
in (
"accumulatedCost",
"accumulatedReward"):
492 """Return True if entity is available."""
493 return self.
_tibber_home_tibber_home.rt_subscription_running
497 if not (live_measurement := self.coordinator.get_live_measurement()):
503 "accumulatedConsumption",
504 "accumulatedProduction",
510 ts_local = dt_util.parse_datetime(live_measurement[
"timestamp"])
511 if ts_local
is not None:
521 ts_local.replace(hour=0, minute=0, second=0, microsecond=0)
530 """Create realtime Tibber entities."""
534 async_add_entities: AddEntitiesCallback,
535 tibber_home: tibber.TibberHome,
536 entity_registry: er.EntityRegistry,
538 """Initialize the data handler."""
541 self._added_sensors: set[str] = set()
546 """Migrate unique id if needed."""
548 translation_key = sensor_description.translation_key
549 description_key = sensor_description.key
550 entity_id: str |
None =
None
551 if translation_key
in RT_SENSORS_UNIQUE_ID_MIGRATION_SIMPLE:
555 f
"{home_id}_rt_{translation_key.replace('_', ' ')}",
557 elif translation_key
in RT_SENSORS_UNIQUE_ID_MIGRATION:
561 f
"{home_id}_rt_{RT_SENSORS_UNIQUE_ID_MIGRATION[translation_key]}",
563 elif translation_key != description_key:
567 f
"{home_id}_rt_{translation_key}",
570 if entity_id
is None:
573 new_unique_id = f
"{home_id}_rt_{description_key}"
576 "Migrating unique id for %s to %s",
582 entity_id, new_unique_id=new_unique_id
584 except ValueError
as err:
589 self, coordinator: TibberRtDataCoordinator, live_measurement: Any
593 for sensor_description
in RT_SENSORS:
594 if sensor_description.key
in self._added_sensors:
596 state = live_measurement.get(sensor_description.key)
607 new_entities.append(entity)
608 self._added_sensors.
add(sensor_description.key)
614 """Handle Tibber realtime data."""
618 add_sensor_callback: Callable[[TibberRtDataCoordinator, Any],
None],
619 tibber_home: tibber.TibberHome,
622 """Initialize the data handler."""
627 name=tibber_home.info[
"viewer"][
"home"][
"address"].
get(
635 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.
_handle_ha_stop_handle_ha_stop)
639 """Handle Home Assistant stopping."""
644 """Triggered when data is updated."""
649 """Get live measurement data."""
650 if errors := self.
datadata.
get(
"errors"):
651 _LOGGER.error(errors[0])
653 return self.
datadata.
get(
"data", {}).
get(
"liveMeasurement")
datetime|None last_reset(self)
StateType|date|datetime|Decimal native_value(self)
_attr_native_unit_of_measurement
None __init__(self, tibber.TibberHome tibber_home, TibberDataCoordinator coordinator, SensorEntityDescription entity_description)
StateType native_value(self)
Any get_live_measurement(self)
None _handle_ha_stop(self, Event _event)
None __init__(self, Callable[[TibberRtDataCoordinator, Any], None] add_sensor_callback, tibber.TibberHome tibber_home, HomeAssistant hass)
_async_remove_device_updates_handler
None _migrate_unique_id(self, SensorEntityDescription sensor_description)
None __init__(self, AddEntitiesCallback async_add_entities, tibber.TibberHome tibber_home, er.EntityRegistry entity_registry)
None add_sensors(self, TibberRtDataCoordinator coordinator, Any live_measurement)
None __init__(self, tibber.TibberHome tibber_home)
_attr_native_unit_of_measurement
_attr_extra_state_attributes
_attr_native_unit_of_measurement
None __init__(self, tibber.TibberHome tibber_home, SensorEntityDescription description, float initial_state, TibberRtDataCoordinator coordinator)
None _handle_coordinator_update(self)
None __init__(self, *Any args, tibber.TibberHome tibber_home, **Any kwargs)
DeviceInfo device_info(self)
None async_write_ha_state(self)
Callable[[], None] async_add_listener(self, CALLBACK_TYPE update_callback, Any context=None)
Callable[[], None] async_add_listener(self, CALLBACK_TYPE update_callback, Any context=None)
bool add(self, _T matcher)
web.Response get(self, web.Request request, str config_key)
IssData update(pyiss.ISS iss)
None async_setup_entry(HomeAssistant hass, ConfigEntry entry, AddEntitiesCallback async_add_entities)
None async_update_entity(HomeAssistant hass, str entity_id)