check and test for missing observations

This commit is contained in:
ktdad
2019-06-01 07:20:59 -04:00
parent 63055f949c
commit ef687d3d7f
3 changed files with 201 additions and 35 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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