Add forecasts to MetOffice integration (#50876)

* MetOfficeData now retrieves both 3-hourly and daily data (full forecast data, as well as "now" snapshot) on each update

* Bump datapoint API up to latest version

* Create 2 sets of sensors - one of each set for 3-hourly and for daily data (same ones initially enabled, for now)

* Create two entities (one each for 3-hourly and daily data) and also add in the forecast data for each dataset

* Testing changes to accommodate now having two sets of everything for 3-hourly and daily update data

* Removed unused import (reported by flake8)

* As per conversation with @MatthewFlamm leave the 3-hourly entity's unique_id unchanged (although the display name is changed)

* Make some improvements based on reviews

Make some improvements and fix up the formatting/linting failures.

* Make some improvements based on reviews

Make some improvements and fix up the formatting/linting failures.

* Added more test coverage

* import asyncio

* Try to fix test

* Rewrote everything using CoordinatorEntity

* Fixed config flow

* Fixed lint errors

Co-authored-by: MrHarcombe <ian.harcombe@gmail.com>
Co-authored-by: Henco Appel <hencoappel+github@gmail.com>
This commit is contained in:
avee87 2021-06-27 20:04:42 +01:00 committed by GitHub
parent 23339cff95
commit 2d1744c573
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 659 additions and 236 deletions

View file

@ -10,16 +10,21 @@ from homeassistant.const import (
TEMP_CELSIUS,
UV_INDEX,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
ATTRIBUTION,
CONDITION_CLASSES,
DOMAIN,
METOFFICE_COORDINATOR,
METOFFICE_DATA,
METOFFICE_COORDINATES,
METOFFICE_DAILY_COORDINATOR,
METOFFICE_HOURLY_COORDINATOR,
METOFFICE_NAME,
MODE_3HOURLY_LABEL,
MODE_DAILY,
MODE_DAILY_LABEL,
VISIBILITY_CLASSES,
VISIBILITY_DISTANCE_CLASSES,
)
@ -85,28 +90,40 @@ async def async_setup_entry(
async_add_entities(
[
MetOfficeCurrentSensor(entry.data, hass_data, sensor_type)
MetOfficeCurrentSensor(
hass_data[METOFFICE_HOURLY_COORDINATOR], hass_data, True, sensor_type
)
for sensor_type in SENSOR_TYPES
]
+ [
MetOfficeCurrentSensor(
hass_data[METOFFICE_DAILY_COORDINATOR], hass_data, False, sensor_type
)
for sensor_type in SENSOR_TYPES
],
False,
)
class MetOfficeCurrentSensor(SensorEntity):
class MetOfficeCurrentSensor(CoordinatorEntity, SensorEntity):
"""Implementation of a Met Office current weather condition sensor."""
def __init__(self, entry_data, hass_data, sensor_type):
def __init__(self, coordinator, hass_data, use_3hourly, sensor_type):
"""Initialize the sensor."""
self._data = hass_data[METOFFICE_DATA]
self._coordinator = hass_data[METOFFICE_COORDINATOR]
super().__init__(coordinator)
self._type = sensor_type
self._name = f"{hass_data[METOFFICE_NAME]} {SENSOR_TYPES[self._type][0]}"
self._unique_id = f"{SENSOR_TYPES[self._type][0]}_{self._data.latitude}_{self._data.longitude}"
mode_label = MODE_3HOURLY_LABEL if use_3hourly else MODE_DAILY_LABEL
self._name = (
f"{hass_data[METOFFICE_NAME]} {SENSOR_TYPES[self._type][0]} {mode_label}"
)
self._unique_id = (
f"{SENSOR_TYPES[self._type][0]}_{hass_data[METOFFICE_COORDINATES]}"
)
if not use_3hourly:
self._unique_id = f"{self._unique_id}_{MODE_DAILY}"
self.metoffice_site_id = None
self.metoffice_site_name = None
self.metoffice_now = None
self.use_3hourly = use_3hourly
@property
def name(self):
@ -124,22 +141,26 @@ class MetOfficeCurrentSensor(SensorEntity):
value = None
if self._type == "visibility_distance" and hasattr(
self.metoffice_now, "visibility"
self.coordinator.data.now, "visibility"
):
value = VISIBILITY_DISTANCE_CLASSES.get(self.metoffice_now.visibility.value)
value = VISIBILITY_DISTANCE_CLASSES.get(
self.coordinator.data.now.visibility.value
)
if self._type == "visibility" and hasattr(self.metoffice_now, "visibility"):
value = VISIBILITY_CLASSES.get(self.metoffice_now.visibility.value)
if self._type == "visibility" and hasattr(
self.coordinator.data.now, "visibility"
):
value = VISIBILITY_CLASSES.get(self.coordinator.data.now.visibility.value)
elif self._type == "weather" and hasattr(self.metoffice_now, self._type):
elif self._type == "weather" and hasattr(self.coordinator.data.now, self._type):
value = [
k
for k, v in CONDITION_CLASSES.items()
if self.metoffice_now.weather.value in v
if self.coordinator.data.now.weather.value in v
][0]
elif hasattr(self.metoffice_now, self._type):
value = getattr(self.metoffice_now, self._type)
elif hasattr(self.coordinator.data.now, self._type):
value = getattr(self.coordinator.data.now, self._type)
if not isinstance(value, int):
value = value.value
@ -175,44 +196,13 @@ class MetOfficeCurrentSensor(SensorEntity):
"""Return the state attributes of the device."""
return {
ATTR_ATTRIBUTION: ATTRIBUTION,
ATTR_LAST_UPDATE: self.metoffice_now.date if self.metoffice_now else None,
ATTR_LAST_UPDATE: self.coordinator.data.now.date,
ATTR_SENSOR_ID: self._type,
ATTR_SITE_ID: self.metoffice_site_id if self.metoffice_site_id else None,
ATTR_SITE_NAME: self.metoffice_site_name
if self.metoffice_site_name
else None,
ATTR_SITE_ID: self.coordinator.data.site.id,
ATTR_SITE_NAME: self.coordinator.data.site.name,
}
async def async_added_to_hass(self) -> None:
"""Set up a listener and load data."""
self.async_on_remove(
self._coordinator.async_add_listener(self._update_callback)
)
self._update_callback()
async def async_update(self):
"""Schedule a custom update via the common entity update service."""
await self._coordinator.async_request_refresh()
@callback
def _update_callback(self) -> None:
"""Load data from integration."""
self.metoffice_site_id = self._data.site_id
self.metoffice_site_name = self._data.site_name
self.metoffice_now = self._data.now
self.async_write_ha_state()
@property
def should_poll(self) -> bool:
"""Entities do not individually poll."""
return False
@property
def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry."""
return SENSOR_TYPES[self._type][4]
@property
def available(self):
"""Return if state is available."""
return self.metoffice_site_id is not None and self.metoffice_now is not None
return SENSOR_TYPES[self._type][4] and self.use_3hourly