Add voluptuous to ecobee, speedtest.net, fast.com, actiontec, forecast.io (#2872)

* add voluptuous

* fixes for comments

* str to cv.string
This commit is contained in:
Nolan Gilley 2016-08-21 13:29:13 -04:00 committed by Paulus Schoutsen
parent fa3d83118a
commit 635e5c8eba
5 changed files with 201 additions and 155 deletions

View File

@ -1,126 +1,129 @@
""" """
Support for Actiontec MI424WR (Verizon FIOS) routers. Support for Actiontec MI424WR (Verizon FIOS) routers.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.actiontec/ https://home-assistant.io/components/device_tracker.actiontec/
""" """
import logging import logging
import re import re
import telnetlib import telnetlib
import threading import threading
from collections import namedtuple from collections import namedtuple
from datetime import timedelta from datetime import timedelta
import voluptuous as vol
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME import homeassistant.util.dt as dt_util
from homeassistant.helpers import validate_config from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA)
from homeassistant.util import Throttle from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
_LOGGER = logging.getLogger(__name__)
_LEASES_REGEX = re.compile(
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})' + _LEASES_REGEX = re.compile(
r'\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))' + r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})' +
r'\svalid\sfor:\s(?P<timevalid>(-?\d+))' + r'\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))' +
r'\ssec') r'\svalid\sfor:\s(?P<timevalid>(-?\d+))' +
r'\ssec')
# pylint: disable=unused-argument PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_scanner(hass, config): vol.Required(CONF_HOST): cv.string,
"""Validate the configuration and return an Actiontec scanner.""" vol.Required(CONF_PASSWORD): cv.string,
if not validate_config(config, vol.Required(CONF_USERNAME): cv.string
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, })
_LOGGER):
return None
scanner = ActiontecDeviceScanner(config[DOMAIN]) # pylint: disable=unused-argument
return scanner if scanner.success_init else None def get_scanner(hass, config):
"""Validate the configuration and return an Actiontec scanner."""
Device = namedtuple("Device", ["mac", "ip", "last_update"]) scanner = ActiontecDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class ActiontecDeviceScanner(object): Device = namedtuple("Device", ["mac", "ip", "last_update"])
"""This class queries a an actiontec router for connected devices."""
def __init__(self, config): class ActiontecDeviceScanner(object):
"""Initialize the scanner.""" """This class queries a an actiontec router for connected devices."""
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME] def __init__(self, config):
self.password = config[CONF_PASSWORD] """Initialize the scanner."""
self.lock = threading.Lock() self.host = config[CONF_HOST]
self.last_results = [] self.username = config[CONF_USERNAME]
data = self.get_actiontec_data() self.password = config[CONF_PASSWORD]
self.success_init = data is not None self.lock = threading.Lock()
_LOGGER.info("actiontec scanner initialized") self.last_results = []
data = self.get_actiontec_data()
def scan_devices(self): self.success_init = data is not None
"""Scan for new devices and return a list with found device IDs.""" _LOGGER.info("actiontec scanner initialized")
self._update_info()
return [client.mac for client in self.last_results] def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
def get_device_name(self, device): self._update_info()
"""Return the name of the given device or None if we don't know.""" return [client.mac for client in self.last_results]
if not self.last_results:
return None def get_device_name(self, device):
for client in self.last_results: """Return the name of the given device or None if we don't know."""
if client.mac == device: if not self.last_results:
return client.ip return None
return None for client in self.last_results:
if client.mac == device:
@Throttle(MIN_TIME_BETWEEN_SCANS) return client.ip
def _update_info(self): return None
"""Ensure the information from the router is up to date.
@Throttle(MIN_TIME_BETWEEN_SCANS)
Return boolean if scanning successful. def _update_info(self):
""" """Ensure the information from the router is up to date.
_LOGGER.info("Scanning")
if not self.success_init: Return boolean if scanning successful.
return False """
_LOGGER.info("Scanning")
with self.lock: if not self.success_init:
now = dt_util.now() return False
actiontec_data = self.get_actiontec_data()
if not actiontec_data: with self.lock:
return False now = dt_util.now()
self.last_results = [Device(data['mac'], name, now) actiontec_data = self.get_actiontec_data()
for name, data in actiontec_data.items() if not actiontec_data:
if data['timevalid'] > -60] return False
_LOGGER.info("actiontec scan successful") self.last_results = [Device(data['mac'], name, now)
return True for name, data in actiontec_data.items()
if data['timevalid'] > -60]
def get_actiontec_data(self): _LOGGER.info("actiontec scan successful")
"""Retrieve data from Actiontec MI424WR and return parsed result.""" return True
try:
telnet = telnetlib.Telnet(self.host) def get_actiontec_data(self):
telnet.read_until(b'Username: ') """Retrieve data from Actiontec MI424WR and return parsed result."""
telnet.write((self.username + '\n').encode('ascii')) try:
telnet.read_until(b'Password: ') telnet = telnetlib.Telnet(self.host)
telnet.write((self.password + '\n').encode('ascii')) telnet.read_until(b'Username: ')
prompt = telnet.read_until( telnet.write((self.username + '\n').encode('ascii'))
b'Wireless Broadband Router> ').split(b'\n')[-1] telnet.read_until(b'Password: ')
telnet.write('firewall mac_cache_dump\n'.encode('ascii')) telnet.write((self.password + '\n').encode('ascii'))
telnet.write('\n'.encode('ascii')) prompt = telnet.read_until(
telnet.read_until(prompt) b'Wireless Broadband Router> ').split(b'\n')[-1]
leases_result = telnet.read_until(prompt).split(b'\n')[1:-1] telnet.write('firewall mac_cache_dump\n'.encode('ascii'))
telnet.write('exit\n'.encode('ascii')) telnet.write('\n'.encode('ascii'))
except EOFError: telnet.read_until(prompt)
_LOGGER.exception("Unexpected response from router") leases_result = telnet.read_until(prompt).split(b'\n')[1:-1]
return telnet.write('exit\n'.encode('ascii'))
except ConnectionRefusedError: except EOFError:
_LOGGER.exception("Connection refused by router," + _LOGGER.exception("Unexpected response from router")
" is telnet enabled?") return
return None except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," +
devices = {} " is telnet enabled?")
for lease in leases_result: return None
match = _LEASES_REGEX.search(lease.decode('utf-8'))
if match is not None: devices = {}
devices[match.group('ip')] = { for lease in leases_result:
'ip': match.group('ip'), match = _LEASES_REGEX.search(lease.decode('utf-8'))
'mac': match.group('mac').upper(), if match is not None:
'timevalid': int(match.group('timevalid')) devices[match.group('ip')] = {
} 'ip': match.group('ip'),
return devices 'mac': match.group('mac').upper(),
'timevalid': int(match.group('timevalid'))
}
return devices

View File

@ -7,7 +7,9 @@ https://home-assistant.io/components/ecobee/
import logging import logging
import os import os
from datetime import timedelta from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.const import CONF_API_KEY from homeassistant.const import CONF_API_KEY
from homeassistant.loader import get_component from homeassistant.loader import get_component
@ -15,12 +17,19 @@ from homeassistant.util import Throttle
DOMAIN = "ecobee" DOMAIN = "ecobee"
NETWORK = None NETWORK = None
HOLD_TEMP = 'hold_temp' CONF_HOLD_TEMP = 'hold_temp'
REQUIREMENTS = [ REQUIREMENTS = [
'https://github.com/nkgilley/python-ecobee-api/archive/' 'https://github.com/nkgilley/python-ecobee-api/archive/'
'4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6'] '4856a704670c53afe1882178a89c209b5f98533d.zip#python-ecobee==0.0.6']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_API_KEY): cv.string,
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean
})
}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ECOBEE_CONFIG_FILE = 'ecobee.conf' ECOBEE_CONFIG_FILE = 'ecobee.conf'
@ -67,7 +76,7 @@ def setup_ecobee(hass, network, config):
configurator = get_component('configurator') configurator = get_component('configurator')
configurator.request_done(_CONFIGURING.pop('ecobee')) configurator.request_done(_CONFIGURING.pop('ecobee'))
hold_temp = config[DOMAIN].get(HOLD_TEMP, False) hold_temp = config[DOMAIN].get(CONF_HOLD_TEMP)
discovery.load_platform(hass, 'climate', DOMAIN, discovery.load_platform(hass, 'climate', DOMAIN,
{'hold_temp': hold_temp}, config) {'hold_temp': hold_temp}, config)

View File

@ -5,10 +5,12 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.fastdotcom/ https://home-assistant.io/components/sensor.fastdotcom/
""" """
import logging import logging
import voluptuous as vol
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
from homeassistant.components import recorder from homeassistant.components import recorder
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import (DOMAIN, PLATFORM_SCHEMA)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_change from homeassistant.helpers.event import track_time_change
@ -22,6 +24,17 @@ CONF_MINUTE = 'minute'
CONF_HOUR = 'hour' CONF_HOUR = 'hour'
CONF_DAY = 'day' CONF_DAY = 'day'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SECOND, default=[0]):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]),
vol.Optional(CONF_MINUTE, default=[0]):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]),
vol.Optional(CONF_HOUR):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 23))]),
vol.Optional(CONF_DAY):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(1, 31))]),
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Fast.com sensor.""" """Setup the Fast.com sensor."""
@ -43,10 +56,10 @@ class SpeedtestSensor(Entity):
def __init__(self, speedtest_data): def __init__(self, speedtest_data):
"""Initialize the sensor.""" """Initialize the sensor."""
self._name = 'Fast.com Speedtest' self._name = 'Fast.com Download'
self.speedtest_client = speedtest_data self.speedtest_client = speedtest_data
self._state = None self._state = None
self._unit_of_measurement = 'Mbps' self._unit_of_measurement = 'Mbit/s'
@property @property
def name(self): def name(self):
@ -94,10 +107,10 @@ class SpeedtestData(object):
"""Initialize the data object.""" """Initialize the data object."""
self.data = None self.data = None
track_time_change(hass, self.update, track_time_change(hass, self.update,
second=config.get(CONF_SECOND, 0), second=config.get(CONF_SECOND),
minute=config.get(CONF_MINUTE, 0), minute=config.get(CONF_MINUTE),
hour=config.get(CONF_HOUR, None), hour=config.get(CONF_HOUR),
day=config.get(CONF_DAY, None)) day=config.get(CONF_DAY))
def update(self, now): def update(self, now):
"""Get the latest data from fast.com.""" """Get the latest data from fast.com."""

View File

@ -6,12 +6,14 @@ https://home-assistant.io/components/sensor.forecast/
""" """
import logging import logging
from datetime import timedelta from datetime import timedelta
import voluptuous as vol
from requests.exceptions import ConnectionError as ConnectError, \ from requests.exceptions import ConnectionError as ConnectError, \
HTTPError, Timeout HTTPError, Timeout
from homeassistant.components.sensor import DOMAIN import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_API_KEY from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers import validate_config from homeassistant.const import (CONF_API_KEY, CONF_NAME,
CONF_MONITORED_CONDITIONS)
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -56,6 +58,16 @@ SENSOR_TYPES = {
'mm', 'in', 'mm', 'mm', 'mm'], 'mm', 'in', 'mm', 'mm', 'mm'],
} }
DEFAULT_NAME = "Forecast.io" DEFAULT_NAME = "Forecast.io"
CONF_UNITS = 'units'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES))]),
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2'])
})
# Return cached results if last scan was less then this time ago. # Return cached results if last scan was less then this time ago.
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120)
@ -67,12 +79,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if None in (hass.config.latitude, hass.config.longitude): if None in (hass.config.latitude, hass.config.longitude):
_LOGGER.error("Latitude or longitude not set in Home Assistant config") _LOGGER.error("Latitude or longitude not set in Home Assistant config")
return False return False
elif not validate_config({DOMAIN: config},
{DOMAIN: [CONF_API_KEY]}, _LOGGER):
return False
if 'units' in config: if CONF_UNITS in config:
units = config['units'] units = config[CONF_UNITS]
elif hass.config.units.is_metric: elif hass.config.units.is_metric:
units = 'si' units = 'si'
else: else:
@ -89,15 +98,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error(error) _LOGGER.error(error)
return False return False
name = config.get('name', DEFAULT_NAME) name = config.get(CONF_NAME)
# Initialize and add all of the sensors. # Initialize and add all of the sensors.
sensors = [] sensors = []
for variable in config['monitored_conditions']: for variable in config[CONF_MONITORED_CONDITIONS]:
if variable in SENSOR_TYPES: sensors.append(ForeCastSensor(forecast_data, variable, name))
sensors.append(ForeCastSensor(forecast_data, variable, name))
else:
_LOGGER.error('Sensor type: "%s" does not exist', variable)
add_devices(sensors) add_devices(sensors)

View File

@ -8,10 +8,13 @@ import logging
import re import re
import sys import sys
from subprocess import check_output, CalledProcessError from subprocess import check_output, CalledProcessError
import voluptuous as vol
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
from homeassistant.components import recorder from homeassistant.components import recorder
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import (DOMAIN, PLATFORM_SCHEMA)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import track_time_change from homeassistant.helpers.event import track_time_change
@ -22,7 +25,6 @@ _SPEEDTEST_REGEX = re.compile(r'Ping:\s(\d+\.\d+)\sms[\r\n]+'
r'Download:\s(\d+\.\d+)\sMbit/s[\r\n]+' r'Download:\s(\d+\.\d+)\sMbit/s[\r\n]+'
r'Upload:\s(\d+\.\d+)\sMbit/s[\r\n]+') r'Upload:\s(\d+\.\d+)\sMbit/s[\r\n]+')
CONF_MONITORED_CONDITIONS = 'monitored_conditions'
CONF_SECOND = 'second' CONF_SECOND = 'second'
CONF_MINUTE = 'minute' CONF_MINUTE = 'minute'
CONF_HOUR = 'hour' CONF_HOUR = 'hour'
@ -33,6 +35,19 @@ SENSOR_TYPES = {
'upload': ['Upload', 'Mbit/s'], 'upload': ['Upload', 'Mbit/s'],
} }
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS):
vol.All(cv.ensure_list, [vol.In(list(SENSOR_TYPES.keys()))]),
vol.Optional(CONF_SECOND, default=[0]):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]),
vol.Optional(CONF_MINUTE, default=[0]):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 59))]),
vol.Optional(CONF_HOUR):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(0, 23))]),
vol.Optional(CONF_DAY):
vol.All(cv.ensure_list, [vol.All(vol.Coerce(int), vol.Range(1, 31))]),
})
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Speedtest sensor.""" """Setup the Speedtest sensor."""
@ -117,10 +132,10 @@ class SpeedtestData(object):
"""Initialize the data object.""" """Initialize the data object."""
self.data = None self.data = None
track_time_change(hass, self.update, track_time_change(hass, self.update,
second=config.get(CONF_SECOND, 0), second=config.get(CONF_SECOND),
minute=config.get(CONF_MINUTE, 0), minute=config.get(CONF_MINUTE),
hour=config.get(CONF_HOUR, None), hour=config.get(CONF_HOUR),
day=config.get(CONF_DAY, None)) day=config.get(CONF_DAY))
def update(self, now): def update(self, now):
"""Get the latest data from speedtest.net.""" """Get the latest data from speedtest.net."""