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_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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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__)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue