2019-02-13 20:21:14 +00:00
|
|
|
"""Support for Tibber sensors."""
|
2017-10-04 08:31:42 +00:00
|
|
|
import asyncio
|
2019-03-21 05:56:46 +00:00
|
|
|
from datetime import timedelta
|
2017-10-04 08:31:42 +00:00
|
|
|
import logging
|
2020-10-03 02:09:29 +00:00
|
|
|
from random import randrange
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2018-03-01 22:15:27 +00:00
|
|
|
import aiohttp
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2020-10-10 12:20:15 +00:00
|
|
|
from homeassistant.components.sensor import DEVICE_CLASS_POWER
|
2020-04-11 13:40:59 +00:00
|
|
|
from homeassistant.const import POWER_WATT
|
2018-12-03 19:53:18 +00:00
|
|
|
from homeassistant.exceptions import PlatformNotReady
|
2017-10-04 08:31:42 +00:00
|
|
|
from homeassistant.helpers.entity import Entity
|
2019-03-21 05:56:46 +00:00
|
|
|
from homeassistant.util import Throttle, dt as dt_util
|
|
|
|
|
2020-05-03 12:40:19 +00:00
|
|
|
from .const import DOMAIN as TIBBER_DOMAIN, MANUFACTURER
|
2017-10-04 08:31:42 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
ICON = "mdi:currency-usd"
|
2017-10-04 08:31:42 +00:00
|
|
|
SCAN_INTERVAL = timedelta(minutes=1)
|
2018-04-20 09:45:11 +00:00
|
|
|
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
|
2020-05-25 10:26:03 +00:00
|
|
|
PARALLEL_UPDATES = 0
|
2017-10-04 08:31:42 +00:00
|
|
|
|
|
|
|
|
2020-05-03 12:40:19 +00:00
|
|
|
async def async_setup_entry(hass, entry, async_add_entities):
|
2017-10-04 08:31:42 +00:00
|
|
|
"""Set up the Tibber sensor."""
|
2018-03-01 22:15:27 +00:00
|
|
|
|
2018-10-04 07:29:49 +00:00
|
|
|
tibber_connection = hass.data.get(TIBBER_DOMAIN)
|
2018-09-26 05:49:09 +00:00
|
|
|
|
2018-12-02 14:35:59 +00:00
|
|
|
dev = []
|
2019-01-21 15:12:03 +00:00
|
|
|
for home in tibber_connection.get_homes(only_active=False):
|
2018-12-02 14:35:59 +00:00
|
|
|
try:
|
2018-03-05 02:38:21 +00:00
|
|
|
await home.update_info()
|
2018-12-03 19:53:18 +00:00
|
|
|
except asyncio.TimeoutError as err:
|
|
|
|
_LOGGER.error("Timeout connecting to Tibber home: %s ", err)
|
2020-08-28 11:50:32 +00:00
|
|
|
raise PlatformNotReady() from err
|
2018-12-03 19:53:18 +00:00
|
|
|
except aiohttp.ClientError as err:
|
|
|
|
_LOGGER.error("Error connecting to Tibber home: %s ", err)
|
2020-08-28 11:50:32 +00:00
|
|
|
raise PlatformNotReady() from err
|
2019-01-19 18:23:22 +00:00
|
|
|
if home.has_active_subscription:
|
|
|
|
dev.append(TibberSensorElPrice(home))
|
2018-12-02 14:35:59 +00:00
|
|
|
if home.has_real_time_consumption:
|
|
|
|
dev.append(TibberSensorRT(home))
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2019-01-19 18:23:22 +00:00
|
|
|
async_add_entities(dev, True)
|
2017-10-04 08:31:42 +00:00
|
|
|
|
|
|
|
|
2019-09-18 05:30:59 +00:00
|
|
|
class TibberSensor(Entity):
|
|
|
|
"""Representation of a generic Tibber sensor."""
|
2017-10-04 08:31:42 +00:00
|
|
|
|
|
|
|
def __init__(self, tibber_home):
|
|
|
|
"""Initialize the sensor."""
|
|
|
|
self._tibber_home = tibber_home
|
|
|
|
self._last_updated = None
|
|
|
|
self._state = None
|
2018-04-20 09:45:11 +00:00
|
|
|
self._is_available = False
|
2018-03-05 02:38:21 +00:00
|
|
|
self._device_state_attributes = {}
|
2019-09-18 05:30:59 +00:00
|
|
|
self._name = tibber_home.info["viewer"]["home"]["appNickname"]
|
|
|
|
if self._name is None:
|
|
|
|
self._name = tibber_home.info["viewer"]["home"]["address"].get(
|
|
|
|
"address1", ""
|
|
|
|
)
|
2020-10-03 02:09:29 +00:00
|
|
|
self._spread_load_constant = randrange(3600)
|
2019-09-18 05:30:59 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_state_attributes(self):
|
|
|
|
"""Return the state attributes."""
|
|
|
|
return self._device_state_attributes
|
|
|
|
|
2020-05-03 12:40:19 +00:00
|
|
|
@property
|
|
|
|
def model(self):
|
|
|
|
"""Return the model of the sensor."""
|
|
|
|
return None
|
|
|
|
|
2019-09-18 05:30:59 +00:00
|
|
|
@property
|
|
|
|
def state(self):
|
|
|
|
"""Return the state of the device."""
|
|
|
|
return self._state
|
|
|
|
|
2020-05-03 12:40:19 +00:00
|
|
|
@property
|
|
|
|
def device_id(self):
|
|
|
|
"""Return the ID of the physical device this sensor is part of."""
|
|
|
|
home = self._tibber_home.info["viewer"]["home"]
|
|
|
|
return home["meteringPointData"]["consumptionEan"]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def device_info(self):
|
|
|
|
"""Return the device_info of the device."""
|
|
|
|
device_info = {
|
|
|
|
"identifiers": {(TIBBER_DOMAIN, self.device_id)},
|
|
|
|
"name": self.name,
|
|
|
|
"manufacturer": MANUFACTURER,
|
|
|
|
}
|
|
|
|
if self.model is not None:
|
|
|
|
device_info["model"] = self.model
|
|
|
|
return device_info
|
|
|
|
|
2019-09-18 05:30:59 +00:00
|
|
|
|
|
|
|
class TibberSensorElPrice(TibberSensor):
|
|
|
|
"""Representation of a Tibber sensor for el price."""
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2018-03-05 02:38:21 +00:00
|
|
|
async def async_update(self):
|
2017-10-04 08:31:42 +00:00
|
|
|
"""Get the latest data and updates the states."""
|
2018-04-26 07:49:35 +00:00
|
|
|
now = dt_util.now()
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
self._tibber_home.current_price_total
|
|
|
|
and self._last_updated
|
|
|
|
and self._last_updated.hour == now.hour
|
|
|
|
and self._tibber_home.last_data_timestamp
|
|
|
|
):
|
2017-10-04 08:31:42 +00:00
|
|
|
return
|
|
|
|
|
2019-07-31 19:25:30 +00:00
|
|
|
if (
|
|
|
|
not self._tibber_home.last_data_timestamp
|
2020-10-03 02:09:29 +00:00
|
|
|
or (self._tibber_home.last_data_timestamp - now).total_seconds()
|
2020-10-15 06:08:57 +00:00
|
|
|
< 5 * 3600 + self._spread_load_constant
|
2019-07-31 19:25:30 +00:00
|
|
|
or not self._is_available
|
|
|
|
):
|
2020-07-05 21:04:19 +00:00
|
|
|
_LOGGER.debug("Asking for new data")
|
2018-04-20 09:45:11 +00:00
|
|
|
await self._fetch_data()
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2019-07-29 06:29:45 +00:00
|
|
|
res = self._tibber_home.current_price_data()
|
|
|
|
self._state, price_level, self._last_updated = res
|
2019-07-31 19:25:30 +00:00
|
|
|
self._device_state_attributes["price_level"] = price_level
|
2019-07-29 06:29:45 +00:00
|
|
|
|
|
|
|
attrs = self._tibber_home.current_attributes()
|
|
|
|
self._device_state_attributes.update(attrs)
|
|
|
|
self._is_available = self._state is not None
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2018-04-20 09:45:11 +00:00
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return True if entity is available."""
|
|
|
|
return self._is_available
|
|
|
|
|
2017-10-04 08:31:42 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the sensor."""
|
2020-04-04 19:39:22 +00:00
|
|
|
return f"Electricity price {self._name}"
|
2017-10-04 08:31:42 +00:00
|
|
|
|
2020-05-03 12:40:19 +00:00
|
|
|
@property
|
|
|
|
def model(self):
|
|
|
|
"""Return the model of the sensor."""
|
|
|
|
return "Price Sensor"
|
|
|
|
|
2017-10-04 08:31:42 +00:00
|
|
|
@property
|
|
|
|
def icon(self):
|
|
|
|
"""Return the icon to use in the frontend."""
|
|
|
|
return ICON
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
|
|
|
"""Return the unit of measurement of this entity."""
|
2019-09-18 05:30:59 +00:00
|
|
|
return self._tibber_home.price_unit
|
2018-03-03 12:18:45 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self):
|
2018-03-03 18:23:55 +00:00
|
|
|
"""Return a unique ID."""
|
2020-05-03 12:40:19 +00:00
|
|
|
return self.device_id
|
2018-04-20 09:45:11 +00:00
|
|
|
|
|
|
|
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
|
|
|
async def _fetch_data(self):
|
2020-10-03 02:09:29 +00:00
|
|
|
_LOGGER.debug("Fetching data")
|
2018-04-20 09:45:11 +00:00
|
|
|
try:
|
2020-09-30 15:17:21 +00:00
|
|
|
await self._tibber_home.update_info_and_price_info()
|
2018-04-20 09:45:11 +00:00
|
|
|
except (asyncio.TimeoutError, aiohttp.ClientError):
|
|
|
|
return
|
2019-07-31 19:25:30 +00:00
|
|
|
data = self._tibber_home.info["viewer"]["home"]
|
|
|
|
self._device_state_attributes["app_nickname"] = data["appNickname"]
|
|
|
|
self._device_state_attributes["grid_company"] = data["meteringPointData"][
|
|
|
|
"gridCompany"
|
|
|
|
]
|
|
|
|
self._device_state_attributes["estimated_annual_consumption"] = data[
|
|
|
|
"meteringPointData"
|
|
|
|
]["estimatedAnnualConsumption"]
|
2018-04-20 09:45:11 +00:00
|
|
|
|
2018-09-26 05:49:09 +00:00
|
|
|
|
2019-09-18 05:30:59 +00:00
|
|
|
class TibberSensorRT(TibberSensor):
|
|
|
|
"""Representation of a Tibber sensor for real time consumption."""
|
2018-09-26 05:49:09 +00:00
|
|
|
|
|
|
|
async def async_added_to_hass(self):
|
2020-05-03 12:40:19 +00:00
|
|
|
"""Start listen for real time data."""
|
2019-07-31 19:25:30 +00:00
|
|
|
await self._tibber_home.rt_subscribe(self.hass.loop, self._async_callback)
|
2018-09-26 05:49:09 +00:00
|
|
|
|
|
|
|
async def _async_callback(self, payload):
|
|
|
|
"""Handle received data."""
|
2019-07-31 19:25:30 +00:00
|
|
|
errors = payload.get("errors")
|
2018-11-11 13:06:21 +00:00
|
|
|
if errors:
|
|
|
|
_LOGGER.error(errors[0])
|
|
|
|
return
|
2019-07-31 19:25:30 +00:00
|
|
|
data = payload.get("data")
|
2018-11-11 13:06:21 +00:00
|
|
|
if data is None:
|
|
|
|
return
|
2019-07-31 19:25:30 +00:00
|
|
|
live_measurement = data.get("liveMeasurement")
|
2018-11-11 13:06:21 +00:00
|
|
|
if live_measurement is None:
|
|
|
|
return
|
2019-07-31 19:25:30 +00:00
|
|
|
self._state = live_measurement.pop("power", None)
|
2018-12-06 08:30:11 +00:00
|
|
|
for key, value in live_measurement.items():
|
|
|
|
if value is None:
|
|
|
|
continue
|
|
|
|
self._device_state_attributes[key] = value
|
|
|
|
|
2020-04-01 21:19:51 +00:00
|
|
|
self.async_write_ha_state()
|
2018-09-26 05:49:09 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return True if entity is available."""
|
|
|
|
return self._tibber_home.rt_subscription_running
|
|
|
|
|
2020-05-03 12:40:19 +00:00
|
|
|
@property
|
|
|
|
def model(self):
|
|
|
|
"""Return the model of the sensor."""
|
|
|
|
return "Tibber Pulse"
|
|
|
|
|
2018-09-26 05:49:09 +00:00
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
"""Return the name of the sensor."""
|
2020-04-04 19:39:22 +00:00
|
|
|
return f"Real time consumption {self._name}"
|
2018-09-26 05:49:09 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""Return the polling state."""
|
|
|
|
return False
|
|
|
|
|
|
|
|
@property
|
|
|
|
def unit_of_measurement(self):
|
|
|
|
"""Return the unit of measurement of this entity."""
|
2020-04-11 13:40:59 +00:00
|
|
|
return POWER_WATT
|
2018-09-26 05:49:09 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return a unique ID."""
|
2020-05-03 12:40:19 +00:00
|
|
|
return f"{self.device_id}_rt_consumption"
|
2020-10-10 12:20:15 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_class(self):
|
|
|
|
"""Return the device class of the sensor."""
|
|
|
|
return DEVICE_CLASS_POWER
|