merged upstream and fixed conflict

This commit is contained in:
Todd Ingarfield
2015-10-15 10:13:02 -05:00
89 changed files with 2545 additions and 1288 deletions

View File

@ -26,6 +26,9 @@ omit =
homeassistant/components/zwave.py
homeassistant/components/*/zwave.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/ifttt.py
homeassistant/components/browser.py
homeassistant/components/camera/*
@ -39,13 +42,16 @@ omit =
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/keyboard.py
homeassistant/components/light/hue.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/mpd.py
@ -60,10 +66,12 @@ omit =
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/xmpp.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/command_sensor.py
homeassistant/components/sensor/cpuinfo.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/forecast.py
@ -71,7 +79,6 @@ omit =
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rfxtrx.py
homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/swiss_public_transport.py

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ import os
from homeassistant.components import verisure
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.entity import Entity
@ -29,6 +29,7 @@ SERVICE_TO_METHOD = {
SERVICE_ALARM_DISARM: 'alarm_disarm',
SERVICE_ALARM_ARM_HOME: 'alarm_arm_home',
SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away',
SERVICE_ALARM_TRIGGER: 'alarm_trigger'
}
ATTR_CODE = 'code'
@ -53,9 +54,9 @@ def setup(hass, config):
target_alarms = component.extract_from_service(service)
if ATTR_CODE not in service.data:
return
code = service.data[ATTR_CODE]
code = None
else:
code = service.data[ATTR_CODE]
method = SERVICE_TO_METHOD[service.service]
@ -72,36 +73,50 @@ def setup(hass, config):
return True
def alarm_disarm(hass, code, entity_id=None):
def alarm_disarm(hass, code=None, entity_id=None):
""" Send the alarm the command for disarm. """
data = {ATTR_CODE: code}
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
def alarm_arm_home(hass, code, entity_id=None):
def alarm_arm_home(hass, code=None, entity_id=None):
""" Send the alarm the command for arm home. """
data = {ATTR_CODE: code}
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
def alarm_arm_away(hass, code, entity_id=None):
def alarm_arm_away(hass, code=None, entity_id=None):
""" Send the alarm the command for arm away. """
data = {ATTR_CODE: code}
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
def alarm_trigger(hass, code=None, entity_id=None):
""" Send the alarm the command for trigger. """
data = {}
if code:
data[ATTR_CODE] = code
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
""" ABC for alarm control devices. """
@ -123,6 +138,10 @@ class AlarmControlPanel(Entity):
""" Send arm away command. """
raise NotImplementedError()
def alarm_trigger(self, code=None):
""" Send alarm trigger command. """
raise NotImplementedError()
@property
def state_attributes(self):
""" Return the state attributes. """

View File

@ -0,0 +1,148 @@
"""
homeassistant.components.alarm_control_panel.manual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for manual alarms.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.manual.html
"""
import logging
import datetime
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.helpers.event import track_point_in_time
import homeassistant.util.dt as dt_util
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
DEFAULT_ALARM_NAME = 'HA Alarm'
DEFAULT_PENDING_TIME = 60
DEFAULT_TRIGGER_TIME = 120
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the manual alarm platform. """
add_devices([ManualAlarm(
hass,
config.get('name', DEFAULT_ALARM_NAME),
config.get('code'),
config.get('pending_time', DEFAULT_PENDING_TIME),
config.get('trigger_time', DEFAULT_TRIGGER_TIME),
)])
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class ManualAlarm(alarm.AlarmControlPanel):
"""
Represents an alarm status.
When armed, will be pending for 'pending_time', after that armed.
When triggered, will be pending for 'trigger_time'. After that will be
triggered for 'trigger_time', after that we return to disarmed.
"""
def __init__(self, hass, name, code, pending_time, trigger_time):
self._state = STATE_ALARM_DISARMED
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._pending_time = datetime.timedelta(seconds=pending_time)
self._trigger_time = datetime.timedelta(seconds=trigger_time)
self._state_ts = None
@property
def should_poll(self):
""" No polling needed. """
return False
@property
def name(self):
""" Returns the name of the device. """
return self._name
@property
def state(self):
""" Returns the state of the device. """
if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING
if self._state == STATE_ALARM_TRIGGERED and self._trigger_time:
if self._state_ts + self._trigger_time > dt_util.utcnow():
return STATE_ALARM_PENDING
elif dt_util.utcnow() >= self._state_ts + (2 * self._trigger_time):
return STATE_ALARM_DISARMED
return self._state
@property
def code_format(self):
""" One or more characters. """
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
""" Send disarm command. """
if not self._validate_code(code, STATE_ALARM_DISARMED):
return
self._state = STATE_ALARM_DISARMED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
def alarm_arm_home(self, code=None):
""" Send arm home command. """
if not self._validate_code(code, STATE_ALARM_ARMED_HOME):
return
self._state = STATE_ALARM_ARMED_HOME
self._state_ts = dt_util.utcnow()
self.update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._pending_time)
def alarm_arm_away(self, code=None):
""" Send arm away command. """
if not self._validate_code(code, STATE_ALARM_ARMED_AWAY):
return
self._state = STATE_ALARM_ARMED_AWAY
self._state_ts = dt_util.utcnow()
self.update_ha_state()
if self._pending_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._pending_time)
def alarm_trigger(self, code=None):
""" Send alarm trigger command. No code needed. """
self._state = STATE_ALARM_TRIGGERED
self._state_ts = dt_util.utcnow()
self.update_ha_state()
if self._trigger_time:
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + self._trigger_time)
track_point_in_time(
self._hass, self.update_ha_state,
self._state_ts + 2 * self._trigger_time)
def _validate_code(self, code, state):
""" Validate given code. """
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Invalid code given for %s', state)
return check

View File

@ -1,68 +1,18 @@
"""
homeassistant.components.alarm_control_panel.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This platform enables the possibility to control a MQTT alarm.
In this platform, 'state_topic' and 'command_topic' are required.
The alarm will only change state after receiving the a new state
from 'state_topic'. If these messages are published with RETAIN flag,
the MQTT alarm will receive an instant state update after subscription
and will start with correct state. Otherwise, the initial state will
be 'unknown'.
Configuration:
alarm_control_panel:
platform: mqtt
name: "MQTT Alarm"
state_topic: "home/alarm"
command_topic: "home/alarm/set"
qos: 0
payload_disarm: "DISARM"
payload_arm_home: "ARM_HOME"
payload_arm_away: "ARM_AWAY"
code: "mySecretCode"
Variables:
name
*Optional
The name of the alarm. Default is 'MQTT Alarm'.
state_topic
*Required
The MQTT topic subscribed to receive state updates.
command_topic
*Required
The MQTT topic to publish commands to change the alarm state.
qos
*Optional
The maximum QoS level of the state topic. Default is 0.
This QoS will also be used to publishing messages.
payload_disarm
*Optional
The payload do disarm alarm. Default is "DISARM".
payload_arm_home
*Optional
The payload to set armed-home mode. Default is "ARM_HOME".
payload_arm_away
*Optional
The payload to set armed-away mode. Default is "ARM_AWAY".
code
*Optional
If defined, specifies a code to enable or disable the alarm in the frontend.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.mqtt.html
"""
import logging
import homeassistant.components.mqtt as mqtt
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (STATE_UNKNOWN)
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
_LOGGER = logging.getLogger(__name__)
@ -99,6 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-arguments, too-many-instance-attributes
# pylint: disable=abstract-method
class MqttAlarm(alarm.AlarmControlPanel):
""" represents a MQTT alarm status within home assistant. """
@ -113,10 +64,15 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
self._code = code
self._code = str(code) if code else None
def message_received(topic, payload, qos):
""" A new MQTT message has been received. """
if payload not in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED):
_LOGGER.warning('Received unexpected payload: %s', payload)
return
self._state = payload
self.update_ha_state()
@ -144,24 +100,28 @@ class MqttAlarm(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None):
""" Send disarm command. """
if code == str(self._code) or self.code_format is None:
mqtt.publish(self.hass, self._command_topic,
self._payload_disarm, self._qos)
else:
_LOGGER.warning("Wrong code entered while disarming!")
if not self._validate_code(code, 'disarming'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_disarm, self._qos)
def alarm_arm_home(self, code=None):
""" Send arm home command. """
if code == str(self._code) or self.code_format is None:
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_home, self._qos)
else:
_LOGGER.warning("Wrong code entered while arming home!")
if not self._validate_code(code, 'arming home'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_home, self._qos)
def alarm_arm_away(self, code=None):
""" Send arm away command. """
if code == str(self._code) or self.code_format is None:
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_away, self._qos)
else:
_LOGGER.warning("Wrong code entered while arming away!")
if not self._validate_code(code, 'arming away'):
return
mqtt.publish(self.hass, self._command_topic,
self._payload_arm_away, self._qos)
def _validate_code(self, code, state):
""" Validate given code. """
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
return check

View File

@ -33,6 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(alarms)
# pylint: disable=abstract-method
class VerisureAlarm(alarm.AlarmControlPanel):
""" Represents a Verisure alarm status. """

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.event
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#event-trigger
"""
import logging

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#mqtt-trigger
"""
import logging

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.numeric_state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#numeric-state-trigger
"""
import logging

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.state
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#state-trigger
"""
import logging
@ -28,6 +30,11 @@ def trigger(hass, config, action):
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
if isinstance(from_state, bool) or isinstance(to_state, bool):
logging.getLogger(__name__).error(
'Config error. Surround to/from values with quotes.')
return False
def state_automation_listener(entity, from_s, to_s):
""" Listens for state changes and calls action. """
action()

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#sun-trigger
"""
import logging
from datetime import timedelta

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.time
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#time-trigger
"""
import logging

View File

@ -1,8 +1,10 @@
"""
homeassistant.components.automation.zone
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Offers zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation.html#zone-trigger
"""
import logging

View File

@ -3,42 +3,6 @@ homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras.
As part of the basic support the following features will be provided:
-MJPEG video streaming
To use this component, add the following to your configuration.yaml file.
camera:
platform: foscam
name: Door Camera
ip: 192.168.0.123
port: 88
username: YOUR_USERNAME
password: YOUR_PASSWORD
Variables:
ip
*Required
The IP address of your Foscam device.
username
*Required
The username of a visitor or operator of your camera. Oddly admin accounts
don't seem to have access to take snapshots.
password
*Required
The password for accessing your camera.
name
*Optional
This parameter allows you to override the name of your camera in homeassistant.
port
*Optional
The port that the camera is running on. The default is 88.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.foscam.html
"""

View File

@ -3,43 +3,8 @@ homeassistant.components.camera.generic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for IP Cameras.
This component provides basic support for IP cameras. For the basic support to
work you camera must support accessing a JPEG snapshot via a URL and you will
need to specify the "still_image_url" parameter which should be the location of
the JPEG image.
As part of the basic support the following features will be provided:
- MJPEG video streaming
- Saving a snapshot
- Recording(JPEG frame capture)
To use this component, add the following to your configuration.yaml file.
camera:
platform: generic
name: Door Camera
username: YOUR_USERNAME
password: YOUR_PASSWORD
still_image_url: http://YOUR_CAMERA_IP_AND_PORT/image.jpg
Variables:
still_image_url
*Required
The URL your camera serves the image on, eg. http://192.168.1.21:2112/
name
*Optional
This parameter allows you to override the name of your camera in Home
Assistant.
username
*Optional
The username for accessing your camera.
password
*Optional
The password for accessing your camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.generic.html
"""
import logging
from requests.auth import HTTPBasicAuth

View File

@ -63,6 +63,14 @@ def setup(hass, config):
'still_image_url': 'http://home-assistant.io/demo/webcam.jpg',
}})
# Setup alarm_control_panel
bootstrap.setup_component(
hass, 'alarm_control_panel',
{'alarm_control_panel': {
'platform': 'manual',
'name': 'Test Alarm',
}})
# Setup scripts
bootstrap.setup_component(
hass, 'script',

View File

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

View File

@ -4,41 +4,8 @@ homeassistant.components.device_tracker.actiontec
Device tracker platform that supports scanning an Actiontec MI424WR
(Verizon FIOS) router for device presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Actiontec tracker you will need to add something like the
following to your configuration.yaml file. If you experience disconnects
you can modify the home_interval variable.
device_tracker:
platform: actiontec
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
# optional:
home_interval: 10
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
home_interval
*Optional
If the home_interval is set then the component will not let a device
be AWAY if it has been HOME in the last home_interval minutes. This is
in addition to the 3 minute wait built into the device_tracker component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.actiontec.html
"""
import logging
from datetime import timedelta
@ -56,7 +23,7 @@ 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)
# interval in minutes to exclude devices from a scan while they are home
# Interval in minutes to exclude devices from a scan while they are home
CONF_HOME_INTERVAL = "home_interval"
_LOGGER = logging.getLogger(__name__)

View File

@ -4,33 +4,8 @@ homeassistant.components.device_tracker.aruba
Device tracker platform that supports scanning a Aruba Access Point for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the Aruba tracker you will need to add something like the following
to your configuration.yaml file. You also need to enable Telnet in the
configuration page of your router.
device_tracker:
platform: aruba
host: YOUR_ACCESS_POINT_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.aruba.html
"""
import logging
from datetime import timedelta

View File

@ -161,9 +161,10 @@ class AsusWrtDeviceScanner(object):
# For leases where the client doesn't set a hostname, ensure
# it is blank and not '*', which breaks the entity_id down
# the line
host = match.group('host')
if host == '*':
host = ''
if match:
host = match.group('host')
if host == '*':
host = ''
devices[match.group('ip')] = {
'host': host,
@ -174,6 +175,6 @@ class AsusWrtDeviceScanner(object):
for neighbor in neighbors:
match = _IP_NEIGH_REGEX.search(neighbor.decode('utf-8'))
if match.group('ip') in devices:
if match and match.group('ip') in devices:
devices[match.group('ip')]['status'] = match.group('status')
return devices

View File

@ -4,30 +4,8 @@ homeassistant.components.device_tracker.ddwrt
Device tracker platform that supports scanning a DD-WRT router for device
presence.
Configuration:
To use the DD-WRT tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: ddwrt
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.ddwrt.html
"""
import logging
from datetime import timedelta
@ -46,6 +24,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
# pylint: disable=unused-argument
@ -77,7 +56,7 @@ class DdWrtDeviceScanner(object):
self.last_results = {}
self.mac2name = None
self.mac2name = {}
# Test the router is accessible
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
@ -98,30 +77,33 @@ class DdWrtDeviceScanner(object):
with self.lock:
# if not initialised and not already scanned and not found
if self.mac2name is None or device not in self.mac2name:
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
data = self.get_ddwrt_data(url)
if not data:
return
return None
dhcp_leases = data.get('dhcp_leases', None)
if dhcp_leases:
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device, None)
if not dhcp_leases:
return None
# remove leading and trailing single quotes
cleaned_str = dhcp_leases.strip().strip('"')
elements = cleaned_str.split('","')
num_clients = int(len(elements)/5)
self.mac2name = {}
for idx in range(0, num_clients):
# this is stupid but the data is a single array
# every 5 elements represents one hosts, the MAC
# is the third element and the name is the first
mac_index = (idx * 5) + 2
if mac_index < len(elements):
mac = elements[mac_index]
self.mac2name[mac] = elements[idx * 5]
return self.mac2name.get(device)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
@ -141,29 +123,25 @@ class DdWrtDeviceScanner(object):
if not data:
return False
if data:
self.last_results = []
active_clients = data.get('active_wireless', None)
if active_clients:
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
self.last_results = []
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
active_clients = data.get('active_wireless', None)
if not active_clients:
return False
num_clients = int(len(elements)/9)
for idx in range(0, num_clients):
# get every 9th element which is the MAC address
index = idx * 9
if index < len(elements):
self.last_results.append(elements[index])
# This is really lame, instead of using JSON the DD-WRT UI
# uses its own data format for some reason and then
# regex's out values so I guess I have to do the same,
# LAME!!!
return True
# remove leading and trailing single quotes
clean_str = active_clients.strip().strip("'")
elements = clean_str.split("','")
return False
self.last_results.extend(item for item in elements
if _MAC_REGEX.match(item))
return True
def get_ddwrt_data(self, url):
""" Retrieve data from DD-WRT and return parsed result. """

View File

@ -0,0 +1,71 @@
"""
homeassistant.components.device_tracker.geofancy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Geofancy platform for the device tracker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.geofancy.html
"""
from homeassistant.const import (
HTTP_UNPROCESSABLE_ENTITY, HTTP_INTERNAL_SERVER_ERROR)
DEPENDENCIES = ['http']
_SEE = 0
URL_API_GEOFANCY_ENDPOINT = "/api/geofancy"
def setup_scanner(hass, config, see):
""" Set up an endpoint for the Geofancy app. """
# Use a global variable to keep setup_scanner compact when using a callback
global _SEE
_SEE = see
# POST would be semantically better, but that currently does not work
# since Geofancy sends the data as key1=value1&key2=value2
# in the request body, while Home Assistant expects json there.
hass.http.register_path(
'GET', URL_API_GEOFANCY_ENDPOINT, _handle_get_api_geofancy)
return True
def _handle_get_api_geofancy(handler, path_match, data):
""" Geofancy message received. """
if not isinstance(data, dict):
handler.write_json_message(
"Error while parsing Geofancy message.",
HTTP_INTERNAL_SERVER_ERROR)
return
if 'latitude' not in data or 'longitude' not in data:
handler.write_json_message(
"Location not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
if 'device' not in data or 'id' not in data:
handler.write_json_message(
"Device id or location id not specified.",
HTTP_UNPROCESSABLE_ENTITY)
return
try:
gps_coords = (float(data['latitude']), float(data['longitude']))
except ValueError:
# If invalid latitude / longitude format
handler.write_json_message(
"Invalid latitude / longitude format.",
HTTP_UNPROCESSABLE_ENTITY)
return
# entity id's in Home Assistant must be alphanumerical
device_uuid = data['device']
device_entity_id = device_uuid.replace('-', '')
_SEE(dev_id=device_entity_id, gps=gps_coords, location_name=data['id'])
handler.write_json_message("Geofancy message processed")

View File

@ -4,33 +4,8 @@ homeassistant.components.device_tracker.luci
Device tracker platform that supports scanning a OpenWRT router for device
presence.
It's required that the luci RPC package is installed on the OpenWRT router:
# opkg install luci-mod-rpc
Configuration:
To use the Luci tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: luci
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.luci.html
"""
import logging
import json

View File

@ -1,15 +1,10 @@
"""
homeassistant.components.device_tracker.mqtt
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MQTT platform for the device tracker.
device_tracker:
platform: mqtt
qos: 1
devices:
paulus_oneplus: /location/paulus
annetherese_n4: /location/annetherese
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt.html
"""
import logging
from homeassistant import util

View File

@ -4,30 +4,8 @@ homeassistant.components.device_tracker.netgear
Device tracker platform that supports scanning a Netgear router for device
presence.
Configuration:
To use the Netgear tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: netgear
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.netgear.html
"""
import logging
from datetime import timedelta

View File

@ -3,26 +3,8 @@ homeassistant.components.device_tracker.nmap
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Device tracker platform that supports scanning a network with nmap.
Configuration:
To use the nmap tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: nmap_tracker
hosts: 192.168.1.1/24
Variables:
hosts
*Required
The IP addresses to scan in the network-prefix notation (192.168.1.1/24) or
the range notation (192.168.1.1-255).
home_interval
*Optional
Number of minutes it will not scan devices that it found in previous results.
This is to save battery.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.nmap_scanner.html
"""
import logging
from datetime import timedelta
@ -117,15 +99,18 @@ class NmapDeviceScanner(object):
scanner = PortScanner()
options = "-F --host-timeout 5"
exclude_targets = set()
if self.home_interval:
now = dt_util.now()
for host in self.last_results:
if host.last_update + self.home_interval > now:
exclude_targets.add(host)
if len(exclude_targets) > 0:
target_list = [t.ip for t in exclude_targets]
options += " --exclude {}".format(",".join(target_list))
boundary = dt_util.now() - self.home_interval
last_results = [device for device in self.last_results
if device.last_update > boundary]
if last_results:
# Pylint is confused here.
# pylint: disable=no-member
options += " --exclude {}".format(",".join(device.ip for device
in last_results))
else:
last_results = []
try:
result = scanner.scan(hosts=self.hosts, arguments=options)
@ -133,18 +118,17 @@ class NmapDeviceScanner(object):
return False
now = dt_util.now()
self.last_results = []
for ipv4, info in result['scan'].items():
if info['status']['state'] != 'up':
continue
name = info['hostnames'][0] if info['hostnames'] else ipv4
name = info['hostnames'][0]['name'] if info['hostnames'] else ipv4
# Mac address only returned if nmap ran as root
mac = info['addresses'].get('mac') or _arp(ipv4)
if mac is None:
continue
device = Device(mac.upper(), name, ipv4, now)
self.last_results.append(device)
self.last_results.extend(exclude_targets)
last_results.append(Device(mac.upper(), name, ipv4, now))
self.last_results = last_results
_LOGGER.info("nmap scan successful")
return True

View File

@ -1,11 +1,10 @@
"""
homeassistant.components.device_tracker.owntracks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
OwnTracks platform for the device tracker.
device_tracker:
platform: owntracks
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks.html
"""
import json
import logging

View File

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

View File

@ -4,32 +4,8 @@ homeassistant.components.device_tracker.thomson
Device tracker platform that supports scanning a THOMSON router for device
presence.
This device tracker needs telnet to be enabled on the router.
Configuration:
To use the THOMSON tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: thomson
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.thomson.html
"""
import logging
from datetime import timedelta

View File

@ -4,36 +4,8 @@ homeassistant.components.device_tracker.tomato
Device tracker platform that supports scanning a Tomato router for device
presence.
Configuration:
To use the Tomato tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: tomato
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
http_id: ABCDEFG
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
http_id
*Required
The value can be obtained by logging in to the Tomato admin interface and
search for http_id in the page source code.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tomato.html
"""
import logging
import json

View File

@ -4,30 +4,8 @@ homeassistant.components.device_tracker.tplink
Device tracker platform that supports scanning a TP-Link router for device
presence.
Configuration:
To use the TP-Link tracker you will need to add something like the following
to your configuration.yaml file.
device_tracker:
platform: tplink
host: YOUR_ROUTER_IP
username: YOUR_ADMIN_USERNAME
password: YOUR_ADMIN_PASSWORD
Variables:
host
*Required
The IP address of your router, e.g. 192.168.1.1.
username
*Required
The username of an user with administrative privileges, usually 'admin'.
password
*Required
The password for your given admin account.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.tplink.html
"""
import base64
import logging
@ -54,10 +32,13 @@ def get_scanner(hass, config):
_LOGGER):
return None
scanner = Tplink2DeviceScanner(config[DOMAIN])
scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
scanner = Tplink2DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@ -156,7 +137,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
with self.lock:
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/data/map_access_wireless_client_grid.json'\
url = 'http://{}/data/map_access_wireless_client_grid.json' \
.format(self.host)
referer = 'http://{}'.format(self.host)
@ -166,7 +147,7 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
b64_encoded_username_password = base64.b64encode(
username_password.encode('ascii')
).decode('ascii')
cookie = 'Authorization=Basic {}'\
cookie = 'Authorization=Basic {}' \
.format(b64_encoded_username_password)
response = requests.post(url, headers={'referer': referer,
@ -183,7 +164,119 @@ class Tplink2DeviceScanner(TplinkDeviceScanner):
self.last_results = {
device['mac_addr'].replace('-', ':'): device['name']
for device in result
}
}
return True
return False
class Tplink3DeviceScanner(TplinkDeviceScanner):
"""
This class queries the Archer C9 router running version 150811 or higher
of TP-Link firmware for connected devices.
"""
def __init__(self, config):
self.stok = ''
self.sysauth = ''
super(Tplink3DeviceScanner, self).__init__(config)
def scan_devices(self):
"""
Scans for new devices and return a list containing found device ids.
"""
self._update_info()
return self.last_results.keys()
# pylint: disable=no-self-use
def get_device_name(self, device):
"""
The TP-Link firmware doesn't save the name of the wireless device.
We are forced to use the MAC address as name here.
"""
return self.last_results.get(device)
def _get_auth_tokens(self):
"""
Retrieves auth tokens from the router.
"""
_LOGGER.info("Retrieving auth tokens...")
url = 'http://{}/cgi-bin/luci/;stok=/login?form=login' \
.format(self.host)
referer = 'http://{}/webpages/login.html'.format(self.host)
# if possible implement rsa encryption of password here
response = requests.post(url,
params={'operation': 'login',
'username': self.username,
'password': self.password},
headers={'referer': referer})
try:
self.stok = response.json().get('data').get('stok')
_LOGGER.info(self.stok)
regex_result = re.search('sysauth=(.*);',
response.headers['set-cookie'])
self.sysauth = regex_result.group(1)
_LOGGER.info(self.sysauth)
return True
except ValueError:
_LOGGER.error("Couldn't fetch auth tokens!")
return False
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""
Ensures the information from the TP-Link router is up to date.
Returns boolean if scanning successful.
"""
with self.lock:
if (self.stok == '') or (self.sysauth == ''):
self._get_auth_tokens()
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics' \
.format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url,
params={'operation': 'load'},
headers={'referer': referer},
cookies={'sysauth': self.sysauth})
try:
json_response = response.json()
if json_response.get('success'):
result = response.json().get('data')
else:
if json_response.get('errorcode') == 'timeout':
_LOGGER.info("Token timed out. "
"Relogging on next scan.")
self.stok = ''
self.sysauth = ''
return False
else:
_LOGGER.error("An unknown error happened "
"while fetching data.")
return False
except ValueError:
_LOGGER.error("Router didn't respond with JSON. "
"Check if credentials are correct.")
return False
if result:
self.last_results = {
device['mac'].replace('-', ':'): device['mac']
for device in result
}
return True
return False

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,233 @@
"""
homeassistant.components.media_player.firetv
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides control over an Amazon Fire TV (/stick) via
python-firetv, a Python 2.x module with a helper script
that exposes a HTTP server to fetch state and perform
actions.
Steps to configure your Amazon Fire TV stick with Home Assistant:
1. Turn on ADB Debugging on your Amazon Fire TV:
a. From the main (Launcher) screen, select Settings.
b. Select System > Developer Options.
c. Select ADB Debugging.
2. Find Amazon Fire TV device IP:
a. From the main (Launcher) screen, select Settings.
b. Select System > About > Network.
3. `pip install firetv[firetv-server]` into a Python 2.x environment
4. `firetv-server -d <fire tv device IP>:5555`, background the process
5. Configure Home Assistant as follows:
media_player:
platform: firetv
# optional: where firetv-server is running (default is 'localhost:5556')
host: localhost:5556
# optional: device id (default is 'default')
device: livingroom-firetv
# optional: friendly name (default is 'Amazon Fire TV')
name: My Amazon Fire TV
Note that python-firetv has support for multiple Amazon Fire TV devices.
If you have more than one configured, be sure to specify the device id used.
Run `firetv-server -h` and/or view the source for complete capabilities.
Possible states are:
- off (TV screen is dark)
- standby (standard UI is active - not apps)
- idle (screen saver is active)
- play (video is playing)
- pause (video is paused)
- disconnected (can't communicate with device)
"""
import logging
import requests
from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_IDLE, STATE_OFF,
STATE_UNKNOWN, STATE_STANDBY)
from homeassistant.components.media_player import (
MediaPlayerDevice,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK)
SUPPORT_FIRETV = SUPPORT_PAUSE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_VOLUME_SET
DOMAIN = 'firetv'
DEVICE_LIST_URL = 'http://{0}/devices/list'
DEVICE_STATE_URL = 'http://{0}/devices/state/{1}'
DEVICE_ACTION_URL = 'http://{0}/devices/action/{1}/{2}'
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the firetv platform. """
host = config.get('host', 'localhost:5556')
device_id = config.get('device', 'default')
try:
response = requests.get(DEVICE_LIST_URL.format(host)).json()
if device_id in response['devices'].keys():
add_devices([
FireTVDevice(
host,
device_id,
config.get('name', 'Amazon Fire TV')
)
])
_LOGGER.info(
'Device %s accessible and ready for control', device_id)
else:
_LOGGER.warn(
'Device %s is not registered with firetv-server', device_id)
except requests.exceptions.RequestException:
_LOGGER.error('Could not connect to firetv-server at %s', host)
class FireTV(object):
""" firetv-server client.
Should a native Python 3 ADB module become available,
python-firetv can support Python 3, it can be added
as a dependency, and this class can be dispensed of.
For now, it acts as a client to the firetv-server
HTTP server (which must be running via Python 2).
"""
def __init__(self, host, device_id):
self.host = host
self.device_id = device_id
@property
def state(self):
""" Get the device state.
An exception means UNKNOWN state.
"""
try:
response = requests.get(
DEVICE_STATE_URL.format(
self.host,
self.device_id
)
).json()
return response.get('state', STATE_UNKNOWN)
except requests.exceptions.RequestException:
_LOGGER.error(
'Could not retrieve device state for %s', self.device_id)
return STATE_UNKNOWN
def action(self, action_id):
""" Perform an action on the device.
There is no action acknowledgment, so exceptions
result in a pass.
"""
try:
requests.get(
DEVICE_ACTION_URL.format(
self.host,
self.device_id,
action_id
)
)
except requests.exceptions.RequestException:
_LOGGER.error(
'Action request for %s was not accepted for device %s',
action_id, self.device_id)
class FireTVDevice(MediaPlayerDevice):
""" Represents an Amazon Fire TV device on the network. """
def __init__(self, host, device, name):
self._firetv = FireTV(host, device)
self._name = name
self._state = STATE_UNKNOWN
@property
def name(self):
""" Get the device name. """
return self._name
@property
def should_poll(self):
""" Device should be polled. """
return True
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
return SUPPORT_FIRETV
@property
def state(self):
""" State of the player. """
return self._state
def update(self):
""" Update device state. """
self._state = {
'idle': STATE_IDLE,
'off': STATE_OFF,
'play': STATE_PLAYING,
'pause': STATE_PAUSED,
'standby': STATE_STANDBY,
'disconnected': STATE_UNKNOWN,
}.get(self._firetv.state, STATE_UNKNOWN)
def turn_on(self):
""" Turns on the device. """
self._firetv.action('turn_on')
def turn_off(self):
""" Turns off the device. """
self._firetv.action('turn_off')
def media_play(self):
""" Send play commmand. """
self._firetv.action('media_play')
def media_pause(self):
""" Send pause command. """
self._firetv.action('media_pause')
def media_play_pause(self):
""" Send play/pause command. """
self._firetv.action('media_play_pause')
def volume_up(self):
""" Send volume up command. """
self._firetv.action('volume_up')
def volume_down(self):
""" Send volume down command. """
self._firetv.action('volume_down')
def media_previous_track(self):
""" Send previous track command (results in rewind). """
self._firetv.action('media_previous')
def media_next_track(self):
""" Send next track command (results in fast-forward). """
self._firetv.action('media_next')
def media_seek(self, position):
raise NotImplementedError()
def mute_volume(self, mute):
raise NotImplementedError()
def play_youtube(self, media_id):
raise NotImplementedError()
def set_volume_level(self, volume):
raise NotImplementedError()

View File

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

View File

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

View File

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

View File

@ -3,26 +3,8 @@ homeassistant.components.notify.file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
File notification service.
Configuration:
To use the File notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: file
filename: FILENAME
timestamp: 1 or 0
Variables:
filename
*Required
Name of the file to use. The file will be created if it doesn't exist and saved
in your config/ folder.
timestamp
*Required
Add a timestamp to the entry, valid entries are 1 or 0.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.file.html
"""
import logging
import os

View File

@ -3,52 +3,8 @@ homeassistant.components.notify.instapush
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Instapush notification service.
Configuration:
To use the Instapush notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: instapush
api_key: YOUR_APP_KEY
app_secret: YOUR_APP_SECRET
event: YOUR_EVENT
tracker: YOUR_TRACKER
Variables:
api_key
*Required
To retrieve this value log into your account at https://instapush.im and go
to 'APPS', choose an app, and check 'Basic Info'.
app_secret
*Required
To get this value log into your account at https://instapush.im and go to
'APPS'. The 'Application ID' can be found under 'Basic Info'.
event
*Required
To retrieve a valid event log into your account at https://instapush.im and go
to 'APPS'. If you have no events to use with Home Assistant, create one event
for your app.
tracker
*Required
To retrieve the tracker value log into your account at https://instapush.im and
go to 'APPS', choose the app, and check the event entries.
Example usage of Instapush if you have an event 'notification' and a tracker
'home-assistant'.
curl -X POST \
-H "x-instapush-appid: YOUR_APP_KEY" \
-H "x-instapush-appsecret: YOUR_APP_SECRET" \
-H "Content-Type: application/json" \
-d '{"event":"notification","trackers":{"home-assistant":"Switch 1"}}' \
https://api.instapush.im/v1/post
Details for the API : https://instapush.im/developer/rest
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.instapush.html
"""
import logging
import json

View File

@ -3,23 +3,8 @@ homeassistant.components.notify.nma
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NMA (Notify My Android) notification service.
Configuration:
To use the NMA notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: nma
api_key: YOUR_API_KEY
Variables:
api_key
*Required
Enter the API key for NMA. Go to https://www.notifymyandroid.com and create a
new API key to use with Home Assistant.
Details for the API : https://www.notifymyandroid.com/api.jsp
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.nma.html
"""
import logging
import xml.etree.ElementTree as ET

View File

@ -3,21 +3,8 @@ homeassistant.components.notify.pushbullet
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
PushBullet platform for notify component.
Configuration:
To use the PushBullet notifier you will need to add something like the
following to your configuration.yaml file.
notify:
platform: pushbullet
api_key: YOUR_API_KEY
Variables:
api_key
*Required
Enter the API key for PushBullet. Go to https://www.pushbullet.com/ to retrieve
your API key.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.pushbullet.html
"""
import logging

View File

@ -3,35 +3,8 @@ homeassistant.components.notify.pushover
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pushover platform for notify component.
Configuration:
To use the Pushover notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: pushover
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
user_key: ABCDEFGHJKLMNOPQRSTUVXYZ
Variables:
api_key
*Required
This parameter is optional but should be configured, in order to get an API
key you should go to https://pushover.net and register a new application.
This is a quote from the pushover website regarding free/open source apps:
"If you are creating a client-side library, application, or open source project
that will be redistributed and installed by end-users, you may want to require
each of your users to register their own application rather than including your
own API token with the software."
When setting up the application I recommend using the icon located here:
https://home-assistant.io/images/favicon-192x192.png
user_key
*Required
To retrieve this value log into your account at https://pushover.net
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.pushover.html
"""
import logging

View File

@ -3,27 +3,8 @@ homeassistant.components.notify.slack
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Slack platform for notify component.
Configuration:
To use the Slack notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: slack
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
default_channel: '#general'
Variables:
api_key
*Required
The slack API token to use for sending slack messages.
You can get your slack API token here https://api.slack.com/web?sudo=1
default_channel
*Required
The default channel to post to if no channel is explicitly specified when
sending the notification message.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.slack.html
"""
import logging

View File

@ -1,56 +1,10 @@
"""
homeassistant.components.notify.mail
homeassistant.components.notify.smtp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Mail (SMTP) notification service.
Configuration:
To use the Mail notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: mail
server: MAIL_SERVER
port: YOUR_SMTP_PORT
sender: SENDER_EMAIL_ADDRESS
starttls: 1 or 0
username: YOUR_SMTP_USERNAME
password: YOUR_SMTP_PASSWORD
recipient: YOUR_RECIPIENT
Variables:
server
*Required
SMTP server which is used to end the notifications. For Google Mail, eg.
smtp.gmail.com. Keep in mind that Google has some extra layers of protection
which need special attention (Hint: 'Less secure apps').
port
*Required
The port that the SMTP server is using, eg. 587 for Google Mail and STARTTLS
or 465/993 depending on your SMTP servers.
sender
*Required
E-Mail address of the sender.
starttls
*Optional
Enables STARTTLS, eg. 1 or 0.
username
*Required
Username for the SMTP account.
password
*Required
Password for the SMTP server that belongs to the given username. If the
password contains a colon it need to be wrapped in apostrophes.
recipient
*Required
Recipient of the notification.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.smtp.html
"""
import logging
import smtplib

View File

@ -3,31 +3,8 @@ homeassistant.components.notify.syslog
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Syslog notification service.
Configuration:
To use the Syslog notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: syslog
facility: SYSLOG_FACILITY
option: SYSLOG_LOG_OPTION
priority: SYSLOG_PRIORITY
Variables:
facility
*Optional
Facility according to RFC 3164 (http://tools.ietf.org/html/rfc3164). Default
is 'syslog' if no value is given.
option
*Option
Log option. Default is 'pid' if no value is given.
priority
*Optional
Priority of the messages. Default is 'info' if no value is given.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.syslog.html
"""
import logging
import syslog

View File

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

View File

@ -3,31 +3,8 @@ homeassistant.components.notify.xmpp
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jabber (XMPP) notification service.
Configuration:
To use the Jabber notifier you will need to add something like the following
to your configuration.yaml file.
notify:
platform: xmpp
sender: YOUR_JID
password: YOUR_JABBER_ACCOUNT_PASSWORD
recipient: YOUR_RECIPIENT
Variables:
sender
*Required
The Jabber ID (JID) that will act as origin of the messages. Add your JID
including the domain, e.g. your_name@jabber.org.
password
*Required
The password for your given Jabber account.
recipient
*Required
The Jabber ID (JID) that will receive the messages.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.xmpp.html
"""
import logging

View File

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

View File

@ -6,34 +6,34 @@ Allows users to set and activate scenes within Home Assistant.
A scene is a set of states that describe how you want certain entities to be.
For example, light A should be red with 100 brightness. Light B should be on.
A scene is active if all states of the scene match the real states.
If a scene is manually activated it will store the previous state of the
entities. These will be restored when the state is deactivated manually.
If one of the enties that are being tracked change state on its own, the
old state will not be restored when it is being deactivated.
"""
import logging
from collections import namedtuple
from homeassistant.core import State
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.state import reproduce_state
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF)
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, SERVICE_TURN_ON)
DOMAIN = 'scene'
DEPENDENCIES = ['group']
ATTR_ACTIVE_REQUESTED = "active_requested"
STATE = 'scening'
CONF_ENTITIES = "entities"
SceneConfig = namedtuple('SceneConfig', ['name', 'states', 'fuzzy_match'])
SceneConfig = namedtuple('SceneConfig', ['name', 'states'])
def activate(hass, entity_id=None):
""" Activate a scene. """
data = {}
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
def setup(hass, config):
@ -43,8 +43,9 @@ def setup(hass, config):
scene_configs = config.get(DOMAIN)
if not isinstance(scene_configs, list):
logger.error('Scene config should be a list of scenes')
if not isinstance(scene_configs, list) or \
any(not isinstance(item, dict) for item in scene_configs):
logger.error('Scene config should be a list of dictionaries')
return False
component = EntityComponent(logger, DOMAIN, hass)
@ -57,12 +58,8 @@ def setup(hass, config):
target_scenes = component.extract_from_service(service)
for scene in target_scenes:
if service.service == SERVICE_TURN_ON:
scene.turn_on()
else:
scene.turn_off()
scene.activate()
hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_scene_service)
hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_scene_service)
return True
@ -72,14 +69,6 @@ def _process_config(scene_config):
""" Process passed in config into a format to work with. """
name = scene_config.get('name')
fuzzy_match = scene_config.get('fuzzy_match')
if fuzzy_match:
# default to 1%
if isinstance(fuzzy_match, int):
fuzzy_match /= 100.0
else:
fuzzy_match = 0.01
states = {}
c_entities = dict(scene_config.get(CONF_ENTITIES, {}))
@ -100,23 +89,16 @@ def _process_config(scene_config):
states[entity_id.lower()] = State(entity_id, state, attributes)
return SceneConfig(name, states, fuzzy_match)
return SceneConfig(name, states)
class Scene(ToggleEntity):
class Scene(Entity):
""" A scene is a group of entities and the states we want them to be. """
def __init__(self, hass, scene_config):
self.hass = hass
self.scene_config = scene_config
self.is_active = False
self.prev_states = None
self.ignore_updates = False
track_state_change(
self.hass, self.entity_ids, self.entity_state_changed)
self.update()
@property
@ -128,8 +110,8 @@ class Scene(ToggleEntity):
return self.scene_config.name
@property
def is_on(self):
return self.is_active
def state(self):
return STATE
@property
def entity_ids(self):
@ -141,82 +123,8 @@ class Scene(ToggleEntity):
""" Scene state attributes. """
return {
ATTR_ENTITY_ID: list(self.entity_ids),
ATTR_ACTIVE_REQUESTED: self.prev_states is not None,
}
def turn_on(self):
def activate(self):
""" Activates scene. Tries to get entities into requested state. """
self.prev_states = tuple(self.hass.states.get(entity_id)
for entity_id in self.entity_ids)
self._reproduce_state(self.scene_config.states.values())
def turn_off(self):
""" Deactivates scene and restores old states. """
if self.prev_states:
self._reproduce_state(self.prev_states)
self.prev_states = None
def entity_state_changed(self, entity_id, old_state, new_state):
""" Called when an entity part of this scene changes state. """
if self.ignore_updates:
return
# If new state is not what we expect, it can never be active
if self._state_as_requested(new_state):
self.update()
else:
self.is_active = False
self.prev_states = None
self.update_ha_state()
def update(self):
"""
Update if the scene is active.
Will look at each requested state and see if the current entity
has the same state and has at least the same attributes with the
same values. The real state can have more attributes.
"""
self.is_active = all(
self._state_as_requested(self.hass.states.get(entity_id))
for entity_id in self.entity_ids)
def _state_as_requested(self, cur_state):
""" Returns if given state is as requested. """
state = self.scene_config.states.get(cur_state and cur_state.entity_id)
return (cur_state is not None and state.state == cur_state.state and
all(self._compare_state_attribites(
value, cur_state.attributes.get(key))
for key, value in state.attributes.items()))
def _fuzzy_attribute_compare(self, attr_a, attr_b):
"""
Compare the attributes passed, use fuzzy logic if they are floats.
"""
if not (isinstance(attr_a, float) and isinstance(attr_b, float)):
return False
diff = abs(attr_a - attr_b) / (abs(attr_a) + abs(attr_b))
return diff <= self.scene_config.fuzzy_match
def _compare_state_attribites(self, attr1, attr2):
""" Compare the attributes passed, using fuzzy logic if specified. """
if attr1 == attr2:
return True
if not self.scene_config.fuzzy_match:
return False
if isinstance(attr1, list):
return all(self._fuzzy_attribute_compare(a, b)
for a, b in zip(attr1, attr2))
return self._fuzzy_attribute_compare(attr1, attr2)
def _reproduce_state(self, states):
""" Wraps reproduce state with Scence specific logic. """
self.ignore_updates = True
reproduce_state(self.hass, states, True)
self.ignore_updates = False
self.update_ha_state(True)
reproduce_state(self.hass, self.scene_config.states.values(), True)

View File

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

View File

@ -3,65 +3,8 @@ homeassistant.components.sensor.bitcoin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bitcoin information service that uses blockchain.info and its online wallet.
Configuration:
You need to enable the API access for your online wallet to get the balance.
To do that log in and move to 'Account Setting', choose 'IP Restrictions', and
check 'Enable Api Access'. You will get an email message from blockchain.info
where you must authorize the API access.
To use the Bitcoin sensor you will need to add something like the following
to your configuration.yaml file.
sensor:
platform: bitcoin
wallet: 'YOUR WALLET_ID'
password: YOUR_ACCOUNT_PASSWORD
currency: YOUR CURRENCY
display_options:
- exchangerate
- trade_volume_btc
- miners_revenue_usd
- btc_mined
- trade_volume_usd
- difficulty
- minutes_between_blocks
- number_of_transactions
- hash_rate
- timestamp
- mined_blocks
- blocks_size
- total_fees_btc
- total_btc_sent
- estimated_btc_sent
- total_btc
- total_blocks
- next_retarget
- estimated_transaction_volume_usd
- miners_revenue_btc
- market_price_usd
Variables:
wallet
*Optional
This is your wallet identifier from https://blockchain.info to access the
online wallet.
password
*Optional
Password your your online wallet.
currency
*Optional
The currency to exchange to, eg. CHF, USD, EUR,etc. Default is USD.
display_options
*Optional
An array specifying the variables to display.
These are the variables for the display_options array. See the configuration
example above for a list of all available variables.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bitcoin.html
"""
import logging
from datetime import timedelta

View File

@ -0,0 +1,76 @@
"""
homeassistant.components.sensor.cpuspeed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows the current CPU speed.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.cpuspeed.html
"""
import logging
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['py-cpuinfo==0.1.6']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = "CPU speed"
ATTR_VENDOR = 'Vendor ID'
ATTR_BRAND = 'Brand'
ATTR_HZ = 'GHz Advertised'
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the CPU speed sensor. """
try:
import cpuinfo # noqa
except ImportError:
_LOGGER.exception(
"Unable to import cpuinfo. "
"Did you maybe not install the 'py-cpuinfo' package?")
return False
add_devices([CpuSpeedSensor(config.get('name', DEFAULT_NAME))])
class CpuSpeedSensor(Entity):
""" A CPU info sensor. """
def __init__(self, name):
self._name = name
self._state = None
self._unit_of_measurement = 'GHz'
self.update()
@property
def name(self):
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._state
@property
def unit_of_measurement(self):
return self._unit_of_measurement
@property
def state_attributes(self):
""" Returns the state attributes. """
if self.info is not None:
return {
ATTR_VENDOR: self.info['vendor_id'],
ATTR_BRAND: self.info['brand'],
ATTR_HZ: round(self.info['hz_advertised_raw'][0]/10**9, 2)
}
def update(self):
""" Gets the latest data and updates the state. """
from cpuinfo import cpuinfo
self.info = cpuinfo.get_cpu_info()
self._state = round(float(self.info['hz_actual_raw'][0])/10**9, 2)

View File

@ -17,6 +17,26 @@ Variables:
port
*Required
Port of your connection to your MySensors device.
debug
*Optional
Enable or disable verbose debug logging.
persistence
*Optional
Enable or disable local persistence of sensor information.
Note: If this is disabled, then each sensor will need to send presentation
messages after Home Assistant starts
persistence_file
*Optional
Path to a file to save sensor information.
Note: The file extension determines the file type. Currently supported file
types are 'pickle' and 'json'.
version
*Optional
Specifies the MySensors protocol version to use (ex. 1.4, 1.5).
"""
import logging
@ -30,14 +50,16 @@ from homeassistant.const import (
CONF_PORT = "port"
CONF_DEBUG = "debug"
CONF_PERSISTENCE = "persistence"
CONF_PERSISTENCE_FILE = "persistence_file"
CONF_VERSION = "version"
ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/'
'35b87d880147a34107da0d40cb815d75e6cb4af7.zip'
'#pymysensors==0.2']
'd4b809c2167650691058d1e29bfd2c4b1792b4b0.zip'
'#pymysensors==0.3']
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -86,9 +108,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
persistence = config.get(CONF_PERSISTENCE, True)
persistence_file = config.get(CONF_PERSISTENCE_FILE, 'mysensors.pickle')
version = config.get(CONF_VERSION, '1.4')
gateway = mysensors.SerialGateway(port, sensor_update,
persistence=persistence)
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
gateway.metric = is_metric
gateway.debug = config.get(CONF_DEBUG, False)
gateway.start()

View File

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

View File

@ -1,47 +1,10 @@
"""
homeassistant.components.sensor.sabnzbd
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Monitors SABnzbd NZB client API
Monitors SABnzbd NZB client API.
Configuration:
To use the SABnzbd sensor you will need to add something like the following to
your configuration.yaml file.
sensor:
platform: sabnzbd
name: SAB
api_key: YOUR_API_KEY
base_url: YOUR_SABNZBD_BASE_URL
monitored_variables:
- type: 'current_status'
- type: 'speed'
- type: 'queue_size'
- type: 'queue_remaining'
- type: 'disk_size'
- type: 'disk_free'
Variables:
base_url
*Required
This is the base URL of your SABnzbd instance including the port number if not
running on 80, e.g. http://192.168.1.32:8124/
name
*Optional
The name to use when displaying this SABnzbd instance.
monitored_variables
*Required
An array specifying the variables to monitor.
These are the variables for the monitored_variables array:
type
*Required
The variable you wish to monitor, see the configuration example above for a
list of all available variables.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.sabnzbd.html
"""
from homeassistant.util import Throttle
from datetime import timedelta

View File

@ -3,60 +3,8 @@ homeassistant.components.sensor.systemmonitor
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shows system monitor values such as: disk, memory, and processor use.
Configuration:
To use the System monitor sensor you will need to add something like the
following to your configuration.yaml file.
sensor:
platform: systemmonitor
resources:
- type: 'disk_use_percent'
arg: '/'
- type: 'disk_use'
arg: '/home'
- type: 'disk_free'
arg: '/'
- type: 'memory_use_percent'
- type: 'memory_use'
- type: 'memory_free'
- type: 'swap_use_percent'
- type: 'swap_use'
- type: 'swap_free'
- type: 'network_in'
arg: 'eth0'
- type: 'network_out'
arg: 'eth0'
- type: 'packets_in'
arg: 'eth0'
- type: 'packets_out'
arg: 'eth0'
- type: 'ipv4_address'
arg: 'eth0'
- type: 'ipv6_address'
arg: 'eth0'
- type: 'processor_use'
- type: 'process'
arg: 'octave-cli'
- type: 'last_boot'
- type: 'since_last_boot'
Variables:
resources
*Required
An array specifying the variables to monitor.
These are the variables for the resources array:
type
*Required
The variable you wish to monitor, see the configuration example above for a
sample list of variables.
arg
*Optional
Additional details for the type, eg. path, binary name, etc.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.systemmonitor.html
"""
import logging
@ -64,7 +12,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.const import STATE_ON, STATE_OFF
REQUIREMENTS = ['psutil==3.0.0']
REQUIREMENTS = ['psutil==3.2.2']
SENSOR_TYPES = {
'disk_use_percent': ['Disk Use', '%'],
'disk_use': ['Disk Use', 'GiB'],

View File

@ -0,0 +1,46 @@
"""
homeassistant.components.shell_command
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Exposes regular shell commands as services.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/shell_command.html
"""
import logging
import subprocess
from homeassistant.util import slugify
DOMAIN = 'shell_command'
DEPENDENCIES = []
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
""" Sets up the shell_command component. """
conf = config.get(DOMAIN)
if not isinstance(conf, dict):
_LOGGER.error('Expected configuration to be a dictionary')
return False
for name in conf.keys():
if name != slugify(name):
_LOGGER.error('Invalid service name: %s. Try %s',
name, slugify(name))
return False
def service_handler(call):
""" Execute a shell command service. """
try:
subprocess.call(conf[call.service].split(' '),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except subprocess.SubprocessError:
_LOGGER.exception('Error running command')
for name in conf.keys():
hass.services.register(DOMAIN, name, service_handler)
return True

View File

@ -1,10 +1,10 @@
"""
homeassistant.components.simple_alarm
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Intruder alerts component.
Provides a simple alarm feature:
- flash a light when a known device comes home
- flash a light red if a light turns on while there is no one home.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/simple_alarm.html
"""
import logging
@ -17,7 +17,7 @@ DOMAIN = "simple_alarm"
DEPENDENCIES = ['group', 'device_tracker', 'light']
# Attribute to tell which light has to flash whem a known person comes home
# If ommitted will flash all.
# If omitted will flash all.
CONF_KNOWN_LIGHT = "known_light"
# Attribute to tell which light has to flash whem an unknown person comes home

View File

@ -1,23 +1,10 @@
"""
homeassistant.components.sun
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Provides functionality to keep track of the sun.
Event listener
--------------
The suns event listener will call the service when the sun rises or sets with
an offset.
The sun event need to have the type 'sun', which service to call, which event
(sunset or sunrise) and the offset.
{
"type": "sun",
"service": "switch.turn_on",
"event": "sunset",
"offset": "-01:00:00"
}
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sun.html
"""
import logging
from datetime import timedelta

View File

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

View File

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

View File

@ -136,22 +136,16 @@ class ThermostatDevice(Entity):
def state_attributes(self):
""" Returns optional state attributes. """
thermostat_unit = self.unit_of_measurement
user_unit = self.hass.config.temperature_unit
data = {
ATTR_CURRENT_TEMPERATURE: round(convert(
self.current_temperature, thermostat_unit, user_unit), 1),
ATTR_MIN_TEMP: round(convert(
self.min_temp, thermostat_unit, user_unit), 0),
ATTR_MAX_TEMP: round(convert(
self.max_temp, thermostat_unit, user_unit), 0),
ATTR_TEMPERATURE: round(convert(
self.target_temperature, thermostat_unit, user_unit), 0),
ATTR_TEMPERATURE_LOW: round(convert(
self.target_temperature_low, thermostat_unit, user_unit), 0),
ATTR_TEMPERATURE_HIGH: round(convert(
self.target_temperature_high, thermostat_unit, user_unit), 0),
ATTR_CURRENT_TEMPERATURE:
self._convert(self.current_temperature, 1),
ATTR_MIN_TEMP: self._convert(self.min_temp, 0),
ATTR_MAX_TEMP: self._convert(self.max_temp, 0),
ATTR_TEMPERATURE: self._convert(self.target_temperature, 0),
ATTR_TEMPERATURE_LOW:
self._convert(self.target_temperature_low, 0),
ATTR_TEMPERATURE_HIGH:
self._convert(self.target_temperature_high, 0),
}
operation = self.operation
@ -228,3 +222,14 @@ class ThermostatDevice(Entity):
def max_temp(self):
""" Return maxmum temperature. """
return convert(35, TEMP_CELCIUS, self.unit_of_measurement)
def _convert(self, temp, round_dec=None):
""" Convert temperature from this thermost into user preferred
temperature. """
if temp is None:
return None
value = convert(temp, self.unit_of_measurement,
self.hass.config.temperature_unit)
return value if round_dec is None else round(value, round_dec)

View File

@ -1,7 +1,7 @@
# coding: utf-8
""" Constants used by Home Assistant components. """
__version__ = "0.7.4dev0"
__version__ = "0.7.6.dev0"
# Can be used to specify a catch all when registering state or event listeners.
MATCH_ALL = '*'
@ -51,6 +51,8 @@ STATE_STANDBY = 'standby'
STATE_ALARM_DISARMED = 'disarmed'
STATE_ALARM_ARMED_HOME = 'armed_home'
STATE_ALARM_ARMED_AWAY = 'armed_away'
STATE_ALARM_PENDING = 'pending'
STATE_ALARM_TRIGGERED = 'triggered'
# #### STATE AND EVENT ATTRIBUTES ####
# Contains current time for a TIME_CHANGED event
@ -128,6 +130,7 @@ SERVICE_MEDIA_SEEK = "media_seek"
SERVICE_ALARM_DISARM = "alarm_disarm"
SERVICE_ALARM_ARM_HOME = "alarm_arm_home"
SERVICE_ALARM_ARM_AWAY = "alarm_arm_away"
SERVICE_ALARM_TRIGGER = "alarm_trigger"
# #### API / REMOTE ####
SERVER_PORT = 8123
@ -154,6 +157,7 @@ HTTP_UNAUTHORIZED = 401
HTTP_NOT_FOUND = 404
HTTP_METHOD_NOT_ALLOWED = 405
HTTP_UNPROCESSABLE_ENTITY = 422
HTTP_INTERNAL_SERVER_ERROR = 500
HTTP_HEADER_HA_AUTH = "X-HA-access"
HTTP_HEADER_ACCEPT_ENCODING = "Accept-Encoding"

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
import logging
logging.disable(logging.CRITICAL)

View File

@ -125,16 +125,14 @@ def mock_http_component(hass):
@mock.patch('homeassistant.components.mqtt.MQTT')
@mock.patch('homeassistant.components.mqtt.MQTT.publish')
def mock_mqtt_component(hass, mock_mqtt, mock_mqtt_publish):
def mock_mqtt_component(hass, mock_mqtt):
mqtt.setup(hass, {
mqtt.DOMAIN: {
mqtt.CONF_BROKER: 'mock-broker',
}
})
hass.config.components.append(mqtt.DOMAIN)
return mock_mqtt_publish
return mock_mqtt
class MockHTTP(object):

View File

@ -0,0 +1,302 @@
"""
tests.components.alarm_control_panel.test_manual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests manual alarm control panel component.
"""
from datetime import timedelta
import unittest
from unittest.mock import patch
import homeassistant.core as ha
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
from homeassistant.components import alarm_control_panel
import homeassistant.util.dt as dt_util
from tests.common import fire_time_changed
CODE = 'HELLO_CODE'
class TestAlarmControlPanelManual(unittest.TestCase):
""" Test the manual alarm module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_arm_home_no_pending(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'code': CODE,
'pending_time': 0
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_arm_home(self.hass, CODE)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_HOME,
self.hass.states.get(entity_id).state)
def test_arm_home_with_pending(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'code': CODE,
'pending_time': 1
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_arm_home(self.hass, CODE, entity_id)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
future = dt_util.utcnow() + timedelta(seconds=1)
with patch(('homeassistant.components.alarm_control_panel.manual.'
'dt_util.utcnow'), return_value=future):
fire_time_changed(self.hass, future)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_HOME,
self.hass.states.get(entity_id).state)
def test_arm_home_with_invalid_code(self):
""" Attempt to arm home without a valid code. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'code': CODE,
'pending_time': 1
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_arm_home(self.hass, CODE + '2')
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
def test_arm_away_no_pending(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'code': CODE,
'pending_time': 0
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
def test_arm_away_with_pending(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'code': CODE,
'pending_time': 1
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_arm_away(self.hass, CODE)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
future = dt_util.utcnow() + timedelta(seconds=1)
with patch(('homeassistant.components.alarm_control_panel.manual.'
'dt_util.utcnow'), return_value=future):
fire_time_changed(self.hass, future)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
def test_arm_away_with_invalid_code(self):
""" Attempt to arm away without a valid code. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'code': CODE,
'pending_time': 1
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_arm_away(self.hass, CODE + '2')
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
def test_trigger_no_pending(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'trigger_time': 0
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
self.hass.states.get(entity_id).state)
def test_trigger_with_pending(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'trigger_time': 1
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_trigger(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
future = dt_util.utcnow() + timedelta(seconds=1)
with patch(('homeassistant.components.alarm_control_panel.manual.'
'dt_util.utcnow'), return_value=future):
fire_time_changed(self.hass, future)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
self.hass.states.get(entity_id).state)
future = dt_util.utcnow() + timedelta(seconds=2)
with patch(('homeassistant.components.alarm_control_panel.manual.'
'dt_util.utcnow'), return_value=future):
fire_time_changed(self.hass, future)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
def test_disarm_while_pending_trigger(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'trigger_time': 5
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_trigger(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
future = dt_util.utcnow() + timedelta(seconds=5)
with patch(('homeassistant.components.alarm_control_panel.manual.'
'dt_util.utcnow'), return_value=future):
fire_time_changed(self.hass, future)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
def test_disarm_during_trigger_with_invalid_code(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'manual',
'name': 'test',
'trigger_time': 5,
'code': CODE + '2'
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_trigger(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
future = dt_util.utcnow() + timedelta(seconds=5)
with patch(('homeassistant.components.alarm_control_panel.manual.'
'dt_util.utcnow'), return_value=future):
fire_time_changed(self.hass, future)
self.hass.pool.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
self.hass.states.get(entity_id).state)

View File

@ -0,0 +1,178 @@
"""
tests.components.alarm_control_panel.test_manual
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests manual alarm control panel component.
"""
import unittest
from unittest.mock import patch
import homeassistant.core as ha
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
from homeassistant.components import alarm_control_panel
from tests.common import mock_mqtt_component, fire_mqtt_message
CODE = 'HELLO_CODE'
class TestAlarmControlPanelMQTT(unittest.TestCase):
""" Test the manual alarm module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.mock_publish = mock_mqtt_component(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
@patch('homeassistant.components.alarm_control_panel.mqtt._LOGGER.error')
def test_fail_setup_without_state_topic(self, mock_error):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'command_topic': 'alarm/command'
}}))
self.assertEqual(1, mock_error.call_count)
@patch('homeassistant.components.alarm_control_panel.mqtt._LOGGER.error')
def test_fail_setup_without_command_topic(self, mock_error):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'state_topic': 'alarm/state'
}}))
self.assertEqual(1, mock_error.call_count)
def test_update_state_via_state_topic(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_UNKNOWN,
self.hass.states.get(entity_id).state)
for state in (STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED):
fire_mqtt_message(self.hass, 'alarm/state', state)
self.hass.pool.block_till_done()
self.assertEqual(state, self.hass.states.get(entity_id).state)
def test_ignore_update_state_if_unknown_via_state_topic(self):
""" Test arm home method. """
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
entity_id = 'alarm_control_panel.test'
self.assertEqual(STATE_UNKNOWN,
self.hass.states.get(entity_id).state)
fire_mqtt_message(self.hass, 'alarm/state', 'unsupported state')
self.hass.pool.block_till_done()
self.assertEqual(STATE_UNKNOWN, self.hass.states.get(entity_id).state)
def test_arm_home_publishes_mqtt(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
alarm_control_panel.alarm_arm_home(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(('alarm/command', 'ARM_HOME', 0),
self.mock_publish.mock_calls[-1][1])
def test_arm_home_not_publishes_mqtt_with_invalid_code(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234'
}}))
call_count = self.mock_publish.call_count
alarm_control_panel.alarm_arm_home(self.hass, 'abcd')
self.hass.pool.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)
def test_arm_away_publishes_mqtt(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
alarm_control_panel.alarm_arm_away(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(('alarm/command', 'ARM_AWAY', 0),
self.mock_publish.mock_calls[-1][1])
def test_arm_away_not_publishes_mqtt_with_invalid_code(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234'
}}))
call_count = self.mock_publish.call_count
alarm_control_panel.alarm_arm_away(self.hass, 'abcd')
self.hass.pool.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)
def test_disarm_publishes_mqtt(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
}}))
alarm_control_panel.alarm_disarm(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(('alarm/command', 'DISARM', 0),
self.mock_publish.mock_calls[-1][1])
def test_disarm_not_publishes_mqtt_with_invalid_code(self):
self.assertTrue(alarm_control_panel.setup(self.hass, {
'alarm_control_panel': {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234'
}}))
call_count = self.mock_publish.call_count
alarm_control_panel.alarm_disarm(self.hass, 'abcd')
self.hass.pool.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)

View File

@ -8,6 +8,7 @@ import unittest
import homeassistant.core as ha
import homeassistant.components.automation as automation
import homeassistant.components.automation.state as state
class TestAutomationState(unittest.TestCase):
@ -334,3 +335,19 @@ class TestAutomationState(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls))
def test_if_fails_setup_if_to_boolean_value(self):
self.assertFalse(state.trigger(
self.hass, {
'platform': 'state',
'entity_id': 'test.entity',
'to': True,
}, lambda x: x))
def test_if_fails_setup_if_from_boolean_value(self):
self.assertFalse(state.trigger(
self.hass, {
'platform': 'state',
'entity_id': 'test.entity',
'from': True,
}, lambda x: x))

View File

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

View File

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

View File

@ -0,0 +1,68 @@
"""
tests.components.test_scene
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests scene component.
"""
import unittest
from homeassistant import loader
from homeassistant.components import light, scene
from tests.common import get_test_home_assistant
class TestScene(unittest.TestCase):
""" Test scene component. """
def setUp(self): # pylint: disable=invalid-name
self.hass = get_test_home_assistant()
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_config_not_list(self):
self.assertFalse(scene.setup(self.hass, {
'scene': {'some': 'dict'}
}))
def test_config_no_dict_in_list(self):
self.assertFalse(scene.setup(self.hass, {
'scene': [[]]
}))
def test_activate_scene(self):
test_light = loader.get_component('light.test')
test_light.init()
self.assertTrue(light.setup(self.hass, {
light.DOMAIN: {'platform': 'test'}
}))
light_1, light_2 = test_light.DEVICES[0:2]
light.turn_off(self.hass, [light_1.entity_id, light_2.entity_id])
self.hass.pool.block_till_done()
self.assertTrue(scene.setup(self.hass, {
'scene': [{
'name': 'test',
'entities': {
light_1.entity_id: 'on',
light_2.entity_id: {
'state': 'on',
'brightness': 100,
}
}
}]
}))
scene.activate(self.hass, 'scene.test')
self.hass.pool.block_till_done()
self.assertTrue(light_1.is_on)
self.assertTrue(light_2.is_on)
self.assertEqual(100,
light_2.last_call('turn_on')[1].get('brightness'))

View File

@ -0,0 +1,71 @@
"""
tests.test_shell_command
~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo component.
"""
import os
import tempfile
import unittest
from unittest.mock import patch
from subprocess import SubprocessError
from homeassistant import core
from homeassistant.components import shell_command
class TestShellCommand(unittest.TestCase):
""" Test the demo module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = core.HomeAssistant()
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_executing_service(self):
""" Test if able to call a configured service. """
with tempfile.TemporaryDirectory() as tempdirname:
path = os.path.join(tempdirname, 'called.txt')
self.assertTrue(shell_command.setup(self.hass, {
'shell_command': {
'test_service': "touch {}".format(path)
}
}))
self.hass.services.call('shell_command', 'test_service',
blocking=True)
self.assertTrue(os.path.isfile(path))
def test_config_not_dict(self):
""" Test if config is not a dict. """
self.assertFalse(shell_command.setup(self.hass, {
'shell_command': ['some', 'weird', 'list']
}))
def test_config_not_valid_service_names(self):
""" Test if config contains invalid service names. """
self.assertFalse(shell_command.setup(self.hass, {
'shell_command': {
'this is invalid because space': 'touch bla.txt'
}}))
@patch('homeassistant.components.shell_command.subprocess.call',
side_effect=SubprocessError)
@patch('homeassistant.components.shell_command._LOGGER.error')
def test_subprocess_raising_error(self, mock_call, mock_error):
with tempfile.TemporaryDirectory() as tempdirname:
path = os.path.join(tempdirname, 'called.txt')
self.assertTrue(shell_command.setup(self.hass, {
'shell_command': {
'test_service': "touch {}".format(path)
}
}))
self.hass.services.call('shell_command', 'test_service',
blocking=True)
self.assertFalse(os.path.isfile(path))
self.assertEqual(1, mock_error.call_count)

View File

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