Use NamedTuple - nws (#53293)

This commit is contained in:
Marc Mueller 2021-07-22 07:24:07 +02:00 committed by GitHub
parent e78a62c802
commit d98e580c3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 122 additions and 146 deletions

View File

@ -1,5 +1,8 @@
"""Constants for National Weather Service Integration."""
from __future__ import annotations
from datetime import timedelta
from typing import NamedTuple
from homeassistant.components.weather import (
ATTR_CONDITION_CLOUDY,
@ -17,7 +20,6 @@ from homeassistant.components.weather import (
ATTR_CONDITION_WINDY_VARIANT,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
DEGREE,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PRESSURE,
@ -40,11 +42,6 @@ ATTRIBUTION = "Data from National Weather Service/NOAA"
ATTR_FORECAST_DETAILED_DESCRIPTION = "detailed_description"
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 = {
ATTR_CONDITION_EXCEPTIONAL: [
@ -101,82 +98,93 @@ COORDINATOR_FORECAST_HOURLY = "coordinator_forecast_hourly"
OBSERVATION_VALID_TIME = timedelta(minutes=20)
FORECAST_VALID_TIME = timedelta(minutes=45)
SENSOR_TYPES = {
"dewpoint": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_LABEL: "Dew Point",
ATTR_UNIT: TEMP_CELSIUS,
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
},
"temperature": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_LABEL: "Temperature",
ATTR_UNIT: TEMP_CELSIUS,
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
},
"windChill": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_LABEL: "Wind Chill",
ATTR_UNIT: TEMP_CELSIUS,
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
},
"heatIndex": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE,
ATTR_ICON: None,
ATTR_LABEL: "Heat Index",
ATTR_UNIT: TEMP_CELSIUS,
ATTR_UNIT_CONVERT: TEMP_CELSIUS,
},
"relativeHumidity": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_HUMIDITY,
ATTR_ICON: None,
ATTR_LABEL: "Relative Humidity",
ATTR_UNIT: PERCENTAGE,
ATTR_UNIT_CONVERT: PERCENTAGE,
},
"windSpeed": {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:weather-windy",
ATTR_LABEL: "Wind Speed",
ATTR_UNIT: SPEED_KILOMETERS_PER_HOUR,
ATTR_UNIT_CONVERT: SPEED_MILES_PER_HOUR,
},
"windGust": {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:weather-windy",
ATTR_LABEL: "Wind Gust",
ATTR_UNIT: SPEED_KILOMETERS_PER_HOUR,
ATTR_UNIT_CONVERT: SPEED_MILES_PER_HOUR,
},
"windDirection": {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:compass-rose",
ATTR_LABEL: "Wind Direction",
ATTR_UNIT: DEGREE,
ATTR_UNIT_CONVERT: DEGREE,
},
"barometricPressure": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
ATTR_ICON: None,
ATTR_LABEL: "Barometric Pressure",
ATTR_UNIT: PRESSURE_PA,
ATTR_UNIT_CONVERT: PRESSURE_INHG,
},
"seaLevelPressure": {
ATTR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE,
ATTR_ICON: None,
ATTR_LABEL: "Sea Level Pressure",
ATTR_UNIT: PRESSURE_PA,
ATTR_UNIT_CONVERT: PRESSURE_INHG,
},
"visibility": {
ATTR_DEVICE_CLASS: None,
ATTR_ICON: "mdi:eye",
ATTR_LABEL: "Visibility",
ATTR_UNIT: LENGTH_METERS,
ATTR_UNIT_CONVERT: LENGTH_MILES,
},
class NWSSensorMetadata(NamedTuple):
"""Sensor metadata for an individual NWS sensor."""
label: str
icon: str | None
device_class: str | None
unit: str
unit_convert: str
SENSOR_TYPES: dict[str, NWSSensorMetadata] = {
"dewpoint": NWSSensorMetadata(
"Dew Point",
icon=None,
device_class=DEVICE_CLASS_TEMPERATURE,
unit=TEMP_CELSIUS,
unit_convert=TEMP_CELSIUS,
),
"temperature": NWSSensorMetadata(
"Temperature",
icon=None,
device_class=DEVICE_CLASS_TEMPERATURE,
unit=TEMP_CELSIUS,
unit_convert=TEMP_CELSIUS,
),
"windChill": NWSSensorMetadata(
"Wind Chill",
icon=None,
device_class=DEVICE_CLASS_TEMPERATURE,
unit=TEMP_CELSIUS,
unit_convert=TEMP_CELSIUS,
),
"heatIndex": NWSSensorMetadata(
"Heat Index",
icon=None,
device_class=DEVICE_CLASS_TEMPERATURE,
unit=TEMP_CELSIUS,
unit_convert=TEMP_CELSIUS,
),
"relativeHumidity": NWSSensorMetadata(
"Relative Humidity",
icon=None,
device_class=DEVICE_CLASS_HUMIDITY,
unit=PERCENTAGE,
unit_convert=PERCENTAGE,
),
"windSpeed": NWSSensorMetadata(
"Wind Speed",
icon="mdi:weather-windy",
device_class=None,
unit=SPEED_KILOMETERS_PER_HOUR,
unit_convert=SPEED_MILES_PER_HOUR,
),
"windGust": NWSSensorMetadata(
"Wind Gust",
icon="mdi:weather-windy",
device_class=None,
unit=SPEED_KILOMETERS_PER_HOUR,
unit_convert=SPEED_MILES_PER_HOUR,
),
"windDirection": NWSSensorMetadata(
"Wind Direction",
icon="mdi:compass-rose",
device_class=None,
unit=DEGREE,
unit_convert=DEGREE,
),
"barometricPressure": NWSSensorMetadata(
"Barometric Pressure",
icon=None,
device_class=DEVICE_CLASS_PRESSURE,
unit=PRESSURE_PA,
unit_convert=PRESSURE_INHG,
),
"seaLevelPressure": NWSSensorMetadata(
"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,
),
}

View File

@ -2,7 +2,6 @@
from homeassistant.components.sensor import SensorEntity
from homeassistant.const import (
ATTR_ATTRIBUTION,
ATTR_DEVICE_CLASS,
CONF_LATITUDE,
CONF_LONGITUDE,
LENGTH_KILOMETERS,
@ -14,6 +13,7 @@ from homeassistant.const import (
SPEED_MILES_PER_HOUR,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.distance import convert as convert_distance
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 .const import (
ATTR_ICON,
ATTR_LABEL,
ATTR_UNIT,
ATTR_UNIT_CONVERT,
ATTRIBUTION,
CONF_STATION,
COORDINATOR_OBSERVATION,
@ -32,6 +28,7 @@ from .const import (
NWS_DATA,
OBSERVATION_VALID_TIME,
SENSOR_TYPES,
NWSSensorMetadata,
)
PARALLEL_UPDATES = 0
@ -43,21 +40,15 @@ async def async_setup_entry(hass, entry, async_add_entities):
station = entry.data[CONF_STATION]
entities = []
for sensor_type, sensor_data in SENSOR_TYPES.items():
if hass.config.units.is_metric:
unit = sensor_data[ATTR_UNIT]
else:
unit = sensor_data[ATTR_UNIT_CONVERT]
for sensor_type, metadata in SENSOR_TYPES.items():
entities.append(
NWSSensor(
hass,
entry.data,
hass_data,
sensor_type,
metadata,
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__(
self,
hass: HomeAssistant,
entry_data,
hass_data,
sensor_type,
metadata: NWSSensorMetadata,
station,
label,
icon,
device_class,
unit,
):
"""Initialise the platform with a data instance."""
super().__init__(hass_data[COORDINATOR_OBSERVATION])
@ -84,11 +73,15 @@ class NWSSensor(CoordinatorEntity, SensorEntity):
self._latitude = entry_data[CONF_LATITUDE]
self._longitude = entry_data[CONF_LONGITUDE]
self._type = sensor_type
self._station = station
self._label = label
self._icon = icon
self._device_class = device_class
self._unit = unit
self._metadata = metadata
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
@property
def state(self):
@ -96,43 +89,23 @@ class NWSSensor(CoordinatorEntity, SensorEntity):
value = self._nws.observation.get(self._type)
if value is 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))
if self._unit == LENGTH_MILES:
if self._attr_unit_of_measurement == 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)
if self._unit == TEMP_CELSIUS:
if self._attr_unit_of_measurement == TEMP_CELSIUS:
return round(value, 1)
if self._unit == PERCENTAGE:
if self._attr_unit_of_measurement == PERCENTAGE:
return round(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
def device_state_attributes(self):
"""Return the attribution."""
return {ATTR_ATTRIBUTION: ATTRIBUTION}
@property
def name(self):
"""Return the name of the station."""
return f"{self._station} {self._label}"
@property
def unique_id(self):
"""Return a unique_id for this entity."""

View File

@ -1,12 +1,7 @@
"""Sensors for National Weather Service (NWS)."""
import pytest
from homeassistant.components.nws.const import (
ATTR_LABEL,
ATTRIBUTION,
DOMAIN,
SENSOR_TYPES,
)
from homeassistant.components.nws.const import ATTRIBUTION, DOMAIN, SENSOR_TYPES
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN
from homeassistant.util import slugify
@ -40,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, sensor_data in SENSOR_TYPES.items():
for sensor_name, metadata in SENSOR_TYPES.items():
registry.async_get_or_create(
SENSOR_DOMAIN,
DOMAIN,
f"35_-75_{sensor_name}",
suggested_object_id=f"abc_{sensor_data[ATTR_LABEL]}",
suggested_object_id=f"abc_{metadata.label}",
disabled_by=None,
)
@ -58,8 +53,8 @@ async def test_imperial_metric(
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
for sensor_name, sensor_data in SENSOR_TYPES.items():
state = hass.states.get(f"sensor.abc_{slugify(sensor_data[ATTR_LABEL])}")
for sensor_name, metadata in SENSOR_TYPES.items():
state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}")
assert state
assert state.state == result_observation[sensor_name]
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()
for sensor_name, sensor_data in SENSOR_TYPES.items():
for sensor_name, metadata in SENSOR_TYPES.items():
registry.async_get_or_create(
SENSOR_DOMAIN,
DOMAIN,
f"35_-75_{sensor_name}",
suggested_object_id=f"abc_{sensor_data[ATTR_LABEL]}",
suggested_object_id=f"abc_{metadata.label}",
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.async_block_till_done()
for sensor_name, sensor_data in SENSOR_TYPES.items():
state = hass.states.get(f"sensor.abc_{slugify(sensor_data[ATTR_LABEL])}")
for sensor_name, metadata in SENSOR_TYPES.items():
state = hass.states.get(f"sensor.abc_{slugify(metadata.label)}")
assert state
assert state.state == STATE_UNKNOWN