diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index 7cb79ed8d8e..58d735602de 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -4,5 +4,8 @@ "documentation": "https://www.home-assistant.io/components/nws", "dependencies": [], "codeowners": ["@MatthewFlamm"], - "requirements": ["pynws==0.6"] + "requirements": [ + "pynws==0.6", + "metar==1.7.0" + ] } diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index e3159803ddc..896449819e5 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -22,8 +22,6 @@ from homeassistant.util.distance import convert as convert_distance from homeassistant.util.pressure import convert as convert_pressure from homeassistant.util.temperature import convert as convert_temperature -REQUIREMENTS = ['pynws==0.6'] - _LOGGER = logging.getLogger(__name__) ATTRIBUTION = 'Data from National Weather Service/NOAA' @@ -127,7 +125,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the NWS weather platform.""" from pynws import Nws - + from metar import Metar latitude = config.get(CONF_LATITUDE, hass.config.latitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude) station = config.get(CONF_STATION) @@ -155,16 +153,21 @@ async def async_setup_platform(hass, config, async_add_entities, nws.station = station _LOGGER.debug("Initialized station %s", station[0]) - async_add_entities([NWSWeather(nws, hass.config.units, config)], True) + async_add_entities( + [NWSWeather(nws, Metar.Metar, hass.config.units, config)], + True) class NWSWeather(WeatherEntity): """Representation of a weather condition.""" - def __init__(self, nws, units, config): + def __init__(self, nws, metar, units, config): """Initialise the platform with a data instance and station name.""" self._nws = nws + self._metar = metar self._station_name = config.get(CONF_NAME, self._nws.station) + + self._metar_obs = None self._observation = None self._forecast = None self._description = None @@ -178,6 +181,11 @@ class NWSWeather(WeatherEntity): _LOGGER.debug("Updating station observations %s", self._nws.station) self._observation = await self._nws.observations() + self._metar_obs = [ + self._metar(obs['rawMessage']) + for obs in self._observation + if 'rawMessage' in obs.keys() + ] _LOGGER.debug("Updating forecast") if self._mode == 'daynight': self._forecast = await self._nws.forecast() @@ -200,6 +208,8 @@ class NWSWeather(WeatherEntity): def temperature(self): """Return the current temperature.""" temp_c = self._observation[0]['temperature']['value'] + if temp_c is None and self._metar_obs: + temp_c = self._metar_obs[0].temp.value(units='C') if temp_c is not None: return convert_temperature(temp_c, TEMP_CELSIUS, TEMP_FAHRENHEIT) return None @@ -208,9 +218,13 @@ class NWSWeather(WeatherEntity): def pressure(self): """Return the current pressure.""" pressure_pa = self._observation[0]['seaLevelPressure']['value'] - # convert Pa to in Hg - if pressure_pa is None: - return None + + if pressure_pa is None and self._metar_obs: + pressure_hpa = self._metar_obs[0].press.value(units='HPA') + if pressure_hpa is None: + return None + pressure_pa = convert_pressure(pressure_hpa, PRESSURE_HPA, + PRESSURE_PA) if self._is_metric: pressure = convert_pressure(pressure_pa, PRESSURE_PA, PRESSURE_HPA) @@ -230,6 +244,9 @@ class NWSWeather(WeatherEntity): def wind_speed(self): """Return the current windspeed.""" wind_m_s = self._observation[0]['windSpeed']['value'] + if wind_m_s is None and self._metar_obs: + wind_m_s = self._metar_obs[0].wind_speed.value(units='MPS') + print(wind_m_s) if wind_m_s is None: return None wind_m_hr = wind_m_s * 3600 @@ -244,7 +261,10 @@ class NWSWeather(WeatherEntity): @property def wind_bearing(self): """Return the current wind bearing (degrees).""" - return self._observation[0]['windDirection']['value'] + wind_bearing = self._observation[0]['windDirection']['value'] + if wind_bearing is None and self._metar_obs: + wind_bearing = self._metar_obs[0].wind_dir.value() + return wind_bearing @property def temperature_unit(self): @@ -262,6 +282,8 @@ class NWSWeather(WeatherEntity): def visibility(self): """Return visibility.""" vis_m = self._observation[0]['visibility']['value'] + if vis_m is None and self._metar_obs: + vis_m = self._metar_obs[0].vis.value(units='M') if vis_m is None: return None diff --git a/requirements_all.txt b/requirements_all.txt index 1a995dbe877..e89f26bb1b7 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -765,6 +765,9 @@ mbddns==0.1.2 # homeassistant.components.message_bird messagebird==1.2.0 +# homeassistant.components.nws +metar==1.7.0 + # homeassistant.components.meteoalarm meteoalertapi==0.1.5 diff --git a/tests/components/nws/test_nws.py b/tests/components/nws/test_nws.py index ae78a290871..8cd73e777fe 100644 --- a/tests/components/nws/test_nws.py +++ b/tests/components/nws/test_nws.py @@ -40,6 +40,21 @@ OBS = [{ 'textDescription': 'Sunny' }] +METAR_MSG = ("PHNG 182257Z 06012KT 10SM FEW020 SCT026 SCT035 " + "28/22 A3007 RMK AO2 SLP177 T02780217") + +OBS_METAR = [{ + "rawMessage": METAR_MSG, + "textDescription": "Partly Cloudy", + "icon": "https://api.weather.gov/icons/land/day/sct?size=medium", + "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 = [{ 'endTime': '2018-12-21T18:00:00-05:00', 'windSpeed': '8 to 10 mph', @@ -306,3 +321,74 @@ class TestNwsMetric(unittest.TestCase): assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180 assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == round( convert_distance(9, LENGTH_MILES, LENGTH_KILOMETERS)) + + +class MockNws_Metar(): + """Mock Station from pynws.""" + + def __init__(self, websession, latlon, userid): + """Init mock nws.""" + pass + + async def observations(self): + """Mock Observation.""" + 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): + """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("pynws") + @patch("pynws.Nws", new=MockNws_Metar) + def test_metar(self, 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', + } + }) + + from metar import Metar + truth = Metar.Metar(METAR_MSG) + state = self.hass.states.get('weather.homeweather') + data = state.attributes + + temp_f = convert_temperature(truth.temp.value(), TEMP_CELSIUS, + TEMP_FAHRENHEIT) + assert data.get(ATTR_WEATHER_TEMPERATURE) == \ + display_temp(self.hass, temp_f, TEMP_FAHRENHEIT, PRECISION_WHOLE) + assert data.get(ATTR_WEATHER_HUMIDITY) is None + assert data.get(ATTR_WEATHER_PRESSURE) == round( + convert_pressure(truth.press.value(units='HPA'), PRESSURE_HPA, + PRESSURE_INHG), + 2) + assert data.get(ATTR_WEATHER_WIND_SPEED) == round( + truth.wind_speed.value(units='MPH')) + assert data.get(ATTR_WEATHER_WIND_BEARING) == truth.wind_dir.value() + assert data.get(ATTR_WEATHER_VISIBILITY) == round( + truth.vis.value(units='MI'))