Merge remote-tracking branch 'upstream/master' into dev

This commit is contained in:
root 2015-10-27 07:51:21 +01:00
commit c5f8095f53
34 changed files with 2093 additions and 2310 deletions

View File

@ -37,6 +37,7 @@ omit =
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/thomson.py
@ -49,6 +50,7 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/hyperion.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.py

View File

@ -20,8 +20,8 @@ After you finish adding support for your device:
- Update the supported devices in the `README.md` file.
- Add any new dependencies to `requirements_all.txt`. There is no ordering right now, so just add it to the end.
- Update the `.coveragerc` file.
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io).
- Make sure all your code passes Pylint and flake8 (PEP8 and some more) validation. To generate reports, run `pylint homeassistant > pylint.txt` and `flake8 homeassistant --exclude bower_components,external > flake8.txt`.
- Provide some documentation for [home-assistant.io](https://home-assistant.io/). The documentation is handled in a separate [git repository](https://github.com/balloob/home-assistant.io). It's OK to add a docstring with configuration details to the file header.
- Make sure all your code passes ``pylint`` and ``flake8`` (PEP8 and some more) validation. To check your repository, run `./script/lint`.
- Create a Pull Request against the [**dev**](https://github.com/balloob/home-assistant/tree/dev) branch of Home Assistant.
- Check for comments and suggestions on your Pull Request and keep an eye on the [Travis output](https://travis-ci.org/balloob/home-assistant/).

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.api
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a Rest API for Home Assistant.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api.html
"""
import re
import logging

View File

@ -1,9 +1,10 @@
"""
homeassistant.components.conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to have conversations with Home Assistant.
This is more a proof of concept.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/conversation.html
"""
import logging
import re

View File

@ -0,0 +1,173 @@
"""
homeassistant.components.device_tracker.ubus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a OpenWRT router for device
presence.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ubus.html
"""
import logging
import json
from datetime import timedelta
import re
import threading
import requests
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
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=5)
_LOGGER = logging.getLogger(__name__)
def get_scanner(hass, config):
""" Validates config and returns a Luci scanner. """
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = UbusDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
# pylint: disable=too-many-instance-attributes
class UbusDeviceScanner(object):
"""
This class queries a wireless router running OpenWrt firmware
for connected devices. Adapted from Tomato scanner.
Configure your routers' ubus ACL based on following instructions:
http://wiki.openwrt.org/doc/techref/ubus
Read only access will be fine.
To use this class you have to install rpcd-mod-file package
in your OpenWrt router:
opkg install rpcd-mod-file
"""
def __init__(self, config):
host = config[CONF_HOST]
username, password = config[CONF_USERNAME], config[CONF_PASSWORD]
self.parse_api_pattern = re.compile(r"(?P<param>\w*) = (?P<value>.*);")
self.lock = threading.Lock()
self.last_results = {}
self.url = 'http://{}/ubus'.format(host)
self.session_id = _get_session_id(self.url, username, password)
self.hostapd = []
self.leasefile = None
self.mac2name = None
self.success_init = self.session_id is not None
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return self.last_results
def get_device_name(self, device):
""" Returns the name of the given device or None if we don't know. """
with self.lock:
if self.leasefile is None:
result = _req_json_rpc(self.url, self.session_id,
'call', 'uci', 'get',
config="dhcp", type="dnsmasq")
if result:
values = result["values"].values()
self.leasefile = next(iter(values))["leasefile"]
else:
return
if self.mac2name is None:
result = _req_json_rpc(self.url, self.session_id,
'call', 'file', 'read',
path=self.leasefile)
if result:
self.mac2name = dict()
for line in result["data"].splitlines():
hosts = line.split(" ")
self.mac2name[hosts[1].upper()] = hosts[3]
else:
# Error, handled in the _req_json_rpc
return
return self.mac2name.get(device.upper(), None)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the Luci router is up to date.
Returns boolean if scanning successful.
"""
if not self.success_init:
return False
with self.lock:
_LOGGER.info("Checking ARP")
if not self.hostapd:
hostapd = _req_json_rpc(self.url, self.session_id,
'list', 'hostapd.*', '')
self.hostapd.extend(hostapd.keys())
self.last_results = []
results = 0
for hostapd in self.hostapd:
result = _req_json_rpc(self.url, self.session_id,
'call', hostapd, 'get_clients')
if result:
results = results + 1
self.last_results.extend(result['clients'].keys())
return bool(results)
def _req_json_rpc(url, session_id, rpcmethod, subsystem, method, **params):
""" Perform one JSON RPC operation. """
data = json.dumps({"jsonrpc": "2.0",
"id": 1,
"method": rpcmethod,
"params": [session_id,
subsystem,
method,
params]})
try:
res = requests.post(url, data=data, timeout=5)
except requests.exceptions.Timeout:
return
if res.status_code == 200:
response = res.json()
if rpcmethod == "call":
return response["result"][1]
else:
return response["result"]
def _get_session_id(url, username, password):
""" Get authentication token for the given host+username+password. """
res = _req_json_rpc(url, "00000000000000000000000000000000", 'call',
'session', 'login', username=username,
password=password)
return res["ubus_rpc_session"]

View File

@ -19,7 +19,7 @@ from homeassistant.const import (
DOMAIN = "discovery"
DEPENDENCIES = []
REQUIREMENTS = ['netdisco==0.5']
REQUIREMENTS = ['netdisco==0.5.1']
SCAN_INTERVAL = 300 # seconds
@ -28,6 +28,7 @@ SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver'
SERVICE_HANDLERS = {
SERVICE_WEMO: "switch",
@ -35,6 +36,7 @@ SERVICE_HANDLERS = {
SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player',
}
@ -88,6 +90,7 @@ def setup(hass, config):
ATTR_DISCOVERED: info
})
# pylint: disable=unused-argument
def start_discovery(event):
""" Start discovering. """
netdisco = DiscoveryService(SCAN_INTERVAL)

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.downloader
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to download files.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/downloader.html
"""
import os
import logging
@ -42,6 +44,10 @@ def setup(hass, config):
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR]
# If path is relative, we assume relative to HASS config dir
if not os.path.isabs(download_path):
download_path = hass.config.path(download_path)
if not os.path.isdir(download_path):
logger.error(

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "90c41bfbaa56f9a1c88db27a54f7d36b"
VERSION = "beb922c55bb26ea576581b453f6d7c04"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit c91fcccf29c977bb0f8e1143fb26fc75613b6a0f
Subproject commit 24623ff26ab8cbf7b39f0a25c26d9d991063b61a

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,11 @@
"""
homeassistant.components.group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to group devices that can be turned on or off.
"""
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/group.html
"""
import homeassistant.core as ha
from homeassistant.helpers import generate_entity_id
from homeassistant.helpers.event import track_state_change

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provide pre-made queries on top of the recorder component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/history.html
"""
import re
from datetime import timedelta

View File

@ -1,76 +1,11 @@
"""
homeassistant.components.httpinterface
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
homeassistant.components.http
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module provides an API and a HTTP interface for debug purposes.
By default it will run on port 8123.
All API calls have to be accompanied by an 'api_password' parameter and will
return JSON. If successful calls will return status code 200 or 201.
Other status codes that can occur are:
- 400 (Bad Request)
- 401 (Unauthorized)
- 404 (Not Found)
- 405 (Method not allowed)
The api supports the following actions:
/api - GET
Returns message if API is up and running.
Example result:
{
"message": "API running."
}
/api/states - GET
Returns a list of entities for which a state is available
Example result:
[
{ .. state object .. },
{ .. state object .. }
]
/api/states/<entity_id> - GET
Returns the current state from an entity
Example result:
{
"attributes": {
"next_rising": "07:04:15 29-10-2013",
"next_setting": "18:00:31 29-10-2013"
},
"entity_id": "weather.sun",
"last_changed": "23:24:33 28-10-2013",
"state": "below_horizon"
}
/api/states/<entity_id> - POST
Updates the current state of an entity. Returns status code 201 if successful
with location header of updated resource and as body the new state.
parameter: new_state - string
optional parameter: attributes - JSON encoded object
Example result:
{
"attributes": {
"next_rising": "07:04:15 29-10-2013",
"next_setting": "18:00:31 29-10-2013"
},
"entity_id": "weather.sun",
"last_changed": "23:24:33 28-10-2013",
"state": "below_horizon"
}
/api/events/<event_type> - POST
Fires an event with event_type
optional parameter: event_data - JSON encoded object
Example result:
{
"message": "Event download_file fired."
}
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api.html
"""
import json
import threading
import logging

View File

@ -1,23 +1,10 @@
"""
homeassistant.components.ifttt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes.
Check https://ifttt.com/maker for details.
Configuration:
To use Maker IFTTT you will need to add something like the following to your
config/configuration.yaml.
ifttt:
key: xxxxx-x-xxxxxxxxxxxxx
Variables:
key
*Required
Your api key
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ifttt.html
"""
import logging
import requests

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.introduction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component that will help guide the user taking its first steps.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/introduction.html
"""
import logging

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.keyboard
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to emulate keyboard presses on host machine.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/keyboard.html
"""
import logging

View File

@ -2,6 +2,8 @@
homeassistant.components.light.hue
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Hue lights.
https://home-assistant.io/components/light.hue.html
"""
import logging
import socket

View File

@ -0,0 +1,125 @@
"""
homeassistant.components.light.hyperion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Hyperion remotes.
https://home-assistant.io/components/light.hyperion.html
"""
import logging
import socket
import json
from homeassistant.const import CONF_HOST
from homeassistant.components.light import (Light, ATTR_RGB_COLOR)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = []
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up a Hyperion server remote """
host = config.get(CONF_HOST, None)
port = config.get("port", 19444)
device = Hyperion(host, port)
if device.setup():
add_devices_callback([device])
return True
else:
return False
class Hyperion(Light):
""" Represents a Hyperion remote """
def __init__(self, host, port):
self._host = host
self._port = port
self._name = host
self._is_available = True
self._rgb_color = [255, 255, 255]
@property
def name(self):
""" Return the hostname of the server. """
return self._name
@property
def rgb_color(self):
""" Last RGB color value set. """
return self._rgb_color
@property
def is_on(self):
""" True if the device is online. """
return self._is_available
def turn_on(self, **kwargs):
""" Turn the lights on. """
if self._is_available:
if ATTR_RGB_COLOR in kwargs:
self._rgb_color = kwargs[ATTR_RGB_COLOR]
self.json_request({"command": "color", "priority": 128,
"color": self._rgb_color})
def turn_off(self, **kwargs):
""" Disconnect the remote. """
self.json_request({"command": "clearall"})
def update(self):
""" Ping the remote. """
# just see if the remote port is open
self._is_available = self.json_request()
def setup(self):
""" Get the hostname of the remote. """
response = self.json_request({"command": "serverinfo"})
if response:
self._name = response["info"]["hostname"]
return True
return False
def json_request(self, request=None, wait_for_response=False):
""" Communicate with the json server. """
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5)
try:
sock.connect((self._host, self._port))
except OSError:
sock.close()
return False
if not request:
# no communication needed, simple presence detection returns True
sock.close()
return True
sock.send(bytearray(json.dumps(request) + "\n", "utf-8"))
try:
buf = sock.recv(4096)
except socket.timeout:
# something is wrong, assume it's offline
sock.close()
return False
# read until a newline or timeout
buffering = True
while buffering:
if "\n" in str(buf, "utf-8"):
response = str(buf, "utf-8").split("\n")[0]
buffering = False
else:
try:
more = sock.recv(4096)
except socket.timeout:
more = None
if not more:
buffering = False
response = str(buf, "utf-8")
else:
buf += more
sock.close()
return json.loads(response)

View File

@ -12,22 +12,7 @@ Support for LimitlessLED bulbs, also known as...
- dekolight
- iLight
Configuration:
To use limitlessled you will need to add the following to your
configuration.yaml file.
light:
platform: limitlessled
bridges:
- host: 192.168.1.10
group_1_name: Living Room
group_2_name: Bedroom
group_3_name: Office
group_3_type: white
group_4_name: Kitchen
- host: 192.168.1.11
group_2_name: Basement
https://home-assistant.io/components/light.limitlessled.html
"""
import logging

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.logbook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parses events and generates a human log.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/logbook.html
"""
from datetime import timedelta
from itertools import groupby

View File

@ -28,6 +28,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = {
discovery.SERVICE_CAST: 'cast',
discovery.SERVICE_SONOS: 'sonos',
discovery.SERVICE_PLEX: 'plex',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'

View File

@ -6,38 +6,114 @@ 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
"""
import os
import json
import logging
from datetime import timedelta
from urllib.parse import urlparse
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
import homeassistant.util as util
DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_PLAYING,
STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
REQUIREMENTS = ['https://github.com/adrienbrault/python-plexapi/archive/'
'df2d0847e801d6d5cda920326d693cf75f304f1a.zip'
'#python-plexapi==1.0.2']
REQUIREMENTS = ['plexapi==1.1.0']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
PLEX_CONFIG_FILE = 'plex.conf'
# Map ip to request id for configuring
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)
SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
# 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
from plexapi.exceptions import BadRequest
def config_from_file(filename, config=None):
''' Small configuration file management function'''
if config:
# We're writing configuration
try:
with open(filename, 'w') as fdesc:
fdesc.write(json.dumps(config))
except IOError as error:
_LOGGER.error('Saving config file failed: %s', error)
return False
return True
else:
# We're reading config
if os.path.isfile(filename):
try:
with open(filename, 'r') as fdesc:
return json.loads(fdesc.read())
except IOError as error:
_LOGGER.error('Reading config file failed: %s', error)
# This won't work yet
return False
else:
return {}
# pylint: disable=abstract-method, unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up the plex platform. """
config = config_from_file(hass.config.path(PLEX_CONFIG_FILE))
if len(config):
# Setup a configured PlexServer
host, token = config.popitem()
token = token['token']
# Via discovery
elif discovery_info is not None:
# Parse discovery data
host = urlparse(discovery_info[1]).netloc
_LOGGER.info('Discovered PLEX server: %s', host)
if host in _CONFIGURING:
return
token = None
else:
return
setup_plexserver(host, token, hass, add_devices_callback)
# pylint: disable=too-many-branches
def setup_plexserver(host, token, hass, add_devices_callback):
''' Setup a plexserver based on host parameter'''
import plexapi.server
import plexapi.exceptions
try:
plexserver = plexapi.server.PlexServer('http://%s' % host, token)
except (plexapi.exceptions.BadRequest,
plexapi.exceptions.Unauthorized,
plexapi.exceptions.NotFound) as error:
_LOGGER.info(error)
# No token or wrong token
request_configuration(host, hass, add_devices_callback)
return
# If we came here and configuring this host, mark as done
if host in _CONFIGURING:
request_id = _CONFIGURING.pop(host)
configurator = get_component('configurator')
configurator.request_done(request_id)
_LOGGER.info('Discovery configuration done!')
# Save config
if not config_from_file(
hass.config.path(PLEX_CONFIG_FILE),
{host: {'token': token}}):
_LOGGER.error('failed to save config file')
_LOGGER.info('Connected to: htts://%s', host)
name = config.get('name', '')
user = config.get('user', '')
password = config.get('password', '')
plexuser = MyPlexUser.signin(user, password)
plexserver = plexuser.getResource(name).connect()
plex_clients = {}
plex_sessions = {}
@ -45,34 +121,34 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def update_devices():
""" Updates the devices objects. """
try:
devices = plexuser.devices()
except BadRequest:
devices = plexserver.clients()
except plexapi.exceptions.BadRequest:
_LOGGER.exception("Error listing plex devices")
return
new_plex_clients = []
for device in devices:
if (all(x not in ['client', 'player'] for x in device.provides)
or 'PlexAPI' == device.product):
# For now, let's allow all deviceClass types
if device.deviceClass in ['badClient']:
continue
if device.clientIdentifier not in plex_clients:
if device.machineIdentifier not in plex_clients:
new_client = PlexClient(device, plex_sessions, update_devices,
update_sessions)
plex_clients[device.clientIdentifier] = new_client
plex_clients[device.machineIdentifier] = new_client
new_plex_clients.append(new_client)
else:
plex_clients[device.clientIdentifier].set_device(device)
plex_clients[device.machineIdentifier].set_device(device)
if new_plex_clients:
add_devices(new_plex_clients)
add_devices_callback(new_plex_clients)
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_sessions():
""" Updates the sessions objects. """
try:
sessions = plexserver.sessions()
except BadRequest:
except plexapi.exceptions.BadRequest:
_LOGGER.exception("Error listing plex sessions")
return
@ -84,10 +160,34 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
update_sessions()
def request_configuration(host, hass, add_devices_callback):
""" Request configuration steps from the user. """
configurator = get_component('configurator')
# We got an error if this method is called while we are configuring
if host in _CONFIGURING:
configurator.notify_errors(
_CONFIGURING[host], "Failed to register, please try again.")
return
def plex_configuration_callback(data):
""" Actions to do when our configuration callback is called. """
setup_plexserver(host, data.get('token'), hass, add_devices_callback)
_CONFIGURING[host] = configurator.request_config(
hass, "Plex Media Server", plex_configuration_callback,
description=('Enter the X-Plex-Token'),
description_image="/static/images/config_plex_mediaserver.png",
submit_caption="Confirm",
fields=[{'id': 'token', 'name': 'X-Plex-Token', 'type': ''}]
)
class PlexClient(MediaPlayerDevice):
""" Represents a Plex device. """
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-public-methods, attribute-defined-outside-init
def __init__(self, device, plex_sessions, update_devices, update_sessions):
self.plex_sessions = plex_sessions
self.update_devices = update_devices
@ -99,17 +199,23 @@ class PlexClient(MediaPlayerDevice):
self.device = device
@property
def session(self):
""" Returns the session, if any. """
if self.device.clientIdentifier not in self.plex_sessions:
return None
return self.plex_sessions[self.device.clientIdentifier]
def unique_id(self):
""" Returns the id of this plex client """
return "{}.{}".format(
self.__class__, self.device.machineIdentifier or self.device.name)
@property
def name(self):
""" Returns the name of the device. """
return self.device.name or self.device.product or self.device.device
return self.device.name or DEVICE_DEFAULT_NAME
@property
def session(self):
""" Returns the session, if any. """
if self.device.machineIdentifier not in self.plex_sessions:
return None
return self.plex_sessions[self.device.machineIdentifier]
@property
def state(self):
@ -120,7 +226,8 @@ class PlexClient(MediaPlayerDevice):
return STATE_PLAYING
elif state == 'paused':
return STATE_PAUSED
elif self.device.isReachable:
# This is nasty. Need to find a way to determine alive
elif self.device:
return STATE_IDLE
else:
return STATE_OFF
@ -196,16 +303,16 @@ class PlexClient(MediaPlayerDevice):
def media_play(self):
""" media_play media player. """
self.device.play({'type': 'video'})
self.device.play()
def media_pause(self):
""" media_pause media player. """
self.device.pause({'type': 'video'})
self.device.pause()
def media_next_track(self):
""" Send next track command. """
self.device.skipNext({'type': 'video'})
self.device.skipNext()
def media_previous_track(self):
""" Send previous track command. """
self.device.skipPrevious({'type': 'video'})
self.device.skipPrevious()

View File

@ -1,9 +1,11 @@
"""
homeassistant.components.recorder
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component that records all events and state changes. Allows other components
to query this database.
Component that records all events and state changes.
Allows other components to query this database.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/recorder.html
"""
import logging
import threading

View File

@ -1,11 +1,10 @@
"""
homeassistant.components.scene
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows users to set and activate scenes.
Allows users to set and activate scenes within Home Assistant.
A scene is a set of states that describe how you want certain entities to be.
For example, light A should be red with 100 brightness. Light B should be on.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/scene.html
"""
import logging
from collections import namedtuple

View File

@ -1,9 +1,11 @@
"""
homeassistant.components.script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
entity_id
Scripts are a sequence of actions that can be triggered manually
by the user or automatically based upon automation events, etc.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/script.html
"""
import logging
from datetime import timedelta

View File

@ -1,37 +1,11 @@
# -*- coding: utf-8 -*-
"""
homeassistant.components.switch.rest
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows to configure a REST switch.
Configuration:
switch:
platform: rest
name: "Bedroom Switch"
resource: "http://IP_ADDRESS/ENDPOINT"
body_on: "ON"
body_off: "OFF"
Variables:
resource
*Required*
name
*Optional
The name of the switch. Default is 'REST Switch'.
body_on
*Optional
The body that represents enabled state. Default is "ON".
body_off
*Optional
The body that represents disabled state. Default is "OFF".
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.rest.html
"""
import logging
import requests
@ -46,7 +20,7 @@ DEFAULT_BODY_OFF = "OFF"
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Add REST Switch """
""" Get REST switch. """
resource = config.get('resource')
@ -61,7 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"Add http:// to your URL.")
return False
except requests.exceptions.ConnectionError:
_LOGGER.error("No route to device. "
_LOGGER.error("No route to resource/endpoint. "
"Please check the IP address in the configuration file.")
return False
@ -75,7 +49,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-arguments
class RestSwitch(SwitchDevice):
""" Represents a switch that can be togggled using REST """
""" Represents a switch that can be toggled using REST. """
def __init__(self, hass, name, resource, body_on, body_off):
self._state = None
self._hass = hass
@ -102,7 +76,7 @@ class RestSwitch(SwitchDevice):
if request.status_code == 200:
self._state = True
else:
_LOGGER.error("Can't turn on %s. Is device offline?",
_LOGGER.error("Can't turn on %s. Is resource/endpoint offline?",
self._resource)
def turn_off(self, **kwargs):
@ -113,7 +87,7 @@ class RestSwitch(SwitchDevice):
if request.status_code == 200:
self._state = False
else:
_LOGGER.error("Can't turn off %s. Is device offline?",
_LOGGER.error("Can't turn off %s. Is resource/endpoint offline?",
self._resource)
def update(self):

View File

@ -11,7 +11,7 @@ import logging
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import STATE_ON, STATE_OFF, STATE_STANDBY
REQUIREMENTS = ['pywemo==0.3.1']
REQUIREMENTS = ['pywemo==0.3.2']
_LOGGER = logging.getLogger(__name__)
@ -22,7 +22,9 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
import pywemo.discovery as discovery
if discovery_info is not None:
device = discovery.device_from_description(discovery_info[2])
location = discovery_info[2]
mac = discovery_info[3]
device = discovery.device_from_description(location, mac)
if device:
add_devices_callback([WemoSwitch(device)])

View File

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

View File

@ -17,7 +17,7 @@ import functools as ft
from collections import namedtuple
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
@ -741,6 +741,7 @@ class Config(object):
'location_name': self.location_name,
'time_zone': time_zone.zone,
'components': self.components,
'version': __version__
}

View File

@ -39,3 +39,43 @@ def color_RGB_to_xy(R, G, B):
# Convert XYZ to xy, see CIE 1931 color space on wikipedia
return X / (X + Y + Z), Y / (X + Y + Z)
# taken from
# https://github.com/benknight/hue-python-rgb-converter/blob/master/rgb_cie.py
# pylint: disable=bad-builtin
def color_xy_brightness_to_RGB(vX, vY, brightness):
'''
Convert from XYZ to RGB.
'''
brightness /= 255.
if brightness == 0:
return (0, 0, 0)
Y = brightness
X = (Y / vY) * vX
Z = (Y / vY) * (1 - vX - vY)
# Convert to RGB using Wide RGB D65 conversion.
r = X * 1.612 - Y * 0.203 - Z * 0.302
g = -X * 0.509 + Y * 1.412 + Z * 0.066
b = X * 0.026 - Y * 0.072 + Z * 0.962
# Apply reverse gamma correction.
r, g, b = map(
lambda x: (12.92 * x) if (x <= 0.0031308) else
((1.0 + 0.055) * pow(x, (1.0 / 2.4)) - 0.055),
[r, g, b]
)
# Bring all negative components to zero.
r, g, b = map(lambda x: max(0, x), [r, g, b])
# If one component is greater than 1, weight components by that value.
max_component = max(r, g, b)
if max_component > 1:
r, g, b = map(lambda x: x / max_component, [r, g, b])
r, g, b = map(lambda x: int(x * 255), [r, g, b])
return (r, g, b)

View File

@ -87,10 +87,10 @@ https://github.com/theolind/pymysensors/archive/d4b809c2167650691058d1e29bfd2c4b
pynetgear==0.3
# Netdisco (discovery)
netdisco==0.5
netdisco==0.5.1
# Wemo (switch.wemo)
pywemo==0.3.1
pywemo==0.3.2
# Wink (*.wink)
https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip#python-wink==0.1
@ -134,7 +134,7 @@ https://github.com/balloob/home-assistant-vera-api/archive/a8f823066ead6c7da6fb5
SoCo==0.11.1
# PlexAPI (media_player.plex)
https://github.com/adrienbrault/python-plexapi/archive/df2d0847e801d6d5cda920326d693cf75f304f1a.zip#python-plexapi==1.0.2
plexapi==1.1.0
# SNMP (device_tracker.snmp)
pysnmp==4.2.5

View File

@ -21,7 +21,7 @@ from homeassistant.exceptions import (
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_state_change
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
ATTR_FRIENDLY_NAME, TEMP_CELCIUS,
TEMP_FAHRENHEIT)
@ -555,6 +555,7 @@ class TestConfig(unittest.TestCase):
'location_name': None,
'time_zone': 'UTC',
'components': [],
'version': __version__,
}
self.assertEqual(expected, self.config.as_dict())