Add async lib and DataUpdateCoordinator for environment_canada (#57746)
parent
3715286969
commit
de4a4c3ba9
|
@ -1,14 +1,19 @@
|
|||
"""The Environment Canada (EC) component."""
|
||||
from functools import partial
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
from env_canada import ECData, ECRadar
|
||||
from env_canada import ECRadar, ECWeather, ec_exc
|
||||
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import CONF_LANGUAGE, CONF_STATION, DOMAIN
|
||||
|
||||
DEFAULT_RADAR_UPDATE_INTERVAL = timedelta(minutes=5)
|
||||
DEFAULT_WEATHER_UPDATE_INTERVAL = timedelta(minutes=5)
|
||||
|
||||
PLATFORMS = ["camera", "sensor", "weather"]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -21,21 +26,26 @@ async def async_setup_entry(hass, config_entry):
|
|||
station = config_entry.data.get(CONF_STATION)
|
||||
lang = config_entry.data.get(CONF_LANGUAGE, "English")
|
||||
|
||||
weather_api = {}
|
||||
coordinators = {}
|
||||
|
||||
weather_init = partial(
|
||||
ECData, station_id=station, coordinates=(lat, lon), language=lang.lower()
|
||||
weather_data = ECWeather(
|
||||
station_id=station,
|
||||
coordinates=(lat, lon),
|
||||
language=lang.lower(),
|
||||
)
|
||||
weather_data = await hass.async_add_executor_job(weather_init)
|
||||
weather_api["weather_data"] = weather_data
|
||||
coordinators["weather_coordinator"] = ECDataUpdateCoordinator(
|
||||
hass, weather_data, "weather", DEFAULT_WEATHER_UPDATE_INTERVAL
|
||||
)
|
||||
await coordinators["weather_coordinator"].async_config_entry_first_refresh()
|
||||
|
||||
radar_init = partial(ECRadar, coordinates=(lat, lon))
|
||||
radar_data = await hass.async_add_executor_job(radar_init)
|
||||
weather_api["radar_data"] = radar_data
|
||||
await hass.async_add_executor_job(radar_data.get_loop)
|
||||
radar_data = ECRadar(coordinates=(lat, lon))
|
||||
coordinators["radar_coordinator"] = ECDataUpdateCoordinator(
|
||||
hass, radar_data, "radar", DEFAULT_RADAR_UPDATE_INTERVAL
|
||||
)
|
||||
await coordinators["radar_coordinator"].async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][config_entry.entry_id] = weather_api
|
||||
hass.data[DOMAIN][config_entry.entry_id] = coordinators
|
||||
|
||||
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
|
||||
|
||||
|
@ -77,3 +87,22 @@ def trigger_import(hass, config):
|
|||
DOMAIN, context={"source": SOURCE_IMPORT}, data=data
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ECDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Class to manage fetching EC data."""
|
||||
|
||||
def __init__(self, hass, ec_data, name, update_interval):
|
||||
"""Initialize global EC data updater."""
|
||||
super().__init__(
|
||||
hass, _LOGGER, name=f"{DOMAIN} {name}", update_interval=update_interval
|
||||
)
|
||||
self.ec_data = ec_data
|
||||
|
||||
async def _async_update_data(self):
|
||||
"""Fetch data from EC."""
|
||||
try:
|
||||
await self.ec_data.update()
|
||||
except (et.ParseError, ec_exc.UnknownStationId) as ex:
|
||||
raise UpdateFailed(f"Error fetching {self.name} data: {ex}") from ex
|
||||
return self.ec_data
|
||||
|
|
|
@ -1,33 +1,18 @@
|
|||
"""Support for the Environment Canada radar imagery."""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from env_canada import get_station_coords
|
||||
from requests.exceptions import ConnectionError as RequestsConnectionError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_NAME,
|
||||
)
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import trigger_import
|
||||
from .const import CONF_ATTRIBUTION, CONF_STATION, DOMAIN
|
||||
from .const import ATTR_OBSERVATION_TIME, CONF_STATION, DOMAIN
|
||||
|
||||
CONF_LOOP = "loop"
|
||||
CONF_PRECIP_TYPE = "precip_type"
|
||||
ATTR_UPDATED = "updated"
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=10)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
|
@ -43,13 +28,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||
|
||||
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Environment Canada camera."""
|
||||
if config.get(CONF_STATION):
|
||||
lat, lon = await hass.async_add_executor_job(
|
||||
get_station_coords, config[CONF_STATION]
|
||||
)
|
||||
else:
|
||||
lat = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
lon = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
lat = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||
lon = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||
|
||||
config[CONF_LATITUDE] = lat
|
||||
config[CONF_LONGITUDE] = lon
|
||||
|
@ -59,52 +39,35 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Add a weather entity from a config_entry."""
|
||||
radar_data = hass.data[DOMAIN][config_entry.entry_id]["radar_data"]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
ECCamera(
|
||||
radar_data,
|
||||
f"{config_entry.title} Radar",
|
||||
f"{config_entry.unique_id}-radar",
|
||||
),
|
||||
]
|
||||
)
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]["radar_coordinator"]
|
||||
async_add_entities([ECCamera(coordinator)])
|
||||
|
||||
|
||||
class ECCamera(Camera):
|
||||
class ECCamera(CoordinatorEntity, Camera):
|
||||
"""Implementation of an Environment Canada radar camera."""
|
||||
|
||||
def __init__(self, radar_object, camera_name, unique_id):
|
||||
def __init__(self, coordinator):
|
||||
"""Initialize the camera."""
|
||||
super().__init__()
|
||||
super().__init__(coordinator)
|
||||
Camera.__init__(self)
|
||||
|
||||
self.radar_object = coordinator.ec_data
|
||||
self._attr_name = f"{coordinator.config_entry.title} Radar"
|
||||
self._attr_unique_id = f"{coordinator.config_entry.unique_id}-radar"
|
||||
self._attr_attribution = self.radar_object.metadata["attribution"]
|
||||
|
||||
self.radar_object = radar_object
|
||||
self._attr_name = camera_name
|
||||
self._attr_unique_id = unique_id
|
||||
self.content_type = "image/gif"
|
||||
self.image = None
|
||||
self.timestamp = None
|
||||
self.observation_time = None
|
||||
|
||||
def camera_image(
|
||||
self, width: int | None = None, height: int | None = None
|
||||
) -> bytes | None:
|
||||
"""Return bytes of camera image."""
|
||||
self.update()
|
||||
return self.image
|
||||
self.observation_time = self.radar_object.timestamp
|
||||
return self.radar_object.image
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes of the device."""
|
||||
return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp}
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Update radar image."""
|
||||
try:
|
||||
self.image = self.radar_object.get_loop()
|
||||
except RequestsConnectionError:
|
||||
_LOGGER.warning("Radar data update failed due to rate limiting")
|
||||
return
|
||||
|
||||
self.timestamp = self.radar_object.timestamp
|
||||
return {ATTR_OBSERVATION_TIME: self.observation_time}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
"""Config flow for Environment Canada integration."""
|
||||
from functools import partial
|
||||
import logging
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
import aiohttp
|
||||
from env_canada import ECData
|
||||
from env_canada import ECWeather, ec_exc
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, exceptions
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
|
@ -16,19 +15,19 @@ from .const import CONF_LANGUAGE, CONF_STATION, CONF_TITLE, DOMAIN
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def validate_input(hass, data):
|
||||
async def validate_input(data):
|
||||
"""Validate the user input allows us to connect."""
|
||||
lat = data.get(CONF_LATITUDE)
|
||||
lon = data.get(CONF_LONGITUDE)
|
||||
station = data.get(CONF_STATION)
|
||||
lang = data.get(CONF_LANGUAGE)
|
||||
|
||||
weather_init = partial(
|
||||
ECData, station_id=station, coordinates=(lat, lon), language=lang.lower()
|
||||
weather_data = ECWeather(
|
||||
station_id=station,
|
||||
coordinates=(lat, lon),
|
||||
language=lang.lower(),
|
||||
)
|
||||
weather_data = await hass.async_add_executor_job(weather_init)
|
||||
if weather_data.metadata.get("location") is None:
|
||||
raise TooManyAttempts
|
||||
await weather_data.update()
|
||||
|
||||
if lat is None or lon is None:
|
||||
lat = weather_data.lat
|
||||
|
@ -52,10 +51,8 @@ class EnvironmentCanadaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors = {}
|
||||
if user_input is not None:
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
except TooManyAttempts:
|
||||
errors["base"] = "too_many_attempts"
|
||||
except et.ParseError:
|
||||
info = await validate_input(user_input)
|
||||
except (et.ParseError, vol.MultipleInvalid, ec_exc.UnknownStationId):
|
||||
errors["base"] = "bad_station_id"
|
||||
except aiohttp.ClientConnectionError:
|
||||
errors["base"] = "cannot_connect"
|
||||
|
@ -102,7 +99,3 @@ class EnvironmentCanadaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
async def async_step_import(self, import_data):
|
||||
"""Import entry from configuration.yaml."""
|
||||
return await self.async_step_user(import_data)
|
||||
|
||||
|
||||
class TooManyAttempts(exceptions.HomeAssistantError):
|
||||
"""Error to indicate station ID is missing, invalid, or not in EC database."""
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
ATTR_OBSERVATION_TIME = "observation_time"
|
||||
ATTR_STATION = "station"
|
||||
CONF_ATTRIBUTION = "Data provided by Environment Canada"
|
||||
CONF_LANGUAGE = "language"
|
||||
CONF_STATION = "station"
|
||||
CONF_TITLE = "title"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"domain": "environment_canada",
|
||||
"name": "Environment Canada",
|
||||
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
|
||||
"requirements": ["env_canada==0.2.7"],
|
||||
"requirements": ["env_canada==0.5.14"],
|
||||
"codeowners": ["@gwww", "@michaeldavie"],
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
"""Support for the Environment Canada weather service."""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
@ -7,7 +6,6 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION,
|
||||
ATTR_LOCATION,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
|
@ -15,12 +13,17 @@ from homeassistant.const import (
|
|||
TEMP_CELSIUS,
|
||||
)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from . import trigger_import
|
||||
from .const import ATTR_STATION, CONF_ATTRIBUTION, CONF_LANGUAGE, CONF_STATION, DOMAIN
|
||||
from .const import (
|
||||
ATTR_OBSERVATION_TIME,
|
||||
ATTR_STATION,
|
||||
CONF_LANGUAGE,
|
||||
CONF_STATION,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
SCAN_INTERVAL = timedelta(minutes=10)
|
||||
ATTR_UPDATED = "updated"
|
||||
ATTR_TIME = "alert time"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -31,7 +34,7 @@ def validate_station(station):
|
|||
if station is None:
|
||||
return None
|
||||
if not re.fullmatch(r"[A-Z]{2}/s0000\d{3}", station):
|
||||
raise vol.error.Invalid('Station ID must be of the form "XX/s0000###"')
|
||||
raise vol.Invalid('Station ID must be of the form "XX/s0000###"')
|
||||
return station
|
||||
|
||||
|
||||
|
@ -52,43 +55,40 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
|
|||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Add a weather entity from a config_entry."""
|
||||
weather_data = hass.data[DOMAIN][config_entry.entry_id]["weather_data"]
|
||||
sensor_list = list(weather_data.conditions) + list(weather_data.alerts)
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]["weather_coordinator"]
|
||||
weather_data = coordinator.ec_data
|
||||
|
||||
sensors = list(weather_data.conditions)
|
||||
labels = [weather_data.conditions[sensor]["label"] for sensor in sensors]
|
||||
alerts_list = list(weather_data.alerts)
|
||||
labels = labels + [weather_data.alerts[sensor]["label"] for sensor in alerts_list]
|
||||
sensors = sensors + alerts_list
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
ECSensor(
|
||||
sensor_type,
|
||||
f"{config_entry.title} {sensor_type}",
|
||||
weather_data,
|
||||
f"{weather_data.metadata['location']}-{sensor_type}",
|
||||
)
|
||||
for sensor_type in sensor_list
|
||||
ECSensor(coordinator, sensor, label)
|
||||
for sensor, label in zip(sensors, labels)
|
||||
],
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class ECSensor(SensorEntity):
|
||||
class ECSensor(CoordinatorEntity, SensorEntity):
|
||||
"""Implementation of an Environment Canada sensor."""
|
||||
|
||||
def __init__(self, sensor_type, name, ec_data, unique_id):
|
||||
def __init__(self, coordinator, sensor, label):
|
||||
"""Initialize the sensor."""
|
||||
self.sensor_type = sensor_type
|
||||
self.ec_data = ec_data
|
||||
super().__init__(coordinator)
|
||||
self.sensor_type = sensor
|
||||
self.ec_data = coordinator.ec_data
|
||||
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_name = name
|
||||
self._state = None
|
||||
self._attr_attribution = self.ec_data.metadata["attribution"]
|
||||
self._attr_name = f"{coordinator.config_entry.title} {label}"
|
||||
self._attr_unique_id = f"{self.ec_data.metadata['location']}-{sensor}"
|
||||
self._attr = None
|
||||
self._unit = None
|
||||
self._device_class = None
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self):
|
||||
"""Return the state attributes of the device."""
|
||||
|
@ -104,32 +104,31 @@ class ECSensor(SensorEntity):
|
|||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
return self._device_class
|
||||
|
||||
def update(self):
|
||||
@property
|
||||
def native_value(self):
|
||||
"""Update current conditions."""
|
||||
self.ec_data.update()
|
||||
self.ec_data.conditions.update(self.ec_data.alerts)
|
||||
|
||||
conditions = self.ec_data.conditions
|
||||
metadata = self.ec_data.metadata
|
||||
sensor_data = conditions.get(self.sensor_type)
|
||||
sensor_data = self.ec_data.conditions.get(self.sensor_type)
|
||||
if not sensor_data:
|
||||
sensor_data = self.ec_data.alerts.get(self.sensor_type)
|
||||
|
||||
self._attr = {}
|
||||
value = sensor_data.get("value")
|
||||
|
||||
if isinstance(value, list):
|
||||
self._state = " | ".join([str(s.get("title")) for s in value])[:255]
|
||||
state = " | ".join([str(s.get("title")) for s in value])[:255]
|
||||
self._attr.update(
|
||||
{ATTR_TIME: " | ".join([str(s.get("date")) for s in value])}
|
||||
)
|
||||
elif self.sensor_type == "tendency":
|
||||
self._state = str(value).capitalize()
|
||||
elif value is not None and len(value) > 255:
|
||||
self._state = value[:255]
|
||||
state = str(value).capitalize()
|
||||
elif isinstance(value, str) and len(value) > 255:
|
||||
state = value[:255]
|
||||
_LOGGER.info(
|
||||
"Value for %s truncated to 255 characters", self._attr_unique_id
|
||||
)
|
||||
else:
|
||||
self._state = value
|
||||
state = value
|
||||
|
||||
if sensor_data.get("unit") == "C" or self.sensor_type in (
|
||||
"wind_chill",
|
||||
|
@ -140,16 +139,11 @@ class ECSensor(SensorEntity):
|
|||
else:
|
||||
self._unit = sensor_data.get("unit")
|
||||
|
||||
if timestamp := metadata.get("timestamp"):
|
||||
updated_utc = datetime.strptime(timestamp, "%Y%m%d%H%M%S").isoformat()
|
||||
else:
|
||||
updated_utc = None
|
||||
|
||||
self._attr.update(
|
||||
{
|
||||
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||
ATTR_UPDATED: updated_utc,
|
||||
ATTR_OBSERVATION_TIME: metadata.get("timestamp"),
|
||||
ATTR_LOCATION: metadata.get("location"),
|
||||
ATTR_STATION: metadata.get("station"),
|
||||
}
|
||||
)
|
||||
return state
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Platform for retrieving meteorological data from Environment Canada."""
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import re
|
||||
|
||||
import voluptuous as vol
|
||||
|
@ -28,15 +29,14 @@ from homeassistant.components.weather import (
|
|||
)
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
from homeassistant.util import dt
|
||||
|
||||
from . import trigger_import
|
||||
from .const import CONF_ATTRIBUTION, CONF_STATION, DOMAIN
|
||||
from .const import CONF_STATION, DOMAIN
|
||||
|
||||
CONF_FORECAST = "forecast"
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def validate_station(station):
|
||||
"""Check that the station ID is well-formed."""
|
||||
|
@ -82,43 +82,25 @@ async def async_setup_platform(hass, config, async_add_entries, discovery_info=N
|
|||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Add a weather entity from a config_entry."""
|
||||
weather_data = hass.data[DOMAIN][config_entry.entry_id]["weather_data"]
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
ECWeather(
|
||||
weather_data,
|
||||
f"{config_entry.title}",
|
||||
config_entry.data,
|
||||
"daily",
|
||||
f"{config_entry.unique_id}-daily",
|
||||
),
|
||||
ECWeather(
|
||||
weather_data,
|
||||
f"{config_entry.title} Hourly",
|
||||
config_entry.data,
|
||||
"hourly",
|
||||
f"{config_entry.unique_id}-hourly",
|
||||
),
|
||||
]
|
||||
)
|
||||
coordinator = hass.data[DOMAIN][config_entry.entry_id]["weather_coordinator"]
|
||||
async_add_entities([ECWeather(coordinator, False), ECWeather(coordinator, True)])
|
||||
|
||||
|
||||
class ECWeather(WeatherEntity):
|
||||
class ECWeather(CoordinatorEntity, WeatherEntity):
|
||||
"""Representation of a weather condition."""
|
||||
|
||||
def __init__(self, ec_data, name, config, forecast_type, unique_id):
|
||||
def __init__(self, coordinator, hourly):
|
||||
"""Initialize Environment Canada weather."""
|
||||
self.ec_data = ec_data
|
||||
self.config = config
|
||||
self._attr_name = name
|
||||
self._attr_unique_id = unique_id
|
||||
self.forecast_type = forecast_type
|
||||
|
||||
@property
|
||||
def attribution(self):
|
||||
"""Return the attribution."""
|
||||
return CONF_ATTRIBUTION
|
||||
super().__init__(coordinator)
|
||||
self.ec_data = coordinator.ec_data
|
||||
self._attr_attribution = self.ec_data.metadata["attribution"]
|
||||
self._attr_name = (
|
||||
f"{coordinator.config_entry.title}{' Hourly' if hourly else ''}"
|
||||
)
|
||||
self._attr_unique_id = (
|
||||
f"{coordinator.config_entry.unique_id}{'-hourly' if hourly else '-daily'}"
|
||||
)
|
||||
self._hourly = hourly
|
||||
|
||||
@property
|
||||
def temperature(self):
|
||||
|
@ -190,18 +172,14 @@ class ECWeather(WeatherEntity):
|
|||
@property
|
||||
def forecast(self):
|
||||
"""Return the forecast array."""
|
||||
return get_forecast(self.ec_data, self.forecast_type)
|
||||
|
||||
def update(self):
|
||||
"""Get the latest data from Environment Canada."""
|
||||
self.ec_data.update()
|
||||
return get_forecast(self.ec_data, self._hourly)
|
||||
|
||||
|
||||
def get_forecast(ec_data, forecast_type):
|
||||
def get_forecast(ec_data, hourly):
|
||||
"""Build the forecast array."""
|
||||
forecast_array = []
|
||||
|
||||
if forecast_type == "daily":
|
||||
if not hourly:
|
||||
if not (half_days := ec_data.daily_forecasts):
|
||||
return None
|
||||
|
||||
|
@ -251,15 +229,11 @@ def get_forecast(ec_data, forecast_type):
|
|||
}
|
||||
)
|
||||
|
||||
elif forecast_type == "hourly":
|
||||
else:
|
||||
for hour in ec_data.hourly_forecasts:
|
||||
forecast_array.append(
|
||||
{
|
||||
ATTR_FORECAST_TIME: datetime.datetime.strptime(
|
||||
hour["period"], "%Y%m%d%H%M%S"
|
||||
)
|
||||
.replace(tzinfo=dt.UTC)
|
||||
.isoformat(),
|
||||
ATTR_FORECAST_TIME: hour["period"],
|
||||
ATTR_FORECAST_TEMP: int(hour["temperature"]),
|
||||
ATTR_FORECAST_CONDITION: icon_code_to_condition(
|
||||
int(hour["icon_code"])
|
||||
|
|
|
@ -597,7 +597,7 @@ enocean==0.50
|
|||
enturclient==0.2.2
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env_canada==0.2.7
|
||||
env_canada==0.5.14
|
||||
|
||||
# homeassistant.components.envirophat
|
||||
# envirophat==0.0.6
|
||||
|
|
|
@ -366,7 +366,7 @@ emulated_roku==0.2.1
|
|||
enocean==0.50
|
||||
|
||||
# homeassistant.components.environment_canada
|
||||
env_canada==0.2.7
|
||||
env_canada==0.5.14
|
||||
|
||||
# homeassistant.components.enphase_envoy
|
||||
envoy_reader==0.20.0
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
"""Test the Environment Canada (EC) config flow."""
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
import xml.etree.ElementTree as et
|
||||
|
||||
import aiohttp
|
||||
|
@ -44,10 +44,10 @@ def mocked_ec(
|
|||
if update:
|
||||
ec_mock.update = update
|
||||
else:
|
||||
ec_mock.update = Mock()
|
||||
ec_mock.update = AsyncMock()
|
||||
|
||||
return patch(
|
||||
"homeassistant.components.environment_canada.config_flow.ECData",
|
||||
"homeassistant.components.environment_canada.config_flow.ECWeather",
|
||||
return_value=ec_mock,
|
||||
)
|
||||
|
||||
|
@ -94,24 +94,6 @@ async def test_create_same_entry_twice(hass):
|
|||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_too_many_attempts(hass):
|
||||
"""Test hitting rate limit."""
|
||||
with mocked_ec(metadata={}), patch(
|
||||
"homeassistant.components.environment_canada.async_setup_entry",
|
||||
return_value=True,
|
||||
):
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
flow["flow_id"],
|
||||
{},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result["errors"] == {"base": "too_many_attempts"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"error",
|
||||
[
|
||||
|
@ -126,7 +108,7 @@ async def test_exception_handling(hass, error):
|
|||
"""Test exception handling."""
|
||||
exc, base_error = error
|
||||
with patch(
|
||||
"homeassistant.components.environment_canada.config_flow.ECData",
|
||||
"homeassistant.components.environment_canada.config_flow.ECWeather",
|
||||
side_effect=exc,
|
||||
):
|
||||
flow = await hass.config_entries.flow.async_init(
|
||||
|
|
Loading…
Reference in New Issue