Merge pull request #506 from balloob/dev

0.7.5rc1
This commit is contained in:
Paulus Schoutsen 2015-10-10 11:45:06 -07:00
commit 853a9fd4cd
36 changed files with 924 additions and 297 deletions

View file

@ -26,6 +26,9 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/ifttt.py
homeassistant/components/browser.py
homeassistant/components/camera/*
@ -39,11 +42,13 @@ omit =
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/keyboard.py
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/itunes.py
@ -60,6 +65,7 @@ omit =
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
@ -71,7 +77,6 @@ omit =
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rfxtrx.py
homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py

View file

@ -1,2 +1,5 @@
recursive-exclude tests *
recursive-include homeassistant services.yaml
include README.md
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:
* 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/), and [ASUSWRT](http://event.asus.com/2013/nw/ASUSWRT/)
* 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
* [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/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
* 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/)
* [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/), and iTunes (by way of [itunes-api](https://github.com/maddox/itunes-api))
* 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).
* [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 lights slowly during sun set to compensate for less light
* 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
* 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)
* 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/), [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).

View file

@ -134,6 +134,9 @@ automation:
service: light.turn_off
entity_id: group.all_lights
# Sensors need to be added into the configuration.yaml as sensor:, sensor 2:, sensor 3:, etc.
# Each sensor label should be unique or your sensors might not load correctly.
sensor:
platform: systemmonitor
resources:

View file

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

View file

@ -46,6 +46,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
_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
@ -77,7 +78,7 @@ class DdWrtDeviceScanner(object):
self.last_results = {}
self.mac2name = None
self.mac2name = {}
# Test the router is accessible
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
@ -98,30 +99,33 @@ class DdWrtDeviceScanner(object):
with self.lock:
# 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)
data = self.get_ddwrt_data(url)
if not data:
return
return None
dhcp_leases = data.get('dhcp_leases', None)
if dhcp_leases:
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device, None)
if not dhcp_leases:
return None
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
@ -141,29 +145,25 @@ class DdWrtDeviceScanner(object):
if not data:
return False
if data:
self.last_results = []
active_clients = data.get('active_wireless', None)
if active_clients:
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
self.last_results = []
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
active_clients = data.get('active_wireless', None)
if not active_clients:
return False
num_clients = int(len(elements)/9)
for idx in range(0, num_clients):
# get every 9th element which is the MAC address
index = idx * 9
if index < len(elements):
self.last_results.append(elements[index])
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
return True
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
return False
self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item))
return True
def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """

View file

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

View file

@ -0,0 +1,119 @@
"""
homeassistant.components.device_tracker.snmp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports fetching WiFi associations
through SNMP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.snmp.html
"""
import logging
from datetime import timedelta
import threading
import binascii
from homeassistant.const import CONF_HOST
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
from homeassistant.components.device_tracker import DOMAIN
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.2.5']
CONF_COMMUNITY = "community"
CONF_BASEOID = "baseoid"
# pylint: disable=unused-argument
def get_scanner(hass, config):
""" Validates config and returns an snmp scanner """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_COMMUNITY, CONF_BASEOID]},
_LOGGER):
return None
scanner = SnmpScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class SnmpScanner(object):
"""
This class queries any SNMP capable Acces Point for connected devices.
"""
def __init__(self, config):
self.host = config[CONF_HOST]
self.community = config[CONF_COMMUNITY]
self.baseoid = config[CONF_BASEOID]
self.lock = threading.Lock()
self.last_results = []
# Test the router is accessible
data = self.get_snmp_data()
self.success_init = data is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device IDs.
"""
self._update_info()
return [client['mac'] for client in self.last_results]
# Supressing no-self-use warning
# pylint: disable=R0201
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
# We have no names
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the WAP is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
data = self.get_snmp_data()
if not data:
return False
self.last_results = data
return True
def get_snmp_data(self):
""" Fetch mac addresses from WAP via SNMP. """
from pysnmp.entity.rfc3413.oneliner import cmdgen
devices = []
snmp = cmdgen.CommandGenerator()
errindication, errstatus, errindex, restable = snmp.nextCmd(
cmdgen.CommunityData(self.community),
cmdgen.UdpTransportTarget((self.host, 161)),
cmdgen.MibVariable(self.baseoid)
)
if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)
return
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1]
or '?')
return
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})
return devices

View file

@ -11,6 +11,7 @@ import logging
from . import version
import homeassistant.util as util
from homeassistant.const import URL_ROOT, HTTP_OK
from homeassistant.config import get_default_config_dir
DOMAIN = 'frontend'
DEPENDENCIES = ['api']
@ -19,7 +20,6 @@ INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template')
_LOGGER = logging.getLogger(__name__)
FRONTEND_URLS = [
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
'/devEvent']
@ -44,6 +44,9 @@ def setup(hass, config):
hass.http.register_path(
'HEAD', re.compile(r'/static/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_static, False)
hass.http.register_path(
'GET', re.compile(r'/local/(?P<file>[a-zA-Z\._\-0-9/]+)'),
_handle_get_local, False)
return True
@ -84,3 +87,16 @@ def _handle_get_static(handler, path_match, data):
path = os.path.join(os.path.dirname(__file__), 'www_static', req_file)
handler.write_file(path)
def _handle_get_local(handler, path_match, data):
"""
Returns a static file from the hass.config.path/www for the frontend.
"""
req_file = util.sanitize_path(path_match.group('file'))
path = os.path.join(get_default_config_dir(), 'www', req_file)
if not os.path.isfile(path):
return False
handler.write_file(path)

@ -1 +1 @@
Subproject commit c8d99bc3ea21cdd7bfb39e7700f92ed09f4b9efd
Subproject commit 6989009b2d59e39fd39b3025ff5899877f618bd3

View file

@ -246,6 +246,7 @@ def setup(hass, config):
rgb_color = dat.get(ATTR_RGB_COLOR)
if len(rgb_color) == 3:
params[ATTR_RGB_COLOR] = [int(val) for val in rgb_color]
params[ATTR_XY_COLOR] = \
color_util.color_RGB_to_xy(int(rgb_color[0]),
int(rgb_color[1]),

View file

@ -0,0 +1,76 @@
"""
homeassistant.components.light.blinksticklight
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Blinkstick lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.blinksticklight.html
"""
import logging
from blinkstick import blinkstick
from homeassistant.components.light import (Light, ATTR_RGB_COLOR)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ["blinkstick==1.1.7"]
DEPENDENCIES = []
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add device specified by serial number. """
stick = blinkstick.find_by_serial(config['serial'])
add_devices_callback([BlinkStickLight(stick, config['name'])])
class BlinkStickLight(Light):
""" Represents a BlinkStick light. """
def __init__(self, stick, name):
self._stick = stick
self._name = name
self._serial = stick.get_serial()
self._rgb_color = stick.get_color()
@property
def should_poll(self):
""" Polling needed. """
return True
@property
def name(self):
""" The name of the light. """
return self._name
@property
def rgb_color(self):
""" Read back the color of the light. """
return self._rgb_color
@property
def is_on(self):
""" Check whether any of the LEDs colors are non-zero. """
return sum(self._rgb_color) > 0
def update(self):
""" Read back the device state """
self._rgb_color = self._stick.get_color()
def turn_on(self, **kwargs):
""" Turn the device on. """
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
else:
self._rgb_color = [255, 255, 255]
self._stick.set_color(red=self._rgb_color[0],
green=self._rgb_color[1],
blue=self._rgb_color[2])
def turn_off(self, **kwargs):
""" Turn the device off """
self._stick.turn_off()

View file

@ -0,0 +1,112 @@
"""
homeassistant.components.light.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for RFXtrx lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.rfxtrx.html
"""
import logging
import homeassistant.components.rfxtrx as rfxtrx
import RFXtrx as rfxtrxmod
from homeassistant.components.light import Light
from homeassistant.util import slugify
DEPENDENCIES = ['rfxtrx']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Setup the RFXtrx platform. """
lights = []
devices = config.get('devices', None)
if devices:
for entity_id, entity_info in devices.items():
if entity_id not in rfxtrx.RFX_DEVICES:
_LOGGER.info("Add %s rfxtrx.light", entity_info['name'])
rfxobject = rfxtrx.get_rfx_object(entity_info['packetid'])
new_light = RfxtrxLight(entity_info['name'], rfxobject, False)
rfxtrx.RFX_DEVICES[entity_id] = new_light
lights.append(new_light)
add_devices_callback(lights)
def light_update(event):
""" Callback for light updates from the RFXtrx gateway. """
if not isinstance(event.device, rfxtrxmod.LightingDevice):
return
# Add entity if not exist and the automatic_add is True
entity_id = slugify(event.device.id_string.lower())
if entity_id not in rfxtrx.RFX_DEVICES:
automatic_add = config.get('automatic_add', False)
if not automatic_add:
return
_LOGGER.info(
"Automatic add %s rfxtrx.light (Class: %s Sub: %s)",
entity_id,
event.device.__class__.__name__,
event.device.subtype
)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (entity_id, pkt_id)
new_light = RfxtrxLight(entity_name, event, False)
rfxtrx.RFX_DEVICES[entity_id] = new_light
add_devices_callback([new_light])
# Check if entity exists or previously added automatically
if entity_id in rfxtrx.RFX_DEVICES:
if event.values['Command'] == 'On'\
or event.values['Command'] == 'Off':
if event.values['Command'] == 'On':
rfxtrx.RFX_DEVICES[entity_id].turn_on()
else:
rfxtrx.RFX_DEVICES[entity_id].turn_off()
# Subscribe to main rfxtrx events
if light_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(light_update)
class RfxtrxLight(Light):
""" Provides a RFXtrx light. """
def __init__(self, name, event, state):
self._name = name
self._event = event
self._state = state
@property
def should_poll(self):
""" No polling needed for a light. """
return False
@property
def name(self):
""" Returns the name of the light if any. """
return self._name
@property
def is_on(self):
""" True if light is on. """
return self._state
def turn_on(self, **kwargs):
""" Turn the light on. """
if hasattr(self, '_event') and self._event:
self._event.device.send_on(rfxtrx.RFXOBJECT.transport)
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the light off. """
if hasattr(self, '_event') and self._event:
self._event.device.send_off(rfxtrx.RFXOBJECT.transport)
self._state = False
self.update_ha_state()

View file

@ -31,6 +31,7 @@ DISCOVERY_PLATFORMS = {
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
SERVICE_PLAY_MEDIA = 'play_media'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
ATTR_MEDIA_VOLUME_MUTED = 'is_volume_muted'
@ -46,6 +47,8 @@ ATTR_MEDIA_TRACK = 'media_track'
ATTR_MEDIA_SERIES_TITLE = 'media_series_title'
ATTR_MEDIA_SEASON = 'media_season'
ATTR_MEDIA_EPISODE = 'media_episode'
ATTR_MEDIA_CHANNEL = 'media_channel'
ATTR_MEDIA_PLAYLIST = 'media_playlist'
ATTR_APP_ID = 'app_id'
ATTR_APP_NAME = 'app_name'
ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
@ -53,6 +56,9 @@ ATTR_SUPPORTED_MEDIA_COMMANDS = 'supported_media_commands'
MEDIA_TYPE_MUSIC = 'music'
MEDIA_TYPE_TVSHOW = 'tvshow'
MEDIA_TYPE_VIDEO = 'movie'
MEDIA_TYPE_EPISODE = 'episode'
MEDIA_TYPE_CHANNEL = 'channel'
MEDIA_TYPE_PLAYLIST = 'playlist'
SUPPORT_PAUSE = 1
SUPPORT_SEEK = 2
@ -63,6 +69,7 @@ SUPPORT_NEXT_TRACK = 32
SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
@ -76,6 +83,7 @@ SERVICE_TO_METHOD = {
SERVICE_MEDIA_PAUSE: 'media_pause',
SERVICE_MEDIA_NEXT_TRACK: 'media_next_track',
SERVICE_MEDIA_PREVIOUS_TRACK: 'media_previous_track',
SERVICE_PLAY_MEDIA: 'play_media',
}
ATTR_TO_PROPERTY = [
@ -92,6 +100,8 @@ ATTR_TO_PROPERTY = [
ATTR_MEDIA_SERIES_TITLE,
ATTR_MEDIA_SEASON,
ATTR_MEDIA_EPISODE,
ATTR_MEDIA_CHANNEL,
ATTR_MEDIA_PLAYLIST,
ATTR_APP_ID,
ATTR_APP_NAME,
ATTR_SUPPORTED_MEDIA_COMMANDS,
@ -180,6 +190,16 @@ def media_previous_track(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
def play_media(hass, media_type, media_id, entity_id=None):
""" Send the media player the command for playing media. """
data = {"media_type": media_type, "media_id": media_id}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_PLAY_MEDIA, data)
def setup(hass, config):
""" Track states and offer events for media_players. """
component = EntityComponent(
@ -275,6 +295,23 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
def play_media_service(service):
""" Plays specified media_id on the media player. """
media_type = service.data.get('media_type')
media_id = service.data.get('media_id')
if media_type is None:
return
if media_id is None:
return
for player in component.extract_from_service(service):
player.play_media(media_type, media_id)
if player.should_poll:
player.update_ha_state(True)
hass.services.register(
DOMAIN, "start_fireplace",
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"),
@ -289,6 +326,10 @@ def setup(hass, config):
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service,
descriptions.get(SERVICE_YOUTUBE_VIDEO))
hass.services.register(
DOMAIN, SERVICE_PLAY_MEDIA, play_media_service,
descriptions.get(SERVICE_PLAY_MEDIA))
return True
@ -373,6 +414,16 @@ class MediaPlayerDevice(Entity):
""" Episode of current playing media. (TV Show only) """
return None
@property
def media_channel(self):
""" Channel currently playing. """
return None
@property
def media_playlist(self):
""" Title of Playlist currently playing. """
return None
@property
def app_id(self):
""" ID of the current running app. """
@ -433,6 +484,10 @@ class MediaPlayerDevice(Entity):
""" Plays a YouTube media. """
raise NotImplementedError()
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
raise NotImplementedError()
# No need to overwrite these.
@property
def support_pause(self):
@ -469,6 +524,11 @@ class MediaPlayerDevice(Entity):
""" Boolean if YouTube is supported. """
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
@property
def support_play_media(self):
""" Boolean if play media command supported. """
return bool(self.supported_media_commands & SUPPORT_PLAY_MEDIA)
def volume_up(self):
""" volume_up media player. """
if self.volume_level < 1:

View file

@ -90,6 +90,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class CastDevice(MediaPlayerDevice):
""" Represents a Cast device on the network. """
# pylint: disable=abstract-method
# pylint: disable=too-many-public-methods
def __init__(self, host):

View file

@ -35,9 +35,10 @@ URL of your running version of iTunes-API. Example: http://192.168.1.50:8181
import logging
from homeassistant.components.media_player import (
MediaPlayerDevice, MEDIA_TYPE_MUSIC, SUPPORT_PAUSE, SUPPORT_SEEK,
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
MediaPlayerDevice, MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_PAUSE,
SUPPORT_SEEK, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK, SUPPORT_TURN_ON,
SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_MEDIA_COMMANDS)
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_ON)
@ -47,7 +48,8 @@ import requests
_LOGGER = logging.getLogger(__name__)
SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \
SUPPORT_PLAY_MEDIA
SUPPORT_AIRPLAY = SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
@ -118,6 +120,20 @@ class Itunes(object):
""" Skips back and returns the current state. """
return self._command('previous')
def play_playlist(self, playlist_id_or_name):
""" Sets a playlist to be current and returns the current state. """
response = self._request('GET', '/playlists')
playlists = response.get('playlists', [])
found_playlists = \
[playlist for playlist in playlists if
(playlist_id_or_name in [playlist["name"], playlist["id"]])]
if len(found_playlists) > 0:
playlist = found_playlists[0]
path = '/playlists/' + playlist['id'] + '/play'
return self._request('PUT', path)
def artwork_url(self):
""" Returns a URL of the current track's album art. """
return self._base_url + '/artwork'
@ -294,6 +310,11 @@ class ItunesDevice(MediaPlayerDevice):
""" Album of current playing media. (Music track only) """
return self.current_album
@property
def media_playlist(self):
""" Title of the currently playing playlist. """
return self.current_playlist
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
@ -329,6 +350,12 @@ class ItunesDevice(MediaPlayerDevice):
response = self.client.previous()
self.update_state(response)
def play_media(self, media_type, media_id):
""" play_media media player. """
if media_type == MEDIA_TYPE_PLAYLIST:
response = self.client.play_playlist(media_id)
self.update_state(response)
class AirPlayDevice(MediaPlayerDevice):
""" Represents an AirPlay device via an iTunes-API instance. """

View file

@ -167,7 +167,7 @@ class KodiDevice(MediaPlayerDevice):
def media_content_id(self):
""" Content ID of current playing media. """
if self._item is not None:
return self._item['uniqueid']
return self._item.get('uniqueid', None)
@property
def media_content_type(self):

View file

@ -1,34 +1,11 @@
"""
homeassistant.components.media_player.plex
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides an interface to the Plex API.
Provides an interface to the Plex API
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
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.plex.html
"""
import logging
from datetime import timedelta
@ -49,10 +26,8 @@ _LOGGER = logging.getLogger(__name__)
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):
""" Sets up the plex platform. """
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)
def update_devices():
""" Updates the devices objects """
""" Updates the devices objects. """
try:
devices = plexuser.devices()
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)
def update_sessions():
""" Updates the sessions objects """
""" Updates the sessions objects. """
try:
sessions = plexserver.sessions()
except BadRequest:
@ -113,7 +88,6 @@ class PlexClient(MediaPlayerDevice):
""" Represents a Plex device. """
# pylint: disable=too-many-public-methods
def __init__(self, device, plex_sessions, update_devices, update_sessions):
self.plex_sessions = plex_sessions
self.update_devices = update_devices
@ -121,12 +95,12 @@ class PlexClient(MediaPlayerDevice):
self.set_device(device)
def set_device(self, device):
""" Sets the device property """
""" Sets the device property. """
self.device = device
@property
def session(self):
""" Returns the session, if any """
""" Returns the session, if any. """
if self.device.clientIdentifier not in self.plex_sessions:
return None
@ -196,21 +170,21 @@ class PlexClient(MediaPlayerDevice):
@property
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
if isinstance(self.session, Show):
return self.session.seasons()[0].index
@property
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
if isinstance(self.session, Show):
return self.session.grandparentTitle
@property
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
if isinstance(self.session, Show):
return self.session.index

View file

@ -1,15 +1,15 @@
"""
homeassistant.components.notify.mail
homeassistant.components.notify.smtp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mail (SMTP) notification service.
Configuration:
To use the Mail notifier you will need to add something like the following
To use the smtp notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: mail
platform: smtp
server: MAIL_SERVER
port: YOUR_SMTP_PORT
sender: SENDER_EMAIL_ADDRESS

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

@ -0,0 +1,89 @@
"""
homeassistant.components.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides support for RFXtrx components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rfxtrx.html
"""
import logging
from homeassistant.util import slugify
DEPENDENCIES = []
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/0.2.zip' +
'#RFXtrx==0.2']
DOMAIN = "rfxtrx"
CONF_DEVICE = 'device'
CONF_DEBUG = 'debug'
RECEIVED_EVT_SUBSCRIBERS = []
RFX_DEVICES = {}
_LOGGER = logging.getLogger(__name__)
RFXOBJECT = None
def setup(hass, config):
""" Setup the RFXtrx component. """
# Declare the Handle event
def handle_receive(event):
""" Callback all subscribers for RFXtrx gateway. """
# Log RFXCOM event
entity_id = slugify(event.device.id_string.lower())
packet_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (entity_id, packet_id)
_LOGGER.info("Receive RFXCOM event from %s => %s",
event.device, entity_name)
# Callback to HA registered components
for subscriber in RECEIVED_EVT_SUBSCRIBERS:
subscriber(event)
# Try to load the RFXtrx module
try:
import RFXtrx as rfxtrxmod
except ImportError:
_LOGGER.exception("Failed to import rfxtrx")
return False
# Init the rfxtrx module
global RFXOBJECT
if CONF_DEVICE not in config[DOMAIN]:
_LOGGER.exception(
"can found device parameter in %s YAML configuration section",
DOMAIN
)
return False
device = config[DOMAIN][CONF_DEVICE]
debug = config[DOMAIN].get(CONF_DEBUG, False)
RFXOBJECT = rfxtrxmod.Core(device, handle_receive, debug=debug)
return True
def get_rfx_object(packetid):
""" Return the RFXObject with the packetid. """
try:
import RFXtrx as rfxtrxmod
except ImportError:
_LOGGER.exception("Failed to import rfxtrx")
return False
binarypacket = bytearray.fromhex(packetid)
pkt = rfxtrxmod.lowlevel.parse(binarypacket)
if pkt is not None:
if isinstance(pkt, rfxtrxmod.lowlevel.SensorPacket):
obj = rfxtrxmod.SensorEvent(pkt)
elif isinstance(pkt, rfxtrxmod.lowlevel.Status):
obj = rfxtrxmod.StatusEvent(pkt)
else:
obj = rfxtrxmod.ControlEvent(pkt)
return obj
return None

View file

@ -3,51 +3,11 @@ homeassistant.components.sensor.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The arest sensor will consume an exposed aREST API of a device.
Configuration:
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
}
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.arest.html
"""
import logging
from requests import get, exceptions
import requests
from datetime import timedelta
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
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):
""" 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:
response = get(resource, timeout=10)
except exceptions.MissingSchema:
response = requests.get(resource, timeout=10).json()
except requests.exceptions.MissingSchema:
_LOGGER.error("Missing resource or schema in configuration. "
"Add http:// to your URL.")
return False
except exceptions.ConnectionError:
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device. "
"Please check the IP address in the configuration file.")
return False
rest = ArestData(resource)
arest = ArestData(resource)
dev = []
for variable in config['monitored_variables']:
if 'unit' not in variable:
variable['unit'] = ' '
if variable['name'] not in response.json()['variables']:
if variable['name'] not in response['variables']:
_LOGGER.error('Variable: "%s" does not exist', variable['name'])
else:
dev.append(ArestSensor(rest,
response.json()['name'],
variable['name'],
variable['unit']))
continue
dev.append(ArestSensor(arest, response['name'], variable['name'],
variable.get('unit')))
add_devices(dev)
@ -95,8 +61,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ArestSensor(Entity):
""" Implements an aREST sensor. """
def __init__(self, rest, location, variable, unit_of_measurement):
self.rest = rest
def __init__(self, arest, location, variable, unit_of_measurement):
self.arest = arest
self._name = '{} {}'.format(location.title(), variable.title())
self._variable = variable
self._state = 'n/a'
@ -116,17 +82,16 @@ class ArestSensor(Entity):
@property
def state(self):
""" Returns the state of the device. """
return self._state
def update(self):
""" Gets the latest data from aREST API and updates the state. """
self.rest.update()
values = self.rest.data
values = self.arest.data
if 'error' in values:
self._state = values['error']
return values['error']
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
@ -135,16 +100,14 @@ class ArestData(object):
def __init__(self, resource):
self.resource = resource
self.data = dict()
self.data = {}
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
""" Gets the latest data from aREST device. """
try:
response = get(self.resource, timeout=10)
if 'error' in self.data:
del self.data['error']
response = requests.get(self.resource, timeout=10)
self.data = response.json()['variables']
except exceptions.ConnectionError:
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device. Is device offline?")
self.data['error'] = 'n/a'
self.data = {'error': 'error fetching'}

View file

@ -3,30 +3,19 @@ homeassistant.components.sensor.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows sensor values from RFXtrx sensors.
Configuration:
To use the rfxtrx sensors you will need to add something like the following to
your configuration.yaml file.
sensor:
platform: rfxtrx
device: PATH_TO_DEVICE
Variables:
device
*Required
Path to your RFXtrx device.
E.g. /dev/serial/by-id/usb-RFXCOM_RFXtrx433_A1Y0NJGR-if00-port0
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.rfxtrx.html
"""
import logging
from collections import OrderedDict
from homeassistant.const import (TEMP_CELCIUS)
from homeassistant.helpers.entity import Entity
import homeassistant.components.rfxtrx as rfxtrx
from RFXtrx import SensorEvent
from homeassistant.util import slugify
REQUIREMENTS = ['https://github.com/Danielhiversen/pyRFXtrx/archive/' +
'ec7a1aaddf8270db6e5da1c13d58c1547effd7cf.zip#RFXtrx==0.15']
DEPENDENCIES = ['rfxtrx']
DATA_TYPES = OrderedDict([
('Temperature', TEMP_CELCIUS),
@ -34,32 +23,30 @@ DATA_TYPES = OrderedDict([
('Barometer', ''),
('Wind direction', ''),
('Rain rate', '')])
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Setup the RFXtrx platform. """
logger = logging.getLogger(__name__)
sensors = {} # keep track of sensors added to HA
def sensor_update(event):
""" Callback for sensor updates from the RFXtrx gateway. """
if event.device.id_string in sensors:
sensors[event.device.id_string].event = event
else:
logger.info("adding new sensor: %s", event.device.type_string)
new_sensor = RfxtrxSensor(event)
sensors[event.device.id_string] = new_sensor
add_devices([new_sensor])
try:
import RFXtrx as rfxtrx
except ImportError:
logger.exception(
"Failed to import rfxtrx")
return False
if isinstance(event.device, SensorEvent):
entity_id = slugify(event.device.id_string.lower())
device = config.get("device", "")
rfxtrx.Core(device, sensor_update)
# Add entity if not exist and the automatic_add is True
if entity_id not in rfxtrx.RFX_DEVICES:
automatic_add = config.get('automatic_add', True)
if automatic_add:
_LOGGER.info("Automatic add %s rfxtrx.sensor", entity_id)
new_sensor = RfxtrxSensor(event)
rfxtrx.RFX_DEVICES[entity_id] = new_sensor
add_devices_callback([new_sensor])
else:
rfxtrx.RFX_DEVICES[entity_id].event = event
if sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(sensor_update)
class RfxtrxSensor(Entity):
@ -67,7 +54,6 @@ class RfxtrxSensor(Entity):
def __init__(self, event):
self.event = event
self._unit_of_measurement = None
self._data_type = None
for data_type in DATA_TYPES:
@ -86,13 +72,14 @@ class RfxtrxSensor(Entity):
@property
def state(self):
""" Returns the state of the device. """
if self._data_type:
return self.event.values[self._data_type]
return None
@property
def name(self):
""" Get the mame of the sensor. """
""" Get the name of the sensor. """
return self._name
@property

View file

@ -3,38 +3,9 @@ homeassistant.components.switch.arest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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.
Only tested with Arduino boards so far.
Configuration:
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
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.arest.html
"""
import logging
from requests import get, exceptions

View file

@ -0,0 +1,112 @@
"""
homeassistant.components.switch.rfxtrx
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for RFXtrx switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.rfxtrx.html
"""
import logging
import homeassistant.components.rfxtrx as rfxtrx
from RFXtrx import LightingDevice
from homeassistant.components.switch import SwitchDevice
from homeassistant.util import slugify
DEPENDENCIES = ['rfxtrx']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Setup the RFXtrx platform. """
# Add switch from config file
switchs = []
devices = config.get('devices')
if devices:
for entity_id, entity_info in devices.items():
if entity_id not in rfxtrx.RFX_DEVICES:
_LOGGER.info("Add %s rfxtrx.switch", entity_info['name'])
rfxobject = rfxtrx.get_rfx_object(entity_info['packetid'])
newswitch = RfxtrxSwitch(entity_info['name'], rfxobject, False)
rfxtrx.RFX_DEVICES[entity_id] = newswitch
switchs.append(newswitch)
add_devices_callback(switchs)
def switch_update(event):
""" Callback for sensor updates from the RFXtrx gateway. """
if isinstance(event.device, LightingDevice):
return
# Add entity if not exist and the automatic_add is True
entity_id = slugify(event.device.id_string.lower())
if entity_id not in rfxtrx.RFX_DEVICES:
automatic_add = config.get('automatic_add', False)
if not automatic_add:
return
_LOGGER.info(
"Automatic add %s rfxtrx.switch (Class: %s Sub: %s)",
entity_id,
event.device.__class__.__name__,
event.device.subtype
)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
entity_name = "%s : %s" % (entity_id, pkt_id)
new_switch = RfxtrxSwitch(entity_name, event, False)
rfxtrx.RFX_DEVICES[entity_id] = new_switch
add_devices_callback([new_switch])
# Check if entity exists or previously added automatically
if entity_id in rfxtrx.RFX_DEVICES:
if event.values['Command'] == 'On'\
or event.values['Command'] == 'Off':
if event.values['Command'] == 'On':
rfxtrx.RFX_DEVICES[entity_id].turn_on()
else:
rfxtrx.RFX_DEVICES[entity_id].turn_off()
# Subscribe to main rfxtrx events
if switch_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS:
rfxtrx.RECEIVED_EVT_SUBSCRIBERS.append(switch_update)
class RfxtrxSwitch(SwitchDevice):
""" Provides a RFXtrx switch. """
def __init__(self, name, event, state):
self._name = name
self._event = event
self._state = state
@property
def should_poll(self):
""" No polling needed for a RFXtrx switch. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
return self._name
@property
def is_on(self):
""" True if device is on. """
return self._state
def turn_on(self, **kwargs):
""" Turn the device on. """
if self._event:
self._event.device.send_on(rfxtrx.RFXOBJECT.transport)
self._state = True
self.update_ha_state()
def turn_off(self, **kwargs):
""" Turn the device off. """
if self._event:
self._event.device.send_off(rfxtrx.RFXOBJECT.transport)
self._state = False
self.update_ha_state()

View file

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

View file

@ -13,6 +13,8 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
STATE_PLAYING, STATE_PAUSED, ATTR_ENTITY_ID)
from homeassistant.components.media_player import (SERVICE_PLAY_MEDIA)
_LOGGER = logging.getLogger(__name__)
@ -57,7 +59,11 @@ def reproduce_state(hass, states, blocking=False):
state.entity_id)
continue
if state.domain == 'media_player' and state.state == STATE_PAUSED:
if state.domain == 'media_player' and state.attributes and \
'media_type' in state.attributes and \
'media_id' in state.attributes:
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

View file

@ -233,35 +233,42 @@ class Throttle(object):
self.limit_no_throttle = limit_no_throttle
def __call__(self, method):
lock = threading.Lock()
if self.limit_no_throttle is not None:
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)
def wrapper(*args, **kwargs):
"""
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 not lock.acquire(False):
# pylint: disable=protected-access
host = wrapper if is_func else args[0]
if not hasattr(host, '_throttle_lock'):
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
force = not last_call or kwargs.pop('no_throttle', False)
try:
last_call = wrapper.last_call
# Check if method is never called or no_throttle is given
force = not last_call or kwargs.pop('no_throttle', False)
if force or utcnow() - last_call > self.min_time:
result = method(*args, **kwargs)
wrapper.last_call = utcnow()
host._throttle_last_call = utcnow()
return result
else:
return None
finally:
lock.release()
wrapper.last_call = None
host._throttle_lock.release()
return wrapper

View file

@ -10,77 +10,77 @@ vincenty==0.1.2
# Sun (sun)
astral==0.8.1
# Philips Hue library (lights.hue)
# Philips Hue (lights.hue)
phue==0.8
# Limitlessled/Easybulb/Milight library (lights.limitlessled)
# Limitlessled/Easybulb/Milight (lights.limitlessled)
ledcontroller==1.1.0
# Chromecast bindings (media_player.cast)
# Chromecast (media_player.cast)
pychromecast==0.6.12
# Keyboard (keyboard)
pyuserinput==0.1.9
# Tellstick bindings (*.tellstick)
# Tellstick (*.tellstick)
tellcore-py==1.1.2
# Nmap bindings (device_tracker.nmap)
# Nmap (device_tracker.nmap)
python-nmap==0.4.3
# PushBullet bindings (notify.pushbullet)
# PushBullet (notify.pushbullet)
pushbullet.py==0.7.1
# Nest Thermostat bindings (thermostat.nest)
# Nest Thermostat (thermostat.nest)
python-nest==2.6.0
# Z-Wave (*.zwave)
pydispatcher==2.0.5
# ISY994 bindings (*.isy994)
# ISY994 (isy994)
PyISY==1.0.5
# PSutil (sensor.systemmonitor)
psutil==3.0.0
# Pushover bindings (notify.pushover)
# Pushover (notify.pushover)
python-pushover==0.2
# Transmission Torrent Client (*.transmission)
transmissionrpc==0.11
# OpenWeatherMap Web API (sensor.openweathermap)
# OpenWeatherMap (sensor.openweathermap)
pyowm==2.2.1
# XMPP Bindings (notify.xmpp)
# XMPP (notify.xmpp)
sleekxmpp==1.3.1
dnspython3==1.12.0
# Blockchain (sensor.bitcoin)
blockchain==1.1.2
# MPD Bindings (media_player.mpd)
# Music Player Daemon (media_player.mpd)
python-mpd2==0.5.4
# Hikvision (switch.hikvisioncam)
hikvision==0.4
# console log coloring
# Console log coloring
colorlog==2.6.0
# JSON-RPC interface (media_player.kodi)
jsonrpc-requests==0.1
# Forecast.io Bindings (sensor.forecast)
# Forecast.io (sensor.forecast)
python-forecastio==1.3.3
# Firmata Bindings (*.arduino)
# Firmata (*.arduino)
PyMata==2.07a
# Rfxtrx sensor (sensor.rfxtrx)
# Rfxtrx (rfxtrx)
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
# Netgear (device_tracker.netgear)
@ -101,18 +101,18 @@ slacker==0.6.8
# Temper sensors (sensor.temper)
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
# RPI-GPIO platform (*.rpi_gpio)
# Uncomment for Raspberry Pi
# RPi.GPIO==0.5.11
# Adafruit temperature/humidity sensor
# uncomment on a Raspberry Pi / Beaglebone
# Adafruit temperature/humidity sensor (sensor.dht)
# Uncomment on a Raspberry Pi / Beaglebone
# 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
# PyModbus (modbus)
@ -121,19 +121,26 @@ https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b6
# Verisure (verisure)
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
# sensor.sabnzbd
# SABnzbd (sensor.sabnzbd)
https://github.com/balloob/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
# switch.vera
# sensor.vera
# light.vera
# Vera (*.vera)
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
# PlexAPI (media_player.plex)
https://github.com/adrienbrault/python-plexapi/archive/df2d0847e801d6d5cda920326d693cf75f304f1a.zip#python-plexapi==1.0.2
# SNMP (device_tracker.snmp)
pysnmp==4.2.5
# Blinkstick (light.blinksticklight)
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.*'])
PACKAGE_DATA = \
{'homeassistant.components.frontend': ['index.html.template'],
'homeassistant.components.frontend.www_static': ['*.*'],
'homeassistant.components.frontend.www_static.images': ['*.*'],
'homeassistant.startup': ['*.*']}
# PACKAGE_DATA = \
# {'homeassistant.components.frontend': ['index.html.template'],
# 'homeassistant.components.frontend.www_static': ['*.*'],
# 'homeassistant.components.frontend.www_static.images': ['*.*'],
# 'homeassistant.components.mqtt': ['*.crt'],
# 'homeassistant.startup': ['*.*']}
REQUIRES = [
'requests>=2,<3',
@ -23,6 +24,7 @@ REQUIRES = [
'vincenty==0.1.2'
]
# package_data=PACKAGE_DATA,
setup(
name=PACKAGE_NAME,
version=__version__,
@ -34,7 +36,6 @@ setup(
description='Open-source home automation platform running on Python 3.',
packages=PACKAGES,
include_package_data=True,
package_data=PACKAGE_DATA,
zip_safe=False,
platforms='any',
install_requires=REQUIRES,

View file

@ -152,9 +152,13 @@ class TestLight(unittest.TestCase):
data)
method, data = dev2.last_call('turn_on')
self.assertEqual(
{light.ATTR_XY_COLOR: color_util.color_RGB_to_xy(255, 255, 255)},
data)
self.assertEquals(
data[light.ATTR_XY_COLOR],
color_util.color_RGB_to_xy(255, 255, 255))
self.assertEquals(
data[light.ATTR_RGB_COLOR],
[255, 255, 255])
method, data = dev3.last_call('turn_on')
self.assertEqual({light.ATTR_XY_COLOR: [.4, .6]}, data)

View file

@ -40,7 +40,7 @@ class TestMediaPlayer(unittest.TestCase):
def test_services(self):
"""
Test if the call service methods conver to correct service calls.
Test if the call service methods convert to correct service calls.
"""
services = {
SERVICE_TURN_ON: media_player.turn_on,

View file

@ -218,3 +218,14 @@ class TestUtil(unittest.TestCase):
self.assertEqual(3, len(calls1))
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())