mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Merge remote-tracking branch 'upstream/master' into dev
This commit is contained in:
commit
c5f8095f53
@ -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
|
||||||
|
@ -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/).
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
173
homeassistant/components/device_tracker/ubus.py
Normal file
173
homeassistant/components/device_tracker/ubus.py
Normal 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"]
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
125
homeassistant/components/light/hyperion.py
Normal file
125
homeassistant/components/light/hyperion.py
Normal 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)
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
@ -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)])
|
||||||
|
@ -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 = '*'
|
||||||
|
@ -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__
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user