From eafddaae83d34d11b5cc7a92545b349c2b5df342 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Thu, 22 Jun 2023 23:10:36 +0200 Subject: [PATCH] Implement Cloud coverage in Weather entity component (#95068) --- homeassistant/components/weather/__init__.py | 14 +++++++++++++- homeassistant/components/weather/const.py | 1 + homeassistant/components/weather/strings.json | 3 +++ tests/components/weather/test_init.py | 16 ++++++++++++---- .../custom_components/test/weather.py | 7 +++++++ 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py index 5002cf47bb9..351f61600f9 100644 --- a/homeassistant/components/weather/__init__.py +++ b/homeassistant/components/weather/__init__.py @@ -31,6 +31,7 @@ from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM from .const import ( ATTR_WEATHER_APPARENT_TEMPERATURE, + ATTR_WEATHER_CLOUD_COVERAGE, ATTR_WEATHER_DEW_POINT, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_OZONE, @@ -87,6 +88,7 @@ ATTR_FORECAST_NATIVE_WIND_SPEED: Final = "native_wind_speed" ATTR_FORECAST_WIND_SPEED: Final = "wind_speed" ATTR_FORECAST_NATIVE_DEW_POINT: Final = "native_dew_point" ATTR_FORECAST_DEW_POINT: Final = "dew_point" +ATTR_FORECAST_CLOUD_COVERAGE: Final = "cloud_coverage" ENTITY_ID_FORMAT = DOMAIN + ".{}" @@ -124,6 +126,7 @@ class Forecast(TypedDict, total=False): condition: str | None datetime: Required[str] precipitation_probability: int | None + cloud_coverage: int | None native_precipitation: float | None precipitation: None native_pressure: float | None @@ -173,6 +176,7 @@ class WeatherEntity(Entity): _attr_forecast: list[Forecast] | None = None _attr_humidity: float | None = None _attr_ozone: float | None = None + _attr_cloud_coverage: int | None = None _attr_precision: float _attr_pressure: None = ( None # Provide backwards compatibility. Use _attr_native_pressure @@ -481,6 +485,11 @@ class WeatherEntity(Entity): """Return the ozone level.""" return self._attr_ozone + @property + def cloud_coverage(self) -> float | None: + """Return the Cloud coverage in %.""" + return self._attr_cloud_coverage + @final @property def visibility(self) -> float | None: @@ -596,7 +605,7 @@ class WeatherEntity(Entity): @final @property - def state_attributes(self) -> dict[str, Any]: + def state_attributes(self) -> dict[str, Any]: # noqa: C901 """Return the state attributes, converted. Attributes are configured from native units to user-configured units. @@ -655,6 +664,9 @@ class WeatherEntity(Entity): if (ozone := self.ozone) is not None: data[ATTR_WEATHER_OZONE] = ozone + if (cloud_coverage := self.cloud_coverage) is not None: + data[ATTR_WEATHER_CLOUD_COVERAGE] = cloud_coverage + if (pressure := self.native_pressure) is not None: from_unit = self.native_pressure_unit or self._default_pressure_unit to_unit = self._pressure_unit diff --git a/homeassistant/components/weather/const.py b/homeassistant/components/weather/const.py index 980a6ced2d8..a0b3ad58750 100644 --- a/homeassistant/components/weather/const.py +++ b/homeassistant/components/weather/const.py @@ -32,6 +32,7 @@ ATTR_WEATHER_WIND_BEARING = "wind_bearing" ATTR_WEATHER_WIND_SPEED = "wind_speed" ATTR_WEATHER_WIND_SPEED_UNIT = "wind_speed_unit" ATTR_WEATHER_PRECIPITATION_UNIT = "precipitation_unit" +ATTR_WEATHER_CLOUD_COVERAGE = "cloud_coverage" DOMAIN: Final = "weather" diff --git a/homeassistant/components/weather/strings.json b/homeassistant/components/weather/strings.json index 7f05594d2dd..33507191e01 100644 --- a/homeassistant/components/weather/strings.json +++ b/homeassistant/components/weather/strings.json @@ -30,6 +30,9 @@ "ozone": { "name": "Ozone" }, + "cloud_coverage": { + "name": "Cloud coverage" + }, "precipitation_unit": { "name": "Precipitation unit" }, diff --git a/tests/components/weather/test_init.py b/tests/components/weather/test_init.py index d22bf2c64be..bdabc7e9c08 100644 --- a/tests/components/weather/test_init.py +++ b/tests/components/weather/test_init.py @@ -31,7 +31,10 @@ from homeassistant.components.weather import ( WeatherEntity, round_temperature, ) -from homeassistant.components.weather.const import ATTR_WEATHER_DEW_POINT +from homeassistant.components.weather.const import ( + ATTR_WEATHER_CLOUD_COVERAGE, + ATTR_WEATHER_DEW_POINT, +) from homeassistant.const import ( ATTR_FRIENDLY_NAME, PRECISION_HALVES, @@ -522,21 +525,26 @@ async def test_precipitation_no_unit( ) -async def test_wind_bearing_and_ozone( +async def test_wind_bearing_ozone_and_cloud_coverage( hass: HomeAssistant, enable_custom_integrations: None, ) -> None: - """Test wind bearing.""" + """Test wind bearing, ozone and cloud coverage.""" wind_bearing_value = 180 ozone_value = 10 + cloud_coverage = 75 entity0 = await create_entity( - hass, wind_bearing=wind_bearing_value, ozone=ozone_value + hass, + wind_bearing=wind_bearing_value, + ozone=ozone_value, + cloud_coverage=cloud_coverage, ) state = hass.states.get(entity0.entity_id) assert float(state.attributes[ATTR_WEATHER_WIND_BEARING]) == 180 assert float(state.attributes[ATTR_WEATHER_OZONE]) == 10 + assert float(state.attributes[ATTR_WEATHER_CLOUD_COVERAGE]) == 75 async def test_none_forecast( diff --git a/tests/testing_config/custom_components/test/weather.py b/tests/testing_config/custom_components/test/weather.py index 121d43c2996..344e879dc08 100644 --- a/tests/testing_config/custom_components/test/weather.py +++ b/tests/testing_config/custom_components/test/weather.py @@ -5,6 +5,7 @@ Call init before using it in your tests to ensure clean test data. from __future__ import annotations from homeassistant.components.weather import ( + ATTR_FORECAST_CLOUD_COVERAGE, ATTR_FORECAST_NATIVE_APPARENT_TEMP, ATTR_FORECAST_NATIVE_DEW_POINT, ATTR_FORECAST_NATIVE_PRECIPITATION, @@ -98,6 +99,11 @@ class MockWeather(MockEntity, WeatherEntity): """Return the ozone level.""" return self._handle("ozone") + @property + def cloud_coverage(self) -> float | None: + """Return the cloud coverage in %.""" + return self._handle("cloud_coverage") + @property def native_visibility(self) -> float | None: """Return the visibility.""" @@ -210,6 +216,7 @@ class MockWeatherMockForecast(MockWeather): ATTR_FORECAST_NATIVE_APPARENT_TEMP: self.native_apparent_temperature, ATTR_FORECAST_NATIVE_TEMP_LOW: self.native_temperature, ATTR_FORECAST_NATIVE_DEW_POINT: self.native_dew_point, + ATTR_FORECAST_CLOUD_COVERAGE: self.cloud_coverage, ATTR_FORECAST_NATIVE_PRESSURE: self.native_pressure, ATTR_FORECAST_NATIVE_WIND_SPEED: self.native_wind_speed, ATTR_FORECAST_WIND_BEARING: self.wind_bearing,