Merge pull request #7482 from home-assistant/release-0-44-1

0.44.1
This commit is contained in:
Paulus Schoutsen 2017-05-07 15:08:37 -07:00 committed by GitHub
commit 5d13f36a4b
8 changed files with 794 additions and 132 deletions

View file

@ -40,6 +40,7 @@ CONF_TILT_OPEN_POSITION = 'tilt_opened_value'
CONF_TILT_MIN = 'tilt_min'
CONF_TILT_MAX = 'tilt_max'
CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic'
CONF_TILT_INVERT_STATE = 'tilt_invert_state'
DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = 'OPEN'
@ -52,6 +53,7 @@ DEFAULT_TILT_OPEN_POSITION = 100
DEFAULT_TILT_MIN = 0
DEFAULT_TILT_MAX = 100
DEFAULT_TILT_OPTIMISTIC = False
DEFAULT_TILT_INVERT_STATE = False
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
@ -74,6 +76,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int,
vol.Optional(CONF_TILT_STATE_OPTIMISTIC,
default=DEFAULT_TILT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_INVERT_STATE,
default=DEFAULT_TILT_INVERT_STATE): cv.boolean,
})
@ -104,6 +108,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_TILT_MIN),
config.get(CONF_TILT_MAX),
config.get(CONF_TILT_STATE_OPTIMISTIC),
config.get(CONF_TILT_INVERT_STATE),
)])
@ -114,7 +119,8 @@ class MqttCover(CoverDevice):
tilt_status_topic, qos, retain, state_open, state_closed,
payload_open, payload_close, payload_stop,
optimistic, value_template, tilt_open_position,
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic):
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
tilt_invert):
"""Initialize the cover."""
self._position = None
self._state = None
@ -138,6 +144,7 @@ class MqttCover(CoverDevice):
self._tilt_min = tilt_min
self._tilt_max = tilt_max
self._tilt_optimistic = tilt_optimistic
self._tilt_invert = tilt_invert
@asyncio.coroutine
def async_added_to_hass(self):
@ -150,8 +157,8 @@ class MqttCover(CoverDevice):
"""Handle tilt updates."""
if (payload.isnumeric() and
self._tilt_min <= int(payload) <= self._tilt_max):
tilt_range = self._tilt_max - self._tilt_min
level = round(float(payload) / tilt_range * 100.0)
level = self.find_percentage_in_range(float(payload))
self._tilt_value = level
self.hass.async_add_job(self.async_update_ha_state())
@ -278,7 +285,8 @@ class MqttCover(CoverDevice):
def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open."""
mqtt.async_publish(self.hass, self._tilt_command_topic,
self._tilt_open_position, self._qos, self._retain)
self._tilt_open_position, self._qos,
self._retain)
if self._tilt_optimistic:
self._tilt_value = self._tilt_open_position
self.hass.async_add_job(self.async_update_ha_state())
@ -287,7 +295,8 @@ class MqttCover(CoverDevice):
def async_close_cover_tilt(self, **kwargs):
"""Tilt the cover closed."""
mqtt.async_publish(self.hass, self._tilt_command_topic,
self._tilt_closed_position, self._qos, self._retain)
self._tilt_closed_position, self._qos,
self._retain)
if self._tilt_optimistic:
self._tilt_value = self._tilt_closed_position
self.hass.async_add_job(self.async_update_ha_state())
@ -301,9 +310,39 @@ class MqttCover(CoverDevice):
position = float(kwargs[ATTR_TILT_POSITION])
# The position needs to be between min and max
tilt_range = self._tilt_max - self._tilt_min
percentage = position / 100.0
level = round(tilt_range * percentage)
level = self.find_in_range_from_percent(position)
mqtt.async_publish(self.hass, self._tilt_command_topic,
level, self._qos, self._retain)
def find_percentage_in_range(self, position):
"""Find the 0-100% value within the specified range."""
# the range of motion as defined by the min max values
tilt_range = self._tilt_max - self._tilt_min
# offset to be zero based
offset_position = position - self._tilt_min
# the percentage value within the range
position_percentage = float(offset_position) / tilt_range * 100.0
if self._tilt_invert:
return 100 - position_percentage
else:
return position_percentage
def find_in_range_from_percent(self, percentage):
"""
Find the adjusted value for 0-100% within the specified range.
if the range is 80-180 and the percentage is 90
this method would determine the value to send on the topic
by offsetting the max and min, getting the percentage value and
returning the offset
"""
offset = self._tilt_min
tilt_range = self._tilt_max - self._tilt_min
position = round(tilt_range * (percentage / 100.0))
position += offset
if self._tilt_invert:
position = self._tilt_max - position + offset
return position

View file

@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL
REQUIREMENTS = ['pyunifi==2.0']
REQUIREMENTS = ['pyunifi==2.12']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'

View file

@ -21,9 +21,7 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
from homeassistant.loader import get_component
REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/'
'c6990eaaa741444a638608e6e00488195e2ca74c.zip#pymysensors==0.9.1']
REQUIREMENTS = ['pymysensors==0.10.0']
_LOGGER = logging.getLogger(__name__)

View file

@ -58,8 +58,8 @@ SCHEMA_SENSORS = vol.Schema({
PLANT_SCHEMA = vol.Schema({
vol.Required(CONF_SENSORS): vol.Schema(SCHEMA_SENSORS),
vol.Optional(CONF_MIN_BATTERY_LEVEL): cv.positive_int,
vol.Optional(CONF_MIN_TEMPERATURE): cv.small_float,
vol.Optional(CONF_MAX_TEMPERATURE): cv.small_float,
vol.Optional(CONF_MIN_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_MIN_MOISTURE): cv.positive_int,
vol.Optional(CONF_MAX_MOISTURE): cv.positive_int,
vol.Optional(CONF_MIN_CONDUCTIVITY): cv.positive_int,

View file

@ -14,13 +14,13 @@ import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS,
STATE_UNKNOWN, ATTR_ATTRIBUTION)
LENGTH_INCHES, LENGTH_KILOMETERS, LENGTH_MILES, LENGTH_FEET,
STATE_UNKNOWN, ATTR_ATTRIBUTION, ATTR_FRIENDLY_NAME)
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_RESOURCE = 'http://api.wunderground.com/api/{}/conditions/{}/q/'
_ALERTS = 'http://api.wunderground.com/api/{}/alerts/{}/q/'
_RESOURCE = 'http://api.wunderground.com/api/{}/{}/{}/q/'
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by the WUnderground weather service"
@ -29,50 +29,562 @@ CONF_LANG = 'lang'
DEFAULT_LANG = 'EN'
MIN_TIME_BETWEEN_UPDATES_ALERTS = timedelta(minutes=15)
MIN_TIME_BETWEEN_UPDATES_OBSERVATION = timedelta(minutes=5)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
# Helper classes for declaring sensor configurations
class WUSensorConfig(object):
"""WU Sensor Configuration.
defines basic HA properties of the weather sensor and
stores callbacks that can parse sensor values out of
the json data received by WU API.
"""
def __init__(self, friendly_name, feature, value,
unit_of_measurement=None, entity_picture=None,
icon="mdi:gauge", device_state_attributes=None):
"""Constructor.
Args:
friendly_name (string|func): Friendly name
feature (string): WU feature. See:
https://www.wunderground.com/weather/api/d/docs?d=data/index
value (function(WUndergroundData)): callback that
extracts desired value from WUndergroundData object
unit_of_measurement (string): unit of meassurement
entity_picture (string): value or callback returning
URL of entity picture
icon (string): icon name or URL
device_state_attributes (dict): dictionary of attributes,
or callable that returns it
"""
self.friendly_name = friendly_name
self.unit_of_measurement = unit_of_measurement
self.feature = feature
self.value = value
self.entity_picture = entity_picture
self.icon = icon
self.device_state_attributes = device_state_attributes or {}
class WUCurrentConditionsSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for current conditions."""
def __init__(self, friendly_name, field, icon="mdi:gauge",
unit_of_measurement=None):
"""Constructor.
Args:
friendly_name (string|func): Friendly name of sensor
field (string): Field name in the "current_observation"
dictionary.
icon (string): icon name or URL, if None sensor
will use current weather symbol
unit_of_measurement (string): unit of meassurement
"""
super().__init__(
friendly_name,
"conditions",
value=lambda wu: wu.data['current_observation'][field],
icon=icon,
unit_of_measurement=unit_of_measurement,
entity_picture=lambda wu: wu.data['current_observation'][
'icon_url'] if icon is None else None,
device_state_attributes={
'date': lambda wu: wu.data['current_observation'][
'observation_time']
}
)
class WUDailyTextForecastSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for daily text forecasts."""
def __init__(self, period, field, unit_of_measurement=None):
"""Constructor.
Args:
period (int): forecast period number
field (string): field name to use as value
unit_of_measurement(string): unit of measurement
"""
super().__init__(
friendly_name=lambda wu: wu.data['forecast']['txt_forecast'][
'forecastday'][period]['title'],
feature='forecast',
value=lambda wu: wu.data['forecast']['txt_forecast'][
'forecastday'][period][field],
entity_picture=lambda wu: wu.data['forecast']['txt_forecast'][
'forecastday'][period]['icon_url'],
unit_of_measurement=unit_of_measurement,
device_state_attributes={
'date': lambda wu: wu.data['forecast']['txt_forecast']['date']
}
)
class WUDailySimpleForecastSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for daily simpleforecasts."""
def __init__(self, friendly_name, period, field, wu_unit=None,
ha_unit=None, icon=None):
"""Constructor.
Args:
period (int): forecast period number
field (string): field name to use as value
wu_unit (string): "fahrenheit", "celsius", "degrees" etc.
see the example json at:
https://www.wunderground.com/weather/api/d/docs?d=data/forecast&MR=1
ha_unit (string): coresponding unit in home assistant
title (string): friendly_name of the sensor
"""
super().__init__(
friendly_name=friendly_name,
feature='forecast',
value=(lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period][field][wu_unit])
if wu_unit else
(lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period][field]),
unit_of_measurement=ha_unit,
entity_picture=lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period]['icon_url'] if not icon else None,
icon=icon,
device_state_attributes={
'date': lambda wu: wu.data['forecast']['simpleforecast'][
'forecastday'][period]['date']['pretty']
}
)
class WUHourlyForecastSensorConfig(WUSensorConfig):
"""Helper for defining sensor configurations for hourly text forecasts."""
def __init__(self, period, field):
"""Constructor.
Args:
period (int): forecast period number
field (int): field name to use as value
"""
super().__init__(
friendly_name=lambda wu: "{} {}".format(
wu.data['hourly_forecast'][period]['FCTTIME'][
'weekday_name_abbrev'],
wu.data['hourly_forecast'][period]['FCTTIME'][
'civil']),
feature='hourly',
value=lambda wu: wu.data['hourly_forecast'][period][
field],
entity_picture=lambda wu: wu.data['hourly_forecast'][
period]["icon_url"],
device_state_attributes={
'temp_c': lambda wu: wu.data['hourly_forecast'][
period]['temp']['metric'],
'temp_f': lambda wu: wu.data['hourly_forecast'][
period]['temp']['english'],
'dewpoint_c': lambda wu: wu.data['hourly_forecast'][
period]['dewpoint']['metric'],
'dewpoint_f': lambda wu: wu.data['hourly_forecast'][
period]['dewpoint']['english'],
'precip_prop': lambda wu: wu.data['hourly_forecast'][
period]['pop'],
'sky': lambda wu: wu.data['hourly_forecast'][
period]['sky'],
'precip_mm': lambda wu: wu.data['hourly_forecast'][
period]['qpf']['metric'],
'precip_in': lambda wu: wu.data['hourly_forecast'][
period]['qpf']['english'],
'humidity': lambda wu: wu.data['hourly_forecast'][
period]['humidity'],
'wind_kph': lambda wu: wu.data['hourly_forecast'][
period]['wspd']['metric'],
'wind_mph': lambda wu: wu.data['hourly_forecast'][
period]['wspd']['english'],
'pressure_mb': lambda wu: wu.data['hourly_forecast'][
period]['mslp']['metric'],
'pressure_inHg': lambda wu: wu.data['hourly_forecast'][
period]['mslp']['english'],
'date': lambda wu: wu.data['hourly_forecast'][
period]['FCTTIME']['pretty'],
},
)
class WUAlmanacSensorConfig(WUSensorConfig):
"""Helper for defining field configurations for almanac sensors."""
def __init__(self, friendly_name, field, value_type, wu_unit,
unit_of_measurement, icon):
"""Constructor.
Args:
friendly_name (string|func): Friendly name
field (string): value name returned in 'almanac' dict
as returned by the WU API
value_type (string): "record" or "normal"
wu_unit (string): unit name in WU API
icon (string): icon name or URL
unit_of_measurement (string): unit of meassurement
"""
super().__init__(
friendly_name=friendly_name,
feature="almanac",
value=lambda wu: wu.data['almanac'][field][value_type][wu_unit],
unit_of_measurement=unit_of_measurement,
icon=icon
)
class WUAlertsSensorConfig(WUSensorConfig):
"""Helper for defining field configuration for alerts."""
def __init__(self, friendly_name):
"""Constructor.
Args:
friendly_name (string|func): Friendly name
"""
super().__init__(
friendly_name=friendly_name,
feature="alerts",
value=lambda wu: len(wu.data['alerts']),
icon=lambda wu: "mdi:alert-circle-outline"
if len(wu.data['alerts']) > 0
else "mdi:check-circle-outline",
device_state_attributes=self._get_attributes
)
@staticmethod
def _get_attributes(rest):
attrs = {}
if 'alerts' not in rest.data:
return attrs
alerts = rest.data['alerts']
multiple_alerts = len(alerts) > 1
for data in alerts:
for alert in ALERTS_ATTRS:
if data[alert]:
if multiple_alerts:
dkey = alert.capitalize() + '_' + data['type']
else:
dkey = alert.capitalize()
attrs[dkey] = data[alert]
return attrs
# Declaration of supported WU sensors
# (see above helper classes for argument explanation)
# Sensor types are defined like: Name, units
SENSOR_TYPES = {
'alerts': ['Alerts', None],
'dewpoint_c': ['Dewpoint (°C)', TEMP_CELSIUS],
'dewpoint_f': ['Dewpoint (°F)', TEMP_FAHRENHEIT],
'dewpoint_string': ['Dewpoint Summary', None],
'feelslike_c': ['Feels Like (°C)', TEMP_CELSIUS],
'feelslike_f': ['Feels Like (°F)', TEMP_FAHRENHEIT],
'feelslike_string': ['Feels Like', None],
'heat_index_c': ['Dewpoint (°C)', TEMP_CELSIUS],
'heat_index_f': ['Dewpoint (°F)', TEMP_FAHRENHEIT],
'heat_index_string': ['Heat Index Summary', None],
'elevation': ['Elevation', 'ft'],
'location': ['Location', None],
'observation_time': ['Observation Time', None],
'precip_1hr_in': ['Precipation 1hr', 'in'],
'precip_1hr_metric': ['Precipation 1hr', 'mm'],
'precip_1hr_string': ['Precipation 1hr', None],
'precip_today_in': ['Precipation Today', 'in'],
'precip_today_metric': ['Precipitation Today', 'mm'],
'precip_today_string': ['Precipitation today', None],
'pressure_in': ['Pressure', 'in'],
'pressure_mb': ['Pressure', 'mb'],
'pressure_trend': ['Pressure Trend', None],
'relative_humidity': ['Relative Humidity', '%'],
'station_id': ['Station ID', None],
'solarradiation': ['Solar Radiation', None],
'temperature_string': ['Temperature Summary', None],
'temp_c': ['Temperature (°C)', TEMP_CELSIUS],
'temp_f': ['Temperature (°F)', TEMP_FAHRENHEIT],
'UV': ['UV', None],
'visibility_km': ['Visibility (km)', 'km'],
'visibility_mi': ['Visibility (miles)', 'mi'],
'weather': ['Weather Summary', None],
'wind_degrees': ['Wind Degrees', None],
'wind_dir': ['Wind Direction', None],
'wind_gust_kph': ['Wind Gust', 'kph'],
'wind_gust_mph': ['Wind Gust', 'mph'],
'wind_kph': ['Wind Speed', 'kph'],
'wind_mph': ['Wind Speed', 'mph'],
'wind_string': ['Wind Summary', None],
'alerts': WUAlertsSensorConfig('Alerts'),
'dewpoint_c': WUCurrentConditionsSensorConfig(
'Dewpoint', 'dewpoint_c', 'mdi:water', TEMP_CELSIUS),
'dewpoint_f': WUCurrentConditionsSensorConfig(
'Dewpoint', 'dewpoint_f', 'mdi:water', TEMP_FAHRENHEIT),
'dewpoint_string': WUCurrentConditionsSensorConfig(
'Dewpoint Summary', 'dewpoint_string', 'mdi:water'),
'feelslike_c': WUCurrentConditionsSensorConfig(
'Feels Like', 'feelslike_c', 'mdi:thermometer', TEMP_CELSIUS),
'feelslike_f': WUCurrentConditionsSensorConfig(
'Feels Like', 'feelslike_f', 'mdi:thermometer', TEMP_FAHRENHEIT),
'feelslike_string': WUCurrentConditionsSensorConfig(
'Feels Like', 'feelslike_string', "mdi:thermometer"),
'heat_index_c': WUCurrentConditionsSensorConfig(
'Heat index', 'heat_index_c', "mdi:thermometer", TEMP_CELSIUS),
'heat_index_f': WUCurrentConditionsSensorConfig(
'Heat index', 'heat_index_f', "mdi:thermometer", TEMP_FAHRENHEIT),
'heat_index_string': WUCurrentConditionsSensorConfig(
'Heat Index Summary', 'heat_index_string', "mdi:thermometer"),
'elevation': WUSensorConfig(
'Elevation',
'conditions',
value=lambda wu: wu.data['current_observation'][
'observation_location']['elevation'].split()[0],
unit_of_measurement=LENGTH_FEET,
icon="mdi:elevation-rise"),
'location': WUSensorConfig(
'Location',
'conditions',
value=lambda wu: wu.data['current_observation'][
'display_location']['full'],
icon="mdi:map-marker"),
'observation_time': WUCurrentConditionsSensorConfig(
'Observation Time', 'observation_time', "mdi:clock"),
'precip_1hr_in': WUCurrentConditionsSensorConfig(
'Precipitation 1hr', 'precip_1hr_in', "mdi:umbrella", LENGTH_INCHES),
'precip_1hr_metric': WUCurrentConditionsSensorConfig(
'Precipitation 1hr', 'precip_1hr_metric', "mdi:umbrella", 'mm'),
'precip_1hr_string': WUCurrentConditionsSensorConfig(
'Precipitation 1hr', 'precip_1hr_string', "mdi:umbrella"),
'precip_today_in': WUCurrentConditionsSensorConfig(
'Precipitation Today', 'precip_today_in', "mdi:umbrella",
LENGTH_INCHES),
'precip_today_metric': WUCurrentConditionsSensorConfig(
'Precipitation Today', 'precip_today_metric', "mdi:umbrella", 'mm'),
'precip_today_string': WUCurrentConditionsSensorConfig(
'Precipitation Today', 'precip_today_string', "mdi:umbrella"),
'pressure_in': WUCurrentConditionsSensorConfig(
'Pressure', 'pressure_in', "mdi:gauge", 'inHg'),
'pressure_mb': WUCurrentConditionsSensorConfig(
'Pressure', 'pressure_mb', "mdi:gauge", 'mb'),
'pressure_trend': WUCurrentConditionsSensorConfig(
'Pressure Trend', 'pressure_trend', "mdi:gauge"),
'relative_humidity': WUSensorConfig(
'Relative Humidity',
'conditions',
value=lambda wu: int(wu.data['current_observation'][
'relative_humidity'][:-1]),
unit_of_measurement='%',
icon="mdi:water-percent"),
'station_id': WUCurrentConditionsSensorConfig(
'Station ID', 'station_id', "mdi:home"),
'solarradiation': WUCurrentConditionsSensorConfig(
'Solar Radiation', 'solarradiation', "mdi:weather-sunny", "w/m2"),
'temperature_string': WUCurrentConditionsSensorConfig(
'Temperature Summary', 'temperature_string', "mdi:thermometer"),
'temp_c': WUCurrentConditionsSensorConfig(
'Temperature', 'temp_c', "mdi:thermometer", TEMP_CELSIUS),
'temp_f': WUCurrentConditionsSensorConfig(
'Temperature', 'temp_f', "mdi:thermometer", TEMP_FAHRENHEIT),
'UV': WUCurrentConditionsSensorConfig(
'UV', 'UV', "mdi:sunglasses"),
'visibility_km': WUCurrentConditionsSensorConfig(
'Visibility (km)', 'visibility_km', "mdi:eye", LENGTH_KILOMETERS),
'visibility_mi': WUCurrentConditionsSensorConfig(
'Visibility (miles)', 'visibility_mi', "mdi:eye", LENGTH_MILES),
'weather': WUCurrentConditionsSensorConfig(
'Weather Summary', 'weather', None),
'wind_degrees': WUCurrentConditionsSensorConfig(
'Wind Degrees', 'wind_degrees', "mdi:weather-windy", "°"),
'wind_dir': WUCurrentConditionsSensorConfig(
'Wind Direction', 'wind_dir', "mdi:weather-windy"),
'wind_gust_kph': WUCurrentConditionsSensorConfig(
'Wind Gust', 'wind_gust_kph', "mdi:weather-windy", 'kph'),
'wind_gust_mph': WUCurrentConditionsSensorConfig(
'Wind Gust', 'wind_gust_mph', "mdi:weather-windy", 'mph'),
'wind_kph': WUCurrentConditionsSensorConfig(
'Wind Speed', 'wind_kph', "mdi:weather-windy", 'kph'),
'wind_mph': WUCurrentConditionsSensorConfig(
'Wind Speed', 'wind_mph', "mdi:weather-windy", 'mph'),
'wind_string': WUCurrentConditionsSensorConfig(
'Wind Summary', 'wind_string', "mdi:weather-windy"),
'temp_high_record_c': WUAlmanacSensorConfig(
lambda wu: 'High Temperature Record ({})'.format(
wu.data['almanac']['temp_high']['recordyear']),
'temp_high', 'record', 'C', TEMP_CELSIUS, 'mdi:thermometer'),
'temp_high_record_f': WUAlmanacSensorConfig(
lambda wu: 'High Temperature Record ({})'.format(
wu.data['almanac']['temp_high']['recordyear']),
'temp_high', 'record', 'F', TEMP_FAHRENHEIT, 'mdi:thermometer'),
'temp_low_record_c': WUAlmanacSensorConfig(
lambda wu: 'Low Temperature Record ({})'.format(
wu.data['almanac']['temp_low']['recordyear']),
'temp_low', 'record', 'C', TEMP_CELSIUS, 'mdi:thermometer'),
'temp_low_record_f': WUAlmanacSensorConfig(
lambda wu: 'Low Temperature Record ({})'.format(
wu.data['almanac']['temp_low']['recordyear']),
'temp_low', 'record', 'F', TEMP_FAHRENHEIT, 'mdi:thermometer'),
'temp_low_avg_c': WUAlmanacSensorConfig(
'Historic Average of Low Temperatures for Today',
'temp_low', 'normal', 'C', TEMP_CELSIUS, 'mdi:thermometer'),
'temp_low_avg_f': WUAlmanacSensorConfig(
'Historic Average of Low Temperatures for Today',
'temp_low', 'normal', 'F', TEMP_FAHRENHEIT, 'mdi:thermometer'),
'temp_high_avg_c': WUAlmanacSensorConfig(
'Historic Average of High Temperatures for Today',
'temp_high', 'normal', 'C', TEMP_CELSIUS, "mdi:thermometer"),
'temp_high_avg_f': WUAlmanacSensorConfig(
'Historic Average of High Temperatures for Today',
'temp_high', 'normal', 'F', TEMP_FAHRENHEIT, "mdi:thermometer"),
'weather_1d': WUDailyTextForecastSensorConfig(0, "fcttext"),
'weather_1d_metric': WUDailyTextForecastSensorConfig(0, "fcttext_metric"),
'weather_1n': WUDailyTextForecastSensorConfig(1, "fcttext"),
'weather_1n_metric': WUDailyTextForecastSensorConfig(1, "fcttext_metric"),
'weather_2d': WUDailyTextForecastSensorConfig(2, "fcttext"),
'weather_2d_metric': WUDailyTextForecastSensorConfig(2, "fcttext_metric"),
'weather_2n': WUDailyTextForecastSensorConfig(3, "fcttext"),
'weather_2n_metric': WUDailyTextForecastSensorConfig(3, "fcttext_metric"),
'weather_3d': WUDailyTextForecastSensorConfig(4, "fcttext"),
'weather_3d_metric': WUDailyTextForecastSensorConfig(4, "fcttext_metric"),
'weather_3n': WUDailyTextForecastSensorConfig(5, "fcttext"),
'weather_3n_metric': WUDailyTextForecastSensorConfig(5, "fcttext_metric"),
'weather_4d': WUDailyTextForecastSensorConfig(6, "fcttext"),
'weather_4d_metric': WUDailyTextForecastSensorConfig(6, "fcttext_metric"),
'weather_4n': WUDailyTextForecastSensorConfig(7, "fcttext"),
'weather_4n_metric': WUDailyTextForecastSensorConfig(7, "fcttext_metric"),
'weather_1h': WUHourlyForecastSensorConfig(0, "condition"),
'weather_2h': WUHourlyForecastSensorConfig(1, "condition"),
'weather_3h': WUHourlyForecastSensorConfig(2, "condition"),
'weather_4h': WUHourlyForecastSensorConfig(3, "condition"),
'weather_5h': WUHourlyForecastSensorConfig(4, "condition"),
'weather_6h': WUHourlyForecastSensorConfig(5, "condition"),
'weather_7h': WUHourlyForecastSensorConfig(6, "condition"),
'weather_8h': WUHourlyForecastSensorConfig(7, "condition"),
'weather_9h': WUHourlyForecastSensorConfig(8, "condition"),
'weather_10h': WUHourlyForecastSensorConfig(9, "condition"),
'weather_11h': WUHourlyForecastSensorConfig(10, "condition"),
'weather_12h': WUHourlyForecastSensorConfig(11, "condition"),
'weather_13h': WUHourlyForecastSensorConfig(12, "condition"),
'weather_14h': WUHourlyForecastSensorConfig(13, "condition"),
'weather_15h': WUHourlyForecastSensorConfig(14, "condition"),
'weather_16h': WUHourlyForecastSensorConfig(15, "condition"),
'weather_17h': WUHourlyForecastSensorConfig(16, "condition"),
'weather_18h': WUHourlyForecastSensorConfig(17, "condition"),
'weather_19h': WUHourlyForecastSensorConfig(18, "condition"),
'weather_20h': WUHourlyForecastSensorConfig(19, "condition"),
'weather_21h': WUHourlyForecastSensorConfig(20, "condition"),
'weather_22h': WUHourlyForecastSensorConfig(21, "condition"),
'weather_23h': WUHourlyForecastSensorConfig(22, "condition"),
'weather_24h': WUHourlyForecastSensorConfig(23, "condition"),
'weather_25h': WUHourlyForecastSensorConfig(24, "condition"),
'weather_26h': WUHourlyForecastSensorConfig(25, "condition"),
'weather_27h': WUHourlyForecastSensorConfig(26, "condition"),
'weather_28h': WUHourlyForecastSensorConfig(27, "condition"),
'weather_29h': WUHourlyForecastSensorConfig(28, "condition"),
'weather_30h': WUHourlyForecastSensorConfig(29, "condition"),
'weather_31h': WUHourlyForecastSensorConfig(30, "condition"),
'weather_32h': WUHourlyForecastSensorConfig(31, "condition"),
'weather_33h': WUHourlyForecastSensorConfig(32, "condition"),
'weather_34h': WUHourlyForecastSensorConfig(33, "condition"),
'weather_35h': WUHourlyForecastSensorConfig(34, "condition"),
'weather_36h': WUHourlyForecastSensorConfig(35, "condition"),
'temp_high_1d_c': WUDailySimpleForecastSensorConfig(
"High Temperature Today", 0, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_high_2d_c': WUDailySimpleForecastSensorConfig(
"High Temperature Tomorrow", 1, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_high_3d_c': WUDailySimpleForecastSensorConfig(
"High Temperature in 3 Days", 2, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_high_4d_c': WUDailySimpleForecastSensorConfig(
"High Temperature in 4 Days", 3, "high", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_high_1d_f': WUDailySimpleForecastSensorConfig(
"High Temperature Today", 0, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_high_2d_f': WUDailySimpleForecastSensorConfig(
"High Temperature Tomorrow", 1, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_high_3d_f': WUDailySimpleForecastSensorConfig(
"High Temperature in 3 Days", 2, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_high_4d_f': WUDailySimpleForecastSensorConfig(
"High Temperature in 4 Days", 3, "high", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_low_1d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature Today", 0, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_low_2d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature Tomorrow", 1, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_low_3d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature in 3 Days", 2, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_low_4d_c': WUDailySimpleForecastSensorConfig(
"Low Temperature in 4 Days", 3, "low", "celsius", TEMP_CELSIUS,
"mdi:thermometer"),
'temp_low_1d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature Today", 0, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_low_2d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature Tomorrow", 1, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_low_3d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature in 3 Days", 2, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'temp_low_4d_f': WUDailySimpleForecastSensorConfig(
"Low Temperature in 4 Days", 3, "low", "fahrenheit", TEMP_FAHRENHEIT,
"mdi:thermometer"),
'wind_gust_1d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind Today", 0, "maxwind", "kph", "kph", "mdi:weather-windy"),
'wind_gust_2d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind Tomorrow", 1, "maxwind", "kph", "kph", "mdi:weather-windy"),
'wind_gust_3d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 3 Days", 2, "maxwind", "kph", "kph",
"mdi:weather-windy"),
'wind_gust_4d_kph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 4 Days", 3, "maxwind", "kph", "kph",
"mdi:weather-windy"),
'wind_gust_1d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind Today", 0, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_gust_2d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind Tomorrow", 1, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_gust_3d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 3 Days", 2, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_gust_4d_mph': WUDailySimpleForecastSensorConfig(
"Max. Wind in 4 Days", 3, "maxwind", "mph", "mph",
"mdi:weather-windy"),
'wind_1d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Today", 0, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_2d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Tomorrow", 1, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_3d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 3 Days", 2, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_4d_kph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 4 Days", 3, "avewind", "kph", "kph",
"mdi:weather-windy"),
'wind_1d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Today", 0, "avewind", "mph", "mph",
"mdi:weather-windy"),
'wind_2d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind Tomorrow", 1, "avewind", "mph", "mph",
"mdi:weather-windy"),
'wind_3d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 3 Days", 2, "avewind", "mph", "mph",
"mdi:weather-windy"),
'wind_4d_mph': WUDailySimpleForecastSensorConfig(
"Avg. Wind in 4 Days", 3, "avewind", "mph", "mph",
"mdi:weather-windy"),
'precip_1d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Today", 0, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_2d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Tomorrow", 1, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_3d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 3 Days", 2, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_4d_mm': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 4 Days", 3, 'qpf_allday', 'mm', 'mm',
"mdi:umbrella"),
'precip_1d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Today", 0, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_2d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity Tomorrow", 1, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_3d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 3 Days", 2, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_4d_in': WUDailySimpleForecastSensorConfig(
"Precipitation Intensity in 4 Days", 3, 'qpf_allday', 'in',
LENGTH_INCHES, "mdi:umbrella"),
'precip_1d': WUDailySimpleForecastSensorConfig(
"Percipitation Probability Today", 0, "pop", None, "%",
"mdi:umbrella"),
'precip_2d': WUDailySimpleForecastSensorConfig(
"Percipitation Probability Tomorrow", 1, "pop", None, "%",
"mdi:umbrella"),
'precip_3d': WUDailySimpleForecastSensorConfig(
"Percipitation Probability in 3 Days", 2, "pop", None, "%",
"mdi:umbrella"),
'precip_4d': WUDailySimpleForecastSensorConfig(
"Percipitation Probability in 4 Days", 3, "pop", None, "%",
"mdi:umbrella"),
}
# Alert Attributes
@ -105,9 +617,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_PWS_ID): cv.string,
vol.Optional(CONF_LANG, default=DEFAULT_LANG):
vol.All(vol.In(LANG_CODES)),
vol.All(vol.In(LANG_CODES)),
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
@ -138,6 +650,20 @@ class WUndergroundSensor(Entity):
"""Initialize the sensor."""
self.rest = rest
self._condition = condition
self.rest.request_feature(SENSOR_TYPES[condition].feature)
def _cfg_expand(self, what, default=None):
cfg = SENSOR_TYPES[self._condition]
val = getattr(cfg, what)
try:
val = val(self.rest)
except (KeyError, IndexError) as err:
_LOGGER.error("Failed to parse response from WU API: %s", err)
val = default
except TypeError:
pass # val was not callable - keep original value
return val
@property
def name(self):
@ -147,69 +673,42 @@ class WUndergroundSensor(Entity):
@property
def state(self):
"""Return the state of the sensor."""
if self.rest.data:
if self._condition == 'elevation' and self._condition in \
self.rest.data['observation_location']:
return self.rest.data['observation_location'][self._condition]\
.split()[0]
if self._condition == 'location' and \
'full' in self.rest.data['display_location']:
return self.rest.data['display_location']['full']
if self._condition in self.rest.data:
if self._condition == 'relative_humidity':
return int(self.rest.data[self._condition][:-1])
else:
return self.rest.data[self._condition]
if self._condition == 'alerts':
if self.rest.alerts:
return len(self.rest.alerts)
else:
return 0
return STATE_UNKNOWN
return self._cfg_expand("value", STATE_UNKNOWN)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
attrs = self._cfg_expand("device_state_attributes", {})
for (attr, callback) in attrs.items():
try:
attrs[attr] = callback(self.rest)
except TypeError:
attrs[attr] = callback
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
if not self.rest.alerts or self._condition != 'alerts':
return attrs
multiple_alerts = len(self.rest.alerts) > 1
for data in self.rest.alerts:
for alert in ALERTS_ATTRS:
if data[alert]:
if multiple_alerts:
dkey = alert.capitalize() + '_' + data['type']
else:
dkey = alert.capitalize()
attrs[dkey] = data[alert]
attrs[ATTR_FRIENDLY_NAME] = self._cfg_expand("friendly_name")
return attrs
@property
def icon(self):
"""Return icon."""
return self._cfg_expand("icon", super().icon)
@property
def entity_picture(self):
"""Return the entity picture."""
if self.rest.data and self._condition == 'weather':
url = self.rest.data['icon_url']
url = self._cfg_expand("entity_picture")
if url is not None:
return re.sub(r'^http://', 'https://', url, flags=re.IGNORECASE)
@property
def unit_of_measurement(self):
"""Return the units of measurement."""
return SENSOR_TYPES[self._condition][1]
return self._cfg_expand("unit_of_measurement")
def update(self):
"""Update current conditions."""
if self._condition == 'alerts':
self.rest.update_alerts()
else:
self.rest.update()
self.rest.update()
class WUndergroundData(object):
@ -223,11 +722,16 @@ class WUndergroundData(object):
self._lang = 'lang:{}'.format(lang)
self._latitude = hass.config.latitude
self._longitude = hass.config.longitude
self._features = set()
self.data = None
self.alerts = None
def request_feature(self, feature):
"""Register feature to be fetched from WU API."""
self._features.add(feature)
def _build_url(self, baseurl=_RESOURCE):
url = baseurl.format(self._api_key, self._lang)
url = baseurl.format(
self._api_key, "/".join(self._features), self._lang)
if self._pws_id:
url = url + 'pws:{}'.format(self._pws_id)
else:
@ -235,7 +739,7 @@ class WUndergroundData(object):
return url + '.json'
@Throttle(MIN_TIME_BETWEEN_UPDATES_OBSERVATION)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from WUnderground."""
try:
@ -244,21 +748,7 @@ class WUndergroundData(object):
raise ValueError(result['response']["error"]
["description"])
else:
self.data = result["current_observation"]
self.data = result
except ValueError as err:
_LOGGER.error("Check WUnderground API %s", err.args)
self.data = None
@Throttle(MIN_TIME_BETWEEN_UPDATES_ALERTS)
def update_alerts(self):
"""Get the latest alerts data from WUnderground."""
try:
result = requests.get(self._build_url(_ALERTS), timeout=10).json()
if "error" in result['response']:
raise ValueError(result['response']["error"]
["description"])
else:
self.alerts = result["alerts"]
except ValueError as err:
_LOGGER.error("Check WUnderground API %s", err.args)
self.alerts = None

View file

@ -318,9 +318,6 @@ https://github.com/tfriedel/python-lightify/archive/1bb1db0e7bd5b14304d7bb267e23
# homeassistant.components.lutron
https://github.com/thecynic/pylutron/archive/v0.1.0.zip#pylutron==0.1.0
# homeassistant.components.mysensors
https://github.com/theolind/pymysensors/archive/c6990eaaa741444a638608e6e00488195e2ca74c.zip#pymysensors==0.9.1
# homeassistant.components.sensor.modem_callerid
https://github.com/vroomfonde1/basicmodem/archive/0.7.zip#basicmodem==0.7
@ -593,6 +590,9 @@ pymailgunner==1.4
# homeassistant.components.mochad
pymochad==0.1.1
# homeassistant.components.mysensors
pymysensors==0.10.0
# homeassistant.components.device_tracker.netgear
pynetgear==0.3.3
@ -698,7 +698,7 @@ pytrackr==0.0.5
pytradfri==1.1
# homeassistant.components.device_tracker.unifi
pyunifi==2.0
pyunifi==2.12
# homeassistant.components.keyboard
# pyuserinput==0.1.11

View file

@ -4,6 +4,7 @@ import unittest
from homeassistant.setup import setup_component
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
import homeassistant.components.cover as cover
from homeassistant.components.cover.mqtt import MqttCover
from tests.common import (
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message)
@ -450,3 +451,75 @@ class TestCoverMQTT(unittest.TestCase):
self.assertEqual(('tilt-command-topic', 25, 0, False),
self.mock_publish.mock_calls[-2][1])
def test_find_percentage_in_range_defaults(self):
"""Test find percentage in range with default range."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, False)
self.assertEqual(44, mqtt_cover.find_percentage_in_range(44))
def test_find_percentage_in_range_altered(self):
"""Test find percentage in range with altered range."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, False)
self.assertEqual(40, mqtt_cover.find_percentage_in_range(120))
def test_find_percentage_in_range_defaults_inverted(self):
"""Test find percentage in range with default range but inverted."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, True)
self.assertEqual(56, mqtt_cover.find_percentage_in_range(44))
def test_find_percentage_in_range_altered_inverted(self):
"""Test find percentage in range with altered range and inverted."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, True)
self.assertEqual(60, mqtt_cover.find_percentage_in_range(120))
def test_find_in_range_defaults(self):
"""Test find in range with default range."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, False)
self.assertEqual(44, mqtt_cover.find_in_range_from_percent(44))
def test_find_in_range_altered(self):
"""Test find in range with altered range."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, False)
self.assertEqual(120, mqtt_cover.find_in_range_from_percent(40))
def test_find_in_range_defaults_inverted(self):
"""Test find in range with default range but inverted."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, True)
self.assertEqual(44, mqtt_cover.find_in_range_from_percent(56))
def test_find_in_range_altered_inverted(self):
"""Test find in range with altered range and inverted."""
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, True)
self.assertEqual(120, mqtt_cover.find_in_range_from_percent(60))

View file

@ -2,7 +2,7 @@
import unittest
from homeassistant.components.sensor import wunderground
from homeassistant.const import TEMP_CELSIUS
from homeassistant.const import TEMP_CELSIUS, LENGTH_INCHES
from tests.common import get_test_home_assistant
@ -19,7 +19,8 @@ VALID_CONFIG = {
'platform': 'wunderground',
'api_key': 'foo',
'monitored_conditions': [
'weather', 'feelslike_c', 'alerts', 'elevation', 'location'
'weather', 'feelslike_c', 'alerts', 'elevation', 'location',
'weather_1d_metric', 'precip_1d_in'
]
}
@ -37,6 +38,8 @@ FEELS_LIKE = '40'
WEATHER = 'Clear'
HTTPS_ICON_URL = 'https://icons.wxug.com/i/c/k/clear.gif'
ALERT_MESSAGE = 'This is a test alert message'
FORECAST_TEXT = 'Mostly Cloudy. Fog overnight.'
PRECIP_IN = 0.03
def mocked_requests_get(*args, **kwargs):
@ -60,7 +63,9 @@ def mocked_requests_get(*args, **kwargs):
"termsofService":
"http://www.wunderground.com/weather/api/d/terms.html",
"features": {
"conditions": 1
"conditions": 1,
"alerts": 1,
"forecast": 1,
}
}, "current_observation": {
"image": {
@ -90,7 +95,58 @@ def mocked_requests_get(*args, **kwargs):
"message": ALERT_MESSAGE,
},
],
], "forecast": {
"txt_forecast": {
"date": "22:35 CEST",
"forecastday": [
{
"period": 0,
"icon_url":
"http://icons.wxug.com/i/c/k/clear.gif",
"title": "Tuesday",
"fcttext": FORECAST_TEXT,
"fcttext_metric": FORECAST_TEXT,
"pop": "0"
},
],
}, "simpleforecast": {
"forecastday": [
{
"date": {
"pretty": "19:00 CEST 4. Duben 2017",
},
"period": 1,
"high": {
"fahrenheit": "56",
"celsius": "13",
},
"low": {
"fahrenheit": "43",
"celsius": "6",
},
"conditions": "Možnost deště",
"icon_url":
"http://icons.wxug.com/i/c/k/chancerain.gif",
"qpf_allday": {
"in": PRECIP_IN,
"mm": 1,
},
"maxwind": {
"mph": 0,
"kph": 0,
"dir": "",
"degrees": 0,
},
"avewind": {
"mph": 0,
"kph": 0,
"dir": "severní",
"degrees": 0
}
},
],
},
},
}, 200)
else:
return MockResponse({
@ -168,7 +224,13 @@ class TestWundergroundSetup(unittest.TestCase):
self.assertEqual('Holly Springs, NC', device.state)
elif device.name == 'PWS_elevation':
self.assertEqual('413', device.state)
else:
elif device.name == 'PWS_feelslike_c':
self.assertIsNone(device.entity_picture)
self.assertEqual(FEELS_LIKE, device.state)
self.assertEqual(TEMP_CELSIUS, device.unit_of_measurement)
elif device.name == 'PWS_weather_1d_metric':
self.assertEqual(FORECAST_TEXT, device.state)
else:
self.assertEqual(device.name, 'PWS_precip_1d_in')
self.assertEqual(PRECIP_IN, device.state)
self.assertEqual(LENGTH_INCHES, device.unit_of_measurement)