diff --git a/.coveragerc b/.coveragerc index b716874c679..6b4133bf476 100644 --- a/.coveragerc +++ b/.coveragerc @@ -8,10 +8,6 @@ omit = homeassistant/scripts/*.py # omit pieces of code that rely on external devices being present - homeassistant/components/accuweather/__init__.py - homeassistant/components/accuweather/const.py - homeassistant/components/accuweather/sensor.py - homeassistant/components/accuweather/weather.py homeassistant/components/acer_projector/switch.py homeassistant/components/actiontec/device_tracker.py homeassistant/components/acmeda/__init__.py diff --git a/homeassistant/components/accuweather/config_flow.py b/homeassistant/components/accuweather/config_flow.py index d50a2ac406b..03d6f40181c 100644 --- a/homeassistant/components/accuweather/config_flow.py +++ b/homeassistant/components/accuweather/config_flow.py @@ -34,7 +34,7 @@ class AccuWeatherFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): if user_input is not None: websession = async_get_clientsession(self.hass) try: - with timeout(10): + async with timeout(10): accuweather = AccuWeather( user_input[CONF_API_KEY], websession, diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index 4e54d937dee..dd039717468 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/accuweather/", "requirements": ["accuweather==0.0.9"], "codeowners": ["@bieniu"], - "config_flow": true + "config_flow": true, + "quality_scale": "platinum" } diff --git a/homeassistant/components/accuweather/sensor.py b/homeassistant/components/accuweather/sensor.py index 878f387c35c..9ab44a318a9 100644 --- a/homeassistant/components/accuweather/sensor.py +++ b/homeassistant/components/accuweather/sensor.py @@ -154,11 +154,7 @@ class AccuWeatherSensor(Entity): def device_state_attributes(self): """Return the state attributes.""" if self.forecast_day is not None: - if self.kind == "WindGustDay": - self._attrs["direction"] = self.coordinator.data[ATTR_FORECAST][ - self.forecast_day - ][self.kind]["Direction"]["English"] - elif self.kind == "WindGustNight": + if self.kind in ["WindGustDay", "WindGustNight"]: self._attrs["direction"] = self.coordinator.data[ATTR_FORECAST][ self.forecast_day ][self.kind]["Direction"]["English"] diff --git a/tests/components/accuweather/__init__.py b/tests/components/accuweather/__init__.py index 97ae531ddd0..28e53d1e2fc 100644 --- a/tests/components/accuweather/__init__.py +++ b/tests/components/accuweather/__init__.py @@ -1 +1,48 @@ """Tests for AccuWeather.""" +import json + +from homeassistant.components.accuweather.const import DOMAIN + +from tests.async_mock import patch +from tests.common import MockConfigEntry, load_fixture + + +async def init_integration( + hass, forecast=False, unsupported_icon=False +) -> MockConfigEntry: + """Set up the AccuWeather integration in Home Assistant.""" + options = {} + if forecast: + options["forecast"] = True + + entry = MockConfigEntry( + domain=DOMAIN, + title="Home", + unique_id="0123456", + data={ + "api_key": "32-character-string-1234567890qw", + "latitude": 55.55, + "longitude": 122.12, + "name": "Home", + }, + options=options, + ) + + current = json.loads(load_fixture("accuweather/current_conditions_data.json")) + forecast = json.loads(load_fixture("accuweather/forecast_data.json")) + + if unsupported_icon: + current["WeatherIcon"] = 999 + + with patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ), patch( + "homeassistant.components.accuweather.AccuWeather.async_get_forecast", + return_value=forecast, + ): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + return entry diff --git a/tests/components/accuweather/test_init.py b/tests/components/accuweather/test_init.py new file mode 100644 index 00000000000..0a54132fd68 --- /dev/null +++ b/tests/components/accuweather/test_init.py @@ -0,0 +1,59 @@ +"""Test init of AccuWeather integration.""" +from homeassistant.components.accuweather.const import DOMAIN +from homeassistant.config_entries import ( + ENTRY_STATE_LOADED, + ENTRY_STATE_NOT_LOADED, + ENTRY_STATE_SETUP_RETRY, +) +from homeassistant.const import STATE_UNAVAILABLE + +from tests.async_mock import patch +from tests.common import MockConfigEntry +from tests.components.accuweather import init_integration + + +async def test_async_setup_entry(hass): + """Test a successful setup entry.""" + await init_integration(hass) + + state = hass.states.get("weather.home") + assert state is not None + assert state.state != STATE_UNAVAILABLE + assert state.state == "sunny" + + +async def test_config_not_ready(hass): + """Test for setup failure if connection to AccuWeather is missing.""" + entry = MockConfigEntry( + domain=DOMAIN, + title="Home", + unique_id="0123456", + data={ + "api_key": "32-character-string-1234567890qw", + "latitude": 55.55, + "longitude": 122.12, + "name": "Home", + }, + ) + + with patch( + "homeassistant.components.accuweather.AccuWeather._async_get_data", + side_effect=ConnectionError(), + ): + entry.add_to_hass(hass) + await hass.config_entries.async_setup(entry.entry_id) + assert entry.state == ENTRY_STATE_SETUP_RETRY + + +async def test_unload_entry(hass): + """Test successful unload of entry.""" + entry = await init_integration(hass) + + assert len(hass.config_entries.async_entries(DOMAIN)) == 1 + assert entry.state == ENTRY_STATE_LOADED + + assert await hass.config_entries.async_unload(entry.entry_id) + await hass.async_block_till_done() + + assert entry.state == ENTRY_STATE_NOT_LOADED + assert not hass.data.get(DOMAIN) diff --git a/tests/components/accuweather/test_sensor.py b/tests/components/accuweather/test_sensor.py new file mode 100644 index 00000000000..26c7f0d9ab6 --- /dev/null +++ b/tests/components/accuweather/test_sensor.py @@ -0,0 +1,598 @@ +"""Test sensor of AccuWeather integration.""" +from datetime import timedelta +import json + +from homeassistant.components.accuweather.const import ( + ATTRIBUTION, + CONCENTRATION_PARTS_PER_CUBIC_METER, + DOMAIN, + LENGTH_MILIMETERS, +) +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_DEVICE_CLASS, + ATTR_ENTITY_ID, + ATTR_ICON, + ATTR_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_TEMPERATURE, + LENGTH_METERS, + SPEED_KILOMETERS_PER_HOUR, + STATE_UNAVAILABLE, + TEMP_CELSIUS, + TIME_HOURS, + UNIT_PERCENTAGE, + UV_INDEX, +) +from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow + +from tests.async_mock import patch +from tests.common import async_fire_time_changed, load_fixture +from tests.components.accuweather import init_integration + + +async def test_sensor_without_forecast(hass): + """Test states of the sensor without forecast.""" + await init_integration(hass) + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get("sensor.home_cloud_ceiling") + assert state + assert state.state == "3200" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_ICON) == "mdi:weather-fog" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_METERS + + entry = registry.async_get("sensor.home_cloud_ceiling") + assert entry + assert entry.unique_id == "0123456-ceiling" + + state = hass.states.get("sensor.home_precipitation") + assert state + assert state.state == "0.0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == LENGTH_MILIMETERS + assert state.attributes.get(ATTR_ICON) == "mdi:weather-rainy" + assert state.attributes.get("type") is None + + entry = registry.async_get("sensor.home_precipitation") + assert entry + assert entry.unique_id == "0123456-precipitation" + + state = hass.states.get("sensor.home_pressure_tendency") + assert state + assert state.state == "falling" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_ICON) == "mdi:gauge" + assert state.attributes.get(ATTR_DEVICE_CLASS) == "accuweather__pressure_tendency" + + entry = registry.async_get("sensor.home_pressure_tendency") + assert entry + assert entry.unique_id == "0123456-pressuretendency" + + state = hass.states.get("sensor.home_realfeel_temperature") + assert state + assert state.state == "25.1" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_realfeel_temperature") + assert entry + assert entry.unique_id == "0123456-realfeeltemperature" + + state = hass.states.get("sensor.home_uv_index") + assert state + assert state.state == "6" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UV_INDEX + assert state.attributes.get("level") == "High" + + entry = registry.async_get("sensor.home_uv_index") + assert entry + assert entry.unique_id == "0123456-uvindex" + + +async def test_sensor_with_forecast(hass): + """Test states of the sensor with forecast.""" + await init_integration(hass, forecast=True) + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get("sensor.home_hours_of_sun_0d") + assert state + assert state.state == "7.2" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_ICON) == "mdi:weather-partly-cloudy" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TIME_HOURS + + entry = registry.async_get("sensor.home_hours_of_sun_0d") + assert entry + assert entry.unique_id == "0123456-hoursofsun-0" + + state = hass.states.get("sensor.home_realfeel_temperature_max_0d") + assert state + assert state.state == "29.8" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_realfeel_temperature_max_0d") + assert entry + + state = hass.states.get("sensor.home_realfeel_temperature_min_0d") + assert state + assert state.state == "15.1" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_realfeel_temperature_min_0d") + assert entry + assert entry.unique_id == "0123456-realfeeltemperaturemin-0" + + state = hass.states.get("sensor.home_thunderstorm_probability_day_0d") + assert state + assert state.state == "40" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_ICON) == "mdi:weather-lightning" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UNIT_PERCENTAGE + + entry = registry.async_get("sensor.home_thunderstorm_probability_day_0d") + assert entry + assert entry.unique_id == "0123456-thunderstormprobabilityday-0" + + state = hass.states.get("sensor.home_thunderstorm_probability_night_0d") + assert state + assert state.state == "40" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_ICON) == "mdi:weather-lightning" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UNIT_PERCENTAGE + + entry = registry.async_get("sensor.home_thunderstorm_probability_night_0d") + assert entry + assert entry.unique_id == "0123456-thunderstormprobabilitynight-0" + + state = hass.states.get("sensor.home_uv_index_0d") + assert state + assert state.state == "5" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_ICON) == "mdi:weather-sunny" + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UV_INDEX + assert state.attributes.get("level") == "Moderate" + + entry = registry.async_get("sensor.home_uv_index_0d") + assert entry + assert entry.unique_id == "0123456-uvindex-0" + + +async def test_sensor_disabled(hass): + """Test sensor disabled by default.""" + await init_integration(hass) + registry = await hass.helpers.entity_registry.async_get_registry() + + entry = registry.async_get("sensor.home_apparent_temperature") + assert entry + assert entry.unique_id == "0123456-apparenttemperature" + assert entry.disabled + assert entry.disabled_by == "integration" + + # Test enabling entity + updated_entry = registry.async_update_entity( + entry.entity_id, **{"disabled_by": None} + ) + + assert updated_entry != entry + assert updated_entry.disabled is False + + +async def test_sensor_enabled_without_forecast(hass): + """Test enabling an advanced sensor.""" + registry = await hass.helpers.entity_registry.async_get_registry() + + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-apparenttemperature", + suggested_object_id="home_apparent_temperature", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-cloudcover", + suggested_object_id="home_cloud_cover", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-dewpoint", + suggested_object_id="home_dew_point", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-realfeeltemperatureshade", + suggested_object_id="home_realfeel_temperature_shade", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-wetbulbtemperature", + suggested_object_id="home_wet_bulb_temperature", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-windchilltemperature", + suggested_object_id="home_wind_chill_temperature", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-windgust", + suggested_object_id="home_wind_gust", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-cloudcoverday-0", + suggested_object_id="home_cloud_cover_day_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-cloudcovernight-0", + suggested_object_id="home_cloud_cover_night_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-grass-0", + suggested_object_id="home_grass_pollen_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-mold-0", + suggested_object_id="home_mold_pollen_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-ozone-0", + suggested_object_id="home_ozone_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-ragweed-0", + suggested_object_id="home_ragweed_pollen_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-realfeeltemperatureshademax-0", + suggested_object_id="home_realfeel_temperature_shade_max_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-realfeeltemperatureshademin-0", + suggested_object_id="home_realfeel_temperature_shade_min_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-tree-0", + suggested_object_id="home_tree_pollen_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-windgustday-0", + suggested_object_id="home_wind_gust_day_0d", + disabled_by=None, + ) + registry.async_get_or_create( + SENSOR_DOMAIN, + DOMAIN, + "0123456-windgustnight-0", + suggested_object_id="home_wind_gust_night_0d", + disabled_by=None, + ) + + await init_integration(hass, forecast=True) + + state = hass.states.get("sensor.home_apparent_temperature") + assert state + assert state.state == "22.8" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_apparent_temperature") + assert entry + assert entry.unique_id == "0123456-apparenttemperature" + + state = hass.states.get("sensor.home_cloud_cover") + assert state + assert state.state == "10" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UNIT_PERCENTAGE + assert state.attributes.get(ATTR_ICON) == "mdi:weather-cloudy" + + entry = registry.async_get("sensor.home_cloud_cover") + assert entry + assert entry.unique_id == "0123456-cloudcover" + + state = hass.states.get("sensor.home_dew_point") + assert state + assert state.state == "16.2" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_dew_point") + assert entry + assert entry.unique_id == "0123456-dewpoint" + + state = hass.states.get("sensor.home_realfeel_temperature_shade") + assert state + assert state.state == "21.1" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_realfeel_temperature_shade") + assert entry + assert entry.unique_id == "0123456-realfeeltemperatureshade" + + state = hass.states.get("sensor.home_wet_bulb_temperature") + assert state + assert state.state == "18.6" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_wet_bulb_temperature") + assert entry + assert entry.unique_id == "0123456-wetbulbtemperature" + + state = hass.states.get("sensor.home_wind_chill_temperature") + assert state + assert state.state == "22.8" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_wind_chill_temperature") + assert entry + assert entry.unique_id == "0123456-windchilltemperature" + + state = hass.states.get("sensor.home_wind_gust") + assert state + assert state.state == "20.3" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == SPEED_KILOMETERS_PER_HOUR + assert state.attributes.get(ATTR_ICON) == "mdi:weather-windy" + + entry = registry.async_get("sensor.home_wind_gust") + assert entry + assert entry.unique_id == "0123456-windgust" + + state = hass.states.get("sensor.home_cloud_cover_day_0d") + assert state + assert state.state == "58" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UNIT_PERCENTAGE + assert state.attributes.get(ATTR_ICON) == "mdi:weather-cloudy" + + entry = registry.async_get("sensor.home_cloud_cover_day_0d") + assert entry + assert entry.unique_id == "0123456-cloudcoverday-0" + + state = hass.states.get("sensor.home_cloud_cover_night_0d") + assert state + assert state.state == "65" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == UNIT_PERCENTAGE + assert state.attributes.get(ATTR_ICON) == "mdi:weather-cloudy" + + entry = registry.async_get("sensor.home_cloud_cover_night_0d") + assert entry + + state = hass.states.get("sensor.home_grass_pollen_0d") + assert state + assert state.state == "0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_CUBIC_METER + ) + assert state.attributes.get("level") == "Low" + assert state.attributes.get(ATTR_ICON) == "mdi:grass" + + entry = registry.async_get("sensor.home_grass_pollen_0d") + assert entry + assert entry.unique_id == "0123456-grass-0" + + state = hass.states.get("sensor.home_mold_pollen_0d") + assert state + assert state.state == "0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_CUBIC_METER + ) + assert state.attributes.get("level") == "Low" + assert state.attributes.get(ATTR_ICON) == "mdi:blur" + + entry = registry.async_get("sensor.home_mold_pollen_0d") + assert entry + assert entry.unique_id == "0123456-mold-0" + + state = hass.states.get("sensor.home_ozone_0d") + assert state + assert state.state == "32" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get("level") == "Good" + assert state.attributes.get(ATTR_ICON) == "mdi:vector-triangle" + + entry = registry.async_get("sensor.home_ozone_0d") + assert entry + assert entry.unique_id == "0123456-ozone-0" + + state = hass.states.get("sensor.home_ragweed_pollen_0d") + assert state + assert state.state == "0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_CUBIC_METER + ) + assert state.attributes.get("level") == "Low" + assert state.attributes.get(ATTR_ICON) == "mdi:sprout" + + entry = registry.async_get("sensor.home_ragweed_pollen_0d") + assert entry + assert entry.unique_id == "0123456-ragweed-0" + + state = hass.states.get("sensor.home_realfeel_temperature_shade_max_0d") + assert state + assert state.state == "28.0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_realfeel_temperature_shade_max_0d") + assert entry + assert entry.unique_id == "0123456-realfeeltemperatureshademax-0" + + state = hass.states.get("sensor.home_realfeel_temperature_shade_min_0d") + assert state + assert state.state == "15.1" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == TEMP_CELSIUS + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TEMPERATURE + + entry = registry.async_get("sensor.home_realfeel_temperature_shade_min_0d") + assert entry + assert entry.unique_id == "0123456-realfeeltemperatureshademin-0" + + state = hass.states.get("sensor.home_tree_pollen_0d") + assert state + assert state.state == "0" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert ( + state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + == CONCENTRATION_PARTS_PER_CUBIC_METER + ) + assert state.attributes.get("level") == "Low" + assert state.attributes.get(ATTR_ICON) == "mdi:tree-outline" + + entry = registry.async_get("sensor.home_tree_pollen_0d") + assert entry + assert entry.unique_id == "0123456-tree-0" + + state = hass.states.get("sensor.home_wind_gust_day_0d") + assert state + assert state.state == "29.6" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == SPEED_KILOMETERS_PER_HOUR + assert state.attributes.get("direction") == "S" + assert state.attributes.get(ATTR_ICON) == "mdi:weather-windy" + + entry = registry.async_get("sensor.home_wind_gust_day_0d") + assert entry + assert entry.unique_id == "0123456-windgustday-0" + + state = hass.states.get("sensor.home_wind_gust_night_0d") + assert state + assert state.state == "18.5" + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == SPEED_KILOMETERS_PER_HOUR + assert state.attributes.get("direction") == "WSW" + assert state.attributes.get(ATTR_ICON) == "mdi:weather-windy" + + entry = registry.async_get("sensor.home_wind_gust_night_0d") + assert entry + assert entry.unique_id == "0123456-windgustnight-0" + + +async def test_availability(hass): + """Ensure that we mark the entities unavailable correctly when service is offline.""" + await init_integration(hass) + + state = hass.states.get("sensor.home_cloud_ceiling") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "3200" + + future = utcnow() + timedelta(minutes=60) + with patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + side_effect=ConnectionError(), + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("sensor.home_cloud_ceiling") + assert state + assert state.state == STATE_UNAVAILABLE + + future = utcnow() + timedelta(minutes=120) + with patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=json.loads( + load_fixture("accuweather/current_conditions_data.json") + ), + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("sensor.home_cloud_ceiling") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "3200" + + +async def test_manual_update_entity(hass): + """Test manual update entity via service homeasasistant/update_entity.""" + await init_integration(hass, forecast=True) + + await async_setup_component(hass, "homeassistant", {}) + + current = json.loads(load_fixture("accuweather/current_conditions_data.json")) + forecast = json.loads(load_fixture("accuweather/forecast_data.json")) + + with patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ) as mock_current, patch( + "homeassistant.components.accuweather.AccuWeather.async_get_forecast", + return_value=forecast, + ) as mock_forecast: + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["sensor.home_cloud_ceiling"]}, + blocking=True, + ) + assert mock_current.call_count == 1 + assert mock_forecast.call_count == 1 diff --git a/tests/components/accuweather/test_weather.py b/tests/components/accuweather/test_weather.py new file mode 100644 index 00000000000..692b2ae243f --- /dev/null +++ b/tests/components/accuweather/test_weather.py @@ -0,0 +1,155 @@ +"""Test weather of AccuWeather integration.""" +from datetime import timedelta +import json + +from homeassistant.components.accuweather.const import ATTRIBUTION +from homeassistant.components.weather import ( + ATTR_FORECAST, + ATTR_FORECAST_CONDITION, + ATTR_FORECAST_PRECIPITATION, + ATTR_FORECAST_PRECIPITATION_PROBABILITY, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TEMP_LOW, + ATTR_FORECAST_TIME, + ATTR_FORECAST_WIND_BEARING, + ATTR_FORECAST_WIND_SPEED, + ATTR_WEATHER_HUMIDITY, + ATTR_WEATHER_OZONE, + ATTR_WEATHER_PRESSURE, + ATTR_WEATHER_TEMPERATURE, + ATTR_WEATHER_VISIBILITY, + ATTR_WEATHER_WIND_BEARING, + ATTR_WEATHER_WIND_SPEED, +) +from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ENTITY_ID, STATE_UNAVAILABLE +from homeassistant.setup import async_setup_component +from homeassistant.util.dt import utcnow + +from tests.async_mock import patch +from tests.common import async_fire_time_changed, load_fixture +from tests.components.accuweather import init_integration + + +async def test_weather_without_forecast(hass): + """Test states of the weather without forecast.""" + await init_integration(hass) + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get("weather.home") + assert state + assert state.state == "sunny" + assert not state.attributes.get(ATTR_FORECAST) + assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67 + assert not state.attributes.get(ATTR_WEATHER_OZONE) + assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0 + assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 + assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1 + assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + + entry = registry.async_get("weather.home") + assert entry + assert entry.unique_id == "0123456" + + +async def test_weather_with_forecast(hass): + """Test states of the weather with forecast.""" + await init_integration(hass, forecast=True) + registry = await hass.helpers.entity_registry.async_get_registry() + + state = hass.states.get("weather.home") + assert state + assert state.state == "sunny" + assert state.attributes.get(ATTR_WEATHER_HUMIDITY) == 67 + assert state.attributes.get(ATTR_WEATHER_OZONE) == 32 + assert state.attributes.get(ATTR_WEATHER_PRESSURE) == 1012.0 + assert state.attributes.get(ATTR_WEATHER_TEMPERATURE) == 22.6 + assert state.attributes.get(ATTR_WEATHER_VISIBILITY) == 16.1 + assert state.attributes.get(ATTR_WEATHER_WIND_BEARING) == 180 + assert state.attributes.get(ATTR_WEATHER_WIND_SPEED) == 14.5 + assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION + forecast = state.attributes.get(ATTR_FORECAST)[0] + assert forecast.get(ATTR_FORECAST_CONDITION) == "lightning-rainy" + assert forecast.get(ATTR_FORECAST_PRECIPITATION) == 4.8 + assert forecast.get(ATTR_FORECAST_PRECIPITATION_PROBABILITY) == 58 + assert forecast.get(ATTR_FORECAST_TEMP) == 29.5 + assert forecast.get(ATTR_FORECAST_TEMP_LOW) == 15.4 + assert forecast.get(ATTR_FORECAST_TIME) == "2020-07-26T05:00:00+00:00" + assert forecast.get(ATTR_FORECAST_WIND_BEARING) == 166 + assert forecast.get(ATTR_FORECAST_WIND_SPEED) == 13.0 + + entry = registry.async_get("weather.home") + assert entry + assert entry.unique_id == "0123456" + + +async def test_availability(hass): + """Ensure that we mark the entities unavailable correctly when service is offline.""" + await init_integration(hass) + + state = hass.states.get("weather.home") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "sunny" + + future = utcnow() + timedelta(minutes=60) + with patch( + "homeassistant.components.accuweather.AccuWeather._async_get_data", + side_effect=ConnectionError(), + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("weather.home") + assert state + assert state.state == STATE_UNAVAILABLE + + future = utcnow() + timedelta(minutes=120) + with patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=json.loads( + load_fixture("accuweather/current_conditions_data.json") + ), + ): + async_fire_time_changed(hass, future) + await hass.async_block_till_done() + + state = hass.states.get("weather.home") + assert state + assert state.state != STATE_UNAVAILABLE + assert state.state == "sunny" + + +async def test_manual_update_entity(hass): + """Test manual update entity via service homeasasistant/update_entity.""" + await init_integration(hass, forecast=True) + + await async_setup_component(hass, "homeassistant", {}) + + current = json.loads(load_fixture("accuweather/current_conditions_data.json")) + forecast = json.loads(load_fixture("accuweather/forecast_data.json")) + + with patch( + "homeassistant.components.accuweather.AccuWeather.async_get_current_conditions", + return_value=current, + ) as mock_current, patch( + "homeassistant.components.accuweather.AccuWeather.async_get_forecast", + return_value=forecast, + ) as mock_forecast: + await hass.services.async_call( + "homeassistant", + "update_entity", + {ATTR_ENTITY_ID: ["weather.home"]}, + blocking=True, + ) + assert mock_current.call_count == 1 + assert mock_forecast.call_count == 1 + + +async def test_unsupported_condition_icon_data(hass): + """Test with unsupported condition icon data.""" + await init_integration(hass, forecast=True, unsupported_icon=True) + + state = hass.states.get("weather.home") + assert state.attributes.get(ATTR_FORECAST_CONDITION) is None diff --git a/tests/fixtures/accuweather/forecast_data.json b/tests/fixtures/accuweather/forecast_data.json new file mode 100644 index 00000000000..2de06dc66f4 --- /dev/null +++ b/tests/fixtures/accuweather/forecast_data.json @@ -0,0 +1,981 @@ +[ + { + "Date": "2020-07-26T07:00:00+02:00", + "EpochDate": 1595739600, + "HoursOfSun": 7.2, + "DegreeDaySummary": { + "Heating": { + "Value": 0.0, + "Unit": "C", + "UnitType": 17 + }, + "Cooling": { + "Value": 4.0, + "Unit": "C", + "UnitType": 17 + } + }, + "Ozone": { + "Value": 32, + "Category": "Good", + "CategoryValue": 1 + }, + "Grass": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Mold": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Ragweed": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Tree": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "UVIndex": { + "Value": 5, + "Category": "Moderate", + "CategoryValue": 2 + }, + "TemperatureMin": { + "Value": 15.4, + "Unit": "C", + "UnitType": 17 + }, + "TemperatureMax": { + "Value": 29.5, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMin": { + "Value": 15.1, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMax": { + "Value": 29.8, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMin": { + "Value": 15.1, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMax": { + "Value": 28.0, + "Unit": "C", + "UnitType": 17 + }, + "IconDay": 17, + "IconPhraseDay": "Partly sunny w/ t-storms", + "HasPrecipitationDay": true, + "PrecipitationTypeDay": "Rain", + "PrecipitationIntensityDay": "Moderate", + "ShortPhraseDay": "A shower and t-storm around", + "LongPhraseDay": "Clouds and sunshine with a couple of showers and a thunderstorm around late this afternoon", + "PrecipitationProbabilityDay": 60, + "ThunderstormProbabilityDay": 40, + "RainProbabilityDay": 60, + "SnowProbabilityDay": 0, + "IceProbabilityDay": 0, + "WindDay": { + "Speed": { + "Value": 13.0, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 166, + "Localized": "SSE", + "English": "SSE" + } + }, + "WindGustDay": { + "Speed": { + "Value": 29.6, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 178, + "Localized": "S", + "English": "S" + } + }, + "TotalLiquidDay": { + "Value": 2.5, + "Unit": "mm", + "UnitType": 3 + }, + "RainDay": { + "Value": 2.5, + "Unit": "mm", + "UnitType": 3 + }, + "SnowDay": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationDay": 1.0, + "HoursOfRainDay": 1.0, + "HoursOfSnowDay": 0.0, + "HoursOfIceDay": 0.0, + "CloudCoverDay": 58, + "IconNight": 41, + "IconPhraseNight": "Partly cloudy w/ t-storms", + "HasPrecipitationNight": true, + "PrecipitationTypeNight": "Rain", + "PrecipitationIntensityNight": "Moderate", + "ShortPhraseNight": "Partly cloudy", + "LongPhraseNight": "Partly cloudy", + "PrecipitationProbabilityNight": 57, + "ThunderstormProbabilityNight": 40, + "RainProbabilityNight": 57, + "SnowProbabilityNight": 0, + "IceProbabilityNight": 0, + "WindNight": { + "Speed": { + "Value": 7.4, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 289, + "Localized": "WNW", + "English": "WNW" + } + }, + "WindGustNight": { + "Speed": { + "Value": 18.5, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 256, + "Localized": "WSW", + "English": "WSW" + } + }, + "TotalLiquidNight": { + "Value": 2.3, + "Unit": "mm", + "UnitType": 3 + }, + "RainNight": { + "Value": 2.3, + "Unit": "mm", + "UnitType": 3 + }, + "SnowNight": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationNight": 1.0, + "HoursOfRainNight": 1.0, + "HoursOfSnowNight": 0.0, + "HoursOfIceNight": 0.0, + "CloudCoverNight": 65 + }, + { + "Date": "2020-07-27T07:00:00+02:00", + "EpochDate": 1595826000, + "HoursOfSun": 7.4, + "DegreeDaySummary": { + "Heating": { + "Value": 0.0, + "Unit": "C", + "UnitType": 17 + }, + "Cooling": { + "Value": 3.0, + "Unit": "C", + "UnitType": 17 + } + }, + "Ozone": { + "Value": 39, + "Category": "Good", + "CategoryValue": 1 + }, + "Grass": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Mold": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Ragweed": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Tree": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "UVIndex": { + "Value": 7, + "Category": "High", + "CategoryValue": 3 + }, + "TemperatureMin": { + "Value": 15.9, + "Unit": "C", + "UnitType": 17 + }, + "TemperatureMax": { + "Value": 26.2, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMin": { + "Value": 15.8, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMax": { + "Value": 28.9, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMin": { + "Value": 15.8, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMax": { + "Value": 25.0, + "Unit": "C", + "UnitType": 17 + }, + "IconDay": 4, + "IconPhraseDay": "Intermittent clouds", + "HasPrecipitationDay": false, + "ShortPhraseDay": "Clouds and sun", + "LongPhraseDay": "Clouds and sun", + "PrecipitationProbabilityDay": 25, + "ThunderstormProbabilityDay": 24, + "RainProbabilityDay": 25, + "SnowProbabilityDay": 0, + "IceProbabilityDay": 0, + "WindDay": { + "Speed": { + "Value": 9.3, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 297, + "Localized": "WNW", + "English": "WNW" + } + }, + "WindGustDay": { + "Speed": { + "Value": 14.8, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 317, + "Localized": "NW", + "English": "NW" + } + }, + "TotalLiquidDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowDay": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationDay": 0.0, + "HoursOfRainDay": 0.0, + "HoursOfSnowDay": 0.0, + "HoursOfIceDay": 0.0, + "CloudCoverDay": 52, + "IconNight": 36, + "IconPhraseNight": "Intermittent clouds", + "HasPrecipitationNight": false, + "ShortPhraseNight": "Partly cloudy", + "LongPhraseNight": "Partly cloudy", + "PrecipitationProbabilityNight": 6, + "ThunderstormProbabilityNight": 0, + "RainProbabilityNight": 6, + "SnowProbabilityNight": 0, + "IceProbabilityNight": 0, + "WindNight": { + "Speed": { + "Value": 7.4, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 162, + "Localized": "SSE", + "English": "SSE" + } + }, + "WindGustNight": { + "Speed": { + "Value": 14.8, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 175, + "Localized": "S", + "English": "S" + } + }, + "TotalLiquidNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowNight": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationNight": 0.0, + "HoursOfRainNight": 0.0, + "HoursOfSnowNight": 0.0, + "HoursOfIceNight": 0.0, + "CloudCoverNight": 63 + }, + { + "Date": "2020-07-28T07:00:00+02:00", + "EpochDate": 1595912400, + "HoursOfSun": 5.7, + "DegreeDaySummary": { + "Heating": { + "Value": 0.0, + "Unit": "C", + "UnitType": 17 + }, + "Cooling": { + "Value": 6.0, + "Unit": "C", + "UnitType": 17 + } + }, + "Ozone": { + "Value": 29, + "Category": "Good", + "CategoryValue": 1 + }, + "Grass": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Mold": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Ragweed": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Tree": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "UVIndex": { + "Value": 7, + "Category": "High", + "CategoryValue": 3 + }, + "TemperatureMin": { + "Value": 16.8, + "Unit": "C", + "UnitType": 17 + }, + "TemperatureMax": { + "Value": 31.7, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMin": { + "Value": 16.7, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMax": { + "Value": 31.6, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMin": { + "Value": 16.7, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMax": { + "Value": 30.0, + "Unit": "C", + "UnitType": 17 + }, + "IconDay": 4, + "IconPhraseDay": "Intermittent clouds", + "HasPrecipitationDay": false, + "ShortPhraseDay": "Partly sunny and very warm", + "LongPhraseDay": "Very warm with a blend of sun and clouds", + "PrecipitationProbabilityDay": 10, + "ThunderstormProbabilityDay": 4, + "RainProbabilityDay": 10, + "SnowProbabilityDay": 0, + "IceProbabilityDay": 0, + "WindDay": { + "Speed": { + "Value": 16.7, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 198, + "Localized": "SSW", + "English": "SSW" + } + }, + "WindGustDay": { + "Speed": { + "Value": 24.1, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 198, + "Localized": "SSW", + "English": "SSW" + } + }, + "TotalLiquidDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowDay": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationDay": 0.0, + "HoursOfRainDay": 0.0, + "HoursOfSnowDay": 0.0, + "HoursOfIceDay": 0.0, + "CloudCoverDay": 65, + "IconNight": 36, + "IconPhraseNight": "Intermittent clouds", + "HasPrecipitationNight": false, + "ShortPhraseNight": "Partly cloudy", + "LongPhraseNight": "Partly cloudy", + "PrecipitationProbabilityNight": 25, + "ThunderstormProbabilityNight": 24, + "RainProbabilityNight": 25, + "SnowProbabilityNight": 0, + "IceProbabilityNight": 0, + "WindNight": { + "Speed": { + "Value": 9.3, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 265, + "Localized": "W", + "English": "W" + } + }, + "WindGustNight": { + "Speed": { + "Value": 22.2, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 271, + "Localized": "W", + "English": "W" + } + }, + "TotalLiquidNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowNight": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationNight": 0.0, + "HoursOfRainNight": 0.0, + "HoursOfSnowNight": 0.0, + "HoursOfIceNight": 0.0, + "CloudCoverNight": 53 + }, + { + "Date": "2020-07-29T07:00:00+02:00", + "EpochDate": 1595998800, + "HoursOfSun": 9.4, + "DegreeDaySummary": { + "Heating": { + "Value": 0.0, + "Unit": "C", + "UnitType": 17 + }, + "Cooling": { + "Value": 0.0, + "Unit": "C", + "UnitType": 17 + } + }, + "Ozone": { + "Value": 18, + "Category": "Good", + "CategoryValue": 1 + }, + "Grass": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Mold": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Ragweed": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Tree": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "UVIndex": { + "Value": 6, + "Category": "High", + "CategoryValue": 3 + }, + "TemperatureMin": { + "Value": 11.7, + "Unit": "C", + "UnitType": 17 + }, + "TemperatureMax": { + "Value": 24.0, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMin": { + "Value": 10.1, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMax": { + "Value": 26.5, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMin": { + "Value": 10.1, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMax": { + "Value": 22.5, + "Unit": "C", + "UnitType": 17 + }, + "IconDay": 3, + "IconPhraseDay": "Partly sunny", + "HasPrecipitationDay": false, + "ShortPhraseDay": "Cooler with partial sunshine", + "LongPhraseDay": "Cooler with partial sunshine", + "PrecipitationProbabilityDay": 9, + "ThunderstormProbabilityDay": 0, + "RainProbabilityDay": 9, + "SnowProbabilityDay": 0, + "IceProbabilityDay": 0, + "WindDay": { + "Speed": { + "Value": 13.0, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 293, + "Localized": "WNW", + "English": "WNW" + } + }, + "WindGustDay": { + "Speed": { + "Value": 24.1, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 271, + "Localized": "W", + "English": "W" + } + }, + "TotalLiquidDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowDay": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationDay": 0.0, + "HoursOfRainDay": 0.0, + "HoursOfSnowDay": 0.0, + "HoursOfIceDay": 0.0, + "CloudCoverDay": 45, + "IconNight": 34, + "IconPhraseNight": "Mostly clear", + "HasPrecipitationNight": false, + "ShortPhraseNight": "Mainly clear", + "LongPhraseNight": "Mainly clear", + "PrecipitationProbabilityNight": 1, + "ThunderstormProbabilityNight": 0, + "RainProbabilityNight": 1, + "SnowProbabilityNight": 0, + "IceProbabilityNight": 0, + "WindNight": { + "Speed": { + "Value": 11.1, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 264, + "Localized": "W", + "English": "W" + } + }, + "WindGustNight": { + "Speed": { + "Value": 18.5, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 266, + "Localized": "W", + "English": "W" + } + }, + "TotalLiquidNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowNight": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationNight": 0.0, + "HoursOfRainNight": 0.0, + "HoursOfSnowNight": 0.0, + "HoursOfIceNight": 0.0, + "CloudCoverNight": 27 + }, + { + "Date": "2020-07-30T07:00:00+02:00", + "EpochDate": 1596085200, + "HoursOfSun": 9.2, + "DegreeDaySummary": { + "Heating": { + "Value": 1.0, + "Unit": "C", + "UnitType": 17 + }, + "Cooling": { + "Value": 0.0, + "Unit": "C", + "UnitType": 17 + } + }, + "Ozone": { + "Value": 14, + "Category": "Good", + "CategoryValue": 1 + }, + "Grass": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Mold": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Ragweed": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "Tree": { + "Value": 0, + "Category": "Low", + "CategoryValue": 1 + }, + "UVIndex": { + "Value": 7, + "Category": "High", + "CategoryValue": 3 + }, + "TemperatureMin": { + "Value": 12.2, + "Unit": "C", + "UnitType": 17 + }, + "TemperatureMax": { + "Value": 21.4, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMin": { + "Value": 11.3, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureMax": { + "Value": 22.2, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMin": { + "Value": 11.3, + "Unit": "C", + "UnitType": 17 + }, + "RealFeelTemperatureShadeMax": { + "Value": 19.5, + "Unit": "C", + "UnitType": 17 + }, + "IconDay": 4, + "IconPhraseDay": "Intermittent clouds", + "HasPrecipitationDay": false, + "ShortPhraseDay": "Clouds and sun", + "LongPhraseDay": "Intervals of clouds and sunshine", + "PrecipitationProbabilityDay": 1, + "ThunderstormProbabilityDay": 0, + "RainProbabilityDay": 1, + "SnowProbabilityDay": 0, + "IceProbabilityDay": 0, + "WindDay": { + "Speed": { + "Value": 18.5, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 280, + "Localized": "W", + "English": "W" + } + }, + "WindGustDay": { + "Speed": { + "Value": 27.8, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 273, + "Localized": "W", + "English": "W" + } + }, + "TotalLiquidDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowDay": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceDay": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationDay": 0.0, + "HoursOfRainDay": 0.0, + "HoursOfSnowDay": 0.0, + "HoursOfIceDay": 0.0, + "CloudCoverDay": 50, + "IconNight": 34, + "IconPhraseNight": "Mostly clear", + "HasPrecipitationNight": false, + "ShortPhraseNight": "Mostly clear", + "LongPhraseNight": "Mostly clear", + "PrecipitationProbabilityNight": 3, + "ThunderstormProbabilityNight": 0, + "RainProbabilityNight": 3, + "SnowProbabilityNight": 0, + "IceProbabilityNight": 0, + "WindNight": { + "Speed": { + "Value": 9.3, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 272, + "Localized": "W", + "English": "W" + } + }, + "WindGustNight": { + "Speed": { + "Value": 18.5, + "Unit": "km/h", + "UnitType": 7 + }, + "Direction": { + "Degrees": 274, + "Localized": "W", + "English": "W" + } + }, + "TotalLiquidNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "RainNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "SnowNight": { + "Value": 0.0, + "Unit": "cm", + "UnitType": 4 + }, + "IceNight": { + "Value": 0.0, + "Unit": "mm", + "UnitType": 3 + }, + "HoursOfPrecipitationNight": 0.0, + "HoursOfRainNight": 0.0, + "HoursOfSnowNight": 0.0, + "HoursOfIceNight": 0.0, + "CloudCoverNight": 13 + } +] \ No newline at end of file