fixed merge conflict

This commit is contained in:
happyleavesaoc 2015-10-10 16:53:55 -04:00
commit 7ca21f577d
20 changed files with 257 additions and 263 deletions

View File

@ -66,6 +66,7 @@ omit =
homeassistant/components/notify/slack.py homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/xmpp.py homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/arest.py homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bitcoin.py

View File

@ -1,2 +1,5 @@
recursive-exclude tests * include README.md
recursive-include homeassistant services.yaml include LICENSE
graft homeassistant
prune homeassistant/components/frontend/www_static/home-assistant-polymer
recursive-exclude * *.py[co]

View File

@ -16,10 +16,12 @@ Check out [the website](https://home-assistant.io) for [a demo][demo], installat
Examples of devices it can interface it: Examples of devices it can interface it:
* Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) and any SNMP capable WAP/WRT * Monitoring connected devices to a wireless router: [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index), [TPLink](http://www.tp-link.us/), [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/) and any SNMP capable Linksys WAP/WRT
*
* [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Edimax](http://www.edimax.com/) switches, [Efergy](https://efergy.com) energy monitoring, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors * [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, [Edimax](http://www.edimax.com/) switches, [Efergy](https://efergy.com) energy monitoring, RFXtrx sensors, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors
* [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Kodi (XBMC)](http://kodi.tv/), iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api)), and Amazon Fire TV (by way of [python-firetv](https://github.com/happyleavesaoc/python-firetv)) * [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/), [Logitech Squeezebox](https://en.wikipedia.org/wiki/Squeezebox_%28network_music_player%29), [Plex](https://plex.tv/), [Kodi (XBMC)](http://kodi.tv/), iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api)), and Amazon Fire TV (by way of [python-firetv](https://github.com/happyleavesaoc/python-firetv))
* Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/) * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), [RFXtrx](http://www.rfxcom.com/), [Arduino](https://www.arduino.cc/), [Raspberry Pi](https://www.raspberrypi.org/), and [Modbus](http://www.modbus.org/)
* Interaction with [IFTTT](https://ifttt.com/)
* Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org). * Integrate data from the [Bitcoin](https://bitcoin.org) network, meteorological data from [OpenWeatherMap](http://openweathermap.org/) and [Forecast.io](https://forecast.io/), [Transmission](http://www.transmissionbt.com/), or [SABnzbd](http://sabnzbd.org).
* [See full list of supported devices](https://home-assistant.io/components/) * [See full list of supported devices](https://home-assistant.io/components/)
@ -29,8 +31,8 @@ Built home automation on top of your devices:
* Turn on the lights when people get home after sun set * Turn on the lights when people get home after sun set
* Turn on lights slowly during sun set to compensate for less light * Turn on lights slowly during sun set to compensate for less light
* Turn off all lights and devices when everybody leaves the house * Turn off all lights and devices when everybody leaves the house
* Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects * Offers a [REST API](https://home-assistant.io/developers/api.html) and can interface with MQTT for easy integration with other projects like [OwnTracks](http://owntracks.org/)
* Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), and [Jabber (XMPP)](http://xmpp.org) * Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), [Slack](https://slack.com/), [Telegram](https://telegram.org/), and [Jabber (XMPP)](http://xmpp.org)
The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html). The system is built modular so support for other devices or actions can be implemented easily. See also the [section on architecture](https://home-assistant.io/developers/architecture.html) and the [section on creating your own components](https://home-assistant.io/developers/creating_components.html).

View File

@ -114,6 +114,8 @@ def setup(hass, config):
os.remove(csv_path) os.remove(csv_path)
conf = config.get(DOMAIN, {}) conf = config.get(DOMAIN, {})
if isinstance(conf, list):
conf = conf[0]
consider_home = timedelta( consider_home = timedelta(
seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int, seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int,
DEFAULT_CONSIDER_HOME)) DEFAULT_CONSIDER_HOME))

View File

@ -46,6 +46,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}') _DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument # pylint: disable=unused-argument
@ -77,7 +78,7 @@ class DdWrtDeviceScanner(object):
self.last_results = {} self.last_results = {}
self.mac2name = None self.mac2name = {}
# Test the router is accessible # Test the router is accessible
url = 'http://{}/Status_Wireless.live.asp'.format(self.host) url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
@ -98,15 +99,18 @@ class DdWrtDeviceScanner(object):
with self.lock: with self.lock:
# if not initialised and not already scanned and not found # if not initialised and not already scanned and not found
if self.mac2name is None or device not in self.mac2name: if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host) url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url) data = self.get_ddwrt_data(url)
if not data: if not data:
return return None
dhcp_leases = data.get('dhcp_leases', None) dhcp_leases = data.get('dhcp_leases', None)
if dhcp_leases:
if not dhcp_leases:
return None
# remove leading and trailing single quotes # remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"') cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","') elements = cleaned_str.split('","')
@ -121,7 +125,7 @@ class DdWrtDeviceScanner(object):
mac = elements[mac_index] mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5] self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device, None) return self.mac2name.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS) @Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self): def _update_info(self):
@ -141,10 +145,12 @@ class DdWrtDeviceScanner(object):
if not data: if not data:
return False return False
if data:
self.last_results = [] self.last_results = []
active_clients = data.get('active_wireless', None) active_clients = data.get('active_wireless', None)
if active_clients: if not active_clients:
return False
# This is really lame, instead of using JSON the DD-WRT UI # This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then # uses its own data format for some reason and then
# regex's out values so I guess I have to do the same, # regex's out values so I guess I have to do the same,
@ -154,17 +160,11 @@ class DdWrtDeviceScanner(object):
clean_str = active_clients.strip().strip("'") clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','") elements = clean_str.split("','")
num_clients = int(len(elements)/9) self.last_results.extend(item for item in elements
for idx in range(0, num_clients): if _MAC_REGEX.match(item))
# get every 9th element which is the MAC address
index = idx * 9
if index < len(elements):
self.last_results.append(elements[index])
return True return True
return False
def get_ddwrt_data(self, url): def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """ """ Retrieve data from DD-WRT and return parsed result. """
try: try:

View File

@ -117,15 +117,18 @@ class NmapDeviceScanner(object):
scanner = PortScanner() scanner = PortScanner()
options = "-F --host-timeout 5" options = "-F --host-timeout 5"
exclude_targets = set()
if self.home_interval: if self.home_interval:
now = dt_util.now() boundary = dt_util.now() - self.home_interval
for host in self.last_results: last_results = [device for device in self.last_results
if host.last_update + self.home_interval > now: if device.last_update > boundary]
exclude_targets.add(host) if last_results:
if len(exclude_targets) > 0: # Pylint is confused here.
target_list = [t.ip for t in exclude_targets] # pylint: disable=no-member
options += " --exclude {}".format(",".join(target_list)) options += " --exclude {}".format(",".join(device.ip for device
in last_results))
else:
last_results = []
try: try:
result = scanner.scan(hosts=self.hosts, arguments=options) result = scanner.scan(hosts=self.hosts, arguments=options)
@ -133,18 +136,17 @@ class NmapDeviceScanner(object):
return False return False
now = dt_util.now() now = dt_util.now()
self.last_results = []
for ipv4, info in result['scan'].items(): for ipv4, info in result['scan'].items():
if info['status']['state'] != 'up': if info['status']['state'] != 'up':
continue continue
name = info['hostnames'][0] if info['hostnames'] else ipv4 name = info['hostnames'][0]['name'] if info['hostnames'] else ipv4
# Mac address only returned if nmap ran as root # Mac address only returned if nmap ran as root
mac = info['addresses'].get('mac') or _arp(ipv4) mac = info['addresses'].get('mac') or _arp(ipv4)
if mac is None: if mac is None:
continue continue
device = Device(mac.upper(), name, ipv4, now) last_results.append(Device(mac.upper(), name, ipv4, now))
self.last_results.append(device)
self.last_results.extend(exclude_targets) self.last_results = last_results
_LOGGER.info("nmap scan successful") _LOGGER.info("nmap scan successful")
return True return True

View File

@ -1,34 +1,11 @@
""" """
homeassistant.components.media_player.plex homeassistant.components.media_player.plex
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the Plex API.
Provides an interface to the Plex API For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.plex.html
Configuration:
To use Plex add something like this to your configuration:
media_player:
platform: plex
name: plex_server
user: plex
password: my_secure_password
Variables:
name
*Required
The name of the backend device (Under Plex Media Server > settings > server).
user
*Required
The Plex username
password
*Required
The Plex password
""" """
import logging import logging
from datetime import timedelta from datetime import timedelta
@ -49,10 +26,8 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
# pylint: disable=abstract-method
# pylint: disable=unused-argument
# pylint: disable=abstract-method, unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the plex platform. """ """ Sets up the plex platform. """
from plexapi.myplex import MyPlexUser from plexapi.myplex import MyPlexUser
@ -68,7 +43,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_devices(): def update_devices():
""" Updates the devices objects """ """ Updates the devices objects. """
try: try:
devices = plexuser.devices() devices = plexuser.devices()
except BadRequest: except BadRequest:
@ -94,7 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_sessions(): def update_sessions():
""" Updates the sessions objects """ """ Updates the sessions objects. """
try: try:
sessions = plexserver.sessions() sessions = plexserver.sessions()
except BadRequest: except BadRequest:
@ -113,7 +88,6 @@ class PlexClient(MediaPlayerDevice):
""" Represents a Plex device. """ """ Represents a Plex device. """
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods
def __init__(self, device, plex_sessions, update_devices, update_sessions): def __init__(self, device, plex_sessions, update_devices, update_sessions):
self.plex_sessions = plex_sessions self.plex_sessions = plex_sessions
self.update_devices = update_devices self.update_devices = update_devices
@ -121,12 +95,12 @@ class PlexClient(MediaPlayerDevice):
self.set_device(device) self.set_device(device)
def set_device(self, device): def set_device(self, device):
""" Sets the device property """ """ Sets the device property. """
self.device = device self.device = device
@property @property
def session(self): def session(self):
""" Returns the session, if any """ """ Returns the session, if any. """
if self.device.clientIdentifier not in self.plex_sessions: if self.device.clientIdentifier not in self.plex_sessions:
return None return None
@ -196,21 +170,21 @@ class PlexClient(MediaPlayerDevice):
@property @property
def media_season(self): def media_season(self):
""" Season of curent playing media. (TV Show only) """ """ Season of curent playing media (TV Show only). """
from plexapi.video import Show from plexapi.video import Show
if isinstance(self.session, Show): if isinstance(self.session, Show):
return self.session.seasons()[0].index return self.session.seasons()[0].index
@property @property
def media_series_title(self): def media_series_title(self):
""" Series title of current playing media. (TV Show only)""" """ Series title of current playing media (TV Show only). """
from plexapi.video import Show from plexapi.video import Show
if isinstance(self.session, Show): if isinstance(self.session, Show):
return self.session.grandparentTitle return self.session.grandparentTitle
@property @property
def media_episode(self): def media_episode(self):
""" Episode of current playing media. (TV Show only) """ """ Episode of current playing media (TV Show only). """
from plexapi.video import Show from plexapi.video import Show
if isinstance(self.session, Show): if isinstance(self.session, Show):
return self.session.index return self.session.index

View File

@ -0,0 +1,66 @@
"""
homeassistant.components.notify.telegram
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Telegram platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.telegram.html
"""
import logging
import urllib
from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
from homeassistant.const import CONF_API_KEY
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-telegram-bot==2.8.7']
def get_service(hass, config):
""" Get the Telegram notification service. """
if not validate_config(config,
{DOMAIN: [CONF_API_KEY, 'chat_id']},
_LOGGER):
return None
try:
import telegram
except ImportError:
_LOGGER.exception(
"Unable to import python-telegram-bot. "
"Did you maybe not install the 'python-telegram-bot' package?")
return None
try:
bot = telegram.Bot(token=config[DOMAIN][CONF_API_KEY])
username = bot.getMe()['username']
_LOGGER.info("Telegram bot is' %s'", username)
except urllib.error.HTTPError:
_LOGGER.error("Please check your access token.")
return None
return TelegramNotificationService(
config[DOMAIN][CONF_API_KEY],
config[DOMAIN]['chat_id'])
# pylint: disable=too-few-public-methods
class TelegramNotificationService(BaseNotificationService):
""" Implements notification service for Telegram. """
def __init__(self, api_key, chat_id):
import telegram
self._api_key = api_key
self._chat_id = chat_id
self.bot = telegram.Bot(token=self._api_key)
def send_message(self, message="", **kwargs):
""" Send a message to a user. """
title = kwargs.get(ATTR_TITLE)
self.bot.sendMessage(chat_id=self._chat_id,
text=title + " " + message)

View File

@ -1,20 +1,10 @@
""" """
homeassistant.components.rfxtrx homeassistant.components.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connects Home Assistant to a RFXtrx device. Provides support for RFXtrx components.
Configuration:
To use Rfxtrx device you will need to add the following to your
configuration.yaml file.
rfxtrx:
device: /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1YVC1P0-if00-port0
*Optional*
debug: True
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rfxtrx.html
""" """
import logging import logging
from homeassistant.util import slugify from homeassistant.util import slugify

View File

@ -3,51 +3,11 @@ homeassistant.components.sensor.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest sensor will consume an exposed aREST API of a device. The arest sensor will consume an exposed aREST API of a device.
Configuration: For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.arest.html
To use the arest sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arest
resource: http://IP_ADDRESS
monitored_variables:
- name: temperature
unit: '°C'
- name: humidity
unit: '%'
Variables:
resource:
*Required
IP address of the device that is exposing an aREST API.
These are the variables for the monitored_variables array:
name
*Required
The name of the variable you wish to monitor.
unit
*Optional
Defines the units of measurement of the sensor, if any.
Details for the API: http://arest.io
Format of a default JSON response by aREST:
{
"variables":{
"temperature":21,
"humidity":89
},
"id":"device008",
"name":"Bedroom",
"connected":true
}
""" """
import logging import logging
from requests import get, exceptions import requests
from datetime import timedelta from datetime import timedelta
from homeassistant.util import Throttle from homeassistant.util import Throttle
@ -58,36 +18,42 @@ _LOGGER = logging.getLogger(__name__)
# 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=60) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
CONF_RESOURCE = 'resource'
CONF_MONITORED_VARIABLES = 'monitored_variables'
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Get the aREST sensor. """ """ Get the aREST sensor. """
resource = config.get('resource', None) resource = config.get(CONF_RESOURCE)
var_conf = config.get(CONF_MONITORED_VARIABLES)
if None in (resource, var_conf):
_LOGGER.error('Not all required config keys present: %s',
', '.join((CONF_RESOURCE, CONF_MONITORED_VARIABLES)))
return False
try: try:
response = get(resource, timeout=10) response = requests.get(resource, timeout=10).json()
except exceptions.MissingSchema: except requests.exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. " _LOGGER.error("Missing resource or schema in configuration. "
"Add http:// to your URL.") "Add http:// to your URL.")
return False return False
except exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device. " _LOGGER.error("No route to device. "
"Please check the IP address in the configuration file.") "Please check the IP address in the configuration file.")
return False return False
rest = ArestData(resource) arest = ArestData(resource)
dev = [] dev = []
for variable in config['monitored_variables']: for variable in config['monitored_variables']:
if 'unit' not in variable: if variable['name'] not in response['variables']:
variable['unit'] = ' '
if variable['name'] not in response.json()['variables']:
_LOGGER.error('Variable: "%s" does not exist', variable['name']) _LOGGER.error('Variable: "%s" does not exist', variable['name'])
else: continue
dev.append(ArestSensor(rest,
response.json()['name'], dev.append(ArestSensor(arest, response['name'], variable['name'],
variable['name'], variable.get('unit')))
variable['unit']))
add_devices(dev) add_devices(dev)
@ -95,8 +61,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ArestSensor(Entity): class ArestSensor(Entity):
""" Implements an aREST sensor. """ """ Implements an aREST sensor. """
def __init__(self, rest, location, variable, unit_of_measurement): def __init__(self, arest, location, variable, unit_of_measurement):
self.rest = rest self.arest = arest
self._name = '{} {}'.format(location.title(), variable.title()) self._name = '{} {}'.format(location.title(), variable.title())
self._variable = variable self._variable = variable
self._state = 'n/a' self._state = 'n/a'
@ -116,17 +82,16 @@ class ArestSensor(Entity):
@property @property
def state(self): def state(self):
""" Returns the state of the device. """ """ Returns the state of the device. """
return self._state values = self.arest.data
def update(self):
""" Gets the latest data from aREST API and updates the state. """
self.rest.update()
values = self.rest.data
if 'error' in values: if 'error' in values:
self._state = values['error'] return values['error']
else: else:
self._state = values[self._variable] return values.get(self._variable, 'n/a')
def update(self):
""" Gets the latest data from aREST API. """
self.arest.update()
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
@ -135,16 +100,14 @@ class ArestData(object):
def __init__(self, resource): def __init__(self, resource):
self.resource = resource self.resource = resource
self.data = dict() self.data = {}
@Throttle(MIN_TIME_BETWEEN_UPDATES) @Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self): def update(self):
""" Gets the latest data from aREST device. """ """ Gets the latest data from aREST device. """
try: try:
response = get(self.resource, timeout=10) response = requests.get(self.resource, timeout=10)
if 'error' in self.data:
del self.data['error']
self.data = response.json()['variables'] self.data = response.json()['variables']
except exceptions.ConnectionError: except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device. Is device offline?") _LOGGER.error("No route to device. Is device offline?")
self.data['error'] = 'n/a' self.data = {'error': 'error fetching'}

View File

@ -3,38 +3,9 @@ homeassistant.components.switch.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest switch can control the digital pins of a device running with the The arest switch can control the digital pins of a device running with the
aREST RESTful framework for Arduino, the ESP8266, and the Raspberry Pi. aREST RESTful framework for Arduino, the ESP8266, and the Raspberry Pi.
Only tested with Arduino boards so far.
Configuration: For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.arest.html
To use the arest switch you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: arest
resource: http://IP_ADDRESS
pins:
11:
name: Fan Office
12:
name: Light Desk
Variables:
resource:
*Required
IP address of the device that is exposing an aREST API.
pins:
The number of the digital pin to switch.
These are the variables for the pins array:
name
*Required
The name for the pin that will be used in the frontend.
Details for the API: http://arest.io
""" """
import logging import logging
from requests import get, exceptions from requests import get, exceptions

View File

@ -1,7 +1,7 @@
# coding: utf-8 # coding: utf-8
""" Constants used by Home Assistant components. """ """ Constants used by Home Assistant components. """
__version__ = "0.7.5dev0" __version__ = "0.7.6.dev0"
# Can be used to specify a catch all when registering state or event listeners. # Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*' MATCH_ALL = '*'

View File

@ -59,14 +59,14 @@ def reproduce_state(hass, states, blocking=False):
state.entity_id) state.entity_id)
continue continue
if state.domain == 'media_player' and state.state == STATE_PAUSED: if state.domain == 'media_player' and state.attributes and \
service = SERVICE_MEDIA_PAUSE
elif state.domain == 'media_player' and state.state == STATE_PLAYING:
service = SERVICE_MEDIA_PLAY
elif state.domain == 'media_player' and state.attributes and \
'media_type' in state.attributes and \ 'media_type' in state.attributes and \
'media_id' in state.attributes: 'media_id' in state.attributes:
service = SERVICE_PLAY_MEDIA service = SERVICE_PLAY_MEDIA
elif state.domain == 'media_player' and state.state == STATE_PAUSED:
service = SERVICE_MEDIA_PAUSE
elif state.domain == 'media_player' and state.state == STATE_PLAYING:
service = SERVICE_MEDIA_PLAY
elif state.state == STATE_ON: elif state.state == STATE_ON:
service = SERVICE_TURN_ON service = SERVICE_TURN_ON
elif state.state == STATE_OFF: elif state.state == STATE_OFF:

View File

@ -233,35 +233,42 @@ class Throttle(object):
self.limit_no_throttle = limit_no_throttle self.limit_no_throttle = limit_no_throttle
def __call__(self, method): def __call__(self, method):
lock = threading.Lock()
if self.limit_no_throttle is not None: if self.limit_no_throttle is not None:
method = Throttle(self.limit_no_throttle)(method) method = Throttle(self.limit_no_throttle)(method)
# We want to be able to differentiate between function and method calls
# All methods have the classname in their qualname seperated by a '.'
# Functions have a '.' in their qualname if defined inline, but will
# be prefixed by '.<locals>.' so we strip that out.
is_func = '.' not in method.__qualname__.split('.<locals>.')[-1]
@wraps(method) @wraps(method)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
""" """
Wrapper that allows wrapped to be called only once per min_time. Wrapper that allows wrapped to be called only once per min_time.
If we cannot acquire the lock, it is running so return None. If we cannot acquire the lock, it is running so return None.
""" """
if not lock.acquire(False): # pylint: disable=protected-access
return None host = wrapper if is_func else args[0]
try: if not hasattr(host, '_throttle_lock'):
last_call = wrapper.last_call host._throttle_lock = threading.Lock()
if not host._throttle_lock.acquire(False):
return None
last_call = getattr(host, '_throttle_last_call', None)
# Check if method is never called or no_throttle is given # Check if method is never called or no_throttle is given
force = not last_call or kwargs.pop('no_throttle', False) force = not last_call or kwargs.pop('no_throttle', False)
try:
if force or utcnow() - last_call > self.min_time: if force or utcnow() - last_call > self.min_time:
result = method(*args, **kwargs) result = method(*args, **kwargs)
wrapper.last_call = utcnow() host._throttle_last_call = utcnow()
return result return result
else: else:
return None return None
finally: finally:
lock.release() host._throttle_lock.release()
wrapper.last_call = None
return wrapper return wrapper

View File

@ -10,77 +10,77 @@ vincenty==0.1.2
# Sun (sun) # Sun (sun)
astral==0.8.1 astral==0.8.1
# Philips Hue library (lights.hue) # Philips Hue (lights.hue)
phue==0.8 phue==0.8
# Limitlessled/Easybulb/Milight library (lights.limitlessled) # Limitlessled/Easybulb/Milight (lights.limitlessled)
ledcontroller==1.1.0 ledcontroller==1.1.0
# Chromecast bindings (media_player.cast) # Chromecast (media_player.cast)
pychromecast==0.6.12 pychromecast==0.6.12
# Keyboard (keyboard) # Keyboard (keyboard)
pyuserinput==0.1.9 pyuserinput==0.1.9
# Tellstick bindings (*.tellstick) # Tellstick (*.tellstick)
tellcore-py==1.1.2 tellcore-py==1.1.2
# Nmap bindings (device_tracker.nmap) # Nmap (device_tracker.nmap)
python-nmap==0.4.3 python-nmap==0.4.3
# PushBullet bindings (notify.pushbullet) # PushBullet (notify.pushbullet)
pushbullet.py==0.7.1 pushbullet.py==0.7.1
# Nest Thermostat bindings (thermostat.nest) # Nest Thermostat (thermostat.nest)
python-nest==2.6.0 python-nest==2.6.0
# Z-Wave (*.zwave) # Z-Wave (*.zwave)
pydispatcher==2.0.5 pydispatcher==2.0.5
# ISY994 bindings (*.isy994) # ISY994 (isy994)
PyISY==1.0.5 PyISY==1.0.5
# PSutil (sensor.systemmonitor) # PSutil (sensor.systemmonitor)
psutil==3.0.0 psutil==3.0.0
# Pushover bindings (notify.pushover) # Pushover (notify.pushover)
python-pushover==0.2 python-pushover==0.2
# Transmission Torrent Client (*.transmission) # Transmission Torrent Client (*.transmission)
transmissionrpc==0.11 transmissionrpc==0.11
# OpenWeatherMap Web API (sensor.openweathermap) # OpenWeatherMap (sensor.openweathermap)
pyowm==2.2.1 pyowm==2.2.1
# XMPP Bindings (notify.xmpp) # XMPP (notify.xmpp)
sleekxmpp==1.3.1 sleekxmpp==1.3.1
dnspython3==1.12.0 dnspython3==1.12.0
# Blockchain (sensor.bitcoin) # Blockchain (sensor.bitcoin)
blockchain==1.1.2 blockchain==1.1.2
# MPD Bindings (media_player.mpd) # Music Player Daemon (media_player.mpd)
python-mpd2==0.5.4 python-mpd2==0.5.4
# Hikvision (switch.hikvisioncam) # Hikvision (switch.hikvisioncam)
hikvision==0.4 hikvision==0.4
# console log coloring # Console log coloring
colorlog==2.6.0 colorlog==2.6.0
# JSON-RPC interface (media_player.kodi) # JSON-RPC interface (media_player.kodi)
jsonrpc-requests==0.1 jsonrpc-requests==0.1
# Forecast.io Bindings (sensor.forecast) # Forecast.io (sensor.forecast)
python-forecastio==1.3.3 python-forecastio==1.3.3
# Firmata Bindings (*.arduino) # Firmata (*.arduino)
PyMata==2.07a PyMata==2.07a
# Rfxtrx sensor (sensor.rfxtrx) # Rfxtrx (rfxtrx)
https://github.com/Danielhiversen/pyRFXtrx/archive/ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip#RFXtrx==0.15 https://github.com/Danielhiversen/pyRFXtrx/archive/ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip#RFXtrx==0.15
# Mysensors # Mysensors (sensor.mysensors)
https://github.com/theolind/pymysensors/archive/35b87d880147a34107da0d40cb815d75e6cb4af7.zip#pymysensors==0.2 https://github.com/theolind/pymysensors/archive/35b87d880147a34107da0d40cb815d75e6cb4af7.zip#pymysensors==0.2
# Netgear (device_tracker.netgear) # Netgear (device_tracker.netgear)
@ -101,18 +101,18 @@ slacker==0.6.8
# Temper sensors (sensor.temper) # Temper sensors (sensor.temper)
https://github.com/rkabadi/temper-python/archive/3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip#temperusb==1.2.3 https://github.com/rkabadi/temper-python/archive/3dbdaf2d87b8db9a3cd6e5585fc704537dd2d09b.zip#temperusb==1.2.3
# PyEdimax # PyEdimax (switch.edimax)
https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip#pyedimax==0.1 https://github.com/rkabadi/pyedimax/archive/365301ce3ff26129a7910c501ead09ea625f3700.zip#pyedimax==0.1
# RPI-GPIO platform (*.rpi_gpio) # RPI-GPIO platform (*.rpi_gpio)
# Uncomment for Raspberry Pi # Uncomment for Raspberry Pi
# RPi.GPIO==0.5.11 # RPi.GPIO==0.5.11
# Adafruit temperature/humidity sensor # Adafruit temperature/humidity sensor (sensor.dht)
# uncomment on a Raspberry Pi / Beaglebone # Uncomment on a Raspberry Pi / Beaglebone
# http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0 # http://github.com/mala-zaba/Adafruit_Python_DHT/archive/4101340de8d2457dd194bca1e8d11cbfc237e919.zip#Adafruit_DHT==1.1.0
# PAHO MQTT Binding (mqtt) # PAHO MQTT (mqtt)
paho-mqtt==1.1 paho-mqtt==1.1
# PyModbus (modbus) # PyModbus (modbus)
@ -121,25 +121,26 @@ https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b6
# Verisure (verisure) # Verisure (verisure)
https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6 https://github.com/persandstrom/python-verisure/archive/9873c4527f01b1ba1f72ae60f7f35854390d59be.zip#python-verisure==0.2.6
# Python tools for interacting with IFTTT Maker Channel (ifttt) # IFTTT Maker Channel (ifttt)
pyfttt==0.3 pyfttt==0.3
# sensor.sabnzbd # SABnzbd (sensor.sabnzbd)
https://github.com/balloob/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 https://github.com/balloob/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
# switch.vera # Vera (*.vera)
# sensor.vera
# light.vera
https://github.com/balloob/home-assistant-vera-api/archive/a8f823066ead6c7da6fb5e7abaf16fef62e63364.zip#python-vera==0.1 https://github.com/balloob/home-assistant-vera-api/archive/a8f823066ead6c7da6fb5e7abaf16fef62e63364.zip#python-vera==0.1
# Sonos bindings (media_player.sonos) # Sonos (media_player.sonos)
SoCo==0.11.1 SoCo==0.11.1
# PlexAPI (media_player.plex) # PlexAPI (media_player.plex)
https://github.com/adrienbrault/python-plexapi/archive/df2d0847e801d6d5cda920326d693cf75f304f1a.zip#python-plexapi==1.0.2 https://github.com/adrienbrault/python-plexapi/archive/df2d0847e801d6d5cda920326d693cf75f304f1a.zip#python-plexapi==1.0.2
# python-pysnmp (device_tracker.snmp) # SNMP (device_tracker.snmp)
pysnmp==4.2.5 pysnmp==4.2.5
# Blinkstick # Blinkstick (light.blinksticklight)
blinkstick==1.1.7 blinkstick==1.1.7
# Telegram (notify.telegram)
python-telegram-bot==2.8.7

View File

@ -9,11 +9,12 @@ DOWNLOAD_URL = ('https://github.com/balloob/home-assistant/archive/'
PACKAGES = find_packages(exclude=['tests', 'tests.*']) PACKAGES = find_packages(exclude=['tests', 'tests.*'])
PACKAGE_DATA = \ # PACKAGE_DATA = \
{'homeassistant.components.frontend': ['index.html.template'], # {'homeassistant.components.frontend': ['index.html.template'],
'homeassistant.components.frontend.www_static': ['*.*'], # 'homeassistant.components.frontend.www_static': ['*.*'],
'homeassistant.components.frontend.www_static.images': ['*.*'], # 'homeassistant.components.frontend.www_static.images': ['*.*'],
'homeassistant.startup': ['*.*']} # 'homeassistant.components.mqtt': ['*.crt'],
# 'homeassistant.startup': ['*.*']}
REQUIRES = [ REQUIRES = [
'requests>=2,<3', 'requests>=2,<3',
@ -23,6 +24,7 @@ REQUIRES = [
'vincenty==0.1.2' 'vincenty==0.1.2'
] ]
# package_data=PACKAGE_DATA,
setup( setup(
name=PACKAGE_NAME, name=PACKAGE_NAME,
version=__version__, version=__version__,
@ -34,7 +36,6 @@ setup(
description='Open-source home automation platform running on Python 3.', description='Open-source home automation platform running on Python 3.',
packages=PACKAGES, packages=PACKAGES,
include_package_data=True, include_package_data=True,
package_data=PACKAGE_DATA,
zip_safe=False, zip_safe=False,
platforms='any', platforms='any',
install_requires=REQUIRES, install_requires=REQUIRES,

View File

@ -218,3 +218,14 @@ class TestUtil(unittest.TestCase):
self.assertEqual(3, len(calls1)) self.assertEqual(3, len(calls1))
self.assertEqual(2, len(calls2)) self.assertEqual(2, len(calls2))
def test_throttle_per_instance(self):
""" Test that the throttle method is done per instance of a class. """
class Tester(object):
@util.Throttle(timedelta(seconds=1))
def hello(self):
return True
self.assertTrue(Tester().hello())
self.assertTrue(Tester().hello())