mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
Use NamedTuple - nws (#53293)
This commit is contained in:
parent
e78a62c802
commit
d98e580c3c
@ -1,5 +1,8 @@
|
|||||||
"""Constants for National Weather Service Integration."""
|
"""Constants for National Weather Service Integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
ATTR_CONDITION_CLOUDY,
|
ATTR_CONDITION_CLOUDY,
|
||||||
@ -17,7 +20,6 @@ from homeassistant.components.weather import (
|
|||||||
ATTR_CONDITION_WINDY_VARIANT,
|
ATTR_CONDITION_WINDY_VARIANT,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
|
||||||
DEGREE,
|
DEGREE,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
@ -40,11 +42,6 @@ ATTRIBUTION = "Data from National Weather Service/NOAA"
|
|||||||
|
|
||||||
ATTR_FORECAST_DETAILED_DESCRIPTION = "detailed_description"
|
ATTR_FORECAST_DETAILED_DESCRIPTION = "detailed_description"
|
||||||
ATTR_FORECAST_DAYTIME = "daytime"
|
ATTR_FORECAST_DAYTIME = "daytime"
|
||||||
ATTR_ICON = "icon"
|
|
||||||
ATTR_LABEL = "label"
|
|
||||||
ATTR_UNIT = "unit"
|
|
||||||
ATTR_UNIT_CONVERT = "unit_convert"
|
|
||||||
ATTR_UNIT_CONVERT_METHOD = "unit_convert_method"
|
|
||||||
|
|
||||||
CONDITION_CLASSES = {
|
CONDITION_CLASSES = {
|
||||||
ATTR_CONDITION_EXCEPTIONAL: [
|
ATTR_CONDITION_EXCEPTIONAL: [
|
||||||
@ -101,82 +98,93 @@ COORDINATOR_FORECAST_HOURLY = "coordinator_forecast_hourly"
|
|||||||
OBSERVATION_VALID_TIME = timedelta(minutes=20)
|
OBSERVATION_VALID_TIME = timedelta(minutes=20)
|
||||||
FORECAST_VALID_TIME = timedelta(minutes=45)
|
FORECAST_VALID_TIME = timedelta(minutes=45)
|
||||||
|
|
||||||
SENSOR_TYPES = {
|
|
||||||
"dewpoint": {
|
class NWSSensorMetadata(NamedTuple):
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
"""Sensor metadata for an individual NWS sensor."""
|
||||||
ATTR_ICON: None,
|
|
||||||
ATTR_LABEL: "Dew Point",
|
label: str
|
||||||
ATTR_UNIT: TEMP_CELSIUS,
|
icon: str | None
|
||||||
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
|
device_class: str | None
|
||||||
},
|
unit: str
|
||||||
"temperature": {
|
unit_convert: str
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
|
||||||
ATTR_ICON: None,
|
|
||||||
ATTR_LABEL: "Temperature",
|
SENSOR_TYPES: dict[str, NWSSensorMetadata] = {
|
||||||
ATTR_UNIT: TEMP_CELSIUS,
|
"dewpoint": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
|
"Dew Point",
|
||||||
},
|
icon=None,
|
||||||
"windChill": {
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
unit=TEMP_CELSIUS,
|
||||||
ATTR_ICON: None,
|
unit_convert=TEMP_CELSIUS,
|
||||||
ATTR_LABEL: "Wind Chill",
|
),
|
||||||
ATTR_UNIT: TEMP_CELSIUS,
|
"temperature": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
|
"Temperature",
|
||||||
},
|
icon=None,
|
||||||
"heatIndex": {
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
|
unit=TEMP_CELSIUS,
|
||||||
ATTR_ICON: None,
|
unit_convert=TEMP_CELSIUS,
|
||||||
ATTR_LABEL: "Heat Index",
|
),
|
||||||
ATTR_UNIT: TEMP_CELSIUS,
|
"windChill": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
|
"Wind Chill",
|
||||||
},
|
icon=None,
|
||||||
"relativeHumidity": {
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
|
unit=TEMP_CELSIUS,
|
||||||
ATTR_ICON: None,
|
unit_convert=TEMP_CELSIUS,
|
||||||
ATTR_LABEL: "Relative Humidity",
|
),
|
||||||
ATTR_UNIT: PERCENTAGE,
|
"heatIndex": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: PERCENTAGE,
|
"Heat Index",
|
||||||
},
|
icon=None,
|
||||||
"windSpeed": {
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit=TEMP_CELSIUS,
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
unit_convert=TEMP_CELSIUS,
|
||||||
ATTR_LABEL: "Wind Speed",
|
),
|
||||||
ATTR_UNIT: SPEED_KILOMETERS_PER_HOUR,
|
"relativeHumidity": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: SPEED_MILES_PER_HOUR,
|
"Relative Humidity",
|
||||||
},
|
icon=None,
|
||||||
"windGust": {
|
device_class=DEVICE_CLASS_HUMIDITY,
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit=PERCENTAGE,
|
||||||
ATTR_ICON: "mdi:weather-windy",
|
unit_convert=PERCENTAGE,
|
||||||
ATTR_LABEL: "Wind Gust",
|
),
|
||||||
ATTR_UNIT: SPEED_KILOMETERS_PER_HOUR,
|
"windSpeed": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: SPEED_MILES_PER_HOUR,
|
"Wind Speed",
|
||||||
},
|
icon="mdi:weather-windy",
|
||||||
"windDirection": {
|
device_class=None,
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit=SPEED_KILOMETERS_PER_HOUR,
|
||||||
ATTR_ICON: "mdi:compass-rose",
|
unit_convert=SPEED_MILES_PER_HOUR,
|
||||||
ATTR_LABEL: "Wind Direction",
|
),
|
||||||
ATTR_UNIT: DEGREE,
|
"windGust": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: DEGREE,
|
"Wind Gust",
|
||||||
},
|
icon="mdi:weather-windy",
|
||||||
"barometricPressure": {
|
device_class=None,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
unit=SPEED_KILOMETERS_PER_HOUR,
|
||||||
ATTR_ICON: None,
|
unit_convert=SPEED_MILES_PER_HOUR,
|
||||||
ATTR_LABEL: "Barometric Pressure",
|
),
|
||||||
ATTR_UNIT: PRESSURE_PA,
|
"windDirection": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: PRESSURE_INHG,
|
"Wind Direction",
|
||||||
},
|
icon="mdi:compass-rose",
|
||||||
"seaLevelPressure": {
|
device_class=None,
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
|
unit=DEGREE,
|
||||||
ATTR_ICON: None,
|
unit_convert=DEGREE,
|
||||||
ATTR_LABEL: "Sea Level Pressure",
|
),
|
||||||
ATTR_UNIT: PRESSURE_PA,
|
"barometricPressure": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: PRESSURE_INHG,
|
"Barometric Pressure",
|
||||||
},
|
icon=None,
|
||||||
"visibility": {
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
ATTR_DEVICE_CLASS: None,
|
unit=PRESSURE_PA,
|
||||||
ATTR_ICON: "mdi:eye",
|
unit_convert=PRESSURE_INHG,
|
||||||
ATTR_LABEL: "Visibility",
|
),
|
||||||
ATTR_UNIT: LENGTH_METERS,
|
"seaLevelPressure": NWSSensorMetadata(
|
||||||
ATTR_UNIT_CONVERT: LENGTH_MILES,
|
"Sea Level Pressure",
|
||||||
},
|
icon=None,
|
||||||
|
device_class=DEVICE_CLASS_PRESSURE,
|
||||||
|
unit=PRESSURE_PA,
|
||||||
|
unit_convert=PRESSURE_INHG,
|
||||||
|
),
|
||||||
|
"visibility": NWSSensorMetadata(
|
||||||
|
"Visibility",
|
||||||
|
icon="mdi:eye",
|
||||||
|
device_class=None,
|
||||||
|
unit=LENGTH_METERS,
|
||||||
|
unit_convert=LENGTH_MILES,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from homeassistant.components.sensor import SensorEntity
|
from homeassistant.components.sensor import SensorEntity
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_DEVICE_CLASS,
|
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
LENGTH_KILOMETERS,
|
LENGTH_KILOMETERS,
|
||||||
@ -14,6 +13,7 @@ from homeassistant.const import (
|
|||||||
SPEED_MILES_PER_HOUR,
|
SPEED_MILES_PER_HOUR,
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
from homeassistant.util.distance import convert as convert_distance
|
from homeassistant.util.distance import convert as convert_distance
|
||||||
from homeassistant.util.dt import utcnow
|
from homeassistant.util.dt import utcnow
|
||||||
@ -21,10 +21,6 @@ from homeassistant.util.pressure import convert as convert_pressure
|
|||||||
|
|
||||||
from . import base_unique_id
|
from . import base_unique_id
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ICON,
|
|
||||||
ATTR_LABEL,
|
|
||||||
ATTR_UNIT,
|
|
||||||
ATTR_UNIT_CONVERT,
|
|
||||||
ATTRIBUTION,
|
ATTRIBUTION,
|
||||||
CONF_STATION,
|
CONF_STATION,
|
||||||
COORDINATOR_OBSERVATION,
|
COORDINATOR_OBSERVATION,
|
||||||
@ -32,6 +28,7 @@ from .const import (
|
|||||||
NWS_DATA,
|
NWS_DATA,
|
||||||
OBSERVATION_VALID_TIME,
|
OBSERVATION_VALID_TIME,
|
||||||
SENSOR_TYPES,
|
SENSOR_TYPES,
|
||||||
|
NWSSensorMetadata,
|
||||||
)
|
)
|
||||||
|
|
||||||
PARALLEL_UPDATES = 0
|
PARALLEL_UPDATES = 0
|
||||||
@ -43,21 +40,15 @@ async def async_setup_entry(hass, entry, async_add_entities):
|
|||||||
station = entry.data[CONF_STATION]
|
station = entry.data[CONF_STATION]
|
||||||
|
|
||||||
entities = []
|
entities = []
|
||||||
for sensor_type, sensor_data in SENSOR_TYPES.items():
|
for sensor_type, metadata in SENSOR_TYPES.items():
|
||||||
if hass.config.units.is_metric:
|
|
||||||
unit = sensor_data[ATTR_UNIT]
|
|
||||||
else:
|
|
||||||
unit = sensor_data[ATTR_UNIT_CONVERT]
|
|
||||||
entities.append(
|
entities.append(
|
||||||
NWSSensor(
|
NWSSensor(
|
||||||
|
hass,
|
||||||
entry.data,
|
entry.data,
|
||||||
hass_data,
|
hass_data,
|
||||||
sensor_type,
|
sensor_type,
|
||||||
|
metadata,
|
||||||
station,
|
station,
|
||||||
sensor_data[ATTR_LABEL],
|
|
||||||
sensor_data[ATTR_ICON],
|
|
||||||
sensor_data[ATTR_DEVICE_CLASS],
|
|
||||||
unit,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,14 +60,12 @@ class NWSSensor(CoordinatorEntity, SensorEntity):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
entry_data,
|
entry_data,
|
||||||
hass_data,
|
hass_data,
|
||||||
sensor_type,
|
sensor_type,
|
||||||
|
metadata: NWSSensorMetadata,
|
||||||
station,
|
station,
|
||||||
label,
|
|
||||||
icon,
|
|
||||||
device_class,
|
|
||||||
unit,
|
|
||||||
):
|
):
|
||||||
"""Initialise the platform with a data instance."""
|
"""Initialise the platform with a data instance."""
|
||||||
super().__init__(hass_data[COORDINATOR_OBSERVATION])
|
super().__init__(hass_data[COORDINATOR_OBSERVATION])
|
||||||
@ -84,11 +73,15 @@ class NWSSensor(CoordinatorEntity, SensorEntity):
|
|||||||
self._latitude = entry_data[CONF_LATITUDE]
|
self._latitude = entry_data[CONF_LATITUDE]
|
||||||
self._longitude = entry_data[CONF_LONGITUDE]
|
self._longitude = entry_data[CONF_LONGITUDE]
|
||||||
self._type = sensor_type
|
self._type = sensor_type
|
||||||
self._station = station
|
self._metadata = metadata
|
||||||
self._label = label
|
|
||||||
self._icon = icon
|
self._attr_name = f"{station} {metadata.label}"
|
||||||
self._device_class = device_class
|
self._attr_icon = metadata.icon
|
||||||
self._unit = unit
|
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
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
@ -96,43 +89,23 @@ class NWSSensor(CoordinatorEntity, SensorEntity):
|
|||||||
value = self._nws.observation.get(self._type)
|
value = self._nws.observation.get(self._type)
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
if self._unit == SPEED_MILES_PER_HOUR:
|
if self._attr_unit_of_measurement == SPEED_MILES_PER_HOUR:
|
||||||
return round(convert_distance(value, LENGTH_KILOMETERS, LENGTH_MILES))
|
return round(convert_distance(value, LENGTH_KILOMETERS, LENGTH_MILES))
|
||||||
if self._unit == LENGTH_MILES:
|
if self._attr_unit_of_measurement == LENGTH_MILES:
|
||||||
return round(convert_distance(value, LENGTH_METERS, LENGTH_MILES))
|
return round(convert_distance(value, LENGTH_METERS, LENGTH_MILES))
|
||||||
if self._unit == PRESSURE_INHG:
|
if self._attr_unit_of_measurement == PRESSURE_INHG:
|
||||||
return round(convert_pressure(value, PRESSURE_PA, PRESSURE_INHG), 2)
|
return round(convert_pressure(value, PRESSURE_PA, PRESSURE_INHG), 2)
|
||||||
if self._unit == TEMP_CELSIUS:
|
if self._attr_unit_of_measurement == TEMP_CELSIUS:
|
||||||
return round(value, 1)
|
return round(value, 1)
|
||||||
if self._unit == PERCENTAGE:
|
if self._attr_unit_of_measurement == PERCENTAGE:
|
||||||
return round(value)
|
return round(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@property
|
|
||||||
def icon(self):
|
|
||||||
"""Return the icon."""
|
|
||||||
return self._icon
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_class(self):
|
|
||||||
"""Return the device class."""
|
|
||||||
return self._device_class
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unit_of_measurement(self):
|
|
||||||
"""Return the unit the value is expressed in."""
|
|
||||||
return self._unit
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
"""Return the attribution."""
|
"""Return the attribution."""
|
||||||
return {ATTR_ATTRIBUTION: ATTRIBUTION}
|
return {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the station."""
|
|
||||||
return f"{self._station} {self._label}"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self):
|
def unique_id(self):
|
||||||
"""Return a unique_id for this entity."""
|
"""Return a unique_id for this entity."""
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
"""Sensors for National Weather Service (NWS)."""
|
"""Sensors for National Weather Service (NWS)."""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.nws.const import (
|
from homeassistant.components.nws.const import ATTRIBUTION, DOMAIN, SENSOR_TYPES
|
||||||
ATTR_LABEL,
|
|
||||||
ATTRIBUTION,
|
|
||||||
DOMAIN,
|
|
||||||
SENSOR_TYPES,
|
|
||||||
)
|
|
||||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN
|
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
@ -40,12 +35,12 @@ async def test_imperial_metric(
|
|||||||
"""Test with imperial and metric units."""
|
"""Test with imperial and metric units."""
|
||||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||||
|
|
||||||
for sensor_name, sensor_data in SENSOR_TYPES.items():
|
for sensor_name, metadata in SENSOR_TYPES.items():
|
||||||
registry.async_get_or_create(
|
registry.async_get_or_create(
|
||||||
SENSOR_DOMAIN,
|
SENSOR_DOMAIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
f"35_-75_{sensor_name}",
|
f"35_-75_{sensor_name}",
|
||||||
suggested_object_id=f"abc_{sensor_data[ATTR_LABEL]}",
|
suggested_object_id=f"abc_{metadata.label}",
|
||||||
disabled_by=None,
|
disabled_by=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,8 +53,8 @@ async def test_imperial_metric(
|
|||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
for sensor_name, sensor_data in SENSOR_TYPES.items():
|
for sensor_name, metadata in SENSOR_TYPES.items():
|
||||||
state = hass.states.get(f"sensor.abc_{slugify(sensor_data[ATTR_LABEL])}")
|
state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == result_observation[sensor_name]
|
assert state.state == result_observation[sensor_name]
|
||||||
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
assert state.attributes.get(ATTR_ATTRIBUTION) == ATTRIBUTION
|
||||||
@ -72,12 +67,12 @@ async def test_none_values(hass, mock_simple_nws, no_weather):
|
|||||||
|
|
||||||
registry = await hass.helpers.entity_registry.async_get_registry()
|
registry = await hass.helpers.entity_registry.async_get_registry()
|
||||||
|
|
||||||
for sensor_name, sensor_data in SENSOR_TYPES.items():
|
for sensor_name, metadata in SENSOR_TYPES.items():
|
||||||
registry.async_get_or_create(
|
registry.async_get_or_create(
|
||||||
SENSOR_DOMAIN,
|
SENSOR_DOMAIN,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
f"35_-75_{sensor_name}",
|
f"35_-75_{sensor_name}",
|
||||||
suggested_object_id=f"abc_{sensor_data[ATTR_LABEL]}",
|
suggested_object_id=f"abc_{metadata.label}",
|
||||||
disabled_by=None,
|
disabled_by=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -89,7 +84,7 @@ async def test_none_values(hass, mock_simple_nws, no_weather):
|
|||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
for sensor_name, sensor_data in SENSOR_TYPES.items():
|
for sensor_name, metadata in SENSOR_TYPES.items():
|
||||||
state = hass.states.get(f"sensor.abc_{slugify(sensor_data[ATTR_LABEL])}")
|
state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNKNOWN
|
||||||
|
Loading…
x
Reference in New Issue
Block a user