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_MIN = 'tilt_min'
CONF_TILT_MAX = 'tilt_max' CONF_TILT_MAX = 'tilt_max'
CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic' CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic'
CONF_TILT_INVERT_STATE = 'tilt_invert_state'
DEFAULT_NAME = 'MQTT Cover' DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = 'OPEN' DEFAULT_PAYLOAD_OPEN = 'OPEN'
@ -52,6 +53,7 @@ DEFAULT_TILT_OPEN_POSITION = 100
DEFAULT_TILT_MIN = 0 DEFAULT_TILT_MIN = 0
DEFAULT_TILT_MAX = 100 DEFAULT_TILT_MAX = 100
DEFAULT_TILT_OPTIMISTIC = False DEFAULT_TILT_OPTIMISTIC = False
DEFAULT_TILT_INVERT_STATE = False
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION) 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_MAX, default=DEFAULT_TILT_MAX): int,
vol.Optional(CONF_TILT_STATE_OPTIMISTIC, vol.Optional(CONF_TILT_STATE_OPTIMISTIC,
default=DEFAULT_TILT_OPTIMISTIC): cv.boolean, 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_MIN),
config.get(CONF_TILT_MAX), config.get(CONF_TILT_MAX),
config.get(CONF_TILT_STATE_OPTIMISTIC), 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, tilt_status_topic, qos, retain, state_open, state_closed,
payload_open, payload_close, payload_stop, payload_open, payload_close, payload_stop,
optimistic, value_template, tilt_open_position, 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.""" """Initialize the cover."""
self._position = None self._position = None
self._state = None self._state = None
@ -138,6 +144,7 @@ class MqttCover(CoverDevice):
self._tilt_min = tilt_min self._tilt_min = tilt_min
self._tilt_max = tilt_max self._tilt_max = tilt_max
self._tilt_optimistic = tilt_optimistic self._tilt_optimistic = tilt_optimistic
self._tilt_invert = tilt_invert
@asyncio.coroutine @asyncio.coroutine
def async_added_to_hass(self): def async_added_to_hass(self):
@ -150,8 +157,8 @@ class MqttCover(CoverDevice):
"""Handle tilt updates.""" """Handle tilt updates."""
if (payload.isnumeric() and if (payload.isnumeric() and
self._tilt_min <= int(payload) <= self._tilt_max): 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._tilt_value = level
self.hass.async_add_job(self.async_update_ha_state()) self.hass.async_add_job(self.async_update_ha_state())
@ -278,7 +285,8 @@ class MqttCover(CoverDevice):
def async_open_cover_tilt(self, **kwargs): def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open.""" """Tilt the cover open."""
mqtt.async_publish(self.hass, self._tilt_command_topic, 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: if self._tilt_optimistic:
self._tilt_value = self._tilt_open_position self._tilt_value = self._tilt_open_position
self.hass.async_add_job(self.async_update_ha_state()) self.hass.async_add_job(self.async_update_ha_state())
@ -287,7 +295,8 @@ class MqttCover(CoverDevice):
def async_close_cover_tilt(self, **kwargs): def async_close_cover_tilt(self, **kwargs):
"""Tilt the cover closed.""" """Tilt the cover closed."""
mqtt.async_publish(self.hass, self._tilt_command_topic, 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: if self._tilt_optimistic:
self._tilt_value = self._tilt_closed_position self._tilt_value = self._tilt_closed_position
self.hass.async_add_job(self.async_update_ha_state()) self.hass.async_add_job(self.async_update_ha_state())
@ -301,9 +310,39 @@ class MqttCover(CoverDevice):
position = float(kwargs[ATTR_TILT_POSITION]) position = float(kwargs[ATTR_TILT_POSITION])
# The position needs to be between min and max # The position needs to be between min and max
tilt_range = self._tilt_max - self._tilt_min level = self.find_in_range_from_percent(position)
percentage = position / 100.0
level = round(tilt_range * percentage)
mqtt.async_publish(self.hass, self._tilt_command_topic, mqtt.async_publish(self.hass, self._tilt_command_topic,
level, self._qos, self._retain) 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_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL from homeassistant.const import CONF_VERIFY_SSL
REQUIREMENTS = ['pyunifi==2.0'] REQUIREMENTS = ['pyunifi==2.12']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port' CONF_PORT = 'port'

View file

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

View file

@ -58,8 +58,8 @@ SCHEMA_SENSORS = vol.Schema({
PLANT_SCHEMA = vol.Schema({ PLANT_SCHEMA = vol.Schema({
vol.Required(CONF_SENSORS): vol.Schema(SCHEMA_SENSORS), vol.Required(CONF_SENSORS): vol.Schema(SCHEMA_SENSORS),
vol.Optional(CONF_MIN_BATTERY_LEVEL): cv.positive_int, vol.Optional(CONF_MIN_BATTERY_LEVEL): cv.positive_int,
vol.Optional(CONF_MIN_TEMPERATURE): cv.small_float, vol.Optional(CONF_MIN_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMPERATURE): cv.small_float, vol.Optional(CONF_MAX_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_MIN_MOISTURE): cv.positive_int, vol.Optional(CONF_MIN_MOISTURE): cv.positive_int,
vol.Optional(CONF_MAX_MOISTURE): cv.positive_int, vol.Optional(CONF_MAX_MOISTURE): cv.positive_int,
vol.Optional(CONF_MIN_CONDUCTIVITY): 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.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
CONF_MONITORED_CONDITIONS, CONF_API_KEY, TEMP_FAHRENHEIT, TEMP_CELSIUS, 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.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_RESOURCE = 'http://api.wunderground.com/api/{}/conditions/{}/q/' _RESOURCE = 'http://api.wunderground.com/api/{}/{}/{}/q/'
_ALERTS = 'http://api.wunderground.com/api/{}/alerts/{}/q/'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by the WUnderground weather service" CONF_ATTRIBUTION = "Data provided by the WUnderground weather service"
@ -29,50 +29,562 @@ CONF_LANG = 'lang'
DEFAULT_LANG = 'EN' DEFAULT_LANG = 'EN'
MIN_TIME_BETWEEN_UPDATES_ALERTS = timedelta(minutes=15) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
MIN_TIME_BETWEEN_UPDATES_OBSERVATION = 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 = { SENSOR_TYPES = {
'alerts': ['Alerts', None], 'alerts': WUAlertsSensorConfig('Alerts'),
'dewpoint_c': ['Dewpoint (°C)', TEMP_CELSIUS], 'dewpoint_c': WUCurrentConditionsSensorConfig(
'dewpoint_f': ['Dewpoint (°F)', TEMP_FAHRENHEIT], 'Dewpoint', 'dewpoint_c', 'mdi:water', TEMP_CELSIUS),
'dewpoint_string': ['Dewpoint Summary', None], 'dewpoint_f': WUCurrentConditionsSensorConfig(
'feelslike_c': ['Feels Like (°C)', TEMP_CELSIUS], 'Dewpoint', 'dewpoint_f', 'mdi:water', TEMP_FAHRENHEIT),
'feelslike_f': ['Feels Like (°F)', TEMP_FAHRENHEIT], 'dewpoint_string': WUCurrentConditionsSensorConfig(
'feelslike_string': ['Feels Like', None], 'Dewpoint Summary', 'dewpoint_string', 'mdi:water'),
'heat_index_c': ['Dewpoint (°C)', TEMP_CELSIUS], 'feelslike_c': WUCurrentConditionsSensorConfig(
'heat_index_f': ['Dewpoint (°F)', TEMP_FAHRENHEIT], 'Feels Like', 'feelslike_c', 'mdi:thermometer', TEMP_CELSIUS),
'heat_index_string': ['Heat Index Summary', None], 'feelslike_f': WUCurrentConditionsSensorConfig(
'elevation': ['Elevation', 'ft'], 'Feels Like', 'feelslike_f', 'mdi:thermometer', TEMP_FAHRENHEIT),
'location': ['Location', None], 'feelslike_string': WUCurrentConditionsSensorConfig(
'observation_time': ['Observation Time', None], 'Feels Like', 'feelslike_string', "mdi:thermometer"),
'precip_1hr_in': ['Precipation 1hr', 'in'], 'heat_index_c': WUCurrentConditionsSensorConfig(
'precip_1hr_metric': ['Precipation 1hr', 'mm'], 'Heat index', 'heat_index_c', "mdi:thermometer", TEMP_CELSIUS),
'precip_1hr_string': ['Precipation 1hr', None], 'heat_index_f': WUCurrentConditionsSensorConfig(
'precip_today_in': ['Precipation Today', 'in'], 'Heat index', 'heat_index_f', "mdi:thermometer", TEMP_FAHRENHEIT),
'precip_today_metric': ['Precipitation Today', 'mm'], 'heat_index_string': WUCurrentConditionsSensorConfig(
'precip_today_string': ['Precipitation today', None], 'Heat Index Summary', 'heat_index_string', "mdi:thermometer"),
'pressure_in': ['Pressure', 'in'], 'elevation': WUSensorConfig(
'pressure_mb': ['Pressure', 'mb'], 'Elevation',
'pressure_trend': ['Pressure Trend', None], 'conditions',
'relative_humidity': ['Relative Humidity', '%'], value=lambda wu: wu.data['current_observation'][
'station_id': ['Station ID', None], 'observation_location']['elevation'].split()[0],
'solarradiation': ['Solar Radiation', None], unit_of_measurement=LENGTH_FEET,
'temperature_string': ['Temperature Summary', None], icon="mdi:elevation-rise"),
'temp_c': ['Temperature (°C)', TEMP_CELSIUS], 'location': WUSensorConfig(
'temp_f': ['Temperature (°F)', TEMP_FAHRENHEIT], 'Location',
'UV': ['UV', None], 'conditions',
'visibility_km': ['Visibility (km)', 'km'], value=lambda wu: wu.data['current_observation'][
'visibility_mi': ['Visibility (miles)', 'mi'], 'display_location']['full'],
'weather': ['Weather Summary', None], icon="mdi:map-marker"),
'wind_degrees': ['Wind Degrees', None], 'observation_time': WUCurrentConditionsSensorConfig(
'wind_dir': ['Wind Direction', None], 'Observation Time', 'observation_time', "mdi:clock"),
'wind_gust_kph': ['Wind Gust', 'kph'], 'precip_1hr_in': WUCurrentConditionsSensorConfig(
'wind_gust_mph': ['Wind Gust', 'mph'], 'Precipitation 1hr', 'precip_1hr_in', "mdi:umbrella", LENGTH_INCHES),
'wind_kph': ['Wind Speed', 'kph'], 'precip_1hr_metric': WUCurrentConditionsSensorConfig(
'wind_mph': ['Wind Speed', 'mph'], 'Precipitation 1hr', 'precip_1hr_metric', "mdi:umbrella", 'mm'),
'wind_string': ['Wind Summary', None], '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 # Alert Attributes
@ -138,6 +650,20 @@ class WUndergroundSensor(Entity):
"""Initialize the sensor.""" """Initialize the sensor."""
self.rest = rest self.rest = rest
self._condition = condition 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 @property
def name(self): def name(self):
@ -147,68 +673,41 @@ class WUndergroundSensor(Entity):
@property @property
def state(self): def state(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
if self.rest.data: return self._cfg_expand("value", STATE_UNKNOWN)
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
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """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 attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
attrs[ATTR_FRIENDLY_NAME] = self._cfg_expand("friendly_name")
if not self.rest.alerts or self._condition != 'alerts':
return attrs return attrs
multiple_alerts = len(self.rest.alerts) > 1 @property
for data in self.rest.alerts: def icon(self):
for alert in ALERTS_ATTRS: """Return icon."""
if data[alert]: return self._cfg_expand("icon", super().icon)
if multiple_alerts:
dkey = alert.capitalize() + '_' + data['type']
else:
dkey = alert.capitalize()
attrs[dkey] = data[alert]
return attrs
@property @property
def entity_picture(self): def entity_picture(self):
"""Return the entity picture.""" """Return the entity picture."""
if self.rest.data and self._condition == 'weather': url = self._cfg_expand("entity_picture")
url = self.rest.data['icon_url'] if url is not None:
return re.sub(r'^http://', 'https://', url, flags=re.IGNORECASE) return re.sub(r'^http://', 'https://', url, flags=re.IGNORECASE)
@property @property
def unit_of_measurement(self): def unit_of_measurement(self):
"""Return the units of measurement.""" """Return the units of measurement."""
return SENSOR_TYPES[self._condition][1] return self._cfg_expand("unit_of_measurement")
def update(self): def update(self):
"""Update current conditions.""" """Update current conditions."""
if self._condition == 'alerts':
self.rest.update_alerts()
else:
self.rest.update() self.rest.update()
@ -223,11 +722,16 @@ class WUndergroundData(object):
self._lang = 'lang:{}'.format(lang) self._lang = 'lang:{}'.format(lang)
self._latitude = hass.config.latitude self._latitude = hass.config.latitude
self._longitude = hass.config.longitude self._longitude = hass.config.longitude
self._features = set()
self.data = None 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): 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: if self._pws_id:
url = url + 'pws:{}'.format(self._pws_id) url = url + 'pws:{}'.format(self._pws_id)
else: else:
@ -235,7 +739,7 @@ class WUndergroundData(object):
return url + '.json' return url + '.json'
@Throttle(MIN_TIME_BETWEEN_UPDATES_OBSERVATION) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
"""Get the latest data from WUnderground.""" """Get the latest data from WUnderground."""
try: try:
@ -244,21 +748,7 @@ class WUndergroundData(object):
raise ValueError(result['response']["error"] raise ValueError(result['response']["error"]
["description"]) ["description"])
else: else:
self.data = result["current_observation"] self.data = result
except ValueError as err: except ValueError as err:
_LOGGER.error("Check WUnderground API %s", err.args) _LOGGER.error("Check WUnderground API %s", err.args)
self.data = None 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 # homeassistant.components.lutron
https://github.com/thecynic/pylutron/archive/v0.1.0.zip#pylutron==0.1.0 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 # homeassistant.components.sensor.modem_callerid
https://github.com/vroomfonde1/basicmodem/archive/0.7.zip#basicmodem==0.7 https://github.com/vroomfonde1/basicmodem/archive/0.7.zip#basicmodem==0.7
@ -593,6 +590,9 @@ pymailgunner==1.4
# homeassistant.components.mochad # homeassistant.components.mochad
pymochad==0.1.1 pymochad==0.1.1
# homeassistant.components.mysensors
pymysensors==0.10.0
# homeassistant.components.device_tracker.netgear # homeassistant.components.device_tracker.netgear
pynetgear==0.3.3 pynetgear==0.3.3
@ -698,7 +698,7 @@ pytrackr==0.0.5
pytradfri==1.1 pytradfri==1.1
# homeassistant.components.device_tracker.unifi # homeassistant.components.device_tracker.unifi
pyunifi==2.0 pyunifi==2.12
# homeassistant.components.keyboard # homeassistant.components.keyboard
# pyuserinput==0.1.11 # pyuserinput==0.1.11

View file

@ -4,6 +4,7 @@ import unittest
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
import homeassistant.components.cover as cover import homeassistant.components.cover as cover
from homeassistant.components.cover.mqtt import MqttCover
from tests.common import ( from tests.common import (
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message) 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.assertEqual(('tilt-command-topic', 25, 0, False),
self.mock_publish.mock_calls[-2][1]) 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 import unittest
from homeassistant.components.sensor import wunderground 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 from tests.common import get_test_home_assistant
@ -19,7 +19,8 @@ VALID_CONFIG = {
'platform': 'wunderground', 'platform': 'wunderground',
'api_key': 'foo', 'api_key': 'foo',
'monitored_conditions': [ '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' WEATHER = 'Clear'
HTTPS_ICON_URL = 'https://icons.wxug.com/i/c/k/clear.gif' HTTPS_ICON_URL = 'https://icons.wxug.com/i/c/k/clear.gif'
ALERT_MESSAGE = 'This is a test alert message' ALERT_MESSAGE = 'This is a test alert message'
FORECAST_TEXT = 'Mostly Cloudy. Fog overnight.'
PRECIP_IN = 0.03
def mocked_requests_get(*args, **kwargs): def mocked_requests_get(*args, **kwargs):
@ -60,7 +63,9 @@ def mocked_requests_get(*args, **kwargs):
"termsofService": "termsofService":
"http://www.wunderground.com/weather/api/d/terms.html", "http://www.wunderground.com/weather/api/d/terms.html",
"features": { "features": {
"conditions": 1 "conditions": 1,
"alerts": 1,
"forecast": 1,
} }
}, "current_observation": { }, "current_observation": {
"image": { "image": {
@ -90,7 +95,58 @@ def mocked_requests_get(*args, **kwargs):
"message": ALERT_MESSAGE, "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) }, 200)
else: else:
return MockResponse({ return MockResponse({
@ -168,7 +224,13 @@ class TestWundergroundSetup(unittest.TestCase):
self.assertEqual('Holly Springs, NC', device.state) self.assertEqual('Holly Springs, NC', device.state)
elif device.name == 'PWS_elevation': elif device.name == 'PWS_elevation':
self.assertEqual('413', device.state) self.assertEqual('413', device.state)
else: elif device.name == 'PWS_feelslike_c':
self.assertIsNone(device.entity_picture) self.assertIsNone(device.entity_picture)
self.assertEqual(FEELS_LIKE, device.state) self.assertEqual(FEELS_LIKE, device.state)
self.assertEqual(TEMP_CELSIUS, device.unit_of_measurement) 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)