commit
5d13f36a4b
8 changed files with 794 additions and 132 deletions
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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__)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue