Merge pull request #6956 from home-assistant/release-0-42

0.42
pull/6993/head 0.42
Paulus Schoutsen 2017-04-08 15:40:30 -07:00 committed by GitHub
commit cd8723f742
224 changed files with 5773 additions and 3272 deletions

View File

@ -147,11 +147,12 @@ omit =
homeassistant/components/tado.py
homeassistant/components/*/tado.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/alarm_control_panel/totalconnect.py
homeassistant/components/apiai.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/concord232.py
@ -225,6 +226,7 @@ omit =
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/lifx.py
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/tikteck.py
@ -235,6 +237,7 @@ omit =
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/nuki.py
homeassistant/components/lock/lockitron.py
homeassistant/components/media_player/anthemav.py
homeassistant/components/media_player/apple_tv.py
homeassistant/components/media_player/aquostv.py
@ -323,6 +326,7 @@ omit =
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/comed_hourly_pricing.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/crimereports.py
homeassistant/components/sensor/cups.py
homeassistant/components/sensor/currencylayer.py
homeassistant/components/sensor/darksky.py
@ -332,6 +336,7 @@ omit =
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/ebox.py
homeassistant/components/sensor/eddystone_temperature.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/fastdotcom.py
@ -357,9 +362,11 @@ omit =
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/lyft.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/modem_callerid.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/mvglive.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nut.py
@ -431,12 +438,12 @@ omit =
homeassistant/components/tts/picotts.py
homeassistant/components/upnp.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/metoffice.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/zamg.py
homeassistant/components/zeroconf.py
homeassistant/components/zwave/__init__.py
homeassistant/components/zwave/util.py
homeassistant/components/zwave/workaround.py
[report]

11
.gitignore vendored
View File

@ -1,15 +1,4 @@
config/*
!config/home-assistant.conf.default
# There is not a better solution afaik..
!config/custom_components
config/custom_components/*
!config/custom_components/example.py
!config/custom_components/hello_world.py
!config/custom_components/mqtt_example.py
!config/panels
config/panels/*
!config/panels/react.html
tests/testing_config/deps
tests/testing_config/home-assistant.log

View File

@ -1,9 +1,7 @@
Home Assistant |Build Status| |Coverage Status| |Join the chat at https://gitter.im/home-assistant/home-assistant| |Join the dev chat at https://gitter.im/home-assistant/home-assistant/devs|
==============================================================================================================================================================================================
Home Assistant is a home automation platform running on Python 3. The
goal of Home Assistant is to be able to track and control all devices at
home and offer a platform for automating control.
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
To get started:
@ -12,83 +10,22 @@ To get started:
python3 -m pip install homeassistant
hass --open-ui
Check out `the website <https://home-assistant.io>`__ for `a
demo <https://home-assistant.io/demo/>`__, installation instructions,
tutorials and documentation.
Check out `home-assistant.io <https://home-assistant.io>`__ for `a
demo <https://home-assistant.io/demo/>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
`tutorials <https://home-assistant.io/getting-started/automation-2/>`__ and `documentation <https://home-assistant.io/docs/>`__.
|screenshot-states|
Examples of devices Home Assistant can interface with:
Featured integrations
---------------------
- 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/>`__,
`Xiaomi <http://miwifi.com/>`__ 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, 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>`__,
`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/>`__
|screenshot-components|
Build home automation on top of your devices:
- Keep a precise history of every change to the state of your house
- Turn on the lights when people get home after sunset
- Turn on lights slowly during sunset 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/rest_api/>`__
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/>`__, `Join <http://joaoapps.com/join/>`__, and `Jabber
(XMPP) <http://xmpp.org>`__
The system is built using a modular approach so support for other devices or actions can
be implemented easily. See also the `section on
architecture <https://home-assistant.io/developers/architecture/>`__
and the `section on creating your own
The system is built using a modular approach so support for other devices or actions can be implemented easily. See also the `section on architecture <https://home-assistant.io/developers/architecture/>`__ and the `section on creating your own
components <https://home-assistant.io/developers/creating_components/>`__.
If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help
section <https://home-assistant.io/help/>`__ of our website for further help and information.
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
.. |Build Status| image:: https://travis-ci.org/home-assistant/home-assistant.svg?branch=master
:target: https://travis-ci.org/home-assistant/home-assistant
@ -100,3 +37,5 @@ section <https://home-assistant.io/help/>`__ of our website for further help and
:target: https://gitter.im/home-assistant/home-assistant/devs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/
.. |screenshot-components| image:: https://raw.github.com/home-assistant/home-assistant/dev/docs/screenshot-components.png
:target: https://home-assistant.io/components/

View File

@ -1,158 +0,0 @@
homeassistant:
# Omitted values in this section will be auto detected using freegeoip.io
# Location required to calculate the time the sun rises and sets.
# Coordinates are also used for location for weather related components.
# Google Maps can be used to determine more precise GPS coordinates.
latitude: 32.87336
longitude: 117.22743
# Impacts weather/sunrise data
elevation: 665
# 'metric' for Metric System, 'imperial' for imperial system
unit_system: metric
# Pick yours from here:
# http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
time_zone: America/Los_Angeles
# Name of the location where Home Assistant is running
name: Home
http:
api_password: mypass
# Set to 1 to enable development mode
# development: 1
# Enable the frontend
frontend:
light:
# platform: hue
wink:
# Get your token at https://winkbearertoken.appspot.com
access_token: 'YOUR_TOKEN'
device_tracker:
# The following tracker are available:
# https://home-assistant.io/components/#presence-detection
platform: netgear
host: 192.168.1.1
username: admin
password: PASSWORD
switch:
platform: wemo
climate:
platform: nest
# Required: username and password that are used to login to the Nest thermostat.
username: myemail@mydomain.com
password: mypassword
downloader:
download_dir: downloads
notify:
platform: pushbullet
api_key: ABCDEFGHJKLMNOPQRSTUVXYZ
device_sun_light_trigger:
# Optional: specify a specific light/group of lights that has to be turned on
light_group: group.living_room
# Optional: specify which light profile to use when turning lights on
light_profile: relax
# Optional: disable lights being turned off when everybody leaves the house
# disable_turn_off: 1
# A comma separated list of states that have to be tracked as a single group
# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME)
# You can also have groups within groups.
# https://home-assistant.io/components/group/
group:
default_view:
view: yes
entities:
- group.awesome_people
- group.climate
kitchen:
name: Kitchen
entities:
- switch.kitchen_pin_3
upstairs:
name: Kids
icon: mdi:account-multiple
view: yes
entities:
- input_boolean.notify_home
- camera.demo_camera
browser:
keyboard:
# https://home-assistant.io/getting-started/automation/
automation:
- alias: Turn on light when sun sets
trigger:
platform: sun
event: sunset
offset: "-01:00:00"
condition:
condition: state
entity_id: group.all_devices
state: 'home'
action:
service: light.turn_on
# Another way to do is to collect all entries under one "sensor:"
# sensor:
# - platform: mqtt
# name: "MQTT Sensor 1"
# - platform: mqtt
# name: "MQTT Sensor 2"
#
# Details: https://home-assistant.io/getting-started/devices/
sensor:
platform: systemmonitor
resources:
- type: 'disk_use_percent'
arg: '/'
- type: 'disk_use_percent'
arg: '/home'
sensor 2:
platform: cpuspeed
script:
wakeup:
alias: Wake Up
sequence:
- event: LOGBOOK_ENTRY
event_data:
name: Paulus
message: is waking up
entity_id: device_tracker.paulus
domain: light
- alias: Bedroom lights on
service: light.turn_on
data:
entity_id: group.bedroom
brightness: 100
- delay:
minutes: 1
- alias: Living room lights on
service: light.turn_on
data:
entity_id: group.living_room
scene:
- name: Romantic
entities:
light.tv_back_light: on
light.ceiling:
state: on
xy_color: [0.33, 0.66]
brightness: 200

View File

@ -1,149 +0,0 @@
"""
Example of a custom component.
Example component to target an entity_id to:
- turn it on at 7AM in the morning
- turn it on if anyone comes home and it is off
- turn it off if all lights are turned off
- turn it off if all people leave the house
- offer a service to turn it on for 10 seconds
Configuration:
To use the Example custom component you will need to add the following to
your configuration.yaml file.
example:
target: TARGET_ENTITY
Variable:
target
*Required
TARGET_ENTITY should be one of your devices that can be turned on and off,
ie a light or a switch. Example value could be light.Ceiling or switch.AC
(if you have these devices with those names).
"""
import time
import logging
from homeassistant.const import STATE_HOME, STATE_NOT_HOME, STATE_ON, STATE_OFF
from homeassistant.helpers import validate_config
from homeassistant.helpers.event_decorators import \
track_state_change, track_time_change
from homeassistant.helpers.service import service
import homeassistant.components as core
from homeassistant.components import device_tracker
from homeassistant.components import light
# The domain of your component. Should be equal to the name of your component.
DOMAIN = "example"
# List of component names (string) your component depends upon.
# We depend on group because group will be loaded after all the components that
# initialize devices have been setup.
DEPENDENCIES = ['group', 'device_tracker', 'light']
# Configuration key for the entity id we are targeting.
CONF_TARGET = 'target'
# Variable for storing configuration parameters.
TARGET_ID = None
# Name of the service that we expose.
SERVICE_FLASH = 'flash'
# Shortcut for the logger
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Setup example component."""
global TARGET_ID
# Validate that all required config options are given.
if not validate_config(config, {DOMAIN: [CONF_TARGET]}, _LOGGER):
return False
TARGET_ID = config[DOMAIN][CONF_TARGET]
# Validate that the target entity id exists.
if hass.states.get(TARGET_ID) is None:
_LOGGER.error("Target entity id %s does not exist",
TARGET_ID)
# Tell the bootstrapper that we failed to initialize and clear the
# stored target id so our functions don't run.
TARGET_ID = None
return False
# Tell the bootstrapper that we initialized successfully.
return True
@track_state_change(device_tracker.ENTITY_ID_ALL_DEVICES)
def track_devices(hass, entity_id, old_state, new_state):
"""Called when the group.all devices change state."""
# If the target id is not set, return
if not TARGET_ID:
return
# If anyone comes home and the entity is not on, turn it on.
if new_state.state == STATE_HOME and not core.is_on(hass, TARGET_ID):
core.turn_on(hass, TARGET_ID)
# If all people leave the house and the entity is on, turn it off.
elif new_state.state == STATE_NOT_HOME and core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
@track_time_change(hour=7, minute=0, second=0)
def wake_up(hass, now):
"""Turn light on in the morning.
Turn the light on at 7 AM if there are people home and it is not already
on.
"""
if not TARGET_ID:
return
if device_tracker.is_on(hass) and not core.is_on(hass, TARGET_ID):
_LOGGER.info('People home at 7AM, turning it on')
core.turn_on(hass, TARGET_ID)
@track_state_change(light.ENTITY_ID_ALL_LIGHTS, STATE_ON, STATE_OFF)
def all_lights_off(hass, entity_id, old_state, new_state):
"""If all lights turn off, turn off."""
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
_LOGGER.info('All lights have been turned off, turning it off')
core.turn_off(hass, TARGET_ID)
@service(DOMAIN, SERVICE_FLASH)
def flash_service(hass, call):
"""Service that will toggle the target.
Set the light to off for 10 seconds if on and vice versa.
"""
if not TARGET_ID:
return
if core.is_on(hass, TARGET_ID):
core.turn_off(hass, TARGET_ID)
time.sleep(10)
core.turn_on(hass, TARGET_ID)
else:
core.turn_on(hass, TARGET_ID)
time.sleep(10)
core.turn_off(hass, TARGET_ID)

View File

@ -1,27 +0,0 @@
"""
The "hello world" custom component.
This component implements the bare minimum that a component should implement.
Configuration:
To use the hello_word component you will need to add the following to your
configuration.yaml file.
hello_world:
"""
# The domain of your component. Should be equal to the name of your component.
DOMAIN = "hello_world"
# List of component names (string) your component depends upon.
DEPENDENCIES = []
def setup(hass, config):
"""Setup our skeleton component."""
# States are in the format DOMAIN.OBJECT_ID.
hass.states.set('hello_world.Hello_World', 'Works!')
# Return boolean to indicate that initialization was successfully.
return True

View File

@ -1,55 +0,0 @@
"""
Example of a custom MQTT component.
Shows how to communicate with MQTT. Follows a topic on MQTT and updates the
state of an entity to the last message received on that topic.
Also offers a service 'set_state' that will publish a message on the topic that
will be passed via MQTT to our message received listener. Call the service with
example payload {"new_state": "some new state"}.
Configuration:
To use the mqtt_example component you will need to add the following to your
configuration.yaml file.
mqtt_example:
topic: "home-assistant/mqtt_example"
"""
import homeassistant.loader as loader
# The domain of your component. Should be equal to the name of your component.
DOMAIN = "mqtt_example"
# List of component names (string) your component depends upon.
DEPENDENCIES = ['mqtt']
CONF_TOPIC = 'topic'
DEFAULT_TOPIC = 'home-assistant/mqtt_example'
def setup(hass, config):
"""Setup the MQTT example component."""
mqtt = loader.get_component('mqtt')
topic = config[DOMAIN].get('topic', DEFAULT_TOPIC)
entity_id = 'mqtt_example.last_message'
# Listen to a message on MQTT.
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
hass.states.set(entity_id, payload)
mqtt.subscribe(hass, topic, message_received)
hass.states.set(entity_id, 'No messages')
# Service to publish a message on MQTT.
def set_state_service(call):
"""Service to send a message."""
mqtt.publish(hass, topic, call.data.get('new_state'))
# Register our service with Home Assistant.
hass.services.register(DOMAIN, 'set_state', set_state_service)
# Return boolean to indicate that initialization was successfully.
return True

View File

@ -1,432 +0,0 @@
<!--
Custom Home Assistant panel example.
Currently only works in Firefox and Chrome because it uses ES6.
Make sure this file is in <config>/panels/react.html
Add to your configuration.yaml:
panel_custom:
- name: react
sidebar_title: TodoMVC
sidebar_icon: mdi:checkbox-marked-outline
config:
title: Wow hello!
-->
<script src="https://fb.me/react-15.2.1.min.js"></script>
<script src="https://fb.me/react-dom-15.2.1.min.js"></script>
<!-- for development, replace with:
<script src="https://fb.me/react-15.2.1.js"></script>
<script src="https://fb.me/react-dom-15.2.1.js"></script>
-->
<!--
CSS taken from ReactJS TodoMVC example by Pete Hunt
http://todomvc.com/examples/react/
-->
<style>
.todoapp input[type="checkbox"] {
outline: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.todoapp .main {
position: relative;
border-top: 1px solid #e6e6e6;
}
.todoapp .todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todoapp .todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todoapp .todo-list li:last-child {
border-bottom: none;
}
.todoapp .todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
cursor: pointer;
}
.todoapp .todo-list li .toggle:focus {
border-left: 3px solid rgba(175, 47, 47, 0.35);
}
.todoapp .todo-list li .toggle:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#ededed" stroke-width="3"/></svg>');
}
.todoapp .todo-list li .toggle:checked:after {
content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="-10 -18 100 135"><circle cx="50" cy="50" r="50" fill="none" stroke="#bddad5" stroke-width="3"/><path fill="#5dc2af" d="M72 25L42 71 27 56l-4 4 20 20 34-52z"/></svg>');
}
.todoapp .todo-list li label {
white-space: pre-line;
word-break: break-all;
padding: 15px 60px 15px 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todoapp .todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todoapp .footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.todoapp .footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todoapp .todo-count {
float: left;
text-align: left;
font-weight: 300;
}
.todoapp .toggle-menu {
position: absolute;
right: 15px;
font-weight: 300;
color: rgba(175, 47, 47, 0.75);
}
.todoapp .filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.todoapp .filters li {
display: inline;
}
.todoapp .filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.todoapp .filters li a.selected,
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
}
.todoapp .filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.todoapp .toggle-all,
.todoapp .todo-list li .toggle {
background: none;
}
.todoapp .todo-list li .toggle {
height: 40px;
}
.todoapp .toggle-all {
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
@media (max-width: 430px) {
.todoapp .footer {
height: 50px;
}
.todoapp .filters {
bottom: 10px;
}
}
</style>
<dom-module id='ha-panel-react'>
<template>
<style>
:host {
background: #f5f5f5;
display: block;
height: 100%;
overflow: auto;
}
.mount {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
font-smoothing: antialiased;
font-weight: 300;
}
</style>
<div id='mount' class='mount'></div>
</template>
</dom-module>
<script>
// Example uses ES6. Will only work in modern browsers
class TodoMVC extends React.Component {
constructor(props) {
super(props);
this.state = {
filter: 'all',
// load initial value of entities
entities: this.props.hass.reactor.evaluate(
this.props.hass.entityGetters.visibleEntityMap),
};
}
componentDidMount() {
// register to entity updates
this._unwatchHass = this.props.hass.reactor.observe(
this.props.hass.entityGetters.visibleEntityMap,
entities => this.setState({entities}))
}
componentWillUnmount() {
// unregister to entity updates
this._unwatchHass();
}
handlePickFilter(filter, ev) {
ev.preventDefault();
this.setState({filter});
}
handleEntityToggle(entity, ev) {
this.props.hass.serviceActions.callService(
entity.domain, 'toggle', { entity_id: entity.entityId });
}
handleToggleMenu(ev) {
ev.preventDefault();
Polymer.Base.fire('open-menu', null, {node: ev.target});
}
entityRow(entity) {
const completed = entity.state === 'on';
return React.createElement(
'li', {
className: completed && 'completed',
key: entity.entityId,
},
React.createElement(
"div", { className: "view" },
React.createElement(
"input", {
checked: completed,
className: "toggle",
type: "checkbox",
onChange: ev => this.handleEntityToggle(entity, ev),
}),
React.createElement("label", null, entity.entityDisplay)));
}
filterRow(filter) {
return React.createElement(
"li", { key: filter },
React.createElement(
"a", {
href: "#",
className: this.state.filter === filter && "selected",
onClick: ev => this.handlePickFilter(filter, ev),
},
filter.substring(0, 1).toUpperCase() + filter.substring(1)
)
);
}
render() {
const { entities, filter } = this.state;
if (!entities) return null;
const filters = ['all', 'light', 'switch'];
const showEntities = filter === 'all' ?
entities.filter(ent => filters.includes(ent.domain)) :
entities.filter(ent => ent.domain == filter);
return React.createElement(
'div', { className: 'todoapp-wrapper' },
React.createElement(
"section", { className: "todoapp" },
React.createElement(
"div", null,
React.createElement(
"header", { className: "header" },
React.createElement("h1", null, this.props.title || "todos")
),
React.createElement(
"section", { className: "main" },
React.createElement(
"ul", { className: "todo-list" },
showEntities.valueSeq().map(ent => this.entityRow(ent)))
)
),
React.createElement(
"footer", { className: "footer" },
React.createElement(
"span", { className: "todo-count" },
showEntities.filter(ent => ent.state === 'off').size + " items left"
),
React.createElement(
"ul", { className: "filters" },
filters.map(filter => this.filterRow(filter))
),
!this.props.showMenu && React.createElement(
"a", {
className: "toggle-menu",
href: '#',
onClick: ev => this.handleToggleMenu(ev),
},
"Show menu"
)
)
));
}
}
Polymer({
is: 'ha-panel-react',
properties: {
// Home Assistant object
hass: {
type: Object,
},
// If should render in narrow mode
narrow: {
type: Boolean,
value: false,
},
// If sidebar is currently shown
showMenu: {
type: Boolean,
value: false,
},
// Home Assistant panel info
// panel.config contains config passed to register_panel serverside
panel: {
type: Object,
}
},
// This will make sure we forward changed properties to React
observers: [
'propsChanged(hass, narrow, showMenu, panel)',
],
// Mount React when element attached
attached: function () {
this.mount(this.hass, this.narrow, this.showMenu, this.panel);
},
// Called when properties change
propsChanged: function (hass, narrow, showMenu, panel) {
this.mount(hass, narrow, showMenu, panel);
},
// Render React. Debounce in case multiple properties change.
mount: function (hass, narrow, showMenu, panel) {
this.debounce('mount', function () {
ReactDOM.render(React.createElement(TodoMVC, {
hass: hass,
narrow: narrow,
showMenu: showMenu,
title: panel.config ? panel.config.title : null
}), this.$.mount);
}.bind(this));
},
// Unmount React node when panel no longer in use.
detached: function () {
ReactDOM.unmountComponentAtNode(this.$.mount);
},
});
</script>

BIN
docs/screenshot-components.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -20,6 +20,17 @@ from homeassistant.const import (
from homeassistant.util.async import run_callback_threadsafe
def attempt_use_uvloop():
"""Attempt to use uvloop."""
import asyncio
try:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
pass
def monkey_patch_asyncio():
"""Replace weakref.WeakSet to address Python 3 bug.
@ -311,8 +322,7 @@ def setup_and_run_hass(config_dir: str,
EVENT_HOMEASSISTANT_START, open_browser
)
hass.start()
return hass.exit_code
return hass.start()
def try_to_restart() -> None:
@ -359,11 +369,13 @@ def try_to_restart() -> None:
def main() -> int:
"""Start Home Assistant."""
validate_python()
attempt_use_uvloop()
if sys.version_info[:3] < (3, 5, 3):
monkey_patch_asyncio()
validate_python()
args = get_arguments()
if args.script is not None:

View File

@ -74,8 +74,6 @@ def async_from_config_dict(config: Dict[str, Any],
This method is a coroutine.
"""
start = time()
hass.async_track_tasks()
core_config = config.get(core.DOMAIN, {})
try:
@ -140,10 +138,10 @@ def async_from_config_dict(config: Dict[str, Any],
continue
hass.async_add_job(async_setup_component(hass, component, config))
yield from hass.async_stop_track_tasks()
yield from hass.async_block_till_done()
stop = time()
_LOGGER.info('Home Assistant initialized in %ss', round(stop-start, 2))
_LOGGER.info('Home Assistant initialized in %.2fs', stop-start)
async_register_signal_handling(hass)
return hass

View File

@ -1,13 +1,13 @@
"""
Interfaces with Alarm.com alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import logging
import asyncio
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
@ -15,10 +15,9 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, CONF_CODE,
CONF_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['https://github.com/Xorso/pyalarmdotcom'
'/archive/0.1.1.zip'
'#pyalarmdotcom==0.1.1']
REQUIREMENTS = ['pyalarmdotcom==0.2.9']
_LOGGER = logging.getLogger(__name__)
@ -32,14 +31,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup an Alarm.com control panel."""
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a Alarm.com control panel."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
add_devices([AlarmDotCom(hass, name, code, username, password)], True)
alarmdotcom = AlarmDotCom(hass, name, code, username, password)
yield from alarmdotcom.async_login()
async_add_devices([alarmdotcom])
class AlarmDotCom(alarm.AlarmControlPanel):
@ -47,18 +49,30 @@ class AlarmDotCom(alarm.AlarmControlPanel):
def __init__(self, hass, name, code, username, password):
"""Initialize the Alarm.com status."""
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
self._alarm = Alarmdotcom(username, password, timeout=10)
from pyalarmdotcom import Alarmdotcom
_LOGGER.debug('Setting up Alarm.com...')
self._hass = hass
self._name = name
self._code = str(code) if code else None
self._username = username
self._password = password
self._websession = async_get_clientsession(self._hass)
self._state = STATE_UNKNOWN
self._alarm = Alarmdotcom(username,
password,
self._websession,
hass.loop)
def update(self):
@asyncio.coroutine
def async_login(self):
"""Login to Alarm.com."""
yield from self._alarm.async_login()
@asyncio.coroutine
def async_update(self):
"""Fetch the latest state."""
self._state = self._alarm.state
yield from self._alarm.async_update()
return self._alarm.state
@property
def name(self):
@ -73,45 +87,36 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
if self._state == 'Disarmed':
if self._alarm.state.lower() == 'disarmed':
return STATE_ALARM_DISARMED
elif self._state == 'Armed Stay':
elif self._alarm.state.lower() == 'armed stay':
return STATE_ALARM_ARMED_HOME
elif self._state == 'Armed Away':
elif self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
else:
return STATE_UNKNOWN
def alarm_disarm(self, code=None):
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._validate_code(code, 'disarming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.disarm()
if self._validate_code(code):
yield from self._alarm.async_alarm_disarm()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_stay()
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm hom command."""
if self._validate_code(code):
yield from self._alarm.async_alarm_arm_home()
def alarm_arm_away(self, code=None):
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
if not self._validate_code(code, 'arming home'):
return
from pyalarmdotcom.pyalarmdotcom import Alarmdotcom
# Open another session to alarm.com to fire off the command
_alarm = Alarmdotcom(self._username, self._password, timeout=10)
_alarm.arm_away()
if self._validate_code(code):
yield from self._alarm.async_alarm_arm_away()
def _validate_code(self, code, state):
def _validate_code(self, code):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
_LOGGER.warning('Wrong code entered.')
return check

View File

@ -0,0 +1,87 @@
"""Interfaces with TotalConnect alarm control panels."""
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN,
CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['total_connect_client==0.7']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Total Connect'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a TotalConnect control panel."""
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
total_connect = TotalConnect(name, username, password)
add_devices([total_connect], True)
class TotalConnect(alarm.AlarmControlPanel):
"""Represent an TotalConnect status."""
def __init__(self, name, username, password):
"""Initialize the TotalConnect status."""
from total_connect_client import TotalConnectClient
_LOGGER.debug('Setting up TotalConnect...')
self._name = name
self._username = username
self._password = password
self._state = STATE_UNKNOWN
self._client = TotalConnectClient.TotalConnectClient(username,
password)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Return the state of the device."""
status = self._client.get_armed_status()
if status == self._client.DISARMED:
state = STATE_ALARM_DISARMED
elif status == self._client.ARMED_STAY:
state = STATE_ALARM_ARMED_HOME
elif status == self._client.ARMED_AWAY:
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN
self._state = state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_stay()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()

View File

@ -26,17 +26,24 @@ from homeassistant.util.dt import utcnow
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL)
DOMAIN = 'android_ip_webcam'
REQUIREMENTS = ["pydroid-ipcam==0.6"]
REQUIREMENTS = ['pydroid-ipcam==0.8']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
DATA_IP_WEBCAM = 'android_ip_webcam'
ATTR_AUD_CONNS = 'Audio Connections'
ATTR_HOST = 'host'
ATTR_VID_CONNS = 'Video Connections'
ATTR_AUD_CONNS = 'Audio Connections'
CONF_MOTION_SENSOR = 'motion_sensor'
DATA_IP_WEBCAM = 'android_ip_webcam'
DEFAULT_NAME = 'IP Webcam'
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 10
DOMAIN = 'android_ip_webcam'
SCAN_INTERVAL = timedelta(seconds=10)
SIGNAL_UPDATE_DATA = 'android_ip_webcam_update'
KEY_MAP = {
'audio_connections': 'Audio Connections',
@ -123,15 +130,6 @@ SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
'sound', 'video_connections']
SIGNAL_UPDATE_DATA = 'android_ip_webcam_update'
CONF_MOTION_SENSOR = 'motion_sensor'
DEFAULT_NAME = 'IP Webcam'
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 10
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@ -153,7 +151,7 @@ CONFIG_SCHEMA = vol.Schema({
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the IP Webcam component."""
"""Set up the IP Webcam component."""
from pydroid_ipcam import PyDroidIPCam
webcams = hass.data[DATA_IP_WEBCAM] = {}
@ -161,7 +159,7 @@ def async_setup(hass, config):
@asyncio.coroutine
def async_setup_ipcamera(cam_config):
"""Setup a ip camera."""
"""Set up an IP camera."""
host = cam_config[CONF_HOST]
username = cam_config.get(CONF_USERNAME)
password = cam_config.get(CONF_PASSWORD)
@ -171,7 +169,7 @@ def async_setup(hass, config):
sensors = cam_config[CONF_SENSORS]
motion = cam_config[CONF_MOTION_SENSOR]
# init ip webcam
# Init ip webcam
cam = PyDroidIPCam(
hass.loop, websession, host, cam_config[CONF_PORT],
username=username, password=password,
@ -192,7 +190,7 @@ def async_setup(hass, config):
@asyncio.coroutine
def async_update_data(now):
"""Update data from ipcam in SCAN_INTERVAL."""
"""Update data from IP camera in SCAN_INTERVAL."""
yield from cam.update()
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
@ -201,7 +199,7 @@ def async_setup(hass, config):
yield from async_update_data(None)
# load platforms
# Load platforms
webcams[host] = cam
mjpeg_camera = {

View File

@ -50,9 +50,11 @@ def setup(hass, config):
hass.http.register_view(APIDomainServicesView)
hass.http.register_view(APIEventForwardingView)
hass.http.register_view(APIComponentsView)
hass.http.register_view(APIErrorLogView)
hass.http.register_view(APITemplateView)
hass.http.register_static_path(
URL_API_ERROR_LOG, hass.config.path(ERROR_LOG_FILENAME), False)
return True
@ -402,20 +404,6 @@ class APIComponentsView(HomeAssistantView):
return self.json(request.app['hass'].config.components)
class APIErrorLogView(HomeAssistantView):
"""View to handle ErrorLog requests."""
url = URL_API_ERROR_LOG
name = "api:error-log"
@asyncio.coroutine
def get(self, request):
"""Serve error log."""
resp = yield from self.file(
request, request.app['hass'].config.path(ERROR_LOG_FILENAME))
return resp
class APITemplateView(HomeAssistantView):
"""View to handle requests."""

View File

@ -12,10 +12,11 @@ import os
import voluptuous as vol
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import CoreState
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, SERVICE_RELOAD)
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
@ -81,8 +82,7 @@ _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA])
PLATFORM_SCHEMA = vol.Schema({
CONF_ALIAS: cv.string,
vol.Optional(CONF_INITIAL_STATE,
default=DEFAULT_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_INITIAL_STATE): cv.boolean,
vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
@ -101,15 +101,13 @@ TRIGGER_SERVICE_SCHEMA = vol.Schema({
RELOAD_SERVICE_SCHEMA = vol.Schema({})
def is_on(hass, entity_id=None):
def is_on(hass, entity_id):
"""
Return true if specified automation entity_id is on.
Check all automation if no entity_id specified.
Async friendly.
"""
entity_ids = [entity_id] if entity_id else hass.states.entity_ids(DOMAIN)
return any(hass.states.is_state(entity_id, STATE_ON)
for entity_id in entity_ids)
return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id=None):
@ -231,7 +229,6 @@ class AutomationEntity(ToggleEntity):
self._async_detach_triggers = None
self._cond_func = cond_func
self._async_action = async_action
self._enabled = False
self._last_triggered = None
self._hidden = hidden
self._initial_state = initial_state
@ -261,38 +258,54 @@ class AutomationEntity(ToggleEntity):
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._enabled
return self._async_detach_triggers is not None
@asyncio.coroutine
def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state is None:
if self._initial_state:
yield from self.async_enable()
enable_automation = DEFAULT_INITIAL_STATE
if self._initial_state is not None:
enable_automation = self._initial_state
else:
self._last_triggered = state.attributes.get('last_triggered')
if state.state == STATE_ON:
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
enable_automation = state.state == STATE_ON
self._last_triggered = state.attributes.get('last_triggered')
if not enable_automation:
return
# HomeAssistant is starting up
elif self.hass.state == CoreState.not_running:
@asyncio.coroutine
def async_enable_automation(event):
"""Start automation on startup."""
yield from self.async_enable()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_enable_automation)
# HomeAssistant is running
else:
yield from self.async_enable()
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self._enabled:
if self.is_on:
return
yield from self.async_enable()
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self._enabled:
if not self.is_on:
return
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
yield from self.async_update_ha_state()
@asyncio.coroutine
@ -318,12 +331,12 @@ class AutomationEntity(ToggleEntity):
This method is a coroutine.
"""
if self._enabled:
if self.is_on:
return
self._async_detach_triggers = yield from self._async_attach_triggers(
self.async_trigger)
self._enabled = True
yield from self.async_update_ha_state()
@asyncio.coroutine
@ -342,7 +355,7 @@ def _async_process_config(hass, config, component):
list_no)
hidden = config_block[CONF_HIDE_ENTITY]
initial_state = config_block[CONF_INITIAL_STATE]
initial_state = config_block.get(CONF_INITIAL_STATE)
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
name)

View File

@ -2,15 +2,15 @@
Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#event-trigger
at https://home-assistant.io/docs/automation/trigger/#event-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.core import callback, CoreState
from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START
from homeassistant.helpers import config_validation as cv
CONF_EVENT_TYPE = "event_type"
@ -31,6 +31,19 @@ def async_trigger(hass, config, action):
event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA)
if (event_type == EVENT_HOMEASSISTANT_START and
hass.state == CoreState.starting):
_LOGGER.warning('Deprecation: Automations should not listen to event '
"'homeassistant_start'. Use platform 'homeassistant' "
'instead. Feature will be removed in 0.45')
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': None,
},
})
return lambda: None
@callback
def handle_event(event):
"""Listen for events and calls the action when data matches."""

View File

@ -0,0 +1,55 @@
"""
Offer Home Assistant core automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#homeassistant-trigger
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback, CoreState
from homeassistant.const import (
CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP)
EVENT_START = 'start'
EVENT_SHUTDOWN = 'shutdown'
_LOGGER = logging.getLogger(__name__)
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'homeassistant',
vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN),
})
@asyncio.coroutine
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
if event == EVENT_SHUTDOWN:
@callback
def hass_shutdown(event):
"""Called when Home Assistant is shutting down."""
hass.async_run_job(action, {
'trigger': {
'platform': 'homeassistant',
'event': event,
},
})
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
hass_shutdown)
# Automation are enabled while hass is starting up, fire right away
# Check state because a config reload shouldn't trigger it.
elif hass.state == CoreState.starting:
hass.async_run_job(action, {
'trigger': {
'platform': 'homeassistant',
'event': event,
},
})
return lambda: None

View File

@ -70,7 +70,7 @@ def async_trigger(hass, config, action):
nonlocal held_less_than, held_more_than
pressed_time = dt_util.utcnow()
if held_more_than is None and held_less_than is None:
call_action()
hass.add_job(call_action)
if held_more_than is not None and held_less_than is None:
cancel_pressed_more_than = track_point_in_utc_time(
hass,
@ -88,7 +88,7 @@ def async_trigger(hass, config, action):
held_time = dt_util.utcnow() - pressed_time
if held_less_than is not None and held_time < held_less_than:
if held_more_than is None or held_time > held_more_than:
call_action()
hass.add_job(call_action)
hass.data['litejet_system'].on_switch_pressed(number, pressed)
hass.data['litejet_system'].on_switch_released(number, released)

View File

@ -2,7 +2,7 @@
Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#mqtt-trigger
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
"""
import asyncio
import json

View File

@ -2,7 +2,7 @@
Offer numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
"""
import asyncio
import logging

View File

@ -2,7 +2,7 @@
Offer state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#state-trigger
at https://home-assistant.io/docs/automation/trigger/#state-trigger
"""
import asyncio
import voluptuous as vol

View File

@ -2,7 +2,7 @@
Offer sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#sun-trigger
at https://home-assistant.io/docs/automation/trigger/#sun-trigger
"""
import asyncio
from datetime import timedelta

View File

@ -2,7 +2,7 @@
Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#template-trigger
at https://home-assistant.io/docs/automation/trigger/#template-trigger
"""
import asyncio
import logging

View File

@ -2,7 +2,7 @@
Offer time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#time-trigger
at https://home-assistant.io/docs/automation/trigger/#time-trigger
"""
import asyncio
import logging

View File

@ -2,7 +2,7 @@
Offer zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#zone-trigger
at https://home-assistant.io/docs/automation/trigger/#zone-trigger
"""
import asyncio
import voluptuous as vol

View File

@ -11,7 +11,7 @@ DEPENDENCIES = ['blink']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the blink binary sensors."""
"""Set up the blink binary sensors."""
if discovery_info is None:
return

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.1.0']
REQUIREMENTS = ['pyhik==0.1.2']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
@ -33,7 +33,6 @@ ATTR_DELAY = 'delay'
DEVICE_CLASS_MAP = {
'Motion': 'motion',
'Line Crossing': 'motion',
'IO Trigger': None,
'Field Detection': 'motion',
'Video Loss': None,
'Tamper Detection': 'motion',
@ -47,6 +46,7 @@ DEVICE_CLASS_MAP = {
'Bad Video': None,
'PIR Alarm': 'motion',
'Face Detection': 'motion',
'Scene Change Detection': 'motion',
}
CUSTOMIZE_SCHEMA = vol.Schema({
@ -91,24 +91,30 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
entities = []
for sensor in data.sensors:
# Build sensor name, then parse customize config.
sensor_name = sensor.replace(' ', '_')
for sensor, channel_list in data.sensors.items():
for channel in channel_list:
# Build sensor name, then parse customize config.
if data.type == 'NVR':
sensor_name = '{}_{}'.format(
sensor.replace(' ', '_'), channel[1])
else:
sensor_name = sensor.replace(' ', '_')
custom = customize.get(sensor_name.lower(), {})
ignore = custom.get(CONF_IGNORED)
delay = custom.get(CONF_DELAY)
custom = customize.get(sensor_name.lower(), {})
ignore = custom.get(CONF_IGNORED)
delay = custom.get(CONF_DELAY)
_LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s',
data.name, sensor_name, ignore, delay)
if not ignore:
entities.append(HikvisionBinarySensor(hass, sensor, data, delay))
_LOGGER.debug('Entity: %s - %s, Options - Ignore: %s, Delay: %s',
data.name, sensor_name, ignore, delay)
if not ignore:
entities.append(HikvisionBinarySensor(
hass, sensor, channel[1], data, delay))
add_entities(entities)
class HikvisionData(object):
"""Hikvision camera event stream object."""
"""Hikvision device event stream object."""
def __init__(self, hass, url, port, name, username, password):
"""Initialize the data oject."""
@ -144,25 +150,40 @@ class HikvisionData(object):
@property
def cam_id(self):
"""Return camera id."""
"""Return device id."""
return self.camdata.get_id
@property
def name(self):
"""Return camera name."""
"""Return device name."""
return self._name
@property
def type(self):
"""Return device type."""
return self.camdata.get_type
def get_attributes(self, sensor, channel):
"""Return attribute list for sensor/channel."""
return self.camdata.fetch_attributes(sensor, channel)
class HikvisionBinarySensor(BinarySensorDevice):
"""Representation of a Hikvision binary sensor."""
def __init__(self, hass, sensor, cam, delay):
def __init__(self, hass, sensor, channel, cam, delay):
"""Initialize the binary_sensor."""
self._hass = hass
self._cam = cam
self._name = self._cam.name + ' ' + sensor
self._id = self._cam.cam_id + '.' + sensor
self._sensor = sensor
self._channel = channel
if self._cam.type == 'NVR':
self._name = '{} {} {}'.format(self._cam.name, sensor, channel)
else:
self._name = '{} {}'.format(self._cam.name, sensor)
self._id = '{}.{}.{}'.format(self._cam.cam_id, sensor, channel)
if delay is None:
self._delay = 0
@ -176,11 +197,11 @@ class HikvisionBinarySensor(BinarySensorDevice):
def _sensor_state(self):
"""Extract sensor state."""
return self._cam.sensors[self._sensor][0]
return self._cam.get_attributes(self._sensor, self._channel)[0]
def _sensor_last_update(self):
"""Extract sensor last update time."""
return self._cam.sensors[self._sensor][3]
return self._cam.get_attributes(self._sensor, self._channel)[3]
@property
def name(self):

View File

@ -32,7 +32,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
InsteonPLMBinarySensorDevice(hass, plm, address, name)
)
hass.async_add_job(async_add_devices(device_list))
async_add_devices(device_list)
class InsteonPLMBinarySensorDevice(BinarySensorDevice):

View File

@ -0,0 +1,109 @@
"""
This component provides HA sensor support for Ring Door Bell/Chimes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ring/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.ring import (
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE)
from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
DEPENDENCIES = ['ring']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
# Sensor types: Name, category, device_class
SENSOR_TYPES = {
'ding': ['Ding', ['doorbell'], 'occupancy'],
'motion': ['Motion', ['doorbell'], 'motion'],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a Ring device."""
ring = hass.data.get('ring')
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
for device in ring.doorbells:
if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
sensors.append(RingBinarySensor(hass,
device,
sensor_type))
add_devices(sensors, True)
return True
class RingBinarySensor(BinarySensorDevice):
"""A binary sensor implementation for Ring device."""
def __init__(self, hass, data, sensor_type):
"""Initialize a sensor for Ring device."""
super(RingBinarySensor, self).__init__()
self._sensor_type = sensor_type
self._data = data
self._name = "{0} {1}".format(self._data.name,
SENSOR_TYPES.get(self._sensor_type)[0])
self._device_class = SENSOR_TYPES.get(self._sensor_type)[2]
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {}
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
attrs['device_id'] = self._data.id
attrs['firmware'] = self._data.firmware
attrs['timezone'] = self._data.timezone
if self._data.alert and self._data.alert_expires_at:
attrs['expires_at'] = self._data.alert_expires_at
attrs['state'] = self._data.alert.get('state')
return attrs
def update(self):
"""Get the latest data and updates the state."""
self._data.check_alerts()
if self._data.alert:
self._state = (self._sensor_type ==
self._data.alert.get('kind'))
else:
self._state = False

View File

@ -1,4 +1,9 @@
"""Sensor to indicate whether the current day is a workday."""
"""
Sensor to indicate whether the current day is a workday.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.workday/
"""
import asyncio
import logging
import datetime
@ -6,8 +11,8 @@ import datetime
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN,
CONF_NAME, WEEKDAYS)
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNKNOWN, CONF_NAME, WEEKDAYS)
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
@ -48,29 +53,18 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Workday sensor."""
"""Set up the Workday sensor."""
import holidays
# Get the Sensor name from the config
sensor_name = config.get(CONF_NAME)
# Get the country code from the config
country = config.get(CONF_COUNTRY)
# Get the province from the config
province = config.get(CONF_PROVINCE)
# Get the list of workdays from the config
workdays = config.get(CONF_WORKDAYS)
# Get the list of excludes from the config
excludes = config.get(CONF_EXCLUDES)
# Instantiate the holidays module for the current year
year = datetime.datetime.now().year
obj_holidays = getattr(holidays, country)(years=year)
# Also apply the provience, if available for the configured country
if province:
if province not in obj_holidays.PROVINCES:
_LOGGER.error('There is no province/state %s in country %s',
@ -81,14 +75,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
obj_holidays = getattr(holidays, country)(prov=province,
years=year)
# Output found public holidays via the debug channel
_LOGGER.debug("Found the following holidays for your configuration:")
for date, name in sorted(obj_holidays.items()):
_LOGGER.debug("%s %s", date, name)
# Add ourselves as device
add_devices([IsWorkdaySensor(obj_holidays, workdays,
excludes, sensor_name)], True)
add_devices([IsWorkdaySensor(
obj_holidays, workdays, excludes, sensor_name)], True)
def day_to_string(day):
@ -122,7 +114,6 @@ class IsWorkdaySensor(Entity):
def is_include(self, day, now):
"""Check if given day is in the includes list."""
# Check includes
if day in self._workdays:
return True
elif 'holiday' in self._workdays and now in self._obj_holidays:
@ -132,7 +123,6 @@ class IsWorkdaySensor(Entity):
def is_exclude(self, day, now):
"""Check if given day is in the excludes list."""
# Check excludes
if day in self._excludes:
return True
elif 'holiday' in self._excludes and now in self._obj_holidays:

View File

@ -5,17 +5,19 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/blink/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (CONF_USERNAME,
CONF_PASSWORD,
ATTR_FRIENDLY_NAME,
ATTR_ARMED)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, ATTR_FRIENDLY_NAME, ATTR_ARMED)
from homeassistant.helpers import discovery
REQUIREMENTS = ['blinkpy==0.5.2']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'blink'
REQUIREMENTS = ['blinkpy==0.5.2']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@ -50,7 +52,7 @@ class BlinkSystem(object):
def setup(hass, config):
"""Setup Blink System."""
"""Set up Blink System."""
hass.data[DOMAIN] = BlinkSystem(config)
discovery.load_platform(hass, 'camera', DOMAIN, {}, config)
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
@ -77,11 +79,11 @@ def setup(hass, config):
hass.data[DOMAIN].blink.arm = value
hass.data[DOMAIN].blink.refresh()
hass.services.register(DOMAIN, 'snap_picture', snap_picture,
schema=SNAP_PICTURE_SCHEMA)
hass.services.register(DOMAIN, 'arm_camera', arm_camera,
schema=ARM_CAMERA_SCHEMA)
hass.services.register(DOMAIN, 'arm_system', arm_system,
schema=ARM_SYSTEM_SCHEMA)
hass.services.register(
DOMAIN, 'snap_picture', snap_picture, schema=SNAP_PICTURE_SCHEMA)
hass.services.register(
DOMAIN, 'arm_camera', arm_camera, schema=ARM_CAMERA_SCHEMA)
hass.services.register(
DOMAIN, 'arm_system', arm_system, schema=ARM_SYSTEM_SCHEMA)
return True

View File

@ -7,6 +7,7 @@ https://home-assistant.io/components/camera/
"""
import asyncio
import collections
from contextlib import suppress
from datetime import timedelta
import logging
import hashlib
@ -58,7 +59,6 @@ def async_get_image(hass, entity_id, timeout=10):
state.attributes.get(ATTR_ENTITY_PICTURE)
)
response = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
response = yield from websession.get(url)
@ -70,13 +70,9 @@ def async_get_image(hass, entity_id, timeout=10):
image = yield from response.read()
return image
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
raise HomeAssistantError("Can't connect to {0}".format(url))
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
def async_setup(hass, config):
@ -172,7 +168,7 @@ class Camera(Entity):
if not img_bytes:
break
if img_bytes is not None and img_bytes != last_image:
if img_bytes and img_bytes != last_image:
write(img_bytes)
# Chrome seems to always ignore first picture,
@ -185,8 +181,8 @@ class Camera(Entity):
yield from asyncio.sleep(.5)
except (asyncio.CancelledError, ConnectionResetError):
_LOGGER.debug("Close stream by frontend.")
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
response = None
finally:
@ -268,16 +264,14 @@ class CameraImageView(CameraView):
@asyncio.coroutine
def handle(self, request, camera):
"""Serve camera image."""
try:
image = yield from camera.async_camera_image()
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(10, loop=request.app['hass'].loop):
image = yield from camera.async_camera_image()
if image is None:
return web.Response(status=500)
if image:
return web.Response(body=image)
return web.Response(body=image)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
return web.Response(status=500)
class CameraMjpegStream(CameraView):

View File

@ -16,9 +16,9 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream)
async_get_clientsession, async_aiohttp_proxy_web)
REQUIREMENTS = ['amcrest==1.1.4']
REQUIREMENTS = ['amcrest==1.1.8']
_LOGGER = logging.getLogger(__name__)
@ -125,7 +125,7 @@ class AmcrestCam(Camera):
stream_coro = websession.get(
streaming_url, auth=self._token, timeout=TIMEOUT)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@ -8,14 +8,14 @@ import asyncio
import logging
import voluptuous as vol
from aiohttp import web
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
@ -69,26 +69,10 @@ class FFmpegCamera(Camera):
yield from stream.open_camera(
self._input, extra_cmd=self._extra_arguments)
response = web.StreamResponse()
response.content_type = 'multipart/x-mixed-replace;boundary=ffserver'
yield from response.prepare(request)
try:
while True:
data = yield from stream.read(102400)
if not data:
break
response.write(data)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by frontend.")
response = None
finally:
yield from stream.close()
if response is not None:
yield from response.write_eof()
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@property
def name(self):

View File

@ -66,9 +66,13 @@ class FoscamCamera(Camera):
def camera_image(self):
"""Return a still image reponse from the camera."""
# Send the request to snap a picture and return raw jpg data
response = requests.get(self._snap_picture_url, timeout=10)
return response.content
# Handle exception if host is not reachable or url failed
try:
response = requests.get(self._snap_picture_url, timeout=10)
except requests.exceptions.ConnectionError:
return None
else:
return response.content
@property
def name(self):

View File

@ -107,7 +107,6 @@ class GenericCamera(Camera):
None, fetch)
# async
else:
response = None
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
@ -117,14 +116,9 @@ class GenericCamera(Camera):
except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image')
return self._last_image
except (aiohttp.errors.ClientError,
aiohttp.errors.DisconnectedError,
aiohttp.errors.HttpProcessingError) as err:
except aiohttp.ClientError as err:
_LOGGER.error('Error getting new camera image: %s', err)
return self._last_image
finally:
if response is not None:
yield from response.release()
self._last_url = url
return self._last_image

View File

@ -19,7 +19,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_stream)
async_get_clientsession, async_aiohttp_proxy_web)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -93,7 +93,6 @@ class MjpegCamera(Camera):
return image
websession = async_get_clientsession(self.hass)
response = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from websession.get(
@ -105,14 +104,9 @@ class MjpegCamera(Camera):
except asyncio.TimeoutError:
_LOGGER.error('Timeout getting camera image')
except (aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as err:
except aiohttp.ClientError as err:
_LOGGER.error('Error getting new camera image: %s', err)
finally:
if response is not None:
yield from response.release()
def camera_image(self):
"""Return a still image response from the camera."""
if self._username and self._password:
@ -140,7 +134,7 @@ class MjpegCamera(Camera):
websession = async_get_clientsession(self.hass)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@ -19,7 +19,7 @@ from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_create_clientsession,
async_aiohttp_proxy_stream)
async_aiohttp_proxy_web)
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
@ -74,7 +74,6 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
'version': '1',
'query': 'SYNO.'
}
query_req = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
query_req = yield from websession_init.get(
@ -88,14 +87,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_api_url)
return False
finally:
if query_req is not None:
yield from query_req.release()
# Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format(
config.get(CONF_URL), WEBAPI_PATH, auth_path)
@ -128,13 +123,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
syno_camera_url,
params=camera_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", syno_camera_url)
return False
camera_resp = yield from camera_req.json()
cameras = camera_resp['data']['cameras']
yield from camera_req.release()
# add cameras
devices = []
@ -172,7 +166,6 @@ def get_session_id(hass, websession, username, password, login_url, timeout):
'session': 'SurveillanceStation',
'format': 'sid'
}
auth_req = None
try:
with async_timeout.timeout(timeout, loop=hass.loop):
auth_req = yield from websession.get(
@ -182,14 +175,10 @@ def get_session_id(hass, websession, username, password, login_url, timeout):
auth_resp = yield from auth_req.json()
return auth_resp['data']['sid']
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", login_url)
return False
finally:
if auth_req is not None:
yield from auth_req.release()
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
@ -235,12 +224,11 @@ class SynologyCamera(Camera):
image_url,
params=image_payload
)
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
_LOGGER.exception("Error on %s", image_url)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error fetching %s", image_url)
return None
image = yield from response.read()
yield from response.release()
return image
@ -260,7 +248,7 @@ class SynologyCamera(Camera):
stream_coro = self._websession.get(
streaming_url, params=streaming_payload)
yield from async_aiohttp_proxy_stream(self.hass, request, stream_coro)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@ -692,18 +692,16 @@ class ClimateDevice(Entity):
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if (temp is None or not isinstance(temp, Number) or
self.temperature_unit == self.unit_of_measurement):
if temp is None or not isinstance(temp, Number):
return temp
value = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
if self.temperature_unit != self.unit_of_measurement:
temp = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
# Round in the units appropriate
if self.precision == PRECISION_HALVES:
return round(value * 2) / 2.0
return round(temp * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(value, 1)
return round(temp, 1)
else:
# PRECISION_WHOLE as a fall back
return round(value)
return round(temp)

View File

@ -16,11 +16,15 @@ _LOGGER = logging.getLogger(__name__)
STATE_MANUAL = "manual"
STATE_BOOST = "boost"
STATE_COMFORT = "comfort"
STATE_LOWERING = "lowering"
HM_STATE_MAP = {
"AUTO_MODE": STATE_AUTO,
"MANU_MODE": STATE_MANUAL,
"BOOST_MODE": STATE_BOOST,
"COMFORT_MODE": STATE_COMFORT,
"LOWERING_MODE": STATE_LOWERING
}
HM_TEMP_MAP = [

View File

@ -1,40 +1,40 @@
"""tado component to create a climate device for each zone."""
"""
Tado component to create a climate device for each zone.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.tado/
"""
import logging
from homeassistant.const import TEMP_CELSIUS
from homeassistant.components.climate import (
ClimateDevice)
from homeassistant.const import (
ATTR_TEMPERATURE)
from homeassistant.components.tado import (
DATA_TADO)
CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode
CONST_MODE_OFF = "OFF" # Switch off heating in a zone
# When we change the temperature setting, we need an overlay mode
# wait until tado changes the mode automatic
CONST_OVERLAY_TADO_MODE = "TADO_MODE"
# the user has change the temperature or mode manually
CONST_OVERLAY_MANUAL = "MANUAL"
# the temperature will be reset after a timespan
CONST_OVERLAY_TIMER = "TIMER"
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: "Manual",
CONST_OVERLAY_TIMER: "Timer",
CONST_OVERLAY_TADO_MODE: "Tado mode",
CONST_MODE_SMART_SCHEDULE: "Smart schedule",
CONST_MODE_OFF: "Off"}
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.components.tado import DATA_TADO
_LOGGER = logging.getLogger(__name__)
CONST_MODE_SMART_SCHEDULE = 'SMART_SCHEDULE' # Default mytado mode
CONST_MODE_OFF = 'OFF' # Switch off heating in a zone
# When we change the temperature setting, we need an overlay mode
# wait until tado changes the mode automatic
CONST_OVERLAY_TADO_MODE = 'TADO_MODE'
# the user has change the temperature or mode manually
CONST_OVERLAY_MANUAL = 'MANUAL'
# the temperature will be reset after a timespan
CONST_OVERLAY_TIMER = 'TIMER'
OPERATION_LIST = {
CONST_OVERLAY_MANUAL: 'Manual',
CONST_OVERLAY_TIMER: 'Timer',
CONST_OVERLAY_TADO_MODE: 'Tado mode',
CONST_MODE_SMART_SCHEDULE: 'Smart schedule',
CONST_MODE_OFF: 'Off',
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the climate platform."""
# get the PyTado object from the hub component
"""Set up the Tado climate platform."""
tado = hass.data[DATA_TADO]
try:
@ -45,10 +45,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
climate_devices = []
for zone in zones:
climate_devices.append(create_climate_device(tado, hass,
zone,
zone['name'],
zone['id']))
climate_devices.append(create_climate_device(
tado, hass, zone, zone['name'], zone['id']))
if len(climate_devices) > 0:
add_devices(climate_devices, True)
@ -58,13 +56,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
def create_climate_device(tado, hass, zone, name, zone_id):
"""Create a climate device."""
"""Create a Tado climate device."""
capabilities = tado.get_capabilities(zone_id)
unit = TEMP_CELSIUS
min_temp = float(capabilities["temperatures"]["celsius"]["min"])
max_temp = float(capabilities["temperatures"]["celsius"]["max"])
ac_mode = capabilities["type"] != "HEATING"
min_temp = float(capabilities['temperatures']['celsius']['min'])
max_temp = float(capabilities['temperatures']['celsius']['max'])
ac_mode = capabilities['type'] != 'HEATING'
data_id = 'zone {} {}'.format(name, zone_id)
device = TadoClimate(tado,
@ -74,10 +72,10 @@ def create_climate_device(tado, hass, zone, name, zone_id):
ac_mode)
tado.add_sensor(data_id, {
"id": zone_id,
"zone": zone,
"name": name,
"climate": device
'id': zone_id,
'zone': zone,
'name': name,
'climate': device
})
return device
@ -89,7 +87,7 @@ class TadoClimate(ClimateDevice):
def __init__(self, store, zone_name, zone_id, data_id,
min_temp, max_temp, ac_mode,
tolerance=0.3):
"""Initialization of TadoClimate device."""
"""Initialization of Tado climate device."""
self._store = store
self._data_id = data_id
@ -202,8 +200,7 @@ class TadoClimate(ClimateDevice):
data = self._store.get_data(self._data_id)
if data is None:
_LOGGER.debug('Recieved no data for zone %s',
self.zone_name)
_LOGGER.debug("Recieved no data for zone %s", self.zone_name)
return
if 'sensorDataPoints' in data:
@ -232,11 +229,11 @@ class TadoClimate(ClimateDevice):
if 'tadoMode' in data:
mode = data['tadoMode']
self._is_away = mode == "AWAY"
self._is_away = mode == 'AWAY'
if 'setting' in data:
power = data['setting']['power']
if power == "OFF":
if power == 'OFF':
self._current_operation = CONST_MODE_OFF
self._device_is_active = False
else:
@ -249,48 +246,47 @@ class TadoClimate(ClimateDevice):
overlay = False
termination = ""
# if you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
# If you set mode manualy to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
if overlay and self._device_is_active:
# there is an overlay the device is on
# There is an overlay the device is on
self._overlay_mode = termination
self._current_operation = termination
else:
# there is no overlay, the mode will always be
# "SMART_SCHEDULE"
# There is no overlay, the mode will always be
# "SMART_SCHEDULE"
self._overlay_mode = CONST_MODE_SMART_SCHEDULE
self._current_operation = CONST_MODE_SMART_SCHEDULE
def _control_heating(self):
"""Send new target temperature to mytado."""
if not self._active and None not in (self._cur_temp,
self._target_temp):
if not self._active and None not in (
self._cur_temp, self._target_temp):
self._active = True
_LOGGER.info('Obtained current and target temperature. '
'tado thermostat active.')
_LOGGER.info("Obtained current and target temperature. "
"Tado thermostat active")
if not self._active or self._current_operation == self._overlay_mode:
return
if self._current_operation == CONST_MODE_SMART_SCHEDULE:
_LOGGER.info('Switching mytado.com to SCHEDULE (default) '
'for zone %s', self.zone_name)
_LOGGER.info("Switching mytado.com to SCHEDULE (default) "
"for zone %s", self.zone_name)
self._store.reset_zone_overlay(self.zone_id)
self._overlay_mode = self._current_operation
return
if self._current_operation == CONST_MODE_OFF:
_LOGGER.info('Switching mytado.com to OFF for zone %s',
_LOGGER.info("Switching mytado.com to OFF for zone %s",
self.zone_name)
self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL)
self._overlay_mode = self._current_operation
return
_LOGGER.info('Switching mytado.com to %s mode for zone %s',
_LOGGER.info("Switching mytado.com to %s mode for zone %s",
self._current_operation, self.zone_name)
self._store.set_zone_overlay(self.zone_id,
self._current_operation,
self._target_temp)
self._store.set_zone_overlay(
self.zone_id, self._current_operation, self._target_temp)
self._overlay_mode = self._current_operation

View File

@ -19,7 +19,6 @@ DEPENDENCIES = ['wink']
STATE_AUX = 'aux'
STATE_ECO = 'eco'
STATE_FAN = 'fan'
SPEED_LOWEST = 'lowest'
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
@ -400,7 +399,7 @@ class WinkAC(WinkDevice, ClimateDevice):
op_list.append(STATE_COOL)
if 'auto_eco' in modes:
op_list.append(STATE_ECO)
if 'fan_eco' in modes:
if 'fan_only' in modes:
op_list.append(STATE_FAN)
return op_list
@ -439,9 +438,7 @@ class WinkAC(WinkDevice, ClimateDevice):
def current_fan_mode(self):
"""Return the current fan mode."""
speed = self.wink.current_fan_speed()
if speed <= 0.3 and speed >= 0.0:
return SPEED_LOWEST
elif speed <= 0.5 and speed > 0.3:
if speed <= 0.4 and speed > 0.3:
return SPEED_LOW
elif speed <= 0.8 and speed > 0.5:
return SPEED_MEDIUM
@ -453,14 +450,12 @@ class WinkAC(WinkDevice, ClimateDevice):
@property
def fan_list(self):
"""List of available fan modes."""
return [SPEED_LOWEST, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, mode):
"""Set fan speed."""
if mode == SPEED_LOWEST:
speed = 0.3
elif mode == SPEED_LOW:
speed = 0.5
if mode == SPEED_LOW:
speed = 0.4
elif mode == SPEED_MEDIUM:
speed = 0.8
elif mode == SPEED_HIGH:

View File

@ -112,8 +112,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = (
round((float(self._current_temperature)), 1))
if self._current_temperature is not None:
self._target_temperature = (
round((float(self._current_temperature)), 1))
else:
self._target_temperature = round(
(float(self.values.primary.data)), 1)

View File

@ -47,6 +47,7 @@ class VeraCover(VeraDevice, CoverDevice):
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self.vera_device.set_level(position)
self.schedule_update_ha_state()
@property
def is_closed(self):
@ -60,11 +61,14 @@ class VeraCover(VeraDevice, CoverDevice):
def open_cover(self, **kwargs):
"""Open the cover."""
self.vera_device.open()
self.schedule_update_ha_state()
def close_cover(self, **kwargs):
"""Close the cover."""
self.vera_device.close()
self.schedule_update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.vera_device.stop()
self.schedule_update_ha_state()

View File

@ -20,12 +20,13 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
def get_device(values, **kwargs):
def get_device(values, node_config, **kwargs):
"""Create zwave entity device."""
invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS)
if (values.primary.command_class ==
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and values.primary.index == 0):
return ZwaveRollershutter(values)
return ZwaveRollershutter(values, invert_buttons)
elif (values.primary.command_class in [
zwave.const.COMMAND_CLASS_SWITCH_BINARY,
zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]):
@ -36,13 +37,14 @@ def get_device(values, **kwargs):
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Representation of an Zwave roller shutter."""
def __init__(self, values):
def __init__(self, values, invert_buttons):
"""Initialize the zwave rollershutter."""
ZWaveDeviceEntity.__init__(self, values, DOMAIN)
# pylint: disable=no-member
self._open_id = None
self._close_id = None
self._current_position = None
self._invert_buttons = invert_buttons
self._workaround = workaround.get_device_mapping(values.primary)
if self._workaround:
@ -56,10 +58,9 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
if self.values.open and self.values.close and \
self._open_id is None and self._close_id is None:
if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
if self._invert_buttons:
self._open_id = self.values.close.value_id
self._close_id = self.values.open.value_id
self._workaround = None
else:
self._open_id = self.values.open.value_id
self._close_id = self.values.close.value_id

View File

@ -553,7 +553,6 @@ class Device(Entity):
# bytes like 00 get truncates to 0, API needs full bytes
oui = '{:02x}:{:02x}:{:02x}'.format(*[int(b, 16) for b in oui_bytes])
url = 'http://api.macvendors.com/' + oui
resp = None
try:
websession = async_get_clientsession(self.hass)
@ -570,13 +569,9 @@ class Device(Entity):
# in the 'known_devices.yaml' file which only happens
# the first time the device is seen.
return 'unknown'
except (asyncio.TimeoutError, aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError):
except (asyncio.TimeoutError, aiohttp.ClientError):
# same as above
return 'unknown'
finally:
if resp is not None:
yield from resp.release()
@asyncio.coroutine
def async_added_to_hass(self):

View File

@ -18,6 +18,7 @@ from homeassistant.components.device_tracker import ( # NOQA
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
URL = '/api/locative'
def setup_scanner(hass, config, see, discovery_info=None):
@ -30,7 +31,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
class LocativeView(HomeAssistantView):
"""View to handle locative requests."""
url = '/api/locative'
url = URL
name = 'api:locative'
def __init__(self, see):

View File

@ -16,11 +16,11 @@ _LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Setup the MySensors tracker."""
def mysensors_callback(gateway, node_id):
def mysensors_callback(gateway, msg):
"""Callback for mysensors platform."""
node = gateway.sensors[node_id]
node = gateway.sensors[msg.node_id]
if node.sketch_name is None:
_LOGGER.info('No sketch_name: node %s', node_id)
_LOGGER.info('No sketch_name: node %s', msg.node_id)
return
pres = gateway.const.Presentation
@ -37,12 +37,12 @@ def setup_scanner(hass, config, see, discovery_info=None):
'latitude,longitude,altitude', position)
continue
name = '{} {} {}'.format(
node.sketch_name, node_id, child.id)
node.sketch_name, msg.node_id, child.id)
attr = {
mysensors.ATTR_CHILD_ID: child.id,
mysensors.ATTR_DESCRIPTION: child.description,
mysensors.ATTR_DEVICE: gateway.device,
mysensors.ATTR_NODE_ID: node_id,
mysensors.ATTR_NODE_ID: msg.node_id,
}
see(
dev_id=slugify(name),

View File

@ -19,7 +19,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.4']
REQUIREMENTS = ['pysnmp==4.3.5']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
@ -125,7 +125,10 @@ class SnmpScanner(DeviceScanner):
for resrow in restable:
for _, val in resrow:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
try:
mac = binascii.hexlify(val.asOctets()).decode('utf-8')
except AttributeError:
continue
_LOGGER.debug("Found MAC %s", mac)
mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
devices.append({'mac': mac})

View File

@ -105,8 +105,6 @@ class TadoDeviceScanner(DeviceScanner):
_LOGGER.debug("Requesting Tado")
last_results = []
response = None
tado_json = None
try:
with async_timeout.timeout(10, loop=self.hass.loop):
@ -127,14 +125,10 @@ class TadoDeviceScanner(DeviceScanner):
tado_json = yield from response.json()
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Cannot load Tado data")
return False
finally:
if response is not None:
yield from response.release()
# Without a home_id, we fetched an URL where the mobile devices can be
# found under the mobileDevices key.
if 'mobileDevices' in tado_json:

View File

@ -101,7 +101,6 @@ class UPCDeviceScanner(DeviceScanner):
@asyncio.coroutine
def async_login(self):
"""Login into firmware and get first token."""
response = None
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
@ -109,7 +108,8 @@ class UPCDeviceScanner(DeviceScanner):
"http://{}/common_page/login.html".format(self.host)
)
yield from response.text()
yield from response.text()
self.token = response.cookies['sessionToken'].value
# login
@ -119,18 +119,12 @@ class UPCDeviceScanner(DeviceScanner):
})
# successfull?
if data is not None:
return True
return False
return data is not None
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Can not load login page from %s", self.host)
return False
finally:
if response is not None:
yield from response.release()
@asyncio.coroutine
def _async_ws_function(self, function, additional_form=None):
"""Execute a command on UPC firmware webservice."""
@ -142,8 +136,7 @@ class UPCDeviceScanner(DeviceScanner):
if additional_form:
form_data.update(additional_form)
redirects = True if function != CMD_DEVICES else False
response = None
redirects = function != CMD_DEVICES
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from self.websession.post(
@ -163,10 +156,6 @@ class UPCDeviceScanner(DeviceScanner):
self.token = response.cookies['sessionToken'].value
return (yield from response.text())
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error on %s", function)
self.token = None
finally:
if response is not None:
yield from response.release()

View File

@ -153,7 +153,7 @@ def setup(hass, config):
sw_path = "service_worker.js"
hass.http.register_static_path("/service_worker.js",
os.path.join(STATIC_PATH, sw_path), 0)
os.path.join(STATIC_PATH, sw_path), False)
hass.http.register_static_path("/robots.txt",
os.path.join(STATIC_PATH, "robots.txt"))
hass.http.register_static_path("/static", STATIC_PATH)

View File

@ -3,8 +3,8 @@
FINGERPRINTS = {
"compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0",
"core.js": "5d08475f03adb5969bd31855d5ca0cfd",
"frontend.html": "53c45b837a3bcae7cfb9ef4a5919844f",
"mdi.html": "4921d26f29dc148c3e8bd5bcd8ce5822",
"frontend.html": "feaf3e9453eca239f29eb10e7645a84f",
"mdi.html": "989f02c51eba561dc32b9ecc628a84b3",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "6dcb246cd356307a638f81c4f89bf9b3",
"panels/ha-panel-dev-event.html": "1f169700c2345785855b1d7919d12326",
@ -12,9 +12,9 @@ FINGERPRINTS = {
"panels/ha-panel-dev-service.html": "0fe8e6acdccf2dc3d1ae657b2c7f2df0",
"panels/ha-panel-dev-state.html": "48d37db4a1d6708314ded1d624d0f4d4",
"panels/ha-panel-dev-template.html": "6f353392d68574fbc5af188bca44d0ae",
"panels/ha-panel-history.html": "bfd5f929d5aa9cefdd799ec37624efa1",
"panels/ha-panel-history.html": "6945cebe5d8075aae560d2c4246b063f",
"panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab",
"panels/ha-panel-logbook.html": "a1fc2b5d739bedb9d87e4da4cd929a71",
"panels/ha-panel-map.html": "9aa065b1908089f3bb5af7fdf9485be5",
"panels/ha-panel-map.html": "e3c7a54f90dd4269d7e53cdcd96514c6",
"websocket_test.html": "575de64b431fe11c3785bf96d7813450"
}

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit f4c59e1eff3223262c198a29cf70c62572de019b
Subproject commit abbdc6f055524c5d3ed0bb50e35400fed40d573f

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,272 @@
"""
Exposes regular rest commands as services.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/hassio/
"""
import asyncio
import logging
import os
import aiohttp
from aiohttp import web
from aiohttp.web_exceptions import HTTPBadGateway
import async_timeout
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.components.http import HomeAssistantView
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
DOMAIN = 'hassio'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
LONG_TASK_TIMEOUT = 900
DEFAULT_TIMEOUT = 10
SERVICE_HOST_SHUTDOWN = 'host_shutdown'
SERVICE_HOST_REBOOT = 'host_reboot'
SERVICE_HOST_UPDATE = 'host_update'
SERVICE_SUPERVISOR_UPDATE = 'supervisor_update'
SERVICE_HOMEASSISTANT_UPDATE = 'homeassistant_update'
SERVICE_ADDON_INSTALL = 'addon_install'
SERVICE_ADDON_UNINSTALL = 'addon_uninstall'
SERVICE_ADDON_UPDATE = 'addon_update'
SERVICE_ADDON_START = 'addon_start'
SERVICE_ADDON_STOP = 'addon_stop'
ATTR_ADDON = 'addon'
ATTR_VERSION = 'version'
SCHEMA_SERVICE_UPDATE = vol.Schema({
vol.Optional(ATTR_VERSION): cv.string,
})
SCHEMA_SERVICE_ADDONS = vol.Schema({
vol.Required(ATTR_ADDON): cv.slug,
})
SCHEMA_SERVICE_ADDONS_VERSION = SCHEMA_SERVICE_ADDONS.extend({
vol.Optional(ATTR_VERSION): cv.string,
})
SERVICE_MAP = {
SERVICE_HOST_SHUTDOWN: None,
SERVICE_HOST_REBOOT: None,
SERVICE_HOST_UPDATE: SCHEMA_SERVICE_UPDATE,
SERVICE_SUPERVISOR_UPDATE: SCHEMA_SERVICE_UPDATE,
SERVICE_HOMEASSISTANT_UPDATE: SCHEMA_SERVICE_UPDATE,
SERVICE_ADDON_INSTALL: SCHEMA_SERVICE_ADDONS_VERSION,
SERVICE_ADDON_UNINSTALL: SCHEMA_SERVICE_ADDONS,
SERVICE_ADDON_START: SCHEMA_SERVICE_ADDONS,
SERVICE_ADDON_STOP: SCHEMA_SERVICE_ADDONS,
SERVICE_ADDON_UPDATE: SCHEMA_SERVICE_ADDONS_VERSION,
}
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the hassio component."""
try:
host = os.environ['HASSIO']
except KeyError:
_LOGGER.error("No HassIO supervisor detect!")
return False
websession = async_get_clientsession(hass)
hassio = HassIO(hass.loop, websession, host)
api_ok = yield from hassio.is_connected()
if not api_ok:
_LOGGER.error("Not connected with HassIO!")
return False
# register base api views
for base in ('host', 'homeassistant'):
hass.http.register_view(HassIOBaseView(hassio, base))
for base in ('supervisor', 'network'):
hass.http.register_view(HassIOBaseEditView(hassio, base))
# register view for addons
hass.http.register_view(HassIOAddonsView(hassio))
@asyncio.coroutine
def async_service_handler(service):
"""Handle HassIO service calls."""
addon = service.data.get(ATTR_ADDON)
if ATTR_VERSION in service.data:
version = {ATTR_VERSION: service.data[ATTR_VERSION]}
else:
version = None
# map to api call
if service.service == SERVICE_HOST_UPDATE:
yield from hassio.send_command(
"/host/update", payload=version)
elif service.service == SERVICE_HOST_REBOOT:
yield from hassio.send_command("/host/reboot")
elif service.service == SERVICE_HOST_SHUTDOWN:
yield from hassio.send_command("/host/shutdown")
elif service.service == SERVICE_SUPERVISOR_UPDATE:
yield from hassio.send_command(
"/supervisor/update", payload=version)
elif service.service == SERVICE_HOMEASSISTANT_UPDATE:
yield from hassio.send_command(
"/homeassistant/update", payload=version,
timeout=LONG_TASK_TIMEOUT)
elif service.service == SERVICE_ADDON_INSTALL:
yield from hassio.send_command(
"/addons/{}/install".format(addon), payload=version,
timeout=LONG_TASK_TIMEOUT)
elif service.service == SERVICE_ADDON_UNINSTALL:
yield from hassio.send_command(
"/addons/{}/uninstall".format(addon))
elif service.service == SERVICE_ADDON_START:
yield from hassio.send_command("/addons/{}/start".format(addon))
elif service.service == SERVICE_ADDON_STOP:
yield from hassio.send_command("/addons/{}/stop".format(addon))
elif service.service == SERVICE_ADDON_UPDATE:
yield from hassio.send_command(
"/addons/{}/update".format(addon), payload=version,
timeout=LONG_TASK_TIMEOUT)
descriptions = yield from hass.loop.run_in_executor(
None, load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
for service, schema in SERVICE_MAP.items():
hass.services.async_register(
DOMAIN, service, async_service_handler,
descriptions[DOMAIN][service], schema=schema)
return True
class HassIO(object):
"""Small API wrapper for HassIO."""
def __init__(self, loop, websession, ip):
"""Initialze HassIO api."""
self.loop = loop
self.websession = websession
self._ip = ip
def is_connected(self):
"""Return True if it connected to HassIO supervisor.
Return a coroutine.
"""
return self.send_command("/supervisor/ping")
@asyncio.coroutine
def send_command(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT):
"""Send request to API."""
answer = yield from self.send_raw(cmd, payload=payload)
if answer['result'] == 'ok':
return answer['data'] if answer['data'] else True
_LOGGER.error("%s return error %s.", cmd, answer['message'])
return False
@asyncio.coroutine
def send_raw(self, cmd, payload=None, timeout=DEFAULT_TIMEOUT):
"""Send raw request to API."""
try:
with async_timeout.timeout(timeout, loop=self.loop):
request = yield from self.websession.get(
"http://{}{}".format(self._ip, cmd),
timeout=None, json=payload
)
if request.status != 200:
_LOGGER.error("%s return code %d.", cmd, request.status)
return
return (yield from request.json())
except asyncio.TimeoutError:
_LOGGER.error("Timeout on api request %s.", cmd)
except aiohttp.ClientError:
_LOGGER.error("Client error on api request %s.", cmd)
class HassIOBaseView(HomeAssistantView):
"""HassIO view to handle base part."""
requires_auth = True
def __init__(self, hassio, base):
"""Initialize a hassio base view."""
self.hassio = hassio
self._url_info = "/{}/info".format(base)
self.url = "/api/hassio/{}".format(base)
self.name = "api:hassio:{}".format(base)
@asyncio.coroutine
def get(self, request):
"""Get base data."""
data = yield from self.hassio.send_command(self._url_info)
if not data:
raise HTTPBadGateway()
return web.json_response(data)
class HassIOBaseEditView(HassIOBaseView):
"""HassIO view to handle base with options support."""
def __init__(self, hassio, base):
"""Initialize a hassio base edit view."""
super().__init__(hassio, base)
self._url_options = "/{}/options".format(base)
@asyncio.coroutine
def post(self, request):
"""Set options on host."""
data = yield from request.json()
response = yield from self.hassio.send_raw(
self._url_options, payload=data)
if not response:
raise HTTPBadGateway()
return web.json_response(response)
class HassIOAddonsView(HomeAssistantView):
"""HassIO view to handle addons part."""
requires_auth = True
url = "/api/hassio/addons/{addon}"
name = "api:hassio:addons"
def __init__(self, hassio):
"""Initialize a hassio addon view."""
self.hassio = hassio
@asyncio.coroutine
def get(self, request, addon):
"""Get addon data."""
data = yield from self.hassio.send_command(
"/addons/{}/info".format(addon))
if not data:
raise HTTPBadGateway()
return web.json_response(data)
@asyncio.coroutine
def post(self, request, addon):
"""Set options on host."""
data = yield from request.json()
response = yield from self.hassio.send_raw(
"/addons/{}/options".format(addon), payload=data)
if not response:
raise HTTPBadGateway()
return web.json_response(response)

View File

@ -22,7 +22,7 @@ from homeassistant.helpers.event import track_time_interval
from homeassistant.config import load_yaml_config_file
DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.22"]
REQUIREMENTS = ["pyhomematic==0.1.24"]
SCAN_INTERVAL_HUB = timedelta(seconds=300)
SCAN_INTERVAL_VARIABLES = timedelta(seconds=30)
@ -82,7 +82,12 @@ HM_ATTRIBUTE_SUPPORT = {
'RSSI_DEVICE': ['rssi', {}],
'VALVE_STATE': ['valve', {}],
'BATTERY_STATE': ['battery', {}],
'CONTROL_MODE': ['mode', {0: 'Auto', 1: 'Manual', 2: 'Away', 3: 'Boost'}],
'CONTROL_MODE': ['mode', {0: 'Auto',
1: 'Manual',
2: 'Away',
3: 'Boost',
4: 'Comfort',
5: 'Lowering'}],
'POWER': ['power', {}],
'CURRENT': ['current', {}],
'VOLTAGE': ['voltage', {}],

View File

@ -9,7 +9,6 @@ import json
import logging
import ssl
from ipaddress import ip_network
from pathlib import Path
import os
import voluptuous as vol
@ -31,11 +30,13 @@ from .const import (
KEY_USE_X_FORWARDED_FOR, KEY_TRUSTED_NETWORKS,
KEY_BANS_ENABLED, KEY_LOGIN_THRESHOLD,
KEY_DEVELOPMENT, KEY_AUTHENTICATED)
from .static import FILE_SENDER, CACHING_FILE_SENDER, staticresource_middleware
from .static import (
staticresource_middleware, CachingFileResponse, CachingStaticResource)
from .util import get_real_ip
REQUIREMENTS = ['aiohttp_cors==0.5.2']
DOMAIN = 'http'
REQUIREMENTS = ('aiohttp_cors==0.5.0',)
CONF_API_PASSWORD = 'api_password'
CONF_SERVER_HOST = 'server_host'
@ -187,7 +188,7 @@ class HomeAssistantWSGI(object):
if is_ban_enabled:
middlewares.insert(0, ban_middleware)
self.app = web.Application(middlewares=middlewares, loop=hass.loop)
self.app = web.Application(middlewares=middlewares)
self.app['hass'] = hass
self.app[KEY_USE_X_FORWARDED_FOR] = use_x_forwarded_for
self.app[KEY_TRUSTED_NETWORKS] = trusted_networks
@ -255,31 +256,39 @@ class HomeAssistantWSGI(object):
self.app.router.add_route('GET', url, redirect)
def register_static_path(self, url_root, path, cache_length=31):
"""Register a folder to serve as a static path.
Specify optional cache length of asset in days.
"""
def register_static_path(self, url_path, path, cache_headers=True):
"""Register a folder or file to serve as a static path."""
if os.path.isdir(path):
self.app.router.add_static(url_root, path)
if cache_headers:
resource = CachingStaticResource
else:
resource = web.StaticResource
self.app.router.register_resource(resource(url_path, path))
return
filepath = Path(path)
@asyncio.coroutine
def serve_file(request):
"""Serve file from disk."""
res = yield from CACHING_FILE_SENDER.send(request, filepath)
return res
if cache_headers:
@asyncio.coroutine
def serve_file(request):
"""Serve file from disk."""
return CachingFileResponse(path)
else:
@asyncio.coroutine
def serve_file(request):
"""Serve file from disk."""
return web.FileResponse(path)
# aiohttp supports regex matching for variables. Using that as temp
# to work around cache busting MD5.
# Turns something like /static/dev-panel.html into
# /static/{filename:dev-panel(-[a-z0-9]{32}|)\.html}
base, ext = url_root.rsplit('.', 1)
base, file = base.rsplit('/', 1)
regex = r"{}(-[a-z0-9]{{32}}|)\.{}".format(file, ext)
url_pattern = "{}/{{filename:{}}}".format(base, regex)
base, ext = os.path.splitext(url_path)
if ext:
base, file = base.rsplit('/', 1)
regex = r"{}(-[a-z0-9]{{32}}|){}".format(file, ext)
url_pattern = "{}/{{filename:{}}}".format(base, regex)
else:
url_pattern = url_path
self.app.router.add_route('GET', url_pattern, serve_file)
@ -318,7 +327,7 @@ class HomeAssistantWSGI(object):
# re-register all redirects, views, static paths.
self.app._frozen = True # pylint: disable=protected-access
self._handler = self.app.make_handler()
self._handler = self.app.make_handler(loop=self.hass.loop)
try:
self.server = yield from self.hass.loop.create_server(
@ -365,8 +374,7 @@ class HomeAssistantView(object):
def file(self, request, fil):
"""Return a file."""
assert isinstance(fil, str), 'only string paths allowed'
response = yield from FILE_SENDER.send(request, Path(fil))
return response
return web.FileResponse(fil)
def register(self, router):
"""Register the view with a router."""

View File

@ -3,14 +3,45 @@ import asyncio
import re
from aiohttp import hdrs
from aiohttp.file_sender import FileSender
from aiohttp.web import FileResponse
from aiohttp.web_exceptions import HTTPNotFound
from aiohttp.web_urldispatcher import StaticResource
from yarl import unquote
from .const import KEY_DEVELOPMENT
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
class CachingFileSender(FileSender):
class CachingStaticResource(StaticResource):
"""Static Resource handler that will add cache headers."""
@asyncio.coroutine
def _handle(self, request):
filename = unquote(request.match_info['filename'])
try:
# PyLint is wrong about resolve not being a member.
# pylint: disable=no-member
filepath = self._directory.joinpath(filename).resolve()
if not self._follow_symlinks:
filepath.relative_to(self._directory)
except (ValueError, FileNotFoundError) as error:
# relatively safe
raise HTTPNotFound() from error
except Exception as error:
# perm error or other kind!
request.app.logger.exception(error)
raise HTTPNotFound() from error
if filepath.is_dir():
return (yield from super()._handle(request))
elif filepath.is_file():
return CachingFileResponse(filepath, chunk_size=self._chunk_size)
else:
raise HTTPNotFound
class CachingFileResponse(FileResponse):
"""FileSender class that caches output if not in dev mode."""
def __init__(self, *args, **kwargs):
@ -20,46 +51,34 @@ class CachingFileSender(FileSender):
orig_sendfile = self._sendfile
@asyncio.coroutine
def sendfile(request, resp, fobj, count):
def sendfile(request, fobj, count):
"""Sendfile that includes a cache header."""
if not request.app[KEY_DEVELOPMENT]:
cache_time = 31 * 86400 # = 1 month
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
self.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
cache_time)
yield from orig_sendfile(request, resp, fobj, count)
yield from orig_sendfile(request, fobj, count)
# Overwriting like this because __init__ can change implementation.
self._sendfile = sendfile
FILE_SENDER = FileSender()
CACHING_FILE_SENDER = CachingFileSender()
@asyncio.coroutine
def staticresource_middleware(app, handler):
"""Enhance StaticResourceHandler middleware.
Adds gzip encoding and fingerprinting matching.
"""
inst = getattr(handler, '__self__', None)
if not isinstance(inst, StaticResource):
return handler
# pylint: disable=protected-access
inst._file_sender = CACHING_FILE_SENDER
"""Middleware to strip out fingerprint from fingerprinted assets."""
@asyncio.coroutine
def static_middleware_handler(request):
"""Strip out fingerprints from resource names."""
if not request.path.startswith('/static/'):
return handler(request)
fingerprinted = _FINGERPRINT.match(request.match_info['filename'])
if fingerprinted:
request.match_info['filename'] = \
'{}.{}'.format(*fingerprinted.groups())
resp = yield from handler(request)
return resp
return handler(request)
return static_middleware_handler

View File

@ -112,8 +112,6 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity):
params['image_bytes'] = str(b64encode(image), 'utf-8')
data = None
request = None
try:
with async_timeout.timeout(self.timeout, loop=self.hass.loop):
request = yield from websession.post(
@ -127,14 +125,10 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity):
request.status, data.get('error'))
return
except (asyncio.TimeoutError, aiohttp.errors.ClientError):
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for openalpr api.")
return
finally:
if request is not None:
yield from request.release()
# processing api data
vehicles = 0
result = {}

View File

@ -30,13 +30,11 @@ SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
DEFAULT_CONFIG = {CONF_INITIAL: DEFAULT_INITIAL}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
cv.slug: vol.Any({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): cv.boolean,
vol.Optional(CONF_INITIAL): cv.boolean,
vol.Optional(CONF_ICON): cv.icon,
}, None)
})
@ -72,13 +70,13 @@ def async_setup(hass, config):
for object_id, cfg in config[DOMAIN].items():
if not cfg:
cfg = DEFAULT_CONFIG
cfg = {}
name = cfg.get(CONF_NAME)
state = cfg.get(CONF_INITIAL)
initial = cfg.get(CONF_INITIAL)
icon = cfg.get(CONF_ICON)
entities.append(InputBoolean(object_id, name, state, icon))
entities.append(InputBoolean(object_id, name, initial, icon))
if not entities:
return False
@ -113,11 +111,11 @@ def async_setup(hass, config):
class InputBoolean(ToggleEntity):
"""Representation of a boolean input."""
def __init__(self, object_id, name, state, icon):
def __init__(self, object_id, name, initial, icon):
"""Initialize a boolean input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._state = state
self._state = initial
self._icon = icon
@property
@ -143,10 +141,12 @@ class InputBoolean(ToggleEntity):
@asyncio.coroutine
def async_added_to_hass(self):
"""Called when entity about to be added to hass."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if not state:
# If not None, we got an initial value.
if self._state is not None:
return
self._state = state.state == STATE_ON
state = yield from async_get_last_state(self.hass, self.entity_id)
self._state = state and state.state == STATE_ON
@asyncio.coroutine
def async_turn_on(self, **kwargs):

View File

@ -58,10 +58,10 @@ SERVICE_SET_OPTIONS_SCHEMA = vol.Schema({
def _cv_input_select(cfg):
"""Config validation helper for input select (Voluptuous)."""
options = cfg[CONF_OPTIONS]
state = cfg.get(CONF_INITIAL, options[0])
if state not in options:
initial = cfg.get(CONF_INITIAL)
if initial is not None and initial not in options:
raise vol.Invalid('initial state "{}" is not part of the options: {}'
.format(state, ','.join(options)))
.format(initial, ','.join(options)))
return cfg
@ -117,9 +117,9 @@ def async_setup(hass, config):
for object_id, cfg in config[DOMAIN].items():
name = cfg.get(CONF_NAME)
options = cfg.get(CONF_OPTIONS)
state = cfg.get(CONF_INITIAL, options[0])
initial = cfg.get(CONF_INITIAL)
icon = cfg.get(CONF_ICON)
entities.append(InputSelect(object_id, name, state, options, icon))
entities.append(InputSelect(object_id, name, initial, options, icon))
if not entities:
return False
@ -187,23 +187,25 @@ def async_setup(hass, config):
class InputSelect(Entity):
"""Representation of a select input."""
def __init__(self, object_id, name, state, options, icon):
def __init__(self, object_id, name, initial, options, icon):
"""Initialize a select input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._current_option = state
self._current_option = initial
self._options = options
self._icon = icon
@asyncio.coroutine
def async_added_to_hass(self):
"""Called when entity about to be added to hass."""
if self._current_option is not None:
return
state = yield from async_get_last_state(self.hass, self.entity_id)
if not state:
return
if state.state not in self._options:
return
self._current_option = state.state
if not state or state.state not in self._options:
self._current_option = self._options[0]
else:
self._current_option = state.state
@property
def should_poll(self):

View File

@ -45,11 +45,10 @@ def _cv_input_slider(cfg):
if minimum >= maximum:
raise vol.Invalid('Maximum ({}) is not greater than minimum ({})'
.format(minimum, maximum))
state = cfg.get(CONF_INITIAL, minimum)
if state < minimum or state > maximum:
state = cfg.get(CONF_INITIAL)
if state is not None and (state < minimum or state > maximum):
raise vol.Invalid('Initial value {} not in range {}-{}'
.format(state, minimum, maximum))
cfg[CONF_INITIAL] = state
return cfg
@ -88,12 +87,12 @@ def async_setup(hass, config):
name = cfg.get(CONF_NAME)
minimum = cfg.get(CONF_MIN)
maximum = cfg.get(CONF_MAX)
state = cfg.get(CONF_INITIAL, minimum)
initial = cfg.get(CONF_INITIAL)
step = cfg.get(CONF_STEP)
icon = cfg.get(CONF_ICON)
unit = cfg.get(ATTR_UNIT_OF_MEASUREMENT)
entities.append(InputSlider(object_id, name, state, minimum, maximum,
entities.append(InputSlider(object_id, name, initial, minimum, maximum,
step, icon, unit))
if not entities:
@ -120,12 +119,12 @@ def async_setup(hass, config):
class InputSlider(Entity):
"""Represent an slider."""
def __init__(self, object_id, name, state, minimum, maximum, step, icon,
def __init__(self, object_id, name, initial, minimum, maximum, step, icon,
unit):
"""Initialize a select input."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
self._current_value = state
self._current_value = initial
self._minimum = minimum
self._maximum = maximum
self._step = step
@ -169,14 +168,17 @@ class InputSlider(Entity):
@asyncio.coroutine
def async_added_to_hass(self):
"""Called when entity about to be added to hass."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if not state:
if self._current_value is not None:
return
num_value = float(state.state)
if num_value < self._minimum or num_value > self._maximum:
return
self._current_value = num_value
state = yield from async_get_last_state(self.hass, self.entity_id)
value = state and float(state.state)
# Check against False because value can be 0
if value is not False and self._minimum < value < self._maximum:
self._current_value = value
else:
self._current_value = self._minimum
@asyncio.coroutine
def async_select_value(self, value):

View File

@ -18,7 +18,7 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['flux_led==0.15']
REQUIREMENTS = ['flux_led==0.18']
_LOGGER = logging.getLogger(__name__)

View File

@ -47,7 +47,7 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
PHUE_CONFIG_FILE = 'phue.conf'
SUPPORT_HUE_ON_OFF = (SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_FLASH)
SUPPORT_HUE_ON_OFF = (SUPPORT_FLASH | SUPPORT_TRANSITION)
SUPPORT_HUE_DIMMABLE = (SUPPORT_HUE_ON_OFF | SUPPORT_BRIGHTNESS)
SUPPORT_HUE_COLOR_TEMP = (SUPPORT_HUE_DIMMABLE | SUPPORT_COLOR_TEMP)
SUPPORT_HUE_COLOR = (SUPPORT_HUE_DIMMABLE | SUPPORT_EFFECT |
@ -58,6 +58,7 @@ SUPPORT_HUE = {
'Extended color light': SUPPORT_HUE_EXTENDED,
'Color light': SUPPORT_HUE_COLOR,
'Dimmable light': SUPPORT_HUE_DIMMABLE,
'On/Off plug-in unit': SUPPORT_HUE_ON_OFF,
'Color temperature light': SUPPORT_HUE_COLOR_TEMP
}

View File

@ -175,7 +175,7 @@ class InsteonLocalDimmerDevice(Light):
if ATTR_BRIGHTNESS in kwargs:
brightness = int(kwargs[ATTR_BRIGHTNESS]) / 255 * 100
self.node.on(brightness)
self.node.change_level(brightness)
def turn_off(self, **kwargs):
"""Turn device off."""

View File

@ -36,7 +36,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
InsteonPLMDimmerDevice(hass, plm, address, name, dimmable)
)
hass.async_add_job(async_add_devices(device_list))
async_add_devices(device_list)
class InsteonPLMDimmerDevice(Light):

View File

@ -7,6 +7,7 @@ https://home-assistant.io/components/light.lifx/
import colorsys
import logging
import asyncio
import sys
from functools import partial
from datetime import timedelta
@ -51,6 +52,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the LIFX platform."""
import aiolifx
if sys.platform == 'win32':
_LOGGER.warning('The lifx platform is known to not work on Windows. '
'Consider using the lifx_legacy platform instead.')
server_addr = config.get(CONF_SERVER)
lifx_manager = LIFXManager(hass, async_add_devices)
@ -90,7 +95,7 @@ class LIFXManager(object):
entity = LIFXLight(device)
_LOGGER.debug("%s register READY", entity.ipaddr)
self.entities[device.mac_addr] = entity
self.hass.async_add_job(self.async_add_devices, [entity])
self.async_add_devices([entity])
@callback
def unregister(self, device):

View File

@ -0,0 +1,280 @@
"""
Support for the LIFX platform that implements lights.
This is a legacy platform, included because the current lifx platform does
not yet support Windows.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.lifx/
"""
import colorsys
import logging
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR,
SUPPORT_TRANSITION, Light, PLATFORM_SCHEMA)
from homeassistant.helpers.event import track_time_change
from homeassistant.util.color import (
color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['liffylights==0.9.4']
BYTE_MAX = 255
CONF_BROADCAST = 'broadcast'
CONF_SERVER = 'server'
SHORT_MAX = 65535
TEMP_MAX = 9000
TEMP_MAX_HASS = 500
TEMP_MIN = 2500
TEMP_MIN_HASS = 154
SUPPORT_LIFX = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR |
SUPPORT_TRANSITION)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SERVER, default=None): cv.string,
vol.Optional(CONF_BROADCAST, default=None): cv.string,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the LIFX platform."""
server_addr = config.get(CONF_SERVER)
broadcast_addr = config.get(CONF_BROADCAST)
lifx_library = LIFX(add_devices, server_addr, broadcast_addr)
# Register our poll service
track_time_change(hass, lifx_library.poll, second=[10, 40])
lifx_library.probe()
class LIFX(object):
"""Representation of a LIFX light."""
def __init__(self, add_devices_callback, server_addr=None,
broadcast_addr=None):
"""Initialize the light."""
import liffylights
self._devices = []
self._add_devices_callback = add_devices_callback
self._liffylights = liffylights.LiffyLights(
self.on_device, self.on_power, self.on_color, server_addr,
broadcast_addr)
def find_bulb(self, ipaddr):
"""Search for bulbs."""
bulb = None
for device in self._devices:
if device.ipaddr == ipaddr:
bulb = device
break
return bulb
def on_device(self, ipaddr, name, power, hue, sat, bri, kel):
"""Initialize the light."""
bulb = self.find_bulb(ipaddr)
if bulb is None:
_LOGGER.debug("new bulb %s %s %d %d %d %d %d",
ipaddr, name, power, hue, sat, bri, kel)
bulb = LIFXLight(
self._liffylights, ipaddr, name, power, hue, sat, bri, kel)
self._devices.append(bulb)
self._add_devices_callback([bulb])
else:
_LOGGER.debug("update bulb %s %s %d %d %d %d %d",
ipaddr, name, power, hue, sat, bri, kel)
bulb.set_power(power)
bulb.set_color(hue, sat, bri, kel)
bulb.schedule_update_ha_state()
def on_color(self, ipaddr, hue, sat, bri, kel):
"""Initialize the light."""
bulb = self.find_bulb(ipaddr)
if bulb is not None:
bulb.set_color(hue, sat, bri, kel)
bulb.schedule_update_ha_state()
def on_power(self, ipaddr, power):
"""Initialize the light."""
bulb = self.find_bulb(ipaddr)
if bulb is not None:
bulb.set_power(power)
bulb.schedule_update_ha_state()
# pylint: disable=unused-argument
def poll(self, now):
"""Polling for the light."""
self.probe()
def probe(self, address=None):
"""Probe the light."""
self._liffylights.probe(address)
def convert_rgb_to_hsv(rgb):
"""Convert Home Assistant RGB values to HSV values."""
red, green, blue = [_ / BYTE_MAX for _ in rgb]
hue, saturation, brightness = colorsys.rgb_to_hsv(red, green, blue)
return [int(hue * SHORT_MAX),
int(saturation * SHORT_MAX),
int(brightness * SHORT_MAX)]
class LIFXLight(Light):
"""Representation of a LIFX light."""
def __init__(self, liffy, ipaddr, name, power, hue, saturation, brightness,
kelvin):
"""Initialize the light."""
_LOGGER.debug("LIFXLight: %s %s", ipaddr, name)
self._liffylights = liffy
self._ip = ipaddr
self.set_name(name)
self.set_power(power)
self.set_color(hue, saturation, brightness, kelvin)
@property
def should_poll(self):
"""No polling needed for LIFX light."""
return False
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def ipaddr(self):
"""Return the IP address of the device."""
return self._ip
@property
def rgb_color(self):
"""Return the RGB value."""
_LOGGER.debug(
"rgb_color: [%d %d %d]", self._rgb[0], self._rgb[1], self._rgb[2])
return self._rgb
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
brightness = int(self._bri / (BYTE_MAX + 1))
_LOGGER.debug("brightness: %d", brightness)
return brightness
@property
def color_temp(self):
"""Return the color temperature."""
temperature = color_temperature_kelvin_to_mired(self._kel)
_LOGGER.debug("color_temp: %d", temperature)
return temperature
@property
def is_on(self):
"""Return true if device is on."""
_LOGGER.debug("is_on: %d", self._power)
return self._power != 0
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_LIFX
def turn_on(self, **kwargs):
"""Turn the device on."""
if ATTR_TRANSITION in kwargs:
fade = int(kwargs[ATTR_TRANSITION] * 1000)
else:
fade = 0
if ATTR_RGB_COLOR in kwargs:
hue, saturation, brightness = \
convert_rgb_to_hsv(kwargs[ATTR_RGB_COLOR])
else:
hue = self._hue
saturation = self._sat
brightness = self._bri
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs[ATTR_BRIGHTNESS] * (BYTE_MAX + 1)
else:
brightness = self._bri
if ATTR_COLOR_TEMP in kwargs:
kelvin = int(color_temperature_mired_to_kelvin(
kwargs[ATTR_COLOR_TEMP]))
else:
kelvin = self._kel
_LOGGER.debug("turn_on: %s (%d) %d %d %d %d %d",
self._ip, self._power,
hue, saturation, brightness, kelvin, fade)
if self._power == 0:
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, 0)
self._liffylights.set_power(self._ip, 65535, fade)
else:
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, fade)
def turn_off(self, **kwargs):
"""Turn the device off."""
if ATTR_TRANSITION in kwargs:
fade = int(kwargs[ATTR_TRANSITION] * 1000)
else:
fade = 0
_LOGGER.debug("turn_off: %s %d", self._ip, fade)
self._liffylights.set_power(self._ip, 0, fade)
def set_name(self, name):
"""Set name of the light."""
self._name = name
def set_power(self, power):
"""Set power state value."""
_LOGGER.debug("set_power: %d", power)
self._power = (power != 0)
def set_color(self, hue, sat, bri, kel):
"""Set color state values."""
self._hue = hue
self._sat = sat
self._bri = bri
self._kel = kel
red, green, blue = colorsys.hsv_to_rgb(hue / SHORT_MAX,
sat / SHORT_MAX,
bri / SHORT_MAX)
red = int(red * BYTE_MAX)
green = int(green * BYTE_MAX)
blue = int(blue * BYTE_MAX)
_LOGGER.debug("set_color: %d %d %d %d [%d %d %d]",
hue, sat, bri, kel, red, green, blue)
self._rgb = [red, green, blue]

View File

@ -25,14 +25,13 @@ CONF_BRIDGES = 'bridges'
CONF_GROUPS = 'groups'
CONF_NUMBER = 'number'
CONF_VERSION = 'version'
CONF_BRIDGE_LED = 'bridge_led'
DEFAULT_LED_TYPE = 'rgbw'
DEFAULT_PORT = 5987
DEFAULT_TRANSITION = 0
DEFAULT_VERSION = 6
LED_TYPE = ['rgbw', 'rgbww', 'white']
LED_TYPE = ['rgbw', 'rgbww', 'white', 'bridge-led']
RGB_BOUNDARY = 40
@ -54,7 +53,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_VERSION,
default=DEFAULT_VERSION): cv.positive_int,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_BRIDGE_LED, default=False): cv.boolean,
vol.Required(CONF_GROUPS): vol.All(cv.ensure_list, [
{
vol.Required(CONF_NAME): cv.string,
@ -116,8 +114,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
group_conf.get(CONF_NAME),
group_conf.get(CONF_TYPE, DEFAULT_LED_TYPE))
lights.append(LimitlessLEDGroup.factory(group))
if bridge_conf.get(CONF_BRIDGE_LED) and bridge.bridge_led is not None:
lights.append(LimitlessLEDGroup.factory(bridge.bridge_led))
add_devices(lights)

View File

@ -48,7 +48,7 @@ class LiteJetLight(Light):
def _on_load_changed(self):
"""Called on a LiteJet thread when a load's state changes."""
_LOGGER.debug("Updating due to notification for %s", self._name)
self._hass.async_add_job(self.async_update_ha_state(True))
self.schedule_update_ha_state(True)
@property
def supported_features(self):

View File

@ -19,7 +19,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Lutron Caseta lights."""
devs = []
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
light_devices = bridge.get_devices_by_type("WallDimmer")
light_devices = bridge.get_devices_by_types(["WallDimmer", "PlugInDimmer"])
for light_device in light_devices:
dev = LutronCasetaLight(light_device, bridge)
devs.append(dev)

View File

@ -148,16 +148,12 @@ class MqttJson(Light):
self._flash_times = flash_times
self._supported_features = (SUPPORT_TRANSITION | SUPPORT_FLASH)
self._supported_features |= (rgb is not None and SUPPORT_RGB_COLOR)
self._supported_features |= (brightness is not None and
SUPPORT_BRIGHTNESS)
self._supported_features |= (color_temp is not None and
SUPPORT_COLOR_TEMP)
self._supported_features |= (effect is not None and
SUPPORT_EFFECT)
self._supported_features |= (white_value is not None and
SUPPORT_WHITE_VALUE)
self._supported_features |= (xy is not None and SUPPORT_XY_COLOR)
self._supported_features |= (rgb and SUPPORT_RGB_COLOR)
self._supported_features |= (brightness and SUPPORT_BRIGHTNESS)
self._supported_features |= (color_temp and SUPPORT_COLOR_TEMP)
self._supported_features |= (effect and SUPPORT_EFFECT)
self._supported_features |= (white_value and SUPPORT_WHITE_VALUE)
self._supported_features |= (xy and SUPPORT_XY_COLOR)
@asyncio.coroutine
def async_added_to_hass(self):

View File

@ -9,15 +9,12 @@ import subprocess
import voluptuous as vol
# Import the device class from the component that you want to support
import homeassistant.helpers.config_validation as cv
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS,
ATTR_RGB_COLOR, SUPPORT_RGB_COLOR,
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, ATTR_RGB_COLOR, SUPPORT_RGB_COLOR,
Light, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
# Home Assistant depends on 3rd party packages for API specific code.
REQUIREMENTS = ['piglow==1.2.4']
_LOGGER = logging.getLogger(__name__)
@ -32,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Piglow Light platform."""
"""Set up the Piglow Light platform."""
import piglow
if subprocess.getoutput("i2cdetect -q -y 1 | grep -o 54") != '54':
@ -41,7 +38,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
name = config.get(CONF_NAME)
# Add devices
add_devices([PiglowLight(piglow, name)])

View File

@ -10,12 +10,14 @@ import logging
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
from homeassistant.components.rflink import (
CONF_ALIASSES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT,
CONF_IGNORE_DEVICES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER,
DATA_ENTITY_LOOKUP, DEVICE_DEFAULTS_SCHEMA, DOMAIN,
EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, cv, vol)
CONF_ALIASSES, CONF_AUTOMATIC_ADD, CONF_DEVICE_DEFAULTS, CONF_DEVICES,
CONF_FIRE_EVENT, CONF_GROUP, CONF_GROUP_ALIASSES, CONF_IGNORE_DEVICES,
CONF_NOGROUP_ALIASSES, CONF_SIGNAL_REPETITIONS, DATA_DEVICE_REGISTER,
DATA_ENTITY_GROUP_LOOKUP, DATA_ENTITY_LOOKUP, DEVICE_DEFAULTS_SCHEMA,
DOMAIN, EVENT_KEY_COMMAND, EVENT_KEY_ID, SwitchableRflinkDevice, cv, vol)
from homeassistant.const import (
CONF_NAME, CONF_PLATFORM, CONF_TYPE, STATE_UNKNOWN)
DEPENDENCIES = ['rflink']
_LOGGER = logging.getLogger(__name__)
@ -30,6 +32,7 @@ PLATFORM_SCHEMA = vol.Schema({
vol.Optional(CONF_IGNORE_DEVICES): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})):
DEVICE_DEFAULTS_SCHEMA,
vol.Optional(CONF_AUTOMATIC_ADD, default=True): cv.boolean,
vol.Optional(CONF_DEVICES, default={}): vol.Schema({
cv.string: {
vol.Optional(CONF_NAME): cv.string,
@ -38,8 +41,13 @@ PLATFORM_SCHEMA = vol.Schema({
TYPE_HYBRID, TYPE_TOGGLE),
vol.Optional(CONF_ALIASSES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_GROUP_ALIASSES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_NOGROUP_ALIASSES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int),
vol.Optional(CONF_GROUP, default=True): cv.boolean,
},
}),
})
@ -110,7 +118,24 @@ def devices_from_config(domain_config, hass=None):
devices.append(device)
# Register entity (and aliasses) to listen to incoming rflink events
for _id in [device_id] + config[CONF_ALIASSES]:
# device id and normal aliasses respond to normal and group command
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
if config[CONF_GROUP]:
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
for _id in config[CONF_ALIASSES]:
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
# group_aliasses only respond to group commands
for _id in config[CONF_GROUP_ALIASSES]:
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
# nogroup_aliasses only respond to normal commands
for _id in config[CONF_NOGROUP_ALIASSES]:
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
@ -138,10 +163,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
# Make sure the event is processed by the new entity
device.handle_event(event)
# Schedule task to process event after entity is created
hass.async_add_job(device.handle_event, event)
hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_COMMAND] = add_new_device
if config[CONF_AUTOMATIC_ADD]:
hass.data[DATA_DEVICE_REGISTER][EVENT_KEY_COMMAND] = add_new_device
class RflinkLight(SwitchableRflinkDevice, Light):

View File

@ -8,7 +8,6 @@ import logging
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ENTITY_ID_FORMAT, Light, SUPPORT_BRIGHTNESS)
from homeassistant.const import (STATE_OFF, STATE_ON)
from homeassistant.components.vera import (
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
@ -53,13 +52,13 @@ class VeraLight(VeraDevice, Light):
else:
self.vera_device.switch_on()
self._state = STATE_ON
self._state = True
self.schedule_update_ha_state(True)
def turn_off(self, **kwargs):
"""Turn the light off."""
self.vera_device.switch_off()
self._state = STATE_OFF
self._state = False
self.schedule_update_ha_state()
@property

View File

@ -44,13 +44,14 @@ DEVICE_SCHEMA = vol.Schema({
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, })
SUPPORT_YEELIGHT_RGB = (SUPPORT_RGB_COLOR |
SUPPORT_COLOR_TEMP)
SUPPORT_YEELIGHT = (SUPPORT_BRIGHTNESS |
SUPPORT_TRANSITION |
SUPPORT_FLASH)
SUPPORT_YEELIGHT_RGB = (SUPPORT_YEELIGHT |
SUPPORT_RGB_COLOR |
SUPPORT_COLOR_TEMP)
def _cmd(func):
"""A wrapper to catch exceptions from the bulb."""
@ -179,9 +180,6 @@ class YeelightLight(Light):
self._bulb_device = yeelight.Bulb(self._ipaddr)
self._bulb_device.get_properties() # force init for type
btype = self._bulb_device.bulb_type
if btype == yeelight.BulbType.Color:
self._supported_features |= SUPPORT_YEELIGHT_RGB
self._available = True
except yeelight.BulbException as ex:
self._available = False
@ -203,6 +201,9 @@ class YeelightLight(Light):
try:
self._bulb.get_properties()
if self._bulb_device.bulb_type == yeelight.BulbType.Color:
self._supported_features = SUPPORT_YEELIGHT_RGB
self._is_on = self._properties.get("power") == "on"
bright = self._properties.get("bright", None)
@ -254,7 +255,8 @@ class YeelightLight(Light):
def set_flash(self, flash) -> None:
"""Activate flash."""
if flash:
from yeelight import RGBTransition, SleepTransition, Flow
from yeelight import (RGBTransition, SleepTransition, Flow,
BulbException)
if self._bulb.last_properties["color_mode"] != 1:
_LOGGER.error("Flash supported currently only in RGB mode.")
return
@ -279,10 +281,14 @@ class YeelightLight(Light):
duration=duration))
flow = Flow(count=count, transitions=transitions)
self._bulb.start_flow(flow)
try:
self._bulb.start_flow(flow)
except BulbException as ex:
_LOGGER.error("Unable to set flash: %s", ex)
def turn_on(self, **kwargs) -> None:
"""Turn the bulb on."""
import yeelight
brightness = kwargs.get(ATTR_BRIGHTNESS)
colortemp = kwargs.get(ATTR_COLOR_TEMP)
rgb = kwargs.get(ATTR_RGB_COLOR)
@ -292,22 +298,43 @@ class YeelightLight(Light):
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
self._bulb.turn_on(duration=duration)
try:
self._bulb.turn_on(duration=duration)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn the bulb on: %s", ex)
return
if self.config[CONF_MODE_MUSIC] and not self._bulb.music_mode:
self.set_music_mode(self.config[CONF_MODE_MUSIC])
try:
self.set_music_mode(self.config[CONF_MODE_MUSIC])
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn on music mode,"
"consider disabling it: %s", ex)
# values checked for none in methods
self.set_rgb(rgb, duration)
self.set_colortemp(colortemp, duration)
self.set_brightness(brightness, duration)
self.set_flash(flash)
try:
# values checked for none in methods
self.set_rgb(rgb, duration)
self.set_colortemp(colortemp, duration)
self.set_brightness(brightness, duration)
self.set_flash(flash)
except yeelight.BulbException as ex:
_LOGGER.error("Unable to set bulb properties: %s", ex)
return
# save the current state if we had a manual change.
if self.config[CONF_SAVE_ON_CHANGE]:
if brightness or colortemp or rgb:
if self.config[CONF_SAVE_ON_CHANGE] and (brightness
or colortemp
or rgb):
try:
self.set_default()
except yeelight.BulbException as ex:
_LOGGER.error("Unable to set the defaults: %s", ex)
return
def turn_off(self, **kwargs) -> None:
"""Turn off."""
self._bulb.turn_off()
import yeelight
try:
self._bulb.turn_off()
except yeelight.BulbException as ex:
_LOGGER.error("Unable to turn the bulb off: %s", ex)

View File

@ -5,21 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.yeelightsunflower/
"""
import logging
import voluptuous as vol
from homeassistant.components.light import (Light,
ATTR_RGB_COLOR, SUPPORT_RGB_COLOR,
ATTR_BRIGHTNESS,
SUPPORT_BRIGHTNESS,
PLATFORM_SCHEMA)
from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
from homeassistant.components.light import (
Light, ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, ATTR_BRIGHTNESS,
SUPPORT_BRIGHTNESS, PLATFORM_SCHEMA)
from homeassistant.const import CONF_HOST
REQUIREMENTS = ['yeelightsunflower==0.0.8']
_LOGGER = logging.getLogger(__name__)
SUPPORT_YEELIGHT_SUNFLOWER = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@ -35,7 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hub = yeelightsunflower.Hub(host)
if not hub.available:
_LOGGER.error('Could not connect to Yeelight Sunflower hub')
_LOGGER.error("Could not connect to Yeelight Sunflower hub")
return False
add_devices(SunflowerBulb(light) for light in hub.get_lights())
@ -55,7 +53,7 @@ class SunflowerBulb(Light):
@property
def name(self):
"""Return the display name of this light."""
return "sunflower_{}".format(self._light.zid)
return 'sunflower_{}'.format(self._light.zid)
@property
def available(self):

View File

@ -10,8 +10,8 @@ import logging
# pylint: disable=import-error
from threading import Timer
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \
SUPPORT_RGB_COLOR, DOMAIN, Light
ATTR_RGB_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, DOMAIN, Light
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import STATE_OFF, STATE_ON
@ -43,11 +43,6 @@ TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN
TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN
TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN
SUPPORT_ZWAVE_DIMMER = SUPPORT_BRIGHTNESS
SUPPORT_ZWAVE_COLOR = SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR
SUPPORT_ZWAVE_COLORTEMP = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR
| SUPPORT_COLOR_TEMP)
def get_device(node, values, node_config, **kwargs):
"""Create zwave entity device."""
@ -72,6 +67,13 @@ def brightness_state(value):
return 0, STATE_OFF
def ct_to_rgb(temp):
"""Convert color temperature (mireds) to RGB."""
colorlist = list(
color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp)))
return [int(val) for val in colorlist]
class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
"""Representation of a Z-Wave dimmer."""
@ -80,6 +82,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self._brightness = None
self._state = None
self._supported_features = None
self._delay = delay
self._refresh_value = refresh
self._zw098 = None
@ -100,6 +103,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
self._timer = None
_LOGGER.debug('self._refreshing=%s self.delay=%s',
self._refresh_value, self._delay)
self.value_added()
self.update_properties()
def update_properties(self):
@ -107,6 +111,12 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
# Brightness
self._brightness, self._state = brightness_state(self.values.primary)
def value_added(self):
"""Called when a new value is added to this entity."""
self._supported_features = SUPPORT_BRIGHTNESS
if self.values.dimming_duration is not None:
self._supported_features |= SUPPORT_TRANSITION
def value_changed(self):
"""Called when a value for this entity's node has changed."""
if self._refresh_value:
@ -139,10 +149,43 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_ZWAVE_DIMMER
return self._supported_features
def _set_duration(self, **kwargs):
"""Set the transition time for the brightness value.
Zwave Dimming Duration values:
0x00 = instant
0x01-0x7F = 1 second to 127 seconds
0x80-0xFE = 1 minute to 127 minutes
0xFF = factory default
"""
if self.values.dimming_duration is None:
if ATTR_TRANSITION in kwargs:
_LOGGER.debug("Dimming not supported by %s.", self.entity_id)
return
if ATTR_TRANSITION not in kwargs:
self.values.dimming_duration.data = 0xFF
return
transition = kwargs[ATTR_TRANSITION]
if transition <= 127:
self.values.dimming_duration.data = int(transition)
elif transition > 7620:
self.values.dimming_duration.data = 0xFE
_LOGGER.warning("Transition clipped to 127 minutes for %s.",
self.entity_id)
else:
minutes = int(transition / 60)
_LOGGER.debug("Transition rounded to %d minutes for %s.",
minutes, self.entity_id)
self.values.dimming_duration.data = minutes + 0x7F
def turn_on(self, **kwargs):
"""Turn the device on."""
self._set_duration(**kwargs)
# Zwave multilevel switches use a range of [0, 99] to control
# brightness. Level 255 means to set it to previous value.
if ATTR_BRIGHTNESS in kwargs:
@ -156,17 +199,12 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
def turn_off(self, **kwargs):
"""Turn the device off."""
self._set_duration(**kwargs)
if self.node.set_dimmer(self.values.primary.value_id, 0):
self._state = STATE_OFF
def ct_to_rgb(temp):
"""Convert color temperature (mireds) to RGB."""
colorlist = list(
color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp)))
return [int(val) for val in colorlist]
class ZwaveColorLight(ZwaveDimmer):
"""Representation of a Z-Wave color changing light."""
@ -178,6 +216,14 @@ class ZwaveColorLight(ZwaveDimmer):
super().__init__(values, refresh, delay)
def value_added(self):
"""Called when a new value is added to this entity."""
super().value_added()
self._supported_features |= SUPPORT_RGB_COLOR
if self._zw098:
self._supported_features |= SUPPORT_COLOR_TEMP
def update_properties(self):
"""Update internal properties based on zwave values."""
super().update_properties()
@ -288,11 +334,3 @@ class ZwaveColorLight(ZwaveDimmer):
self.values.color.data = rgbw
super().turn_on(**kwargs)
@property
def supported_features(self):
"""Flag supported features."""
if self._zw098:
return SUPPORT_ZWAVE_COLORTEMP
else:
return SUPPORT_ZWAVE_COLOR

View File

@ -0,0 +1,92 @@
"""
Lockitron lock platform.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/lockitron/
"""
import logging
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.lock import LockDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_ID
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'lockitron'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Required(CONF_ID): cv.string
})
BASE_URL = 'https://api.lockitron.com'
API_STATE_URL = BASE_URL + '/v2/locks/{}?access_token={}'
API_ACTION_URL = BASE_URL + '/v2/locks/{}?access_token={}&state={}'
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Lockitron platform."""
access_token = config.get(CONF_ACCESS_TOKEN)
device_id = config.get(CONF_ID)
response = requests.get(API_STATE_URL.format(device_id, access_token))
if response.status_code == 200:
add_devices([Lockitron(response.json()['state'], access_token,
device_id)])
else:
_LOGGER.error('Error retrieving lock status during init: %s',
response.text)
class Lockitron(LockDevice):
"""Representation of a Lockitron lock."""
LOCK_STATE = 'lock'
UNLOCK_STATE = 'unlock'
def __init__(self, state, access_token, device_id):
"""Initialize the lock."""
self._state = state
self.access_token = access_token
self.device_id = device_id
@property
def name(self):
"""Return the name of the device."""
return DOMAIN
@property
def is_locked(self):
"""Return True if the lock is currently locked, else False."""
return self._state == Lockitron.LOCK_STATE
def lock(self, **kwargs):
"""Lock the device."""
self._state = self.do_change_request(Lockitron.LOCK_STATE)
def unlock(self, **kwargs):
"""Unlock the device."""
self._state = self.do_change_request(Lockitron.UNLOCK_STATE)
def update(self):
"""Update the internal state of the device."""
response = requests \
.get(API_STATE_URL.format(self.device_id, self.access_token))
if response.status_code == 200:
self._state = response.json()['state']
else:
_LOGGER.error('Error retrieving lock status: %s', response.text)
def do_change_request(self, requested_state):
"""Execute the change request and pull out the new state."""
response = requests.put(
API_ACTION_URL.format(self.device_id, self.access_token,
requested_state))
if response.status_code == 200:
return response.json()['state']
else:
_LOGGER.error('Error setting lock state: %s\n%s',
requested_state, response.text)
return self._state

View File

@ -53,8 +53,8 @@ LOCK_ALARM_TYPE = {
'9': 'Deadbolt Jammed',
'18': 'Locked with Keypad by user ',
'19': 'Unlocked with Keypad by user ',
'21': 'Manually Locked by ',
'22': 'Manually Unlocked by Key or Inside thumb turn',
'21': 'Manually Locked ',
'22': 'Manually Unlocked ',
'24': 'Locked by RF',
'25': 'Unlocked by RF',
'27': 'Auto re-lock',
@ -69,8 +69,8 @@ LOCK_ALARM_TYPE = {
}
MANUAL_LOCK_ALARM_LEVEL = {
'1': 'Key Cylinder or Inside thumb turn',
'2': 'Touch function (lock and leave)'
'1': 'by Key Cylinder or Inside thumb turn',
'2': 'by Touch function (lock and leave)'
}
TAMPER_ALARM_LEVEL = {

View File

@ -17,8 +17,8 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['https://github.com/gurumitts/'
'pylutron-caseta/archive/v0.2.4.zip#'
'pylutron-caseta==v0.2.4']
'pylutron-caseta/archive/v0.2.5.zip#'
'pylutron-caseta==v0.2.5']
_LOGGER = logging.getLogger(__name__)

View File

@ -864,21 +864,17 @@ def _async_fetch_image(hass, url):
content, content_type = (None, None)
websession = async_get_clientsession(hass)
response = None
try:
with async_timeout.timeout(10, loop=hass.loop):
response = yield from websession.get(url)
if response.status == 200:
content = yield from response.read()
content_type = response.headers.get(CONTENT_TYPE_HEADER)
if response.status == 200:
content = yield from response.read()
content_type = response.headers.get(CONTENT_TYPE_HEADER)
except asyncio.TimeoutError:
pass
finally:
if response is not None:
yield from response.release()
if not content:
return (None, None)

View File

@ -21,7 +21,7 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['pyemby==1.1']
REQUIREMENTS = ['pyemby==1.2']
_LOGGER = logging.getLogger(__name__)

View File

@ -42,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Frontier Silicon platform."""
"""Set up the Frontier Silicon platform."""
import requests
if discovery_info is not None:
@ -59,10 +59,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(
[FSAPIDevice(DEVICE_URL.format(host, port), password)],
update_before_add=True)
_LOGGER.debug('FSAPI device %s:%s -> %s', host, port, password)
_LOGGER.debug("FSAPI device %s:%s -> %s", host, port, password)
return True
except requests.exceptions.RequestException:
_LOGGER.error('Could not add the FSAPI device at %s:%s -> %s',
_LOGGER.error("Could not add the FSAPI device at %s:%s -> %s",
host, port, password)
return False

View File

@ -27,7 +27,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
REQUIREMENTS = ['jsonrpc-async==0.4', 'jsonrpc-websocket==0.3']
REQUIREMENTS = ['jsonrpc-async==0.6', 'jsonrpc-websocket==0.5']
_LOGGER = logging.getLogger(__name__)

View File

@ -14,8 +14,9 @@ from homeassistant.components.media_player import (
from homeassistant.const import (STATE_OFF, STATE_ON, CONF_HOST, CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/danieljkemp/onkyo-eiscp/archive/'
'python3.zip#onkyo-eiscp==0.9.2']
REQUIREMENTS = ['https://github.com/miracle2k/onkyo-eiscp/archive/'
'066023aec04770518d494c32fb72eea0ec5c1b7c.zip#'
'onkyo-eiscp==1.0']
_LOGGER = logging.getLogger(__name__)
@ -127,7 +128,7 @@ class OnkyoDevice(MediaPlayerDevice):
self._current_source = '_'.join(
[i for i in current_source_tuples[1]])
self._muted = bool(mute_raw[1] == 'on')
self._volume = int(volume_raw[1], 16) / 80.0
self._volume = volume_raw[1] / 80.0
@property
def name(self):

View File

@ -4,11 +4,14 @@ Support to interface with Sonos players (via SoCo).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.sonos/
"""
import asyncio
import datetime
import functools as ft
import logging
from os import path
import socket
import urllib
import voluptuous as vol
from homeassistant.components.media_player import (
@ -107,7 +110,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
if player.is_visible:
device = SonosDevice(hass, player)
device = SonosDevice(player)
add_devices([device], True)
hass.data[DATA_SONOS].append(device)
if len(hass.data[DATA_SONOS]) > 1:
@ -132,7 +135,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.warning('No Sonos speakers found.')
return
hass.data[DATA_SONOS] = [SonosDevice(hass, p) for p in players]
hass.data[DATA_SONOS] = [SonosDevice(p) for p in players]
add_devices(hass.data[DATA_SONOS], True)
_LOGGER.info('Added %s Sonos speakers', len(players))
@ -216,19 +219,42 @@ class _ProcessSonosEventQueue():
def _get_entity_from_soco(hass, soco):
"""Return SonosDevice from SoCo."""
for device in hass.data[DATA_SONOS]:
if soco == device.soco_device:
if soco == device.soco:
return device
raise ValueError("No entity for SoCo device!")
def soco_error(funct):
"""Decorator to catch soco exceptions."""
@ft.wraps(funct)
def wrapper(*args, **kwargs):
"""Wrapper for all soco exception."""
from soco.exceptions import SoCoException
try:
return funct(*args, **kwargs)
except SoCoException as err:
_LOGGER.error("Error on %s with %s.", funct.__name__, err)
return wrapper
def soco_coordinator(funct):
"""Decorator to call funct on coordinator."""
@ft.wraps(funct)
def wrapper(device, *args, **kwargs):
"""Wrapper for call to coordinator."""
if device.is_coordinator:
return funct(device, *args, **kwargs)
return funct(device.coordinator, *args, **kwargs)
return wrapper
class SonosDevice(MediaPlayerDevice):
"""Representation of a Sonos device."""
def __init__(self, hass, player):
def __init__(self, player):
"""Initialize the Sonos device."""
from soco.snapshot import Snapshot
self.hass = hass
self.volume_increment = 5
self._unique_id = player.uid
self._player = player
@ -260,9 +286,14 @@ class SonosDevice(MediaPlayerDevice):
self._is_playing_tv = None
self._favorite_sources = None
self._source_name = None
self.soco_snapshot = Snapshot(self._player)
self._soco_snapshot = None
self._snapshot_group = None
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe sonos events."""
self.hass.loop.run_in_executor(None, self._subscribe_to_player_events)
@property
def should_poll(self):
"""Polling needed."""
@ -297,7 +328,7 @@ class SonosDevice(MediaPlayerDevice):
return self._coordinator is None
@property
def soco_device(self):
def soco(self):
"""Return soco device."""
return self._player
@ -327,7 +358,6 @@ class SonosDevice(MediaPlayerDevice):
auto_renew=True,
event_queue=self._queue)
# pylint: disable=too-many-branches, too-many-statements
def update(self):
"""Retrieve latest state."""
if self._speaker_info is None:
@ -606,16 +636,6 @@ class SonosDevice(MediaPlayerDevice):
self._is_playing_tv = is_playing_tv
self._is_playing_line_in = is_playing_line_in
self._source_name = source_name
# update state of the whole group
for device in [x for x in self.hass.data[DATA_SONOS]
if x.coordinator == self]:
if device.entity_id is not self.entity_id:
self.schedule_update_ha_state()
if self._queue is None and self.entity_id is not None:
self._subscribe_to_player_events()
self._last_avtransport_event = None
def _format_media_image_url(self, url, fallback_uri):
@ -781,27 +801,31 @@ class SonosDevice(MediaPlayerDevice):
return supported
@soco_error
def volume_up(self):
"""Volume up media player."""
self._player.volume += self.volume_increment
@soco_error
def volume_down(self):
"""Volume down media player."""
self._player.volume -= self.volume_increment
@soco_error
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self._player.volume = str(int(volume * 100))
@soco_error
def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player."""
self._player.mute = mute
@soco_error
@soco_coordinator
def select_source(self, source):
"""Select input source."""
if self._coordinator:
self._coordinator.select_source(source)
elif source == SUPPORT_SOURCE_LINEIN:
if source == SUPPORT_SOURCE_LINEIN:
self._source_name = SUPPORT_SOURCE_LINEIN
self._player.switch_to_line_in()
elif source == SUPPORT_SOURCE_TV:
@ -842,83 +866,78 @@ class SonosDevice(MediaPlayerDevice):
else:
return self._source_name
@soco_error
def turn_off(self):
"""Turn off media player."""
self.media_pause()
@soco_error
@soco_coordinator
def media_play(self):
"""Send play command."""
if self._coordinator:
self._coordinator.media_play()
else:
self._player.play()
self._player.play()
@soco_error
@soco_coordinator
def media_stop(self):
"""Send stop command."""
if self._coordinator:
self._coordinator.media_stop()
else:
self._player.stop()
self._player.stop()
@soco_error
@soco_coordinator
def media_pause(self):
"""Send pause command."""
if self._coordinator:
self._coordinator.media_pause()
else:
self._player.pause()
self._player.pause()
@soco_error
@soco_coordinator
def media_next_track(self):
"""Send next track command."""
if self._coordinator:
self._coordinator.media_next_track()
else:
self._player.next()
self._player.next()
@soco_error
@soco_coordinator
def media_previous_track(self):
"""Send next track command."""
if self._coordinator:
self._coordinator.media_previous_track()
else:
self._player.previous()
self._player.previous()
@soco_error
@soco_coordinator
def media_seek(self, position):
"""Send seek command."""
if self._coordinator:
self._coordinator.media_seek(position)
else:
self._player.seek(str(datetime.timedelta(seconds=int(position))))
self._player.seek(str(datetime.timedelta(seconds=int(position))))
@soco_error
@soco_coordinator
def clear_playlist(self):
"""Clear players playlist."""
if self._coordinator:
self._coordinator.clear_playlist()
else:
self._player.clear_queue()
self._player.clear_queue()
@soco_error
def turn_on(self):
"""Turn the media player on."""
self.media_play()
@soco_error
@soco_coordinator
def play_media(self, media_type, media_id, **kwargs):
"""
Send the play_media command to the media player.
If ATTR_MEDIA_ENQUEUE is True, add `media_id` to the queue.
"""
if self._coordinator:
self._coordinator.play_media(media_type, media_id, **kwargs)
if kwargs.get(ATTR_MEDIA_ENQUEUE):
from soco.exceptions import SoCoUPnPException
try:
self._player.add_uri_to_queue(media_id)
except SoCoUPnPException:
_LOGGER.error('Error parsing media uri "%s", '
"please check it's a valid media resource "
'supported by Sonos', media_id)
else:
if kwargs.get(ATTR_MEDIA_ENQUEUE):
from soco.exceptions import SoCoUPnPException
try:
self._player.add_uri_to_queue(media_id)
except SoCoUPnPException:
_LOGGER.error('Error parsing media uri "%s", '
"please check it's a valid media resource "
'supported by Sonos', media_id)
else:
self._player.play_uri(media_id)
self._player.play_uri(media_id)
@soco_error
def join(self, master):
"""Join the player to a group."""
coord = [device for device in self.hass.data[DATA_SONOS]
@ -926,29 +945,26 @@ class SonosDevice(MediaPlayerDevice):
if coord and master != self.entity_id:
coord = coord[0]
if coord.soco_device.group.coordinator != coord.soco_device:
coord.soco_device.unjoin()
self._player.join(coord.soco_device)
if coord.soco.group.coordinator != coord.soco:
coord.soco.unjoin()
self._player.join(coord.soco)
self._coordinator = coord
else:
_LOGGER.error("Master not found %s", master)
@soco_error
def unjoin(self):
"""Unjoin the player from a group."""
self._player.unjoin()
self._coordinator = None
@soco_error
def snapshot(self, with_group=True):
"""Snapshot the player."""
from soco.exceptions import SoCoException
try:
self.soco_snapshot.is_playing_queue = False
self.soco_snapshot.is_coordinator = False
self.soco_snapshot.snapshot()
except SoCoException:
_LOGGER.debug("Error on snapshot %s", self.entity_id)
self._snapshot_group = None
return
from soco.snapshot import Snapshot
self._soco_snapshot = Snapshot(self._player)
self._soco_snapshot.snapshot()
if with_group:
self._snapshot_group = self._player.group
@ -957,14 +973,15 @@ class SonosDevice(MediaPlayerDevice):
else:
self._snapshot_group = None
@soco_error
def restore(self, with_group=True):
"""Restore snapshot for the player."""
from soco.exceptions import SoCoException
try:
# need catch exception if a coordinator is going to slave.
# this state will recover with group part.
self.soco_snapshot.restore(False)
except (TypeError, SoCoException):
self._soco_snapshot.restore(False)
except (TypeError, AttributeError, SoCoException):
_LOGGER.debug("Error on restore %s", self.entity_id)
# restore groups
@ -1006,19 +1023,17 @@ class SonosDevice(MediaPlayerDevice):
if s_dev != old.coordinator:
s_dev.join(old.coordinator)
@soco_error
@soco_coordinator
def set_sleep_timer(self, sleep_time):
"""Set the timer on the player."""
if self._coordinator:
self._coordinator.set_sleep_timer(sleep_time)
else:
self._player.set_sleep_timer(sleep_time)
self._player.set_sleep_timer(sleep_time)
@soco_error
@soco_coordinator
def clear_sleep_timer(self):
"""Clear the timer on the player."""
if self._coordinator:
self._coordinator.set_sleep_timer(None)
else:
self._player.set_sleep_timer(None)
self._player.set_sleep_timer(None)
@property
def device_state_attributes(self):

View File

@ -117,7 +117,6 @@ class LogitechMediaServer(object):
@asyncio.coroutine
def async_query(self, *command, player=""):
"""Abstract out the JSON-RPC connection."""
response = None
auth = None if self._username is None else aiohttp.BasicAuth(
self._username, self._password)
url = "http://{}:{}/jsonrpc.js".format(
@ -138,22 +137,17 @@ class LogitechMediaServer(object):
data=data,
auth=auth)
if response.status == 200:
data = yield from response.json()
else:
if response.status != 200:
_LOGGER.error(
"Query failed, response code: %s Full message: %s",
response.status, response)
return False
except (asyncio.TimeoutError,
aiohttp.errors.ClientError,
aiohttp.errors.ClientDisconnectedError) as error:
data = yield from response.json()
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed communicating with LMS: %s", type(error))
return False
finally:
if response is not None:
yield from response.release()
try:
return data['result']

View File

@ -425,12 +425,12 @@ class UniversalMediaPlayer(MediaPlayerDevice):
return self._async_call_service(
SERVICE_VOLUME_MUTE, data, allow_override=True)
def async_set_volume_level(self, volume_level):
def async_set_volume_level(self, volume):
"""Set volume level, range 0..1.
This method must be run in the event loop and returns a coroutine.
"""
data = {ATTR_MEDIA_VOLUME_LEVEL: volume_level}
data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
return self._async_call_service(
SERVICE_VOLUME_SET, data, allow_override=True)

Some files were not shown because too many files have changed in this diff Show More