diff --git a/homeassistant/components/nws/const.py b/homeassistant/components/nws/const.py index b5814613847..6e08ef408d3 100644 --- a/homeassistant/components/nws/const.py +++ b/homeassistant/components/nws/const.py @@ -1,9 +1,10 @@ """Constants for National Weather Service Integration.""" from __future__ import annotations +from dataclasses import dataclass from datetime import timedelta -from typing import NamedTuple +from homeassistant.components.sensor import SensorEntityDescription from homeassistant.components.weather import ( ATTR_CONDITION_CLOUDY, ATTR_CONDITION_EXCEPTIONAL, @@ -99,92 +100,100 @@ OBSERVATION_VALID_TIME = timedelta(minutes=20) FORECAST_VALID_TIME = timedelta(minutes=45) -class NWSSensorMetadata(NamedTuple): - """Sensor metadata for an individual NWS sensor.""" +@dataclass +class NWSSensorEntityDescription(SensorEntityDescription): + """Class describing NWSSensor entities.""" - label: str - icon: str | None - device_class: str | None - unit: str - unit_convert: str + unit_convert: str | None = None -SENSOR_TYPES: dict[str, NWSSensorMetadata] = { - "dewpoint": NWSSensorMetadata( - "Dew Point", +SENSOR_TYPES: tuple[NWSSensorEntityDescription, ...] = ( + NWSSensorEntityDescription( + key="dewpoint", + name="Dew Point", icon=None, device_class=DEVICE_CLASS_TEMPERATURE, - unit=TEMP_CELSIUS, + unit_of_measurement=TEMP_CELSIUS, unit_convert=TEMP_CELSIUS, ), - "temperature": NWSSensorMetadata( - "Temperature", + NWSSensorEntityDescription( + key="temperature", + name="Temperature", icon=None, device_class=DEVICE_CLASS_TEMPERATURE, - unit=TEMP_CELSIUS, + unit_of_measurement=TEMP_CELSIUS, unit_convert=TEMP_CELSIUS, ), - "windChill": NWSSensorMetadata( - "Wind Chill", + NWSSensorEntityDescription( + key="windChill", + name="Wind Chill", icon=None, device_class=DEVICE_CLASS_TEMPERATURE, - unit=TEMP_CELSIUS, + unit_of_measurement=TEMP_CELSIUS, unit_convert=TEMP_CELSIUS, ), - "heatIndex": NWSSensorMetadata( - "Heat Index", + NWSSensorEntityDescription( + key="heatIndex", + name="Heat Index", icon=None, device_class=DEVICE_CLASS_TEMPERATURE, - unit=TEMP_CELSIUS, + unit_of_measurement=TEMP_CELSIUS, unit_convert=TEMP_CELSIUS, ), - "relativeHumidity": NWSSensorMetadata( - "Relative Humidity", + NWSSensorEntityDescription( + key="relativeHumidity", + name="Relative Humidity", icon=None, device_class=DEVICE_CLASS_HUMIDITY, - unit=PERCENTAGE, + unit_of_measurement=PERCENTAGE, unit_convert=PERCENTAGE, ), - "windSpeed": NWSSensorMetadata( - "Wind Speed", + NWSSensorEntityDescription( + key="windSpeed", + name="Wind Speed", icon="mdi:weather-windy", device_class=None, - unit=SPEED_KILOMETERS_PER_HOUR, + unit_of_measurement=SPEED_KILOMETERS_PER_HOUR, unit_convert=SPEED_MILES_PER_HOUR, ), - "windGust": NWSSensorMetadata( - "Wind Gust", + NWSSensorEntityDescription( + key="windGust", + name="Wind Gust", icon="mdi:weather-windy", device_class=None, - unit=SPEED_KILOMETERS_PER_HOUR, + unit_of_measurement=SPEED_KILOMETERS_PER_HOUR, unit_convert=SPEED_MILES_PER_HOUR, ), - "windDirection": NWSSensorMetadata( - "Wind Direction", + NWSSensorEntityDescription( + key="windDirection", + name="Wind Direction", icon="mdi:compass-rose", device_class=None, - unit=DEGREE, + unit_of_measurement=DEGREE, unit_convert=DEGREE, ), - "barometricPressure": NWSSensorMetadata( - "Barometric Pressure", + NWSSensorEntityDescription( + key="barometricPressure", + name="Barometric Pressure", icon=None, device_class=DEVICE_CLASS_PRESSURE, - unit=PRESSURE_PA, + unit_of_measurement=PRESSURE_PA, unit_convert=PRESSURE_INHG, ), - "seaLevelPressure": NWSSensorMetadata( - "Sea Level Pressure", + NWSSensorEntityDescription( + key="seaLevelPressure", + name="Sea Level Pressure", icon=None, device_class=DEVICE_CLASS_PRESSURE, - unit=PRESSURE_PA, + unit_of_measurement=PRESSURE_PA, unit_convert=PRESSURE_INHG, ), - "visibility": NWSSensorMetadata( - "Visibility", + NWSSensorEntityDescription( + key="visibility", + name="Visibility", icon="mdi:eye", device_class=None, - unit=LENGTH_METERS, + unit_of_measurement=LENGTH_METERS, unit_convert=LENGTH_MILES, ), -} +) diff --git a/homeassistant/components/nws/sensor.py b/homeassistant/components/nws/sensor.py index 8bbf6af8057..409856831a2 100644 --- a/homeassistant/components/nws/sensor.py +++ b/homeassistant/components/nws/sensor.py @@ -28,7 +28,7 @@ from .const import ( NWS_DATA, OBSERVATION_VALID_TIME, SENSOR_TYPES, - NWSSensorMetadata, + NWSSensorEntityDescription, ) PARALLEL_UPDATES = 0 @@ -39,32 +39,29 @@ async def async_setup_entry(hass, entry, async_add_entities): hass_data = hass.data[DOMAIN][entry.entry_id] station = entry.data[CONF_STATION] - entities = [] - for sensor_type, metadata in SENSOR_TYPES.items(): - entities.append( - NWSSensor( - hass, - entry.data, - hass_data, - sensor_type, - metadata, - station, - ), + async_add_entities( + NWSSensor( + hass=hass, + entry_data=entry.data, + hass_data=hass_data, + description=description, + station=station, ) - - async_add_entities(entities, False) + for description in SENSOR_TYPES + ) class NWSSensor(CoordinatorEntity, SensorEntity): """An NWS Sensor Entity.""" + entity_description: NWSSensorEntityDescription + def __init__( self, hass: HomeAssistant, entry_data, hass_data, - sensor_type, - metadata: NWSSensorMetadata, + description: NWSSensorEntityDescription, station, ): """Initialise the platform with a data instance.""" @@ -72,32 +69,29 @@ class NWSSensor(CoordinatorEntity, SensorEntity): self._nws = hass_data[NWS_DATA] self._latitude = entry_data[CONF_LATITUDE] self._longitude = entry_data[CONF_LONGITUDE] - self._type = sensor_type - self._metadata = metadata + self.entity_description = description - self._attr_name = f"{station} {metadata.label}" - self._attr_icon = metadata.icon - self._attr_device_class = metadata.device_class - if hass.config.units.is_metric: - self._attr_unit_of_measurement = metadata.unit - else: - self._attr_unit_of_measurement = metadata.unit_convert + self._attr_name = f"{station} {description.name}" + if not hass.config.units.is_metric: + self._attr_unit_of_measurement = description.unit_convert @property def state(self): """Return the state.""" - value = self._nws.observation.get(self._type) + value = self._nws.observation.get(self.entity_description.key) if value is None: return None - if self._attr_unit_of_measurement == SPEED_MILES_PER_HOUR: + # Set alias to unit property -> prevent unnecessary hasattr calls + unit_of_measurement = self.unit_of_measurement + if unit_of_measurement == SPEED_MILES_PER_HOUR: return round(convert_distance(value, LENGTH_KILOMETERS, LENGTH_MILES)) - if self._attr_unit_of_measurement == LENGTH_MILES: + if unit_of_measurement == LENGTH_MILES: return round(convert_distance(value, LENGTH_METERS, LENGTH_MILES)) - if self._attr_unit_of_measurement == PRESSURE_INHG: + if unit_of_measurement == PRESSURE_INHG: return round(convert_pressure(value, PRESSURE_PA, PRESSURE_INHG), 2) - if self._attr_unit_of_measurement == TEMP_CELSIUS: + if unit_of_measurement == TEMP_CELSIUS: return round(value, 1) - if self._attr_unit_of_measurement == PERCENTAGE: + if unit_of_measurement == PERCENTAGE: return round(value) return value @@ -109,7 +103,7 @@ class NWSSensor(CoordinatorEntity, SensorEntity): @property def unique_id(self): """Return a unique_id for this entity.""" - return f"{base_unique_id(self._latitude, self._longitude)}_{self._type}" + return f"{base_unique_id(self._latitude, self._longitude)}_{self.entity_description.key}" @property def available(self): diff --git a/tests/components/nws/test_sensor.py b/tests/components/nws/test_sensor.py index f5c0773380d..aa5ca3bf66c 100644 --- a/tests/components/nws/test_sensor.py +++ b/tests/components/nws/test_sensor.py @@ -35,12 +35,12 @@ async def test_imperial_metric( """Test with imperial and metric units.""" registry = await hass.helpers.entity_registry.async_get_registry() - for sensor_name, metadata in SENSOR_TYPES.items(): + for description in SENSOR_TYPES: registry.async_get_or_create( SENSOR_DOMAIN, DOMAIN, - f"35_-75_{sensor_name}", - suggested_object_id=f"abc_{metadata.label}", + f"35_-75_{description.key}", + suggested_object_id=f"abc_{description.name}", disabled_by=None, ) @@ -53,10 +53,11 @@ async def test_imperial_metric( await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - for sensor_name, metadata in SENSOR_TYPES.items(): - state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}") + for description in SENSOR_TYPES: + assert description.name + state = hass.states.get(f"sensor.abc_{slugify(description.name)}") assert state - assert state.state == result_observation[sensor_name] + assert state.state == result_observation[description.key] assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION @@ -67,12 +68,12 @@ async def test_none_values(hass, mock_simple_nws, no_weather): registry = await hass.helpers.entity_registry.async_get_registry() - for sensor_name, metadata in SENSOR_TYPES.items(): + for description in SENSOR_TYPES: registry.async_get_or_create( SENSOR_DOMAIN, DOMAIN, - f"35_-75_{sensor_name}", - suggested_object_id=f"abc_{metadata.label}", + f"35_-75_{description.key}", + suggested_object_id=f"abc_{description.name}", disabled_by=None, ) @@ -84,7 +85,8 @@ async def test_none_values(hass, mock_simple_nws, no_weather): await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() - for sensor_name, metadata in SENSOR_TYPES.items(): - state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}") + for description in SENSOR_TYPES: + assert description.name + state = hass.states.get(f"sensor.abc_{slugify(description.name)}") assert state assert state.state == STATE_UNKNOWN