mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Tibber sensors (#50418)
* Tibber, split attribute to sensors Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Tibber, split attribute to sensors Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Tibber, split attribute to sensors Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * fix review comments Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * migrate to new device ids Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Migrate entity id Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Migrate entity id Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/tibber/sensor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * move registers out of looå Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
216b0df908
commit
2945c79c5a
@ -2,7 +2,7 @@
|
|||||||
"domain": "tibber",
|
"domain": "tibber",
|
||||||
"name": "Tibber",
|
"name": "Tibber",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tibber",
|
"documentation": "https://www.home-assistant.io/integrations/tibber",
|
||||||
"requirements": ["pyTibber==0.16.2"],
|
"requirements": ["pyTibber==0.17.0"],
|
||||||
"codeowners": ["@danielhiversen"],
|
"codeowners": ["@danielhiversen"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
|
@ -6,9 +6,29 @@ from random import randrange
|
|||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
from homeassistant.components.sensor import DEVICE_CLASS_POWER, SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
from homeassistant.const import POWER_WATT
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
SensorEntity,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
ELECTRICAL_CURRENT_AMPERE,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
POWER_WATT,
|
||||||
|
SIGNAL_STRENGTH_DECIBELS,
|
||||||
|
VOLT,
|
||||||
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady
|
||||||
|
from homeassistant.helpers.device_registry import async_get as async_get_dev_reg
|
||||||
|
from homeassistant.helpers.dispatcher import (
|
||||||
|
async_dispatcher_connect,
|
||||||
|
async_dispatcher_send,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.entity_registry import async_get as async_get_entity_reg
|
||||||
from homeassistant.util import Throttle, dt as dt_util
|
from homeassistant.util import Throttle, dt as dt_util
|
||||||
|
|
||||||
from .const import DOMAIN as TIBBER_DOMAIN, MANUFACTURER
|
from .const import DOMAIN as TIBBER_DOMAIN, MANUFACTURER
|
||||||
@ -19,6 +39,56 @@ ICON = "mdi:currency-usd"
|
|||||||
SCAN_INTERVAL = timedelta(minutes=1)
|
SCAN_INTERVAL = timedelta(minutes=1)
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
|
SIGNAL_UPDATE_ENTITY = "tibber_rt_update_{}"
|
||||||
|
|
||||||
|
RT_SENSOR_MAP = {
|
||||||
|
"averagePower": ["average power", DEVICE_CLASS_POWER, POWER_WATT],
|
||||||
|
"power": ["power", DEVICE_CLASS_POWER, POWER_WATT],
|
||||||
|
"minPower": ["min power", DEVICE_CLASS_POWER, POWER_WATT],
|
||||||
|
"maxPower": ["max power", DEVICE_CLASS_POWER, POWER_WATT],
|
||||||
|
"accumulatedConsumption": [
|
||||||
|
"accumulated consumption",
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
],
|
||||||
|
"accumulatedConsumptionLastHour": [
|
||||||
|
"accumulated consumption last hour",
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
],
|
||||||
|
"accumulatedProduction": [
|
||||||
|
"accumulated production",
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
],
|
||||||
|
"accumulatedProductionLastHour": [
|
||||||
|
"accumulated production last hour",
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
],
|
||||||
|
"lastMeterConsumption": [
|
||||||
|
"last meter consumption",
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
],
|
||||||
|
"lastMeterProduction": [
|
||||||
|
"last meter production",
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
ENERGY_KILO_WATT_HOUR,
|
||||||
|
],
|
||||||
|
"voltagePhase1": ["voltage phase1", DEVICE_CLASS_VOLTAGE, VOLT],
|
||||||
|
"voltagePhase2": ["voltage phase2", DEVICE_CLASS_VOLTAGE, VOLT],
|
||||||
|
"voltagePhase3": ["voltage phase3", DEVICE_CLASS_VOLTAGE, VOLT],
|
||||||
|
"currentL1": ["current L1", DEVICE_CLASS_CURRENT, ELECTRICAL_CURRENT_AMPERE],
|
||||||
|
"currentL2": ["current L2", DEVICE_CLASS_CURRENT, ELECTRICAL_CURRENT_AMPERE],
|
||||||
|
"currentL3": ["current L3", DEVICE_CLASS_CURRENT, ELECTRICAL_CURRENT_AMPERE],
|
||||||
|
"signalStrength": [
|
||||||
|
"signal strength",
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
|
SIGNAL_STRENGTH_DECIBELS,
|
||||||
|
],
|
||||||
|
"accumulatedCost": ["accumulated cost", None, None],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry, async_add_entities):
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
@ -26,7 +96,10 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
|
|
||||||
tibber_connection = hass.data.get(TIBBER_DOMAIN)
|
tibber_connection = hass.data.get(TIBBER_DOMAIN)
|
||||||
|
|
||||||
dev = []
|
entity_registry = async_get_entity_reg(hass)
|
||||||
|
device_registry = async_get_dev_reg(hass)
|
||||||
|
|
||||||
|
entities = []
|
||||||
for home in tibber_connection.get_homes(only_active=False):
|
for home in tibber_connection.get_homes(only_active=False):
|
||||||
try:
|
try:
|
||||||
await home.update_info()
|
await home.update_info()
|
||||||
@ -36,12 +109,36 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
except aiohttp.ClientError as err:
|
except aiohttp.ClientError as err:
|
||||||
_LOGGER.error("Error connecting to Tibber home: %s ", err)
|
_LOGGER.error("Error connecting to Tibber home: %s ", err)
|
||||||
raise PlatformNotReady() from err
|
raise PlatformNotReady() from err
|
||||||
if home.has_active_subscription:
|
|
||||||
dev.append(TibberSensorElPrice(home))
|
|
||||||
if home.has_real_time_consumption:
|
|
||||||
dev.append(TibberSensorRT(home))
|
|
||||||
|
|
||||||
async_add_entities(dev, True)
|
if home.has_active_subscription:
|
||||||
|
entities.append(TibberSensorElPrice(home))
|
||||||
|
if home.has_real_time_consumption:
|
||||||
|
await home.rt_subscribe(
|
||||||
|
TibberRtDataHandler(async_add_entities, home, hass).async_callback
|
||||||
|
)
|
||||||
|
|
||||||
|
# migrate
|
||||||
|
old_id = home.info["viewer"]["home"]["meteringPointData"]["consumptionEan"]
|
||||||
|
if old_id is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# migrate to new device ids
|
||||||
|
old_entity_id = entity_registry.async_get_entity_id(
|
||||||
|
"sensor", TIBBER_DOMAIN, old_id
|
||||||
|
)
|
||||||
|
if old_entity_id is not None:
|
||||||
|
entity_registry.async_update_entity(
|
||||||
|
old_entity_id, new_unique_id=home.home_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# migrate to new device ids
|
||||||
|
device_entry = device_registry.async_get_device({(TIBBER_DOMAIN, old_id)})
|
||||||
|
if device_entry and entry.entry_id in device_entry.config_entries:
|
||||||
|
device_registry.async_update_device(
|
||||||
|
device_entry.id, new_identifiers={(TIBBER_DOMAIN, home.home_id)}
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class TibberSensor(SensorEntity):
|
class TibberSensor(SensorEntity):
|
||||||
@ -50,21 +147,13 @@ class TibberSensor(SensorEntity):
|
|||||||
def __init__(self, tibber_home):
|
def __init__(self, tibber_home):
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._tibber_home = tibber_home
|
self._tibber_home = tibber_home
|
||||||
self._last_updated = None
|
|
||||||
self._state = None
|
self._state = None
|
||||||
self._is_available = False
|
|
||||||
self._extra_state_attributes = {}
|
|
||||||
self._name = tibber_home.info["viewer"]["home"]["appNickname"]
|
self._name = tibber_home.info["viewer"]["home"]["appNickname"]
|
||||||
if self._name is None:
|
if self._name is None:
|
||||||
self._name = tibber_home.info["viewer"]["home"]["address"].get(
|
self._name = tibber_home.info["viewer"]["home"]["address"].get(
|
||||||
"address1", ""
|
"address1", ""
|
||||||
)
|
)
|
||||||
self._spread_load_constant = randrange(3600)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
return self._extra_state_attributes
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self):
|
def model(self):
|
||||||
@ -79,8 +168,7 @@ class TibberSensor(SensorEntity):
|
|||||||
@property
|
@property
|
||||||
def device_id(self):
|
def device_id(self):
|
||||||
"""Return the ID of the physical device this sensor is part of."""
|
"""Return the ID of the physical device this sensor is part of."""
|
||||||
home = self._tibber_home.info["viewer"]["home"]
|
return self._tibber_home.home_id
|
||||||
return home["meteringPointData"]["consumptionEan"]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self):
|
def device_info(self):
|
||||||
@ -98,6 +186,19 @@ class TibberSensor(SensorEntity):
|
|||||||
class TibberSensorElPrice(TibberSensor):
|
class TibberSensorElPrice(TibberSensor):
|
||||||
"""Representation of a Tibber sensor for el price."""
|
"""Representation of a Tibber sensor for el price."""
|
||||||
|
|
||||||
|
def __init__(self, tibber_home):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(tibber_home)
|
||||||
|
self._last_updated = None
|
||||||
|
self._is_available = False
|
||||||
|
self._extra_state_attributes = {}
|
||||||
|
self._spread_load_constant = randrange(5000)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra_state_attributes(self):
|
||||||
|
"""Return the state attributes."""
|
||||||
|
return self._extra_state_attributes
|
||||||
|
|
||||||
async def async_update(self):
|
async def async_update(self):
|
||||||
"""Get the latest data and updates the states."""
|
"""Get the latest data and updates the states."""
|
||||||
now = dt_util.now()
|
now = dt_util.now()
|
||||||
@ -176,29 +277,23 @@ class TibberSensorElPrice(TibberSensor):
|
|||||||
class TibberSensorRT(TibberSensor):
|
class TibberSensorRT(TibberSensor):
|
||||||
"""Representation of a Tibber sensor for real time consumption."""
|
"""Representation of a Tibber sensor for real time consumption."""
|
||||||
|
|
||||||
|
def __init__(self, tibber_home, sensor_name, device_class, unit, initial_state):
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(tibber_home)
|
||||||
|
self._sensor_name = sensor_name
|
||||||
|
self._device_class = device_class
|
||||||
|
self._unit = unit
|
||||||
|
self._state = initial_state
|
||||||
|
|
||||||
async def async_added_to_hass(self):
|
async def async_added_to_hass(self):
|
||||||
"""Start listen for real time data."""
|
"""Start listen for real time data."""
|
||||||
await self._tibber_home.rt_subscribe(self.hass.loop, self._async_callback)
|
self.async_on_remove(
|
||||||
|
async_dispatcher_connect(
|
||||||
async def _async_callback(self, payload):
|
self.hass,
|
||||||
"""Handle received data."""
|
SIGNAL_UPDATE_ENTITY.format(self._sensor_name),
|
||||||
errors = payload.get("errors")
|
self._set_state,
|
||||||
if errors:
|
)
|
||||||
_LOGGER.error(errors[0])
|
)
|
||||||
return
|
|
||||||
data = payload.get("data")
|
|
||||||
if data is None:
|
|
||||||
return
|
|
||||||
live_measurement = data.get("liveMeasurement")
|
|
||||||
if live_measurement is None:
|
|
||||||
return
|
|
||||||
self._state = live_measurement.pop("power", None)
|
|
||||||
for key, value in live_measurement.items():
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
self._extra_state_attributes[key] = value
|
|
||||||
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self):
|
def available(self):
|
||||||
@ -213,7 +308,13 @@ class TibberSensorRT(TibberSensor):
|
|||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return f"Real time consumption {self._name}"
|
return f"{self._sensor_name} {self._name}"
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _set_state(self, state):
|
||||||
|
"""Set sensor state."""
|
||||||
|
self._state = state
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -223,14 +324,60 @@ class TibberSensorRT(TibberSensor):
|
|||||||
@property
|
@property
|
||||||
def unit_of_measurement(self):
|
def unit_of_measurement(self):
|
||||||
"""Return the unit of measurement of this entity."""
|
"""Return the unit of measurement of this entity."""
|
||||||
return POWER_WATT
|
return self._unit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique ID."""
|
"""Return a unique ID."""
|
||||||
return f"{self.device_id}_rt_consumption"
|
return f"{self.device_id}_rt_{self._sensor_name}"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self):
|
def device_class(self):
|
||||||
"""Return the device class of the sensor."""
|
"""Return the device class of the sensor."""
|
||||||
return DEVICE_CLASS_POWER
|
return self._device_class
|
||||||
|
|
||||||
|
|
||||||
|
class TibberRtDataHandler:
|
||||||
|
"""Handle Tibber realtime data."""
|
||||||
|
|
||||||
|
def __init__(self, async_add_entities, tibber_home, hass):
|
||||||
|
"""Initialize the data handler."""
|
||||||
|
self._async_add_entities = async_add_entities
|
||||||
|
self._tibber_home = tibber_home
|
||||||
|
self.hass = hass
|
||||||
|
self._entities = set()
|
||||||
|
|
||||||
|
async def async_callback(self, payload):
|
||||||
|
"""Handle received data."""
|
||||||
|
errors = payload.get("errors")
|
||||||
|
if errors:
|
||||||
|
_LOGGER.error(errors[0])
|
||||||
|
return
|
||||||
|
data = payload.get("data")
|
||||||
|
if data is None:
|
||||||
|
return
|
||||||
|
live_measurement = data.get("liveMeasurement")
|
||||||
|
if live_measurement is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_entities = []
|
||||||
|
for sensor_type, state in live_measurement.items():
|
||||||
|
if state is None or sensor_type not in RT_SENSOR_MAP:
|
||||||
|
continue
|
||||||
|
if sensor_type in self._entities:
|
||||||
|
async_dispatcher_send(
|
||||||
|
self.hass,
|
||||||
|
SIGNAL_UPDATE_ENTITY.format(RT_SENSOR_MAP[sensor_type][0]),
|
||||||
|
state,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
sensor_name, device_class, unit = RT_SENSOR_MAP[sensor_type]
|
||||||
|
if sensor_type == "accumulatedCost":
|
||||||
|
unit = self._tibber_home.currency
|
||||||
|
entity = TibberSensorRT(
|
||||||
|
self._tibber_home, sensor_name, device_class, unit, state
|
||||||
|
)
|
||||||
|
new_entities.append(entity)
|
||||||
|
self._entities.add(sensor_type)
|
||||||
|
if new_entities:
|
||||||
|
self._async_add_entities(new_entities)
|
||||||
|
@ -1250,7 +1250,7 @@ pyRFXtrx==0.26.1
|
|||||||
# pySwitchmate==0.4.6
|
# pySwitchmate==0.4.6
|
||||||
|
|
||||||
# homeassistant.components.tibber
|
# homeassistant.components.tibber
|
||||||
pyTibber==0.16.2
|
pyTibber==0.17.0
|
||||||
|
|
||||||
# homeassistant.components.dlink
|
# homeassistant.components.dlink
|
||||||
pyW215==0.7.0
|
pyW215==0.7.0
|
||||||
|
@ -687,7 +687,7 @@ pyMetno==0.8.3
|
|||||||
pyRFXtrx==0.26.1
|
pyRFXtrx==0.26.1
|
||||||
|
|
||||||
# homeassistant.components.tibber
|
# homeassistant.components.tibber
|
||||||
pyTibber==0.16.2
|
pyTibber==0.17.0
|
||||||
|
|
||||||
# homeassistant.components.nextbus
|
# homeassistant.components.nextbus
|
||||||
py_nextbusnext==0.1.4
|
py_nextbusnext==0.1.4
|
||||||
|
Loading…
x
Reference in New Issue
Block a user