mirror of
https://github.com/home-assistant/core.git
synced 2025-08-05 13:45:12 +02:00
check and test for missing observations
This commit is contained in:
@@ -18,7 +18,7 @@ from homeassistant.const import (
|
|||||||
CONF_API_KEY, CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE,
|
CONF_API_KEY, CONF_NAME, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE,
|
||||||
LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_MILES, PRESSURE_HPA, PRESSURE_PA,
|
LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_MILES, PRESSURE_HPA, PRESSURE_PA,
|
||||||
PRESSURE_INHG, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
PRESSURE_INHG, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
from homeassistant.exceptions import PlatformNotReady
|
from homeassistant.exceptions import PlatformNotReady, ConfigEntryNotReady
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.util import Throttle
|
from homeassistant.util import Throttle
|
||||||
@@ -139,7 +139,7 @@ async def async_setup_platform(hass, config, async_add_entities,
|
|||||||
|
|
||||||
if None in (latitude, longitude):
|
if None in (latitude, longitude):
|
||||||
_LOGGER.error("Latitude/longitude not set in Home Assistant config")
|
_LOGGER.error("Latitude/longitude not set in Home Assistant config")
|
||||||
return
|
return ConfigEntryNotReady
|
||||||
|
|
||||||
websession = async_get_clientsession(hass)
|
websession = async_get_clientsession(hass)
|
||||||
# ID request as being from HA, pynws prepends the api_key in addition
|
# ID request as being from HA, pynws prepends the api_key in addition
|
||||||
@@ -154,7 +154,7 @@ async def async_setup_platform(hass, config, async_add_entities,
|
|||||||
stations = await nws.stations()
|
stations = await nws.stations()
|
||||||
except ERRORS as status:
|
except ERRORS as status:
|
||||||
_LOGGER.error("Error getting station list for %s: %s",
|
_LOGGER.error("Error getting station list for %s: %s",
|
||||||
nws.latlon, status)
|
(latitude, longitude), status)
|
||||||
raise PlatformNotReady
|
raise PlatformNotReady
|
||||||
_LOGGER.debug("Station list: %s", stations)
|
_LOGGER.debug("Station list: %s", stations)
|
||||||
nws.station = stations[0]
|
nws.station = stations[0]
|
||||||
@@ -197,9 +197,9 @@ class NWSWeather(WeatherEntity):
|
|||||||
self._nws.station, status)
|
self._nws.station, status)
|
||||||
else:
|
else:
|
||||||
self._observation = obs[0]
|
self._observation = obs[0]
|
||||||
if 'rawMessage' in self._observation.keys():
|
metar_msg = self._observation.get('rawMessage')
|
||||||
self._metar_obs = self._metar(
|
if metar_msg:
|
||||||
self._observation['rawMessage'])
|
self._metar_obs = self._metar(metar_msg)
|
||||||
else:
|
else:
|
||||||
self._metar_obs = None
|
self._metar_obs = None
|
||||||
_LOGGER.debug("Observations: %s", self._observation)
|
_LOGGER.debug("Observations: %s", self._observation)
|
||||||
@@ -229,7 +229,9 @@ class NWSWeather(WeatherEntity):
|
|||||||
@property
|
@property
|
||||||
def temperature(self):
|
def temperature(self):
|
||||||
"""Return the current temperature."""
|
"""Return the current temperature."""
|
||||||
temp_c = self._observation['temperature']['value']
|
temp_c = None
|
||||||
|
if self._observation:
|
||||||
|
temp_c = self._observation.get('temperature', {}).get('value')
|
||||||
if temp_c is None and self._metar_obs and self._metar_obs.temp:
|
if temp_c is None and self._metar_obs and self._metar_obs.temp:
|
||||||
temp_c = self._metar_obs.temp.value(units='C')
|
temp_c = self._metar_obs.temp.value(units='C')
|
||||||
if temp_c is not None:
|
if temp_c is not None:
|
||||||
@@ -239,7 +241,10 @@ class NWSWeather(WeatherEntity):
|
|||||||
@property
|
@property
|
||||||
def pressure(self):
|
def pressure(self):
|
||||||
"""Return the current pressure."""
|
"""Return the current pressure."""
|
||||||
pressure_pa = self._observation['seaLevelPressure']['value']
|
pressure_pa = None
|
||||||
|
if self._observation:
|
||||||
|
pressure_pa = self._observation.get('seaLevelPressure',
|
||||||
|
{}).get('value')
|
||||||
|
|
||||||
if pressure_pa is None and self._metar_obs and self._metar_obs.press:
|
if pressure_pa is None and self._metar_obs and self._metar_obs.press:
|
||||||
pressure_hpa = self._metar_obs.press.value(units='HPA')
|
pressure_hpa = self._metar_obs.press.value(units='HPA')
|
||||||
@@ -247,9 +252,11 @@ class NWSWeather(WeatherEntity):
|
|||||||
return None
|
return None
|
||||||
pressure_pa = convert_pressure(pressure_hpa, PRESSURE_HPA,
|
pressure_pa = convert_pressure(pressure_hpa, PRESSURE_HPA,
|
||||||
PRESSURE_PA)
|
PRESSURE_PA)
|
||||||
|
if pressure_pa is None:
|
||||||
|
return None
|
||||||
if self._is_metric:
|
if self._is_metric:
|
||||||
pressure = convert_pressure(pressure_pa, PRESSURE_PA, PRESSURE_HPA)
|
pressure = convert_pressure(pressure_pa,
|
||||||
|
PRESSURE_PA, PRESSURE_HPA)
|
||||||
pressure = round(pressure)
|
pressure = round(pressure)
|
||||||
else:
|
else:
|
||||||
pressure = convert_pressure(pressure_pa,
|
pressure = convert_pressure(pressure_pa,
|
||||||
@@ -260,12 +267,18 @@ class NWSWeather(WeatherEntity):
|
|||||||
@property
|
@property
|
||||||
def humidity(self):
|
def humidity(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
return self._observation['relativeHumidity']['value']
|
humidity = None
|
||||||
|
if self._observation:
|
||||||
|
humidity = self._observation.get('relativeHumidity',
|
||||||
|
{}).get('value')
|
||||||
|
return humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wind_speed(self):
|
def wind_speed(self):
|
||||||
"""Return the current windspeed."""
|
"""Return the current windspeed."""
|
||||||
wind_m_s = self._observation['windSpeed']['value']
|
wind_m_s = None
|
||||||
|
if self._observation:
|
||||||
|
wind_m_s = self._observation.get('windSpeed', {}).get('value')
|
||||||
if wind_m_s is None and self._metar_obs and self._metar_obs.wind_speed:
|
if wind_m_s is None and self._metar_obs and self._metar_obs.wind_speed:
|
||||||
wind_m_s = self._metar_obs.wind_speed.value(units='MPS')
|
wind_m_s = self._metar_obs.wind_speed.value(units='MPS')
|
||||||
if wind_m_s is None:
|
if wind_m_s is None:
|
||||||
@@ -282,7 +295,10 @@ class NWSWeather(WeatherEntity):
|
|||||||
@property
|
@property
|
||||||
def wind_bearing(self):
|
def wind_bearing(self):
|
||||||
"""Return the current wind bearing (degrees)."""
|
"""Return the current wind bearing (degrees)."""
|
||||||
wind_bearing = self._observation['windDirection']['value']
|
wind_bearing = None
|
||||||
|
if self._observation:
|
||||||
|
wind_bearing = self._observation.get('windDirection',
|
||||||
|
{}).get('value')
|
||||||
if wind_bearing is None and (self._metar_obs
|
if wind_bearing is None and (self._metar_obs
|
||||||
and self._metar_obs.wind_dir):
|
and self._metar_obs.wind_dir):
|
||||||
wind_bearing = self._metar_obs.wind_dir.value()
|
wind_bearing = self._metar_obs.wind_dir.value()
|
||||||
@@ -296,14 +312,21 @@ class NWSWeather(WeatherEntity):
|
|||||||
@property
|
@property
|
||||||
def condition(self):
|
def condition(self):
|
||||||
"""Return current condition."""
|
"""Return current condition."""
|
||||||
time, weather = parse_icon(self._observation['icon'])
|
icon = None
|
||||||
cond, _ = convert_condition(time, weather)
|
if self._observation:
|
||||||
return cond
|
icon = self._observation.get('icon')
|
||||||
|
if icon:
|
||||||
|
time, weather = parse_icon(self._observation['icon'])
|
||||||
|
cond, _ = convert_condition(time, weather)
|
||||||
|
return cond
|
||||||
|
return
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visibility(self):
|
def visibility(self):
|
||||||
"""Return visibility."""
|
"""Return visibility."""
|
||||||
vis_m = self._observation['visibility']['value']
|
vis_m = None
|
||||||
|
if self._observation:
|
||||||
|
vis_m = self._observation.get('visibility', {}).get('value')
|
||||||
if vis_m is None and self._metar_obs and self._metar_obs.vis:
|
if vis_m is None and self._metar_obs and self._metar_obs.vis:
|
||||||
vis_m = self._metar_obs.vis.value(units='M')
|
vis_m = self._metar_obs.vis.value(units='M')
|
||||||
if vis_m is None:
|
if vis_m is None:
|
||||||
|
@@ -116,11 +116,13 @@ class WeatherEntity(Entity):
|
|||||||
@property
|
@property
|
||||||
def state_attributes(self):
|
def state_attributes(self):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
data = {
|
data = {}
|
||||||
ATTR_WEATHER_TEMPERATURE: show_temp(
|
if self.temperature is not None:
|
||||||
self.hass, self.temperature, self.temperature_unit,
|
data = {
|
||||||
self.precision),
|
ATTR_WEATHER_TEMPERATURE: show_temp(
|
||||||
}
|
self.hass, self.temperature, self.temperature_unit,
|
||||||
|
self.precision),
|
||||||
|
}
|
||||||
|
|
||||||
humidity = self.humidity
|
humidity = self.humidity
|
||||||
if humidity is not None:
|
if humidity is not None:
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from homeassistant.components import weather
|
from homeassistant.components import weather
|
||||||
from homeassistant.components.nws.weather import ATTR_FORECAST_PRECIP_PROB
|
from homeassistant.components.nws.weather import ATTR_FORECAST_PRECIP_PROB
|
||||||
from homeassistant.components.weather import (
|
from homeassistant.components.weather import (
|
||||||
@@ -55,6 +57,19 @@ OBS_METAR = [{
|
|||||||
"relativeHumidity": {"value": None, "qualityControl": "qc:Z"},
|
"relativeHumidity": {"value": None, "qualityControl": "qc:Z"},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
OBS_NONE = [{
|
||||||
|
"rawMessage": None,
|
||||||
|
"textDescription": None,
|
||||||
|
"icon": None,
|
||||||
|
"temperature": {"value": None, "qualityControl": "qc:Z"},
|
||||||
|
"windDirection": {"value": None, "qualityControl": "qc:Z"},
|
||||||
|
"windSpeed": {"value": None, "qualityControl": "qc:Z"},
|
||||||
|
"seaLevelPressure": {"value": None, "qualityControl": "qc:Z"},
|
||||||
|
"visibility": {"value": None, "qualityControl": "qc:Z"},
|
||||||
|
"relativeHumidity": {"value": None, "qualityControl": "qc:Z"},
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
FORE = [{
|
FORE = [{
|
||||||
'endTime': '2018-12-21T18:00:00-05:00',
|
'endTime': '2018-12-21T18:00:00-05:00',
|
||||||
'windSpeed': '8 to 10 mph',
|
'windSpeed': '8 to 10 mph',
|
||||||
@@ -356,7 +371,7 @@ class TestNwsMetric(unittest.TestCase):
|
|||||||
convert_distance(9, LENGTH_MILES, LENGTH_KILOMETERS))
|
convert_distance(9, LENGTH_MILES, LENGTH_KILOMETERS))
|
||||||
|
|
||||||
|
|
||||||
class MockNws_Metar():
|
class MockNws_Metar(MockNws):
|
||||||
"""Mock Station from pynws."""
|
"""Mock Station from pynws."""
|
||||||
|
|
||||||
def __init__(self, websession, latlon, userid):
|
def __init__(self, websession, latlon, userid):
|
||||||
@@ -367,18 +382,6 @@ class MockNws_Metar():
|
|||||||
"""Mock Observation."""
|
"""Mock Observation."""
|
||||||
return OBS_METAR
|
return OBS_METAR
|
||||||
|
|
||||||
async def forecast(self):
|
|
||||||
"""Mock Forecast."""
|
|
||||||
return FORE
|
|
||||||
|
|
||||||
async def forecast_hourly(self):
|
|
||||||
"""Mock Hourly Forecast."""
|
|
||||||
return HOURLY_FORE
|
|
||||||
|
|
||||||
async def stations(self):
|
|
||||||
"""Mock stations."""
|
|
||||||
return [STN]
|
|
||||||
|
|
||||||
|
|
||||||
class TestNWS_Metar(unittest.TestCase):
|
class TestNWS_Metar(unittest.TestCase):
|
||||||
"""Test the NWS weather component with metar code."""
|
"""Test the NWS weather component with metar code."""
|
||||||
@@ -429,3 +432,141 @@ class TestNWS_Metar(unittest.TestCase):
|
|||||||
assert data.get(ATTR_WEATHER_WIND_BEARING) == truth.wind_dir.value()
|
assert data.get(ATTR_WEATHER_WIND_BEARING) == truth.wind_dir.value()
|
||||||
vis = convert_distance(truth.vis.value(), LENGTH_METERS, LENGTH_MILES)
|
vis = convert_distance(truth.vis.value(), LENGTH_METERS, LENGTH_MILES)
|
||||||
assert data.get(ATTR_WEATHER_VISIBILITY) == round(vis)
|
assert data.get(ATTR_WEATHER_VISIBILITY) == round(vis)
|
||||||
|
|
||||||
|
|
||||||
|
class MockNwsFailObs(MockNws):
|
||||||
|
"""Mock Station from pynws."""
|
||||||
|
|
||||||
|
def __init__(self, websession, latlon, userid):
|
||||||
|
"""Init mock nws."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def observations(self, limit):
|
||||||
|
"""Mock Observation."""
|
||||||
|
raise aiohttp.ClientError
|
||||||
|
|
||||||
|
|
||||||
|
class MockNwsFailStn(MockNws):
|
||||||
|
"""Mock Station from pynws."""
|
||||||
|
|
||||||
|
def __init__(self, websession, latlon, userid):
|
||||||
|
"""Init mock nws."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def stations(self):
|
||||||
|
"""Mock Observation."""
|
||||||
|
raise aiohttp.ClientError
|
||||||
|
|
||||||
|
|
||||||
|
class MockNwsFailFore(MockNws):
|
||||||
|
"""Mock Station from pynws."""
|
||||||
|
|
||||||
|
def __init__(self, websession, latlon, userid):
|
||||||
|
"""Init mock nws."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def forecast(self):
|
||||||
|
"""Mock Observation."""
|
||||||
|
raise aiohttp.ClientError
|
||||||
|
|
||||||
|
|
||||||
|
class MockNws_NoObs(MockNws):
|
||||||
|
"""Mock Station from pynws."""
|
||||||
|
|
||||||
|
def __init__(self, websession, latlon, userid):
|
||||||
|
"""Init mock nws."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def observations(self, limit):
|
||||||
|
"""Mock Observation."""
|
||||||
|
return OBS_NONE
|
||||||
|
|
||||||
|
|
||||||
|
class TestFailures(unittest.TestCase):
|
||||||
|
"""Test the NWS weather component."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up things to be run when tests are started."""
|
||||||
|
self.hass = get_test_home_assistant()
|
||||||
|
self.hass.config.units = IMPERIAL_SYSTEM
|
||||||
|
self.lat = self.hass.config.latitude = 40.00
|
||||||
|
self.lon = self.hass.config.longitude = -8.00
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Stop down everything that was started."""
|
||||||
|
self.hass.stop()
|
||||||
|
|
||||||
|
@MockDependency("metar")
|
||||||
|
@MockDependency("pynws")
|
||||||
|
@patch("pynws.Nws", new=MockNwsFailObs)
|
||||||
|
def test_obs_fail(self, mock_metar, mock_pynws):
|
||||||
|
"""Test for successfully setting up the NWS platform with name."""
|
||||||
|
assert setup_component(self.hass, weather.DOMAIN, {
|
||||||
|
'weather': {
|
||||||
|
'name': 'HomeWeather',
|
||||||
|
'platform': 'nws',
|
||||||
|
'api_key': 'test_email',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@MockDependency("metar")
|
||||||
|
@MockDependency("pynws")
|
||||||
|
@patch("pynws.Nws", new=MockNwsFailStn)
|
||||||
|
def test_fail_stn(self, mock_metar, mock_pynws):
|
||||||
|
"""Test for successfully setting up the NWS platform with name."""
|
||||||
|
assert setup_component(self.hass, weather.DOMAIN, {
|
||||||
|
'weather': {
|
||||||
|
'name': 'HomeWeather',
|
||||||
|
'platform': 'nws',
|
||||||
|
'api_key': 'test_email',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
state = self.hass.states.get('weather.homeweather')
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
@MockDependency("metar")
|
||||||
|
@MockDependency("pynws")
|
||||||
|
@patch("pynws.Nws", new=MockNwsFailFore)
|
||||||
|
def test_fail_fore(self, mock_metar, mock_pynws):
|
||||||
|
"""Test for successfully setting up the NWS platform with name."""
|
||||||
|
assert setup_component(self.hass, weather.DOMAIN, {
|
||||||
|
'weather': {
|
||||||
|
'name': 'HomeWeather',
|
||||||
|
'platform': 'nws',
|
||||||
|
'api_key': 'test_email',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@MockDependency("metar")
|
||||||
|
@MockDependency("pynws")
|
||||||
|
@patch("pynws.Nws", new=MockNws_NoObs)
|
||||||
|
def test_no_obs(self, mock_metar, mock_pynws):
|
||||||
|
"""Test for successfully setting up the NWS platform with name."""
|
||||||
|
assert setup_component(self.hass, weather.DOMAIN, {
|
||||||
|
'weather': {
|
||||||
|
'name': 'HomeWeather',
|
||||||
|
'platform': 'nws',
|
||||||
|
'api_key': 'test_email',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
state = self.hass.states.get('weather.homeweather')
|
||||||
|
assert state.state == 'unknown'
|
||||||
|
|
||||||
|
@MockDependency("metar")
|
||||||
|
@MockDependency("pynws")
|
||||||
|
@patch("pynws.Nws", new=MockNws)
|
||||||
|
def test_no_lat(self, mock_metar, mock_pynws):
|
||||||
|
"""Test for successfully setting up the NWS platform with name."""
|
||||||
|
hass = self.hass
|
||||||
|
hass.config.latitude = None
|
||||||
|
|
||||||
|
assert setup_component(self.hass, weather.DOMAIN, {
|
||||||
|
'weather': {
|
||||||
|
'name': 'HomeWeather',
|
||||||
|
'platform': 'nws',
|
||||||
|
'api_key': 'test_email',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
state = self.hass.states.get('weather.homeweather')
|
||||||
|
assert state is None
|
||||||
|
Reference in New Issue
Block a user