change to simpler api

This commit is contained in:
ktdad
2019-07-18 06:50:03 -04:00
parent b5727c84a7
commit fd3a90a91e
4 changed files with 178 additions and 468 deletions

View File

@@ -4,8 +4,5 @@
"documentation": "https://www.home-assistant.io/components/nws", "documentation": "https://www.home-assistant.io/components/nws",
"dependencies": [], "dependencies": [],
"codeowners": ["@MatthewFlamm"], "codeowners": ["@MatthewFlamm"],
"requirements": [ "requirements": ["pynws==0.7.0"]
"pynws==0.6",
"metar==1.7.0"
]
} }

View File

@@ -1,13 +1,10 @@
"""Support for NWS weather service.""" """Support for NWS weather service."""
import asyncio
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
from json import JSONDecodeError from json import JSONDecodeError
import logging import logging
from statistics import mean
import aiohttp import aiohttp
import async_timeout
import voluptuous as vol import voluptuous as vol
from homeassistant.components.weather import ( from homeassistant.components.weather import (
@@ -72,13 +69,7 @@ CONDITION_CLASSES = OrderedDict([
'Partly cloudy']), 'Partly cloudy']),
]) ])
ERRORS = (aiohttp.ClientError, JSONDecodeError, asyncio.CancelledError) ERRORS = (aiohttp.ClientError, JSONDecodeError)
FORECAST_CLASSES = {
ATTR_FORECAST_DETAIL_DESCRIPTION: 'detailedForecast',
ATTR_FORECAST_TEMP: 'temperature',
ATTR_FORECAST_TIME: 'startTime',
}
FORECAST_MODE = ['daynight', 'hourly'] FORECAST_MODE = ['daynight', 'hourly']
@@ -88,7 +79,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LONGITUDE): cv.longitude, vol.Optional(CONF_LONGITUDE): cv.longitude,
vol.Optional(CONF_MODE, default='daynight'): vol.In(FORECAST_MODE), vol.Optional(CONF_MODE, default='daynight'): vol.In(FORECAST_MODE),
vol.Optional(CONF_STATION): cv.string, vol.Optional(CONF_STATION): cv.string,
vol.Required(CONF_API_KEY): cv.string vol.Required(CONF_API_KEY): cv.string,
}) })
@@ -109,16 +100,16 @@ def convert_condition(time, weather):
if cond == 'clear': if cond == 'clear':
if time == 'day': if time == 'day':
return 'sunny', max(prec_prob) return 'sunny', max(prec_probs)
if time == 'night': if time == 'night':
return 'clear-night', max(prec_prob) return 'clear-night', max(prec_probs)
return cond, max(prec_prob) return cond, max(prec_probs)
async def async_setup_platform(hass, config, async_add_entities, async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None): discovery_info=None):
"""Set up the NWS weather platform.""" """Set up the NWS weather platform."""
from pynws import SimpleNws from pynws import SimpleNWS
latitude = config.get(CONF_LATITUDE, hass.config.latitude) latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude) longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
station = config.get(CONF_STATION) station = config.get(CONF_STATION)
@@ -132,7 +123,7 @@ async def async_setup_platform(hass, config, async_add_entities,
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
api_key_ha = f"{api_key} homeassistant" api_key_ha = f"{api_key} homeassistant"
nws = simple_nws(lat, lon, userid=api_key_ha, mode, websession) nws = SimpleNWS(latitude, longitude, api_key_ha, mode, websession)
_LOGGER.debug("Setting up station: %s", station) _LOGGER.debug("Setting up station: %s", station)
try: try:
@@ -147,25 +138,29 @@ async def async_setup_platform(hass, config, async_add_entities,
latitude, longitude, nws.station) latitude, longitude, nws.station)
async_add_entities( async_add_entities(
[NWSWeather(nws, hass.config.units, config)], [NWSWeather(nws, mode, hass.config.units, config)],
True) True)
class NWSWeather(WeatherEntity): class NWSWeather(WeatherEntity):
"""Representation of a weather condition.""" """Representation of a weather condition."""
def __init__(self, nws, units, config): def __init__(self, nws, mode, units, config):
"""Initialise the platform with a data instance and station name.""" """Initialise the platform with a data instance and station name."""
self.nws = nws self.nws = nws
self.station_name = config.get(CONF_NAME, self._nws.station) self.station_name = config.get(CONF_NAME, self.nws.station)
self.is_metric = units.is_metric self.is_metric = units.is_metric
self.mode = mode
self.observation = None
self._forecast = None
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
async def async_update(self): async def async_update(self):
"""Update Condition.""" """Update Condition."""
_LOGGER.debug("Updating station observations %s", self.nws.station) _LOGGER.debug("Updating station observations %s", self.nws.station)
try: try:
await self.nws.update_observations() await self.nws.update_observation()
except ERRORS as status: except ERRORS as status:
_LOGGER.error("Error updating observation from station %s: %s", _LOGGER.error("Error updating observation from station %s: %s",
self.nws.station, status) self.nws.station, status)
@@ -178,7 +173,7 @@ class NWSWeather(WeatherEntity):
_LOGGER.error("Error updating forecast from station %s: %s", _LOGGER.error("Error updating forecast from station %s: %s",
self.nws.station, status) self.nws.station, status)
else: else:
self.forecast = self.nws.forecast self._forecast = self.nws.forecast
return return
@property @property
@@ -205,7 +200,7 @@ class NWSWeather(WeatherEntity):
def pressure(self): def pressure(self):
"""Return the current pressure.""" """Return the current pressure."""
pressure_pa = None pressure_pa = None
if self._observation: if self.observation:
pressure_pa = self.observation.get('seaLevelPressure') pressure_pa = self.observation.get('seaLevelPressure')
if pressure_pa is None: if pressure_pa is None:
return None return None
@@ -237,7 +232,7 @@ class NWSWeather(WeatherEntity):
return None return None
wind_m_hr = wind_m_s * 3600 wind_m_hr = wind_m_s * 3600
if self._is_metric: if self.is_metric:
wind = convert_distance(wind_m_hr, wind = convert_distance(wind_m_hr,
LENGTH_METERS, LENGTH_KILOMETERS) LENGTH_METERS, LENGTH_KILOMETERS)
else: else:
@@ -275,11 +270,11 @@ class NWSWeather(WeatherEntity):
"""Return visibility.""" """Return visibility."""
vis_m = None vis_m = None
if self.observation: if self.observation:
vis_m = self._observation.get('visibility') vis_m = self.observation.get('visibility')
if vis_m is None: if vis_m is None:
return None return None
if self._is_metric: if self.is_metric:
vis = convert_distance(vis_m, LENGTH_METERS, LENGTH_KILOMETERS) vis = convert_distance(vis_m, LENGTH_METERS, LENGTH_KILOMETERS)
else: else:
vis = convert_distance(vis_m, LENGTH_METERS, LENGTH_MILES) vis = convert_distance(vis_m, LENGTH_METERS, LENGTH_MILES)
@@ -288,31 +283,39 @@ class NWSWeather(WeatherEntity):
@property @property
def forecast(self): def forecast(self):
"""Return forecast.""" """Return forecast."""
if self._forecast is None:
return None
forecast = [] forecast = []
for forecast_entry in self._forecast: for forecast_entry in self._forecast:
data = { data = {
ATTR_FORECAST_DETAIL_DESCRIPTION: forecast_entry.get('detailedForecast'), ATTR_FORECAST_DETAIL_DESCRIPTION: forecast_entry.get(
'detailedForecast'),
ATTR_FORECAST_TEMP: forecast_entry.get('temperature'), ATTR_FORECAST_TEMP: forecast_entry.get('temperature'),
ATTR_FORECAST_TIME: forecast_entry.get('startTime'), ATTR_FORECAST_TIME: forecast_entry.get('startTime'),
} }
if self._mode == 'daynight': if self.mode == 'daynight':
data[ATTR_FORECAST_DAYTIME] = forecast_entry.get('isDaytime') data[ATTR_FORECAST_DAYTIME] = forecast_entry.get('isDaytime')
time = forecast_entry.get('iconTime') time = forecast_entry.get('iconTime')
weather = forecast_entry.get('iconWeather') weather = forecast_entry.get('iconWeather')
if time and weather:
cond, precip = convert_condition(time, weather) cond, precip = convert_condition(time, weather)
else:
cond, precip = None, None
data[ATTR_FORECAST_CONDITION] = cond data[ATTR_FORECAST_CONDITION] = cond
data[ATTR_FORECAST_PRECIP_PROB] = precip data[ATTR_FORECAST_PRECIP_PROB] = precip
data[ATTR_FORECAST_WIND_BEARING] = \ data[ATTR_FORECAST_WIND_BEARING] = \
WIND[forecast_entry['windDirection']] forecast_entry.get('windBearing')
wind_speed = forecast_entry.get('windSpeedAvg') wind_speed = forecast_entry.get('windSpeedAvg')
if self._is_metric: if wind_speed:
if self.is_metric:
data[ATTR_FORECAST_WIND_SPEED] = round( data[ATTR_FORECAST_WIND_SPEED] = round(
convert_distance(wind_speed, convert_distance(wind_speed,
LENGTH_MILES, LENGTH_KILOMETERS)) LENGTH_MILES, LENGTH_KILOMETERS))
else: else:
data[ATTR_FORECAST_WIND_SPEED] = round(wind_speed_avg) data[ATTR_FORECAST_WIND_SPEED] = round(wind_speed)
else:
data[ATTR_FORECAST_WIND_SPEED] = None
forecast.append(data) forecast.append(data)
return forecast return forecast

View File

@@ -765,9 +765,6 @@ mbddns==0.1.2
# homeassistant.components.message_bird # homeassistant.components.message_bird
messagebird==1.2.0 messagebird==1.2.0
# homeassistant.components.nws
metar==1.7.0
# homeassistant.components.meteoalarm # homeassistant.components.meteoalarm
meteoalertapi==0.1.5 meteoalertapi==0.1.5
@@ -1292,7 +1289,7 @@ pynuki==1.3.3
pynut2==2.1.2 pynut2==2.1.2
# homeassistant.components.nws # homeassistant.components.nws
pynws==0.6 pynws==0.7.0
# homeassistant.components.nx584 # homeassistant.components.nx584
pynx584==0.4 pynx584==0.4

View File

@@ -26,129 +26,74 @@ from homeassistant.setup import setup_component
from tests.common import get_test_home_assistant, MockDependency from tests.common import get_test_home_assistant, MockDependency
OBS = {
OBS = [{ 'temperature': 7,
'temperature': {'value': 7, 'qualityControl': 'qc:V'}, 'relativeHumidity': 10,
'relativeHumidity': {'value': 10, 'qualityControl': 'qc:V'}, 'windBearing': 180,
'windChill': {'value': 10, 'qualityControl': 'qc:V'}, 'visibility': 10000,
'heatIndex': {'value': 10, 'qualityControl': 'qc:V'}, 'windSpeed': 10,
'windDirection': {'value': 180, 'qualityControl': 'qc:V'}, 'seaLevelPressure': 30000,
'visibility': {'value': 10000, 'qualityControl': 'qc:V'}, 'iconTime': 'day',
'windSpeed': {'value': 10, 'qualityControl': 'qc:V'}, 'iconWeather': (('Fair/clear', None),),
'seaLevelPressure': {'value': 30000, 'qualityControl': 'qc:V'}, }
'windGust': {'value': 10, 'qualityControl': 'qc:V'},
'dewpoint': {'value': 10, 'qualityControl': 'qc:V'},
'icon': 'https://api.weather.gov/icons/land/day/skc?size=medium',
'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"},
}]
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',
'windSpeed': '8 to 10 mph',
'windDirection': 'S',
'shortForecast': 'Chance Showers And Thunderstorms',
'isDaytime': True,
'startTime': '2018-12-21T15:00:00-05:00',
'temperatureTrend': None,
'temperature': 41, 'temperature': 41,
'temperatureUnit': 'F', 'windBearing': 180,
'detailedForecast': 'A detailed description', 'windSpeedAvg': 9,
'name': 'This Afternoon', 'iconTime': 'day',
'number': 1, 'startTime': '2018-12-21T15:00:00-05:00',
'icon': 'https://api.weather.gov/icons/land/day/skc/tsra,40?size=medium' 'iconWeather': (('Fair/Clear', None),
}] ('Thunderstorm (high cloud cover)', 40),),
HOURLY_FORE = [{
'endTime': '2018-12-22T05:00:00-05:00',
'windSpeed': '4 mph',
'windDirection': 'N',
'shortForecast': 'Chance Showers And Thunderstorms',
'startTime': '2018-12-22T04:00:00-05:00',
'temperatureTrend': None,
'temperature': 32,
'temperatureUnit': 'F',
'detailedForecast': '',
'number': 2,
'icon': 'https://api.weather.gov/icons/land/night/skc?size=medium'
}] }]
STN = 'STNA' STN = 'STNA'
class MockNws(): class MockNws:
"""Mock Station from pynws.""" """Mock Station from pynws."""
def __init__(self, websession, latlon, userid): data_obs = None
data_fore = None
error_obs = False
error_fore = False
error_stn = False
def __init__(self, lat, lon, userid, mode, session):
"""Init mock nws.""" """Init mock nws."""
pass self.station = None
self.stations = None
async def observations(self, limit): async def update_observation(self):
"""Mock observation update."""
if self.error_obs:
raise aiohttp.ClientError
return
async def update_forecast(self):
"""Mock forecast update."""
if self.error_fore:
raise aiohttp.ClientError
return
@property
def observation(self):
"""Mock Observation.""" """Mock Observation."""
return OBS return self.data_obs
async def forecast(self): @property
def forecast(self):
"""Mock Forecast.""" """Mock Forecast."""
return FORE return self.data_fore
async def forecast_hourly(self): async def set_station(self, station=None):
"""Mock Hourly Forecast."""
return HOURLY_FORE
async def stations(self):
"""Mock stations.""" """Mock stations."""
return [STN] if self.error_stn:
raise aiohttp.ClientError
self.stations = [STN]
class Prop: self.station = station or STN
"""Property data class for metar. Initialize with desired return value.""" return
def __init__(self, value_return):
"""Initialize with desired return."""
self.value_return = value_return
def value(self, units=''):
"""Return provided value."""
return self.value_return
class MockMetar:
"""Mock Metar parser."""
def __init__(self, code):
"""Set up mocked return values."""
self.temp = Prop(27)
self.press = Prop(1111)
self.wind_speed = Prop(27)
self.wind_dir = Prop(175)
self.vis = Prop(5000)
class TestNWS(unittest.TestCase): class TestNWS(unittest.TestCase):
@@ -161,15 +106,25 @@ class TestNWS(unittest.TestCase):
self.lat = self.hass.config.latitude = 40.00 self.lat = self.hass.config.latitude = 40.00
self.lon = self.hass.config.longitude = -8.00 self.lon = self.hass.config.longitude = -8.00
# Initialize class variables as tests modify them
MockNws.data_obs = None
MockNws.data_fore = None
MockNws.error_obs = False
MockNws.error_fore = False
MockNws.error_stn = False
def tearDown(self): def tearDown(self):
"""Stop down everything that was started.""" """Stop down everything that was started."""
self.hass.stop() self.hass.stop()
@MockDependency("metar") @MockDependency('pynws')
@MockDependency("pynws") @patch("pynws.SimpleNWS", new=MockNws)
@patch("pynws.Nws", new=MockNws) def test_nws(self, mock_pynws):
def test_w_name(self, mock_metar, mock_pynws): """Test for successfully setting up with imperial."""
"""Test for successfully setting up the NWS platform with name.""" mock_pynws.SimpleNWS.data_obs = OBS
mock_pynws.SimpleNWS.data_fore = FORE
assert setup_component(self.hass, weather.DOMAIN, { assert setup_component(self.hass, weather.DOMAIN, {
'weather': { 'weather': {
'name': 'HomeWeather', 'name': 'HomeWeather',
@@ -203,137 +158,14 @@ class TestNWS(unittest.TestCase):
assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180 assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180
assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == 9 assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == 9
@MockDependency("metar") @MockDependency('pynws')
@MockDependency("pynws") @patch("pynws.SimpleNWS", new=MockNws)
@patch("pynws.Nws", new=MockNws) def test_nws_metric(self, mock_pynws):
def test_w_station(self, mock_metar, mock_pynws): """Test for successfully setting up with metric."""
"""Test for successfully setting up the NWS platform with station.""" mock_pynws.SimpleNWS.data_obs = OBS
assert setup_component(self.hass, weather.DOMAIN, { mock_pynws.SimpleNWS.data_fore = FORE
'weather': {
'platform': 'nws',
'station': 'STNB',
'api_key': 'test_email',
}
})
assert self.hass.states.get('weather.stnb')
@MockDependency("metar")
@MockDependency("pynws")
@patch("pynws.Nws", new=MockNws)
def test_w_no_name(self, mock_metar, mock_pynws):
"""Test for successfully setting up the NWS platform w no name."""
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'platform': 'nws',
'api_key': 'test_email',
}
})
assert self.hass.states.get('weather.' + STN)
@MockDependency("metar")
@MockDependency("pynws")
@patch("pynws.Nws", new=MockNws)
def test__hourly(self, mock_metar, mock_pynws):
"""Test for successfully setting up hourly forecast."""
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'name': 'HourlyWeather',
'platform': 'nws',
'api_key': 'test_email',
'mode': 'hourly',
}
})
state = self.hass.states.get('weather.hourlyweather')
data = state.attributes
forecast = data.get(ATTR_FORECAST)
assert forecast[0].get(ATTR_FORECAST_CONDITION) == 'clear-night'
assert forecast[0].get(ATTR_FORECAST_PRECIP_PROB) is None
assert forecast[0].get(ATTR_FORECAST_TEMP) == 32
assert forecast[0].get(ATTR_FORECAST_TIME) == \
'2018-12-22T04:00:00-05:00'
assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 0
assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == 4
@MockDependency("metar")
@MockDependency("pynws")
@patch("pynws.Nws", new=MockNws)
def test_daynight(self, mock_metar, mock_pynws):
"""Test for successfully setting up daynight forecast."""
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'platform': 'nws',
'api_key': 'test_email',
'mode': 'daynight',
}
})
assert self.hass.states.get('weather.' + STN)
@MockDependency("metar")
@MockDependency("pynws")
@patch("pynws.Nws", new=MockNws)
def test_latlon(self, mock_metar, mock_pynws):
"""Test for successsfully setting up the NWS platform with lat/lon."""
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'platform': 'nws',
'api_key': 'test_email',
'latitude': self.lat,
'longitude': self.lon,
}
})
assert self.hass.states.get('weather.' + STN)
@MockDependency("metar")
@MockDependency("pynws")
@patch("pynws.Nws", new=MockNws)
def test_setup_failure_mode(self, mock_metar, mock_pynws):
"""Test for unsuccessfully setting up incorrect mode."""
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'platform': 'nws',
'api_key': 'test_email',
'mode': 'abc',
}
})
assert self.hass.states.get('weather.' + STN) is None
@MockDependency("metar")
@MockDependency("pynws")
@patch("pynws.Nws", new=MockNws)
def test_setup_failure_no_apikey(self, mock_metar, mock_pynws):
"""Test for unsuccessfully setting up without api_key."""
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'platform': 'nws',
}
})
assert self.hass.states.get('weather.' + STN) is None
class TestNwsMetric(unittest.TestCase):
"""Test the NWS weather component using metric units."""
def setUp(self):
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.hass.config.units = METRIC_SYSTEM self.hass.config.units = METRIC_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=MockNws)
def test_metric(self, mock_metar, mock_pynws):
"""Test for successfully setting up the NWS platform with name."""
assert setup_component(self.hass, weather.DOMAIN, { assert setup_component(self.hass, weather.DOMAIN, {
'weather': { 'weather': {
'name': 'HomeWeather', 'name': 'HomeWeather',
@@ -346,14 +178,13 @@ class TestNwsMetric(unittest.TestCase):
assert state.state == 'sunny' assert state.state == 'sunny'
data = state.attributes data = state.attributes
temp_f = convert_temperature(7, TEMP_CELSIUS, TEMP_FAHRENHEIT)
assert data.get(ATTR_WEATHER_TEMPERATURE) == \ assert data.get(ATTR_WEATHER_TEMPERATURE) == \
display_temp(self.hass, 7, TEMP_CELSIUS, PRECISION_WHOLE) display_temp(self.hass, temp_f, TEMP_FAHRENHEIT, PRECISION_WHOLE)
assert data.get(ATTR_WEATHER_HUMIDITY) == 10 assert data.get(ATTR_WEATHER_HUMIDITY) == 10
assert data.get(ATTR_WEATHER_PRESSURE) == round( assert data.get(ATTR_WEATHER_PRESSURE) == round(
convert_pressure(30000, PRESSURE_PA, PRESSURE_HPA)) convert_pressure(30000, PRESSURE_PA, PRESSURE_HPA))
# m/s to km/hr assert data.get(ATTR_WEATHER_WIND_SPEED) == round(3.6 * 10)
assert data.get(ATTR_WEATHER_WIND_SPEED) == round(10 * 3.6)
assert data.get(ATTR_WEATHER_WIND_BEARING) == 180 assert data.get(ATTR_WEATHER_WIND_BEARING) == 180
assert data.get(ATTR_WEATHER_VISIBILITY) == round( assert data.get(ATTR_WEATHER_VISIBILITY) == round(
convert_distance(10000, LENGTH_METERS, LENGTH_KILOMETERS)) convert_distance(10000, LENGTH_METERS, LENGTH_KILOMETERS))
@@ -362,47 +193,18 @@ class TestNwsMetric(unittest.TestCase):
forecast = data.get(ATTR_FORECAST) forecast = data.get(ATTR_FORECAST)
assert forecast[0].get(ATTR_FORECAST_CONDITION) == 'lightning-rainy' assert forecast[0].get(ATTR_FORECAST_CONDITION) == 'lightning-rainy'
assert forecast[0].get(ATTR_FORECAST_PRECIP_PROB) == 40 assert forecast[0].get(ATTR_FORECAST_PRECIP_PROB) == 40
assert forecast[0].get(ATTR_FORECAST_TEMP) == round( assert forecast[0].get(ATTR_FORECAST_TEMP) == convert_temperature(
convert_temperature(41, TEMP_FAHRENHEIT, TEMP_CELSIUS)) 41, TEMP_FAHRENHEIT, TEMP_CELSIUS)
assert forecast[0].get(ATTR_FORECAST_TIME) == \ assert forecast[0].get(ATTR_FORECAST_TIME) == \
'2018-12-21T15:00:00-05:00' '2018-12-21T15:00:00-05:00'
assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180 assert forecast[0].get(ATTR_FORECAST_WIND_BEARING) == 180
assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == round( assert forecast[0].get(ATTR_FORECAST_WIND_SPEED) == round(
convert_distance(9, LENGTH_MILES, LENGTH_KILOMETERS)) convert_distance(9, LENGTH_MILES, LENGTH_KILOMETERS))
@MockDependency('pynws')
class MockNws_Metar(MockNws): @patch("pynws.SimpleNWS", new=MockNws)
"""Mock Station from pynws.""" def test_nws_no_obs_fore1x(self, mock_pynws):
"""Test with no data."""
def __init__(self, websession, latlon, userid):
"""Init mock nws."""
pass
async def observations(self, limit):
"""Mock Observation."""
return OBS_METAR
class TestNWS_Metar(unittest.TestCase):
"""Test the NWS weather component with metar code."""
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=MockNws_Metar)
@patch("metar.Metar.Metar", new=MockMetar)
def test_metar(self, mock_metar, mock_pynws):
"""Test for successfully setting up the NWS platform with name."""
assert setup_component(self.hass, weather.DOMAIN, { assert setup_component(self.hass, weather.DOMAIN, {
'weather': { 'weather': {
'name': 'HomeWeather', 'name': 'HomeWeather',
@@ -411,154 +213,15 @@ class TestNWS_Metar(unittest.TestCase):
} }
}) })
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(), PRESSURE_HPA, PRESSURE_INHG),
2)
wind_speed_mi_s = convert_distance(
truth.wind_speed.value(), LENGTH_METERS, LENGTH_MILES)
assert data.get(ATTR_WEATHER_WIND_SPEED) == round(
wind_speed_mi_s * 3600)
assert data.get(ATTR_WEATHER_WIND_BEARING) == truth.wind_dir.value()
vis = convert_distance(truth.vis.value(), LENGTH_METERS, LENGTH_MILES)
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') state = self.hass.states.get('weather.homeweather')
assert state.state == 'unknown' assert state.state == 'unknown'
@MockDependency("metar") @MockDependency('pynws')
@MockDependency("pynws") @patch("pynws.SimpleNWS", new=MockNws)
@patch("pynws.Nws", new=MockNws) def test_nws_missing_valuess(self, mock_pynws):
def test_no_lat(self, mock_metar, mock_pynws): """Test with missing data."""
"""Test for successfully setting up the NWS platform with name.""" mock_pynws.SimpleNWS.data_obs = {key: None for key in OBS}
hass = self.hass mock_pynws.SimpleNWS.data_fore = [{key: None for key in FORE[0]}]
hass.config.latitude = None
assert setup_component(self.hass, weather.DOMAIN, { assert setup_component(self.hass, weather.DOMAIN, {
'weather': { 'weather': {
@@ -568,5 +231,55 @@ class TestFailures(unittest.TestCase):
} }
}) })
state = self.hass.states.get('weather.homeweather')
assert state.state == 'unknown'
@MockDependency('pynws')
@patch("pynws.SimpleNWS", new=MockNws)
def test_nws_error_obs(self, mock_pynws):
"""Test for successfully setting up the NWS platform with name."""
mock_pynws.SimpleNWS.error_obs = True
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('pynws')
@patch("pynws.SimpleNWS", new=MockNws)
def test_nws_error_fore(self, mock_pynws):
"""Test error forecast."""
mock_pynws.SimpleNWS.error_fore = True
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'name': 'HomeWeather',
'platform': 'nws',
'api_key': 'test_email',
}
})
state = self.hass.states.get('weather.homeweather')
data = state.attributes
assert data.get('forecast') is None
@MockDependency('pynws')
@patch("pynws.SimpleNWS", new=MockNws)
def test_nws_error_stn(self, mock_pynws):
"""Test station error.."""
mock_pynws.SimpleNWS.error_stn = True
assert setup_component(self.hass, weather.DOMAIN, {
'weather': {
'name': 'HomeWeather',
'platform': 'nws',
'api_key': 'test_email',
}
})
state = self.hass.states.get('weather.homeweather') state = self.hass.states.get('weather.homeweather')
assert state is None assert state is None