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/asuswrt.py
homeassistant/components/device_tracker/ddwrt.py homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/luci.py homeassistant/components/device_tracker/luci.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/thomson.py homeassistant/components/device_tracker/thomson.py
@ -49,6 +50,7 @@ omit =
homeassistant/components/light/hue.py homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py homeassistant/components/light/blinksticklight.py
homeassistant/components/light/hyperion.py
homeassistant/components/media_player/cast.py homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.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. - 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. - 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. - 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). - 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 generate reports, run `pylint homeassistant > pylint.txt` and `flake8 homeassistant --exclude bower_components,external > flake8.txt`. - 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. - 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/). - 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 homeassistant.components.api
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides a Rest API for Home Assistant. 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 re
import logging import logging

View File

@ -1,9 +1,10 @@
""" """
homeassistant.components.conversation homeassistant.components.conversation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to have conversations with Home Assistant. 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 logging
import re 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" DOMAIN = "discovery"
DEPENDENCIES = [] DEPENDENCIES = []
REQUIREMENTS = ['netdisco==0.5'] REQUIREMENTS = ['netdisco==0.5.1']
SCAN_INTERVAL = 300 # seconds SCAN_INTERVAL = 300 # seconds
@ -28,6 +28,7 @@ SERVICE_HUE = 'philips_hue'
SERVICE_CAST = 'google_cast' SERVICE_CAST = 'google_cast'
SERVICE_NETGEAR = 'netgear_router' SERVICE_NETGEAR = 'netgear_router'
SERVICE_SONOS = 'sonos' SERVICE_SONOS = 'sonos'
SERVICE_PLEX = 'plex_mediaserver'
SERVICE_HANDLERS = { SERVICE_HANDLERS = {
SERVICE_WEMO: "switch", SERVICE_WEMO: "switch",
@ -35,6 +36,7 @@ SERVICE_HANDLERS = {
SERVICE_HUE: "light", SERVICE_HUE: "light",
SERVICE_NETGEAR: 'device_tracker', SERVICE_NETGEAR: 'device_tracker',
SERVICE_SONOS: 'media_player', SERVICE_SONOS: 'media_player',
SERVICE_PLEX: 'media_player',
} }
@ -88,6 +90,7 @@ def setup(hass, config):
ATTR_DISCOVERED: info ATTR_DISCOVERED: info
}) })
# pylint: disable=unused-argument
def start_discovery(event): def start_discovery(event):
""" Start discovering. """ """ Start discovering. """
netdisco = DiscoveryService(SCAN_INTERVAL) netdisco = DiscoveryService(SCAN_INTERVAL)

View File

@ -1,8 +1,10 @@
""" """
homeassistant.components.downloader homeassistant.components.downloader
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to download files. 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 os
import logging import logging
@ -42,6 +44,10 @@ def setup(hass, config):
download_path = config[DOMAIN][CONF_DOWNLOAD_DIR] 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): if not os.path.isdir(download_path):
logger.error( logger.error(

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ 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 homeassistant.components.group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to group devices that can be turned on or off. 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 import homeassistant.core as ha
from homeassistant.helpers import generate_entity_id from homeassistant.helpers import generate_entity_id
from homeassistant.helpers.event import track_state_change from homeassistant.helpers.event import track_state_change

View File

@ -1,8 +1,10 @@
""" """
homeassistant.components.history homeassistant.components.history
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provide pre-made queries on top of the recorder component. 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 import re
from datetime import timedelta 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. This module provides an API and a HTTP interface for debug purposes.
By default it will run on port 8123. For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api.html
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."
}
""" """
import json import json
import threading import threading
import logging import logging

View File

@ -1,23 +1,10 @@
""" """
homeassistant.components.ifttt homeassistant.components.ifttt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component enable you to trigger Maker IFTTT recipes. 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 logging
import requests import requests

View File

@ -1,8 +1,10 @@
""" """
homeassistant.components.introduction homeassistant.components.introduction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component that will help guide the user taking its first steps. 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 import logging

View File

@ -1,8 +1,10 @@
""" """
homeassistant.components.keyboard homeassistant.components.keyboard
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to emulate keyboard presses on host machine. 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 import logging

View File

@ -2,6 +2,8 @@
homeassistant.components.light.hue homeassistant.components.light.hue
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Hue lights. Support for Hue lights.
https://home-assistant.io/components/light.hue.html
""" """
import logging import logging
import socket 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 - dekolight
- iLight - iLight
Configuration: https://home-assistant.io/components/light.limitlessled.html
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
""" """
import logging import logging

View File

@ -1,8 +1,10 @@
""" """
homeassistant.components.logbook homeassistant.components.logbook
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parses events and generates a human log. 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 datetime import timedelta
from itertools import groupby from itertools import groupby

View File

@ -28,6 +28,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
DISCOVERY_PLATFORMS = { DISCOVERY_PLATFORMS = {
discovery.SERVICE_CAST: 'cast', discovery.SERVICE_CAST: 'cast',
discovery.SERVICE_SONOS: 'sonos', discovery.SERVICE_SONOS: 'sonos',
discovery.SERVICE_PLEX: 'plex',
} }
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video' 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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.plex.html https://home-assistant.io/components/media_player.plex.html
""" """
import os
import json
import logging import logging
from datetime import timedelta 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 ( from homeassistant.components.media_player import (
MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, MediaPlayerDevice, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO) SUPPORT_NEXT_TRACK, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
from homeassistant.const import ( from homeassistant.const import (
STATE_IDLE, STATE_PLAYING, STATE_PAUSED, STATE_OFF, STATE_UNKNOWN) DEVICE_DEFAULT_NAME, STATE_IDLE, STATE_PLAYING,
import homeassistant.util as util STATE_PAUSED, STATE_OFF, STATE_UNKNOWN)
REQUIREMENTS = ['https://github.com/adrienbrault/python-plexapi/archive/' REQUIREMENTS = ['plexapi==1.1.0']
'df2d0847e801d6d5cda920326d693cf75f304f1a.zip'
'#python-plexapi==1.0.2']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1) 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__) _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, unused-argument def config_from_file(filename, config=None):
def setup_platform(hass, config, add_devices, discovery_info=None): ''' Small configuration file management function'''
""" Sets up the plex platform. """ if config:
from plexapi.myplex import MyPlexUser # We're writing configuration
from plexapi.exceptions import BadRequest 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_clients = {}
plex_sessions = {} plex_sessions = {}
@ -45,34 +121,34 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def update_devices(): def update_devices():
""" Updates the devices objects. """ """ Updates the devices objects. """
try: try:
devices = plexuser.devices() devices = plexserver.clients()
except BadRequest: except plexapi.exceptions.BadRequest:
_LOGGER.exception("Error listing plex devices") _LOGGER.exception("Error listing plex devices")
return return
new_plex_clients = [] new_plex_clients = []
for device in devices: for device in devices:
if (all(x not in ['client', 'player'] for x in device.provides) # For now, let's allow all deviceClass types
or 'PlexAPI' == device.product): if device.deviceClass in ['badClient']:
continue continue
if device.clientIdentifier not in plex_clients: if device.machineIdentifier not in plex_clients:
new_client = PlexClient(device, plex_sessions, update_devices, new_client = PlexClient(device, plex_sessions, update_devices,
update_sessions) update_sessions)
plex_clients[device.clientIdentifier] = new_client plex_clients[device.machineIdentifier] = new_client
new_plex_clients.append(new_client) new_plex_clients.append(new_client)
else: else:
plex_clients[device.clientIdentifier].set_device(device) plex_clients[device.machineIdentifier].set_device(device)
if new_plex_clients: 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) @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 plexapi.exceptions.BadRequest:
_LOGGER.exception("Error listing plex sessions") _LOGGER.exception("Error listing plex sessions")
return return
@ -84,10 +160,34 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
update_sessions() 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): class PlexClient(MediaPlayerDevice):
""" Represents a Plex device. """ """ 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): 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
@ -99,17 +199,23 @@ class PlexClient(MediaPlayerDevice):
self.device = device self.device = device
@property @property
def session(self): def unique_id(self):
""" Returns the session, if any. """ """ Returns the id of this plex client """
if self.device.clientIdentifier not in self.plex_sessions: return "{}.{}".format(
return None self.__class__, self.device.machineIdentifier or self.device.name)
return self.plex_sessions[self.device.clientIdentifier]
@property @property
def name(self): def name(self):
""" Returns the name of the device. """ """ 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 @property
def state(self): def state(self):
@ -120,7 +226,8 @@ class PlexClient(MediaPlayerDevice):
return STATE_PLAYING return STATE_PLAYING
elif state == 'paused': elif state == 'paused':
return 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 return STATE_IDLE
else: else:
return STATE_OFF return STATE_OFF
@ -196,16 +303,16 @@ class PlexClient(MediaPlayerDevice):
def media_play(self): def media_play(self):
""" media_play media player. """ """ media_play media player. """
self.device.play({'type': 'video'}) self.device.play()
def media_pause(self): def media_pause(self):
""" media_pause media player. """ """ media_pause media player. """
self.device.pause({'type': 'video'}) self.device.pause()
def media_next_track(self): def media_next_track(self):
""" Send next track command. """ """ Send next track command. """
self.device.skipNext({'type': 'video'}) self.device.skipNext()
def media_previous_track(self): def media_previous_track(self):
""" Send previous track command. """ """ Send previous track command. """
self.device.skipPrevious({'type': 'video'}) self.device.skipPrevious()

View File

@ -1,9 +1,11 @@
""" """
homeassistant.components.recorder 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. For more details about this component, please refer to the documentation at
Allows other components to query this database. https://home-assistant.io/components/recorder.html
""" """
import logging import logging
import threading import threading

View File

@ -1,11 +1,10 @@
""" """
homeassistant.components.scene homeassistant.components.scene
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Allows users to set and activate scenes.
Allows users to set and activate scenes within Home Assistant. For more details about this component, please refer to the documentation at
https://home-assistant.io/components/scene.html
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.
""" """
import logging import logging
from collections import namedtuple from collections import namedtuple

View File

@ -1,9 +1,11 @@
""" """
homeassistant.components.script homeassistant.components.script
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
entity_id
Scripts are a sequence of actions that can be triggered manually Scripts are a sequence of actions that can be triggered manually
by the user or automatically based upon automation events, etc. 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 import logging
from datetime import timedelta from datetime import timedelta

View File

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

View File

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

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.6.dev0" __version__ = "0.7.6"
# 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

@ -17,7 +17,7 @@ import functools as ft
from collections import namedtuple from collections import namedtuple
from homeassistant.const import ( 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, SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL, EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
@ -741,6 +741,7 @@ class Config(object):
'location_name': self.location_name, 'location_name': self.location_name,
'time_zone': time_zone.zone, 'time_zone': time_zone.zone,
'components': self.components, '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 # Convert XYZ to xy, see CIE 1931 color space on wikipedia
return X / (X + Y + Z), Y / (X + Y + Z) 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 pynetgear==0.3
# Netdisco (discovery) # Netdisco (discovery)
netdisco==0.5 netdisco==0.5.1
# Wemo (switch.wemo) # Wemo (switch.wemo)
pywemo==0.3.1 pywemo==0.3.2
# Wink (*.wink) # Wink (*.wink)
https://github.com/balloob/python-wink/archive/c2b700e8ca866159566ecf5e644d9c297f69f257.zip#python-wink==0.1 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 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 plexapi==1.1.0
# SNMP (device_tracker.snmp) # SNMP (device_tracker.snmp)
pysnmp==4.2.5 pysnmp==4.2.5

View File

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