mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Remove Darksky integration (#90322)
This commit is contained in:
parent
d539bddabc
commit
724eb7f2bd
@ -228,8 +228,6 @@ build.json @home-assistant/supervisor
|
|||||||
/homeassistant/components/cups/ @fabaff
|
/homeassistant/components/cups/ @fabaff
|
||||||
/homeassistant/components/daikin/ @fredrike
|
/homeassistant/components/daikin/ @fredrike
|
||||||
/tests/components/daikin/ @fredrike
|
/tests/components/daikin/ @fredrike
|
||||||
/homeassistant/components/darksky/ @fabaff
|
|
||||||
/tests/components/darksky/ @fabaff
|
|
||||||
/homeassistant/components/debugpy/ @frenck
|
/homeassistant/components/debugpy/ @frenck
|
||||||
/tests/components/debugpy/ @frenck
|
/tests/components/debugpy/ @frenck
|
||||||
/homeassistant/components/deconz/ @Kane610
|
/homeassistant/components/deconz/ @Kane610
|
||||||
|
@ -1 +0,0 @@
|
|||||||
"""The darksky component."""
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"domain": "darksky",
|
|
||||||
"name": "Dark Sky",
|
|
||||||
"codeowners": ["@fabaff"],
|
|
||||||
"documentation": "https://www.home-assistant.io/integrations/darksky",
|
|
||||||
"iot_class": "cloud_polling",
|
|
||||||
"loggers": ["forecastio"],
|
|
||||||
"requirements": ["python-forecastio==1.4.0"]
|
|
||||||
}
|
|
@ -1,927 +0,0 @@
|
|||||||
"""Support for Dark Sky weather service."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
|
||||||
from typing import Literal, NamedTuple
|
|
||||||
|
|
||||||
import forecastio
|
|
||||||
from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
|
||||||
PLATFORM_SCHEMA,
|
|
||||||
SensorDeviceClass,
|
|
||||||
SensorEntity,
|
|
||||||
SensorEntityDescription,
|
|
||||||
SensorStateClass,
|
|
||||||
)
|
|
||||||
from homeassistant.const import (
|
|
||||||
CONF_API_KEY,
|
|
||||||
CONF_LATITUDE,
|
|
||||||
CONF_LONGITUDE,
|
|
||||||
CONF_MONITORED_CONDITIONS,
|
|
||||||
CONF_NAME,
|
|
||||||
CONF_SCAN_INTERVAL,
|
|
||||||
DEGREE,
|
|
||||||
PERCENTAGE,
|
|
||||||
UV_INDEX,
|
|
||||||
UnitOfLength,
|
|
||||||
UnitOfPrecipitationDepth,
|
|
||||||
UnitOfPressure,
|
|
||||||
UnitOfSpeed,
|
|
||||||
UnitOfTemperature,
|
|
||||||
UnitOfVolumetricFlux,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
from homeassistant.util import Throttle
|
|
||||||
from homeassistant.util.unit_system import METRIC_SYSTEM
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
CONF_FORECAST = "forecast"
|
|
||||||
CONF_HOURLY_FORECAST = "hourly_forecast"
|
|
||||||
CONF_LANGUAGE = "language"
|
|
||||||
CONF_UNITS = "units"
|
|
||||||
|
|
||||||
DEFAULT_LANGUAGE = "en"
|
|
||||||
DEFAULT_NAME = "Dark Sky"
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=300)
|
|
||||||
|
|
||||||
DEPRECATED_SENSOR_TYPES = {
|
|
||||||
"apparent_temperature_max",
|
|
||||||
"apparent_temperature_min",
|
|
||||||
"temperature_max",
|
|
||||||
"temperature_min",
|
|
||||||
}
|
|
||||||
|
|
||||||
MAP_UNIT_SYSTEM: dict[
|
|
||||||
Literal["si", "us", "ca", "uk", "uk2"],
|
|
||||||
Literal["si_unit", "us_unit", "ca_unit", "uk_unit", "uk2_unit"],
|
|
||||||
] = {
|
|
||||||
"si": "si_unit",
|
|
||||||
"us": "us_unit",
|
|
||||||
"ca": "ca_unit",
|
|
||||||
"uk": "uk_unit",
|
|
||||||
"uk2": "uk2_unit",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DarkskySensorEntityDescription(SensorEntityDescription):
|
|
||||||
"""Describes Darksky sensor entity."""
|
|
||||||
|
|
||||||
si_unit: str | None = None
|
|
||||||
us_unit: str | None = None
|
|
||||||
ca_unit: str | None = None
|
|
||||||
uk_unit: str | None = None
|
|
||||||
uk2_unit: str | None = None
|
|
||||||
forecast_mode: list[str] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
SENSOR_TYPES: dict[str, DarkskySensorEntityDescription] = {
|
|
||||||
"summary": DarkskySensorEntityDescription(
|
|
||||||
key="summary",
|
|
||||||
name="Summary",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"minutely_summary": DarkskySensorEntityDescription(
|
|
||||||
key="minutely_summary",
|
|
||||||
name="Minutely Summary",
|
|
||||||
forecast_mode=[],
|
|
||||||
),
|
|
||||||
"hourly_summary": DarkskySensorEntityDescription(
|
|
||||||
key="hourly_summary",
|
|
||||||
name="Hourly Summary",
|
|
||||||
forecast_mode=[],
|
|
||||||
),
|
|
||||||
"daily_summary": DarkskySensorEntityDescription(
|
|
||||||
key="daily_summary",
|
|
||||||
name="Daily Summary",
|
|
||||||
forecast_mode=[],
|
|
||||||
),
|
|
||||||
"icon": DarkskySensorEntityDescription(
|
|
||||||
key="icon",
|
|
||||||
name="Icon",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"nearest_storm_distance": DarkskySensorEntityDescription(
|
|
||||||
key="nearest_storm_distance",
|
|
||||||
name="Nearest Storm Distance",
|
|
||||||
si_unit=UnitOfLength.KILOMETERS,
|
|
||||||
us_unit=UnitOfLength.MILES,
|
|
||||||
ca_unit=UnitOfLength.KILOMETERS,
|
|
||||||
uk_unit=UnitOfLength.KILOMETERS,
|
|
||||||
uk2_unit=UnitOfLength.MILES,
|
|
||||||
icon="mdi:weather-lightning",
|
|
||||||
forecast_mode=["currently"],
|
|
||||||
),
|
|
||||||
"nearest_storm_bearing": DarkskySensorEntityDescription(
|
|
||||||
key="nearest_storm_bearing",
|
|
||||||
name="Nearest Storm Bearing",
|
|
||||||
si_unit=DEGREE,
|
|
||||||
us_unit=DEGREE,
|
|
||||||
ca_unit=DEGREE,
|
|
||||||
uk_unit=DEGREE,
|
|
||||||
uk2_unit=DEGREE,
|
|
||||||
icon="mdi:weather-lightning",
|
|
||||||
forecast_mode=["currently"],
|
|
||||||
),
|
|
||||||
"precip_type": DarkskySensorEntityDescription(
|
|
||||||
key="precip_type",
|
|
||||||
name="Precip",
|
|
||||||
icon="mdi:weather-pouring",
|
|
||||||
forecast_mode=["currently", "minutely", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"precip_intensity": DarkskySensorEntityDescription(
|
|
||||||
key="precip_intensity",
|
|
||||||
name="Precip Intensity",
|
|
||||||
si_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
us_unit=UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
|
||||||
ca_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
uk_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
uk2_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
icon="mdi:weather-rainy",
|
|
||||||
forecast_mode=["currently", "minutely", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"precip_probability": DarkskySensorEntityDescription(
|
|
||||||
key="precip_probability",
|
|
||||||
name="Precip Probability",
|
|
||||||
si_unit=PERCENTAGE,
|
|
||||||
us_unit=PERCENTAGE,
|
|
||||||
ca_unit=PERCENTAGE,
|
|
||||||
uk_unit=PERCENTAGE,
|
|
||||||
uk2_unit=PERCENTAGE,
|
|
||||||
icon="mdi:water-percent",
|
|
||||||
forecast_mode=["currently", "minutely", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"precip_accumulation": DarkskySensorEntityDescription(
|
|
||||||
key="precip_accumulation",
|
|
||||||
name="Precip Accumulation",
|
|
||||||
device_class=SensorDeviceClass.PRECIPITATION,
|
|
||||||
si_unit=UnitOfPrecipitationDepth.CENTIMETERS,
|
|
||||||
us_unit=UnitOfPrecipitationDepth.INCHES,
|
|
||||||
ca_unit=UnitOfPrecipitationDepth.CENTIMETERS,
|
|
||||||
uk_unit=UnitOfPrecipitationDepth.CENTIMETERS,
|
|
||||||
uk2_unit=UnitOfPrecipitationDepth.CENTIMETERS,
|
|
||||||
icon="mdi:weather-snowy",
|
|
||||||
forecast_mode=["hourly", "daily"],
|
|
||||||
),
|
|
||||||
"temperature": DarkskySensorEntityDescription(
|
|
||||||
key="temperature",
|
|
||||||
name="Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["currently", "hourly"],
|
|
||||||
),
|
|
||||||
"apparent_temperature": DarkskySensorEntityDescription(
|
|
||||||
key="apparent_temperature",
|
|
||||||
name="Apparent Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["currently", "hourly"],
|
|
||||||
),
|
|
||||||
"dew_point": DarkskySensorEntityDescription(
|
|
||||||
key="dew_point",
|
|
||||||
name="Dew Point",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"wind_speed": DarkskySensorEntityDescription(
|
|
||||||
key="wind_speed",
|
|
||||||
name="Wind Speed",
|
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
|
||||||
si_unit=UnitOfSpeed.METERS_PER_SECOND,
|
|
||||||
us_unit=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
ca_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
|
||||||
uk_unit=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
uk2_unit=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"wind_bearing": DarkskySensorEntityDescription(
|
|
||||||
key="wind_bearing",
|
|
||||||
name="Wind Bearing",
|
|
||||||
si_unit=DEGREE,
|
|
||||||
us_unit=DEGREE,
|
|
||||||
ca_unit=DEGREE,
|
|
||||||
uk_unit=DEGREE,
|
|
||||||
uk2_unit=DEGREE,
|
|
||||||
icon="mdi:compass",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"wind_gust": DarkskySensorEntityDescription(
|
|
||||||
key="wind_gust",
|
|
||||||
name="Wind Gust",
|
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
|
||||||
si_unit=UnitOfSpeed.METERS_PER_SECOND,
|
|
||||||
us_unit=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
ca_unit=UnitOfSpeed.KILOMETERS_PER_HOUR,
|
|
||||||
uk_unit=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
uk2_unit=UnitOfSpeed.MILES_PER_HOUR,
|
|
||||||
icon="mdi:weather-windy-variant",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"cloud_cover": DarkskySensorEntityDescription(
|
|
||||||
key="cloud_cover",
|
|
||||||
name="Cloud Coverage",
|
|
||||||
si_unit=PERCENTAGE,
|
|
||||||
us_unit=PERCENTAGE,
|
|
||||||
ca_unit=PERCENTAGE,
|
|
||||||
uk_unit=PERCENTAGE,
|
|
||||||
uk2_unit=PERCENTAGE,
|
|
||||||
icon="mdi:weather-partly-cloudy",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"humidity": DarkskySensorEntityDescription(
|
|
||||||
key="humidity",
|
|
||||||
name="Humidity",
|
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
|
||||||
si_unit=PERCENTAGE,
|
|
||||||
us_unit=PERCENTAGE,
|
|
||||||
ca_unit=PERCENTAGE,
|
|
||||||
uk_unit=PERCENTAGE,
|
|
||||||
uk2_unit=PERCENTAGE,
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"pressure": DarkskySensorEntityDescription(
|
|
||||||
key="pressure",
|
|
||||||
name="Pressure",
|
|
||||||
device_class=SensorDeviceClass.PRESSURE,
|
|
||||||
si_unit=UnitOfPressure.MBAR,
|
|
||||||
us_unit=UnitOfPressure.MBAR,
|
|
||||||
ca_unit=UnitOfPressure.MBAR,
|
|
||||||
uk_unit=UnitOfPressure.MBAR,
|
|
||||||
uk2_unit=UnitOfPressure.MBAR,
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"visibility": DarkskySensorEntityDescription(
|
|
||||||
key="visibility",
|
|
||||||
name="Visibility",
|
|
||||||
si_unit=UnitOfLength.KILOMETERS,
|
|
||||||
us_unit=UnitOfLength.MILES,
|
|
||||||
ca_unit=UnitOfLength.KILOMETERS,
|
|
||||||
uk_unit=UnitOfLength.KILOMETERS,
|
|
||||||
uk2_unit=UnitOfLength.MILES,
|
|
||||||
icon="mdi:eye",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"ozone": DarkskySensorEntityDescription(
|
|
||||||
key="ozone",
|
|
||||||
name="Ozone",
|
|
||||||
device_class=SensorDeviceClass.OZONE,
|
|
||||||
si_unit="DU",
|
|
||||||
us_unit="DU",
|
|
||||||
ca_unit="DU",
|
|
||||||
uk_unit="DU",
|
|
||||||
uk2_unit="DU",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"apparent_temperature_max": DarkskySensorEntityDescription(
|
|
||||||
key="apparent_temperature_max",
|
|
||||||
name="Daily High Apparent Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"apparent_temperature_high": DarkskySensorEntityDescription(
|
|
||||||
key="apparent_temperature_high",
|
|
||||||
name="Daytime High Apparent Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"apparent_temperature_min": DarkskySensorEntityDescription(
|
|
||||||
key="apparent_temperature_min",
|
|
||||||
name="Daily Low Apparent Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"apparent_temperature_low": DarkskySensorEntityDescription(
|
|
||||||
key="apparent_temperature_low",
|
|
||||||
name="Overnight Low Apparent Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"temperature_max": DarkskySensorEntityDescription(
|
|
||||||
key="temperature_max",
|
|
||||||
name="Daily High Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"temperature_high": DarkskySensorEntityDescription(
|
|
||||||
key="temperature_high",
|
|
||||||
name="Daytime High Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"temperature_min": DarkskySensorEntityDescription(
|
|
||||||
key="temperature_min",
|
|
||||||
name="Daily Low Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"temperature_low": DarkskySensorEntityDescription(
|
|
||||||
key="temperature_low",
|
|
||||||
name="Overnight Low Temperature",
|
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
|
||||||
si_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
us_unit=UnitOfTemperature.FAHRENHEIT,
|
|
||||||
ca_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
uk2_unit=UnitOfTemperature.CELSIUS,
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"precip_intensity_max": DarkskySensorEntityDescription(
|
|
||||||
key="precip_intensity_max",
|
|
||||||
name="Daily Max Precip Intensity",
|
|
||||||
si_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
us_unit=UnitOfVolumetricFlux.INCHES_PER_HOUR,
|
|
||||||
ca_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
uk_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
uk2_unit=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
|
||||||
icon="mdi:thermometer",
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"uv_index": DarkskySensorEntityDescription(
|
|
||||||
key="uv_index",
|
|
||||||
name="UV Index",
|
|
||||||
si_unit=UV_INDEX,
|
|
||||||
us_unit=UV_INDEX,
|
|
||||||
ca_unit=UV_INDEX,
|
|
||||||
uk_unit=UV_INDEX,
|
|
||||||
uk2_unit=UV_INDEX,
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
forecast_mode=["currently", "hourly", "daily"],
|
|
||||||
),
|
|
||||||
"moon_phase": DarkskySensorEntityDescription(
|
|
||||||
key="moon_phase",
|
|
||||||
name="Moon Phase",
|
|
||||||
icon="mdi:weather-night",
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"sunrise_time": DarkskySensorEntityDescription(
|
|
||||||
key="sunrise_time",
|
|
||||||
name="Sunrise",
|
|
||||||
icon="mdi:white-balance-sunny",
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"sunset_time": DarkskySensorEntityDescription(
|
|
||||||
key="sunset_time",
|
|
||||||
name="Sunset",
|
|
||||||
icon="mdi:weather-night",
|
|
||||||
forecast_mode=["daily"],
|
|
||||||
),
|
|
||||||
"alerts": DarkskySensorEntityDescription(
|
|
||||||
key="alerts",
|
|
||||||
name="Alerts",
|
|
||||||
icon="mdi:alert-circle-outline",
|
|
||||||
forecast_mode=[],
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ConditionPicture(NamedTuple):
|
|
||||||
"""Entity picture and icon for condition."""
|
|
||||||
|
|
||||||
entity_picture: str
|
|
||||||
icon: str
|
|
||||||
|
|
||||||
|
|
||||||
CONDITION_PICTURES: dict[str, ConditionPicture] = {
|
|
||||||
"clear-day": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-sunny.svg",
|
|
||||||
icon="mdi:weather-sunny",
|
|
||||||
),
|
|
||||||
"clear-night": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-night.svg",
|
|
||||||
icon="mdi:weather-night",
|
|
||||||
),
|
|
||||||
"rain": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-pouring.svg",
|
|
||||||
icon="mdi:weather-pouring",
|
|
||||||
),
|
|
||||||
"snow": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-snowy.svg",
|
|
||||||
icon="mdi:weather-snowy",
|
|
||||||
),
|
|
||||||
"sleet": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-hail.svg",
|
|
||||||
icon="mdi:weather-snowy-rainy",
|
|
||||||
),
|
|
||||||
"wind": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-windy.svg",
|
|
||||||
icon="mdi:weather-windy",
|
|
||||||
),
|
|
||||||
"fog": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-fog.svg",
|
|
||||||
icon="mdi:weather-fog",
|
|
||||||
),
|
|
||||||
"cloudy": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-cloudy.svg",
|
|
||||||
icon="mdi:weather-cloudy",
|
|
||||||
),
|
|
||||||
"partly-cloudy-day": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-partlycloudy.svg",
|
|
||||||
icon="mdi:weather-partly-cloudy",
|
|
||||||
),
|
|
||||||
"partly-cloudy-night": ConditionPicture(
|
|
||||||
entity_picture="/static/images/darksky/weather-cloudy.svg",
|
|
||||||
icon="mdi:weather-night-partly-cloudy",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Language Supported Codes
|
|
||||||
LANGUAGE_CODES = [
|
|
||||||
"ar",
|
|
||||||
"az",
|
|
||||||
"be",
|
|
||||||
"bg",
|
|
||||||
"bn",
|
|
||||||
"bs",
|
|
||||||
"ca",
|
|
||||||
"cs",
|
|
||||||
"da",
|
|
||||||
"de",
|
|
||||||
"el",
|
|
||||||
"en",
|
|
||||||
"ja",
|
|
||||||
"ka",
|
|
||||||
"kn",
|
|
||||||
"ko",
|
|
||||||
"eo",
|
|
||||||
"es",
|
|
||||||
"et",
|
|
||||||
"fi",
|
|
||||||
"fr",
|
|
||||||
"he",
|
|
||||||
"hi",
|
|
||||||
"hr",
|
|
||||||
"hu",
|
|
||||||
"id",
|
|
||||||
"is",
|
|
||||||
"it",
|
|
||||||
"kw",
|
|
||||||
"lv",
|
|
||||||
"ml",
|
|
||||||
"mr",
|
|
||||||
"nb",
|
|
||||||
"nl",
|
|
||||||
"pa",
|
|
||||||
"pl",
|
|
||||||
"pt",
|
|
||||||
"ro",
|
|
||||||
"ru",
|
|
||||||
"sk",
|
|
||||||
"sl",
|
|
||||||
"sr",
|
|
||||||
"sv",
|
|
||||||
"ta",
|
|
||||||
"te",
|
|
||||||
"tet",
|
|
||||||
"tr",
|
|
||||||
"uk",
|
|
||||||
"ur",
|
|
||||||
"x-pig-latin",
|
|
||||||
"zh",
|
|
||||||
"zh-tw",
|
|
||||||
]
|
|
||||||
|
|
||||||
ALLOWED_UNITS = ["auto", "si", "us", "ca", "uk", "uk2"]
|
|
||||||
|
|
||||||
ALERTS_ATTRS = ["time", "description", "expires", "severity", "uri", "regions", "title"]
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_MONITORED_CONDITIONS): vol.All(
|
|
||||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
|
||||||
),
|
|
||||||
vol.Required(CONF_API_KEY): cv.string,
|
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
||||||
vol.Optional(CONF_UNITS): vol.In(ALLOWED_UNITS),
|
|
||||||
vol.Optional(CONF_LANGUAGE, default=DEFAULT_LANGUAGE): vol.In(LANGUAGE_CODES),
|
|
||||||
vol.Inclusive(
|
|
||||||
CONF_LATITUDE, "coordinates", "Latitude and longitude must exist together"
|
|
||||||
): cv.latitude,
|
|
||||||
vol.Inclusive(
|
|
||||||
CONF_LONGITUDE, "coordinates", "Latitude and longitude must exist together"
|
|
||||||
): cv.longitude,
|
|
||||||
vol.Optional(CONF_FORECAST): vol.All(cv.ensure_list, [vol.Range(min=0, max=7)]),
|
|
||||||
vol.Optional(CONF_HOURLY_FORECAST): vol.All(
|
|
||||||
cv.ensure_list, [vol.Range(min=0, max=48)]
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config: ConfigType,
|
|
||||||
add_entities: AddEntitiesCallback,
|
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Set up the Dark Sky sensor."""
|
|
||||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
|
||||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
|
||||||
language = config.get(CONF_LANGUAGE)
|
|
||||||
interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
|
|
||||||
|
|
||||||
if CONF_UNITS in config:
|
|
||||||
units = config[CONF_UNITS]
|
|
||||||
elif hass.config.units is METRIC_SYSTEM:
|
|
||||||
units = "si"
|
|
||||||
else:
|
|
||||||
units = "us"
|
|
||||||
|
|
||||||
forecast_data = DarkSkyData(
|
|
||||||
api_key=config.get(CONF_API_KEY),
|
|
||||||
latitude=latitude,
|
|
||||||
longitude=longitude,
|
|
||||||
units=units,
|
|
||||||
language=language,
|
|
||||||
interval=interval,
|
|
||||||
)
|
|
||||||
forecast_data.update()
|
|
||||||
forecast_data.update_currently()
|
|
||||||
|
|
||||||
# If connection failed don't setup platform.
|
|
||||||
if forecast_data.data is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
name = config.get(CONF_NAME)
|
|
||||||
|
|
||||||
forecast = config.get(CONF_FORECAST)
|
|
||||||
forecast_hour = config.get(CONF_HOURLY_FORECAST)
|
|
||||||
sensors: list[SensorEntity] = []
|
|
||||||
for variable in config[CONF_MONITORED_CONDITIONS]:
|
|
||||||
if variable in DEPRECATED_SENSOR_TYPES:
|
|
||||||
_LOGGER.warning("Monitored condition %s is deprecated", variable)
|
|
||||||
description = SENSOR_TYPES[variable]
|
|
||||||
if not description.forecast_mode or "currently" in description.forecast_mode:
|
|
||||||
if variable == "alerts":
|
|
||||||
sensors.append(DarkSkyAlertSensor(forecast_data, description, name))
|
|
||||||
else:
|
|
||||||
sensors.append(DarkSkySensor(forecast_data, description, name))
|
|
||||||
|
|
||||||
if forecast is not None and "daily" in description.forecast_mode:
|
|
||||||
sensors.extend(
|
|
||||||
[
|
|
||||||
DarkSkySensor(
|
|
||||||
forecast_data, description, name, forecast_day=forecast_day
|
|
||||||
)
|
|
||||||
for forecast_day in forecast
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if forecast_hour is not None and "hourly" in description.forecast_mode:
|
|
||||||
sensors.extend(
|
|
||||||
[
|
|
||||||
DarkSkySensor(
|
|
||||||
forecast_data, description, name, forecast_hour=forecast_h
|
|
||||||
)
|
|
||||||
for forecast_h in forecast_hour
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
add_entities(sensors, True)
|
|
||||||
|
|
||||||
|
|
||||||
class DarkSkySensor(SensorEntity):
|
|
||||||
"""Implementation of a Dark Sky sensor."""
|
|
||||||
|
|
||||||
_attr_attribution = "Powered by Dark Sky"
|
|
||||||
entity_description: DarkskySensorEntityDescription
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
forecast_data,
|
|
||||||
description: DarkskySensorEntityDescription,
|
|
||||||
name,
|
|
||||||
forecast_day=None,
|
|
||||||
forecast_hour=None,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the sensor."""
|
|
||||||
self.entity_description = description
|
|
||||||
self.forecast_data = forecast_data
|
|
||||||
self.forecast_day = forecast_day
|
|
||||||
self.forecast_hour = forecast_hour
|
|
||||||
self._icon: str | None = None
|
|
||||||
|
|
||||||
if forecast_day is not None:
|
|
||||||
self._attr_name = f"{name} {description.name} {forecast_day}d"
|
|
||||||
elif forecast_hour is not None:
|
|
||||||
self._attr_name = f"{name} {description.name} {forecast_hour}h"
|
|
||||||
else:
|
|
||||||
self._attr_name = f"{name} {description.name}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unit_system(self):
|
|
||||||
"""Return the unit system of this entity."""
|
|
||||||
return self.forecast_data.unit_system
|
|
||||||
|
|
||||||
@property
|
|
||||||
def entity_picture(self) -> str | None:
|
|
||||||
"""Return the entity picture to use in the frontend, if any."""
|
|
||||||
if self._icon is None or "summary" not in self.entity_description.key:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self._icon in CONDITION_PICTURES:
|
|
||||||
return CONDITION_PICTURES[self._icon].entity_picture
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def update_unit_of_measurement(self) -> None:
|
|
||||||
"""Update units based on unit system."""
|
|
||||||
unit_key = MAP_UNIT_SYSTEM.get(self.unit_system, "si_unit")
|
|
||||||
self._attr_native_unit_of_measurement = getattr(
|
|
||||||
self.entity_description, unit_key
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self) -> str | None:
|
|
||||||
"""Icon to use in the frontend, if any."""
|
|
||||||
if (
|
|
||||||
"summary" in self.entity_description.key
|
|
||||||
and self._icon in CONDITION_PICTURES
|
|
||||||
):
|
|
||||||
return CONDITION_PICTURES[self._icon].icon
|
|
||||||
|
|
||||||
return self.entity_description.icon
|
|
||||||
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Get the latest data from Dark Sky and updates the states."""
|
|
||||||
# Call the API for new forecast data. Each sensor will re-trigger this
|
|
||||||
# same exact call, but that's fine. We cache results for a short period
|
|
||||||
# of time to prevent hitting API limits. Note that Dark Sky will
|
|
||||||
# charge users for too many calls in 1 day, so take care when updating.
|
|
||||||
self.forecast_data.update()
|
|
||||||
self.update_unit_of_measurement()
|
|
||||||
|
|
||||||
sensor_type = self.entity_description.key
|
|
||||||
if sensor_type == "minutely_summary":
|
|
||||||
self.forecast_data.update_minutely()
|
|
||||||
minutely = self.forecast_data.data_minutely
|
|
||||||
self._attr_native_value = getattr(minutely, "summary", "")
|
|
||||||
self._icon = getattr(minutely, "icon", "")
|
|
||||||
elif sensor_type == "hourly_summary":
|
|
||||||
self.forecast_data.update_hourly()
|
|
||||||
hourly = self.forecast_data.data_hourly
|
|
||||||
self._attr_native_value = getattr(hourly, "summary", "")
|
|
||||||
self._icon = getattr(hourly, "icon", "")
|
|
||||||
elif self.forecast_hour is not None:
|
|
||||||
self.forecast_data.update_hourly()
|
|
||||||
hourly = self.forecast_data.data_hourly
|
|
||||||
if hasattr(hourly, "data"):
|
|
||||||
self._attr_native_value = self.get_state(
|
|
||||||
hourly.data[self.forecast_hour]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._attr_native_value = 0
|
|
||||||
elif sensor_type == "daily_summary":
|
|
||||||
self.forecast_data.update_daily()
|
|
||||||
daily = self.forecast_data.data_daily
|
|
||||||
self._attr_native_value = getattr(daily, "summary", "")
|
|
||||||
self._icon = getattr(daily, "icon", "")
|
|
||||||
elif self.forecast_day is not None:
|
|
||||||
self.forecast_data.update_daily()
|
|
||||||
daily = self.forecast_data.data_daily
|
|
||||||
if hasattr(daily, "data"):
|
|
||||||
self._attr_native_value = self.get_state(daily.data[self.forecast_day])
|
|
||||||
else:
|
|
||||||
self._attr_native_value = 0
|
|
||||||
else:
|
|
||||||
self.forecast_data.update_currently()
|
|
||||||
currently = self.forecast_data.data_currently
|
|
||||||
self._attr_native_value = self.get_state(currently)
|
|
||||||
|
|
||||||
def get_state(self, data):
|
|
||||||
"""Return a new state based on the type.
|
|
||||||
|
|
||||||
If the sensor type is unknown, the current state is returned.
|
|
||||||
"""
|
|
||||||
sensor_type = self.entity_description.key
|
|
||||||
lookup_type = convert_to_camel(sensor_type)
|
|
||||||
|
|
||||||
if (state := getattr(data, lookup_type, None)) is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if "summary" in sensor_type:
|
|
||||||
self._icon = getattr(data, "icon", "")
|
|
||||||
|
|
||||||
# Some state data needs to be rounded to whole values or converted to
|
|
||||||
# percentages
|
|
||||||
if sensor_type in {"precip_probability", "cloud_cover", "humidity"}:
|
|
||||||
return round(state * 100, 1)
|
|
||||||
|
|
||||||
if sensor_type in {
|
|
||||||
"dew_point",
|
|
||||||
"temperature",
|
|
||||||
"apparent_temperature",
|
|
||||||
"temperature_low",
|
|
||||||
"apparent_temperature_low",
|
|
||||||
"temperature_min",
|
|
||||||
"apparent_temperature_min",
|
|
||||||
"temperature_high",
|
|
||||||
"apparent_temperature_high",
|
|
||||||
"temperature_max",
|
|
||||||
"apparent_temperature_max",
|
|
||||||
"precip_accumulation",
|
|
||||||
"pressure",
|
|
||||||
"ozone",
|
|
||||||
"uvIndex",
|
|
||||||
}:
|
|
||||||
return round(state, 1)
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
class DarkSkyAlertSensor(SensorEntity):
|
|
||||||
"""Implementation of a Dark Sky sensor."""
|
|
||||||
|
|
||||||
entity_description: DarkskySensorEntityDescription
|
|
||||||
_attr_native_value: int | None
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, forecast_data, description: DarkskySensorEntityDescription, name
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the sensor."""
|
|
||||||
self.entity_description = description
|
|
||||||
self.forecast_data = forecast_data
|
|
||||||
self._alerts = None
|
|
||||||
|
|
||||||
self._attr_name = f"{name} {description.name}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self):
|
|
||||||
"""Icon to use in the frontend, if any."""
|
|
||||||
if self._attr_native_value is not None and self._attr_native_value > 0:
|
|
||||||
return "mdi:alert-circle"
|
|
||||||
return "mdi:alert-circle-outline"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self):
|
|
||||||
"""Return the state attributes."""
|
|
||||||
return self._alerts
|
|
||||||
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Get the latest data from Dark Sky and updates the states."""
|
|
||||||
# Call the API for new forecast data. Each sensor will re-trigger this
|
|
||||||
# same exact call, but that's fine. We cache results for a short period
|
|
||||||
# of time to prevent hitting API limits. Note that Dark Sky will
|
|
||||||
# charge users for too many calls in 1 day, so take care when updating.
|
|
||||||
self.forecast_data.update()
|
|
||||||
self.forecast_data.update_alerts()
|
|
||||||
alerts = self.forecast_data.data_alerts
|
|
||||||
self._attr_native_value = self.get_state(alerts)
|
|
||||||
|
|
||||||
def get_state(self, data):
|
|
||||||
"""Return a new state based on the type.
|
|
||||||
|
|
||||||
If the sensor type is unknown, the current state is returned.
|
|
||||||
"""
|
|
||||||
alerts = {}
|
|
||||||
if data is None:
|
|
||||||
self._alerts = alerts
|
|
||||||
return data
|
|
||||||
|
|
||||||
multiple_alerts = len(data) > 1
|
|
||||||
for i, alert in enumerate(data):
|
|
||||||
for attr in ALERTS_ATTRS:
|
|
||||||
if multiple_alerts:
|
|
||||||
dkey = f"{attr}_{i!s}"
|
|
||||||
else:
|
|
||||||
dkey = attr
|
|
||||||
alerts[dkey] = getattr(alert, attr)
|
|
||||||
self._alerts = alerts
|
|
||||||
|
|
||||||
return len(data)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_to_camel(data):
|
|
||||||
"""Convert snake case (foo_bar_bat) to camel case (fooBarBat).
|
|
||||||
|
|
||||||
This is not pythonic, but needed for certain situations.
|
|
||||||
"""
|
|
||||||
components = data.split("_")
|
|
||||||
capital_components = "".join(x.title() for x in components[1:])
|
|
||||||
return f"{components[0]}{capital_components}"
|
|
||||||
|
|
||||||
|
|
||||||
class DarkSkyData:
|
|
||||||
"""Get the latest data from Darksky."""
|
|
||||||
|
|
||||||
def __init__(self, api_key, latitude, longitude, units, language, interval):
|
|
||||||
"""Initialize the data object."""
|
|
||||||
self._api_key = api_key
|
|
||||||
self.latitude = latitude
|
|
||||||
self.longitude = longitude
|
|
||||||
self.units = units
|
|
||||||
self.language = language
|
|
||||||
self._connect_error = False
|
|
||||||
|
|
||||||
self.data = None
|
|
||||||
self.unit_system = None
|
|
||||||
self.data_currently = None
|
|
||||||
self.data_minutely = None
|
|
||||||
self.data_hourly = None
|
|
||||||
self.data_daily = None
|
|
||||||
self.data_alerts = None
|
|
||||||
|
|
||||||
# Apply throttling to methods using configured interval
|
|
||||||
self.update = Throttle(interval)(self._update)
|
|
||||||
self.update_currently = Throttle(interval)(self._update_currently)
|
|
||||||
self.update_minutely = Throttle(interval)(self._update_minutely)
|
|
||||||
self.update_hourly = Throttle(interval)(self._update_hourly)
|
|
||||||
self.update_daily = Throttle(interval)(self._update_daily)
|
|
||||||
self.update_alerts = Throttle(interval)(self._update_alerts)
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
"""Get the latest data from Dark Sky."""
|
|
||||||
try:
|
|
||||||
self.data = forecastio.load_forecast(
|
|
||||||
self._api_key,
|
|
||||||
self.latitude,
|
|
||||||
self.longitude,
|
|
||||||
units=self.units,
|
|
||||||
lang=self.language,
|
|
||||||
)
|
|
||||||
if self._connect_error:
|
|
||||||
self._connect_error = False
|
|
||||||
_LOGGER.info("Reconnected to Dark Sky")
|
|
||||||
except (ConnectError, HTTPError, Timeout, ValueError) as error:
|
|
||||||
if not self._connect_error:
|
|
||||||
self._connect_error = True
|
|
||||||
_LOGGER.error("Unable to connect to Dark Sky: %s", error)
|
|
||||||
self.data = None
|
|
||||||
self.unit_system = self.data and self.data.json["flags"]["units"]
|
|
||||||
|
|
||||||
def _update_currently(self):
|
|
||||||
"""Update currently data."""
|
|
||||||
self.data_currently = self.data and self.data.currently()
|
|
||||||
|
|
||||||
def _update_minutely(self):
|
|
||||||
"""Update minutely data."""
|
|
||||||
self.data_minutely = self.data and self.data.minutely()
|
|
||||||
|
|
||||||
def _update_hourly(self):
|
|
||||||
"""Update hourly data."""
|
|
||||||
self.data_hourly = self.data and self.data.hourly()
|
|
||||||
|
|
||||||
def _update_daily(self):
|
|
||||||
"""Update daily data."""
|
|
||||||
self.data_daily = self.data and self.data.daily()
|
|
||||||
|
|
||||||
def _update_alerts(self):
|
|
||||||
"""Update alerts data."""
|
|
||||||
self.data_alerts = self.data and self.data.alerts()
|
|
@ -1,281 +0,0 @@
|
|||||||
"""Support for retrieving meteorological data from Dark Sky."""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import forecastio
|
|
||||||
from requests.exceptions import ConnectionError as ConnectError, HTTPError, Timeout
|
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.components.weather import (
|
|
||||||
ATTR_CONDITION_CLEAR_NIGHT,
|
|
||||||
ATTR_CONDITION_CLOUDY,
|
|
||||||
ATTR_CONDITION_FOG,
|
|
||||||
ATTR_CONDITION_HAIL,
|
|
||||||
ATTR_CONDITION_LIGHTNING,
|
|
||||||
ATTR_CONDITION_PARTLYCLOUDY,
|
|
||||||
ATTR_CONDITION_RAINY,
|
|
||||||
ATTR_CONDITION_SNOWY,
|
|
||||||
ATTR_CONDITION_SNOWY_RAINY,
|
|
||||||
ATTR_CONDITION_SUNNY,
|
|
||||||
ATTR_CONDITION_WINDY,
|
|
||||||
ATTR_FORECAST_CONDITION,
|
|
||||||
ATTR_FORECAST_NATIVE_PRECIPITATION,
|
|
||||||
ATTR_FORECAST_NATIVE_TEMP,
|
|
||||||
ATTR_FORECAST_NATIVE_TEMP_LOW,
|
|
||||||
ATTR_FORECAST_NATIVE_WIND_SPEED,
|
|
||||||
ATTR_FORECAST_TIME,
|
|
||||||
ATTR_FORECAST_WIND_BEARING,
|
|
||||||
PLATFORM_SCHEMA,
|
|
||||||
WeatherEntity,
|
|
||||||
)
|
|
||||||
from homeassistant.const import (
|
|
||||||
CONF_API_KEY,
|
|
||||||
CONF_LATITUDE,
|
|
||||||
CONF_LONGITUDE,
|
|
||||||
CONF_MODE,
|
|
||||||
CONF_NAME,
|
|
||||||
UnitOfLength,
|
|
||||||
UnitOfPrecipitationDepth,
|
|
||||||
UnitOfPressure,
|
|
||||||
UnitOfSpeed,
|
|
||||||
UnitOfTemperature,
|
|
||||||
)
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|
||||||
from homeassistant.util import Throttle
|
|
||||||
from homeassistant.util.dt import utc_from_timestamp
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
ATTRIBUTION = "Powered by Dark Sky"
|
|
||||||
|
|
||||||
FORECAST_MODE = ["hourly", "daily"]
|
|
||||||
|
|
||||||
MAP_CONDITION = {
|
|
||||||
"clear-day": ATTR_CONDITION_SUNNY,
|
|
||||||
"clear-night": ATTR_CONDITION_CLEAR_NIGHT,
|
|
||||||
"rain": ATTR_CONDITION_RAINY,
|
|
||||||
"snow": ATTR_CONDITION_SNOWY,
|
|
||||||
"sleet": ATTR_CONDITION_SNOWY_RAINY,
|
|
||||||
"wind": ATTR_CONDITION_WINDY,
|
|
||||||
"fog": ATTR_CONDITION_FOG,
|
|
||||||
"cloudy": ATTR_CONDITION_CLOUDY,
|
|
||||||
"partly-cloudy-day": ATTR_CONDITION_PARTLYCLOUDY,
|
|
||||||
"partly-cloudy-night": ATTR_CONDITION_PARTLYCLOUDY,
|
|
||||||
"hail": ATTR_CONDITION_HAIL,
|
|
||||||
"thunderstorm": ATTR_CONDITION_LIGHTNING,
|
|
||||||
"tornado": None,
|
|
||||||
}
|
|
||||||
|
|
||||||
CONF_UNITS = "units"
|
|
||||||
|
|
||||||
DEFAULT_NAME = "Dark Sky"
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = vol.All(
|
|
||||||
cv.removed(CONF_UNITS),
|
|
||||||
PLATFORM_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_API_KEY): cv.string,
|
|
||||||
vol.Optional(CONF_LATITUDE): cv.latitude,
|
|
||||||
vol.Optional(CONF_LONGITUDE): cv.longitude,
|
|
||||||
vol.Optional(CONF_MODE, default="hourly"): vol.In(FORECAST_MODE),
|
|
||||||
vol.Optional(CONF_UNITS): vol.In(["auto", "si", "us", "ca", "uk", "uk2"]),
|
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=3)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
config: ConfigType,
|
|
||||||
add_entities: AddEntitiesCallback,
|
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Set up the Dark Sky weather."""
|
|
||||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
|
||||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
|
||||||
name = config.get(CONF_NAME)
|
|
||||||
mode = config.get(CONF_MODE)
|
|
||||||
|
|
||||||
units = "si"
|
|
||||||
dark_sky = DarkSkyData(config.get(CONF_API_KEY), latitude, longitude, units)
|
|
||||||
|
|
||||||
add_entities([DarkSkyWeather(name, dark_sky, mode)], True)
|
|
||||||
|
|
||||||
|
|
||||||
class DarkSkyWeather(WeatherEntity):
|
|
||||||
"""Representation of a weather condition."""
|
|
||||||
|
|
||||||
_attr_native_precipitation_unit = UnitOfPrecipitationDepth.MILLIMETERS
|
|
||||||
_attr_native_pressure_unit = UnitOfPressure.MBAR
|
|
||||||
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
|
|
||||||
_attr_native_visibility_unit = UnitOfLength.KILOMETERS
|
|
||||||
_attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
|
|
||||||
|
|
||||||
def __init__(self, name, dark_sky, mode):
|
|
||||||
"""Initialize Dark Sky weather."""
|
|
||||||
self._name = name
|
|
||||||
self._dark_sky = dark_sky
|
|
||||||
self._mode = mode
|
|
||||||
|
|
||||||
self._ds_data = None
|
|
||||||
self._ds_currently = None
|
|
||||||
self._ds_hourly = None
|
|
||||||
self._ds_daily = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return if weather data is available from Dark Sky."""
|
|
||||||
return self._ds_data is not None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def attribution(self):
|
|
||||||
"""Return the attribution."""
|
|
||||||
return ATTRIBUTION
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the sensor."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_temperature(self):
|
|
||||||
"""Return the temperature."""
|
|
||||||
return self._ds_currently.get("temperature")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def humidity(self):
|
|
||||||
"""Return the humidity."""
|
|
||||||
return round(self._ds_currently.get("humidity") * 100.0, 2)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_wind_speed(self):
|
|
||||||
"""Return the wind speed."""
|
|
||||||
return self._ds_currently.get("windSpeed")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def wind_bearing(self):
|
|
||||||
"""Return the wind bearing."""
|
|
||||||
return self._ds_currently.get("windBearing")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ozone(self):
|
|
||||||
"""Return the ozone level."""
|
|
||||||
return self._ds_currently.get("ozone")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_pressure(self):
|
|
||||||
"""Return the pressure."""
|
|
||||||
return self._ds_currently.get("pressure")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def native_visibility(self):
|
|
||||||
"""Return the visibility."""
|
|
||||||
return self._ds_currently.get("visibility")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def condition(self):
|
|
||||||
"""Return the weather condition."""
|
|
||||||
return MAP_CONDITION.get(self._ds_currently.get("icon"))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def forecast(self):
|
|
||||||
"""Return the forecast array."""
|
|
||||||
|
|
||||||
# Per conversation with Joshua Reyes of Dark Sky, to get the total
|
|
||||||
# forecasted precipitation, you have to multiple the intensity by
|
|
||||||
# the hours for the forecast interval
|
|
||||||
def calc_precipitation(intensity, hours):
|
|
||||||
amount = None
|
|
||||||
if intensity is not None:
|
|
||||||
amount = round((intensity * hours), 1)
|
|
||||||
return amount if amount > 0 else None
|
|
||||||
|
|
||||||
data = None
|
|
||||||
|
|
||||||
if self._mode == "daily":
|
|
||||||
data = [
|
|
||||||
{
|
|
||||||
ATTR_FORECAST_TIME: utc_from_timestamp(
|
|
||||||
entry.d.get("time")
|
|
||||||
).isoformat(),
|
|
||||||
ATTR_FORECAST_NATIVE_TEMP: entry.d.get("temperatureHigh"),
|
|
||||||
ATTR_FORECAST_NATIVE_TEMP_LOW: entry.d.get("temperatureLow"),
|
|
||||||
ATTR_FORECAST_NATIVE_PRECIPITATION: calc_precipitation(
|
|
||||||
entry.d.get("precipIntensity"), 24
|
|
||||||
),
|
|
||||||
ATTR_FORECAST_NATIVE_WIND_SPEED: entry.d.get("windSpeed"),
|
|
||||||
ATTR_FORECAST_WIND_BEARING: entry.d.get("windBearing"),
|
|
||||||
ATTR_FORECAST_CONDITION: MAP_CONDITION.get(entry.d.get("icon")),
|
|
||||||
}
|
|
||||||
for entry in self._ds_daily.data
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
data = [
|
|
||||||
{
|
|
||||||
ATTR_FORECAST_TIME: utc_from_timestamp(
|
|
||||||
entry.d.get("time")
|
|
||||||
).isoformat(),
|
|
||||||
ATTR_FORECAST_NATIVE_TEMP: entry.d.get("temperature"),
|
|
||||||
ATTR_FORECAST_NATIVE_PRECIPITATION: calc_precipitation(
|
|
||||||
entry.d.get("precipIntensity"), 1
|
|
||||||
),
|
|
||||||
ATTR_FORECAST_CONDITION: MAP_CONDITION.get(entry.d.get("icon")),
|
|
||||||
}
|
|
||||||
for entry in self._ds_hourly.data
|
|
||||||
]
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def update(self) -> None:
|
|
||||||
"""Get the latest data from Dark Sky."""
|
|
||||||
self._dark_sky.update()
|
|
||||||
|
|
||||||
self._ds_data = self._dark_sky.data
|
|
||||||
currently = self._dark_sky.currently
|
|
||||||
self._ds_currently = currently.d if currently else {}
|
|
||||||
self._ds_hourly = self._dark_sky.hourly
|
|
||||||
self._ds_daily = self._dark_sky.daily
|
|
||||||
|
|
||||||
|
|
||||||
class DarkSkyData:
|
|
||||||
"""Get the latest data from Dark Sky."""
|
|
||||||
|
|
||||||
def __init__(self, api_key, latitude, longitude, units):
|
|
||||||
"""Initialize the data object."""
|
|
||||||
self._api_key = api_key
|
|
||||||
self.latitude = latitude
|
|
||||||
self.longitude = longitude
|
|
||||||
self.requested_units = units
|
|
||||||
|
|
||||||
self.data = None
|
|
||||||
self.currently = None
|
|
||||||
self.hourly = None
|
|
||||||
self.daily = None
|
|
||||||
self._connect_error = False
|
|
||||||
|
|
||||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
|
||||||
def update(self):
|
|
||||||
"""Get the latest data from Dark Sky."""
|
|
||||||
try:
|
|
||||||
self.data = forecastio.load_forecast(
|
|
||||||
self._api_key, self.latitude, self.longitude, units=self.requested_units
|
|
||||||
)
|
|
||||||
self.currently = self.data.currently()
|
|
||||||
self.hourly = self.data.hourly()
|
|
||||||
self.daily = self.data.daily()
|
|
||||||
if self._connect_error:
|
|
||||||
self._connect_error = False
|
|
||||||
_LOGGER.info("Reconnected to Dark Sky")
|
|
||||||
except (ConnectError, HTTPError, Timeout, ValueError) as error:
|
|
||||||
if not self._connect_error:
|
|
||||||
self._connect_error = True
|
|
||||||
_LOGGER.error("Unable to connect to Dark Sky. %s", error)
|
|
||||||
self.data = None
|
|
@ -926,12 +926,6 @@
|
|||||||
"config_flow": false,
|
"config_flow": false,
|
||||||
"iot_class": "local_polling"
|
"iot_class": "local_polling"
|
||||||
},
|
},
|
||||||
"darksky": {
|
|
||||||
"name": "Dark Sky",
|
|
||||||
"integration_type": "hub",
|
|
||||||
"config_flow": false,
|
|
||||||
"iot_class": "cloud_polling"
|
|
||||||
},
|
|
||||||
"datadog": {
|
"datadog": {
|
||||||
"name": "Datadog",
|
"name": "Datadog",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
|
@ -2041,9 +2041,6 @@ python-etherscan-api==0.0.3
|
|||||||
# homeassistant.components.familyhub
|
# homeassistant.components.familyhub
|
||||||
python-family-hub-local==0.0.2
|
python-family-hub-local==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.darksky
|
|
||||||
python-forecastio==1.4.0
|
|
||||||
|
|
||||||
# homeassistant.components.fully_kiosk
|
# homeassistant.components.fully_kiosk
|
||||||
python-fullykiosk==0.0.12
|
python-fullykiosk==0.0.12
|
||||||
|
|
||||||
|
@ -1473,9 +1473,6 @@ python-bsblan==0.5.11
|
|||||||
# homeassistant.components.ecobee
|
# homeassistant.components.ecobee
|
||||||
python-ecobee-api==0.2.14
|
python-ecobee-api==0.2.14
|
||||||
|
|
||||||
# homeassistant.components.darksky
|
|
||||||
python-forecastio==1.4.0
|
|
||||||
|
|
||||||
# homeassistant.components.fully_kiosk
|
# homeassistant.components.fully_kiosk
|
||||||
python-fullykiosk==0.0.12
|
python-fullykiosk==0.0.12
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
"""Tests for the darksky component."""
|
|
@ -1,171 +0,0 @@
|
|||||||
"""The tests for the Dark Sky platform."""
|
|
||||||
from datetime import timedelta
|
|
||||||
import re
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import forecastio
|
|
||||||
from requests.exceptions import ConnectionError as ConnectError
|
|
||||||
import requests_mock
|
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
|
|
||||||
from tests.common import load_fixture
|
|
||||||
|
|
||||||
VALID_CONFIG_MINIMAL = {
|
|
||||||
"sensor": {
|
|
||||||
"platform": "darksky",
|
|
||||||
"api_key": "foo",
|
|
||||||
"forecast": [1, 2],
|
|
||||||
"hourly_forecast": [1, 2],
|
|
||||||
"monitored_conditions": ["summary", "icon", "temperature_high", "alerts"],
|
|
||||||
"scan_interval": timedelta(seconds=120),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INVALID_CONFIG_MINIMAL = {
|
|
||||||
"sensor": {
|
|
||||||
"platform": "darksky",
|
|
||||||
"api_key": "foo",
|
|
||||||
"forecast": [1, 2],
|
|
||||||
"hourly_forecast": [1, 2],
|
|
||||||
"monitored_conditions": ["summary", "iocn", "temperature_high"],
|
|
||||||
"scan_interval": timedelta(seconds=120),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VALID_CONFIG_LANG_DE = {
|
|
||||||
"sensor": {
|
|
||||||
"platform": "darksky",
|
|
||||||
"api_key": "foo",
|
|
||||||
"forecast": [1, 2],
|
|
||||||
"hourly_forecast": [1, 2],
|
|
||||||
"units": "us",
|
|
||||||
"language": "de",
|
|
||||||
"monitored_conditions": [
|
|
||||||
"summary",
|
|
||||||
"icon",
|
|
||||||
"temperature_high",
|
|
||||||
"minutely_summary",
|
|
||||||
"hourly_summary",
|
|
||||||
"daily_summary",
|
|
||||||
"humidity",
|
|
||||||
"alerts",
|
|
||||||
],
|
|
||||||
"scan_interval": timedelta(seconds=120),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INVALID_CONFIG_LANG = {
|
|
||||||
"sensor": {
|
|
||||||
"platform": "darksky",
|
|
||||||
"api_key": "foo",
|
|
||||||
"forecast": [1, 2],
|
|
||||||
"hourly_forecast": [1, 2],
|
|
||||||
"language": "yz",
|
|
||||||
"monitored_conditions": ["summary", "icon", "temperature_high"],
|
|
||||||
"scan_interval": timedelta(seconds=120),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_with_config(
|
|
||||||
hass: HomeAssistant, requests_mock: requests_mock.Mocker
|
|
||||||
) -> None:
|
|
||||||
"""Test the platform setup with configuration."""
|
|
||||||
with patch("homeassistant.components.darksky.sensor.forecastio.load_forecast"):
|
|
||||||
assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_summary")
|
|
||||||
assert state is not None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_with_invalid_config(hass: HomeAssistant) -> None:
|
|
||||||
"""Test the platform setup with invalid configuration."""
|
|
||||||
assert await async_setup_component(hass, "sensor", INVALID_CONFIG_MINIMAL)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_summary")
|
|
||||||
assert state is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_with_language_config(hass: HomeAssistant) -> None:
|
|
||||||
"""Test the platform setup with language configuration."""
|
|
||||||
with patch("homeassistant.components.darksky.sensor.forecastio.load_forecast"):
|
|
||||||
assert await async_setup_component(hass, "sensor", VALID_CONFIG_LANG_DE)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_summary")
|
|
||||||
assert state is not None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_with_invalid_language_config(hass: HomeAssistant) -> None:
|
|
||||||
"""Test the platform setup with language configuration."""
|
|
||||||
assert await async_setup_component(hass, "sensor", INVALID_CONFIG_LANG)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_summary")
|
|
||||||
assert state is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_bad_api_key(
|
|
||||||
hass: HomeAssistant, requests_mock: requests_mock.Mocker
|
|
||||||
) -> None:
|
|
||||||
"""Test for handling a bad API key."""
|
|
||||||
# The Dark Sky API wrapper that we use raises an HTTP error
|
|
||||||
# when you try to use a bad (or no) API key.
|
|
||||||
url = "https://api.darksky.net/forecast/{}/{},{}?units=auto".format(
|
|
||||||
"foo", str(hass.config.latitude), str(hass.config.longitude)
|
|
||||||
)
|
|
||||||
msg = f"400 Client Error: Bad Request for url: {url}"
|
|
||||||
requests_mock.get(url, text=msg, status_code=400)
|
|
||||||
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass, "sensor", {"sensor": {"platform": "darksky", "api_key": "foo"}}
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert hass.states.get("sensor.dark_sky_summary") is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_connection_error(hass: HomeAssistant) -> None:
|
|
||||||
"""Test setting up with a connection error."""
|
|
||||||
with patch(
|
|
||||||
"homeassistant.components.darksky.sensor.forecastio.load_forecast",
|
|
||||||
side_effect=ConnectError(),
|
|
||||||
):
|
|
||||||
await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_summary")
|
|
||||||
assert state is None
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass: HomeAssistant, requests_mock: requests_mock.Mocker) -> None:
|
|
||||||
"""Test for successfully setting up the forecast.io platform."""
|
|
||||||
with patch(
|
|
||||||
"forecastio.api.get_forecast", wraps=forecastio.api.get_forecast
|
|
||||||
) as mock_get_forecast:
|
|
||||||
uri = (
|
|
||||||
r"https://api.(darksky.net|forecast.io)\/forecast\/(\w+)\/"
|
|
||||||
r"(-?\d+\.?\d*),(-?\d+\.?\d*)"
|
|
||||||
)
|
|
||||||
requests_mock.get(re.compile(uri), text=load_fixture("darksky.json"))
|
|
||||||
|
|
||||||
assert await async_setup_component(hass, "sensor", VALID_CONFIG_MINIMAL)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert mock_get_forecast.call_count == 1
|
|
||||||
assert len(hass.states.async_entity_ids()) == 13
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_summary")
|
|
||||||
assert state is not None
|
|
||||||
assert state.state == "Clear"
|
|
||||||
assert state.attributes.get("friendly_name") == "Dark Sky Summary"
|
|
||||||
state = hass.states.get("sensor.dark_sky_alerts")
|
|
||||||
assert state.state == "2"
|
|
||||||
|
|
||||||
state = hass.states.get("sensor.dark_sky_daytime_high_temperature_1d")
|
|
||||||
assert state is not None
|
|
||||||
assert state.attributes.get("device_class") == "temperature"
|
|
@ -1,52 +0,0 @@
|
|||||||
"""The tests for the Dark Sky weather component."""
|
|
||||||
import re
|
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import forecastio
|
|
||||||
from requests.exceptions import ConnectionError as ConnectError
|
|
||||||
import requests_mock
|
|
||||||
|
|
||||||
from homeassistant.components import weather
|
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
from homeassistant.setup import async_setup_component
|
|
||||||
|
|
||||||
from tests.common import load_fixture
|
|
||||||
|
|
||||||
|
|
||||||
async def test_setup(hass: HomeAssistant, requests_mock: requests_mock.Mocker) -> None:
|
|
||||||
"""Test for successfully setting up the forecast.io platform."""
|
|
||||||
with patch(
|
|
||||||
"forecastio.api.get_forecast", wraps=forecastio.api.get_forecast
|
|
||||||
) as mock_get_forecast:
|
|
||||||
requests_mock.get(
|
|
||||||
re.compile(
|
|
||||||
r"https://api.(darksky.net|forecast.io)\/forecast\/(\w+)\/"
|
|
||||||
r"(-?\d+\.?\d*),(-?\d+\.?\d*)"
|
|
||||||
),
|
|
||||||
text=load_fixture("darksky.json"),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
weather.DOMAIN,
|
|
||||||
{"weather": {"name": "test", "platform": "darksky", "api_key": "foo"}},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
assert mock_get_forecast.call_count == 1
|
|
||||||
state = hass.states.get("weather.test")
|
|
||||||
assert state.state == "sunny"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_failed_setup(hass: HomeAssistant) -> None:
|
|
||||||
"""Test to ensure that a network error does not break component state."""
|
|
||||||
with patch("forecastio.load_forecast", side_effect=ConnectError()):
|
|
||||||
assert await async_setup_component(
|
|
||||||
hass,
|
|
||||||
weather.DOMAIN,
|
|
||||||
{"weather": {"name": "test", "platform": "darksky", "api_key": "foo"}},
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
|
|
||||||
state = hass.states.get("weather.test")
|
|
||||||
assert state.state == "unavailable"
|
|
Loading…
x
Reference in New Issue
Block a user