From aaecfac8603b2e769248ac8e57718f86c25e2ab3 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 29 Sep 2025 20:57:34 +0000 Subject: [PATCH 1/6] Add fire sensors to smhi --- homeassistant/components/smhi/coordinator.py | 31 +++++- homeassistant/components/smhi/icons.json | 30 ++++++ homeassistant/components/smhi/sensor.py | 100 ++++++++++++++++++- homeassistant/components/smhi/strings.json | 54 ++++++++++ 4 files changed, 212 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/smhi/coordinator.py b/homeassistant/components/smhi/coordinator.py index d8e85917db515..b9ce21b001c16 100644 --- a/homeassistant/components/smhi/coordinator.py +++ b/homeassistant/components/smhi/coordinator.py @@ -5,7 +5,14 @@ import asyncio from dataclasses import dataclass -from pysmhi import SMHIForecast, SmhiForecastException, SMHIPointForecast +from pysmhi import ( + SMHIFireForecast, + SmhiFireForecastException, + SMHIFirePointForecast, + SMHIForecast, + SmhiForecastException, + SMHIPointForecast, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE @@ -25,6 +32,8 @@ class SMHIForecastData: daily: list[SMHIForecast] hourly: list[SMHIForecast] twice_daily: list[SMHIForecast] + fire_daily: list[SMHIFireForecast] + fire_hourly: list[SMHIFireForecast] class SMHIDataUpdateCoordinator(DataUpdateCoordinator[SMHIForecastData]): @@ -46,6 +55,11 @@ def __init__(self, hass: HomeAssistant, config_entry: SMHIConfigEntry) -> None: config_entry.data[CONF_LOCATION][CONF_LATITUDE], session=aiohttp_client.async_get_clientsession(hass), ) + self._smhi_fire_api = SMHIFirePointForecast( + config_entry.data[CONF_LOCATION][CONF_LONGITUDE], + config_entry.data[CONF_LOCATION][CONF_LATITUDE], + session=aiohttp_client.async_get_clientsession(hass), + ) async def _async_update_data(self) -> SMHIForecastData: """Fetch data from SMHI.""" @@ -56,7 +70,13 @@ async def _async_update_data(self) -> SMHIForecastData: _forecast_twice_daily = ( await self._smhi_api.async_get_twice_daily_forecast() ) - except SmhiForecastException as ex: + _forecast_fire_daily = ( + await self._smhi_fire_api.async_get_daily_forecast() + ) + _forecast_fire_hourly = ( + await self._smhi_fire_api.async_get_hourly_forecast() + ) + except (SmhiForecastException, SmhiFireForecastException) as ex: raise UpdateFailed( "Failed to retrieve the forecast from the SMHI API" ) from ex @@ -65,9 +85,16 @@ async def _async_update_data(self) -> SMHIForecastData: daily=_forecast_daily, hourly=_forecast_hourly, twice_daily=_forecast_twice_daily, + fire_daily=_forecast_fire_daily, + fire_hourly=_forecast_fire_hourly, ) @property def current(self) -> SMHIForecast: """Return the current metrics.""" return self.data.daily[0] + + @property + def fire_current(self) -> SMHIFireForecast: + """Return the current fire metrics.""" + return self.data.fire_daily[0] diff --git a/homeassistant/components/smhi/icons.json b/homeassistant/components/smhi/icons.json index 5eaa0b78ed144..568a195b25507 100644 --- a/homeassistant/components/smhi/icons.json +++ b/homeassistant/components/smhi/icons.json @@ -1,12 +1,39 @@ { "entity": { "sensor": { + "bui": { + "default": "mdi:grass" + }, + "dc": { + "default": "mdi:grass" + }, + "dmc": { + "default": "mdi:grass" + }, + "ffmc": { + "default": "mdi:grass" + }, + "forestdry": { + "default": "mdi:forest" + }, "frozen_precipitation": { "default": "mdi:weather-snowy-rainy" }, + "fwi": { + "default": "mdi:pine-tree-fire" + }, + "fwiindex": { + "default": "mdi:pine-tree-fire" + }, + "grassfire": { + "default": "mdi:fire-circle" + }, "high_cloud": { "default": "mdi:cloud-arrow-up" }, + "isi": { + "default": "mdi:grass" + }, "low_cloud": { "default": "mdi:cloud-arrow-down" }, @@ -16,6 +43,9 @@ "precipitation_category": { "default": "mdi:weather-pouring" }, + "rn": { + "default": "mdi:grass" + }, "thunder": { "default": "mdi:weather-lightning" }, diff --git a/homeassistant/components/smhi/sensor.py b/homeassistant/components/smhi/sensor.py index bba207c0f0920..383acdcb862b1 100644 --- a/homeassistant/components/smhi/sensor.py +++ b/homeassistant/components/smhi/sensor.py @@ -10,8 +10,15 @@ SensorDeviceClass, SensorEntity, SensorEntityDescription, + SensorStateClass, +) +from homeassistant.const import ( + CONF_LATITUDE, + CONF_LOCATION, + CONF_LONGITUDE, + PERCENTAGE, + UnitOfSpeed, ) -from homeassistant.const import CONF_LATITUDE, CONF_LOCATION, CONF_LONGITUDE, PERCENTAGE from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType @@ -32,6 +39,14 @@ def get_percentage_values(entity: SMHISensor, key: str) -> int | None: return None +def get_fire_index_value(entity: SMHISensor, key: str) -> str | None: + """Return index value as string.""" + value: int | None = entity.coordinator.fire_current.get(key) # type: ignore[assignment] + if value is not None and value > 0: + return str(value) + return None + + @dataclass(frozen=True, kw_only=True) class SMHISensorEntityDescription(SensorEntityDescription): """Describes SMHI sensor entity.""" @@ -89,6 +104,89 @@ class SMHISensorEntityDescription(SensorEntityDescription): value_fn=lambda entity: get_percentage_values(entity, "frozen_precipitation"), native_unit_of_measurement=PERCENTAGE, ), + # Fire sensors + SMHISensorEntityDescription( + key="fwiindex", + translation_key="fwiindex", + value_fn=lambda entity: str(get_fire_index_value(entity, "fwiindex")), + device_class=SensorDeviceClass.ENUM, + options=["1", "2", "3", "4", "5", "6"], + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="fwi", + translation_key="fwi", + value_fn=lambda entity: entity.coordinator.fire_current.get("fwi"), + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="isi", + translation_key="isi", + value_fn=lambda entity: entity.coordinator.fire_current.get("isi"), + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="bui", # codespell:ignore bui + translation_key="bui", # codespell:ignore bui + value_fn=( + lambda entity: entity.coordinator.fire_current.get( + "bui" # codespell:ignore bui + ) + ), + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="ffmc", + translation_key="ffmc", + value_fn=lambda entity: entity.coordinator.fire_current.get("ffmc"), + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="dmc", + translation_key="dmc", + value_fn=lambda entity: entity.coordinator.fire_current.get("dmc"), + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="dc", + translation_key="dc", + value_fn=lambda entity: entity.coordinator.fire_current.get("dc"), + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="grassfire", + translation_key="grassfire", + value_fn=lambda entity: str(get_fire_index_value(entity, "grassfire")), + device_class=SensorDeviceClass.ENUM, + options=["1", "2", "3", "4", "5", "6"], + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="rn", + translation_key="rn", + value_fn=lambda entity: entity.coordinator.fire_current.get("rn"), + device_class=SensorDeviceClass.SPEED, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfSpeed.METERS_PER_MINUTE, + entity_registry_enabled_default=False, + ), + SMHISensorEntityDescription( + key="forestdry", + translation_key="forestdry", + value_fn=lambda entity: str(get_fire_index_value(entity, "forestdry")), + device_class=SensorDeviceClass.ENUM, + options=["1", "2", "3", "4", "5", "6"], + state_class=SensorStateClass.MEASUREMENT, + entity_registry_enabled_default=False, + ), ) diff --git a/homeassistant/components/smhi/strings.json b/homeassistant/components/smhi/strings.json index bc1362fd468d3..1165a68bcf652 100644 --- a/homeassistant/components/smhi/strings.json +++ b/homeassistant/components/smhi/strings.json @@ -26,12 +26,63 @@ }, "entity": { "sensor": { + "bui": { + "name": "Build Up Index" + }, + "dc": { + "name": "Drought Code" + }, + "dmc": { + "name": "Duff Moisture Code" + }, + "ffmc": { + "name": "Fine Fuel Moisture Code" + }, + "forestdry": { + "name": "Fuel drying", + "state": { + "1": "Very wet", + "2": "Wet", + "3": "Moderate wet", + "4": "Dry", + "5": "Very dry", + "6": "Extemely dry" + } + }, "frozen_precipitation": { "name": "Frozen precipitation" }, + "fwi": { + "name": "Fire Weather Index" + }, + "fwiindex": { + "name": "FWI-index", + "state": { + "1": "Very low risk", + "2": "Low risk", + "3": "Moderate risk", + "4": "High risk", + "5": "Very high risk", + "6": "Extremely high risk" + } + }, + "grassfire": { + "name": "Highest grass fire risk", + "state": { + "1": "Snow cover", + "2": "Grass fire season over", + "3": "Low", + "4": "Moderate", + "5": "High", + "6": "Very high" + } + }, "high_cloud": { "name": "High cloud coverage" }, + "isi": { + "name": "Initial Spread Index" + }, "low_cloud": { "name": "Low cloud coverage" }, @@ -50,6 +101,9 @@ "6": "Freezing drizzle" } }, + "rn": { + "name": "Potential rate of spread" + }, "thunder": { "name": "Thunder probability" }, From c62414950a15936681414d393826321ee3581b19 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 3 Oct 2025 18:15:02 +0000 Subject: [PATCH 2/6] Fixes --- homeassistant/components/smhi/sensor.py | 8 +- tests/components/smhi/conftest.py | 56 +- tests/components/smhi/fixtures/smhi_fire.json | 1365 +++++++++++++++++ .../smhi/snapshots/test_sensor.ambr | 528 +++++++ tests/components/smhi/test_config_flow.py | 4 + tests/components/smhi/test_init.py | 7 +- tests/components/smhi/test_weather.py | 15 +- 7 files changed, 1972 insertions(+), 11 deletions(-) create mode 100644 tests/components/smhi/fixtures/smhi_fire.json diff --git a/homeassistant/components/smhi/sensor.py b/homeassistant/components/smhi/sensor.py index 383acdcb862b1..2d6e069ba810d 100644 --- a/homeassistant/components/smhi/sensor.py +++ b/homeassistant/components/smhi/sensor.py @@ -43,7 +43,7 @@ def get_fire_index_value(entity: SMHISensor, key: str) -> str | None: """Return index value as string.""" value: int | None = entity.coordinator.fire_current.get(key) # type: ignore[assignment] if value is not None and value > 0: - return str(value) + return str(int(value)) return None @@ -108,7 +108,7 @@ class SMHISensorEntityDescription(SensorEntityDescription): SMHISensorEntityDescription( key="fwiindex", translation_key="fwiindex", - value_fn=lambda entity: str(get_fire_index_value(entity, "fwiindex")), + value_fn=lambda entity: get_fire_index_value(entity, "fwiindex"), device_class=SensorDeviceClass.ENUM, options=["1", "2", "3", "4", "5", "6"], state_class=SensorStateClass.MEASUREMENT, @@ -163,7 +163,7 @@ class SMHISensorEntityDescription(SensorEntityDescription): SMHISensorEntityDescription( key="grassfire", translation_key="grassfire", - value_fn=lambda entity: str(get_fire_index_value(entity, "grassfire")), + value_fn=lambda entity: get_fire_index_value(entity, "grassfire"), device_class=SensorDeviceClass.ENUM, options=["1", "2", "3", "4", "5", "6"], state_class=SensorStateClass.MEASUREMENT, @@ -181,7 +181,7 @@ class SMHISensorEntityDescription(SensorEntityDescription): SMHISensorEntityDescription( key="forestdry", translation_key="forestdry", - value_fn=lambda entity: str(get_fire_index_value(entity, "forestdry")), + value_fn=lambda entity: get_fire_index_value(entity, "forestdry"), device_class=SensorDeviceClass.ENUM, options=["1", "2", "3", "4", "5", "6"], state_class=SensorStateClass.MEASUREMENT, diff --git a/tests/components/smhi/conftest.py b/tests/components/smhi/conftest.py index 82982a7c82f63..3f588ddd4c1e0 100644 --- a/tests/components/smhi/conftest.py +++ b/tests/components/smhi/conftest.py @@ -7,6 +7,7 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock, patch +from pysmhi.smhi_fire_forecast import SMHIFirePointForecast from pysmhi.smhi_forecast import SMHIForecast, SMHIPointForecast import pytest @@ -40,6 +41,7 @@ async def patch_platform_constant() -> list[Platform]: async def load_int( hass: HomeAssistant, mock_client: SMHIPointForecast, + mock_fire_client: SMHIFirePointForecast, load_platforms: list[Platform], ) -> MockConfigEntry: """Set up the SMHI integration.""" @@ -87,6 +89,23 @@ async def get_client( yield client +@pytest.fixture(name="mock_fire_client") +async def get_fire_client( + hass: HomeAssistant, + get_fire_data: tuple[list[SMHIForecast], list[SMHIForecast], list[SMHIForecast]], +) -> AsyncGenerator[MagicMock]: + """Mock SMHIFirePointForecast client.""" + + with patch( + "homeassistant.components.smhi.coordinator.SMHIFirePointForecast", + autospec=True, + ) as mock_client: + client = mock_client.return_value + client.async_get_daily_forecast.return_value = get_fire_data[0] + client.async_get_hourly_forecast.return_value = get_fire_data[1] + yield client + + @pytest.fixture(name="get_data") async def get_data_from_library( hass: HomeAssistant, @@ -112,9 +131,41 @@ async def get_data_from_library( await client._api._session.close() +@pytest.fixture(name="get_fire_data") +async def get_fire_data_from_library( + hass: HomeAssistant, + aioclient_mock: AiohttpClientMocker, + load_fire_json: dict[str, Any], +) -> AsyncGenerator[tuple[list[SMHIForecast], list[SMHIForecast], list[SMHIForecast]]]: + """Get data from api.""" + client = SMHIFirePointForecast( + TEST_CONFIG[CONF_LOCATION][CONF_LONGITUDE], + TEST_CONFIG[CONF_LOCATION][CONF_LATITUDE], + aioclient_mock.create_session(hass.loop), + ) + with patch.object( + client._api, + "async_get_data", + return_value=load_fire_json, + ): + data_daily = await client.async_get_daily_forecast() + data_hourly = await client.async_get_hourly_forecast() + + yield (data_daily, data_hourly) + await client._api._session.close() + + +@pytest.fixture(name="load_fire_json") +def load_fire_json_from_fixture( + load_data: tuple[str, str, str, str], +) -> dict[str, Any]: + """Load fixture with json data and return.""" + return json.loads(load_data[3]) + + @pytest.fixture(name="load_json") def load_json_from_fixture( - load_data: tuple[str, str, str], + load_data: tuple[str, str, str, str], to_load: int, ) -> dict[str, Any]: """Load fixture with json data and return.""" @@ -122,12 +173,13 @@ def load_json_from_fixture( @pytest.fixture(name="load_data", scope="package") -def load_data_from_fixture() -> tuple[str, str, str]: +def load_data_from_fixture() -> tuple[str, str, str, str]: """Load fixture with fixture data and return.""" return ( load_fixture("smhi.json", "smhi"), load_fixture("smhi_night.json", "smhi"), load_fixture("smhi_short.json", "smhi"), + load_fixture("smhi_fire.json", "smhi"), ) diff --git a/tests/components/smhi/fixtures/smhi_fire.json b/tests/components/smhi/fixtures/smhi_fire.json new file mode 100644 index 0000000000000..4069dc7a5d835 --- /dev/null +++ b/tests/components/smhi/fixtures/smhi_fire.json @@ -0,0 +1,1365 @@ +{ + "approvedTime": "2025-10-03T10:04:40Z", + "referenceTime": "2025-10-01T12:00:00Z", + "geometry": { "type": "Point", "coordinates": [[15.990068, 57.997072]] }, + "timeSeries": [ + { + "validTime": "2025-10-02T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [3.2] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.3] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [37.6] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [80.6] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [20.7] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [505.5] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [3.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [12.9] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [163] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [0.6] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [59] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [0.1] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [0.9] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.9] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.9] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.9] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.9] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [3.7] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [17.2] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [54.2] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [57.8] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [71.0] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [77.7] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [0.1] + } + ] + }, + { + "validTime": "2025-10-03T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [2.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [6.6] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [2.6] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [39.0] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [83.2] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [21.6] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [508.2] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [3.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [11.4] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [139] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [2.5] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [57] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [0.1] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [0.2] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.0] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [2.0] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [2.0] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [2.0] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [3.8] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [15.1] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [48.9] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [58.0] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [71.1] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [77.8] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [0.1] + } + ] + }, + { + "validTime": "2025-10-04T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.2] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.2] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [23.5] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [43.3] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [12.5] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [482.4] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [2.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [7.7] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [145] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [6.5] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [85] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [6.5] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [6.6] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [6.7] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [8.5] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [8.5] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [8.5] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [10.3] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [19.7] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [55.4] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [64.4] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [77.5] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [77.8] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [6.5] + } + ] + }, + { + "validTime": "2025-10-05T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.1] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.1] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [23.6] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [39.5] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [12.6] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [485.3] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [2.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [12.3] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [185] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [3.0] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [96] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.4] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [7.9] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [8.1] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.0] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.9] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.9] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.9] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [14.6] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [56.3] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [65.4] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [77.8] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [79.2] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [1.4] + } + ] + }, + { + "validTime": "2025-10-06T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.2] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.2] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [24.2] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [46.7] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [12.9] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [488.1] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [2.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [11.5] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [340] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [2.9] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [85] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.5] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [2.9] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.4] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.6] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [11.4] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [11.4] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [11.4] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [16.0] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [54.4] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [66.9] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [79.2] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [80.6] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [1.5] + } + ] + }, + { + "validTime": "2025-10-07T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.7] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.6] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [25.4] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [63.1] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [13.6] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [491.2] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [3.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [13.0] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [193] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [1.3] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [69] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [0.0] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [1.5] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [2.9] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [9.5] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [10.4] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [11.4] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [11.4] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [13.2] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [54.4] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [66.6] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [79.2] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [80.6] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [0.0] + } + ] + }, + { + "validTime": "2025-10-08T12:00:00Z", + "parameters": [ + { + "name": "fwiindex", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [1.0] + }, + { + "name": "fwi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.0] + }, + { + "name": "isi", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [0.0] + }, + { + "name": "bui", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [16.1] + }, + { + "name": "ffmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [27.1] + }, + { + "name": "dmc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [8.4] + }, + { + "name": "dc", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [477.1] + }, + { + "name": "forestdry", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [2.0] + }, + { + "name": "t", + "levelType": "hl", + "level": 2, + "unit": "Cel", + "values": [10.6] + }, + { + "name": "wd", + "levelType": "hl", + "level": 10, + "unit": "degree", + "values": [191] + }, + { + "name": "ws", + "levelType": "hl", + "level": 10, + "unit": "m/s", + "values": [3.1] + }, + { + "name": "r", + "levelType": "hl", + "level": 2, + "unit": "percent", + "values": [99] + }, + { + "name": "grassfire", + "levelType": "hl", + "level": 0, + "unit": "Numeric", + "values": [-1.0] + }, + { + "name": "rn", + "levelType": "hl", + "level": 0, + "unit": "m min**-1", + "values": [0.0] + }, + { + "name": "prec1d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [4.6] + }, + { + "name": "prec2d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [4.6] + }, + { + "name": "prec3d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [6.0] + }, + { + "name": "prec5d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [14.0] + }, + { + "name": "prec7d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [14.2] + }, + { + "name": "prec10d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [16.0] + }, + { + "name": "prec14d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [16.0] + }, + { + "name": "prec20d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [17.8] + }, + { + "name": "prec30d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [58.9] + }, + { + "name": "prec40d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [70.7] + }, + { + "name": "prec50d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [82.2] + }, + { + "name": "prec60d", + "levelType": "hl", + "level": 0, + "unit": "kg m**-2", + "values": [85.2] + }, + { + "name": "prec24h", + "levelType": "hl", + "level": 0, + "unit": "mm", + "values": [4.6] + } + ] + } + ] +} diff --git a/tests/components/smhi/snapshots/test_sensor.ambr b/tests/components/smhi/snapshots/test_sensor.ambr index 8fbdf229494f0..3851c88b006a8 100644 --- a/tests/components/smhi/snapshots/test_sensor.ambr +++ b/tests/components/smhi/snapshots/test_sensor.ambr @@ -1,4 +1,264 @@ # serializer version: 1 +# name: test_sensor_setup[load_platforms0][sensor.test_build_up_index-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_build_up_index', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Build Up Index', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'bui', + 'unique_id': '59.32624, 17.84197-bui', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_build_up_index-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'friendly_name': 'Test Build Up Index', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_build_up_index', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '39.0', + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_drought_code-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_drought_code', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Drought Code', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dc', + 'unique_id': '59.32624, 17.84197-dc', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_drought_code-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'friendly_name': 'Test Drought Code', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_drought_code', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '508.2', + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_duff_moisture_code-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_duff_moisture_code', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Duff Moisture Code', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'dmc', + 'unique_id': '59.32624, 17.84197-dmc', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_duff_moisture_code-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'friendly_name': 'Test Duff Moisture Code', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_duff_moisture_code', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '21.6', + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fine_fuel_moisture_code-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_fine_fuel_moisture_code', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fine Fuel Moisture Code', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'ffmc', + 'unique_id': '59.32624, 17.84197-ffmc', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fine_fuel_moisture_code-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'friendly_name': 'Test Fine Fuel Moisture Code', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_fine_fuel_moisture_code', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '83.2', + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fire_weather_index-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_fire_weather_index', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Fire Weather Index', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'fwi', + 'unique_id': '59.32624, 17.84197-fwi', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fire_weather_index-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'friendly_name': 'Test Fire Weather Index', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_fire_weather_index', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6.6', + }) +# --- # name: test_sensor_setup[load_platforms0][sensor.test_frozen_precipitation-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -49,6 +309,112 @@ 'state': '0', }) # --- +# name: test_sensor_setup[load_platforms0][sensor.test_fuel_drying-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_fuel_drying', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Fuel drying', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'forestdry', + 'unique_id': '59.32624, 17.84197-forestdry', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fuel_drying-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'device_class': 'enum', + 'friendly_name': 'Test Fuel drying', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_fuel_drying', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fwi_index-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_fwi_index', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'FWI-index', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'fwiindex', + 'unique_id': '59.32624, 17.84197-fwiindex', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_fwi_index-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'device_class': 'enum', + 'friendly_name': 'Test FWI-index', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_fwi_index', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2', + }) +# --- # name: test_sensor_setup[load_platforms0][sensor.test_high_cloud_coverage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -99,6 +465,111 @@ 'state': '88', }) # --- +# name: test_sensor_setup[load_platforms0][sensor.test_highest_grass_fire_risk-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_highest_grass_fire_risk', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Highest grass fire risk', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'grassfire', + 'unique_id': '59.32624, 17.84197-grassfire', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_highest_grass_fire_risk-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'device_class': 'enum', + 'friendly_name': 'Test Highest grass fire risk', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_highest_grass_fire_risk', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'unknown', + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_initial_spread_index-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_initial_spread_index', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Initial Spread Index', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'isi', + 'unique_id': '59.32624, 17.84197-isi', + 'unit_of_measurement': None, + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_initial_spread_index-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'friendly_name': 'Test Initial Spread Index', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.test_initial_spread_index', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '2.6', + }) +# --- # name: test_sensor_setup[load_platforms0][sensor.test_low_cloud_coverage-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -199,6 +670,63 @@ 'state': '88', }) # --- +# name: test_sensor_setup[load_platforms0][sensor.test_potential_rate_of_spread-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.test_potential_rate_of_spread', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + 'sensor': dict({ + 'suggested_display_precision': 1, + }), + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Potential rate of spread', + 'platform': 'smhi', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'rn', + 'unique_id': '59.32624, 17.84197-rn', + 'unit_of_measurement': , + }) +# --- +# name: test_sensor_setup[load_platforms0][sensor.test_potential_rate_of_spread-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'attribution': 'Swedish weather institute (SMHI)', + 'device_class': 'speed', + 'friendly_name': 'Test Potential rate of spread', + 'state_class': , + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'sensor.test_potential_rate_of_spread', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0.0', + }) +# --- # name: test_sensor_setup[load_platforms0][sensor.test_precipitation_category-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ diff --git a/tests/components/smhi/test_config_flow.py b/tests/components/smhi/test_config_flow.py index b8e7508fcbcdc..272e9338bdde7 100644 --- a/tests/components/smhi/test_config_flow.py +++ b/tests/components/smhi/test_config_flow.py @@ -23,6 +23,7 @@ async def test_form( hass: HomeAssistant, mock_client: MagicMock, + mock_fire_client: MagicMock, ) -> None: """Test we get the form and create an entry.""" @@ -87,6 +88,7 @@ async def test_form( async def test_form_invalid_coordinates( hass: HomeAssistant, mock_client: MagicMock, + mock_fire_client: MagicMock, ) -> None: """Test we handle invalid coordinates.""" result = await hass.config_entries.flow.async_init( @@ -133,6 +135,7 @@ async def test_form_invalid_coordinates( async def test_form_unique_id_exist( hass: HomeAssistant, mock_client: MagicMock, + mock_fire_client: MagicMock, ) -> None: """Test we handle unique id already exist.""" entry = MockConfigEntry( @@ -168,6 +171,7 @@ async def test_form_unique_id_exist( async def test_reconfigure_flow( hass: HomeAssistant, mock_client: MagicMock, + mock_fire_client: MagicMock, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, ) -> None: diff --git a/tests/components/smhi/test_init.py b/tests/components/smhi/test_init.py index b873f316a71f1..2567b793bc2f4 100644 --- a/tests/components/smhi/test_init.py +++ b/tests/components/smhi/test_init.py @@ -1,6 +1,6 @@ """Test SMHI component setup process.""" -from pysmhi import SMHIPointForecast +from pysmhi import SMHIFirePointForecast, SMHIPointForecast from homeassistant.components.smhi.const import DOMAIN from homeassistant.config_entries import ConfigEntryState @@ -34,6 +34,7 @@ async def test_migrate_entry( hass: HomeAssistant, entity_registry: er.EntityRegistry, mock_client: SMHIPointForecast, + mock_fire_client: SMHIFirePointForecast, ) -> None: """Test migrate entry data.""" @@ -65,7 +66,9 @@ async def test_migrate_entry( async def test_migrate_from_future_version( - hass: HomeAssistant, mock_client: SMHIPointForecast + hass: HomeAssistant, + mock_client: SMHIPointForecast, + mock_fire_client: SMHIFirePointForecast, ) -> None: """Test migrate entry not possible from future version.""" entry = MockConfigEntry(domain=DOMAIN, data=TEST_CONFIG_MIGRATE, version=4) diff --git a/tests/components/smhi/test_weather.py b/tests/components/smhi/test_weather.py index 9acacb10ffa59..fab78d5893542 100644 --- a/tests/components/smhi/test_weather.py +++ b/tests/components/smhi/test_weather.py @@ -5,7 +5,12 @@ from freezegun import freeze_time from freezegun.api import FrozenDateTimeFactory -from pysmhi import SMHIForecast, SmhiForecastException, SMHIPointForecast +from pysmhi import ( + SMHIFirePointForecast, + SMHIForecast, + SmhiForecastException, + SMHIPointForecast, +) import pytest from syrupy.assertion import SnapshotAssertion @@ -65,6 +70,7 @@ async def test_setup_hass( async def test_clear_night( hass: HomeAssistant, mock_client: SMHIPointForecast, + mock_fire_client: SMHIFirePointForecast, snapshot: SnapshotAssertion, ) -> None: """Test for successfully setting up the smhi integration.""" @@ -102,6 +108,7 @@ async def test_properties_no_data( hass: HomeAssistant, load_int: MockConfigEntry, mock_client: MagicMock, + mock_fire_client: SMHIFirePointForecast, freezer: FrozenDateTimeFactory, ) -> None: """Test properties when no API data available.""" @@ -135,7 +142,8 @@ async def test_properties_no_data( async def test_properties_unknown_symbol( hass: HomeAssistant, - mock_client: MagicMock, + mock_client: SMHIPointForecast, + mock_fire_client: SMHIFirePointForecast, ) -> None: """Test behaviour when unknown symbol from API.""" data = SMHIForecast( @@ -244,7 +252,8 @@ async def test_refresh_weather_forecast_retry( hass: HomeAssistant, error: Exception, load_int: MockConfigEntry, - mock_client: MagicMock, + mock_client: SMHIPointForecast, + mock_fire_client: SMHIFirePointForecast, freezer: FrozenDateTimeFactory, ) -> None: """Test the refresh weather forecast function.""" From 690745cfbc443e36600078281bdde4f7737a8618 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 6 Oct 2025 18:45:47 +0000 Subject: [PATCH 3/6] Fire --- homeassistant/components/smhi/__init__.py | 10 +- homeassistant/components/smhi/coordinator.py | 66 +++++++--- homeassistant/components/smhi/entity.py | 51 ++++++-- homeassistant/components/smhi/sensor.py | 120 +++++++++++++------ homeassistant/components/smhi/weather.py | 6 +- tests/components/smhi/conftest.py | 18 +-- 6 files changed, 203 insertions(+), 68 deletions(-) diff --git a/homeassistant/components/smhi/__init__.py b/homeassistant/components/smhi/__init__.py index 085cbdcbbce57..07f53b02d9872 100644 --- a/homeassistant/components/smhi/__init__.py +++ b/homeassistant/components/smhi/__init__.py @@ -9,7 +9,11 @@ ) from homeassistant.core import HomeAssistant -from .coordinator import SMHIConfigEntry, SMHIDataUpdateCoordinator +from .coordinator import ( + SMHIConfigEntry, + SMHIDataUpdateCoordinator, + SMHIFireDataUpdateCoordinator, +) PLATFORMS = [Platform.SENSOR, Platform.WEATHER] @@ -24,7 +28,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: SMHIConfigEntry) -> bool coordinator = SMHIDataUpdateCoordinator(hass, entry) await coordinator.async_config_entry_first_refresh() - entry.runtime_data = coordinator + fire_coordinator = SMHIFireDataUpdateCoordinator(hass, entry) + await fire_coordinator.async_config_entry_first_refresh() + entry.runtime_data = (coordinator, fire_coordinator) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/homeassistant/components/smhi/coordinator.py b/homeassistant/components/smhi/coordinator.py index b9ce21b001c16..d5a3c9ed154c3 100644 --- a/homeassistant/components/smhi/coordinator.py +++ b/homeassistant/components/smhi/coordinator.py @@ -22,7 +22,9 @@ from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT -type SMHIConfigEntry = ConfigEntry[SMHIDataUpdateCoordinator] +type SMHIConfigEntry = ConfigEntry[ + tuple[SMHIDataUpdateCoordinator, SMHIFireDataUpdateCoordinator] +] @dataclass @@ -32,6 +34,12 @@ class SMHIForecastData: daily: list[SMHIForecast] hourly: list[SMHIForecast] twice_daily: list[SMHIForecast] + + +@dataclass +class SMHIFireForecastData: + """Dataclass for SMHI fire data.""" + fire_daily: list[SMHIFireForecast] fire_hourly: list[SMHIFireForecast] @@ -55,11 +63,6 @@ def __init__(self, hass: HomeAssistant, config_entry: SMHIConfigEntry) -> None: config_entry.data[CONF_LOCATION][CONF_LATITUDE], session=aiohttp_client.async_get_clientsession(hass), ) - self._smhi_fire_api = SMHIFirePointForecast( - config_entry.data[CONF_LOCATION][CONF_LONGITUDE], - config_entry.data[CONF_LOCATION][CONF_LATITUDE], - session=aiohttp_client.async_get_clientsession(hass), - ) async def _async_update_data(self) -> SMHIForecastData: """Fetch data from SMHI.""" @@ -70,13 +73,7 @@ async def _async_update_data(self) -> SMHIForecastData: _forecast_twice_daily = ( await self._smhi_api.async_get_twice_daily_forecast() ) - _forecast_fire_daily = ( - await self._smhi_fire_api.async_get_daily_forecast() - ) - _forecast_fire_hourly = ( - await self._smhi_fire_api.async_get_hourly_forecast() - ) - except (SmhiForecastException, SmhiFireForecastException) as ex: + except SmhiForecastException as ex: raise UpdateFailed( "Failed to retrieve the forecast from the SMHI API" ) from ex @@ -85,8 +82,6 @@ async def _async_update_data(self) -> SMHIForecastData: daily=_forecast_daily, hourly=_forecast_hourly, twice_daily=_forecast_twice_daily, - fire_daily=_forecast_fire_daily, - fire_hourly=_forecast_fire_hourly, ) @property @@ -94,6 +89,47 @@ def current(self) -> SMHIForecast: """Return the current metrics.""" return self.data.daily[0] + +class SMHIFireDataUpdateCoordinator(DataUpdateCoordinator[SMHIFireForecastData]): + """A SMHI Fire Data Update Coordinator.""" + + config_entry: SMHIConfigEntry + + def __init__(self, hass: HomeAssistant, config_entry: SMHIConfigEntry) -> None: + """Initialize the SMHI coordinator.""" + super().__init__( + hass, + LOGGER, + config_entry=config_entry, + name=DOMAIN, + update_interval=DEFAULT_SCAN_INTERVAL, + ) + self._smhi_fire_api = SMHIFirePointForecast( + config_entry.data[CONF_LOCATION][CONF_LONGITUDE], + config_entry.data[CONF_LOCATION][CONF_LATITUDE], + session=aiohttp_client.async_get_clientsession(hass), + ) + + async def _async_update_data(self) -> SMHIFireForecastData: + """Fetch data from SMHI.""" + try: + async with asyncio.timeout(TIMEOUT): + _forecast_fire_daily = ( + await self._smhi_fire_api.async_get_daily_forecast() + ) + _forecast_fire_hourly = ( + await self._smhi_fire_api.async_get_hourly_forecast() + ) + except SmhiFireForecastException as ex: + raise UpdateFailed( + "Failed to retrieve the forecast from the SMHI API" + ) from ex + + return SMHIFireForecastData( + fire_daily=_forecast_fire_daily, + fire_hourly=_forecast_fire_hourly, + ) + @property def fire_current(self) -> SMHIFireForecast: """Return the current fire metrics.""" diff --git a/homeassistant/components/smhi/entity.py b/homeassistant/components/smhi/entity.py index fb565a7fc5182..1f0b94cddbd0e 100644 --- a/homeassistant/components/smhi/entity.py +++ b/homeassistant/components/smhi/entity.py @@ -6,13 +6,14 @@ from homeassistant.core import callback from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN -from .coordinator import SMHIDataUpdateCoordinator +from .coordinator import SMHIDataUpdateCoordinator, SMHIFireDataUpdateCoordinator -class SmhiWeatherBaseEntity(CoordinatorEntity[SMHIDataUpdateCoordinator]): +class SmhiWeatherBaseEntity(Entity): """Representation of a base weather entity.""" _attr_attribution = "Swedish weather institute (SMHI)" @@ -22,10 +23,8 @@ def __init__( self, latitude: str, longitude: str, - coordinator: SMHIDataUpdateCoordinator, ) -> None: """Initialize the SMHI base weather entity.""" - super().__init__(coordinator) self._attr_unique_id = f"{latitude}, {longitude}" self._attr_device_info = DeviceInfo( entry_type=DeviceEntryType.SERVICE, @@ -36,12 +35,50 @@ def __init__( ) self.update_entity_data() + @abstractmethod + def update_entity_data(self) -> None: + """Refresh the entity data.""" + + +class SmhiWeatherEntity( + CoordinatorEntity[SMHIDataUpdateCoordinator], SmhiWeatherBaseEntity +): + """Representation of a weather entity.""" + + def __init__( + self, + latitude: str, + longitude: str, + coordinator: SMHIDataUpdateCoordinator, + ) -> None: + """Initialize the SMHI base weather entity.""" + super().__init__(coordinator) + SmhiWeatherBaseEntity.__init__(self, latitude, longitude) + @callback def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" self.update_entity_data() super()._handle_coordinator_update() - @abstractmethod - def update_entity_data(self) -> None: - """Refresh the entity data.""" + +class SmhiFireEntity( + CoordinatorEntity[SMHIFireDataUpdateCoordinator], SmhiWeatherBaseEntity +): + """Representation of a weather entity.""" + + def __init__( + self, + latitude: str, + longitude: str, + coordinator: SMHIFireDataUpdateCoordinator, + ) -> None: + """Initialize the SMHI base weather entity.""" + super().__init__(coordinator) + SmhiWeatherBaseEntity.__init__(self, latitude, longitude) + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.update_entity_data() + super()._handle_coordinator_update() diff --git a/homeassistant/components/smhi/sensor.py b/homeassistant/components/smhi/sensor.py index 2d6e069ba810d..3b5f3906222bd 100644 --- a/homeassistant/components/smhi/sensor.py +++ b/homeassistant/components/smhi/sensor.py @@ -23,13 +23,17 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.typing import StateType -from .coordinator import SMHIConfigEntry, SMHIDataUpdateCoordinator -from .entity import SmhiWeatherBaseEntity +from .coordinator import ( + SMHIConfigEntry, + SMHIDataUpdateCoordinator, + SMHIFireDataUpdateCoordinator, +) +from .entity import SmhiFireEntity, SmhiWeatherEntity PARALLEL_UPDATES = 0 -def get_percentage_values(entity: SMHISensor, key: str) -> int | None: +def get_percentage_values(entity: SMHIWeatherSensor, key: str) -> int | None: """Return percentage values in correct range.""" value: int | None = entity.coordinator.current.get(key) # type: ignore[assignment] if value is not None and 0 <= value <= 100: @@ -39,7 +43,7 @@ def get_percentage_values(entity: SMHISensor, key: str) -> int | None: return None -def get_fire_index_value(entity: SMHISensor, key: str) -> str | None: +def get_fire_index_value(entity: SMHIFireSensor, key: str) -> str | None: """Return index value as string.""" value: int | None = entity.coordinator.fire_current.get(key) # type: ignore[assignment] if value is not None and value > 0: @@ -48,48 +52,55 @@ def get_fire_index_value(entity: SMHISensor, key: str) -> str | None: @dataclass(frozen=True, kw_only=True) -class SMHISensorEntityDescription(SensorEntityDescription): - """Describes SMHI sensor entity.""" +class SMHIWeatherEntityDescription(SensorEntityDescription): + """Describes SMHI weather entity.""" + + value_fn: Callable[[SMHIWeatherSensor], StateType | datetime] + + +@dataclass(frozen=True, kw_only=True) +class SMHIFireEntityDescription(SensorEntityDescription): + """Describes SMHI fire entity.""" - value_fn: Callable[[SMHISensor], StateType | datetime] + value_fn: Callable[[SMHIFireSensor], StateType | datetime] -SENSOR_DESCRIPTIONS: tuple[SMHISensorEntityDescription, ...] = ( - SMHISensorEntityDescription( +WEATHER_SENSOR_DESCRIPTIONS: tuple[SMHIWeatherEntityDescription, ...] = ( + SMHIWeatherEntityDescription( key="thunder", translation_key="thunder", value_fn=lambda entity: get_percentage_values(entity, "thunder"), native_unit_of_measurement=PERCENTAGE, ), - SMHISensorEntityDescription( + SMHIWeatherEntityDescription( key="total_cloud", translation_key="total_cloud", value_fn=lambda entity: get_percentage_values(entity, "total_cloud"), native_unit_of_measurement=PERCENTAGE, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIWeatherEntityDescription( key="low_cloud", translation_key="low_cloud", value_fn=lambda entity: get_percentage_values(entity, "low_cloud"), native_unit_of_measurement=PERCENTAGE, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIWeatherEntityDescription( key="medium_cloud", translation_key="medium_cloud", value_fn=lambda entity: get_percentage_values(entity, "medium_cloud"), native_unit_of_measurement=PERCENTAGE, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIWeatherEntityDescription( key="high_cloud", translation_key="high_cloud", value_fn=lambda entity: get_percentage_values(entity, "high_cloud"), native_unit_of_measurement=PERCENTAGE, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIWeatherEntityDescription( key="precipitation_category", translation_key="precipitation_category", value_fn=lambda entity: str( @@ -98,14 +109,15 @@ class SMHISensorEntityDescription(SensorEntityDescription): device_class=SensorDeviceClass.ENUM, options=["0", "1", "2", "3", "4", "5", "6"], ), - SMHISensorEntityDescription( + SMHIWeatherEntityDescription( key="frozen_precipitation", translation_key="frozen_precipitation", value_fn=lambda entity: get_percentage_values(entity, "frozen_precipitation"), native_unit_of_measurement=PERCENTAGE, ), - # Fire sensors - SMHISensorEntityDescription( +) +FIRE_SENSOR_DESCRIPTIONS: tuple[SMHIFireEntityDescription, ...] = ( + SMHIFireEntityDescription( key="fwiindex", translation_key="fwiindex", value_fn=lambda entity: get_fire_index_value(entity, "fwiindex"), @@ -114,21 +126,21 @@ class SMHISensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="fwi", translation_key="fwi", value_fn=lambda entity: entity.coordinator.fire_current.get("fwi"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="isi", translation_key="isi", value_fn=lambda entity: entity.coordinator.fire_current.get("isi"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="bui", # codespell:ignore bui translation_key="bui", # codespell:ignore bui value_fn=( @@ -139,28 +151,28 @@ class SMHISensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="ffmc", translation_key="ffmc", value_fn=lambda entity: entity.coordinator.fire_current.get("ffmc"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="dmc", translation_key="dmc", value_fn=lambda entity: entity.coordinator.fire_current.get("dmc"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="dc", translation_key="dc", value_fn=lambda entity: entity.coordinator.fire_current.get("dc"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="grassfire", translation_key="grassfire", value_fn=lambda entity: get_fire_index_value(entity, "grassfire"), @@ -169,7 +181,7 @@ class SMHISensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="rn", translation_key="rn", value_fn=lambda entity: entity.coordinator.fire_current.get("rn"), @@ -178,7 +190,7 @@ class SMHISensorEntityDescription(SensorEntityDescription): native_unit_of_measurement=UnitOfSpeed.METERS_PER_MINUTE, entity_registry_enabled_default=False, ), - SMHISensorEntityDescription( + SMHIFireEntityDescription( key="forestdry", translation_key="forestdry", value_fn=lambda entity: get_fire_index_value(entity, "forestdry"), @@ -197,30 +209,43 @@ async def async_setup_entry( ) -> None: """Set up SMHI sensor platform.""" - coordinator = entry.runtime_data + coordinator = entry.runtime_data[0] + fire_coordinator = entry.runtime_data[1] location = entry.data - async_add_entities( - SMHISensor( + entities: list[SMHIWeatherSensor | SMHIFireSensor] = [] + entities.extend( + SMHIWeatherSensor( location[CONF_LOCATION][CONF_LATITUDE], location[CONF_LOCATION][CONF_LONGITUDE], coordinator=coordinator, entity_description=description, ) - for description in SENSOR_DESCRIPTIONS + for description in WEATHER_SENSOR_DESCRIPTIONS + ) + entities.extend( + SMHIFireSensor( + location[CONF_LOCATION][CONF_LATITUDE], + location[CONF_LOCATION][CONF_LONGITUDE], + coordinator=fire_coordinator, + entity_description=description, + ) + for description in FIRE_SENSOR_DESCRIPTIONS ) + async_add_entities(entities) -class SMHISensor(SmhiWeatherBaseEntity, SensorEntity): - """Representation of a SMHI Sensor.""" - entity_description: SMHISensorEntityDescription +class SMHIWeatherSensor(SmhiWeatherEntity, SensorEntity): + """Representation of a SMHI Weather Sensor.""" + + entity_description: SMHIWeatherEntityDescription def __init__( self, latitude: str, longitude: str, coordinator: SMHIDataUpdateCoordinator, - entity_description: SMHISensorEntityDescription, + entity_description: SMHIWeatherEntityDescription, ) -> None: """Initiate SMHI Sensor.""" self.entity_description = entity_description @@ -235,3 +260,30 @@ def update_entity_data(self) -> None: """Refresh the entity data.""" if self.coordinator.data.daily: self._attr_native_value = self.entity_description.value_fn(self) + + +class SMHIFireSensor(SmhiFireEntity, SensorEntity): + """Representation of a SMHI Weather Sensor.""" + + entity_description: SMHIFireEntityDescription + + def __init__( + self, + latitude: str, + longitude: str, + coordinator: SMHIFireDataUpdateCoordinator, + entity_description: SMHIFireEntityDescription, + ) -> None: + """Initiate SMHI Sensor.""" + self.entity_description = entity_description + super().__init__( + latitude, + longitude, + coordinator, + ) + self._attr_unique_id = f"{latitude}, {longitude}-{entity_description.key}" + + def update_entity_data(self) -> None: + """Refresh the entity data.""" + if self.coordinator.data.fire_daily: + self._attr_native_value = self.entity_description.value_fn(self) diff --git a/homeassistant/components/smhi/weather.py b/homeassistant/components/smhi/weather.py index 9496321b8b428..ee5597abff8c5 100644 --- a/homeassistant/components/smhi/weather.py +++ b/homeassistant/components/smhi/weather.py @@ -55,7 +55,7 @@ from .const import ATTR_SMHI_THUNDER_PROBABILITY, ENTITY_ID_SENSOR_FORMAT from .coordinator import SMHIConfigEntry -from .entity import SmhiWeatherBaseEntity +from .entity import SmhiWeatherEntity # Used to map condition from API results CONDITION_CLASSES: Final[dict[str, list[int]]] = { @@ -89,7 +89,7 @@ async def async_setup_entry( """Add a weather entity from map location.""" location = config_entry.data - coordinator = config_entry.runtime_data + coordinator = config_entry.runtime_data[0] entity = SmhiWeather( location[CONF_LOCATION][CONF_LATITUDE], @@ -101,7 +101,7 @@ async def async_setup_entry( async_add_entities([entity]) -class SmhiWeather(SmhiWeatherBaseEntity, SingleCoordinatorWeatherEntity): +class SmhiWeather(SmhiWeatherEntity, SingleCoordinatorWeatherEntity): """Representation of a weather entity.""" _attr_native_temperature_unit = UnitOfTemperature.CELSIUS diff --git a/tests/components/smhi/conftest.py b/tests/components/smhi/conftest.py index 3f588ddd4c1e0..69e776897eca3 100644 --- a/tests/components/smhi/conftest.py +++ b/tests/components/smhi/conftest.py @@ -7,7 +7,8 @@ from typing import Any from unittest.mock import AsyncMock, MagicMock, patch -from pysmhi.smhi_fire_forecast import SMHIFirePointForecast +from freezegun import freeze_time +from pysmhi.smhi_fire_forecast import SMHIFireForecast, SMHIFirePointForecast from pysmhi.smhi_forecast import SMHIForecast, SMHIPointForecast import pytest @@ -92,7 +93,7 @@ async def get_client( @pytest.fixture(name="mock_fire_client") async def get_fire_client( hass: HomeAssistant, - get_fire_data: tuple[list[SMHIForecast], list[SMHIForecast], list[SMHIForecast]], + get_fire_data: tuple[list[SMHIFireForecast], list[SMHIFireForecast]], ) -> AsyncGenerator[MagicMock]: """Mock SMHIFirePointForecast client.""" @@ -136,17 +137,20 @@ async def get_fire_data_from_library( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, load_fire_json: dict[str, Any], -) -> AsyncGenerator[tuple[list[SMHIForecast], list[SMHIForecast], list[SMHIForecast]]]: +) -> AsyncGenerator[tuple[list[SMHIFireForecast], list[SMHIFireForecast]]]: """Get data from api.""" client = SMHIFirePointForecast( TEST_CONFIG[CONF_LOCATION][CONF_LONGITUDE], TEST_CONFIG[CONF_LOCATION][CONF_LATITUDE], aioclient_mock.create_session(hass.loop), ) - with patch.object( - client._api, - "async_get_data", - return_value=load_fire_json, + with ( + freeze_time("2025-10-03"), + patch.object( + client._api, + "async_get_data", + return_value=load_fire_json, + ), ): data_daily = await client.async_get_daily_forecast() data_hourly = await client.async_get_hourly_forecast() From c3b3488c6d626a23a7d649042ff134f1297b015a Mon Sep 17 00:00:00 2001 From: G Johansson Date: Mon, 6 Oct 2025 19:14:21 +0000 Subject: [PATCH 4/6] codes --- homeassistant/components/smhi/icons.json | 20 ++++- homeassistant/components/smhi/sensor.py | 78 +++++++++++------ homeassistant/components/smhi/strings.json | 21 +++++ .../smhi/snapshots/test_sensor.ambr | 86 ++++++++++++++----- 4 files changed, 157 insertions(+), 48 deletions(-) diff --git a/homeassistant/components/smhi/icons.json b/homeassistant/components/smhi/icons.json index 568a195b25507..f550562f50835 100644 --- a/homeassistant/components/smhi/icons.json +++ b/homeassistant/components/smhi/icons.json @@ -43,7 +43,25 @@ "precipitation_category": { "default": "mdi:weather-pouring" }, - "rn": { + "fire_weather_index": { + "default": "mdi:pine-tree-fire" + }, + "initial_spread_index": { + "default": "mdi:grass" + }, + "build_up_index": { + "default": "mdi:grass" + }, + "fine_fuel_moisture_code": { + "default": "mdi:grass" + }, + "duff_moisture_code": { + "default": "mdi:grass" + }, + "drought_code": { + "default": "mdi:grass" + }, + "rate_of_spread": { "default": "mdi:grass" }, "thunder": { diff --git a/homeassistant/components/smhi/sensor.py b/homeassistant/components/smhi/sensor.py index 3b5f3906222bd..7531e4e4d6dd5 100644 --- a/homeassistant/components/smhi/sensor.py +++ b/homeassistant/components/smhi/sensor.py @@ -32,6 +32,31 @@ PARALLEL_UPDATES = 0 +FWI_INDEX_MAP = { + "1": "very_low", + "2": "low", + "3": "moderate", + "4": "high", + "5": "very_high", + "6": "extreme", +} +GRASSFIRE_MAP = { + "1": "snow_cover", + "2": "season_over", + "3": "low", + "4": "moderate", + "5": "high", + "6": "very_high", +} +FORESTDRY_MAP = { + "1": "very_wet", + "2": "wet", + "3": "moderate_wet", + "4": "dry", + "5": "very_dry", + "6": "extremely_dry", +} + def get_percentage_values(entity: SMHIWeatherSensor, key: str) -> int | None: """Return percentage values in correct range.""" @@ -43,12 +68,12 @@ def get_percentage_values(entity: SMHIWeatherSensor, key: str) -> int | None: return None -def get_fire_index_value(entity: SMHIFireSensor, key: str) -> str | None: +def get_fire_index_value(entity: SMHIFireSensor, key: str) -> str: """Return index value as string.""" value: int | None = entity.coordinator.fire_current.get(key) # type: ignore[assignment] if value is not None and value > 0: return str(int(value)) - return None + return "0" @dataclass(frozen=True, kw_only=True) @@ -120,29 +145,30 @@ class SMHIFireEntityDescription(SensorEntityDescription): SMHIFireEntityDescription( key="fwiindex", translation_key="fwiindex", - value_fn=lambda entity: get_fire_index_value(entity, "fwiindex"), + value_fn=( + lambda entity: FWI_INDEX_MAP.get(get_fire_index_value(entity, "fwiindex")) + ), device_class=SensorDeviceClass.ENUM, - options=["1", "2", "3", "4", "5", "6"], - state_class=SensorStateClass.MEASUREMENT, + options=[*FWI_INDEX_MAP.values()], entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="fwi", - translation_key="fwi", + key="fire_weather_index", + translation_key="fire_weather_index", value_fn=lambda entity: entity.coordinator.fire_current.get("fwi"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="isi", - translation_key="isi", + key="initial_spread_index", + translation_key="initial_spread_index", value_fn=lambda entity: entity.coordinator.fire_current.get("isi"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="bui", # codespell:ignore bui - translation_key="bui", # codespell:ignore bui + key="build_up_index", + translation_key="build_up_index", value_fn=( lambda entity: entity.coordinator.fire_current.get( "bui" # codespell:ignore bui @@ -152,22 +178,22 @@ class SMHIFireEntityDescription(SensorEntityDescription): entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="ffmc", - translation_key="ffmc", + key="fine_fuel_moisture_code", + translation_key="fine_fuel_moisture_code", value_fn=lambda entity: entity.coordinator.fire_current.get("ffmc"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="dmc", - translation_key="dmc", + key="duff_moisture_code", + translation_key="duff_moisture_code", value_fn=lambda entity: entity.coordinator.fire_current.get("dmc"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="dc", - translation_key="dc", + key="drought_code", + translation_key="drought_code", value_fn=lambda entity: entity.coordinator.fire_current.get("dc"), state_class=SensorStateClass.MEASUREMENT, entity_registry_enabled_default=False, @@ -175,15 +201,16 @@ class SMHIFireEntityDescription(SensorEntityDescription): SMHIFireEntityDescription( key="grassfire", translation_key="grassfire", - value_fn=lambda entity: get_fire_index_value(entity, "grassfire"), + value_fn=( + lambda entity: GRASSFIRE_MAP.get(get_fire_index_value(entity, "grassfire")) + ), device_class=SensorDeviceClass.ENUM, - options=["1", "2", "3", "4", "5", "6"], - state_class=SensorStateClass.MEASUREMENT, + options=[*GRASSFIRE_MAP.values()], entity_registry_enabled_default=False, ), SMHIFireEntityDescription( - key="rn", - translation_key="rn", + key="rate_of_spread", + translation_key="rate_of_spread", value_fn=lambda entity: entity.coordinator.fire_current.get("rn"), device_class=SensorDeviceClass.SPEED, state_class=SensorStateClass.MEASUREMENT, @@ -193,10 +220,11 @@ class SMHIFireEntityDescription(SensorEntityDescription): SMHIFireEntityDescription( key="forestdry", translation_key="forestdry", - value_fn=lambda entity: get_fire_index_value(entity, "forestdry"), + value_fn=( + lambda entity: FORESTDRY_MAP.get(get_fire_index_value(entity, "forestdry")) + ), device_class=SensorDeviceClass.ENUM, - options=["1", "2", "3", "4", "5", "6"], - state_class=SensorStateClass.MEASUREMENT, + options=[*FORESTDRY_MAP.values()], entity_registry_enabled_default=False, ), ) diff --git a/homeassistant/components/smhi/strings.json b/homeassistant/components/smhi/strings.json index 1165a68bcf652..5af75cf6bae43 100644 --- a/homeassistant/components/smhi/strings.json +++ b/homeassistant/components/smhi/strings.json @@ -109,6 +109,27 @@ }, "total_cloud": { "name": "Total cloud coverage" + }, + "fire_weather_index": { + "name": "Fire Weather Index" + }, + "initial_spread_index": { + "name": "Initial Spread Index" + }, + "build_up_index": { + "name": "Build Up Index" + }, + "fine_fuel_moisture_code": { + "name": "Fine Fuel Moisture Code" + }, + "duff_moisture_code": { + "name": "Duff Moisture Code" + }, + "drought_code": { + "name": "Drought Code" + }, + "rate_of_spread": { + "name": "Potential rate of spread" } } } diff --git a/tests/components/smhi/snapshots/test_sensor.ambr b/tests/components/smhi/snapshots/test_sensor.ambr index 3851c88b006a8..20fcf3f201c90 100644 --- a/tests/components/smhi/snapshots/test_sensor.ambr +++ b/tests/components/smhi/snapshots/test_sensor.ambr @@ -31,8 +31,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'bui', - 'unique_id': '59.32624, 17.84197-bui', + 'translation_key': 'build_up_index', + 'unique_id': '59.32624, 17.84197-build_up_index', 'unit_of_measurement': None, }) # --- @@ -83,8 +83,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'dc', - 'unique_id': '59.32624, 17.84197-dc', + 'translation_key': 'drought_code', + 'unique_id': '59.32624, 17.84197-drought_code', 'unit_of_measurement': None, }) # --- @@ -135,8 +135,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'dmc', - 'unique_id': '59.32624, 17.84197-dmc', + 'translation_key': 'duff_moisture_code', + 'unique_id': '59.32624, 17.84197-duff_moisture_code', 'unit_of_measurement': None, }) # --- @@ -187,8 +187,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'ffmc', - 'unique_id': '59.32624, 17.84197-ffmc', + 'translation_key': 'fine_fuel_moisture_code', + 'unique_id': '59.32624, 17.84197-fine_fuel_moisture_code', 'unit_of_measurement': None, }) # --- @@ -239,8 +239,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'fwi', - 'unique_id': '59.32624, 17.84197-fwi', + 'translation_key': 'fire_weather_index', + 'unique_id': '59.32624, 17.84197-fire_weather_index', 'unit_of_measurement': None, }) # --- @@ -315,7 +315,14 @@ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'very_wet', + 'wet', + 'moderate_wet', + 'dry', + 'very_dry', + 'extremely_dry', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -352,14 +359,21 @@ 'attribution': 'Swedish weather institute (SMHI)', 'device_class': 'enum', 'friendly_name': 'Test Fuel drying', - 'state_class': , + 'options': list([ + 'very_wet', + 'wet', + 'moderate_wet', + 'dry', + 'very_dry', + 'extremely_dry', + ]), }), 'context': , 'entity_id': 'sensor.test_fuel_drying', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '3', + 'state': 'moderate_wet', }) # --- # name: test_sensor_setup[load_platforms0][sensor.test_fwi_index-entry] @@ -368,7 +382,14 @@ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'very_low', + 'low', + 'moderate', + 'high', + 'very_high', + 'extreme', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -405,14 +426,21 @@ 'attribution': 'Swedish weather institute (SMHI)', 'device_class': 'enum', 'friendly_name': 'Test FWI-index', - 'state_class': , + 'options': list([ + 'very_low', + 'low', + 'moderate', + 'high', + 'very_high', + 'extreme', + ]), }), 'context': , 'entity_id': 'sensor.test_fwi_index', 'last_changed': , 'last_reported': , 'last_updated': , - 'state': '2', + 'state': 'low', }) # --- # name: test_sensor_setup[load_platforms0][sensor.test_high_cloud_coverage-entry] @@ -471,7 +499,14 @@ }), 'area_id': None, 'capabilities': dict({ - 'state_class': , + 'options': list([ + 'snow_cover', + 'season_over', + 'low', + 'moderate', + 'high', + 'very_high', + ]), }), 'config_entry_id': , 'config_subentry_id': , @@ -508,7 +543,14 @@ 'attribution': 'Swedish weather institute (SMHI)', 'device_class': 'enum', 'friendly_name': 'Test Highest grass fire risk', - 'state_class': , + 'options': list([ + 'snow_cover', + 'season_over', + 'low', + 'moderate', + 'high', + 'very_high', + ]), }), 'context': , 'entity_id': 'sensor.test_highest_grass_fire_risk', @@ -550,8 +592,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'isi', - 'unique_id': '59.32624, 17.84197-isi', + 'translation_key': 'initial_spread_index', + 'unique_id': '59.32624, 17.84197-initial_spread_index', 'unit_of_measurement': None, }) # --- @@ -705,8 +747,8 @@ 'previous_unique_id': None, 'suggested_object_id': None, 'supported_features': 0, - 'translation_key': 'rn', - 'unique_id': '59.32624, 17.84197-rn', + 'translation_key': 'rate_of_spread', + 'unique_id': '59.32624, 17.84197-rate_of_spread', 'unit_of_measurement': , }) # --- From da4b71173eabea2016ebee1ca86b23f7ffa176b8 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 2 Nov 2025 15:23:32 +0000 Subject: [PATCH 5/6] Fix --- homeassistant/components/smhi/icons.json | 31 +++++-------------- homeassistant/components/smhi/strings.json | 36 ++++++---------------- 2 files changed, 17 insertions(+), 50 deletions(-) diff --git a/homeassistant/components/smhi/icons.json b/homeassistant/components/smhi/icons.json index f550562f50835..da791de637491 100644 --- a/homeassistant/components/smhi/icons.json +++ b/homeassistant/components/smhi/icons.json @@ -1,18 +1,21 @@ { "entity": { "sensor": { - "bui": { + "build_up_index": { "default": "mdi:grass" }, - "dc": { + "drought_code": { "default": "mdi:grass" }, - "dmc": { + "duff_moisture_code": { "default": "mdi:grass" }, - "ffmc": { + "fine_fuel_moisture_code": { "default": "mdi:grass" }, + "fire_weather_index": { + "default": "mdi:pine-tree-fire" + }, "forestdry": { "default": "mdi:forest" }, @@ -31,7 +34,7 @@ "high_cloud": { "default": "mdi:cloud-arrow-up" }, - "isi": { + "initial_spread_index": { "default": "mdi:grass" }, "low_cloud": { @@ -43,24 +46,6 @@ "precipitation_category": { "default": "mdi:weather-pouring" }, - "fire_weather_index": { - "default": "mdi:pine-tree-fire" - }, - "initial_spread_index": { - "default": "mdi:grass" - }, - "build_up_index": { - "default": "mdi:grass" - }, - "fine_fuel_moisture_code": { - "default": "mdi:grass" - }, - "duff_moisture_code": { - "default": "mdi:grass" - }, - "drought_code": { - "default": "mdi:grass" - }, "rate_of_spread": { "default": "mdi:grass" }, diff --git a/homeassistant/components/smhi/strings.json b/homeassistant/components/smhi/strings.json index 5af75cf6bae43..b8d9f46d890ab 100644 --- a/homeassistant/components/smhi/strings.json +++ b/homeassistant/components/smhi/strings.json @@ -26,18 +26,21 @@ }, "entity": { "sensor": { - "bui": { + "build_up_index": { "name": "Build Up Index" }, - "dc": { + "drought_code": { "name": "Drought Code" }, - "dmc": { + "duff_moisture_code": { "name": "Duff Moisture Code" }, - "ffmc": { + "fine_fuel_moisture_code": { "name": "Fine Fuel Moisture Code" }, + "fire_weather_index": { + "name": "Fire Weather Index" + }, "forestdry": { "name": "Fuel drying", "state": { @@ -80,7 +83,7 @@ "high_cloud": { "name": "High cloud coverage" }, - "isi": { + "initial_spread_index": { "name": "Initial Spread Index" }, "low_cloud": { @@ -101,7 +104,7 @@ "6": "Freezing drizzle" } }, - "rn": { + "rate_of_spread": { "name": "Potential rate of spread" }, "thunder": { @@ -109,27 +112,6 @@ }, "total_cloud": { "name": "Total cloud coverage" - }, - "fire_weather_index": { - "name": "Fire Weather Index" - }, - "initial_spread_index": { - "name": "Initial Spread Index" - }, - "build_up_index": { - "name": "Build Up Index" - }, - "fine_fuel_moisture_code": { - "name": "Fine Fuel Moisture Code" - }, - "duff_moisture_code": { - "name": "Duff Moisture Code" - }, - "drought_code": { - "name": "Drought Code" - }, - "rate_of_spread": { - "name": "Potential rate of spread" } } } From 005cdf960b163c1e9cf8a36b0f8851242b415e13 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Tue, 4 Nov 2025 19:07:59 +0000 Subject: [PATCH 6/6] Fix strings --- homeassistant/components/smhi/strings.json | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/homeassistant/components/smhi/strings.json b/homeassistant/components/smhi/strings.json index b8d9f46d890ab..570d915204230 100644 --- a/homeassistant/components/smhi/strings.json +++ b/homeassistant/components/smhi/strings.json @@ -44,12 +44,12 @@ "forestdry": { "name": "Fuel drying", "state": { - "1": "Very wet", - "2": "Wet", - "3": "Moderate wet", - "4": "Dry", - "5": "Very dry", - "6": "Extemely dry" + "dry": "Dry", + "extremely_dry": "Extemely dry", + "moderate_wet": "Moderate wet", + "very_dry": "Very dry", + "very_wet": "Very wet", + "wet": "Wet" } }, "frozen_precipitation": { @@ -61,23 +61,23 @@ "fwiindex": { "name": "FWI-index", "state": { - "1": "Very low risk", - "2": "Low risk", - "3": "Moderate risk", - "4": "High risk", - "5": "Very high risk", - "6": "Extremely high risk" + "extreme": "Extremely high risk", + "high": "High risk", + "low": "Low risk", + "moderate": "Moderate risk", + "very_high": "Very high risk", + "very_low": "Very low risk" } }, "grassfire": { "name": "Highest grass fire risk", "state": { - "1": "Snow cover", - "2": "Grass fire season over", - "3": "Low", - "4": "Moderate", - "5": "High", - "6": "Very high" + "high": "High", + "low": "Low", + "moderate": "Moderate", + "season_over": "Grass fire season over", + "snow_cover": "Snow cover", + "very_high": "Very high" } }, "high_cloud": {