1 """Support for NWS weather service."""
3 from __future__
import annotations
5 from functools
import partial
6 from types
import MappingProxyType
7 from typing
import TYPE_CHECKING, Any, Literal, Required, TypedDict, cast
9 import voluptuous
as vol
12 ATTR_CONDITION_CLEAR_NIGHT,
14 ATTR_FORECAST_CONDITION,
15 ATTR_FORECAST_HUMIDITY,
16 ATTR_FORECAST_IS_DAYTIME,
17 ATTR_FORECAST_NATIVE_DEW_POINT,
18 ATTR_FORECAST_NATIVE_TEMP,
19 ATTR_FORECAST_NATIVE_WIND_SPEED,
20 ATTR_FORECAST_PRECIPITATION_PROBABILITY,
22 ATTR_FORECAST_WIND_BEARING,
23 DOMAIN
as WEATHER_DOMAIN,
24 CoordinatorWeatherEntity,
48 from .
import NWSConfigEntry, NWSData, base_unique_id, device_info
50 ATTR_FORECAST_DETAILED_DESCRIPTION,
51 ATTR_FORECAST_SHORT_DESCRIPTION,
64 """Convert NWS codes to HA condition.
66 Choose first condition in CONDITION_CLASSES that exists in weather code.
67 If no match is found, return first condition from NWS
69 conditions: list[str] = [w[0]
for w
in weather]
75 for key, value
in CONDITION_CLASSES.items()
76 if any(condition
in value
for condition
in conditions)
83 return ATTR_CONDITION_SUNNY
85 return ATTR_CONDITION_CLEAR_NIGHT
90 hass: HomeAssistant, entry: NWSConfigEntry, async_add_entities: AddEntitiesCallback
92 """Set up the NWS weather platform."""
93 entity_registry = er.async_get(hass)
94 nws_data = entry.runtime_data
97 if entity_id := entity_registry.async_get_entity_id(
102 entity_registry.async_remove(entity_id)
104 platform = entity_platform.async_get_current_platform()
106 platform.async_register_entity_service(
107 "get_forecasts_extra",
108 {vol.Required(
"type"): vol.In((
"hourly",
"twice_daily"))},
109 "async_get_forecasts_extra_service",
110 supports_response=SupportsResponse.ONLY,
117 """Forecast extra fields from NWS."""
120 datetime: Required[str]
121 is_daytime: bool |
None
123 detailed_description: str |
None
124 short_description: str |
None
128 """Calculate unique ID."""
129 latitude = entry_data[CONF_LATITUDE]
130 longitude = entry_data[CONF_LONGITUDE]
131 return f
"{base_unique_id(latitude, longitude)}_{mode}"
135 """Representation of a weather condition."""
137 _attr_attribution = ATTRIBUTION
138 _attr_should_poll =
False
139 _attr_supported_features = (
140 WeatherEntityFeature.FORECAST_HOURLY | WeatherEntityFeature.FORECAST_TWICE_DAILY
142 _attr_native_temperature_unit = UnitOfTemperature.CELSIUS
143 _attr_native_pressure_unit = UnitOfPressure.PA
144 _attr_native_wind_speed_unit = UnitOfSpeed.KILOMETERS_PER_HOUR
145 _attr_native_visibility_unit = UnitOfLength.METERS
149 entry_data: MappingProxyType[str, Any],
152 """Initialise the platform with a data instance and station name."""
154 observation_coordinator=nws_data.coordinator_observation,
155 hourly_coordinator=nws_data.coordinator_forecast_hourly,
156 twice_daily_coordinator=nws_data.coordinator_forecast,
157 hourly_forecast_valid=FORECAST_VALID_TIME,
158 twice_daily_forecast_valid=FORECAST_VALID_TIME,
160 self.
nwsnws = nws_data.api
161 latitude = entry_data[CONF_LATITUDE]
162 longitude = entry_data[CONF_LONGITUDE]
171 """When entity is added to hass."""
177 for forecast_type
in (
"twice_daily",
"hourly"):
181 forecast_type = cast(Literal[
"twice_daily",
"hourly"], forecast_type)
182 self.unsub_forecast[forecast_type] = coordinator.async_add_listener(
188 """Return the current temperature."""
189 if observation := self.
nwsnws.observation:
190 return observation.get(
"temperature")
195 """Return the current pressure."""
196 if observation := self.
nwsnws.observation:
197 return observation.get(
"seaLevelPressure")
202 """Return the name of the sensor."""
203 if observation := self.
nwsnws.observation:
204 return observation.get(
"relativeHumidity")
209 """Return the current windspeed."""
210 if observation := self.
nwsnws.observation:
211 return observation.get(
"windSpeed")
216 """Return the current wind bearing (degrees)."""
217 if observation := self.
nwsnws.observation:
218 return observation.get(
"windDirection")
223 """Return current condition."""
225 if observation := self.
nwsnws.observation:
226 weather = observation.get(
"iconWeather")
227 time = cast(str, observation.get(
"iconTime"))
235 """Return visibility."""
236 if observation := self.
nwsnws.observation:
237 return observation.get(
"visibility")
242 nws_forecast: list[dict[str, Any]],
245 """Return forecast."""
246 if nws_forecast
is None:
248 forecast: list[Forecast] = []
249 for forecast_entry
in nws_forecast:
251 ATTR_FORECAST_TIME: cast(str, forecast_entry.get(
"startTime")),
254 if (temp := forecast_entry.get(
"temperature"))
is not None:
255 data[ATTR_FORECAST_NATIVE_TEMP] = TemperatureConverter.convert(
256 temp, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS
259 data[ATTR_FORECAST_NATIVE_TEMP] =
None
261 data[ATTR_FORECAST_PRECIPITATION_PROBABILITY] = forecast_entry.get(
262 "probabilityOfPrecipitation"
265 if (dewp := forecast_entry.get(
"dewpoint"))
is not None:
266 data[ATTR_FORECAST_NATIVE_DEW_POINT] = TemperatureConverter.convert(
267 dewp, UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS
270 data[ATTR_FORECAST_NATIVE_DEW_POINT] =
None
272 data[ATTR_FORECAST_HUMIDITY] = forecast_entry.get(
"relativeHumidity")
275 data[ATTR_FORECAST_IS_DAYTIME] = forecast_entry.get(
"isDaytime")
277 time = forecast_entry.get(
"iconTime")
278 weather = forecast_entry.get(
"iconWeather")
279 data[ATTR_FORECAST_CONDITION] = (
283 data[ATTR_FORECAST_WIND_BEARING] = forecast_entry.get(
"windBearing")
284 wind_speed = forecast_entry.get(
"windSpeedAvg")
285 if wind_speed
is not None:
286 data[ATTR_FORECAST_NATIVE_WIND_SPEED] = SpeedConverter.convert(
288 UnitOfSpeed.MILES_PER_HOUR,
289 UnitOfSpeed.KILOMETERS_PER_HOUR,
292 data[ATTR_FORECAST_NATIVE_WIND_SPEED] =
None
293 forecast.append(data)
298 nws_forecast: list[dict[str, Any]] |
None,
300 ) -> list[ExtraForecast]:
301 """Return forecast."""
302 if nws_forecast
is None:
304 forecast: list[ExtraForecast] = []
305 for forecast_entry
in nws_forecast:
306 data: ExtraForecast = {
307 ATTR_FORECAST_TIME: cast(str, forecast_entry.get(
"startTime")),
310 data[ATTR_FORECAST_IS_DAYTIME] = forecast_entry.get(
"isDaytime")
312 data[ATTR_FORECAST_DETAILED_DESCRIPTION] = forecast_entry.get(
316 data[ATTR_FORECAST_SHORT_DESCRIPTION] = forecast_entry.get(
"shortForecast")
317 forecast.append(data)
322 """Return the hourly forecast in native units."""
323 return self.
_forecast_forecast(self.
nwsnws.forecast_hourly, HOURLY)
327 """Return the twice daily forecast in native units."""
328 return self.
_forecast_forecast(self.
nwsnws.forecast, DAYNIGHT)
331 """Update the entity.
333 Only used by the generic entity update service.
337 for forecast_type
in (
"twice_daily",
"hourly"):
339 await coordinator.async_request_refresh()
342 """Get extra weather forecast."""
344 nws_forecast = self.
_forecast_extra_forecast_extra(self.
nwsnws.forecast_hourly, HOURLY)
348 "forecast": cast(JsonValueType, nws_forecast),
list[Forecast]|None _async_forecast_twice_daily(self)
list[ExtraForecast] _forecast_extra(self, list[dict[str, Any]]|None nws_forecast, str mode)
float|None humidity(self)
int|None native_pressure(self)
ServiceResponse async_get_forecasts_extra_service(self, type)
float|None native_temperature(self)
int|None native_visibility(self)
int|None wind_bearing(self)
None __init__(self, MappingProxyType[str, Any] entry_data, NWSData nws_data)
None async_added_to_hass(self)
list[Forecast] _forecast(self, list[dict[str, Any]] nws_forecast, str mode)
list[Forecast]|None _async_forecast_hourly(self)
float|None native_wind_speed(self)
None _handle_forecast_update(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
None _remove_forecast_listener(self, Literal["daily", "hourly", "twice_daily"] forecast_type)
None async_on_remove(self, CALLBACK_TYPE func)
DeviceInfo|None device_info(self)
None async_request_refresh(self)
None async_setup_entry(HomeAssistant hass, NWSConfigEntry entry, AddEntitiesCallback async_add_entities)
str _calculate_unique_id(MappingProxyType[str, Any] entry_data, str mode)
str convert_condition(str time, tuple[tuple[str, int|None],...] weather)