Compare commits

..

713 Commits

Author SHA1 Message Date
Paulus Schoutsen
3701ac292c Merge pull request #4017 from home-assistant/release-0-31-1
0.31.1
2016-10-23 21:57:38 -07:00
Lewis Juggins
1db18478d2 Exclude dirs/files prefixed with . (#3986) 2016-10-23 21:08:56 -07:00
Robbie Trencheny
3230869f74 Fix a spelling problem on user-facing error 2016-10-23 20:34:31 -07:00
Paulus Schoutsen
3e92318cb2 Version bump to 0.31.1 2016-10-23 19:20:30 -07:00
Johann Kellerman
f0a38dded6 Catch UnicodeDecodeError Error (#4007)
* Catch UnicodeDecodeError Error

Error for #3933

* Forgot (exc)

* catch...

* Tests by @lwis

* Docstring

* Create open
2016-10-23 19:20:13 -07:00
Robbie Trencheny
c32f47aea6 iOS component hotfixes (#4015)
* iOS component hot fixes around component/platform loading, logging, and more

* Load device_tracker and zeroconf in deps instead of bootstraping

* Change conditional check on status code
2016-10-23 19:18:13 -07:00
Robbie Trencheny
ef2ed7bfc9 Merge pull request #3937 from home-assistant/dev
0.31
2016-10-22 15:22:07 -07:00
Robbie Trencheny
6040a40af2 Update version 2016-10-22 15:09:05 -07:00
jbags81
fb352c20d9 Update wink.py (#3957)
* Update wink.py

added lambda and smoke detector call in component loading routine to fix broken functionality.

* Update wink.py

fixed extra space.

* Update wink.py

applied cleaner refactor per comments

* Update wink.py

fixed spacing

* Update wink.py

fixed lint error #1
2016-10-22 16:59:20 -04:00
John Arild Berentsen
678f30def1 Prevent Verisure cam to delete a file when it is None (#3988) 2016-10-22 21:01:12 +02:00
Paulus Schoutsen
0fce5ccc7f Update frontend 2016-10-22 11:24:06 -07:00
John Arild Berentsen
02afc98668 Prevent zwave from firing event at shutdown (#3987) 2016-10-22 14:08:24 +02:00
Eric Hagan
57777ef79a Adds support for Pioneer AVR interface port number (#3878)
* Adds support for Pioneer AVR interface port number

https://community.home-assistant.io/t/support-for-pioneer-avr/503
telnetlib supports a port number so adding port as
an optional config element with a default of 23 resolves this.

* Adds timeout to Pioneer AVR

timeout in telnetlib defaults to socket._GLOBAL_DEFAULT_TIMEOUT
which is not a value, but rather a bare Object used for comparison.

telnetlib says the following about the timeout optional argument:
"The optional timeout parameter specifies a timeout in seconds
 for blocking operations like the connection attempt (if not
 specified, the global default timeout setting will be used)."

From the documentation for sockets:
"Sockets are by default always created in blocking mode"

Catching connect and timeout errors, logging to debug
and continuing.

* Catches timeout exceptions, logs and continues.
2016-10-22 11:05:00 +02:00
Paulus Schoutsen
ca6fa1313e Fix updater, add new fields (#3982)
* Fix updater

* Add Docker and virtualenv checks

* Add log line informing user of update/analytics

* Remove str
2016-10-21 23:30:40 -07:00
Robbie Trencheny
ea91d24eb2 HA iOS support (#3752)
* Initial commit of the iOS component and platform

* Allow extra

* Add battery to identify, a new function to get devices, and load the upcoming sensor

* Add iOS sensor platform, currently for battery state & level

* Add discoverability for the iOS app

* Convert single quote to double quotes

* Load all required components and platforms when loading the iOS component for the best experience

* Unify quote style to double

* Change to hass_ios

* Update push URL, add support for logging based on status code, log rate limit updates

* Block iOS from coverage checks for now...
2016-10-21 23:20:15 -07:00
Brent Hughes
6e5a3c0a94 Fixed statsd stopping if state is not numeric or only attributes changed (#3981)
* Fixed statsd stopping if attribute changed by not the state

* Fixed tests which exposed a new bug.

* Fixed another issue. whoops
2016-10-21 23:18:13 -07:00
dasos
754d536974 Work better with password-protected Squeezebox / LMS servers (#3953)
* Work better with password-protected Squeezebox / LMS servers, including getting file art. Refactored to only have a single method calling the telent lib. (Should make it easier to convert to the more appropriate JSON interface)

* Update squeezebox.py

* Update squeezebox.py

* Update squeezebox.py

* Update squeezebox.py

* Update squeezebox.py
2016-10-21 22:37:35 -07:00
Georgi Kirichkov
4f6ed09a99 Adds energy monitoring capabilities to the TP-Link HS110 (#3917)
* Adds energy monitoring capabilities to the TP-Link HS110

Energy monitoring works only on the HS110 model

* Reverts to using GadgetReactor's module

* Updates requirements_all.txt

* Refactors tplink switch to use attribute caching

* Update tplink.py
2016-10-21 21:45:36 -07:00
Johann Kellerman
8d375e2d47 Improve known_device.yaml writing (#3955)
* Better known_device.yaml writing

* yaml dump
2016-10-21 21:41:27 -07:00
Hydreliox
1d2d338cd0 Add Bbox Router bandwidth as sensors (#3956)
* Add Bbox Routeur bandwidth as sensors

Add possibility to monitor max and currently used bandwidth of your xdsl connection for Bbox Routeur

* Minor Fixes

Unit constant get back into the main sensor file

* Unused round removed
2016-10-21 21:34:22 -07:00
Hugo Dupras
cb47507002 Add support for Neato Connected robot as a switch (#3935)
* Add support for Neato Connected robot as a switch

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Add checklist items

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Add missing docstring

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* [Neato] Add update function to retrieve robot state

* Add docstring for update function + catch exception when retrieving state

* Change type of HTTPError when updating state

* Fix pylint errors
2016-10-21 21:14:45 -07:00
Fabian Affolter
fae620f3b3 Migrate to voluptuous (#3748) 2016-10-21 21:14:35 -07:00
Paulus Schoutsen
b821a82417 Merge remote-tracking branch 'origin/master' into dev 2016-10-21 20:57:07 -07:00
Paulus Schoutsen
da7837af73 Update frontend 2016-10-21 20:52:58 -07:00
Johann Kellerman
6e903fd429 Updater component - rename opt_out to reporting (#3979)
* Updater component - rename opt_out to reporting

* Fix tests
2016-10-21 20:40:23 -07:00
Martin Hjelmare
9f7e167669 Fix test using libsodium and SECRET_KEY (#3975)
* Move test to class with custom config setups and with config
  validation.
2016-10-21 20:23:29 -07:00
John Arild Berentsen
54a64fb8d9 Add support for verisure file camera. (#3952) 2016-10-21 22:41:17 +02:00
Nick Vella
2d89c3ecf4 Telstra SMS API notification component (#3949)
* Telstra API component

* import PLATFORM_SCHEMA

* Exclude Telstra notify component in coveragerc

* fix authentication issues

* Include title in SMS if it's provided

* pass lint

* Fix many code styling issues

* Confirm credentials are correct on component setup
2016-10-20 23:47:21 -07:00
Teemu Mikkonen
9f6d1c4e7b Device tracker: SNMPv3 (#3961)
* Add initial SNMPv3 support for better security

* Fixed indentation errors

* Fixed flake8 E128 on row 65

* Disabled warning about too many instance-attributes

* Removed extra code, added Inclusive to make better config validation
2016-10-20 23:01:30 -07:00
AlucardZero
62b8e54235 Upgrade SoCo to 0.12 (#3951)
Changelog: https://github.com/SoCo/SoCo/releases/tag/v0.12

Backwards Compatability changes:

    Dropped support for Python 3.2
    Methods relating to the music library (get_artists, get_album_artists, get_albums and others) have been moved to the music_library module. Instead of device.get_album_artists(), please now use device.music_library.get_album_artists() etc. Old code will continue to work for the moment, but will raise deprecation warnings
    Made a hard deprecation of the Spotify plugin since the API it relied on has been deprecated and it therefore no longer worked
    Dropped pylint checks for Python 2.6
2016-10-20 22:51:00 -07:00
Dustin S
1ceac8407d Adding security contexts to the resources. (#3840) 2016-10-20 22:43:39 -07:00
Jason Carter
0c0b02eb3d Moving updates out of state property (#3966) 2016-10-20 22:35:25 -07:00
Johann Kellerman
c70722dbae Updater component with basic system reporting (#3781) 2016-10-20 21:30:44 +02:00
Martin Hjelmare
a05fb4cef8 Upgrade pymysensors to 0.8 (#3954) 2016-10-20 20:56:26 +02:00
Marcelo Moreira de Mello
fee01fcccc Added unit test to the Yahoo Finance sensor (#3943) 2016-10-20 20:14:50 +02:00
henryk
2b37b4251b Fritzbox - Only report a MAC address if it's really there (#3964)
Fixes 'Neither mac or device id passed in'
2016-10-20 20:04:11 +02:00
Fabian Affolter
3aa1b6a3f8 Fix PEP257 issues (#3962) 2016-10-20 19:10:12 +02:00
Pascal Vizeli
c32afcd961 Bugfix sonos (#3926)
* Bugfix Sonos

* lint

* Use player uid for looking of exists

* fix lint

* fix unittest

* Change player_id to unique_id
2016-10-20 08:36:48 -07:00
hcooper
d60c2d604f Add support for multiple inputs to nmap tracking module as a list (#3944) 2016-10-20 07:15:00 +02:00
David-Leon Pohl
081e61528d Bugfixes for pilight hub component and unit tests (#3948) 2016-10-19 22:02:11 +02:00
Richard Cox
5799d1aec9 Fixing verbage (#3940) 2016-10-18 18:25:47 -07:00
Adam Mills
7d32e5eeeb Logbook filtering of automations by entity_id (#3927)
* Logbook filtering of automations by entity_id

* Trigger action function parameters required
2016-10-18 18:11:35 -07:00
Johann Kellerman
57f32fa629 Fixup device_tracekt.mqtt voluptuous & unit tests (#3904) 2016-10-18 18:10:28 -07:00
Sean Dague
7da47852d4 Yamaha zones (#3920)
* Enhance yamaha component

This enhances the yamaha component to create 1 player per zone,
instead of only one play, which provides direct control of the various
zones for the player.

It also exposes play_media for NET_RADIO sources, which allows direct
setting of that.

This requires code changes in rxv 0.2.0, so the requirement dependency
is raised.

* Support current playback metadata for NET_RADIO

When on NET RADIO, support the currently playing information.
2016-10-18 18:04:15 -07:00
Justin Weberg
f1b658ea5d Fix Typo (#3942) 2016-10-18 17:59:14 -07:00
Pascal Vizeli
754e93ff6a Rename add_devices to async_add_devices like dev guide (#3938) 2016-10-19 00:35:22 +02:00
David-Leon Pohl
947c1efca2 New pilight sensor component (#3822)
* Pilight daemon expects JSON serializable data. Thus dict is needed and not a mapping proxy.
* Add explanation why dict as message data is needed
* Use pytest-caplog and no unittest.TestCase
2016-10-18 23:16:20 +02:00
hexa-
a1239077d9 Add support for matrix notifications (#3827) 2016-10-18 21:13:00 +02:00
Paulus Schoutsen
53b5dc8e84 Build frontend 2016-10-18 09:10:22 -07:00
Lewis Juggins
09bcd7321a Reset Bravia playing info to ensure state reflects correctly (#3903) 2016-10-17 23:13:35 -07:00
Pascal Vizeli
14ef0ca786 Bugfix Template sensors (#3931) 2016-10-18 07:27:32 +02:00
Rob Capellini
272539105f Replacing tempfile with mock_open in tests (#3753)
- test_bootstrap.py
- test_config.py
- components/test_init.py
- components/test_panel_custom.py
- components/test_api.py
- components/notify/test_file.py
- components/notify/test_demo.py
- components/camera/test_local_file.py
- helpers/test_config_validation.py
- util/test_package.py
- util/test_yaml.py

No changes needed in:
- components/cover/test_command_line.py
- components/switch/test_command_line.py
- components/rollershutter/test_command_line.py
- components/test_shell_command.py
- components/notify/test_command_line.py

Misc changes in:
- components/mqtt/test_server.py

Also, removed some unused mock parameters in tests/components/mqtt/test_server.py.
2016-10-17 20:16:36 -07:00
Fabian Affolter
7d67017de7 Upgrade python-digitalocean to 1.10.0 (#3921) 2016-10-17 20:13:43 -07:00
juggie
d921073e77 Ecobee - Celsius (#3906)
* #3899 - Ecobee tempoeratures

* #3899 Remove unused import

* #3899 Implement min/max_temp in ecobee.py as these temperatures have to be in F for ecobee api.

* #3899 Stale print

* #3899 Use min/max_temp from base class

* #3899 Removed unused import (again) so tests pass since changing to use super class

* #3899 Fix long lines

* #3899 Install tox locally... make it happy, commit

* #3899 Remove overridden min/max_temp and instead update __init__:min/max_temp to convert to self.temperature_unit (of the thermostat) as opposed to self.unit_of_measurement (of the system), which is wrong

* Remove unused import from ecobee
2016-10-17 20:06:03 -07:00
Jason Carter
9cf2acb495 Concord232 alarm panel (#3842)
* Adding Concord232 Alarm Panel

* Adding Concord232 Zones as Sensors

* Updating requirements_all.txt

* Adding DOCType and making helper function a closure for clarity

* Fixing D400 error in build

* Fixing pylint errors

* Adding # pylint: disable=too-many-locals

* Updating with proper polling methods

* Fixing Merge issues

* Fixing DocStyle Issue

* Moving Import out of setup

* Fixing DocString

* Removing overthought GLOBAL
2016-10-17 19:59:41 -07:00
Giel Janssens
3b424b034a Netatmo thermostat (#3888)
* Added Netatmo-thermostat

* Remove-CONF_DEVICES
2016-10-17 19:57:02 -07:00
Marcelo Moreira de Mello
76598bc4d2 #3829 - Fixed issued LowHighTuple doesn't define __round__ method for Nest Sensor (#3881)
* #3829 - Fixed type LowHighTuple doesn't define __round__ method issue when Nest is operating in range mode

* Testing if temperature is a tuple instead int or float
2016-10-17 19:55:53 -07:00
William Scanlon
c54476b62f Protocol is an int (#3928) 2016-10-17 19:53:50 -07:00
sam-io
ae8a8e22ad Support for Apple Push Notification Service (#3756)
* added push notification implementation

* some lint changes

* added docs

* added push notification implementation

* some lint changes

* added docs

* Fixed comment formatting issues

* Added requirments

* Update requirements_all.txt

* Update apns.py

* re-generated requirments_all.txt

* Added link to online docs

* added push notification implementation

* some lint changes

* added docs

* added push notification implementation

* some lint changes

* added docs

* Fixed comment formatting issues

* Added requirments

* Update requirements_all.txt

* Update apns.py

* re-generated requirments_all.txt

* Added link to online docs

* changed to use http/2 library for push notifications

* fixed lint issue

* fixed test that fails on CI

* another go at fixing test that fails on CI

* another go at fixing test that fails on CI

* another go at fixing test that fails on CI

* added missing docstring

* moved service description to main services.yaml file

* renamed apns service
2016-10-17 19:41:49 -07:00
Paulus Schoutsen
4c8d1d9d2f Clean up some async stuff (#3915)
* Clean up some async stuff

* Adjust comments

* Pass hass instance to eventbus
2016-10-17 19:38:41 -07:00
Sean Dague
daea93d9f9 Suppress requests/urllib3 connection pool log messages (#3854)
requests/urllib3 is notorious for using the INFO log level for very
DEBUG kinds of information. Given the configurability of python
logging it's actually pretty easy to just set requests to WARN by
default. This cleans out a bunch of largely unuseful log lines from
home assistant output.
2016-10-17 21:14:10 +02:00
Pascal Vizeli
1540bb1279 Async template (#3909)
* port binary_sensor/template

* port sensor/template

* port switch/template

* fix unittest

* fix

* use task instead yield on it

* fix unittest

* fix unittest v2

* fix invalid config

* fix lint

* fix unuset import
2016-10-17 07:00:55 +02:00
Jan Harkes
555e533f67 Added tests for the template is_defined filter 2016-10-17 03:20:07 +02:00
Jan Harkes
118f2f0bad Use a filter to fail rendering undefined variables
Instead of globally using StrictUndefined, introduce a filter that
will trigger a render failure on undefined variables.

Use as `{{ value_json.someval | is_defined }}`.
2016-10-17 03:20:07 +02:00
Jan Harkes
c8add59ea5 Fail when rendering undefined objects in Jinja2 templates
By not successfully rendering unknown objects to an empty string
the caller provided error_value actually gets used.

This allows, for instance, the MQTT sensor to retain its state when an
unexpected or unwanted message (#2733/#3834) is received.
2016-10-17 03:20:07 +02:00
Willems Davy
18f5258aaf Emoncms history component (#3531)
* Emoncms_history component, fix git mess

* - switch to track_point_in_time to send all data at foxed interval
- don't use json_dump
- switch to http post instead of http get
2016-10-16 17:05:01 -07:00
Justin Weberg
207c9e8575 Fix Comment (#3913) 2016-10-16 16:56:02 -07:00
Rob Capellini
4891ca1610 Removing calls to mock.assert_called_once_with (#3896)
If a mock's assert_called_once_with method is misspelled (e.g. asert_called_once_with) then the test will appear as passing.  Therefore, this commit removes all instances of assert_called_once_with calls and replaces them with two assertions:

        self.assertEqual(mock.call_count, 1)
        self.assertEqual(mock.call_args, mock.call(call_args))
2016-10-16 16:13:27 -07:00
Lewis Juggins
10c9132046 Resolve issue with delay not passing variables to render (#3901) 2016-10-16 16:08:12 -07:00
Fabian Affolter
71ee847aee Add web scrape sensor (#3841)
* Add web scrape sensor

* Add support for 'value_template', set 'verify_ssl' to true,
and remove 'before', 'after' & 'element'

* Fix pylint issue
2016-10-16 16:06:07 -07:00
Per Sandström
d9ae7ceb0c Tellstick switch force update (#3874) 2016-10-16 15:56:55 -07:00
Justin Weberg
2a972b2334 Move micromarkdown to HA (#3908)
* Move micromarkdown to HA

* Fix requests

* Update micromarkdown-js.html& .gz

* Update micromarkdown-js files
2016-10-16 15:47:34 -07:00
Pascal Vizeli
7484152be1 Async speedup add_device callback (#3910)
* Speed up entities processing from add_device callback

* fix lint

* fix bug
2016-10-17 00:35:57 +02:00
Paulus Schoutsen
6581dc2381 Document more core pieces 2016-10-16 13:45:17 -07:00
Paulus Schoutsen
31ec0ac6a7 Add util.async to the dev docs 2016-10-16 13:35:01 -07:00
John Arild Berentsen
8b2edc1514 ZWave: Add association service (#3894)
* Add association service

* Refactor service

* Requested changes

* Grammar in pydocstyle
2016-10-16 20:36:06 +02:00
Adam Mills
2612c6d6b8 Zwave alt delay workaround for HS-WD100+ (#3893) 2016-10-16 20:35:39 +02:00
Pascal Vizeli
0b8b9ecb94 Async EntitiesComponent (#3820)
* first version

* First draft component entities

* Change add_entities to callback from coroutine

* Fix bug add async_prepare_reload

* Group draft v1

* group async

* bugfix

* bugfix v2

* fix lint

* fix extract_entity_ids

* fix other things

* move get_component out of executor

* bugfix

* Address minor changes

* lint

* bugfix - should work now

* make group init async only

* change update handling to old stuff

* fix group handling, remove generator from init

* fix lint

* protect loop for spaming with updates

* fix lint

* update test_group

* fix

* update group handling

* fix __init__ async trouble

* move device_tracker to new layout

* lint

* fix group unittest

* Test with coroutine

* fix bug

* now it works 💯

* ups

* first part of suggestion

* add_entities to coroutine

* change group

* convert add async_add_entity to coroutine

* fix unit tests

* fix lint

* fix lint part 2

* fix wrong import delete

* change async_update_tracked_entity_ids to coroutine

* fix

* revert last change

* fix unittest entity id

* fix unittest

* fix unittest

* fix unittest entity_component

* fix group

* fix group_test

* try part 2 to fix test_group

* fix all entity_component

* rename _process_config

* Change Group to init with factory

* fix lint

* fix lint

* fix callback

* Tweak entity component and group

* More fixes

* Final fixes

* No longer needed blocks

* Address @bbangert comments

* Add test for group.stop

* More callbacks for automation
2016-10-16 09:35:46 -07:00
Fabian Affolter
a0fdb2778d Fix name allocation (#3890) 2016-10-16 18:07:34 +02:00
Fabian Affolter
5ef8ca9b03 Upgrade sendgrid to 3.6.0 (#3887) 2016-10-16 09:09:48 +02:00
Fabian Affolter
a10fa90357 Update link to docs repo (#3886) 2016-10-15 13:46:45 +02:00
Fabian Affolter
9743e17d62 Minimum/maximum/mean sensor (#3852)
* Add min/max sensor

* Update min_max.py
2016-10-14 21:43:46 -07:00
Marcelo Moreira de Mello
6fcb1b548e Added the ability to Weather Underground to track severe weather alerts (#3505)
*  Added the ability to Weather Underground to track severe weather alerts

*   * Added message on the advisory attr

  * Updated tests

* * Making use of guard clause

* Checking multiple_alerts prior loop

* Using a better way to create dict

* Fixed issue to set to None only the object that failed

* Added unittest

* Split update() method to different calls with their one throttle control to minimize API calls

* Updated unittest and make sure the alert sensor will not return 'unknown' status'

* Removed update() method from state property

* Branch rebased and include Weather Underground attribution

* Update wunderground.py
2016-10-14 21:35:27 -07:00
Adam Mills
1bf5554017 Zwave rgb fix (#3879)
* _zw098 must be set before update_properties

* Fix problematic zwave color light value matching

See https://community.home-assistant.io/t/color-issues-with-aeotec-zwave-zw098/2830
2016-10-14 21:17:48 -07:00
Georgi Kirichkov
49b1643ff0 Relaxes the configuration options for influxdb (#3869)
* Relaxes the configuration options for influxdb

By default influxdb allows unauthenticated access
Home Assistant required at least username and password to be present to properly submit data to influxdb

* Removes unused import of 'copy'

The copy module was used only in the removed test case responsible for testing the missing keys

* Updates InfluxDB config schema to require user and password

Current InfluxDB (v 1.0) can work without any authentication, but when authentication is enabled both username and password should be set.

* Removes extra white space in test_influxdb.py
2016-10-14 21:10:04 -07:00
Michael
ce19e6367f Catch MQTT encoding errors (#3749)
* added error handling to mqtt message receive if payload is not utf-8 unicode
added mqtt test for above code as well

* change permission back to 644

* attempting to test new code

* changed exception to AttributeError
fixed test for above

* fixed lint errors I made in tests....mqtt/test_init.py

* more lint fixes for my added test

* remove dual decode of MQTT payload

* convert if to try, except, else statement for mqtt payload decode

* rework mqtt unicode testing code to properly check for log file entriy on unicode decode exception

* fixed lint error

* Update test_init.py
2016-10-14 21:08:44 -07:00
Fabian Affolter
180e146e14 Upgrade uber_rides to 0.2.7 (#3876) 2016-10-14 21:00:27 -07:00
Fabian Affolter
bead274b20 Upgrade slacker to 0.9.28 (#3877) 2016-10-14 20:58:49 -07:00
Richard Cox
1cbf8c8049 Zoneminder component (#3795)
* Initial Zoneminder commit

* Fixing bug when ZM sets its function to 'None'

* Adding zoneminder to coverage

* Quick Doc fix

* Update zoneminder.py

Doc Fix

* making the url base optional
2016-10-14 20:56:40 -07:00
Lukas
ad259ead50 Add ignore option to zwave customize configuration (#3865) 2016-10-14 08:36:55 -07:00
Daniel Høyer Iversen
bb457f47cc Merge pull request #3868 from home-assistant/flux_led_lib
update flux led library
2016-10-14 11:41:18 +02:00
Daniel
df3e904fe7 update flux led library 2016-10-14 11:17:03 +02:00
Hugo Dupras
7697cdef0a Pushbullet push an url note if an url is provided inside data (#3758) 2016-10-14 00:11:48 -07:00
Igor Shults
6951b6f60b Allow any positive integer for Z-Wave polling intensity (#3859) 2016-10-14 00:09:52 -07:00
John Arild Berentsen
6ca0d4cd14 Use pass instead of return None (#3856) 2016-10-14 00:09:24 -07:00
Sean Dague
a5b756e1e5 Bump proliphix library to 0.4.0 (#3855)
There was a bug in setback setting which is now fixed in 0.4.0. This
ensures that any users have working setback code.
2016-10-14 00:06:53 -07:00
Sean Dague
7848791a04 add arwn sensor platform (#3846)
This adds a sensor component that builds sensors based on the arwn
project (https://github.com/sdague/arwn). This uses a 433mhz receiver
to collect weather data and publish it over mqtt in a well defined
schema, which home-assistant can display as sensors.
2016-10-14 00:06:04 -07:00
Paulus Schoutsen
f916fc04f9 Update frontend 2016-10-14 00:02:21 -07:00
John Arild Berentsen
8f4608c654 Use only node id to identify node in set_config_parameter (#3801) 2016-10-13 23:45:00 -07:00
Per Sandström
399a0b470a select next and previous of input select (#3839) 2016-10-13 21:53:47 -07:00
Jan Harkes
4d716cec2b Bump pychromecast dependency to 0.7.6 (#3864) 2016-10-13 21:24:54 -07:00
Lukas
1373db8b60 Include index and instance in object_id of zwave devices (#3759)
* Include index and instance in object_id of zwave devices

* Add the instance id if there is more than one instance for the value
2016-10-13 21:13:05 -07:00
Hydreliox
7771cc2ccf Add Bbox Modem Routeur for device tracker (#3848) 2016-10-13 19:43:51 -07:00
Per Sandström
c5ad7996fb Merge pull request #3857 from persandstrom/vsure0.11.1
vsure 0.11.1
2016-10-13 22:18:42 +02:00
Per Sandström
3d94f77998 vsure 0.11.1 2016-10-13 21:53:50 +02:00
Marcelo Moreira de Mello
6330d9cde6 Fixed Fitbit resting heart rate attribute (#3835)
* Fixed Fitbit restingHeartRate field to grab the correct field from dictionary

In [31]: r['activities-heart'][-1].get('value')
Out[31]:
{'customHeartRateZones': [],
 'heartRateZones': [{'caloriesOut': 126.18348,
   'max': 94,
   'min': 30,
   'minutes': 67,
   'name': 'Out of Range'},
  {'caloriesOut': 27.21339,
   'max': 131,
   'min': 94,
   'minutes': 5,
   'name': 'Fat Burn'},
  {'caloriesOut': 0, 'max': 159, 'min': 131, 'minutes': 0, 'name': 'Cardio'},
  {'caloriesOut': 0, 'max': 220, 'min': 159, 'minutes': 0, 'name': 'Peak'}],
 'restingHeartRate': 69}

In [32]: r['activities-heart'][-1].get('value').get('restingHeartRate')
Out[32]: 69

* Renamed sensor to Resting Heart Rate to match it

* Fixed lint style
2016-10-13 09:22:05 -07:00
Hugo Dupras
9a0bb62654 Hotfix for Netatmo discovery (#3837)
This should definetly fix #2601

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-10-13 09:21:22 -07:00
Fabian Affolter
d873a7baf0 Use async_render_* and fix config validation (#3847) 2016-10-13 09:20:49 -07:00
Robbie Trencheny
c663d85129 Add Alexa Flash Briefing Skill API support (#3745)
* Add Alexa Flash Briefing Skill API support

* Set default value for text to empty string as per API docs

* Clean up existing Alexa tests

* Update configuration parsing and validation

* Add tests for the Flash Briefing API

* Update test_alexa.py
2016-10-13 09:14:22 -07:00
John
8c13d3ed4c Update zwave lights to increase update delay timer as needed (#3741)
Fix permissions
2016-10-13 09:09:41 -07:00
Johann Kellerman
cb322f72db Add persistent notifications to bootstrap (#3738)
* Add persistent notifications to bootstrap

* Rebase, Fix test
2016-10-13 09:09:07 -07:00
Scott Reston
39a446c43c Proper title, added album and artist for Squeezebox (#3735)
* Proper title, added album and artist

Title had previously concatenated artist - title.

* Made changes suggested by @balloobbot
2016-10-13 09:07:10 -07:00
wokar
aa8622f8e8 Added include and exclude functionality to history component (#3674)
* added include and exclude functionality to history component

* fixed summary lines in test method doc.

* cleanup of query filter creation

* o improved config validation
o move move IGNORE_DOMAINS to Filter.apply()
o removed config from Last5StatesView
o Filters instance is now created on setup
o config values are processed in setup and set to the Filters instance
o function _set_filters_in_query() moved to Filters class and renamed to apply()

* added unittests for more include/exclude filter combinations

* make pylint happy
2016-10-13 08:54:45 -07:00
Vittorio Monaco
e031b8078f Fixes an issue where Chromecast audio groups were not properly discovered (#3630)
* Fixes an issue where Chromecast audio groups were not properly discovered

* Forgot to commit the main fix

* Removes unused variable

* Doesn't use a protected API anymore

* PR remarks

* Fixes tests, adds comment

* Restores line as it was in the original commit, rephrases comment

* Should fix lint issues

* Trailing whitespace

* Some more lint
2016-10-13 08:51:43 -07:00
jgriff2
e1647fb6ac Add synology ss cameras (#3626)
* Add files via upload

* Update .coveragerc

* test

* Update synology camera

* Use voluptuous for synology

* Use voluptuous for synology

* Use voluptuous for synology

* Use voluptuous for synology

* Conform synology to flake8

* Added Whitelist to synology

* Sync to dev branch

* Added helper function to synology
2016-10-13 08:49:58 -07:00
Lewis Juggins
10feac11d9 Support recursive config inclusions (#3783) 2016-10-12 12:05:41 +02:00
Paulus Schoutsen
d4e2332ce0 Merge pull request #3814 from home-assistant/release-0-30-1
0.30.2
2016-10-11 22:28:00 -07:00
Jean Regisser
1d7169403b Add again certifi to Docker image (#3813)
Latest versions of certifi (>= 2016.8.31) don't seem to break anything
anymore.
See home-assistant/home-assistant#2554
2016-10-11 22:25:17 -07:00
Keith Lamprecht
e4685de459 Restore Optional Target Config Attribute (notify.pushover) (#3769)
* Restore Optional Target Config Attribute

* Fix Tabs

* Change indents to spaces

* Make a target fix

* Change to simpler not syntax
2016-10-11 21:59:45 -07:00
Keith Lamprecht
d83de36c32 Restore Optional Target Config Attribute (notify.pushover) (#3769)
* Restore Optional Target Config Attribute

* Fix Tabs

* Change indents to spaces

* Make a target fix

* Change to simpler not syntax
2016-10-11 21:59:34 -07:00
Fabian Affolter
73547c8c4b Fix slack targets (#3826) 2016-10-11 21:58:54 -07:00
Fabian Affolter
40094cecae Fix slack targets (#3826) 2016-10-11 21:16:11 -07:00
Robbie Trencheny
0b327cd4d9 Notify: Only attach target if in call data (#3831)
* Only pass through the target if it has a value

* Target will no longer be none
2016-10-11 20:34:14 -07:00
Robbie Trencheny
d302dbec2d Notify: Only attach target if in call data (#3831)
* Only pass through the target if it has a value

* Target will no longer be none
2016-10-11 20:33:41 -07:00
Fabian Affolter
e135691bd6 Migrate to voluptuous (#3817) 2016-10-11 08:33:22 -07:00
Fabian Affolter
d4dc2707a1 Use voluptuous for eQ-3 thermostat (#3729)
* Migrate to voluptuous

* Fix requirement and typo
2016-10-11 11:27:31 +02:00
Paulus Schoutsen
a8cdf36d5c Update recorder callback (#3812) 2016-10-11 00:58:43 -07:00
Fabian Affolter
a99f36f519 Migrate to voluptuous (#3737) 2016-10-11 00:56:57 -07:00
Fabian Affolter
0568ef025b Use voluptuous for Heatmiser (#3732) 2016-10-11 00:53:24 -07:00
Fabian Affolter
8ded8f572a Add/adjust attribution of sensor platform (#3719)
* Add/adjust attribution

* Fix typo
2016-10-11 00:28:19 -07:00
Fabian Affolter
7cf2c48175 Use voluptuous for FitBit (#3686)
* Migrate to voluptuous

* Fix default
2016-10-11 00:27:15 -07:00
Fabian Affolter
7cf9ff83bc Migrate to voluptuous (#3293) [BREAKING CHANGE] 2016-10-11 00:26:11 -07:00
Paulus Schoutsen
dfe9af7110 Version bump to 0.30.2 2016-10-11 00:21:01 -07:00
Erik Eriksson
016e8f833d slugify (#3777) 2016-10-11 00:20:49 -07:00
Teemu Mikkonen
574df0f420 Fix for html5 notification tag problem. Fixes #3774 (#3790) 2016-10-11 00:20:49 -07:00
Pascal Vizeli
f18f181962 Hotfix device name with autodiscovery (#3791) 2016-10-11 00:20:49 -07:00
Ellis Percival
4754455295 Make 'pin' optional for zigbee device config. (#3799) 2016-10-11 00:20:49 -07:00
Robbie Trencheny
8c1317f278 Wrap found target in list (#3809)
* Wrap found target in list

* Fix test_messages_to_targets_route
2016-10-11 00:20:49 -07:00
Russell Cloran
7c2cb6cffd Separate climate platform and presentation units (#3755)
* Separate platform and presentation units in climate

* Fix unit tests

Maybe

* Fix unit tests some more

Maybe

* Rename _platform_unit_of_measurement to temperature_unit

* Fix tests for renamed attribute
2016-10-11 00:00:29 -07:00
Ferry van Zeelst
8c9d1d9af1 Added additional checks which hides functions which are not supported by Nest (#3751)
* Added additional checks which hides functions which are not support (like fans / humidity / cooling)

* Fixed pylint and flake8 errors (not test file available)

* Fixed pydocstyle error

* Refactored Code and Comments as described in pull-request

* Added additional comment and requesting retest

* Upgraded to python-nest 2.11 which contains previously hidden functions
2016-10-10 23:57:14 -07:00
Lukas
941fccd3fc Update python-lirc to 1.2.3 (#3784)
* Upgrade to latest python-lirc 1.2.3

* update requirements_all.txt
2016-10-10 23:44:41 -07:00
Clemens Wolff
711526e574 Cache condition in helpers.Script (#3797)
* Cache condition in helpers.Script

The caching is a simple in-memory, per-instance dictionary.
We use __str__ to format cache keys.

This naive implementation has some disadvantages (e.g., we won't be able
to cache two conditions that contain references to
distinct-but-equivalent object instances and we don't have any control
over the size of the condition cache), but for most simple use-cases the
approach should be good enough.

Resolves #3629

* Fix docstring style
2016-10-10 23:36:38 -07:00
Fabian Affolter
a2503e4d13 Upgrade sqlalchemy to 1.1.1 (#3796) 2016-10-10 23:29:43 -07:00
Fabian Affolter
656ee52435 Upgrade cherrypy to 8.1.2 (#3805) 2016-10-10 23:27:53 -07:00
Fabian Affolter
002660fd9e Upgrade dnspython3 to 1.15.0 (#3804) 2016-10-10 23:24:10 -07:00
Fabian Affolter
63580f9e03 Upgrade pyowm to 2.5.0 (#3806) 2016-10-10 23:23:32 -07:00
Teemu Mikkonen
87d9cdd78f Fix for html5 notification tag problem. Fixes #3774 (#3790) 2016-10-10 23:17:27 -07:00
phardy
c8ca66b671 Stop GTFS component from overwriting friendly_name (#3798)
* Prevent update from clobbering configured name.

* linted

* Fixing awkward line breaking.
2016-10-10 22:36:20 -07:00
Robbie Trencheny
9b98d470c2 Wrap found target in list (#3809)
* Wrap found target in list

* Fix test_messages_to_targets_route
2016-10-10 22:31:15 -07:00
sander76
b19ec21e88 Changing import as powerview api did change. (#3780) 2016-10-10 22:30:27 -07:00
Fabian Affolter
3b331eac56 Update docstrings (sensor.pi_hole, sensor.haveibeenpwned) (#3793)
* Fix docstrings

* Update docstrings
2016-10-10 19:38:32 +02:00
Fabian Affolter
552265bc31 No longer use old name (#3792) 2016-10-10 19:38:20 +02:00
Ellis Percival
df58f718ab Make 'pin' optional for zigbee device config. (#3799) 2016-10-10 16:53:18 +02:00
Pascal Vizeli
76a1a54369 Hotfix device name with autodiscovery (#3791) 2016-10-10 13:46:23 +02:00
Per Sandström
b6e008be71 Merge pull request #3782 from persandstrom/vsure0.11.0
vsure0.11.0
2016-10-09 22:01:46 +02:00
Per Sandström
9d5c20b629 vsure0.11.0 2016-10-09 21:47:35 +02:00
Erik Eriksson
63d9ea6643 slugify (#3777) 2016-10-09 09:15:58 -07:00
Hugo Dupras
1d0df63615 Netatmo binary sensor (#3455)
* Basic support for Netatmo welcome binary sensors

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Bug fixes and optimization for Netatmo devices

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* pylint fixes

* Bug Fixing and optimization for Netatmo devices

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Add unique_id for cameras to avoid duplicate
And global config to disable discovery for netatmo devices

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-10-09 08:45:12 -07:00
hexa-
154eacef6c Http: Change approved_ips from string to cidr validation (#3532) [BREAKING CHANGE]
* Change approved_ips from string to cidr validation

Relabel to trusted_networks, better reflecting its expected inputs,
everything that ipaddress.ip_networks recognizes as an ip network
is possible:
- 127.0.0.1      (single ipv4 addresses)
- 192.168.0.0/24 (ipv4 networks)
- ::1            (single ipv6 addresses)
- 2001:DB8::/48  (ipv6 networks)

* Add support for the X-Forwarded-For header
2016-10-09 08:13:30 -07:00
Fabian Affolter
dc95b28487 Use voluptuous for Russound RNET (#3689)
* Migrate to voluptuous

* Remove wrong default
2016-10-09 16:40:54 +02:00
Paulus Schoutsen
c9a88322d6 Merge pull request #3765 from home-assistant/hotfix-0-30-1
0.30.1
2016-10-08 14:50:52 -07:00
Paulus Schoutsen
46b58db2ff Version bump to 0.30.1 2016-10-08 14:42:47 -07:00
mtl010957
78cbfa3f96 Fixed issue #3760, handle X10 unit numbers greater than 9. (#3763) 2016-10-08 14:42:24 -07:00
Roi Dayan
dba5c74c8f Fix command line cover template (#3754)
The command line cover value template is optional so we
need to check it's not none before assigning hass to it.

Fixes #3649

Signed-off-by: Roi Dayan <roi.dayan@gmail.com>
2016-10-08 14:42:24 -07:00
Johann Kellerman
a94a5ac9b5 Coerce device IDs from known_devices to be slugs (#3764)
* Slugify & consider_home test fix [due to load valid PR]

* undo schema change

* Fix slugify error
2016-10-08 14:42:24 -07:00
Johann Kellerman
4d9bac6f9c Coerce device IDs from known_devices to be slugs (#3764)
* Slugify & consider_home test fix [due to load valid PR]

* undo schema change

* Fix slugify error
2016-10-08 14:40:50 -07:00
Roi Dayan
6419d273ea Fix command line cover template (#3754)
The command line cover value template is optional so we
need to check it's not none before assigning hass to it.

Fixes #3649

Signed-off-by: Roi Dayan <roi.dayan@gmail.com>
2016-10-08 13:03:32 -07:00
mtl010957
7882b19dc5 Fixed issue #3760, handle X10 unit numbers greater than 9. (#3763) 2016-10-08 12:57:40 -07:00
Willems Davy
8f8bba4ad7 Haveibeenpwned sensor platform (#3618)
* Initial version of "haveibeenpwned" sensor component

* 2 flake8 fixes

* remove debugging error message

* Increase scan_interval as well as throttle to make sure that during initial startup of hass the request happens with 5 seconds delays and after startup with 15 minutes delays. Scan_interval is increased also to not call update as often

* update .coveragerc

* remove (ssl) verify=False

* - use dict to keep the request values with email as key
- use track_point_in_time system to make sure data updates initially at 5 seconds between each call until all sensor's email have a result in the dict.

* fix a pylint error that happend on the py35 tests
2016-10-08 11:38:58 -07:00
Johann Kellerman
7b40a641ec Continue on invalid platforms and new setup_component unit tests (#3736) 2016-10-08 11:27:35 -07:00
wokar
1b26b5ad14 add include configuration to logbook (#3739) 2016-10-08 11:26:14 -07:00
Hugo Dupras
bbbb4441ea Add discovery support for Netatmo weather Station (#3714)
* Add discovery support for Netatmo Weather station

Only The weather information are currently supported (No battery or wifi status supported)

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Add unique_id for netatmo sensors to avoid duplicate

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>

* Update requirements_all.txt and PEP8 fixes

Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-10-08 11:26:01 -07:00
Paulus Schoutsen
1e2e877302 Version bump to 0.31.0.dev0 2016-10-08 09:58:28 -07:00
Paulus Schoutsen
4239a2b844 Merge pull request #3715 from home-assistant/dev
0.30
2016-10-08 09:58:08 -07:00
Paulus Schoutsen
408b68bfac Version bump to 0.30.0 2016-10-08 09:57:51 -07:00
Paulus Schoutsen
fe317b806f Close event loop to avoid error on exiting HASS (#3762) 2016-10-08 18:56:36 +02:00
Paulus Schoutsen
09cbf68637 Update frontend 2016-10-08 09:23:45 -07:00
Johann Kellerman
fb94aaa5a1 Load yaml using validator and include consider_home (#3743)
* Load yaml using validator, consider_home

* timedelta, track_if_away

* improve voluptuous

* Add default back

* Change time_period validation order
2016-10-07 18:08:33 -07:00
Fabian Affolter
b09b13f552 Catch exception (fixes #3699) (#3727) 2016-10-07 17:30:59 -07:00
Erik Eriksson
2d4df42a65 improved error handling (#3725) 2016-10-07 17:25:51 -07:00
Erik Eriksson
fccc7e69d0 Improved exception handling. Don't stop updating from server because of exception. (#3724) 2016-10-07 17:24:02 -07:00
Pascal Vizeli
f1e5d32ef5 Async exception handling (#3731)
* remove unused exception

* add logging

* disable pylint broad-except

* add exception handler

* fix lint

* update log output

* change log message in async with exc_info

* Add exc_info to asyncio exception handler
2016-10-07 17:20:39 -07:00
Erik Eriksson
1c24018fbb Improved exception handling (#3746) 2016-10-07 17:16:35 -07:00
Robbie Trencheny
f37038921f Fix E501 line too long error 2016-10-07 11:48:38 -07:00
Robbie Trencheny
f030ff67ad Set anel_pwrctrl switch to specific commit instead of master 2016-10-07 11:38:39 -07:00
Robbie Trencheny
3cfec2b5e2 Set TP-Link switch to specific commit instead of master 2016-10-07 11:36:43 -07:00
Robbie Trencheny
58b3dd7cc0 Set specific commit for Hunter Douglas API package due to upstream rename 2016-10-07 11:34:41 -07:00
Fabian Affolter
5e16dc6307 Fix typo (#3744) 2016-10-07 00:00:12 -07:00
Fabian Affolter
8b059e9aad Migrate to voluptuous (#3687) 2016-10-06 16:11:08 +02:00
Fabian Affolter
12f1be9b1c Fix PEP257 issues and ordering (#3720) 2016-10-05 17:32:29 -07:00
Robbie Trencheny
519f400175 Merge branch 'master' into dev 2016-10-05 15:35:01 -07:00
Fabian Affolter
a94571fd10 Change name of Forecast.io platform to Dark Sky (#3698)
* Rename Forecast.io platform to Dark Sky

* Upgrade to python-forecastio to 1.3.5

* Update to reflect name change (Forecast.io -> Dark Sky)

* Rename forecast to darksky
2016-10-05 21:42:58 +02:00
Fabian Affolter
cb3a78b330 Adjust vol to accept filenames (fixes #3701) (#3707) 2016-10-05 18:02:45 +02:00
John
1bc6366051 Increase allowable polling intensity values (#3711) 2016-10-05 16:58:06 +02:00
Fabian Affolter
5d339fb141 Fix sentence (#3709) 2016-10-05 16:28:38 +02:00
Fabian Affolter
0c68f381b0 Migrate to voluptuous (#3679) 2016-10-05 14:40:08 +02:00
Daniel Høyer Iversen
3e40b24293 Merge pull request #3708 from home-assistant/flux_led
Update flux led library
2016-10-05 11:12:56 +02:00
Daniel Høyer Iversen
4b828d225e Merge pull request #3656 from home-assistant/pushetta
customize if pushetta should send test message on start up
2016-10-05 11:03:35 +02:00
Daniel
201294e481 Update flux led library 2016-10-05 10:49:33 +02:00
Daniel
350d23f7eb customize if pushetta should send test message on start up 2016-10-05 10:45:41 +02:00
Daniel Høyer Iversen
b8beae9c6c Merge pull request #3651 from home-assistant/automation
Customize initial state of automation
2016-10-05 09:48:55 +02:00
Daniel
46f3337b07 Customize initial state of automation 2016-10-05 09:20:51 +02:00
John Arild Berentsen
b2354f45be Add possibility to set temperature to a given operation_mode (#3646)
* Add possibility to set temperature to a given operation_mode

* Correctly break

* Review changes
2016-10-04 23:48:25 -07:00
Paulus Schoutsen
e4e13b59cf Update frontend 2016-10-04 23:15:10 -07:00
Paulus Schoutsen
8c694eb279 Speed up MQTT server test (#3703) 2016-10-04 22:49:43 -07:00
Paulus Schoutsen
9d085a023c Merge pull request #3706 from home-assistant/hotfix-0-29-7b
Fix broken unit tests
2016-10-04 22:47:46 -07:00
Martin Hjelmare
c06313a897 Set force_update to true for mysensors sensors (#3648) 2016-10-04 22:45:38 -07:00
Per Sandström
ed490b1c11 Add västtrafik sensor (#3640) 2016-10-04 22:37:45 -07:00
Marcelo Moreira de Mello
70fc94003d Fixed issue #3624 and bumped python-nest to 2.10.0 version (#3665)
* Fixed issue #3624 and bumped python-nest to 2.10.0 version

*    Fixed return state when accessing attribute operation_mode

* Fixed typo
2016-10-04 22:29:21 -07:00
Paulus Schoutsen
114fae76e1 Fix broken unit tests 2016-10-04 22:23:58 -07:00
Paulus Schoutsen
4f0064b00e Fix broken tests (#3704)
* Fix broken tests

* Lint
2016-10-04 22:19:12 -07:00
Fabian Affolter
dc53c21548 Check that no configuration is provided (#3675) 2016-10-04 22:10:31 -07:00
Pascal Vizeli
b9b41d3855 Update ha-alpr / change default interval (#3691) 2016-10-04 22:04:50 -07:00
John Arild Berentsen
17a8dd3f70 Add set_config_parameter service (#3696) 2016-10-04 22:04:19 -07:00
gwendalg
03d6a7c42a Add Ted5000 Sensor (#3559)
* Add Ted5000 Sensor

Signed-off-by: Gwendal Grignou <gwendal@gmail.com>

* sensors: ted5000: use requests instead of urllib.

Signed-off-by: Gwendal Grignou <gwendal@gmail.com>

* Update ted5000.py

Add timeout to requests call.
2016-10-04 21:57:40 -07:00
Daniel Høyer Iversen
d2d393feb5 Add automations and scripts to group.all_automations and group.all_scripts (#3664)
* Add automations to group.all_automations

* Add scripts to group.all_scripts
2016-10-04 21:20:48 -07:00
Paulus Schoutsen
e49651cdeb Merge pull request #3702 from home-assistant/hotfix-0-29-7
0.29.7
2016-10-04 21:03:47 -07:00
Paulus Schoutsen
a60e845203 Version bump to 0.29.7 2016-10-04 21:01:28 -07:00
Pascal Vizeli
f23eb9336f Service & signal (stop/restart) fix (#3690)
* Bugfix signhandling/services

* change from coroutine to callback

* add error handling

* fix bug with endless running

* fix unit test

* Revert "fix unit test"

This reverts commit 31135c7709.

* Disable sigterm/sighup test
2016-10-04 21:01:08 -07:00
Pascal Vizeli
0bf8bb62ad Service & signal (stop/restart) fix (#3690)
* Bugfix signhandling/services

* change from coroutine to callback

* add error handling

* fix bug with endless running

* fix unit test

* Revert "fix unit test"

This reverts commit 31135c7709.

* Disable sigterm/sighup test
2016-10-04 21:00:36 -07:00
Paulus Schoutsen
5085cdb0f7 Add async_safe annotation (#3688)
* Add async_safe annotation

* More async_run_job

* coroutine -> async_save

* Lint

* Rename async_safe -> callback

* Add tests to core for different job types

* Add one more test with different type of callbacks

* Fix typing signature for callback methods

* Fix callback service executed method

* Fix method signatures for callback
2016-10-04 20:44:32 -07:00
Robbie Trencheny
8358f938b5 Now no one wants to be blacklisted, so lets remove the configuration entirely 2016-10-04 13:39:48 -07:00
Robbie Trencheny
be7401f4a2 Now no one wants to be blacklisted, so lets remove the configuration entirely 2016-10-04 13:38:56 -07:00
Robbie Trencheny
760117167d Update .mention-bot to add more users to blacklist and remove skipAlreadyMentionedPR 2016-10-04 13:25:57 -07:00
Robbie Trencheny
2fd83b439c Update .mention-bot to add more users to blacklist and remove skipAlreadyMentionedPR 2016-10-04 13:25:16 -07:00
Robbie Trencheny
e523c3d196 Add @mention-bot configuration 2016-10-04 13:22:35 -07:00
Per Sandström
d9e73d1d71 Merge pull request #3697 from persandstrom/verisure_0.10.3
vsure 0.10.3
2016-10-04 22:20:12 +02:00
Per Sandström
5d3e93b4ef vsure 0.10.3 2016-10-04 22:01:36 +02:00
sam-io
85782f9c29 fix for date formatting issue (#3693) 2016-10-04 08:41:39 -07:00
William Scanlon
2cf49f3de6 Added support for Wink Smoke and CO detectors (#3645) 2016-10-04 01:07:50 -07:00
Fabian Affolter
a072047d9d Auth and headers support for REST sensor (#3592)
* Add auth and header support

* Update header part
2016-10-04 01:07:17 -07:00
Fabian Affolter
74b0e4cb45 Statistics sensor (#3513)
* Initial stats sensor

* Add total and tests

* Use deque, rename var, set default, and only update sensor
2016-10-04 01:04:00 -07:00
Rob Capellini
694983379f test: mocking IO in HTML5 notify tests (#3685)
Replacing temporary file creation in tests with mock's mock_open for faster IO.
2016-10-04 01:01:41 -07:00
Erik Eriksson
5900d8a2c1 only query vehicle attributes once (#3668)
use registration number as dev id
2016-10-04 00:58:25 -07:00
Erik Eriksson
287a7e2720 Support for encrypted payload (#3587) 2016-10-04 00:57:37 -07:00
William Scanlon
8592ba3cb9 Report availability of arest (#3614) 2016-10-04 00:51:45 -07:00
deisi
c93b63963b Fix 3621 (#3642)
Happens when the scanner searches for a mac address, that has been forgotten
by the fritzbox.
2016-10-04 00:47:58 -07:00
Fabian Affolter
194402f7e7 Upgrade Sphinx to 1.4.8 and set missing variables (#3650) 2016-10-04 00:47:28 -07:00
Paulus Schoutsen
d58548dd1c Address asyncio comments (#3663)
* Template platforms: create_task instead of yield from

* Automation: less yielding, more create_tasking

* Helpers.script: less yielding, more create_tasking

* Deflake logbook test

* Deflake automation reload config test

* MQTT: Use async_add_job and threaded_listener_factory

* Deflake other logbook test

* lint

* Add test for automation trigger service

* MQTT client can be called from within async
2016-10-03 22:39:27 -07:00
Robbie Trencheny
f2a12b7ac2 Add @mention-bot configuration 2016-10-03 17:36:00 -07:00
Pascal Vizeli
625319846c Big Homematic update (#3677)
* Update homeassistant with new pyhomematic layout

* fix linter
2016-10-03 23:21:53 +02:00
Daniel Høyer Iversen
c3ea04f2dd revert update of input_boolean 2016-10-03 22:05:06 +02:00
Daniel Høyer Iversen
4a5a1e1e96 Ensure unique entity id for input_boolean 2016-10-03 22:03:16 +02:00
Pascal Vizeli
41aff96375 Bugfix temp convert on none temp attribute / unittest for that (#3654) 2016-10-03 10:46:31 +02:00
Justin Weberg
0219df17f5 Expose Configuration path to frontend (#3660)
* Expose config path to frontend

* Fix typo

* Add deleted code.
2016-10-03 00:04:43 -07:00
Robbie Trencheny
b586e80977 Squashed commit of the following:
commit 220331260e9748ac8e17b3ce776330c1dfb7725b
Merge: 73d93e5 c891820
Author: Robbie Trencheny <me@robbiet.us>
Date:   Sun Oct 2 17:57:24 2016 -0700

    Merge branch 'color_temp_for_mqtt_light' of https://github.com/alterscape/home-assistant into alterscape-color_temp_for_mqtt_light

commit c89182008a
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 23:06:34 2016 -0700

    fix missing docstring.

commit e61dda4dd3
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 22:43:04 2016 -0700

    fix pep8 errors and typos in tests.

commit 559d1752d2
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 21:41:07 2016 -0700

    add tests for mqtt color temp support

commit 702defb932
Author: Ryan Spicer <ryanspicer@gmail.com>
Date:   Sun Sep 18 20:55:07 2016 -0700

    Add color temp support to mqtt lights.
2016-10-02 18:04:00 -07:00
hexa-
73d93e526e Add anel_pwrctrl platform to control switches on ANEL PwrCtrl devices (#3644)
* Add pwrctrl platform to control switches on ANEL PwrCtrl devices

* make requested changes
2016-10-02 16:51:15 -07:00
Paulus Schoutsen
148ea82513 Update frontend 2016-10-02 15:38:18 -07:00
Paulus Schoutsen
abb8bcb6d9 Protect waiting for event loop from within event loop (#3658)
* Protect waiting for event loop from within event loop

* Faster fetching of loop attribute for ident check
2016-10-02 15:07:23 -07:00
John Arild Berentsen
e455daa61d Make sure temperature slider is shown if reported temp is 0 (#3653) 2016-10-02 17:47:04 +02:00
Martin Hjelmare
b6b0bad0c7 Add new mysensors types (#3637)
* Add S_INFO, S_GAS, S_GPS, S_WATER_QUALITY.
* Extend S_CUSTOM, S_POWER.
* Add more units.
2016-10-01 23:23:31 -07:00
Paulus Schoutsen
d5ab292ff3 Sensor.emoncms: Never disable SSL verification. 2016-10-01 23:13:07 -07:00
Erik Eriksson
9f8acbec95 Add support for tracking position and status for a Volvo car (#3617) 2016-10-01 23:00:01 -07:00
Jeff Wilson
d55ed7a3a2 Flux switch improvements (#3615)
* Make flux always adjust brightness of light (even when not in XY mode)

* Remove kelvin mode from flux switch

The light/turn_on service only works with mired values, kelvin values
are out of range per the schema.

* Use already defined min/max values for light/turn_on schema

* Clamp temp value to light/turn_on allowed values
2016-10-01 22:57:15 -07:00
Greg Dowling
fcbfcf0aa7 Bump pywemo version - changes port order for faster startup. (#3612) 2016-10-01 22:53:24 -07:00
Simon Szustkowski
183936375f Add a Timeout for InfluxDB connections (#3563)
* Add a Timeout for InfluxDB connections

* Removed trailing whitespace
2016-10-01 22:29:06 -07:00
Paulus Schoutsen
1f1ac02457 Merge branch 'pr/3552' into dev 2016-10-01 22:20:58 -07:00
Robbie Trencheny
646eaccd4d Update notify to expect a list of string targets instead of a single … (#3548)
* Update notify to expect a list of string targets instead of a single string

* Actually do the thing I set out to do

* Fix notify.group test to expect an array of targets

* REST platform will only use the first target in the list

* Update notify platforms to expect a list of targets

* Update notify services.yaml
2016-10-01 22:19:17 -07:00
Pascal Vizeli
c189c05676 Add temp convert to climate base object (#3611)
* Add temp convert to climate base object

* fix nest

* fix lint
2016-10-01 22:15:19 -07:00
Lewis Juggins
9683e3e3ad Correctly define requirements for emulated hue (#3535) 2016-10-01 21:56:45 -07:00
Fabian Affolter
a61e08680a Add persistent notification for failed login attempts (#3528) 2016-10-01 21:20:16 -07:00
Klaas Hoekema
9da2d6edd0 Make forecast.io update interval configurable (#3520)
Adds a config parameter (`update_interval`) to the `forecast` sensor to
set the minimum update interval. The default remains 120 seconds.
2016-10-01 21:18:10 -07:00
William Scanlon
1f38e9fa57 Support for wink oath2 and relay sensors (#3496) 2016-10-01 20:45:39 -07:00
Paulus Schoutsen
996d7cf1cd Merge pull request #3627 from home-assistant/async-entity-update
Add async updates to entities
2016-10-01 16:20:48 -07:00
Paulus Schoutsen
c36d30f4fe Typo 2016-10-01 15:43:33 -07:00
Paulus Schoutsen
8f3e12c9b8 Make Automation.reload_service_handler async 2016-10-01 15:42:17 -07:00
Paulus Schoutsen
56fdc2a625 Automation: call prepare_setup_platform in executor 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
e18825ba20 Automation: only call executor once when processing config 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
7ab7edd81c Make automation async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
16ff68ca84 Add mqtt.async_subscribe 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
b8504f8fc8 Make helpers.script async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
185bd6c28a Make helpers.condition.* async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
33a51623f8 Make Service.call_from_config async 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
4198c42736 Have template platforms never leave the event loop 2016-10-01 14:11:16 -07:00
Paulus Schoutsen
3e24a35c1e Skip RFXtrx tests unless RFXTRX=RUN (#3625)
* Skip RFXtrx tests unless RFXTRX=RUN

* Remove my previous hacks to slightly speed up rfxtrx

* Exclude RFXTRX tests from coverage

* Remove unused import in rfxtrx tstt

* Add close connection back to RFXtrx tests

* Typo
2016-10-01 13:57:10 -07:00
Paulus Schoutsen
651f3ab55c Merge pull request #3641 from home-assistant/hotfix-0-29-6
0.29.6
2016-10-01 12:12:56 -07:00
Paulus Schoutsen
756f23f0b4 Version bump to 0.29.6 2016-10-01 12:10:00 -07:00
Paulus Schoutsen
ef0e018cbb Service config calls will no longer mutate original config (#3628) 2016-10-01 12:09:25 -07:00
Ben Bangert
9cf2ad0b55 Monkey-patch a weakref set in Task to be a no-op. (#3639)
* Monkey-patch a weakref set in Task to be a no-op.

* Fix linting issues
2016-10-01 12:08:56 -07:00
Ben Bangert
892bbdc2dd Monkey-patch a weakref set in Task to be a no-op. (#3639)
* Monkey-patch a weakref set in Task to be a no-op.

* Fix linting issues
2016-10-01 12:08:25 -07:00
Paulus Schoutsen
bb03960ba5 Voluptuous arest (#3558)
* Migrate to voluptuous

* Adjust sensor.arest for new template system

* Use items() to align the var section with the pins
2016-10-01 18:45:43 +02:00
Fabian Affolter
15ed8c6332 Add units (fixes #3619) (#3633) 2016-10-01 17:35:32 +02:00
Paulus Schoutsen
412b5350ce Service config calls will no longer mutate original config (#3628) 2016-09-30 23:26:15 -07:00
Otto Winter
d5f8aa52c4 Add support for MySensors cover (#3512)
* Added support for MySensors cover device

* Fixed set_req not defined

* Fixed V_PERCENTAGE is str

* Removed set_cover_position

The MySensors documentation doesn’t specify when sending a V_PERCENTAGE
is allowed.

* Fixed homeassistant/components/mysensors.py line too long

* Fixed lint ATTR_POSITION imported but unused

* Use V_PERCENTAGE for MySensors cover

* Revert "Removed set_cover_position"

This reverts commit d78cb3a04d.

* Fix set_req, ATTR_POSITION not defined

* Added support for non-exactly positionable covers

* Fixed V_PERCENTAGE cast to bool

* Ported MySensors cover back to v1.4

`V_PERCENTAGE` and `V_DIMMER` are aliases just like `V_STATUS` and
`V_LIGHT`, so the code inside `MySensorsCover` doesn’t need to be
updated.

* Fixed v1.5 V_TYPES not in in v1.4 gateway SetReq
2016-10-01 01:36:04 +02:00
Paulus Schoutsen
b650b2b0db Spread async love (#3575)
* Convert Entity.update_ha_state to be async

* Make Service.call async

* Update entity.py

* Add Entity.async_update

* Make automation zone trigger async

* Fix linting

* Reduce flakiness in hass.block_till_done

* Make automation.numeric_state async

* Make mqtt.subscribe async

* Make automation.mqtt async

* Make automation.time async

* Make automation.sun async

* Add async_track_point_in_utc_time

* Make helpers.track_sunrise/set async

* Add async_track_state_change

* Make automation.state async

* Clean up helpers/entity.py tests

* Lint

* Lint

* Core.is_state and Core.is_state_attr are async friendly

* Lint

* Lint
2016-09-30 12:57:24 -07:00
Fabian Affolter
7e50ccd32a Component for Digital Ocean (#3322)
* Add Digital Ocean implementation

* Remove kernel
2016-09-30 18:30:44 +02:00
John Arild Berentsen
521080d1b0 Zwave: Update commandclasses and deviceclasses according to sigma SDK (#3495)
* Update Command classes and device types to Sigma SDK

* Fix some pylint

* Seperate constants to file

* Flake8

* coverage and flake8 pylint

* Add services.yaml

* Service descriptions was missing

* Spelling :)

* grammar

* Remove zwave service descriptions from main
2016-09-30 08:43:18 -07:00
Paulus Schoutsen
d76cf092c3 Merge pull request #3610 from home-assistant/hotfix-0-29-5
0.29.5
2016-09-30 00:24:59 -07:00
Paulus Schoutsen
dfb92fa836 Version bump to 0.29.5 2016-09-30 00:15:14 -07:00
Paulus Schoutsen
5cb8ce71ef Lint 2016-09-30 00:14:59 -07:00
Jeff Wilson
099e983ca0 Nest operation modes (#3609)
* Add ability to change Nest mode to all available options

* Make Nest state reflect current operation not current operation mode

* Update Nest sensor to use operation mode

* Fix linting

* Revert "Make Nest state reflect current operation not current operation mode"

This reverts commit 573ba028d8.

Conflicts:
	homeassistant/components/climate/nest.py
2016-09-30 00:14:59 -07:00
Marcelo Moreira de Mello
39514be1f9 Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode (#3606)
* Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode

* Fixed target_temperature to return None when self.is_away_mode_on is True
2016-09-30 00:14:59 -07:00
Paulus Schoutsen
234f4449b0 Lint 2016-09-30 00:14:08 -07:00
Jeff Wilson
a89d036e26 Nest operation modes (#3609)
* Add ability to change Nest mode to all available options

* Make Nest state reflect current operation not current operation mode

* Update Nest sensor to use operation mode

* Fix linting

* Revert "Make Nest state reflect current operation not current operation mode"

This reverts commit 573ba028d8.

Conflicts:
	homeassistant/components/climate/nest.py
2016-09-29 23:44:14 -07:00
Marcelo Moreira de Mello
9ea030f42e Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode (#3606)
* Fixed issue #3574 - Temperature slider fixed on frontend on heat/cool mode

* Fixed target_temperature to return None when self.is_away_mode_on is True
2016-09-29 22:17:13 -07:00
Paulus Schoutsen
7ac8425099 Fix tests 2016-09-29 21:42:36 -07:00
Fabian Affolter
c000e74d0a Migrate to voluptuous (#3374) 2016-09-29 19:07:35 -07:00
Fabian Affolter
6632747543 Migrate to voluptuous (#3341) 2016-09-29 19:06:28 -07:00
Fabian Affolter
a7266ae6cf Check that no configuration is provided (#3553) 2016-09-29 19:02:22 -07:00
Paulus Schoutsen
4031f70665 Merge pull request #3603 from home-assistant/hotfix-0-29-4
Hotfix 0 29 4
2016-09-29 18:59:09 -07:00
Paulus Schoutsen
2b59409e52 Version 0.29.4 2016-09-29 18:57:33 -07:00
Dan Cinnamon
b41c795d34 Passing original config reference to load_platform. (#3602) 2016-09-29 18:56:09 -07:00
Pascal Vizeli
db56ed400d Bugfix voluptuous acer_projector (#3598) 2016-09-29 18:56:09 -07:00
Paulus Schoutsen
c603ffbe26 Fix voluptuous alexa config (#3596) 2016-09-29 18:56:09 -07:00
Dan Cinnamon
68028afb98 Passing original config reference to load_platform. (#3602) 2016-09-29 18:55:43 -07:00
Paulus Schoutsen
733120c577 Fix voluptuous alexa config (#3596) 2016-09-29 18:45:55 -07:00
Pascal Vizeli
01435f7f42 Bugfix voluptuous acer_projector (#3598) 2016-09-29 18:36:20 -07:00
Paulus Schoutsen
77c91c8a5e Merge pull request #3590 from home-assistant/hotfix-0-29-3
Hotfix 0 29 3
2016-09-29 09:09:51 -07:00
Paulus Schoutsen
a321c2f0d8 Version bump to 0.29.3 2016-09-29 09:07:19 -07:00
Pascal Vizeli
807daf8f5d Bugfix voluptuous for hue (#3589) 2016-09-29 09:07:00 -07:00
Pascal Vizeli
08bacd8e31 Bugfix voluptuous for hue (#3589) 2016-09-29 09:06:41 -07:00
Paulus Schoutsen
f79d762e66 Merge pull request #3585 from home-assistant/hotfix-29-2
Hotfix 29 2
2016-09-29 08:18:51 -07:00
Paulus Schoutsen
30aa67b789 Version bump to 0.29.2 2016-09-29 07:50:34 -07:00
Hugo Dupras
883e45c476 Netatmo: Hotfix for hass 0.29 (#3576)
Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-09-29 07:46:04 -07:00
Hugo Dupras
dd551cf056 Netatmo: Hotfix for hass 0.29 (#3576)
Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2016-09-29 07:45:54 -07:00
Fabian Affolter
47a3adb6b3 Remove duplicate port entry (fixes #3583) (#3584) 2016-09-29 07:45:39 -07:00
Fabian Affolter
7d86fb8c72 Remove duplicate port entry (fixes #3583) (#3584) 2016-09-29 07:45:25 -07:00
Greg Dowling
e87467cb20 Merge pull request #3577 from home-assistant/fix_loopenergy_exceptions
Loopenergy library - bump to catch more exceptions in poll thread.
2016-09-29 10:49:14 +01:00
pavoni
abd1213cc4 Bump library to catch more exceptions in poll thread. 2016-09-29 09:55:05 +01:00
Paulus Schoutsen
f3d838c448 Version bump to 0.29.1 2016-09-28 22:08:02 -07:00
Marcelo Moreira de Mello
574e3231d0 Fixed typo (#3569)
Sep 29 00:59:22 pi hass[21333]: if self.device.measurment_scale == 'F':
Sep 29 00:59:22 pi hass[21333]: AttributeError: 'Device' object has no attribute 'measurment_scale'
2016-09-28 22:07:40 -07:00
Marcelo Moreira de Mello
48ffdea31f Fixed typo (#3569)
Sep 29 00:59:22 pi hass[21333]: if self.device.measurment_scale == 'F':
Sep 29 00:59:22 pi hass[21333]: AttributeError: 'Device' object has no attribute 'measurment_scale'
2016-09-28 22:07:23 -07:00
Paulus Schoutsen
8e14da7454 Version bump to 0.29 2016-09-28 21:21:13 -07:00
Paulus Schoutsen
6e80581b30 Version bump to 0.30.0.dev0 2016-09-28 21:21:03 -07:00
Paulus Schoutsen
9780ea5c52 Version bump to 0.29 2016-09-28 21:20:47 -07:00
Paulus Schoutsen
c4d817146f Merge pull request #3486 from home-assistant/dev
0.29 - 🎈 anniversary edition
2016-09-28 20:59:50 -07:00
Fabian Affolter
f3caca6a06 Upgrade python-telegram-bot to 5.1.0 (#3525) 2016-09-28 00:08:24 -07:00
Fabian Affolter
d6c586bf42 Upgrade fuzzywuzzy to 0.12.0 (#3524) 2016-09-28 00:07:52 -07:00
Fabian Affolter
3b5142eb85 Upgrade dnspython3 to 1.14.0 (#3522) 2016-09-28 00:07:39 -07:00
Paulus Schoutsen
4b8bc90d16 Limit worker pool to 10 threads (#3560)
* Limit worker pool to 10 threads

* Comment evdev in requirements

* Allow skipping RFXtrx tests locally

* Fix worker pool size tests

* lol whut
2016-09-28 00:05:38 -07:00
Fabian Affolter
a084232cf5 Add link to error message (#3530) 2016-09-27 23:59:34 -07:00
Paulus Schoutsen
00e298206e Optimize template 2 (#3521)
* Enforce compiling templates

* Refactor templates

* Add template validator to Logbook service

* Some more fixes

* Lint

* Allow easy skipping of rfxtrx tests

* Fix template bug in AND & OR conditions

* add entities extractor

Conflicts:
	tests/helpers/test_template.py

* fix unittest

* Convert template to be async

* Fix Farcy

* Lint fix

* Limit template updates to related entities

* Make template automation async
2016-09-27 21:29:55 -07:00
happyleaves
559f63bfc3 bump limitlessled version 2016-09-27 16:30:26 -04:00
John Arild Berentsen
6694b0470e Update Climate slider code (#3394)
* Update ecobee to use only range setpoints

* Update nest to only use range setpoints

* Update demo to use only range for ecobee test device

* Update test

* Fetch unit from ecobee

* generic_thermostat did not have state

* generic_thermostat test update
2016-09-27 19:34:16 +02:00
Daniel Høyer Iversen
da6c09640c Merge pull request #3542 from home-assistant/rfxtrx_lib
update rfxtrx lib
2016-09-27 12:35:02 +02:00
Daniel
2505792ef3 update rfxtrx lib 2016-09-27 10:42:03 +02:00
Per Sandström
4c45e92116 modbus update, add error log instead of AttributeError exception (#3517) 2016-09-26 20:46:34 -07:00
Fabian Affolter
041c92699a Change line separator to lf instead of crlf (#3533)
* Change line separator to lf instead of crlf

* Update permissions

* Use LN instead of CRLF

* Remove BOM and use LN instead of CRLF
2016-09-26 20:26:32 -07:00
Dan Cinnamon
d761b000a5 Added a dispatch call to the envisalink sensor to also get partition status updates. 2016-09-26 20:13:41 -07:00
Pascal Vizeli
cae10cfe26 Use setup_component in tests v2 (#3537)
* setup_component - sun

* setup_component - updater

* setup_component - weblink
2016-09-26 23:20:36 +02:00
Martin Hjelmare
ea4f49f0a0 Fix mqtt cover retain and state (#3519)
* Platform schema had duplicate retain keys, which made it always set
	to default.
* Optimistic state changed was inverted, due to using integer position
	instead	of boolean.
* Add more tests for mqtt cover.
2016-09-26 00:53:38 +02:00
Pascal Vizeli
bbfd86dec3 Use setup_component in tests v1 (#3507)
* update unittests like #3414

* setup_component - splunk

* setup_component - statsd

* fix statsd & splunk unittest config values

* component_setup - device_sun_light_trigger

* setup_component - introduction

* component_setup - persistent_notification

* setup_component - logentries, mqtt eventstream

* fix unittest logentries
2016-09-25 23:15:21 +02:00
Paulus Schoutsen
0c0feda834 Pre-compile templates (#3515)
* Pre-compile templates

* Compile templates in numeric_state condition
2016-09-25 13:33:01 -07:00
Robbie Trencheny
b3d67a7ed9 Change notify target property to be a dictionary (#3501)
* Change notify target property to be a dictionary

* Make demo target properties unique and fix test to match behavior
2016-09-25 09:41:11 -07:00
Martin Hjelmare
986873834a Fix mysensors white value (#3508)
* Fix turning on mysensors light with white value attribute in kwargs.
* Fix import order in check_config.py.
2016-09-24 23:45:01 +02:00
Paulus Schoutsen
36921748ed Merge branch 'master' into dev
Conflicts:
	homeassistant/components/climate/ecobee.py
	homeassistant/components/cover/wink.py
	homeassistant/const.py
2016-09-24 01:03:56 -07:00
Paulus Schoutsen
b628fb088b Merge pull request #3503 from home-assistant/fix-platform-component-no-config
Allow platform components without config
2016-09-24 00:15:58 -07:00
Pascal Vizeli
4a5cc5ad3d Add new component for licence plates processing (OpenAlpr) (#3461)
* Add new component for licence plates processing (OpenAlpr)

* address balloobbot comments

* add to coveragerc

* move config from device to base

* fix lint

* move local api test to voluptous

* split render engine

* change cloud_api pip string & lint

* update requirements_all.txt

* fix lint

* update cloud_api url

* convert base64 byte string to string

* Update cloudapi / add configence / add state

* fix lint

* change state to high confidence plate

* fix cloudapi

* fix local api detection

* add wraper for local api

* fix lint

* fix wrong import

* fix HAAlpr name

* update ha-alpr without async

* support only eventbased requests with interval 0

* fix minor things

* fix lint

* fix lint2
2016-09-24 00:07:42 -07:00
Paulus Schoutsen
a1488b46f6 Fix zone being setup twice 2016-09-24 00:04:03 -07:00
Paulus Schoutsen
ac4e54c6ff Filter out falsey platform configs 2016-09-24 00:03:44 -07:00
Dan Smith
bac8ffdd52 Bump somecomfort to 0.3.2 (#3502)
This has fixes related to #3468
2016-09-23 22:15:05 -07:00
Robbie Trencheny
d3a012a536 Fix ENTITY_ID_ALL_COVERS format 2016-09-23 17:14:29 -07:00
Robbie Trencheny
e00a469828 Fix all_covers group friendly_name
It now matches other components (`all covers` instead of `all_covers`).
2016-09-23 17:13:12 -07:00
Johann Kellerman
1b9d867d60 Add domain to boolean (#3500) 2016-09-23 14:10:12 -07:00
Martin Hjelmare
8d0009b894 Fix mysensors required version for HVAC (#3499) 2016-09-23 22:07:06 +02:00
Fabian Affolter
ad2dea939b Fix lint issues (#3492) 2016-09-23 12:20:22 +02:00
Fabian Affolter
2ecbcac2b1 Fix PEP257 issues (#3491)
* Align test name with platform

* Fix PEP257 issues
2016-09-23 10:28:28 +02:00
Fabian Affolter
3d31d26b6c Fix typos (#3490) 2016-09-23 10:28:16 +02:00
Fabian Affolter
0065dc0cd7 Update links (#3488) 2016-09-23 10:28:05 +02:00
Daniel Høyer Iversen
e4c5f356e2 Merge pull request #3489 from home-assistant/rfxtrx_update
Update rfxtrx lib to 0.12
2016-09-23 09:25:15 +02:00
Johann Kellerman
9631179126 Use voluptuous for input_slider, input_boolean, input_select (#3256)
* Use voluptuous for input slider

* floats

* _setup_component

* Imperative mood

* CONFIG_SCHEMA

* None returns empty ensure_list

* allow_extra

* bool

* restore ensure_list behaviour
2016-09-23 00:12:11 -07:00
Johann Kellerman
de51cfbc07 Sorted yaml output for check_config (#3354)
* Consistent display of check_config dicts

* OrderedDict

* remove sorted
2016-09-23 00:10:19 -07:00
Open Home Automation
de4c63b437 Added more configuration parameters (#3479)
Upgraded miflora library to 0.1.9 (which is more stable)
2016-09-23 00:09:15 -07:00
Sytone
d5912f41fb Added play media to squeezebox (#3306)
* Added play media to squeezebox

The squeezebox component can now add a URI to an existing playlist or just over write it to force a stream to play.

* Cleaned up flake8 issues with formatting. 

Spacing... The end of the world! Fixed. Once day the tools will fix this on the fly, one day...

[x] ./homeassistant/components/media_player/squeezebox.py:307:1: W293 blank line contains whitespace
[x] ./homeassistant/components/media_player/squeezebox.py:366:1: W391 blank line at end of file
[x] ./homeassistant/components/media_player/squeezebox.py:366:1: W293 blank line contains whitespace

Updated SUPPORT_SQUEEZEBOX to add SUPPORT_PLAY_MEDIA

[x] ./homeassistant/components/media_player/squeezebox.py:13:1: F401 'homeassistant.components.media_player.SUPPORT_PLAY_MEDIA' imported but unused

* Updates from review

Updated the comments to indicate they are developer / API comments and not for end users.
Marked the private functions with a leading underscore (_)

* Fixed Lint issues. 

202ERROR: InvocationError: '/home/travis/build/home-assistant/home-assistant/.tox/lint/bin/flake8'

203lint runtests: commands[1] | pylint homeassistant

204************* Module homeassistant.components.media_player.squeezebox

205C:322, 0: Trailing whitespace (trailing-whitespace)
2016-09-23 00:05:33 -07:00
Daniel
03b2c48d45 Update rfxtrx to 0.12 2016-09-23 09:04:57 +02:00
irvingwa
65b1a731ca Added check for channel in kodi media player to play channel from PVR. (#3475)
* Added check for channel in kodi media player to play channel from PVR.

* test
2016-09-22 23:50:07 -07:00
kaustubhphatak
7625aae373 Add support for mysensors HVAC device (#3405)
* Added Support for mysensnors Climate/HVAC device

* Added Support for mysensnors-hvac device:fix pylint error

* Added Support for mysensnors-hvac device:fix pylint error2

* Fixed Issues in code as per review comments

* Fixed Linter Errors

* Fixed Linter Errors:2

* Fixed Linter Errors:2

* Fixed Linter Errors

* Fixed Linter Errors

* Fixed Linter Errors

* Added Support for MySensors HVAC| Fixed Review Comments| Removed Additional Comments

* Added Support for MySensors HVAC| Fixed Review Comments Itr2

* Changes to correctly support devices with both high and low bound temperatures

* Changed to optimize the code
2016-09-22 23:47:40 -07:00
Paulus Schoutsen
8251039ca4 Fix nmap config (#3482) 2016-09-22 08:44:18 -07:00
Pascal Vizeli
3418d03e69 convert first to string befor matching (#3476) 2016-09-22 00:03:32 -07:00
tinglis1
f1caf3f2b5 Bom weather current component (#3370)
* add bom_weather_current component

* wrong fork

* wrong fork

* Code tidy up

- fixed order of imports
- changes some of the units to be standardised

* Rename bom_weather_current.py to bom.py

* pylint changes

* lint formatting changes

* formatting

* fix logger string variable formatting

* Update .coveragerc
2016-09-21 19:26:28 +02:00
Paulus Schoutsen
3dea4be2cc Fix missing commits from 0.28
Add missing commits to master
2016-09-21 08:52:30 -07:00
Paulus Schoutsen
8f9fea37b2 Merge branch 'master' into potential-master-fix 2016-09-21 08:50:53 -07:00
Teagan Glenn
380993f2ca Automatic polling (#3360)
* Test updating automatic

* Scan interval

* Schedule scan every time delta

* Pass around has

* Recursive issue

* Method invocation

* Oops

* Set up poll

* Default argument value

* Unused import

* Semicolon

* Fix tests

* Linting

* Unneeded throttle as it's handled by time event

* Use track time change event listener

* Disable lint rule

* Attribute removed - removing test

* Debug instead of info

* Unused import
2016-09-21 08:34:22 -07:00
Paulus Schoutsen
5fd93e8d80 Version bump to 0.28.2 2016-09-21 08:34:22 -07:00
Nick Vella
bc9d2586c6 Add open/closed state for open_cover and close_cover in SERVICE_TO_STATE (#3180)
* Add open/closed state mapping for open_cover and close_cover

* Add 'open', 'closed' for open/close_cover_tilt

* Revert "Add 'open', 'closed' for open/close_cover_tilt"

This reverts commit e45582d439.
2016-09-21 08:34:22 -07:00
John Arild Berentsen
ad7683470a Bugfix ecobee: inverted high and low temps and enforce int to temps (#3325)
* inverted high and low temps

* Looks like somethings are mixed up

* Added debugentires

* Added debugentires 2

* Enforce int on temperatures
2016-09-21 08:34:22 -07:00
John Arild Berentsen
329474d3e3 Missing garage door detection (#3349) 2016-09-21 08:34:22 -07:00
Pascal Vizeli
b7430d939d Bugfix voluptuous on recorder (#3350) 2016-09-21 08:34:22 -07:00
David-Leon Pohl
e5af126fae Bugfix pilight component (#3355)
* BUG Message data cannot be changed thus use voluptuous to ensure format

* Pilight daemon expects JSON serializable data

Thus dict is needed and not a mapping proxy.

* Add explanation why dict as message data is needed

* Use more obvious voluptuous validation scheme

* Pylint:  Trailing whitespace
2016-09-21 08:34:22 -07:00
Paulus Schoutsen
9aff839925 Version bump to 0.28.1 2016-09-21 08:34:22 -07:00
Paulus Schoutsen
287f9c9bda Bugfix group order (#3323)
* Add ordered dict config validator

* Have group component use ordered dict config validator

* Improve config_validation testing

* update doc string config_validation.ordered_dict

* validate full dict entries

* Further simplify ordered_dict validator.

* Lint fix
2016-09-21 08:34:22 -07:00
Teagan Glenn
a6673f6741 Automatic Device Tracker Bug Fix (#3330)
* Iterate over items

* Pass display name as host name
2016-09-21 08:34:22 -07:00
John Arild Berentsen
784cf0c4bd Revert only add 1 device (#3324) 2016-09-21 08:34:22 -07:00
Marcelo Moreira de Mello
5966c46a67 Fixed voluptuous to accept string instead positive_int for CODE on Simplisafe (#3310) 2016-09-21 08:34:22 -07:00
Teagan Glenn
b9992a9914 UOM is a list - not a string. (#3469) 2016-09-21 08:03:26 -07:00
Dan Cinnamon
edf812c0ea Envisalink Fixes + Enhancements (#3460)
* Added the ability to trigger the alarm.

* Bump version of pyenvisalink to 1.3

* Fixed an issue where the panic_type was not passed to the sub-components properly.

* Bump pyenvisalink version, and make default panic mode = police.

* Pass in event loop to pyenvisalink.

* Made the components play nicely with asyncio.

* Bump pyenvisalink to 1.6

* Bump up pyenvisalink, and better handle synchronous setup.
2016-09-20 23:51:10 -07:00
Hugo Dupras
a310599a03 Add specific icon for forecast.io sensors (#3465) 2016-09-20 22:40:10 -07:00
Simon Szustkowski
4c625d09aa Add the ability to manually specify a Yamaha AVR via it's IP address (#3451)
* Added the possibility to manually specify a Yamaha Receiver

* Added the possibility to manually specify a Yamaha AVR

* Using string formatting

* Hostname checks for None now

* Do not use add_devices for each if-branch separately

* Fixed linting
2016-09-20 22:26:43 -07:00
Fabian Affolter
0335f88e61 Migrate to voluptuous (#3342) [Breaking Change] 2016-09-20 22:21:06 -07:00
Lewis Juggins
769bc37150 Add additional fields to influx (#3462) 2016-09-20 22:20:05 -07:00
Marc Pabst
138205a019 Adding support for a white value (#3338)
* Update __init__.py

addedattribute "WHITE_VALUE" to improve support for RGBW devices

* Update services.yaml

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

shortened line

* Update __init__.py

* Update __init__.py

* Add mysensors RGBW and light tests

* Activate support for mysensors RGBW devices with support for
	white_value attribute.
* Add white_value support in light demo platform.
* Add tests for white_value and more for light component.
* Add tests for light demo platform.
* Fix import order in check_config.
2016-09-20 21:26:40 -07:00
wokar
e891f1a260 Filter entities from logbook (#3426)
* o added ability to exclude entities or domains from logbook
o exclude hidden entities

* fixed remaned configuration key

* - filter the events before they get passed to humanify, to separate concerns
- instead of looking at customize, look for the hidden attribute on the state change events
- access to configuration defaults to an empty list - no need to check

* - filter only events of type EVENT_STATE_CHANGED
- improve config handling

* added unit tests to cover all filter cases and logbook message creation
2016-09-20 20:07:26 -07:00
Milas Bowman
eb1871dc5b Allow pairing with Harmony Hub (#3467)
The real Hue hub responds to both `/api` and `/api/`. For greater
compatibility, the view now responds to both using `extra_urls`.
2016-09-20 20:05:14 -07:00
Fabian Affolter
f75b0a99d9 Use voluptuous for Hue (#3340)
* Migrate to voluptuous

* Change name used in test
2016-09-20 12:35:10 +02:00
Christian Brædstrup
81ebdadcec D-link switch library bump and error handling for W110 devices (#3386) (#3425) 2016-09-20 00:10:15 -07:00
sam-io
de5bd26050 Email (#3421)
* Added email component

* added email sensor component

* added doc string to test class

* fixed lint error

* fixed lint error

* rename of email component

* added another block as test fails on CI

* added retry to multi email test

* added delay to retry

* added to .coveragerc

* removed sleep from tests and fixed up stale comments
2016-09-20 00:09:14 -07:00
Lewis Juggins
54248863b3 Use uvloop for asyncio policy (#3417) 2016-09-20 00:06:33 -07:00
William Scanlon
43c395232a Sensor updates (#3410) 2016-09-20 00:05:54 -07:00
Paulus Schoutsen
68835c4b4b Update frontend 2016-09-19 23:56:40 -07:00
Paulus Schoutsen
be68fe0d85 Move worker pool monitoring to be time based instead of add_job based. (#3439)
* Move worker pool monitoring to be time based instead of add_job based.

* Stub out worker pool monitor during tests

* Add test for monitor worker pool.

* Improve naming

* Test stop_monitor coroutine

* Add async_create_timer test

* Finish rename create_timer
2016-09-19 23:39:49 -07:00
Micha LaQua
d31f6bc3f0 Allow hiding automation entities from UIs (#3442)
* Allow hiding automation entities from UIs

* Flake8 fixes: Allow hiding automation entities from UIs

* Automation: Rework hide entity feature

 * Refactor keyword 'hidden' to 'hide_entity' to avoid ambiguity
 * Migrate hide_entity subsetting to Voluptuous
2016-09-19 23:39:07 -07:00
Marcelo Moreira de Mello
ae1b69430e Added support to Yahoo Finance to track the stock market within Home Assistant (#3446)
* Added support to Yahoo Finance to track the stock market within Home Assistant

* Fixed pylint issues

* Fixed formatting issues

* Fixed pep257 issues

*   - Fixed URL link
  - Added attributes for Yahoo Finance

* Removed price sales ATTR

* Fixed lint and flake8 issues. Added attribution to Yahoo! per https://developer.yahoo.com/attribution/
2016-09-19 23:38:10 -07:00
Fabian Affolter
a998846961 Add unit to comment (#3452) 2016-09-19 23:17:52 -07:00
Paulus Schoutsen
9ac39df33f Fix logger config validation (#3459) 2016-09-19 21:12:56 -07:00
Paulus Schoutsen
fa2ce366de Update frontend 2016-09-19 21:12:31 -07:00
Teagan Glenn
35603268ca Isy fixes (#3457)
* Fix binary sensor

* Add 'stopped' to states

* Add '%' to states for light

* ISY light brightness support

* Method case

* Z-Wave unit 51 is a light
2016-09-19 20:16:51 -07:00
Paulus Schoutsen
d6ad4bc22b Remove validate_config (#3448) 2016-09-18 21:40:49 -07:00
Paulus Schoutsen
87fe83dcb9 Fix slow tests (#3444)
* Fix RFXtrx tests

* Report slow tests on CI

* Minor rfxtrx clean up

* rfxtrx test tweak
2016-09-18 21:40:37 -07:00
Ben Bangert
256062fd99 Fix test shutdown to ensure loop/threads are clean. (#3447)
* Fix test shutdown to ensure loop/threads are clean.

We now ensure the loop is closed, it has completed, and the
executer has completed. This ensure all threads are freed
up with any test calling hass.stop().

* Fix lint issue with run_loop
2016-09-18 20:35:58 -07:00
Paulus Schoutsen
8a99ce78c2 Better Hue error reporting (#3443) 2016-09-18 19:59:38 -07:00
Paulus Schoutsen
9a87e5e336 Feature/voluptuous influxdb (#3441)
* Migrate to voluptuous

* Fix voluptuous influxdb
2016-09-18 15:32:18 -07:00
Paulus Schoutsen
da8994e4b5 Migrate camera.uvc to voluptuous (#3440) 2016-09-18 14:22:32 -07:00
Josh Nichols
b34101b277 Update Ecobee state after making changes to climate (#3436)
* Update Ecobee state after making changes to climate

Without this, climate and sensor state will take up to 3 minutes
(the MIN_TIME_BETWEEN_UPDATES on its update throttle) to update in
the interface, which makes it more difficult to do automation around the
state.

* Use a boolean instance variable that update can check, rather than always calling update
2016-09-18 13:20:06 -07:00
Daniel Høyer Iversen
11c07440fe Accept login from approved ips without password (#3427) 2016-09-18 10:20:19 -07:00
John
2c43d6718b Update pyenvisalink to latest version (#3435) 2016-09-18 10:19:32 -07:00
Pascal Vizeli
de4cc5034e Add toggle service to input_boolean (#3432) 2016-09-18 10:18:44 -07:00
Paulus Schoutsen
c89a77dc74 Update frontend 2016-09-18 00:04:43 -07:00
Phil Hawthorne
91e36f380b Add PyBluez to Dockerfile (#3423)
* Add PyBluez to Dockerfile

Adds PyBluez to Dockerfile so people using Docker can run Bluetooth
devices

* Remove pip install of pybluez

Pybluez will be installed automatically when the Bluetooth device
tracker is enabled
2016-09-17 23:57:12 -07:00
Johann Kellerman
169f054c6c Use voluptuous for nmap, tplink, thomson device trackers (#3124)
* Use Voluptuous for nmap, tplink, thomson device_trackers

* Fix logic
2016-09-17 23:44:15 -07:00
joyrider3774
9184773f8f Emoncms feeds sensor component (#3258)
* Add Initial version for emoncms feeds sensor

* flake8 test fixes

* pylint test fixes

* - fix bug with include_feed_id_names not assigning the name to the  element in the same postion as found in include_feed_id
- a few structure changes to have less nesting (pylint fix)
- minor other changes

* update .coveragerc

* voluptuous fixes:
- exclude_feed_id and include_feed_id Exclusive group so that only one (or none) can be specified at once
- id must be positive int
- exclude_feed_id and include_feed_id must be positive int

* Fix comment so it refers to the documentation

* use string formatting

* Remove outer try

* clean up sensors.append calls
(break them out for loop)

* multiple changes like:
- rename config value "include_feed_id" to "include_only_feed_id"
- rename config value "include_feed_id_names" to "sensor_names"
- renamed config value sensor_names is now a dictionary an can also be used when renamed config value "include_only_feed_id" is not specified
- Set default value for scan_interval using the config validation
- blank lines between default, 3rd party and own imports
- removed homeassistant.util import, it was not needed anymore

* fix extended voluptuous schema
scan_interval should not be extended to PLATFORM_SCHEMA as it was already in the schema by config_validation helper so i should not add / change it. It was also causing problems reading the value from the config.

* Use Home Assistant polling

* remove statement that can never happen

* Guard clause

* Reduce instance variables
2016-09-17 23:34:24 -07:00
Paulus Schoutsen
e19a092934 Update Docker to use Python 3.5 (#3430) 2016-09-17 23:32:11 -07:00
deisi
1c706834e0 Recieve signals from a keyboard and use keyboard as a remote control (#3305) 2016-09-17 23:31:27 -07:00
Fabian Affolter
04d31e4ef4 Use voluptuous for RPi GPIO (#3371)
* Migrate to voluptuous

* Remove the check for lists
2016-09-17 23:28:37 -07:00
Fabian Affolter
2b7d1fe20d Use voluptuous for logger (#3375)
* Migrate to voluptuous

* No list for configuration check
2016-09-17 23:23:45 -07:00
Daniel Høyer Iversen
a90049568e Fix issue #3401 weblink (#3402) 2016-09-17 23:21:24 -07:00
Pascal Vizeli
534f56a3e2 Bugfix if ffmpeg is not present on config / Update ha-ffmpeg 0.13 (#3418) 2016-09-17 22:57:18 -07:00
Lewis Juggins
81928b1a6b Update pychromecast (#3416) 2016-09-17 22:51:40 -07:00
Paulus Schoutsen
325220e009 Make track_point_in_utc_time more async (#3428)
* Make track_point_in_utc_time more async

* Make track_point_in_time async friendly
2016-09-17 19:51:18 -07:00
Paulus Schoutsen
aca375c312 Asyncio event helpers (#3415)
* Automation - Event: Use coroutine

* Convert event helpers to coroutine

* Fix linting

* Add hass.async_add_job

* Automation - Event to use async_add_job
2016-09-17 18:28:01 -07:00
Paulus Schoutsen
4076ccf639 Use setup_component in tests (#3414)
* Alarm Control Panel Manual - use setup_component

* Update automation - zone tests

* Update climate - demo tests

* Update climate - generic thermostat tests

* Update cover - command line tests

* Update cover - demo tests

* Update device tracker tests

* Update device tracker - owntracks tests

* Update fan - demo tests

* Update garage door - demo tests

* Update light tests

* Update lock - demo tests

* Update media player - demo tests

* Update notify - command line tests

* Update notify - demo tests

* Update notify - file tests

* Update notify - group tests

* Update sensor - mfi tests

* Update sensor - moldindicator tests

* Update sensor - mqtt room tests

* Update switch - command line

* Update switch - flux

* Update switch tests

* Update scene tests

* Fix wrong default port for mfi switch
2016-09-17 10:29:58 -07:00
Jeff Wilson
d7452f9d5d Add ability to set fan made to Nest climate component (#3399)
* Add ability to set fan made to Nest climate component

* Use constants for fan values

* Use STATE_ON from cost

* Fix lint error
2016-09-15 21:01:32 -07:00
Paulus Schoutsen
c23ad3e285 Fix zones (#3413) 2016-09-15 19:40:18 -07:00
Greg Dowling
0a6f496425 Add support for Vera covers. (#3411) 2016-09-15 20:47:03 +02:00
Pascal Vizeli
07a92e8ac3 Split ffmpeg to compoment (#3396)
Add an optional extended description…
2016-09-15 14:35:40 +02:00
Dan
d1b08824e8 Implemented onkyo reconnect (#3061)
* Implemented onkyo reconnect

Connection object is cleared after a failed command. It will be
automatically recreated upon the next command running. This should allow
for failed connections to be restored.

* Remove reduntant error catching

* Run all update commands with command wrapper.

* Handle errors better

* Removed unused global

I have no idea how that got there.
2016-09-14 18:21:01 -07:00
gross1989
c693db49b3 Nuimo controller component based on SDK (#3039) 2016-09-14 18:20:49 -07:00
Dan
b58976bc36 Add operation_list to radiotherm (#3393)
* Add operation_list to radiotherm

* Use constants
2016-09-14 18:14:39 -07:00
Eric Jansen
70a79efb77 Bugfix: incorrectly inverted value when setting cover position (#3376) 2016-09-14 17:41:45 -07:00
chrom3
982a0bc195 Kodi notification platform (#3403) 2016-09-14 21:54:45 +02:00
Daniel Høyer Iversen
9ad592e606 Merge pull request #3395 from home-assistant/style_fix
fix style in tellstick sensor
2016-09-14 12:12:45 +02:00
Daniel
6f840de1d2 fix style in tellstick sensor 2016-09-14 11:58:26 +02:00
Fabian Affolter
d029861c93 Use voluptuous for Tellstick (#3367)
* Migrate to voluptuous

* Update tellstick.py
2016-09-13 23:29:15 -07:00
Fabian Affolter
c6fa07d059 Migrate to voluptuous (#3372) 2016-09-13 23:20:58 -07:00
Fabian Affolter
782838af56 Use voluptuous for Zone (#3377)
* Migrate to voluptuous

* Zone: Remove unneeded latitude/longitude check
2016-09-13 23:13:10 -07:00
Jeff Wilson
7724cb9eb4 Fix octoprint sensor (#3385)
* Fix non-temperature sensors for octoprint

* Fix double space in octoprint temperature names

* Fix tox linting errors
2016-09-13 23:10:49 -07:00
Pascal Vizeli
b78e98702a Update yahooweather 0.8 / change request time (#3352) [Breaking change] 2016-09-13 23:04:26 -07:00
Fabian Affolter
727b756054 Use voluptuous for KNX (#3345)
* Migrate to voluptuous

* Make host optional and set default
2016-09-13 23:03:30 -07:00
Fabian Affolter
4791b5679e Use voluptuous for iTunes (#3344)
* Migrate to voluptuous

* Add support for SSL/TLS
2016-09-13 23:01:51 -07:00
Fabian Affolter
79bff0fc57 Migrate to voluptuous (#3337) 2016-09-13 22:52:51 -07:00
Fabian Affolter
1e8cf8c1b7 Upgrade PyMata to 2.13 (#3335) 2016-09-13 22:52:11 -07:00
Fabian Affolter
26a118e75d Upgrade cherrypy to 8.1.0 (#3334) 2016-09-13 22:51:55 -07:00
Fabian Affolter
e7f9fdca67 Upgrade sqlalchemy to 1.0.15 (#3333) 2016-09-13 22:51:13 -07:00
Open Home Automation
0c7c85dbfe Miflora (#3053)
* First version of the MiFlora sensor (not yet finished)

* First workign version

* Added some documentation
Get name from sensor, if not defined

* Ignore IOError

* Added force_update option

* Updated comments

* Renamed fertility to conductivity (what it really is)

* MiFlora library update

* Updated helper files

* Formatting

* Fixed pylint errors

* Removed default from monitored conditions

* Removed KeyError handling as a KeyError should never be raised

* Added a return when no data is received

* emoved unnecessary return statement

* Changed default name

* Changes quotes and string operation ( @Teagan42 )

* - number of samples for median calculation is now configurable
- set state to None if no data could be polled from sensor

* Bugfix in library
more logging

* Fixed miflora version number
2016-09-13 22:37:57 -07:00
Nolan Gilley
2c01a67446 short sleep (#3115) 2016-09-13 21:46:11 -07:00
Fabian Affolter
68def21615 Use voluptuous for Yamaha receiver (#3210)
* Migrate to voluptuous

* Add missing configuration variables
2016-09-13 21:39:03 -07:00
Rob Johnson
7528da455c Vera Thermostat Support (#3247)
* vera thermostat & climate support

* disable abstract

* code review changes

* fix

* fix build

* remove old method
2016-09-13 21:36:49 -07:00
Heiko Rothe
8da85d7a91 Added away timeout setting for idle devices (#3321) 2016-09-13 20:34:59 -07:00
Fabian Affolter
ac5647a30e Use voluptuous for statsd (#2928)
* Migrate to voluptuous

* Update tests
2016-09-13 18:22:30 -07:00
David Baumann
cb3ab1e873 Implement Sensor for KNX Platform (#2911)
* Added Sensor Support for KNX Devices

Added Sensor for KNX Group Addresses
- Temperature
- Wind Speed
- Illuminance(LUX)

Mostly to fetch from a KNX Wetterstation

* Some pylint,flake8 fixes

* Pydoc Fixes

* Fix Coverage Ordering

* Refactor KNX Sensor

Refactor to Idea from @usul27 and added Minimum Maximum

* Removed Measurement Untis from const.py

Removed needed Measurement Units from const.py and add it to
sensor\knx.py

* Change .coveragerc

* Add as Requested from @Teagan42 the new Type Names

Additional add CONF_MINIMUM and CONF_MAXIMUM

* Added Changes as Requested from @Teagan42

* Fixed the Merge Conflict, Hopefully i done it rigth :-)

* Fixed Styling
2016-09-13 18:21:43 -07:00
Johann Kellerman
afc527ea55 Fix tests (#3387) 2016-09-13 18:17:51 -07:00
wokar
165362da0c fixed link constructor to show icon again (#3388) 2016-09-13 18:15:19 -07:00
Pascal Vizeli
1697a8c774 SleepIQ component with sensor and binary sensor platforms (#3390)
Original from #2949
2016-09-14 00:11:50 +02:00
Eric Clymer
de2eed3c9f Fix saving push_notification.conf as suggested by @robbiet480 (#3382) 2016-09-13 14:22:55 -07:00
Per Sandström
ca646c08c2 Modbus component refactoring - sensors and switches (#3297) 2016-09-13 22:47:44 +02:00
Pascal Vizeli
e4f4e91096 Bugfix auto/manual mode change (#3384) 2016-09-13 21:43:37 +02:00
Pascal Vizeli
812dc99073 fix xbox live entity id (#3368) 2016-09-13 21:27:43 +02:00
John Arild Berentsen
898cf1b352 zxt_120 set temperature did not update on setpoint (#3380) 2016-09-13 20:26:44 +02:00
Fabian Affolter
bba75bf6c3 Add Simplepush notifications (#3336) 2016-09-13 19:26:47 +02:00
John Arild Berentsen
efbc378226 Fix temp conversion for nest setpoint (#3373)
* Fix temp conversion for nest setpoint

* DEbug
2016-09-13 19:16:17 +02:00
Fabian Affolter
8ba952ee0e Use voluptuous for SCSGate (#3265)
* Migrate to voluptuous

* Extend platforms
2016-09-13 07:23:53 +02:00
Teagan Glenn
8189ec2c8d Automatic polling (#3360)
* Test updating automatic

* Scan interval

* Schedule scan every time delta

* Pass around has

* Recursive issue

* Method invocation

* Oops

* Set up poll

* Default argument value

* Unused import

* Semicolon

* Fix tests

* Linting

* Unneeded throttle as it's handled by time event

* Use track time change event listener

* Disable lint rule

* Attribute removed - removing test

* Debug instead of info

* Unused import
2016-09-12 19:59:34 -07:00
beepmill
7f6fb95afd Ignore desktop.ini (Windows Explorer) (#3363) 2016-09-12 19:45:39 -07:00
Paulus Schoutsen
609d7ebea5 Migrate core from threads to async awesomeness (#3248)
* Add event loop to the core

* Add block_till_done to HA core object

* Fix some tests

* Linting core

* Fix statemachine tests

* Core test fixes

* fix block_till_done to wait for loop and queue to empty

* fix test_core for passing, and correct start/stop/block_till_done

* Fix remote tests

* Fix tests: block_till_done

* Fix linting

* Fix more tests

* Fix final linting

* Fix remote test

* remove unnecessary import

* reduce sleep to avoid slowing down the tests excessively

* fix remaining tests to wait for non-threadsafe operations

* Add async_ doc strings for event loop / coroutine info

* Fix command line test to block for the right timeout

* Fix py3.4.2 loop var access

* Fix SERVICE_CALL_LIMIT being in effect for other tests

* Fix lint errors

* Fix lint error with proper placement

* Fix slave start to not start a timer

* Add asyncio compatible listeners.

* Increase min Python version to 3.4.2

* Move async backports to util

* Add backported async tests

* Fix linting

* Simplify Python version check

* Fix lint

* Remove unneeded try/except and queue listener appproriately.

* Fix tuple vs. list unorderable error on version compare.

* Fix version tests
2016-09-12 19:16:14 -07:00
Nick Vella
24f1bff7f1 Add open/closed state for open_cover and close_cover in SERVICE_TO_STATE (#3180)
* Add open/closed state mapping for open_cover and close_cover

* Add 'open', 'closed' for open/close_cover_tilt

* Revert "Add 'open', 'closed' for open/close_cover_tilt"

This reverts commit e45582d439.
2016-09-12 18:31:44 -07:00
David-Leon Pohl
6959407dfd Bugfix pilight component (#3355)
* BUG Message data cannot be changed thus use voluptuous to ensure format

* Pilight daemon expects JSON serializable data

Thus dict is needed and not a mapping proxy.

* Add explanation why dict as message data is needed

* Use more obvious voluptuous validation scheme

* Pylint:  Trailing whitespace
2016-09-12 18:28:11 -07:00
John Arild Berentsen
14b6f9d927 Missing garage door detection (#3349) 2016-09-12 18:23:18 -07:00
Pascal Vizeli
d7e3fa22eb Bugfix voluptuous on recorder (#3350) 2016-09-12 18:21:35 -07:00
Pascal Vizeli
a9ef8d8568 Update ha-ffmpeg version 0.12 and add tests (#3301)
* update ha-ffmpeg version 0.12 and add tests

* change error logging
2016-09-12 22:52:49 +02:00
John Arild Berentsen
a69c575dab Bugfix ecobee: inverted high and low temps and enforce int to temps (#3325)
* inverted high and low temps

* Looks like somethings are mixed up

* Added debugentires

* Added debugentires 2

* Enforce int on temperatures
2016-09-12 17:40:46 +02:00
Heiko Rothe
240cb9b8f0 Minor naming fix (#3318) 2016-09-12 16:58:49 +02:00
Fabian Affolter
ac063f8e61 Fix typo (#3343) 2016-09-12 16:19:46 +02:00
Fabian Affolter
c028e1fc6f Update ordering, constants, and callback name (light.*) (#3339)
* Extend schema

* Update ordering

* Add line breaks

* Align callback name with other platforms

* ALign callbackname with other platforms

* Update callback name to match other platforms

* Update callback name

* Update callback name
2016-09-12 15:52:22 +02:00
Richard Cox
44681ebd55 Slack notification optional username / icon (#3314)
* Slack notification optional username / icon

* Small bugfix to handle defaults better

* Dedup'ing code
2016-09-12 09:49:26 +02:00
clach04
c90cc77c41 bluetooth_le_tracker clarify header with LE (#3328) 2016-09-12 07:56:12 +02:00
Paulus Schoutsen
838b09bb8f Bugfix group order (#3323)
* Add ordered dict config validator

* Have group component use ordered dict config validator

* Improve config_validation testing

* update doc string config_validation.ordered_dict

* validate full dict entries

* Further simplify ordered_dict validator.

* Lint fix
2016-09-11 22:25:01 -07:00
Paulus Schoutsen
fa4b253871 Comment out pyuserinput in requirements_all (#3307)
* Comment out pyuserinput in requirements_all

* Ignore import error for keyboard component
2016-09-11 21:59:48 -07:00
Teagan Glenn
360a650370 Automatic Device Tracker Bug Fix (#3330)
* Iterate over items

* Pass display name as host name
2016-09-11 21:53:05 -07:00
John Arild Berentsen
26abe83be5 Revert only add 1 device (#3324) 2016-09-11 21:46:14 -07:00
Johann Kellerman
dba78b02da Add voluptuous to locative (#3254) 2016-09-12 01:12:28 +02:00
Fabian Affolter
515c4773f3 Use voluptuous for netatmo (#3287)
* Migrate to voluptuous

* Switch back to archive (reverting #3285)
2016-09-11 21:27:58 +02:00
Teagan Glenn
05a3b610ff Add ISY programs and support for all device types (#3082)
*  ISY Lock, Binary Sensor, Cover devices, Sensors and Fan support
* Support for ISY Programs
2016-09-11 20:18:53 +02:00
William Scanlon
8c7a1b4b05 Merge pull request #3218 from w1ll1am23/full_color_support_wink_osram
Add full color support for Osram Lightify bulbs with Wink
2016-09-11 10:55:19 -04:00
William Scanlon
58c0990508 Convert rgb to hsb for Wink Osram light 2016-09-11 10:45:04 -04:00
Fabian Affolter
11396a221e Extend schema (#3278) 2016-09-11 11:45:38 +02:00
Fabian Affolter
ab826eef0d Migrate to voluptuous (#3276) 2016-09-11 11:38:43 +02:00
Fabian Affolter
d20b4c17a2 Migrate to voluptuous (#3277) 2016-09-11 11:30:27 +02:00
Fabian Affolter
1b77b2c3a3 Migrate to voluptuous (#3280) 2016-09-11 11:19:10 +02:00
Fabian Affolter
f5df5615be Migrate to voluptuous (#3282) 2016-09-11 10:04:07 +02:00
Fabian Affolter
cc99d266b7 Use constants and update ordering (#3275) 2016-09-11 10:01:46 +02:00
Fabian Affolter
aed1348411 Use constants and update ordering (#3274) 2016-09-11 09:25:19 +02:00
Fabian Affolter
f6bc63092c Migrate to voluptuous (#3281) 2016-09-11 09:24:25 +02:00
Fabian Affolter
d48ed41122 Use constants (#3284) 2016-09-11 09:24:07 +02:00
Fabian Affolter
cce3e284d7 Use voluptuous for Neurio (#3289)
* Migrate to voluptuous

* Migrate to voluptuous
2016-09-11 09:23:33 +02:00
Fabian Affolter
ac9151af54 Migrate to voluptuous (#3292) 2016-09-11 09:22:49 +02:00
Fabian Affolter
f341974b8b Migrate to voluptuous (#3290) 2016-09-11 09:22:08 +02:00
Fabian Affolter
78313c793c Migrate to voluptuous (#3298) 2016-09-11 09:21:16 +02:00
Fabian Affolter
3f4d30c8da Add timeout (#3304) 2016-09-11 09:21:01 +02:00
Marcelo Moreira de Mello
9bbe7be684 Fixed voluptuous to accept string instead positive_int for CODE on Simplisafe (#3310) 2016-09-11 09:07:13 +02:00
Robbie Trencheny
4748e7f7e9 Version bump 2016-09-10 18:23:21 -07:00
Robbie Trencheny
e8f8ea080b Remove dev tag 2016-09-10 18:13:27 -07:00
Paulus Schoutsen
b8251b084a Fix bloomsky platform discovery (#3303) 2016-09-10 09:12:24 -07:00
Per Sandström
54a17f5d98 modbus write registers service (#3252) 2016-09-10 08:17:28 -07:00
Mal Curtis
8438001942 Fix TP-Link Archer C7 long passwords (#3225)
* Fix tplink C7 long passwords

Fixes an issue where passwords longer than 15 chars could not log in to Archer C7 routers.

* Truncate in correct place

* Add comment about TP-Link C7 pass truncation

* Fix lint error

* Truncate comment at 79 chars not 80
2016-09-10 08:08:51 -07:00
Lewis Juggins
de150ecbc9 Hotfix for #3100 (#3302) 2016-09-10 07:36:55 -07:00
Robbie Trencheny
d466bae244 Update frontend 2016-09-09 14:23:03 -07:00
Pascal Vizeli
e87da765c5 cleanup Homematic code (#3291)
* cleanup old code

* cleanup round 2

* remove unwanted platforms
2016-09-09 19:33:12 +02:00
John Arild Berentsen
ba28208106 Added stuff for support range setting (#3189) 2016-09-09 11:06:53 -06:00
Fabian Affolter
545329174d Pi-Hole statistics sensor (#3158)
* Add Pi-Hole sensor

* Update docstrings and remove print()

* Use None for payload
2016-09-09 17:10:46 +02:00
Fabian Affolter
5881f6000e Point to master till archive is back (#3285) 2016-09-09 16:53:18 +02:00
Daniel Høyer Iversen
3411c4c7c3 Merge pull request #3260 from home-assistant/issue_#3250
issue #3250
2016-09-09 09:33:26 +02:00
Fabian Affolter
5bf66cae1f Use voluptuous for Device Sun Light Trigger (#3105)
* Migrate to voluptuous

* Use default
2016-09-09 09:06:24 +02:00
Brian Karani Ndwiga
53c8115f82 Updated braviatv's braviarc version to 0.3.5 (#3271) 2016-09-09 08:38:32 +02:00
Fabian Affolter
911231afc1 Use constants and update ordering (#3266) 2016-09-09 08:37:30 +02:00
Lewis Juggins
44f5a66b66 Add additional template for custom date formats (#3262)
I can live with a few visual line breaks 🐬
2016-09-08 18:49:02 -06:00
Fabian Affolter
ee6c83f569 Use constants and update ordering (#3267)
🐬
2016-09-08 18:34:55 -06:00
Fabian Affolter
fb0232429e Use constants and update ordering (#3268)
🐬
2016-09-08 18:32:32 -06:00
Nolan Gilley
1cace5782c fastdotcom from pypi (#3269) 2016-09-08 18:26:50 -06:00
Fabian Affolter
02848b3949 Use voluptuous for nx584 alarm (#3231)
* Migrate to voluptuous

* Fix pylint issue
2016-09-08 23:06:57 +02:00
Johann Kellerman
e8ad76c816 Improve yaml fault tolerance and handle check_config border cases (#3159) 2016-09-08 22:20:38 +02:00
Pascal Vizeli
267cda447e Add support for complex template structures to data_template (#3255) 2016-09-08 18:19:47 +02:00
Fabian Affolter
94e3986d54 Use constants and update ordering (#3261) 2016-09-08 16:26:54 +02:00
Pascal Vizeli
24aa3b3c97 Add voluptuous to ecobee (#3257) 2016-09-08 16:11:00 +02:00
Daniel
b3d2db45de issue #3250 2016-09-08 08:43:05 +02:00
Fabian Affolter
1af5d4c8b8 Minor Ecobee changes (#3131)
* Update configuration check, ordering, and constants

* Make API key optional
2016-09-07 20:21:42 +02:00
Daniel Høyer Iversen
4d41c5cd0f Fix issue #3250 (#3253) 2016-09-07 19:17:16 +02:00
Pascal Vizeli
e632a47772 protect service data for changes in calls (#3249)
* protect service data for changes in calls

* change handling

* move MappingProxyType to service call
2016-09-07 08:19:19 -07:00
Fabian Affolter
32c234ffcc Add linux battery sensor (#3238) 2016-09-07 16:32:35 +02:00
Paulus Schoutsen
5995f2438e fix remove listener (#3196) 2016-09-07 06:59:59 -07:00
Paulus Schoutsen
35b388edce Reload groups (#3203)
* Allow reloading groups without restart

* Test to make sure automation listeners are removed.

* Remove unused imports for group tests

* Simplify group config validation

* Add prepare_reload function to entity component

* Migrate group to use entity_component.prepare_reload

* Migrate automation to use entity_component.prepare_reload

* Clean up group.get_entity_ids

* Use cv.boolean for group config validation
2016-09-07 06:59:16 -07:00
Pascal Vizeli
91028cbc13 Change PR to suggestion on gitter (#3243) 2016-09-07 06:57:59 -07:00
Dave Banks
3668afe306 Zwave set temperature fix (#3221)
* If device was off set target temp would not work.

* Changed to use a workaround just for Horstmann HRT4-ZW Zwave Thermostat

* Wrong Horseman id

* style changes
2016-09-07 11:22:51 +02:00
Johann Kellerman
47864fc7d7 Owntracks voluptuous fix (#3191) 2016-09-06 18:35:10 -07:00
John Arild Berentsen
e88e6d1030 Zwave climate fix and wink cover. (#3205)
* Fixes setpoint get was done outside loop

* zxt_120

* Wink not migrated to cover

* Clarifying debug

* too long line

* Only add 1 device entity
2016-09-06 18:34:28 -07:00
Pascal Vizeli
d7b757fb97 fix bugfix with unique_id (#3217) 2016-09-06 18:31:56 -07:00
Fabian Affolter
6a837f3aad Migrate to voluptuous (#3234) 2016-09-06 18:28:55 -07:00
Pascal Vizeli
165871d48a update ffmpeg version to 0.10 add get image to camera (#3235) 2016-09-06 18:24:11 -07:00
Marcelo Moreira de Mello
fb719f530a Upgraded fitbit to version 0.2.3 which fixed oauthlib.oauth2.rfc6749.errors.TokenExpiredError: (token_expired) (#3244) 2016-09-06 18:23:08 -07:00
Per Sandström
d53d8f5ea9 thread safe modbus (#3188) 2016-09-06 18:21:38 -07:00
Fabian Affolter
7aafa309c9 Use voluptuous for Verisure (#3169)
* Migrate to voluptuous

* Update type and add missing config variable
2016-09-06 18:18:34 -07:00
Fabian Affolter
abff2f2b36 Use voluptuous for PulseAudio Loopback (#3160)
* Migrate to voluptuous

* Fix conf var
2016-09-06 18:16:03 -07:00
Dan Smith
f55095df83 Fix mFi sensors in uninitialized state (#3246)
If mFi sensors are identified but not fully assigned they can
have no tag value, and mficlient throws a ValueError to signal this.
This patch handles that case by considering such devices to always
be STATE_OFF.
2016-09-06 18:04:20 -07:00
Fabian Affolter
9d4ccb1f49 Migrate to voluptuous (#3230)
🐬
2016-09-06 18:03:43 -06:00
Pascal Vizeli
9eacde0005 Use voluptuous for pushbullet, pushetta and pushover (#3240) 2016-09-06 18:00:33 -06:00
Pascal Vizeli
22870d424a Use voluptuous for gntp (#3237) 2016-09-06 16:16:21 -06:00
Fabian Affolter
e00f9339d1 Use voluptuous for Alarm.com (#3229) 2016-09-06 23:48:32 +02:00
Fabian Affolter
d8db881e9a Replace rollershutter and garage door with cover, add fan (#3242) 2016-09-06 23:41:26 +02:00
Ardetus
fa8ed4de41 Improve 1-Wire device family detection and error checking. Use volupt… (#3233)
* Improve 1-Wire device family detection and error checking. Use voluptuous

* Fix detection of gpio connected devices
2016-09-06 14:50:02 -06:00
Pascal Vizeli
79fa9963da Use voluptuous for nma (#3241) 2016-09-06 22:24:04 +02:00
Pascal Vizeli
d06a3c9145 Use voluptuous for free mobile (#3236) 2016-09-06 21:33:11 +02:00
Christian Brædstrup
c1139a9fda dlink switch added device state attributes and support for legacy firmware (#3211) 2016-09-06 11:52:22 -06:00
Bart274
9ade87013e Bluetooth: keep looking for new devices (#3201)
* keep looking for new devices

* Update bluetooth_tracker.py

* change default value for tracking new devices

* remove commented code
2016-09-06 11:51:36 -06:00
Fabian Affolter
478c82c34c Upgrade sendgrid to 3.4.0 (#3226) 2016-09-06 18:12:24 +02:00
Fabian Affolter
85baebb23b Upgrade Werkzeug to 0.11.11 (#3220) 2016-09-06 08:55:23 -06:00
Fabian Affolter
88d62bd935 Upgrade gps3 to 0.33.3 (#3222) 2016-09-06 08:53:21 -06:00
Fabian Affolter
9530c7366b Upgrade psutil to 4.3.1 (#3223) 2016-09-06 08:51:51 -06:00
Fabian Affolter
26eba4cb1a Upgrade slacker to 0.9.25 (#3224) 2016-09-06 08:51:23 -06:00
Fabian Affolter
c06fe51122 Fix email validation (fixes #3138) (#3227) 2016-09-06 08:48:24 -06:00
Fabian Affolter
f595c8715c Update email validation (#3228)
🐬
2016-09-06 08:45:33 -06:00
Robbie Trencheny
6e6b2ae3f4 Add new docs requirements 2016-09-05 18:12:44 -07:00
Robbie Trencheny
d903661577 Flake8/pylint 2016-09-05 18:10:04 -07:00
Robbie Trencheny
a5faa851e8 Docs update 2016-09-05 18:06:19 -07:00
Fabian Affolter
5ec6eaf7d0 Update ordering (#3216) 2016-09-05 22:53:23 +02:00
Pascal Vizeli
73036f4725 change update handling with variable for breack CCU2 (#3215) 2016-09-05 22:39:29 +02:00
Johann Kellerman
17a2cac7e1 Use Voluptuous for Luci and Netgear device trackers (#3123)
* Use Voluptuous for Luci and NEtgear device trackers

* str_schema shortcut

* Undo str_schema
2016-09-05 11:37:36 -06:00
Fabian Affolter
e0a6d7941c Migrate to voluptuous (#3200)
🐬
2016-09-05 11:34:35 -06:00
Fabian Affolter
4638696f8c Migrate to voluptuous and upgrade uber_rides to 0.2.5 (#3181) 2016-09-05 11:33:35 -06:00
Fabian Affolter
428db4a644 Use voluptuous for SqueezeBox (#3212)
* Migrate to voluptuous

* Remove name
2016-09-05 19:27:06 +02:00
Fabian Affolter
ea1e4ea215 Migrate to voluptuous (#3214) 2016-09-05 19:22:26 +02:00
Fabian Affolter
6b787ee01e Migrate to voluptuous (#3209)
🐬
2016-09-05 10:05:27 -06:00
Fabian Affolter
6be20883f0 Migrate to voluptuous (#3208)
🐬
2016-09-05 10:04:46 -06:00
Fabian Affolter
95ea0c02b9 Migrate to voluptuous (#3207) 2016-09-05 10:03:25 -06:00
Fabian Affolter
5059d8dde9 Migrate to voluptuous (#3206) 2016-09-05 10:01:50 -06:00
arsaboo
3bbd909b20 Updated to use the occupancy sensor_class (#3204)
🐬
2016-09-05 09:55:29 -06:00
Fabian Affolter
909b5ffa5b Migrate to voluptuous (#3202)
🐬
2016-09-05 09:51:18 -06:00
Fabian Affolter
e324885ff6 Use extend of PLATFORM_SCHEMA (#3199) 2016-09-05 09:47:53 -06:00
Fabian Affolter
8afed2cafa Migrate to voluptuous (#3198)
🐬
2016-09-05 09:46:57 -06:00
Fabian Affolter
6bbe3483d9 Migrate to voluptuous (#3197) 2016-09-05 09:45:06 -06:00
Fabian Affolter
9c600012a1 Migrate to voluptuous (#3194)
🐬
2016-09-05 09:40:57 -06:00
Fabian Affolter
aed59aea7d Migrate to voluptuous (#3193) 2016-09-05 09:39:21 -06:00
Greg Dowling
09d52820dd Simple trend sensor. (#3073)
* First cut of trend sensor.

* Tidy.
2016-09-05 16:32:14 +02:00
Pascal Vizeli
48c1631178 Update voluptuous for existing notify platforms (#3133)
* Update voluptuous for exists notify platforms

* fix constants
2016-09-05 13:27:10 +02:00
Robbie Trencheny
1170b2897a Doc updates 2016-09-05 03:31:48 -07:00
Robbie Trencheny
5144547b70 Try out the RTD theme 2016-09-05 02:25:03 -07:00
Pascal Vizeli
e460d8f637 Use voluptuous for message_bird, sendgrid (#3136) 2016-09-05 07:07:31 +02:00
Marcelo Moreira de Mello
7bab4055a5 Added support to 'effect: random' to Osram Lightify lights (#3192)
* Added support to 'effect: random' to Osram Lightify lights

* removed extra line not required
2016-09-04 21:15:44 -07:00
Fabian Affolter
892f6a706a Use voluptuous for Bravia TV (#3165)
🐬
2016-09-04 19:22:01 -06:00
Fabian Affolter
59cd92cb4d Use voluptuous for Edimax (#3178)
🐬
2016-09-04 19:17:40 -06:00
Fabian Affolter
98bdcd3405 Use voluptuous for Hikvisioncam switch (#3184)
* Migrate to voluptuous

* Use vol.Optional
2016-09-04 19:16:16 -06:00
Robbie Trencheny
a569ee787d Correct binary_sensor.ecobee docs URL 2016-09-04 14:37:10 -07:00
Johann Kellerman
ad52816595 Use voluptuous for BT and Owntracks device trackers (#3187)
🐬
2016-09-04 11:10:20 -06:00
Heiko Rothe
29870b301e Added scale and offset to the Temper component (#2853)
🐬
2016-09-04 10:37:10 -06:00
Fabian Affolter
b4c8d10dbc Migrate to voluptuous (#3179)
🐬
2016-09-04 10:32:12 -06:00
Fabian Affolter
cd67368bb7 Migrate to voluptuous (#3182)
🐬
2016-09-04 10:27:19 -06:00
Paulus Schoutsen
e9813b219e Allow reloading automation without restarting HA (#3002) 2016-09-04 17:15:52 +02:00
Ben Bangert
641d531be3 Add Sphinx API doc generation (#3029)
* add's sphinx project to docs/ dir
* include core/helpers autodocs for API reference
2016-09-04 14:36:44 +02:00
Robbie Trencheny
74980d9563 MQTT fan platform (#3095)
* Add fan.mqtt, allow brightness to be passed and mapped to a fan speed for compatibility with emulated_hue

* Pylint/Flake8 fixes

* Remove brightness

* Add more features, like custom oscillation/speed payloads and setting the speed list

* Flake8 fixes

* flake8/pylint fixes

* Use constants

* block fan.mqtt from coverage

* Fix oscillating comment
2016-09-04 03:15:55 -07:00
Robbie Trencheny
0f37d8d8eb Using alert with Hue maintains prior state (#3147)
* When using flash with hue, dont change the on/off state of the light so that it will naturally return to its previous state once flash is complete

* ATTR_FLASH not ATTR_EFFECT
2016-09-04 03:04:12 -07:00
Johann Kellerman
22362727e4 Use voluptuous for Unifi, Ubus (#3125) 2016-09-04 10:06:16 +02:00
Robbie Trencheny
48e6befc13 Update frontend 2016-09-03 22:04:23 -07:00
Robbie Trencheny
4de9717256 Add the occupancy sensor_class (#3176)
Such a complicated PR
2016-09-03 22:52:31 -06:00
Fabian Affolter
b02b008fe5 Migrate to voluptuous (#3144)
🐬
2016-09-03 20:36:21 -06:00
Fabian Affolter
3c615e2319 Use voluptuous for mFi switch (#3168)
* Migrate to voluptuous

* Take change configuration into account
2016-09-03 20:32:35 -06:00
Fabian Affolter
8467d07a3d Migrate to voluptuous (#3171) 2016-09-03 20:24:29 -06:00
Fabian Affolter
6f45906eda Migrate to voluptuous (#3174) 2016-09-03 20:21:59 -06:00
Martin Hjelmare
34ba4d3e09 Exclude www_static from pydocstyle linting (#3175)
🐬
2016-09-03 20:21:19 -06:00
Fabian Affolter
3b1c0a7502 Migrate to voluptuous (#3162)
🐬 and 🍪 for fixing quotes!
2016-09-03 20:20:45 -06:00
Fabian Affolter
6a2f0fc456 Migrate to voluptuous (#3163)
🐬
2016-09-03 20:18:11 -06:00
Fabian Affolter
2aab77a486 Migrate to voluptuous (#3164)
🐬
2016-09-03 20:14:28 -06:00
Fabian Affolter
02960ec482 Migrate to voluptuous (#3166)
🐬
2016-09-03 20:09:02 -06:00
Open Home Automation
db7f6a328f Bugfix: ctach Runtime errors (#3153)
"RuntimeError: Disable scan failed" has been seen in a live installation
2016-09-03 16:47:11 -07:00
Fabian Affolter
290ec9b4ac Use constants (#3156) 2016-09-03 16:45:49 -07:00
Fabian Affolter
0198ba4eac Allow https (fixes #3150) (#3155) 2016-09-03 16:45:31 -07:00
Steven Barnes
09b53a0d55 Adding link_names to post.message call (#3167)
If you do not turn link_names on, Slack will not highlight @channel and @username messages.
2016-09-03 16:44:30 -07:00
Heiko Rothe
269e97c6de Added Xbox Live component (#3013)
* Added Xbox Live component

* Added Xbox Live sensor to coveralls

* Added init success checks

* Added entity id
2016-09-03 16:43:33 -07:00
Dan
68ef55a982 Add exclude option to nmap device tracker (#2983)
* Add exclude option to nmap device tracker

Adds an optional exclude paramater to nmap device tracker.
Devices specified in the exclude list will never be scanned
by nmap. This can help to reduce log spam.

ex:
```
device_tracker:
  - platform: nmap_tracker
    hosts: 10.0.0.1/24
    home_interval: 1
    interval_seconds: 12
    consider_home: 120
    track_new_devices: yes
    exclude:
      - 10.0.0.2
      - 10.0.0.1
```

* Handle optional exclude

* Style fixed
2016-09-03 16:41:38 -07:00
Johann Kellerman
91a3522100 Improve voluptuous and login errors for Asus device tracker (#3170) 2016-09-03 17:32:43 -06:00
Johann Kellerman
fe7f797ad9 Add voluptuous for tomato and SNMP (#3172) 2016-09-03 17:30:48 -06:00
Fabian Affolter
70888532f8 Migrate to voluptuous (#3173) 2016-09-03 17:30:21 -06:00
Robbie Trencheny
32e1e046ae Merge branch 'master' into dev 2016-09-03 16:25:59 -07:00
Teagan Glenn
601395bc12 Automatic ODB device tracker & device tracker attributes (#3035) 2016-09-03 18:38:17 +02:00
Fabian Affolter
a08ac85971 Display the error instead of the traceback (notify.slack) (#3079)
* Display the error instead of the traceback

* Remove name for check
2016-09-03 17:01:05 +02:00
Fabian Affolter
5dc63c17c8 Migrate to voluptuous (#3092) 2016-09-03 10:56:41 +02:00
Fabian Affolter
795121d5a8 Update ordering (#3149) 2016-09-03 09:35:33 +02:00
Fabian Affolter
6ae4e5cb6c Use constants (#3148) 2016-09-03 00:09:14 +02:00
Robbie Trencheny
b5ae005acc Update frontend 2016-09-02 14:50:10 -07:00
Fabian Affolter
fb9627deda Move details to docs (#3146) 2016-09-02 23:25:35 +02:00
Teagan Glenn
6fdd7f5350 Back out insteon hub and fan changes (#3062) 2016-09-02 12:18:32 -07:00
Robbie Trencheny
a7a662d224 Merge pull request #3130 from turbokongen/zwave_fixes
Bugfix. climate and covermqt
2016-09-02 12:15:27 -07:00
Fabian Affolter
3bbcf4d8b1 Migrate to voluptuous (#3142)
🐬
2016-09-02 11:16:42 -06:00
Fabian Affolter
a0a509ceea Add coinmarketcap sensor (#3064) 2016-09-02 16:59:05 +02:00
Fabian Affolter
40c71b5d96 Use voluptuous for Command line platforms (#2968)
* Migrate to voluptuous

* Fix pylint issues

* Remove FIXME

* Split setup test

* Test with bootstrap

* Remove lon and lat

* Fix pylint issues
2016-09-02 08:09:09 -06:00
Pascal Vizeli
81628b01c2 Use voluptuous for webostv (#3135) 2016-09-02 07:59:38 -06:00
Pascal Vizeli
28e939afcf Use voluptuous for twilio (#3134) 2016-09-02 07:59:08 -06:00
Fabian Affolter
e5ef548f10 Use voluptuous for Acer projector switch (#3077)
🐬
2016-09-02 07:42:38 -06:00
Tomi Tuhkanen
dedc4a129c Updated braviatv's braviarc version to 0.3.4 (#2997)
* Updated braviarc version to 0.3.4

* Updated braviarc version to requirements_all.txt
2016-09-02 15:07:40 +02:00
Fabian Affolter
95cc672161 Add missing docstrings (fix PEP257 issues) (#3098)
* Add missing docstrings (fix PEP257 issues)

* Finish sentence
2016-09-02 14:25:13 +02:00
Fabian Affolter
6a84b82663 Use voluptuous for Octoprint (#3111)
* Migrate to voluptuous

* Fix pylint issues
2016-09-02 12:26:23 +02:00
Pascal Vizeli
000832a82c Use voluptuous for instapush (#3132) 2016-09-02 11:14:18 +02:00
turbokongen
0907eea442 move units to temperature for climate zwave. wrong state was sent to mqtt cove 2016-09-02 10:49:53 +02:00
Robbie Trencheny
b8b1fadc6d Update frontend 2016-09-01 21:59:32 -07:00
happyleavesaoc
24d3cbdfe9 orvibo updates (#3006)
🐬
2016-09-01 22:37:09 -06:00
happyleavesaoc
451f0cb3f1 snapcast update (#3012)
* snapcast update

* snapcast update

* validate config

* use conf constants
2016-09-01 22:36:14 -06:00
Fabian Affolter
b4df9b30d8 Migrate to voluptuous (#3066)
🐬
2016-09-01 22:34:42 -06:00
Fabian Affolter
27ee4c555a Migrate to voluptuous (#3069)
🐬
2016-09-01 22:34:07 -06:00
Daniel Perna
0c310c166a Fixed Homematic cover (#3116) 2016-09-01 22:32:12 -06:00
Fabian Affolter
06df31bb5b Migrate to voluptuous (#3084) 2016-09-01 22:31:49 -06:00
Fabian Affolter
177d8ef4ef Migrate to voluptuous (#3096) 2016-09-01 22:31:32 -06:00
John Arild Berentsen
a50205aedb Climate and cover bugfix (#3097)
* Avoid None comparison for zwave cover.

* Just rely on unit from config for unit_of_measurement

* Explicit return None

* Mqtt (#11)

* Explicit return None

* Missing service and wrong service name defined

* Mqtt state was inverted, and never triggering
2016-09-01 22:31:25 -06:00
Fabian Affolter
9226cef61e Update voluptuous (#3104) 2016-09-01 22:30:49 -06:00
Fabian Affolter
29f2dd2ce9 Migrate to voluptuous (#3106) 2016-09-01 22:30:25 -06:00
Fabian Affolter
ed7a227035 Fix typo (#3108) 2016-09-01 22:30:20 -06:00
Fabian Affolter
d8ad4e1584 Migrate to voluptuous (#3113) 2016-09-01 22:29:35 -06:00
Fabian Affolter
db7abc1cfe Use constants, update configuration check, and ordering (Pilight) (#3118)
* Use contants, update configuration check, and ordering

* Fix pylint issue
2016-09-01 22:28:52 -06:00
Johann Kellerman
a571271c39 Use voluptuous for Aruba (#3119) 2016-09-01 22:28:46 -06:00
Pascal Vizeli
9e38255c26 Use voluptuous for syslog (#3120) 2016-09-01 22:28:33 -06:00
Johann Kellerman
586e47d08d Use Voluptuous for BT Home Hub (#3121) 2016-09-01 22:28:28 -06:00
Johann Kellerman
78f0e681ed Use voluptuous for Fritzbox and DDWRT (#3122) 2016-09-01 22:28:03 -06:00
Pascal Vizeli
afdd734b44 Use voluptuous for twitter (#3126) 2016-09-01 22:27:38 -06:00
Pascal Vizeli
6b6d34ba51 Use voluptuous for xmpp (#3127) 2016-09-01 22:27:28 -06:00
Johann Kellerman
dadcf92290 Allow 'None' MAC to be loaded from known_devices (#3102) 2016-09-02 00:02:35 +02:00
Pascal Vizeli
dcfc1ef361 fix homematic climate implementation (#3114) 2016-09-01 22:20:55 +02:00
Open Home Automation
83f1272662 Fix for BLE device tracker (#3019)
* Bug fix tracked devices
* Added scan_duration configuration parameter
2016-09-01 22:18:58 +02:00
Fabian Affolter
d2dfe04ec9 Update voluptuous for nest (#3109)
* Update configuration check
* Extend platform
2016-09-01 22:08:03 +02:00
Fabian Affolter
24d412938e Use voluptuous for HDMI CEC & CONF_DEVICES constants (#3107) 2016-09-01 22:04:00 +02:00
Fabian Affolter
748d7f4ecb Bitcoin sensor use warning instead of error (#3103) 2016-09-01 21:57:47 +02:00
Daniel Høyer Iversen
1094de7ad9 Merge pull request #3110 from home-assistant/rfxtrx_sensor_cleanup
rfxtrx sensor clean up
2016-09-01 19:42:40 +02:00
Daniel
831d96995d rfxtrx sensor clean up 2016-09-01 19:23:26 +02:00
Paulus Schoutsen
60f540315a Fix broken test 2016-09-01 14:40:13 +01:00
Lewis Juggins
0bcfb65a30 Refactor notification titles to allow for them to be None, this also includes a change in Telegram to only include the title if it's present, and to use a Markdown parse mode for messages (#3100) 2016-09-01 14:35:46 +01:00
Pascal Vizeli
5036bb0bc6 improve isfile validation check (#3101) 2016-09-01 14:35:00 +01:00
Paulus Schoutsen
87e332c777 Merge pull request #2980 from home-assistant/automation-entities
Create entities for automation
2016-09-01 09:50:39 +01:00
Fabian Affolter
88e600827e Upgrade pyowm to 2.4.0 (#3067) 2016-09-01 09:38:50 +01:00
Fabian Affolter
e045a6f0c3 Upgrade pyuserinput to 0.1.11 (#3068) 2016-09-01 09:37:58 +01:00
Paulus Schoutsen
c792dd4126 Fix linting 2016-09-01 09:12:42 +01:00
John Arild Berentsen
571cbdf40c If device was off target temp was null. Default to Heating setpoint (#3091) 2016-09-01 09:31:52 +02:00
John Arild Berentsen
4b12ea04d6 humidity slider (#3088) 2016-09-01 07:13:33 +02:00
John Arild Berentsen
5f664acb4f unit fix (#3083) 2016-08-31 22:30:44 +02:00
John Arild Berentsen
e5b6592870 Zwave climate Bugfix: if some setpoints have different units, we should fetch the o… (#3078)
* Bugfix: if some setpoints have different units, we should fetch the one that are active.

* Move order of population for first time detection

* Default to config if None unit_of_measurement
2016-08-31 21:50:03 +02:00
Pascal Vizeli
705b3571f4 Use voluptuous for file (#3049) 2016-08-31 18:12:34 +02:00
Daniel Høyer Iversen
dfee443312 Host should be optional for apcupsd component (#3072) 2016-08-31 18:09:22 +02:00
Greg Dowling
0943cc78cd Merge pull request #2973 from nma83/owntracks-waypoint-import
Owntracks waypoint import
2016-08-31 15:25:49 +01:00
Daniel Høyer Iversen
8816b62d9c Merge pull request #3063 from tchellomello/flux_led_supportability
Added bitfield of features for flux_led since we are supporting effects
2016-08-31 10:25:29 +02:00
Marcelo Moreira de Mello
eadd07dc7d Added bitfield of features for flux_led since we are supporting effects 2016-08-31 03:52:19 -04:00
NMA
12e2c38436 Code review feedback from @pavoni 2016-08-31 08:16:01 +05:30
Robbie Trencheny
4864a67dcd Back to 0.28.0.dev0 2016-08-30 14:23:00 -07:00
NMA
70fe7f747a * Improved zone naming in waypoint import
* Added more test coverage for owntracks and zone
2016-08-28 13:18:30 +05:30
Paulus Schoutsen
7f27cc5468 Fix tests docstring 2016-08-27 07:45:46 +01:00
Paulus Schoutsen
586208b3ed Fix JSON encoder issue in recorder 2016-08-27 07:43:42 +01:00
NMA
2430acf3ad Added unit test to enhance waypoint_whitelist coverage 2016-08-26 22:00:48 +05:30
NMA
5a25c74276 Refactored zone creation based on code review feedback, enhanced configuration 2016-08-26 19:52:08 +05:30
Paulus Schoutsen
3fa1963345 Convert automation to entities with services 2016-08-25 23:36:48 -07:00
Paulus Schoutsen
d9ecc4af64 EventBus: return function to unlisten 2016-08-25 23:25:35 -07:00
Paulus Schoutsen
62ba0fa7a2 Do not install pip packages in tests 2016-08-25 23:23:14 -07:00
NMA
ed872f6054 Fixed E302 2016-08-25 22:29:16 +05:30
NMA
47a9313fdb Fixed variable scope issues for entities 2016-08-25 22:15:31 +05:30
NMA
ca73295dd1 Fixed style issues 2016-08-25 21:35:04 +05:30
NMA
2ca3541eac Fixed zone test break and code style issues 2016-08-25 21:33:07 +05:30
NMA
95b7a8c4b9 Removed redundant assignment to CONF_WAYPOINT_IMPORT_USER 2016-08-25 17:07:53 +05:30
NMA
185ae50e24 Rebased to upstream 2016-08-25 17:02:45 +05:30
NMA
e6b7511e7d Added test for Owntracks waypoints import 2016-08-25 16:52:22 +05:30
NMA
1ada7d6211 Backend support for importing waypoints from owntracks as HA zones 2016-08-25 16:52:22 +05:30
NMA
2bea5a484f Added test for Owntracks waypoints import 2016-08-25 16:47:34 +05:30
NMA
75e6ed87d6 Backend support for importing waypoints from owntracks as HA zones 2016-08-12 14:48:28 +05:30
517 changed files with 25805 additions and 9632 deletions

View File

@@ -16,6 +16,9 @@ omit =
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
@@ -28,6 +31,9 @@ omit =
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
homeassistant/components/ios.py
homeassistant/components/*/ios.py
homeassistant/components/isy994.py
homeassistant/components/*/isy994.py
@@ -46,6 +52,9 @@ omit =
homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/rpi_gpio.py
homeassistant/components/*/rpi_gpio.py
@@ -77,7 +86,7 @@ omit =
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zwave.py
homeassistant/components/zwave/*
homeassistant/components/*/zwave.py
homeassistant/components/enocean.py
@@ -89,26 +98,30 @@ omit =
homeassistant/components/homematic.py
homeassistant/components/*/homematic.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
homeassistant/components/switch/pilight.py
homeassistant/components/knx.py
homeassistant/components/switch/knx.py
homeassistant/components/binary_sensor/knx.py
homeassistant/components/thermostat/knx.py
homeassistant/components/*/knx.py
homeassistant/components/ffmpeg.py
homeassistant/components/*/ffmpeg.py
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.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/binary_sensor/arest.py
homeassistant/components/binary_sensor/ffmpeg.py
homeassistant/components/binary_sensor/concord232.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/heatmiser.py
homeassistant/components/climate/homematic.py
@@ -122,6 +135,7 @@ omit =
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/bbox.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bluetooth_le_tracker.py
homeassistant/components/device_tracker/bt_home_hub_5.py
@@ -136,8 +150,10 @@ omit =
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/volvooncall.py
homeassistant/components/discovery.py
homeassistant/components/downloader.py
homeassistant/components/emoncms_history.py
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
@@ -147,6 +163,7 @@ omit =
homeassistant/components/ifttt.py
homeassistant/components/joaoapps_join.py
homeassistant/components/keyboard.py
homeassistant/components/keyboard_remote.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/flux_led.py
homeassistant/components/light/hue.py
@@ -188,7 +205,9 @@ omit =
homeassistant/components/notify/group.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/joaoapps_join.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
@@ -196,38 +215,49 @@ omit =
homeassistant/components/notify/pushover.py
homeassistant/components/notify/rest.py
homeassistant/components/notify/sendgrid.py
homeassistant/components/notify/simplepush.py
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/telstra.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
homeassistant/components/openalpr.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/efergy.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gpsd.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/mhz19.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nzbget.py
@@ -239,6 +269,7 @@ omit =
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/speedtest.py
@@ -247,21 +278,25 @@ omit =
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/worldclock.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/yweather.py
homeassistant/components/switch/acer_projector.py
homeassistant/components/switch/anel_pwrctrl.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
homeassistant/components/switch/hikvisioncam.py
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/neato.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pulseaudio_loopback.py

View File

@@ -1,9 +1,9 @@
**Description:**
**Related issue (if applicable):** fixes #
**Related issue (if applicable):** fixes #<home-assistant issue number goes here>
**Pull request in [home-assistant.io](https://github.com/home-assistant/home-assistant.io) with documentation (if applicable):** home-assistant/home-assistant.io#
**Pull request in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) with documentation (if applicable):** home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here>
**Example entry for `configuration.yaml` (if applicable):**
```yaml
@@ -13,9 +13,9 @@
**Checklist:**
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)
- [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io)
If code communicates with devices, web services, or a:
If the code communicates with devices, web services, or third-party tools:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).

5
.gitignore vendored
View File

@@ -99,4 +99,7 @@ virtualization/vagrant/config
.vscode
# Built docs
docs/build
docs/build
# Windows Explorer
desktop.ini

View File

@@ -1,4 +1,4 @@
FROM python:3.4
FROM python:3.5
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
VOLUME /config
@@ -10,7 +10,7 @@ RUN pip3 install --no-cache-dir colorlog cython
# For the nmap tracker, bluetooth tracker, Z-Wave
RUN apt-get update && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev && \
apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev bluetooth libbluetooth-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY script/build_python_openzwave script/build_python_openzwave
@@ -19,9 +19,8 @@ RUN script/build_python_openzwave && \
ln -sf /usr/src/app/build/python-openzwave/openzwave/config /usr/local/share/python-openzwave/config
COPY requirements_all.txt requirements_all.txt
# certifi breaks Debian based installs
RUN pip3 install --no-cache-dir -r requirements_all.txt && pip3 uninstall -y certifi && \
pip3 install mysqlclient psycopg2
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install mysqlclient psycopg2 uvloop
# Copy source
COPY . .

View File

@@ -8,11 +8,31 @@
.. autoclass:: Config
:members:
.. autoclass:: Event
:members:
.. autoclass:: EventBus
:members:
.. autoclass:: HomeAssistant
:members:
.. autoclass:: State
:members:
.. autoclass:: StateMachine
:members:
.. autoclass:: ServiceCall
:members:
.. autoclass:: ServiceRegistry
:members:
Module contents
---------------
.. automodule:: homeassistant.core
:members:
:undoc-members:
:show-inheritance:

View File

@@ -4,6 +4,14 @@ homeassistant.util package
Submodules
----------
homeassistant.util.async module
-------------------------------
.. automodule:: homeassistant.util.async
:members:
:undoc-members:
:show-inheritance:
homeassistant.util.color module
-------------------------------

View File

@@ -340,8 +340,8 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Home-Assistant.tex', 'Home-Assistant Documentation',
'Home-Assistant Team', 'manual'),
(master_doc, 'home-assistant.tex', 'Home Assistant Documentation',
'Home Assistant Team', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -382,7 +382,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'home-assistant', 'Home-Assistant Documentation',
(master_doc, 'home-assistant', 'Home Assistant Documentation',
[author], 1)
]
@@ -397,8 +397,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Home-Assistant', 'Home-Assistant Documentation',
author, 'Home-Assistant', 'One line description of project.',
(master_doc, 'Home-Assistant', 'Home Assistant Documentation',
author, 'Home Assistant', 'Open-source home automation platform.',
'Miscellaneous'),
]

View File

@@ -2,7 +2,7 @@ swagger: '2.0'
info:
title: Home Assistant
description: Home Assistant REST API
version: "1.0.0"
version: "1.0.1"
# the domain of the service
host: localhost:8123
@@ -12,17 +12,17 @@ schemes:
- https
securityDefinitions:
api_key:
type: apiKey
description: API password
name: api_password
in: query
# api_key:
#api_key:
# type: apiKey
# description: API password
# name: x-ha-access
# in: header
# name: api_password
# in: query
api_key:
type: apiKey
description: API password
name: x-ha-access
in: header
# will be prefixed to all paths
basePath: /api
@@ -38,6 +38,8 @@ paths:
description: Returns message if API is up and running.
tags:
- Core
security:
- api_key: []
responses:
200:
description: API is up and running
@@ -53,6 +55,8 @@ paths:
description: Returns the current configuration as JSON.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Current configuration
@@ -81,6 +85,8 @@ paths:
summary: Returns all data needed to bootstrap Home Assistant.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Bootstrap information
@@ -96,6 +102,8 @@ paths:
description: Returns an array of event objects. Each event object contain event name and listener count.
tags:
- Events
security:
- api_key: []
responses:
200:
description: Events
@@ -113,6 +121,8 @@ paths:
description: Returns an array of service objects. Each object contains the domain and which services it contains.
tags:
- Services
security:
- api_key: []
responses:
200:
description: Services
@@ -130,6 +140,8 @@ paths:
description: Returns an array of state changes in the past. Each object contains further detail for the entities.
tags:
- State
security:
- api_key: []
responses:
200:
description: State changes
@@ -148,6 +160,8 @@ paths:
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
tags:
- State
security:
- api_key: []
responses:
200:
description: States
@@ -166,6 +180,8 @@ paths:
Returns a state object for specified entity_id.
tags:
- State
security:
- api_key: []
parameters:
- name: entity_id
in: path
@@ -223,6 +239,8 @@ paths:
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
tags:
- Core
security:
- api_key: []
produces:
- text/plain
responses:
@@ -239,6 +257,8 @@ paths:
Returns the data (image) from the specified camera entity_id.
tags:
- Camera
security:
- api_key: []
produces:
- image/jpeg
parameters:
@@ -262,6 +282,8 @@ paths:
Fires an event with event_type
tags:
- Events
security:
- api_key: []
consumes:
- application/json
parameters:
@@ -286,6 +308,8 @@ paths:
Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first.
tags:
- Services
security:
- api_key: []
consumes:
- application/json
parameters:
@@ -317,6 +341,8 @@ paths:
Render a Home Assistant template.
tags:
- Template
security:
- api_key: []
consumes:
- application/json
produces:
@@ -338,6 +364,8 @@ paths:
Setup event forwarding to another Home Assistant instance.
tags:
- Core
security:
- api_key: []
consumes:
- application/json
parameters:
@@ -376,6 +404,8 @@ paths:
tags:
- Core
- Events
security:
- api_key: []
produces:
- text/event-stream
parameters:
@@ -420,8 +450,16 @@ definitions:
location_name:
type: string
unit_system:
type: string
description: The system for measurement units
type: object
properties:
length:
type: string
mass:
type: string
temperature:
type: string
volume:
type: string
time_zone:
type: string
version:

View File

@@ -16,16 +16,57 @@ from homeassistant.const import (
REQUIRED_PYTHON_VER,
RESTART_EXIT_CODE,
)
from homeassistant.util.async import run_callback_threadsafe
def monkey_patch_asyncio():
"""Replace weakref.WeakSet to address Python 3 bug.
Under heavy threading operations that schedule calls into
the asyncio event loop, Task objects are created. Due to
a bug in Python, GC may have an issue when switching between
the threads and objects with __del__ (which various components
in HASS have).
This monkey-patch removes the weakref.Weakset, and replaces it
with an object that ignores the only call utilizing it (the
Task.__init__ which calls _all_tasks.add(self)). It also removes
the __del__ which could trigger the future objects __del__ at
unpredictable times.
The side-effect of this manipulation of the Task is that
Task.all_tasks() is no longer accurate, and there will be no
warning emitted if a Task is GC'd while in use.
On Python 3.6, after the bug is fixed, this monkey-patch can be
disabled.
See https://bugs.python.org/issue26617 for details of the Python
bug.
"""
# pylint: disable=no-self-use, too-few-public-methods, protected-access
# pylint: disable=bare-except
import asyncio.tasks
class IgnoreCalls:
"""Ignore add calls."""
def add(self, other):
"""No-op add."""
return
asyncio.tasks.Task._all_tasks = IgnoreCalls()
try:
del asyncio.tasks.Task.__del__
except:
pass
def validate_python() -> None:
"""Validate we're running the right Python version."""
major, minor = sys.version_info[:2]
req_major, req_minor = REQUIRED_PYTHON_VER
if major < req_major or (major == req_major and minor < req_minor):
print("Home Assistant requires at least Python {}.{}".format(
req_major, req_minor))
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
print("Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER))
sys.exit(1)
@@ -256,12 +297,14 @@ def setup_and_run_hass(config_dir: str,
import webbrowser
webbrowser.open(hass.config.api.base_url)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
run_callback_threadsafe(
hass.loop,
hass.bus.async_listen_once,
EVENT_HOMEASSISTANT_START, open_browser
)
hass.start()
exit_code = int(hass.block_till_stopped())
return exit_code
return hass.exit_code
def try_to_restart() -> None:
@@ -308,6 +351,8 @@ def try_to_restart() -> None:
def main() -> int:
"""Start Home Assistant."""
monkey_patch_asyncio()
validate_python()
args = get_arguments()

View File

@@ -32,12 +32,15 @@ _CURRENT_SETUP = []
ATTR_COMPONENT = 'component'
ERROR_LOG_FILENAME = 'home-assistant.log'
_PERSISTENT_PLATFORMS = set()
_PERSISTENT_VALIDATION = set()
def setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies."""
if domain in hass.config.components:
_LOGGER.debug('Component %s already set up.', domain)
return True
_ensure_loader_prepared(hass)
@@ -53,6 +56,7 @@ def setup_component(hass: core.HomeAssistant, domain: str,
for component in components:
if not _setup_component(hass, component, config):
_LOGGER.error('Component %s failed to setup', component)
return False
return True
@@ -118,11 +122,13 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool:
# Assumption: if a component does not depend on groups
# it communicates with devices
if 'group' not in getattr(component, 'DEPENDENCIES', []):
if 'group' not in getattr(component, 'DEPENDENCIES', []) and \
hass.pool.worker_count <= 10:
hass.pool.add_worker()
hass.bus.fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN})
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
)
return True
@@ -144,8 +150,8 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, config)
except vol.Invalid as ex:
log_exception(ex, domain, config, hass)
return None
elif hasattr(component, 'PLATFORM_SCHEMA'):
@@ -154,9 +160,9 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.MultipleInvalid as ex:
log_exception(ex, domain, p_config)
return None
except vol.Invalid as ex:
log_exception(ex, domain, config, hass)
continue
# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
@@ -169,16 +175,16 @@ def prepare_setup_component(hass: core.HomeAssistant, config: dict,
p_name)
if platform is None:
return None
continue
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.MultipleInvalid as ex:
except vol.Invalid as ex:
log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated)
return None
p_validated, hass)
continue
platforms.append(p_validated)
@@ -207,6 +213,13 @@ def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
# Not found
if platform is None:
_LOGGER.error('Unable to find platform %s', platform_path)
_PERSISTENT_PLATFORMS.add(platform_path)
message = ('Unable to find the following platforms: ' +
', '.join(list(_PERSISTENT_PLATFORMS)) +
'(please check your configuration)')
persistent_notification.create(
hass, message, 'Invalid platforms', 'platform_errors')
return None
# Already loaded
@@ -253,7 +266,7 @@ def from_config_dict(config: Dict[str, Any],
try:
conf_util.process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
log_exception(ex, 'homeassistant', core_config)
log_exception(ex, 'homeassistant', core_config, hass)
return None
conf_util.process_ha_config_upgrade(hass)
@@ -278,22 +291,29 @@ def from_config_dict(config: Dict[str, Any],
components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN)
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
# Setup in a thread to avoid blocking
def component_setup():
"""Set up a component."""
if not core_components.setup(hass, config):
_LOGGER.error('Home Assistant core failed to initialize. '
'Further initialization aborted.')
return hass
persistent_notification.setup(hass, config)
persistent_notification.setup(hass, config)
_LOGGER.info('Home Assistant core initialized')
_LOGGER.info('Home Assistant core initialized')
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# Give event decorators access to HASS
event_decorators.HASS = hass
service.HASS = hass
# Setup the components
for domain in loader.load_order_components(components):
_setup_component(hass, domain, config)
# Setup the components
for domain in loader.load_order_components(components):
_setup_component(hass, domain, config)
hass.loop.run_until_complete(
hass.loop.run_in_executor(None, component_setup)
)
return hass
@@ -335,6 +355,11 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False,
logging.basicConfig(level=logging.INFO)
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
"[%(name)s] %(message)s%(reset)s")
# suppress overly verbose logs from libraries that aren't helpful
logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger("urllib3").setLevel(logging.WARNING)
try:
from colorlog import ColoredFormatter
logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
@@ -387,19 +412,31 @@ def _ensure_loader_prepared(hass: core.HomeAssistant) -> None:
loader.prepare(hass)
def log_exception(ex, domain, config):
def log_exception(ex, domain, config, hass=None):
"""Generate log exception for config validation."""
message = 'Invalid config for [{}]: '.format(domain)
if hass is not None:
_PERSISTENT_VALIDATION.add(domain)
message = ('The following platforms contain invalid configuration: ' +
', '.join(list(_PERSISTENT_VALIDATION)) +
' (please check your configuration)')
persistent_notification.create(
hass, message, 'Invalid config', 'invalid_config')
if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain,
'->'.join('%s' % m for m in ex.path))
else:
message += humanize_error(config, ex)
message += '{}.'.format(humanize_error(config, ex))
if hasattr(config, '__line__'):
message += " (See {}:{})".format(config.__config_file__,
config.__line__ or '?')
message += " (See {}:{})".format(
config.__config_file__, config.__line__ or '?')
if domain != 'homeassistant':
message += (' Please check the docs at '
'https://home-assistant.io/components/{}/'.format(domain))
_LOGGER.error(message)

View File

@@ -0,0 +1,136 @@
"""
Support for Concord232 alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.concord232/
"""
import datetime
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
import requests
import voluptuous as vol
REQUIREMENTS = ['concord232==0.14']
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'CONCORD232'
DEFAULT_PORT = 5007
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
})
SCAN_INTERVAL = 1
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup concord232 platform."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
url = 'http://{}:{}'.format(host, port)
try:
add_devices([Concord232Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to Concord232: %s', str(ex))
return False
class Concord232Alarm(alarm.AlarmControlPanel):
"""Represents the Concord232-based alarm panel."""
def __init__(self, hass, url, name):
"""Initalize the concord232 alarm panel."""
from concord232 import client as concord232_client
self._state = STATE_UNKNOWN
self._hass = hass
self._name = name
self._url = url
try:
client = concord232_client.Client(self._url)
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to Concord232: %s', str(ex))
self._alarm = client
self._alarm.partitions = self._alarm.list_partitions()
self._alarm.last_partition_update = datetime.datetime.now()
self.update()
@property
def should_poll(self):
"""Polling needed."""
return True
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def code_format(self):
"""The characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Update values from API."""
try:
part = self._alarm.list_partitions()[0]
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
dict(host=self._url, reason=ex))
newstate = STATE_UNKNOWN
except IndexError:
_LOGGER.error('concord232 reports no partitions')
newstate = STATE_UNKNOWN
if part['arming_level'] == "Off":
newstate = STATE_ALARM_DISARMED
elif "Home" in part['arming_level']:
newstate = STATE_ALARM_ARMED_HOME
else:
newstate = STATE_ALARM_ARMED_AWAY
if not newstate == self._state:
_LOGGER.info("State Chnage from %s to %s", self._state, newstate)
self._state = newstate
return self._state
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._alarm.disarm(code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._alarm.arm('home')
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._alarm.arm('auto')
def alarm_trigger(self, code=None):
"""Alarm trigger command."""
raise NotImplementedError()

View File

@@ -10,6 +10,7 @@ from homeassistant.components.envisalink import (EVL_CONTROLLER,
EnvisalinkDevice,
PARTITION_SCHEMA,
CONF_CODE,
CONF_PANIC,
CONF_PARTITIONNAME,
SIGNAL_PARTITION_UPDATE,
SIGNAL_KEYPAD_UPDATE)
@@ -26,6 +27,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
_configured_partitions = discovery_info['partitions']
_code = discovery_info[CONF_CODE]
_panic_type = discovery_info[CONF_PANIC]
for part_num in _configured_partitions:
_device_config_data = PARTITION_SCHEMA(
_configured_partitions[part_num])
@@ -33,6 +35,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
part_num,
_device_config_data[CONF_PARTITIONNAME],
_code,
_panic_type,
EVL_CONTROLLER.alarm_state['partition'][part_num],
EVL_CONTROLLER)
add_devices_callback([_device])
@@ -44,11 +47,13 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Represents the Envisalink-based alarm panel."""
# pylint: disable=too-many-arguments
def __init__(self, partition_number, alarm_name, code, info, controller):
def __init__(self, partition_number, alarm_name,
code, panic_type, info, controller):
"""Initialize the alarm panel."""
from pydispatch import dispatcher
self._partition_number = partition_number
self._code = code
self._panic_type = panic_type
_LOGGER.debug('Setting up alarm: ' + alarm_name)
EnvisalinkDevice.__init__(self, alarm_name, info, controller)
dispatcher.connect(self._update_callback,
@@ -61,7 +66,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
def _update_callback(self, partition):
"""Update HA state, if needed."""
if partition is None or int(partition) == self._partition_number:
self.update_ha_state()
self.hass.async_add_job(self.update_ha_state)
@property
def code_format(self):
@@ -101,5 +106,6 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
self._partition_number)
def alarm_trigger(self, code=None):
"""Alarm trigger command. Not possible for us."""
raise NotImplementedError()
"""Alarm trigger command. Will be used to trigger a panic alarm."""
if self._code:
EVL_CONTROLLER.panic_alarm(self._panic_type)

View File

@@ -28,7 +28,7 @@ PLATFORM_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.All(vol.Coerce(int), vol.Range(min=0)),
vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_DISARM_AFTER_TRIGGER,

View File

@@ -60,6 +60,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
# talk to the API and trigger a requests exception for setup_platform()
# to catch
self._alarm.list_zones()
self._state = STATE_UNKNOWN
@property
def should_poll(self):
@@ -79,16 +80,20 @@ class NX584Alarm(alarm.AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Process new events from panel."""
try:
part = self._alarm.list_partitions()[0]
zones = self._alarm.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to %(host)s: %(reason)s',
dict(host=self._url, reason=ex))
return STATE_UNKNOWN
self._state = STATE_UNKNOWN
except IndexError:
_LOGGER.error('nx584 reports no partitions')
return STATE_UNKNOWN
self._state = STATE_UNKNOWN
bypassed = False
for zone in zones:
@@ -100,11 +105,11 @@ class NX584Alarm(alarm.AlarmControlPanel):
break
if not part['armed']:
return STATE_ALARM_DISARMED
self._state = STATE_ALARM_DISARMED
elif bypassed:
return STATE_ALARM_ARMED_HOME
self._state = STATE_ALARM_ARMED_HOME
else:
return STATE_ALARM_ARMED_AWAY
self._state = STATE_ALARM_ARMED_AWAY
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -4,44 +4,123 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
import copy
import enum
import logging
import uuid
from datetime import datetime
import voluptuous as vol
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import template, script
from homeassistant.helpers import template, script, config_validation as cv
from homeassistant.components.http import HomeAssistantView
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
API_ENDPOINT = '/api/alexa'
INTENTS_API_ENDPOINT = '/api/alexa'
FLASH_BRIEFINGS_API_ENDPOINT = '/api/alexa/flash_briefings/<briefing_id>'
CONF_ACTION = 'action'
CONF_CARD = 'card'
CONF_INTENTS = 'intents'
CONF_SPEECH = 'speech'
CONF_TYPE = 'type'
CONF_TITLE = 'title'
CONF_CONTENT = 'content'
CONF_TEXT = 'text'
CONF_FLASH_BRIEFINGS = 'flash_briefings'
CONF_UID = 'uid'
CONF_DATE = 'date'
CONF_TITLE = 'title'
CONF_AUDIO = 'audio'
CONF_TEXT = 'text'
CONF_DISPLAY_URL = 'display_url'
ATTR_UID = 'uid'
ATTR_UPDATE_DATE = 'updateDate'
ATTR_TITLE_TEXT = 'titleText'
ATTR_STREAM_URL = 'streamUrl'
ATTR_MAIN_TEXT = 'mainText'
ATTR_REDIRECTION_URL = 'redirectionURL'
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z'
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
CONF_INTENTS: {
cv.string: {
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_CARD): {
vol.Required(CONF_TYPE): cv.enum(CardType),
vol.Required(CONF_TITLE): cv.template,
vol.Required(CONF_CONTENT): cv.template,
},
vol.Optional(CONF_SPEECH): {
vol.Required(CONF_TYPE): cv.enum(SpeechType),
vol.Required(CONF_TEXT): cv.template,
}
}
},
CONF_FLASH_BRIEFINGS: {
cv.string: vol.All(cv.ensure_list, [{
vol.Required(CONF_UID, default=str(uuid.uuid4())): cv.string,
vol.Optional(CONF_DATE, default=datetime.utcnow()): cv.string,
vol.Required(CONF_TITLE): cv.template,
vol.Optional(CONF_AUDIO): cv.template,
vol.Required(CONF_TEXT, default=""): cv.template,
vol.Optional(CONF_DISPLAY_URL): cv.template,
}]),
}
}
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Activate Alexa component."""
hass.wsgi.register_view(AlexaView(hass,
config[DOMAIN].get(CONF_INTENTS, {})))
intents = config[DOMAIN].get(CONF_INTENTS, {})
flash_briefings = config[DOMAIN].get(CONF_FLASH_BRIEFINGS, {})
hass.wsgi.register_view(AlexaIntentsView(hass, intents))
hass.wsgi.register_view(AlexaFlashBriefingView(hass, flash_briefings))
return True
class AlexaView(HomeAssistantView):
class AlexaIntentsView(HomeAssistantView):
"""Handle Alexa requests."""
url = API_ENDPOINT
url = INTENTS_API_ENDPOINT
name = 'api:alexa'
def __init__(self, hass, intents):
"""Initialize Alexa view."""
super().__init__(hass)
intents = copy.deepcopy(intents)
template.attach(hass, intents)
for name, intent in intents.items():
if CONF_ACTION in intent:
intent[CONF_ACTION] = script.Script(
@@ -101,29 +180,15 @@ class AlexaView(HomeAssistantView):
# pylint: disable=unsubscriptable-object
if speech is not None:
response.add_speech(SpeechType[speech['type']], speech['text'])
response.add_speech(speech[CONF_TYPE], speech[CONF_TEXT])
if card is not None:
response.add_card(CardType[card['type']], card['title'],
card['content'])
response.add_card(card[CONF_TYPE], card[CONF_TITLE],
card[CONF_CONTENT])
return self.json(response)
class SpeechType(enum.Enum):
"""The Alexa speech types."""
plaintext = "PlainText"
ssml = "SSML"
class CardType(enum.Enum):
"""The Alexa card types."""
simple = "Simple"
link_account = "LinkAccount"
class AlexaResponse(object):
"""Help generating the response for Alexa."""
@@ -153,8 +218,8 @@ class AlexaResponse(object):
self.card = card
return
card["title"] = self._render(title),
card["content"] = self._render(content)
card["title"] = title.render(self.variables)
card["content"] = content.render(self.variables)
self.card = card
def add_speech(self, speech_type, text):
@@ -163,9 +228,12 @@ class AlexaResponse(object):
key = 'ssml' if speech_type == SpeechType.ssml else 'text'
if isinstance(text, template.Template):
text = text.render(self.variables)
self.speech = {
'type': speech_type.value,
key: self._render(text)
key: text
}
def add_reprompt(self, speech_type, text):
@@ -176,7 +244,7 @@ class AlexaResponse(object):
self.reprompt = {
'type': speech_type.value,
key: self._render(text)
key: text.render(self.variables)
}
def as_dict(self):
@@ -202,6 +270,68 @@ class AlexaResponse(object):
'response': response,
}
def _render(self, template_string):
"""Render a response, adding data from intent if available."""
return template.render(self.hass, template_string, self.variables)
class AlexaFlashBriefingView(HomeAssistantView):
"""Handle Alexa Flash Briefing skill requests."""
url = FLASH_BRIEFINGS_API_ENDPOINT
name = 'api:alexa:flash_briefings'
def __init__(self, hass, flash_briefings):
"""Initialize Alexa view."""
super().__init__(hass)
self.flash_briefings = copy.deepcopy(flash_briefings)
template.attach(hass, self.flash_briefings)
# pylint: disable=too-many-branches
def get(self, request, briefing_id):
"""Handle Alexa Flash Briefing request."""
_LOGGER.debug('Received Alexa flash briefing request for: %s',
briefing_id)
if self.flash_briefings.get(briefing_id) is None:
err = 'No configured Alexa flash briefing was found for: %s'
_LOGGER.error(err, briefing_id)
return self.Response(status=404)
briefing = []
for item in self.flash_briefings.get(briefing_id, []):
output = {}
if item.get(CONF_TITLE) is not None:
if isinstance(item.get(CONF_TITLE), template.Template):
output[ATTR_TITLE_TEXT] = item[CONF_TITLE].render()
else:
output[ATTR_TITLE_TEXT] = item.get(CONF_TITLE)
if item.get(CONF_TEXT) is not None:
if isinstance(item.get(CONF_TEXT), template.Template):
output[ATTR_MAIN_TEXT] = item[CONF_TEXT].render()
else:
output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT)
if item.get(CONF_UID) is not None:
output[ATTR_UID] = item.get(CONF_UID)
if item.get(CONF_AUDIO) is not None:
if isinstance(item.get(CONF_AUDIO), template.Template):
output[ATTR_STREAM_URL] = item[CONF_AUDIO].render()
else:
output[ATTR_STREAM_URL] = item.get(CONF_AUDIO)
if item.get(CONF_DISPLAY_URL) is not None:
if isinstance(item.get(CONF_DISPLAY_URL),
template.Template):
output[ATTR_REDIRECTION_URL] = \
item[CONF_DISPLAY_URL].render()
else:
output[ATTR_REDIRECTION_URL] = item.get(CONF_DISPLAY_URL)
if isinstance(item[CONF_DATE], str):
item[CONF_DATE] = dt_util.parse_datetime(item[CONF_DATE])
output[ATTR_UPDATE_DATE] = item[CONF_DATE].strftime(DATE_FORMAT)
briefing.append(output)
return self.json(briefing)

View File

@@ -4,6 +4,7 @@ Rest API for Home Assistant.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/
"""
import asyncio
import json
import logging
import queue
@@ -79,6 +80,7 @@ class APIEventStream(HomeAssistantView):
if restrict:
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
@asyncio.coroutine
def forward_events(event):
"""Forward events to the open request."""
if event.event_type == EVENT_TIME_CHANGED:
@@ -376,8 +378,8 @@ class APITemplateView(HomeAssistantView):
def post(self, request):
"""Render a template."""
try:
return template.render(self.hass, request.json['template'],
request.json.get('variables'))
tpl = template.Template(request.json['template'], self.hass)
return tpl.render(request.json.get('variables'))
except TemplateError as ex:
return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST)

View File

@@ -13,7 +13,7 @@ from homeassistant.const import (
from homeassistant.const import CONF_PORT
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['PyMata==2.12']
REQUIREMENTS = ['PyMata==2.13']
_LOGGER = logging.getLogger(__name__)

View File

@@ -4,12 +4,14 @@ Allow to setup simple automation rules via the config file.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/automation/
"""
import asyncio
from functools import partial
import logging
import os
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant import config as conf_util
from homeassistant.const import (
@@ -23,27 +25,31 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group']
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
CONF_ALIAS = 'alias'
CONF_HIDE_ENTITY = 'hide_entity'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
CONF_TRIGGER = 'trigger'
CONF_CONDITION_TYPE = 'condition_type'
CONF_INITIAL_STATE = 'initial_state'
CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values'
CONDITION_TYPE_AND = 'and'
CONDITION_TYPE_OR = 'or'
DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND
METHOD_TRIGGER = 'trigger'
METHOD_IF_ACTION = 'if_action'
DEFAULT_HIDE_ENTITY = False
DEFAULT_INITIAL_STATE = True
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
@@ -53,21 +59,14 @@ SERVICE_RELOAD = 'reload'
_LOGGER = logging.getLogger(__name__)
def _platform_validator(method, schema):
"""Generate platform validator for different steps."""
def validator(config):
"""Validate it is a valid platform."""
platform = get_platform(DOMAIN, config[CONF_PLATFORM])
def _platform_validator(config):
"""Validate it is a valid platform."""
platform = get_platform(DOMAIN, config[CONF_PLATFORM])
if not hasattr(platform, method):
raise vol.Invalid('invalid method platform')
if not hasattr(platform, 'TRIGGER_SCHEMA'):
return config
if not hasattr(platform, schema):
return config
return getattr(platform, schema)(config)
return validator
return getattr(platform, 'TRIGGER_SCHEMA')(config)
_TRIGGER_SCHEMA = vol.All(
cv.ensure_list,
@@ -76,32 +75,19 @@ _TRIGGER_SCHEMA = vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN)
}, extra=vol.ALLOW_EXTRA),
_platform_validator(METHOD_TRIGGER, 'TRIGGER_SCHEMA')
_platform_validator
),
]
)
_CONDITION_SCHEMA = vol.Any(
CONDITION_USE_TRIGGER_VALUES,
vol.All(
cv.ensure_list,
[
vol.All(
vol.Schema({
CONF_PLATFORM: str,
CONF_CONDITION: str,
}, extra=vol.ALLOW_EXTRA),
cv.has_at_least_one_key(CONF_PLATFORM, CONF_CONDITION),
),
]
)
)
_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_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean,
vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA,
vol.Required(CONF_CONDITION_TYPE, default=DEFAULT_CONDITION_TYPE):
vol.All(vol.Lower, vol.Any(CONDITION_TYPE_AND, CONDITION_TYPE_OR)),
vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
})
@@ -160,9 +146,11 @@ def reload(hass):
def setup(hass, config):
"""Setup the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_AUTOMATIONS)
success = _process_config(hass, config, component)
success = run_coroutine_threadsafe(
_async_process_config(hass, config, component), hass.loop).result()
if not success:
return False
@@ -170,22 +158,36 @@ def setup(hass, config):
descriptions = conf_util.load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@callback
def trigger_service_handler(service_call):
"""Handle automation triggers."""
for entity in component.extract_from_service(service_call):
entity.trigger(service_call.data.get(ATTR_VARIABLES))
for entity in component.async_extract_from_service(service_call):
hass.loop.create_task(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
def service_handler(service_call):
"""Handle automation service calls."""
for entity in component.extract_from_service(service_call):
getattr(entity, service_call.service)()
@callback
def turn_onoff_service_handler(service_call):
"""Handle automation turn on/off service calls."""
method = 'async_{}'.format(service_call.service)
for entity in component.async_extract_from_service(service_call):
hass.loop.create_task(getattr(entity, method)())
@callback
def toggle_service_handler(service_call):
"""Handle automation toggle service calls."""
for entity in component.async_extract_from_service(service_call):
if entity.is_on:
hass.loop.create_task(entity.async_turn_off())
else:
hass.loop.create_task(entity.async_turn_on())
@asyncio.coroutine
def reload_service_handler(service_call):
"""Remove all automations and load new ones from config."""
conf = component.prepare_reload()
conf = yield from component.async_prepare_reload()
if conf is None:
return
_process_config(hass, conf, component)
hass.loop.create_task(_async_process_config(hass, conf, component))
hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service_handler,
descriptions.get(SERVICE_TRIGGER),
@@ -195,8 +197,12 @@ def setup(hass, config):
descriptions.get(SERVICE_RELOAD),
schema=RELOAD_SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE):
hass.services.register(DOMAIN, service, service_handler,
hass.services.register(DOMAIN, SERVICE_TOGGLE, toggle_service_handler,
descriptions.get(SERVICE_TOGGLE),
schema=SERVICE_SCHEMA)
for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF):
hass.services.register(DOMAIN, service, turn_onoff_service_handler,
descriptions.get(service),
schema=SERVICE_SCHEMA)
@@ -206,15 +212,19 @@ def setup(hass, config):
class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
def __init__(self, name, attach_triggers, cond_func, action):
# pylint: disable=abstract-method
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, name, async_attach_triggers, cond_func, async_action,
hidden):
"""Initialize an automation entity."""
self._name = name
self._attach_triggers = attach_triggers
self._detach_triggers = attach_triggers(self.trigger)
self._async_attach_triggers = async_attach_triggers
self._async_detach_triggers = None
self._cond_func = cond_func
self._action = action
self._enabled = True
self._async_action = async_action
self._enabled = False
self._last_triggered = None
self._hidden = hidden
@property
def name(self):
@@ -233,46 +243,77 @@ class AutomationEntity(ToggleEntity):
ATTR_LAST_TRIGGERED: self._last_triggered
}
@property
def hidden(self) -> bool:
"""Return True if the automation entity should be hidden from UIs."""
return self._hidden
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._enabled
def turn_on(self, **kwargs) -> None:
"""Turn the entity on."""
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
if self._enabled:
return
self._detach_triggers = self._attach_triggers(self.trigger)
self._enabled = True
self.update_ha_state()
yield from self.async_enable()
self.hass.loop.create_task(self.async_update_ha_state())
def turn_off(self, **kwargs) -> None:
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
"""Turn the entity off."""
if not self._enabled:
return
self._detach_triggers()
self._detach_triggers = None
self._async_detach_triggers()
self._async_detach_triggers = None
self._enabled = False
self.update_ha_state()
# It's important that the update is finished before this method
# ends because async_remove depends on it.
yield from self.async_update_ha_state()
def trigger(self, variables):
"""Trigger automation."""
if self._cond_func(variables):
self._action(variables)
@asyncio.coroutine
def async_trigger(self, variables, skip_condition=False):
"""Trigger automation.
This method is a coroutine.
"""
if skip_condition or self._cond_func(variables):
yield from self._async_action(self.entity_id, variables)
self._last_triggered = utcnow()
self.update_ha_state()
self.hass.loop.create_task(self.async_update_ha_state())
def remove(self):
@asyncio.coroutine
def async_remove(self):
"""Remove automation from HASS."""
self.turn_off()
super().remove()
yield from self.async_turn_off()
yield from super().async_remove()
@asyncio.coroutine
def async_enable(self):
"""Enable this automation entity.
This method is a coroutine.
"""
if self._enabled:
return
self._async_detach_triggers = yield from self._async_attach_triggers(
self.async_trigger)
self._enabled = True
def _process_config(hass, config, component):
"""Process config and add automations."""
success = False
@asyncio.coroutine
def _async_process_config(hass, config, component):
"""Process config and add automations.
This method is a coroutine.
"""
entities = []
tasks = []
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
@@ -281,10 +322,13 @@ def _process_config(hass, config, component):
name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key,
list_no)
action = _get_action(hass, config_block.get(CONF_ACTION, {}), name)
hidden = config_block[CONF_HIDE_ENTITY]
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
name)
if CONF_CONDITION in config_block:
cond_func = _process_if(hass, config, config_block)
cond_func = _async_process_if(hass, config, config_block)
if cond_func is None:
continue
@@ -293,100 +337,78 @@ def _process_config(hass, config, component):
"""Condition will always pass."""
return True
attach_triggers = partial(_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, attach_triggers, cond_func, action)
component.add_entities((entity,))
success = True
async_attach_triggers = partial(
_async_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, async_attach_triggers, cond_func,
action, hidden)
if config_block[CONF_INITIAL_STATE]:
tasks.append(entity.async_enable())
entities.append(entity)
return success
yield from asyncio.gather(*tasks, loop=hass.loop)
hass.loop.create_task(component.async_add_entities(entities))
return len(entities) > 0
def _get_action(hass, config, name):
def _async_get_action(hass, config, name):
"""Return an action based on a configuration."""
script_obj = script.Script(hass, config, name)
def action(variables=None):
@asyncio.coroutine
def action(entity_id, variables):
"""Action to be executed."""
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
script_obj.run(variables)
logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id)
hass.loop.create_task(script_obj.async_run(variables))
return action
def _process_if(hass, config, p_config):
def _async_process_if(hass, config, p_config):
"""Process if checks."""
cond_type = p_config.get(CONF_CONDITION_TYPE,
DEFAULT_CONDITION_TYPE).lower()
# Deprecated since 0.19 - 5/5/2016
if cond_type != DEFAULT_CONDITION_TYPE:
_LOGGER.warning('Using condition_type: "or" is deprecated. Please use '
'"condition: or" instead.')
if_configs = p_config.get(CONF_CONDITION)
use_trigger = if_configs == CONDITION_USE_TRIGGER_VALUES
if use_trigger:
if_configs = p_config[CONF_TRIGGER]
checks = []
for if_config in if_configs:
# Deprecated except for used by use_trigger_values
# since 0.19 - 5/5/2016
if CONF_PLATFORM in if_config:
if not use_trigger:
_LOGGER.warning("Please switch your condition configuration "
"to use 'condition' instead of 'platform'.")
if_config = dict(if_config)
if_config[CONF_CONDITION] = if_config.pop(CONF_PLATFORM)
# To support use_trigger_values with state trigger accepting
# multiple entity_ids to monitor.
if_entity_id = if_config.get(ATTR_ENTITY_ID)
if isinstance(if_entity_id, list) and len(if_entity_id) == 1:
if_config[ATTR_ENTITY_ID] = if_entity_id[0]
try:
checks.append(condition.from_config(if_config))
checks.append(condition.async_from_config(if_config, False))
except HomeAssistantError as ex:
# Invalid conditions are allowed if we base it on trigger
if use_trigger:
_LOGGER.warning('Ignoring invalid condition: %s', ex)
else:
_LOGGER.warning('Invalid condition: %s', ex)
return None
_LOGGER.warning('Invalid condition: %s', ex)
return None
if cond_type == CONDITION_TYPE_AND:
def if_action(variables=None):
"""AND all conditions."""
return all(check(hass, variables) for check in checks)
else:
def if_action(variables=None):
"""OR all conditions."""
return any(check(hass, variables) for check in checks)
def if_action(variables=None):
"""AND all conditions."""
return all(check(hass, variables) for check in checks)
return if_action
def _process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers."""
@asyncio.coroutine
def _async_process_trigger(hass, config, trigger_configs, name, action):
"""Setup the triggers.
This method is a coroutine.
"""
removes = []
for conf in trigger_configs:
platform = _resolve_platform(METHOD_TRIGGER, hass, config,
conf.get(CONF_PLATFORM))
if platform is None:
continue
platform = yield from hass.loop.run_in_executor(
None, prepare_setup_platform, hass, config, DOMAIN,
conf.get(CONF_PLATFORM))
remove = platform.trigger(hass, conf, action)
if platform is None:
return None
remove = platform.async_trigger(hass, conf, action)
if not remove:
_LOGGER.error("Error setting up rule %s", name)
_LOGGER.error("Error setting up trigger %s", name)
continue
_LOGGER.info("Initialized rule %s", name)
_LOGGER.info("Initialized trigger %s", name)
removes.append(remove)
if not removes:
@@ -398,17 +420,3 @@ def _process_trigger(hass, config, trigger_configs, name, action):
remove()
return remove_triggers
def _resolve_platform(method, hass, config, platform):
"""Find the automation platform."""
if platform is None:
return None
platform = prepare_setup_platform(hass, config, DOMAIN, platform)
if platform is None or not hasattr(platform, method):
_LOGGER.error("Unknown automation platform specified for %s: %s",
method, platform)
return None
return platform

View File

@@ -8,6 +8,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
@@ -23,20 +24,21 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data = config.get(CONF_EVENT_DATA)
@callback
def handle_event(event):
"""Listen for events and calls the action when data matches."""
if not event_data or all(val == event.data.get(key) for key, val
in event_data.items()):
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
return hass.bus.listen(event_type, handle_event)
return hass.bus.async_listen(event_type, handle_event)

View File

@@ -6,6 +6,7 @@ at https://home-assistant.io/components/automation/#mqtt-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv
@@ -21,15 +22,16 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
@callback
def mqtt_automation_listener(msg_topic, msg_payload, qos):
"""Listen for MQTT messages."""
if payload is None or payload == msg_payload:
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'mqtt',
'topic': msg_topic,
@@ -38,4 +40,4 @@ def trigger(hass, config, action):
}
})
return mqtt.subscribe(hass, topic, mqtt_automation_listener)
return mqtt.async_subscribe(hass, topic, mqtt_automation_listener)

View File

@@ -8,10 +8,11 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID,
CONF_BELOW, CONF_ABOVE)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import condition, config_validation as cv
TRIGGER_SCHEMA = vol.All(vol.Schema({
@@ -25,14 +26,16 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__)
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW)
above = config.get(CONF_ABOVE)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
# pylint: disable=unused-argument
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
if to_s is None:
@@ -48,19 +51,19 @@ def trigger(hass, config, action):
}
# If new one doesn't match, nothing to do
if not condition.numeric_state(
if not condition.async_numeric_state(
hass, to_s, below, above, value_template, variables):
return
# Only match if old didn't exist or existed but didn't match
# Written as: skip if old one did exist and matched
if from_s is not None and condition.numeric_state(
if from_s is not None and condition.async_numeric_state(
hass, from_s, below, above, value_template, variables):
return
variables['trigger']['from_state'] = from_s
variables['trigger']['to_state'] = to_s
action(variables)
hass.async_run_job(action, variables)
return track_state_change(hass, entity_id, state_automation_listener)
return async_track_state_change(hass, entity_id, state_automation_listener)

View File

@@ -6,9 +6,11 @@ at https://home-assistant.io/components/automation/#state-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
from homeassistant.const import MATCH_ALL, CONF_PLATFORM
from homeassistant.helpers.event import track_state_change, track_point_in_time
from homeassistant.helpers.event import (
async_track_state_change, async_track_point_in_utc_time)
import homeassistant.helpers.config_validation as cv
CONF_ENTITY_ID = "entity_id"
@@ -32,22 +34,23 @@ TRIGGER_SCHEMA = vol.All(
)
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
to_state = config.get(CONF_TO) or config.get(CONF_STATE) or MATCH_ALL
time_delta = config.get(CONF_FOR)
remove_state_for_cancel = None
remove_state_for_listener = None
async_remove_state_for_cancel = None
async_remove_state_for_listener = None
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal remove_state_for_cancel, remove_state_for_listener
nonlocal async_remove_state_for_cancel, async_remove_state_for_listener
def call_action():
"""Call action with right context."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'state',
'entity_id': entity,
@@ -61,35 +64,37 @@ def trigger(hass, config, action):
call_action()
return
@callback
def state_for_listener(now):
"""Fire on state changes after a delay and calls action."""
remove_state_for_cancel()
async_remove_state_for_cancel()
call_action()
@callback
def state_for_cancel_listener(entity, inner_from_s, inner_to_s):
"""Fire on changes and cancel for listener if changed."""
if inner_to_s.state == to_s.state:
return
remove_state_for_listener()
remove_state_for_cancel()
async_remove_state_for_listener()
async_remove_state_for_cancel()
remove_state_for_listener = track_point_in_time(
async_remove_state_for_listener = async_track_point_in_utc_time(
hass, state_for_listener, dt_util.utcnow() + time_delta)
remove_state_for_cancel = track_state_change(
async_remove_state_for_cancel = async_track_state_change(
hass, entity, state_for_cancel_listener)
unsub = track_state_change(hass, entity_id, state_automation_listener,
from_state, to_state)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)
def remove():
"""Remove state listeners."""
def async_remove():
"""Remove state listeners async."""
unsub()
# pylint: disable=not-callable
if remove_state_for_cancel is not None:
remove_state_for_cancel()
if async_remove_state_for_cancel is not None:
async_remove_state_for_cancel()
if remove_state_for_listener is not None:
remove_state_for_listener()
if async_remove_state_for_listener is not None:
async_remove_state_for_listener()
return remove
return async_remove

View File

@@ -9,9 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE)
from homeassistant.helpers.event import track_sunrise, track_sunset
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['sun']
@@ -25,14 +26,15 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)
@callback
def call_action():
"""Call action with right context."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'sun',
'event': event,
@@ -42,6 +44,6 @@ def trigger(hass, config, action):
# Do something to call action
if event == SUN_EVENT_SUNRISE:
return track_sunrise(hass, call_action, offset)
return async_track_sunrise(hass, call_action, offset)
else:
return track_sunset(hass, call_action, offset)
return async_track_sunset(hass, call_action, offset)

View File

@@ -8,10 +8,10 @@ import logging
import voluptuous as vol
from homeassistant.const import (
CONF_VALUE_TEMPLATE, CONF_PLATFORM, MATCH_ALL)
from homeassistant.core import callback
from homeassistant.const import CONF_VALUE_TEMPLATE, CONF_PLATFORM
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
@@ -23,22 +23,24 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
# Local variable to keep track of if the action has already been triggered
already_triggered = False
@callback
def state_changed_listener(entity_id, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal already_triggered
template_result = condition.template(hass, value_template)
template_result = condition.async_template(hass, value_template)
# Check to see if template returns true
if template_result and not already_triggered:
already_triggered = True
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'template',
'entity_id': entity_id,
@@ -49,4 +51,5 @@ def trigger(hass, config, action):
elif not template_result:
already_triggered = False
return track_state_change(hass, MATCH_ALL, state_changed_listener)
return async_track_state_change(hass, value_template.extract_entities(),
state_changed_listener)

View File

@@ -8,9 +8,10 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_AFTER, CONF_PLATFORM
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import track_time_change
from homeassistant.helpers.event import async_track_time_change
CONF_HOURS = "hours"
CONF_MINUTES = "minutes"
@@ -28,7 +29,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
CONF_SECONDS, CONF_AFTER))
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
if CONF_AFTER in config:
after = config.get(CONF_AFTER)
@@ -38,14 +39,15 @@ def trigger(hass, config, action):
minutes = config.get(CONF_MINUTES)
seconds = config.get(CONF_SECONDS)
@callback
def time_automation_listener(now):
"""Listen for time changes and calls action."""
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'time',
'now': now,
},
})
return track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)
return async_track_time_change(hass, time_automation_listener,
hour=hours, minute=minutes, second=seconds)

View File

@@ -6,9 +6,10 @@ at https://home-assistant.io/components/automation/#zone-trigger
"""
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_ENTITY_ID, CONF_ZONE, MATCH_ALL, CONF_PLATFORM)
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers import (
condition, config_validation as cv, location)
@@ -25,12 +26,13 @@ TRIGGER_SCHEMA = vol.Schema({
})
def trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
event = config.get(CONF_EVENT)
@callback
def zone_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
if from_s and not location.has_location(from_s) or \
@@ -47,7 +49,7 @@ def trigger(hass, config, action):
# pylint: disable=too-many-boolean-expressions
if event == EVENT_ENTER and not from_match and to_match or \
event == EVENT_LEAVE and from_match and not to_match:
action({
hass.async_run_job(action, {
'trigger': {
'platform': 'zone',
'entity_id': entity,
@@ -58,5 +60,5 @@ def trigger(hass, config, action):
},
})
return track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)
return async_track_state_change(hass, entity_id, zone_automation_listener,
MATCH_ALL, MATCH_ALL)

View File

@@ -1,5 +1,5 @@
"""
Support for exposed aREST RESTful API of a device.
Support for an exposed aREST RESTful API of a device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.arest/
@@ -8,34 +8,32 @@ import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (BinarySensorDevice,
SENSOR_CLASSES)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_SENSOR_CLASS)
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
CONF_RESOURCE = 'resource'
CONF_PIN = 'pin'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_PIN): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the aREST binary sensor."""
resource = config.get(CONF_RESOURCE)
pin = config.get(CONF_PIN)
sensor_class = config.get('sensor_class')
if sensor_class not in SENSOR_CLASSES:
_LOGGER.warning('Unknown sensor class: %s', sensor_class)
sensor_class = None
if None in (resource, pin):
_LOGGER.error('Not all required config keys present: %s',
', '.join((CONF_RESOURCE, CONF_PIN)))
return False
sensor_class = config.get(CONF_SENSOR_CLASS)
try:
response = requests.get(resource, timeout=10).json()
@@ -52,11 +50,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
arest = ArestData(resource, pin)
add_devices([ArestBinarySensor(
arest,
resource,
config.get('name', response['name']),
sensor_class,
pin)])
arest, resource, config.get(CONF_NAME, response[CONF_NAME]),
sensor_class, pin)])
# pylint: disable=too-many-instance-attributes, too-many-arguments

View File

@@ -15,7 +15,6 @@ from homeassistant.components.sensor.command_line import CommandSensorData
from homeassistant.const import (
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_COMMAND)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -45,7 +44,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload_on = config.get(CONF_PAYLOAD_ON)
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(command)
add_devices([CommandBinarySensor(
@@ -91,8 +91,8 @@ class CommandBinarySensor(BinarySensorDevice):
value = self.data.value
if self._value_template is not None:
value = template.render_with_possible_json_value(
self._hass, self._value_template, value, False)
value = self._value_template.render_with_possible_json_value(
value, False)
if value == self._payload_on:
self._state = True
elif value == self._payload_off:

View File

@@ -0,0 +1,143 @@
"""
Support for exposing Concord232 elements as sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.concord232/
"""
import datetime
import logging
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, SENSOR_CLASSES)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
import requests
import voluptuous as vol
REQUIREMENTS = ['concord232==0.14']
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_ZONES = 'exclude_zones'
CONF_ZONE_TYPES = 'zone_types'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXCLUDE_ZONES, default=[]):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
})
SCAN_INTERVAL = 1
DEFAULT_NAME = "Alarm"
# pylint: disable=too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Concord232 binary sensor platform."""
from concord232 import client as concord232_client
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
exclude = config.get(CONF_EXCLUDE_ZONES)
zone_types = config.get(CONF_ZONE_TYPES)
sensors = []
try:
_LOGGER.debug('Initializing Client.')
client = concord232_client.Client('http://{}:{}'
.format(host, port))
client.zones = client.list_zones()
client.last_zone_update = datetime.datetime.now()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to Concord232: %s', str(ex))
return False
for zone in client.zones:
_LOGGER.info('Loading Zone found: %s', zone['name'])
if zone['number'] not in exclude:
sensors.append(Concord232ZoneSensor(
hass,
client,
zone,
zone_types.get(zone['number'], get_opening_type(zone))))
add_devices(sensors)
return True
def get_opening_type(zone):
"""Helper function to try to guess sensor type frm name."""
if "MOTION" in zone["name"]:
return "motion"
if "KEY" in zone["name"]:
return "safety"
if "SMOKE" in zone["name"]:
return "smoke"
if "WATER" in zone["name"]:
return "water"
return "opening"
class Concord232ZoneSensor(BinarySensorDevice):
"""Representation of a Concord232 zone as a sensor."""
def __init__(self, hass, client, zone, zone_type):
"""Initialize the Concord232 binary sensor."""
self._hass = hass
self._client = client
self._zone = zone
self._number = zone['number']
self._zone_type = zone_type
self.update()
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return self._zone_type
@property
def should_poll(self):
"""No polling needed."""
return True
@property
def name(self):
"""Return the name of the binary sensor."""
return self._zone['name']
@property
def is_on(self):
"""Return true if the binary sensor is on."""
# True means "faulted" or "open" or "abnormal state"
return bool(self._zone['state'] == 'Normal')
def update(self):
""""Get updated stats from API."""
last_update = datetime.datetime.now() - self._client.last_zone_update
_LOGGER.debug("Zone: %s ", self._zone)
if last_update > datetime.timedelta(seconds=1):
self._client.zones = self._client.list_zones()
self._client.last_zone_update = datetime.datetime.now()
_LOGGER.debug("Updated from Zone: %s", self._zone['name'])
if hasattr(self._client, 'zones'):
self._zone = next((x for x in self._client.zones
if x['number'] == self._number), None)

View File

@@ -0,0 +1,91 @@
"""
Support for monitoring the state of Digital Ocean droplets.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.digital_ocean/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.digital_ocean import (
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS)
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Droplet'
DEFAULT_SENSOR_CLASS = 'motion'
DEPENDENCIES = ['digital_ocean']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Digital Ocean droplet sensor."""
digital_ocean = get_component('digital_ocean')
droplets = config.get(CONF_DROPLETS)
dev = []
for droplet in droplets:
droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet)
dev.append(DigitalOceanBinarySensor(
digital_ocean.DIGITAL_OCEAN, droplet_id))
add_devices(dev)
class DigitalOceanBinarySensor(BinarySensorDevice):
"""Representation of a Digital Ocean droplet sensor."""
def __init__(self, do, droplet_id):
"""Initialize a new Digital Ocean sensor."""
self._digital_ocean = do
self._droplet_id = droplet_id
self._state = None
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self.data.name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.data.status == 'active'
@property
def sensor_class(self):
"""Return the class of this sensor."""
return DEFAULT_SENSOR_CLASS
@property
def state_attributes(self):
"""Return the state attributes of the Digital Ocean droplet."""
return {
ATTR_CREATED_AT: self.data.created_at,
ATTR_DROPLET_ID: self.data.id,
ATTR_DROPLET_NAME: self.data.name,
ATTR_FEATURES: self.data.features,
ATTR_IPV4_ADDRESS: self.data.ip_address,
ATTR_IPV6_ADDRESS: self.data.ip_v6_address,
ATTR_MEMORY: self.data.memory,
ATTR_REGION: self.data.region['name'],
ATTR_VCPUS: self.data.vcpus,
}
def update(self):
"""Update state of sensor."""
self._digital_ocean.update()
for droplet in self._digital_ocean.data:
if droplet.id == self._droplet_id:
self.data = droplet

View File

@@ -68,4 +68,4 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
def _update_callback(self, zone):
"""Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number:
self.update_ha_state()
self.hass.async_add_job(self.update_ha_state)

View File

@@ -10,13 +10,17 @@ from os import path
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (BinarySensorDevice,
PLATFORM_SCHEMA, DOMAIN)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DOMAIN)
from homeassistant.components.ffmpeg import (
get_binary, run_test, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS)
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME,
ATTR_ENTITY_ID)
REQUIREMENTS = ["ha-ffmpeg==0.10"]
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
SERVICE_RESTART = 'ffmpeg_restart'
@@ -29,10 +33,6 @@ MAP_FFMPEG_BIN = [
]
CONF_TOOL = 'tool'
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
CONF_OUTPUT = 'output'
CONF_PEAK = 'peak'
CONF_DURATION = 'duration'
CONF_RESET = 'reset'
@@ -40,11 +40,12 @@ CONF_CHANGES = 'changes'
CONF_REPEAT = 'repeat'
CONF_REPEAT_TIME = 'repeat_time'
DEFAULT_NAME = 'FFmpeg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN),
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string,
vol.Optional(CONF_NAME, default="FFmpeg"): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_OUTPUT): cv.string,
vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int),
@@ -65,16 +66,25 @@ SERVICE_RESTART_SCHEMA = vol.Schema({
})
def restart(hass, entity_id=None):
"""Restart a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_RESTART, data)
# list of all ffmpeg sensors
DEVICES = []
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the binary sensor."""
from haffmpeg import SensorNoise, SensorMotion
# check source
if not run_test(config.get(CONF_INPUT)):
return
# generate sensor object
if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE:
entity = FFmpegNoise(SensorNoise, config)
else:
@@ -88,7 +98,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
# exists service?
if hass.services.has_service(DOMAIN, SERVICE_RESTART):
return True
return
descriptions = load_yaml_config_file(
path.join(path.dirname(__file__), 'services.yaml'))
@@ -105,13 +115,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_devices = DEVICES
for device in _devices:
device.reset_ffmpeg()
device.restart_ffmpeg()
hass.services.register(DOMAIN, SERVICE_RESTART,
_service_handle_restart,
descriptions.get(SERVICE_RESTART),
schema=SERVICE_RESTART_SCHEMA)
return True
class FFmpegBinarySensor(BinarySensorDevice):
@@ -122,7 +131,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
self._state = False
self._config = config
self._name = config.get(CONF_NAME)
self._ffmpeg = ffobj(config.get(CONF_FFMPEG_BIN), self._callback)
self._ffmpeg = ffobj(get_binary(), self._callback)
self._start_ffmpeg(config)
@@ -139,7 +148,7 @@ class FFmpegBinarySensor(BinarySensorDevice):
"""For STOP event to shutdown ffmpeg."""
self._ffmpeg.close()
def reset_ffmpeg(self):
def restart_ffmpeg(self):
"""Restart ffmpeg with new config."""
self._ffmpeg.close()
self._start_ffmpeg(self._config)

View File

@@ -16,6 +16,7 @@ DEPENDENCIES = ['homematic']
SENSOR_TYPES_CLASS = {
"Remote": None,
"ShutterContact": "opening",
"IPShutterContact": "opening",
"Smoke": "smoke",
"SmokeV2": "smoke",
"Motion": "motion",

View File

@@ -0,0 +1,71 @@
"""
Support for ISY994 binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.isy994/
"""
import logging
from typing import Callable # noqa
from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
VALUE_TO_STATE = {
False: STATE_OFF,
True: STATE_ON,
}
UOM = ['2', '78']
STATES = [STATE_OFF, STATE_ON, 'true', 'false']
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Setup the ISY994 binary sensor platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error('A connection has not been made to the ISY controller.')
return False
devices = []
for node in isy.filter_nodes(isy.SENSOR_NODES, units=UOM,
states=STATES):
devices.append(ISYBinarySensorDevice(node))
for program in isy.PROGRAMS.get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
except (KeyError, AssertionError):
pass
else:
devices.append(ISYBinarySensorProgram(program.name, status))
add_devices(devices)
class ISYBinarySensorDevice(isy.ISYDevice, BinarySensorDevice):
"""Representation of an ISY994 binary sensor device."""
def __init__(self, node) -> None:
"""Initialize the ISY994 binary sensor device."""
isy.ISYDevice.__init__(self, node)
@property
def is_on(self) -> bool:
"""Get whether the ISY994 binary sensor device is on."""
return bool(self.value)
class ISYBinarySensorProgram(ISYBinarySensorDevice):
"""Representation of an ISY994 binary sensor program."""
def __init__(self, name, node) -> None:
"""Initialize the ISY994 binary sensor program."""
ISYBinarySensorDevice.__init__(self, node)
self._name = name

View File

@@ -5,17 +5,14 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.knx import (
KNXConfig, KNXGroupAddress)
from homeassistant.components.knx import (KNXConfig, KNXGroupAddress)
DEPENDENCIES = ["knx"]
DEPENDENCIES = ['knx']
def setup_platform(hass, config, add_entities, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the KNX binary sensor platform."""
add_entities([
KNXSwitch(hass, KNXConfig(config))
])
add_devices([KNXSwitch(hass, KNXConfig(config))])
class KNXSwitch(KNXGroupAddress, BinarySensorDevice):

View File

@@ -0,0 +1,61 @@
"""
Support for Modbus Coil sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.modbus/
"""
import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus
from homeassistant.const import CONF_NAME
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['modbus']
CONF_COIL = "coil"
CONF_COILS = "coils"
CONF_SLAVE = "slave"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COILS): [{
vol.Required(CONF_COIL): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_SLAVE): cv.positive_int
}]
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Modbus binary sensors."""
sensors = []
for coil in config.get(CONF_COILS):
sensors.append(ModbusCoilSensor(
coil.get(CONF_NAME),
coil.get(CONF_SLAVE),
coil.get(CONF_COIL)))
add_devices(sensors)
class ModbusCoilSensor(BinarySensorDevice):
"""Modbus coil sensor."""
def __init__(self, name, slave, coil):
"""Initialize the modbus coil sensor."""
self._name = name
self._slave = int(slave) if slave else None
self._coil = int(coil)
self._value = None
@property
def is_on(self):
"""Return the state of the sensor."""
return self._value
def update(self):
"""Update the state of the sensor."""
result = modbus.HUB.read_coils(self._slave, self._coil, 1)
self._value = result.bits[0]

View File

@@ -15,7 +15,6 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_SENSOR_CLASS)
from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -37,6 +36,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT binary sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttBinarySensor(
hass,
config.get(CONF_NAME),
@@ -45,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
config.get(CONF_QOS),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_VALUE_TEMPLATE)
value_template
)])
@@ -68,8 +70,8 @@ class MqttBinarySensor(BinarySensorDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._payload_on:
self._state = True
self.update_ha_state()

View File

@@ -0,0 +1,127 @@
"""
Support for the Netatmo binary sensors.
The binary sensors based on events seen by the NetatmoCamera
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.netatmo/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.netatmo import WelcomeData
from homeassistant.loader import get_component
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ["netatmo"]
_LOGGER = logging.getLogger(__name__)
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
"Someone known": "motion",
"Someone unknown": "motion",
"Motion": "motion",
}
CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES.keys()):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo binary sensor."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME, None)
import lnetatmo
try:
data = WelcomeData(netatmo.NETATMO_AUTH, home)
if data.get_camera_names() == []:
return None
except lnetatmo.NoDevice:
return None
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)
for camera_name in data.get_camera_names():
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]:
continue
for variable in sensors:
add_devices([WelcomeBinarySensor(data, camera_name, home,
variable)])
class WelcomeBinarySensor(BinarySensorDevice):
"""Represent a single binary sensor in a Netatmo Welcome device."""
def __init__(self, data, camera_name, home, sensor):
"""Setup for access to the Netatmo camera events."""
self._data = data
self._camera_name = camera_name
self._home = home
if home:
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
self._sensor_name = sensor
self._name += ' ' + sensor
camera_id = data.welcomedata.cameraByName(camera=camera_name,
home=home)['id']
self._unique_id = "Welcome_binary_sensor {0} - {1}".format(self._name,
camera_id)
self.update()
@property
def name(self):
"""The name of the Netatmo device and this sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
@property
def sensor_class(self):
"""Return the class of this sensor, from SENSOR_CLASSES."""
return SENSOR_TYPES.get(self._sensor_name)
@property
def is_on(self):
"""Return true if binary sensor is on."""
return self._state
def update(self):
"""Request an update from the Netatmo API."""
self._data.update()
self._data.welcomedata.updateEvent(home=self._data.home)
if self._sensor_name == "Someone known":
self._state =\
self._data.welcomedata.someoneKnownSeen(self._home,
self._camera_name)
elif self._sensor_name == "Someone unknown":
self._state =\
self._data.welcomedata.someoneUnknownSeen(self._home,
self._camera_name)
elif self._sensor_name == "Motion":
self._state =\
self._data.welcomedata.motionDetected(self._home,
self._camera_name)
else:
return None

View File

@@ -1,41 +1,56 @@
"""
Support for exposing nx584 elements as sensors.
Support for exposing NX584 elements as sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.nx584/
https://home-assistant.io/components/binary_sensor.nx584/
"""
import logging
import threading
import time
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor import (
SENSOR_CLASSES, BinarySensorDevice)
SENSOR_CLASSES, BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_HOST, CONF_PORT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.2']
_LOGGER = logging.getLogger(__name__)
CONF_EXCLUDE_ZONES = 'exclude_zones'
CONF_ZONE_TYPES = 'zone_types'
DEFAULT_HOST = 'localhost'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(SENSOR_CLASSES),
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXCLUDE_ZONES, default=[]):
vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup nx584 binary sensor platform."""
"""Setup the NX584 binary sensor platform."""
from nx584 import client as nx584_client
host = config.get('host', 'localhost:5007')
exclude = config.get('exclude_zones', [])
zone_types = config.get('zone_types', {})
if not all(isinstance(zone, int) for zone in exclude):
_LOGGER.error('Invalid excluded zone specified (use zone number)')
return False
if not all(isinstance(zone, int) and ztype in SENSOR_CLASSES
for zone, ztype in zone_types.items()):
_LOGGER.error('Invalid zone_types entry')
return False
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
exclude = config.get(CONF_EXCLUDE_ZONES)
zone_types = config.get(CONF_ZONE_TYPES)
try:
client = nx584_client.Client('http://%s' % host)
client = nx584_client.Client('http://{}:{}'.format(host, port))
zones = client.list_zones()
except requests.exceptions.ConnectionError as ex:
_LOGGER.error('Unable to connect to NX584: %s', str(ex))
@@ -43,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
version = [int(v) for v in client.get_version().split('.')]
if version < [1, 1]:
_LOGGER.error('NX584 is too old to use for sensors (>=0.2 required)')
_LOGGER.error("NX584 is too old to use for sensors (>=0.2 required)")
return False
zone_sensors = {
@@ -57,13 +72,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
watcher = NX584Watcher(client, zone_sensors)
watcher.start()
else:
_LOGGER.warning('No zones found on NX584')
_LOGGER.warning("No zones found on NX584")
return True
class NX584ZoneSensor(BinarySensorDevice):
"""Represents a NX584 zone as a sensor."""
"""Representation of a NX584 zone as a sensor."""
def __init__(self, zone, zone_type):
"""Initialize the nx594 binary sensor."""
@@ -96,7 +110,7 @@ class NX584Watcher(threading.Thread):
"""Event listener thread to process NX584 events."""
def __init__(self, client, zone_sensors):
"""Initialize nx584 watcher thread."""
"""Initialize NX584 watcher thread."""
super(NX584Watcher, self).__init__()
self.daemon = True
self._client = client
@@ -130,5 +144,5 @@ class NX584Watcher(threading.Thread):
try:
self._run()
except requests.exceptions.ConnectionError:
_LOGGER.error('Failed to reach NX584 server')
_LOGGER.error("Failed to reach NX584 server")
time.sleep(10)

View File

@@ -7,14 +7,16 @@ https://home-assistant.io/components/binary_sensor.rest/
import logging
import voluptuous as vol
from requests.auth import HTTPBasicAuth, HTTPDigestAuth
from homeassistant.components.binary_sensor import (
BinarySensorDevice, SENSOR_CLASSES_SCHEMA, PLATFORM_SCHEMA)
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE,
CONF_SENSOR_CLASS, CONF_VERIFY_SSL)
from homeassistant.helpers import template
CONF_SENSOR_CLASS, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD,
CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -25,16 +27,21 @@ DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_HEADERS): {cv.string: cv.string},
vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PAYLOAD): cv.string,
vol.Optional(CONF_SENSOR_CLASS): SENSOR_CLASSES_SCHEMA,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
# pylint: disable=unused-variable
# pylint: disable=unused-variable, too-many-locals
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the REST binary sensor."""
name = config.get(CONF_NAME)
@@ -42,14 +49,27 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
method = config.get(CONF_METHOD)
payload = config.get(CONF_PAYLOAD)
verify_ssl = config.get(CONF_VERIFY_SSL)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
headers = config.get(CONF_HEADERS)
sensor_class = config.get(CONF_SENSOR_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
rest = RestData(method, resource, payload, verify_ssl)
if username and password:
if config.get(CONF_AUTHENTICATION) == HTTP_DIGEST_AUTHENTICATION:
auth = HTTPDigestAuth(username, password)
else:
auth = HTTPBasicAuth(username, password)
else:
auth = None
rest = RestData(method, resource, auth, headers, payload, verify_ssl)
rest.update()
if rest.data is None:
_LOGGER.error('Unable to fetch REST data')
_LOGGER.error("Unable to fetch REST data from %s", resource)
return False
add_devices([RestBinarySensor(
@@ -88,8 +108,8 @@ class RestBinarySensor(BinarySensorDevice):
return False
if self._value_template is not None:
response = template.render_with_possible_json_value(
self._hass, self._value_template, self.rest.data, False)
response = self._value_template.\
async_render_with_possible_json_value(self.rest.data, False)
try:
return bool(int(response))

View File

@@ -6,16 +6,37 @@ https://home-assistant.io/components/binary_sensor.rpi_gpio/
"""
import logging
import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import DEVICE_DEFAULT_NAME
import voluptuous as vol
import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_BOUNCETIME = 'bouncetime'
CONF_INVERT_LOGIC = 'invert_logic'
CONF_PORTS = 'ports'
CONF_PULL_MODE = 'pull_mode'
DEFAULT_PULL_MODE = "UP"
DEFAULT_BOUNCETIME = 50
DEFAULT_INVERT_LOGIC = False
DEFAULT_PULL_MODE = 'UP'
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
_SENSORS_SCHEMA = vol.Schema({
cv.positive_int: cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PORTS): _SENSORS_SCHEMA,
vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int,
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string,
})
# pylint: disable=unused-argument

View File

@@ -0,0 +1,53 @@
"""
Support for SleepIQ sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.sleepiq/
"""
from homeassistant.components import sleepiq
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['sleepiq']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the SleepIQ sensors."""
if discovery_info is None:
return
data = sleepiq.DATA
data.update()
dev = list()
for bed_id, _ in data.beds.items():
for side in sleepiq.SIDES:
dev.append(IsInBedBinarySensor(data, bed_id, side))
add_devices(dev)
# pylint: disable=too-many-instance-attributes
class IsInBedBinarySensor(sleepiq.SleepIQSensor, BinarySensorDevice):
"""Implementation of a SleepIQ presence sensor."""
def __init__(self, sleepiq_data, bed_id, side):
"""Initialize the sensor."""
sleepiq.SleepIQSensor.__init__(self, sleepiq_data, bed_id, side)
self.type = sleepiq.IS_IN_BED
self._state = None
self._name = sleepiq.SENSOR_TYPES[self.type]
self.update()
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state is True
@property
def sensor_class(self):
"""Return the class of this sensor."""
return "occupancy"
def update(self):
"""Get the latest data from SleepIQ and updates the states."""
sleepiq.SleepIQSensor.update(self)
self._state = self.side.is_in_bed

View File

@@ -7,21 +7,20 @@ https://home-assistant.io/components/binary_sensor.tcp/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.tcp import Sensor, CONF_VALUE_ON
from homeassistant.components.sensor.tcp import (
TcpSensor, CONF_VALUE_ON, PLATFORM_SCHEMA)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create the binary sensor."""
if not BinarySensor.validate_config(config):
return False
add_entities((BinarySensor(hass, config),))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
class BinarySensor(BinarySensorDevice, Sensor):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the TCP binary sensor."""
add_devices([TcpBinarySensor(hass, config)])
class TcpBinarySensor(BinarySensorDevice, TcpSensor):
"""A binary sensor which is on when its state == CONF_VALUE_ON."""
required = (CONF_VALUE_ON,)

View File

@@ -4,20 +4,21 @@ Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
SENSOR_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, MATCH_ALL, CONF_VALUE_TEMPLATE,
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -25,7 +26,7 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID, default=MATCH_ALL): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_SENSOR_CLASS, default=None): SENSOR_CLASSES_SCHEMA
})
@@ -34,16 +35,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup template binary sensors."""
sensors = []
for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE]
entity_ids = device_config[ATTR_ENTITY_ID]
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
sensor_class = device_config.get(CONF_SENSOR_CLASS)
if value_template is not None:
value_template.hass = hass
sensors.append(
BinarySensorTemplate(
hass,
@@ -56,8 +62,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if not sensors:
_LOGGER.error('No sensors added')
return False
add_devices(sensors)
hass.loop.create_task(async_add_devices(sensors))
return True
@@ -69,20 +75,22 @@ class BinarySensorTemplate(BinarySensorDevice):
value_template, entity_ids):
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device,
hass=hass)
self._name = friendly_name
self._sensor_class = sensor_class
self._template = value_template
self._state = None
self.update()
self._async_render()
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.update_ha_state(True)
hass.loop.create_task(self.async_update_ha_state(True))
track_state_change(hass, entity_ids, template_bsensor_state_listener)
async_track_state_change(
hass, entity_ids, template_bsensor_state_listener)
@property
def name(self):
@@ -104,11 +112,15 @@ class BinarySensorTemplate(BinarySensorDevice):
"""No polling needed."""
return False
def update(self):
"""Get the latest data and update the state."""
@asyncio.coroutine
def async_update(self):
"""Update the state from the template."""
self._async_render()
def _async_render(self):
"""Render the state from the template."""
try:
self._state = template.render(
self.hass, self._template).lower() == 'true'
self._state = self._template.async_render().lower() == 'true'
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):

View File

@@ -2,7 +2,7 @@
A sensor that monitors trands in other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.template/
https://home-assistant.io/components/sensor.trend/
"""
import logging
import voluptuous as vol
@@ -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 template sensors."""
"""Setup the trend sensors."""
sensors = []
for device, device_config in config[CONF_SENSORS].items():
@@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SensorTrend(BinarySensorDevice):
"""Representation of a Template Sensor."""
"""Representation of a trend Sensor."""
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, hass, device_id, friendly_name,
@@ -90,14 +90,14 @@ class SensorTrend(BinarySensorDevice):
self.update()
def template_sensor_state_listener(entity, old_state, new_state):
def trend_sensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
self.from_state = old_state
self.to_state = new_state
self.update_ha_state(True)
track_state_change(hass, target_entity,
template_sensor_state_listener)
trend_sensor_state_listener)
@property
def name(self):

View File

@@ -1,19 +1,17 @@
"""
Support for Wink sensors.
Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/sensor.wink/
at https://home-assistant.io/components/binary_sensor.wink/
"""
import logging
import json
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.sensor.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.helpers.entity import Entity
from homeassistant.loader import get_component
REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2']
DEPENDENCIES = ['wink']
# These are the available sensors mapped to binary_sensor class
SENSOR_TYPES = {
@@ -21,7 +19,11 @@ SENSOR_TYPES = {
"brightness": "light",
"vibration": "vibration",
"loudness": "sound",
"liquid_detected": "moisture"
"liquid_detected": "moisture",
"motion": "motion",
"presence": "occupancy",
"co_detected": "gas",
"smoke_detected": "smoke"
}
@@ -29,17 +31,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink binary sensor platform."""
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
for sensor in pywink.get_sensors():
if sensor.capability() in SENSOR_TYPES:
add_devices([WinkBinarySensorDevice(sensor)])
@@ -47,6 +38,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for key in pywink.get_keys():
add_devices([WinkBinarySensorDevice(key)])
for sensor in pywink.get_smoke_and_co_detectors():
add_devices([WinkBinarySensorDevice(sensor)])
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Representation of a Wink binary sensor."""
@@ -70,15 +64,25 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
def is_on(self):
"""Return true if the binary sensor is on."""
if self.capability == "loudness":
return self.wink.loudness_boolean()
state = self.wink.loudness_boolean()
elif self.capability == "vibration":
return self.wink.vibration_boolean()
state = self.wink.vibration_boolean()
elif self.capability == "brightness":
return self.wink.brightness_boolean()
state = self.wink.brightness_boolean()
elif self.capability == "liquid_detected":
return self.wink.liquid_boolean()
state = self.wink.liquid_boolean()
elif self.capability == "motion":
state = self.wink.motion_boolean()
elif self.capability == "presence":
state = self.wink.presence_boolean()
elif self.capability == "co_detected":
state = self.wink.co_detected_boolean()
elif self.capability == "smoke_detected":
state = self.wink.smoke_detected_boolean()
else:
return self.wink.state()
state = self.wink.state()
return state
@property
def sensor_class(self):

View File

@@ -36,8 +36,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
# Make sure that we have values for the key before converting to int
@@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
return
if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY:
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])

View File

@@ -65,7 +65,7 @@ class BloomSky(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def refresh_devices(self):
"""Use the API to retreive a list of devices."""
"""Use the API to retrieve a list of devices."""
_LOGGER.debug("Fetching BloomSky update")
response = requests.get(self.API_URL,
headers={"Authorization": self._api_key},

View File

@@ -9,30 +9,28 @@ import logging
import voluptuous as vol
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.components.ffmpeg import (
run_test, get_binary, CONF_INPUT, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
REQUIREMENTS = ['ha-ffmpeg==0.10']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
CONF_INPUT = 'input'
CONF_FFMPEG_BIN = 'ffmpeg_bin'
CONF_EXTRA_ARGUMENTS = 'extra_arguments'
DEFAULT_BINARY = 'ffmpeg'
DEFAULT_NAME = 'FFmpeg'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_INPUT): cv.string,
vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string,
vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a FFmpeg Camera."""
if not run_test(config.get(CONF_INPUT)):
return
add_devices([FFmpegCamera(config)])
@@ -45,12 +43,11 @@ class FFmpegCamera(Camera):
self._name = config.get(CONF_NAME)
self._input = config.get(CONF_INPUT)
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN)
def camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageSingle, IMAGE_JPEG
ffmpeg = ImageSingle(self._ffmpeg_bin)
ffmpeg = ImageSingle(get_binary())
return ffmpeg.get_image(self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments)
@@ -59,7 +56,7 @@ class FFmpegCamera(Camera):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg_bin)
stream = CameraMjpeg(get_binary())
stream.open_camera(self._input, extra_cmd=self._extra_arguments)
return response(
stream,

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.exceptions import TemplateError
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers import config_validation as cv, template
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -25,7 +25,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
DEFAULT_NAME = 'Generic Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STILL_IMAGE_URL): vol.Any(cv.url, cv.template),
vol.Required(CONF_STILL_IMAGE_URL): cv.template,
vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean,
@@ -38,18 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a generic IP Camera."""
add_devices([GenericCamera(config)])
add_devices([GenericCamera(hass, config)])
# pylint: disable=too-many-instance-attributes
class GenericCamera(Camera):
"""A generic implementation of an IP camera."""
def __init__(self, device_info):
def __init__(self, hass, device_info):
"""Initialize a generic camera."""
super().__init__()
self.hass = hass
self._name = device_info.get(CONF_NAME)
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
username = device_info.get(CONF_USERNAME)
@@ -69,7 +71,7 @@ class GenericCamera(Camera):
def camera_image(self):
"""Return a still image response from the camera."""
try:
url = template.render(self.hass, self._still_image_url)
url = self._still_image_url.render()
except TemplateError as err:
_LOGGER.error('Error parsing template %s: %s',
self._still_image_url, err)

View File

@@ -5,12 +5,11 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.netatmo/
"""
import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.util import Throttle
from homeassistant.components.netatmo import WelcomeData
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
from homeassistant.helpers import config_validation as cv
@@ -22,8 +21,6 @@ _LOGGER = logging.getLogger(__name__)
CONF_HOME = 'home'
CONF_CAMERAS = 'cameras'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_CAMERAS, default=[]):
@@ -36,20 +33,24 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup access to Netatmo Welcome cameras."""
netatmo = get_component('netatmo')
home = config.get(CONF_HOME)
data = WelcomeData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
if CONF_CAMERAS in config:
if camera_name not in config[CONF_CAMERAS]:
continue
add_devices([WelcomeCamera(data, camera_name, home)])
import lnetatmo
try:
data = WelcomeData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
camera_name not in config[CONF_CAMERAS]:
continue
add_devices([WelcomeCamera(data, camera_name, home)])
except lnetatmo.NoDevice:
return None
class WelcomeCamera(Camera):
"""Representation of the images published from Welcome camera."""
def __init__(self, data, camera_name, home):
"""Setup for access to the BloomSky camera images."""
"""Setup for access to the Netatmo camera images."""
super(WelcomeCamera, self).__init__()
self._data = data
self._camera_name = camera_name
@@ -57,6 +58,10 @@ class WelcomeCamera(Camera):
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
camera_id = data.welcomedata.cameraByName(camera=camera_name,
home=home)['id']
self._unique_id = "Welcome_camera {0} - {1}".format(self._name,
camera_id)
self._vpnurl, self._localurl = self._data.welcomedata.cameraUrls(
camera=camera_name
)
@@ -83,31 +88,7 @@ class WelcomeCamera(Camera):
"""Return the name of this Netatmo Welcome device."""
return self._name
class WelcomeData(object):
"""Get the latest data from NetAtmo."""
def __init__(self, auth, home=None):
"""Initialize the data object."""
self.auth = auth
self.welcomedata = None
self.camera_names = []
self.home = home
def get_camera_names(self):
"""Return all module available on the API as a list."""
self.update()
if not self.home:
for home in self.welcomedata.cameras:
for camera in self.welcomedata.cameras[home].values():
self.camera_names.append(camera['name'])
else:
for camera in self.welcomedata.cameras[self.home].values():
self.camera_names.append(camera['name'])
return self.camera_names
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.welcomedata = lnetatmo.WelcomeData(self.auth)
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id

View File

@@ -0,0 +1,223 @@
"""
Support for Synology Surveillance Station Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.synology/
"""
import logging
import voluptuous as vol
import requests
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_URL, CONF_WHITELIST)
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
# pylint: disable=too-many-locals
DEFAULT_NAME = 'Synology Camera'
DEFAULT_STREAM_ID = '0'
TIMEOUT = 5
CONF_CAMERA_NAME = 'camera_name'
CONF_STREAM_ID = 'stream_id'
CONF_VALID_CERT = 'valid_cert'
QUERY_CGI = 'query.cgi'
QUERY_API = 'SYNO.API.Info'
AUTH_API = 'SYNO.API.Auth'
CAMERA_API = 'SYNO.SurveillanceStation.Camera'
STREAMING_API = 'SYNO.SurveillanceStation.VideoStream'
SESSION_ID = '0'
WEBAPI_PATH = '/webapi/'
AUTH_PATH = 'auth.cgi'
CAMERA_PATH = 'camera.cgi'
STREAMING_PATH = 'SurveillanceStation/videoStreaming.cgi'
SYNO_API_URL = '{0}{1}{2}'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_URL): cv.string,
vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list,
vol.Optional(CONF_VALID_CERT, default=True): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a Synology IP Camera."""
# Determine API to use for authentication
syno_api_url = SYNO_API_URL.format(config.get(CONF_URL),
WEBAPI_PATH,
QUERY_CGI)
query_payload = {'api': QUERY_API,
'method': 'Query',
'version': '1',
'query': 'SYNO.'}
query_req = requests.get(syno_api_url,
params=query_payload,
verify=config.get(CONF_VALID_CERT),
timeout=TIMEOUT)
query_resp = query_req.json()
auth_path = query_resp['data'][AUTH_API]['path']
camera_api = query_resp['data'][CAMERA_API]['path']
camera_path = query_resp['data'][CAMERA_API]['path']
streaming_path = query_resp['data'][STREAMING_API]['path']
# Authticate to NAS to get a session id
syno_auth_url = SYNO_API_URL.format(config.get(CONF_URL),
WEBAPI_PATH,
auth_path)
session_id = get_session_id(config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
syno_auth_url,
config.get(CONF_VALID_CERT))
# Use SessionID to get cameras in system
syno_camera_url = SYNO_API_URL.format(config.get(CONF_URL),
WEBAPI_PATH,
camera_api)
camera_payload = {'api': CAMERA_API,
'method': 'List',
'version': '1'}
camera_req = requests.get(syno_camera_url,
params=camera_payload,
verify=config.get(CONF_VALID_CERT),
timeout=TIMEOUT,
cookies={'id': session_id})
camera_resp = camera_req.json()
cameras = camera_resp['data']['cameras']
for camera in cameras:
if not config.get(CONF_WHITELIST):
camera_id = camera['id']
snapshot_path = camera['snapshot_path']
add_devices([SynologyCamera(config,
camera_id,
camera['name'],
snapshot_path,
streaming_path,
camera_path,
auth_path)])
def get_session_id(username, password, login_url, valid_cert):
"""Get a session id."""
auth_payload = {'api': AUTH_API,
'method': 'Login',
'version': '2',
'account': username,
'passwd': password,
'session': 'SurveillanceStation',
'format': 'sid'}
auth_req = requests.get(login_url,
params=auth_payload,
verify=valid_cert,
timeout=TIMEOUT)
auth_resp = auth_req.json()
return auth_resp['data']['sid']
# pylint: disable=too-many-instance-attributes
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
# pylint: disable=too-many-arguments
def __init__(self, config, camera_id, camera_name,
snapshot_path, streaming_path, camera_path, auth_path):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self._name = camera_name
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
self._synology_url = config.get(CONF_URL)
self._api_url = config.get(CONF_URL) + 'webapi/'
self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi'
self._camera_name = config.get(CONF_CAMERA_NAME)
self._stream_id = config.get(CONF_STREAM_ID)
self._valid_cert = config.get(CONF_VALID_CERT)
self._camera_id = camera_id
self._snapshot_path = snapshot_path
self._streaming_path = streaming_path
self._camera_path = camera_path
self._auth_path = auth_path
self._session_id = get_session_id(self._username,
self._password,
self._login_url,
self._valid_cert)
def get_sid(self):
"""Get a session id."""
auth_payload = {'api': AUTH_API,
'method': 'Login',
'version': '2',
'account': self._username,
'passwd': self._password,
'session': 'SurveillanceStation',
'format': 'sid'}
auth_req = requests.get(self._login_url,
params=auth_payload,
verify=self._valid_cert,
timeout=TIMEOUT)
auth_resp = auth_req.json()
self._session_id = auth_resp['data']['sid']
def camera_image(self):
"""Return a still image response from the camera."""
image_url = SYNO_API_URL.format(self._synology_url,
WEBAPI_PATH,
self._camera_path)
image_payload = {'api': CAMERA_API,
'method': 'GetSnapshot',
'version': '1',
'cameraId': self._camera_id}
try:
response = requests.get(image_url,
params=image_payload,
timeout=TIMEOUT,
verify=self._valid_cert,
cookies={'id': self._session_id})
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
return response.content
def camera_stream(self):
"""Return a MJPEG stream image response directly from the camera."""
streaming_url = SYNO_API_URL.format(self._synology_url,
WEBAPI_PATH,
self._streaming_path)
streaming_payload = {'api': STREAMING_API,
'method': 'Stream',
'version': '1',
'cameraId': self._camera_id,
'format': 'mjpeg'}
response = requests.get(streaming_url,
payload=streaming_payload,
stream=True,
timeout=TIMEOUT,
cookies={'id': self._session_id})
return response
def mjpeg_steam(self, response):
"""Generate an HTTP MJPEG Stream from the Synology NAS."""
stream = self.camera_stream()
return response(
stream.iter_content(chunk_size=1024),
mimetype=stream.headers['CONTENT_TYPE_HEADER'],
direct_passthrough=True
)
@property
def name(self):
"""Return the name of this device."""
return self._name

View File

@@ -8,28 +8,33 @@ import logging
import socket
import requests
import voluptuous as vol
from homeassistant.components.camera import DOMAIN, Camera
from homeassistant.helpers import validate_config
from homeassistant.const import CONF_PORT
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['uvcclient==0.9.0']
_LOGGER = logging.getLogger(__name__)
CONF_NVR = 'nvr'
CONF_KEY = 'key'
DEFAULT_PORT = 7080
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NVR): cv.string,
vol.Required(CONF_KEY): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Discover cameras on a Unifi NVR."""
if not validate_config({DOMAIN: config}, {DOMAIN: ['nvr', 'key']},
_LOGGER):
return None
addr = config.get('nvr')
key = config.get('key')
try:
port = int(config.get('port', 7080))
except ValueError:
_LOGGER.error('Invalid port number provided')
return False
addr = config[CONF_NVR]
key = config[CONF_KEY]
port = config[CONF_PORT]
from uvcclient import nvr
nvrconn = nvr.UVCRemote(addr, port, key)

View File

@@ -0,0 +1,103 @@
"""
Camera that loads a picture from a local file.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.verisure/
"""
import errno
import logging
import os
from homeassistant.components.camera import Camera
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.verisure import HUB as hub
from homeassistant.components.verisure import CONF_SMARTCAM
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Camera."""
if not int(hub.config.get(CONF_SMARTCAM, 1)):
return False
directory_path = hass.config.config_dir
if not os.access(directory_path, os.R_OK):
_LOGGER.error("file path %s is not readable", directory_path)
return False
hub.update_smartcam()
smartcams = []
smartcams.extend([
VerisureSmartcam(hass, value.deviceLabel, directory_path)
for value in hub.smartcam_status.values()])
add_devices(smartcams)
class VerisureSmartcam(Camera):
"""Local camera."""
def __init__(self, hass, device_id, directory_path):
"""Initialize Verisure File Camera component."""
super().__init__()
self._device_id = device_id
self._directory_path = directory_path
self._image = None
self._image_id = None
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
self.delete_image)
def camera_image(self):
"""Return image response."""
self.check_imagelist()
if not self._image:
_LOGGER.debug('No image to display')
return
_LOGGER.debug('Trying to open %s', self._image)
with open(self._image, 'rb') as file:
return file.read()
def check_imagelist(self):
"""Check the contents of the image list."""
hub.update_smartcam_imagelist()
if (self._device_id not in hub.smartcam_dict or
not hub.smartcam_dict[self._device_id]):
return
images = hub.smartcam_dict[self._device_id]
new_image_id = images[0]
_LOGGER.debug('self._device_id=%s, self._images=%s, '
'self._new_image_id=%s', self._device_id,
images, new_image_id)
if (new_image_id == '-1' or
self._image_id == new_image_id):
_LOGGER.debug('The image is the same, or loading image_id')
return
_LOGGER.debug('Download new image %s', new_image_id)
hub.my_pages.smartcam.download_image(self._device_id,
new_image_id,
self._directory_path)
_LOGGER.debug('Old image_id=%s', self._image_id)
self.delete_image(self)
self._image_id = new_image_id
self._image = os.path.join(self._directory_path,
'{}{}'.format(
self._image_id,
'.jpg'))
def delete_image(self, event):
"""Delete an old image."""
remove_image = os.path.join(self._directory_path,
'{}{}'.format(
self._image_id,
'.jpg'))
try:
os.remove(remove_image)
_LOGGER.debug('Deleting old image %s', remove_image)
except OSError as error:
if error.errno != errno.ENOENT:
raise
@property
def name(self):
"""Return the name of this camera."""
return hub.smartcam_status[self._device_id].location

View File

@@ -58,6 +58,12 @@ ATTR_OPERATION_LIST = "operation_list"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_LIST = "swing_list"
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_HIGH,
]
_LOGGER = logging.getLogger(__name__)
SET_AWAY_MODE_SCHEMA = vol.Schema({
@@ -73,6 +79,7 @@ SET_TEMPERATURE_SCHEMA = vol.Schema({
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
})
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
@@ -116,8 +123,10 @@ def set_aux_heat(hass, aux_heat, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
# pylint: disable=too-many-arguments
def set_temperature(hass, temperature=None, entity_id=None,
target_temp_high=None, target_temp_low=None):
target_temp_high=None, target_temp_low=None,
operation_mode=None):
"""Set new target temperature."""
kwargs = {
key: value for key, value in [
@@ -125,6 +134,7 @@ def set_temperature(hass, temperature=None, entity_id=None,
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
(ATTR_TARGET_TEMP_LOW, target_temp_low),
(ATTR_ENTITY_ID, entity_id),
(ATTR_OPERATION_MODE, operation_mode)
] if value is not None
}
_LOGGER.debug("set_temperature start data=%s", kwargs)
@@ -235,10 +245,20 @@ def setup(hass, config):
def temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.extract_from_service(service)
kwargs = service.data
for climate in target_climate:
climate.set_temperature(**kwargs)
for climate in target_climate:
kwargs = {}
for value, temp in service.data.items():
if value in CONVERTIBLE_ATTRIBUTE:
kwargs[value] = convert_temperature(
temp,
hass.config.units.temperature_unit,
climate.temperature_unit
)
else:
kwargs[value] = temp
climate.set_temperature(**kwargs)
if climate.should_poll:
climate.update_ha_state(True)
@@ -348,7 +368,10 @@ class ClimateDevice(Entity):
@property
def state(self):
"""Return the current state."""
return self.current_operation or STATE_UNKNOWN
if self.current_operation:
return self.current_operation
else:
return STATE_UNKNOWN
@property
def state_attributes(self):
@@ -378,17 +401,20 @@ class ClimateDevice(Entity):
fan_mode = self.current_fan_mode
if fan_mode is not None:
data[ATTR_FAN_MODE] = fan_mode
data[ATTR_FAN_LIST] = self.fan_list
if self.fan_list:
data[ATTR_FAN_LIST] = self.fan_list
operation_mode = self.current_operation
if operation_mode is not None:
data[ATTR_OPERATION_MODE] = operation_mode
data[ATTR_OPERATION_LIST] = self.operation_list
if self.operation_list:
data[ATTR_OPERATION_LIST] = self.operation_list
swing_mode = self.current_swing_mode
if swing_mode is not None:
data[ATTR_SWING_MODE] = swing_mode
data[ATTR_SWING_LIST] = self.swing_list
if self.swing_list:
data[ATTR_SWING_LIST] = self.swing_list
is_away = self.is_away_mode_on
if is_away is not None:
@@ -402,7 +428,12 @@ class ClimateDevice(Entity):
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
"""The unit of measurement to display."""
return self.hass.config.units.temperature_unit
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
raise NotImplementedError
@property
@@ -514,12 +545,12 @@ class ClimateDevice(Entity):
@property
def min_temp(self):
"""Return the minimum temperature."""
return convert_temperature(7, TEMP_CELSIUS, self.unit_of_measurement)
return convert_temperature(7, TEMP_CELSIUS, self.temperature_unit)
@property
def max_temp(self):
"""Return the maximum temperature."""
return convert_temperature(35, TEMP_CELSIUS, self.unit_of_measurement)
return convert_temperature(35, TEMP_CELSIUS, self.temperature_unit)
@property
def min_humidity(self):
@@ -536,10 +567,10 @@ class ClimateDevice(Entity):
if temp is None or not isinstance(temp, Number):
return temp
value = convert_temperature(temp, self.unit_of_measurement,
self.hass.config.units.temperature_unit)
value = convert_temperature(temp, self.temperature_unit,
self.unit_of_measurement)
if self.hass.config.units.temperature_unit is TEMP_CELSIUS:
if self.unit_of_measurement is TEMP_CELSIUS:
decimal_count = 1
else:
# Users of fahrenheit generally expect integer units.

View File

@@ -16,7 +16,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
None, None, "Auto", "heat", None, None, None),
DemoClimate("Hvac", 21, TEMP_CELSIUS, True, 22, "On High",
67, 54, "Off", "cool", False, None, None),
DemoClimate("Ecobee", 23, TEMP_CELSIUS, None, 23, "Auto Low",
DemoClimate("Ecobee", None, TEMP_CELSIUS, None, 23, "Auto Low",
None, None, "Auto", "auto", None, 24, 21)
])
@@ -59,7 +59,7 @@ class DemoClimate(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement

View File

@@ -14,7 +14,7 @@ from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, TEMP_FAHRENHEIT)
from homeassistant.config import load_yaml_config_file
import homeassistant.helpers.config_validation as cv
@@ -86,10 +86,16 @@ class Thermostat(ClimateDevice):
self.hold_temp = hold_temp
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
self.update_without_throttle = False
def update(self):
"""Get the latest state from the thermostat."""
self.data.update()
if self.update_without_throttle:
self.data.update(no_throttle=True)
self.update_without_throttle = False
else:
self.data.update()
self.thermostat = self.data.ecobee.get_thermostat(
self.thermostat_index)
@@ -99,7 +105,7 @@ class Thermostat(ClimateDevice):
return self.thermostat['name']
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@@ -108,15 +114,6 @@ class Thermostat(ClimateDevice):
"""Return the current temperature."""
return self.thermostat['runtime']['actualTemperature'] / 10
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if (self.operation_mode == 'heat' or
self.operation_mode == 'auxHeatOnly'):
return self.target_temperature_low
elif self.operation_mode == 'cool':
return self.target_temperature_high
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
@@ -211,17 +208,15 @@ class Thermostat(ClimateDevice):
"away", "indefinite")
else:
self.data.ecobee.set_climate_hold(self.thermostat_index, "away")
self.update_without_throttle = True
def turn_away_mode_off(self):
"""Turn away off."""
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
low_temp = int(temperature)
high_temp = int(temperature)
if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \
kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None:
high_temp = int(kwargs.get(ATTR_TARGET_TEMP_LOW))
@@ -241,15 +236,18 @@ class Thermostat(ClimateDevice):
"high=%s, is=%s", low_temp, isinstance(
low_temp, (int, float)), high_temp,
isinstance(high_temp, (int, float)))
self.update_without_throttle = True
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
self.data.ecobee.set_hvac_mode(self.thermostat_index, operation_mode)
self.update_without_throttle = True
def set_fan_min_on_time(self, fan_min_on_time):
"""Set the minimum fan on time."""
self.data.ecobee.set_fan_min_on_time(self.thermostat_index,
fan_min_on_time)
self.update_without_throttle = True
# Home and Sleep mode aren't used in UI yet:

View File

@@ -1,24 +1,38 @@
"""
Support for eq3 Bluetooth Smart thermostats.
Support for eQ-3 Bluetooth Smart thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.eq3btsmart/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE
import voluptuous as vol
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE)
from homeassistant.util.temperature import convert
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bluepy_devices==0.2.0']
CONF_MAC = 'mac'
_LOGGER = logging.getLogger(__name__)
ATTR_MODE = 'mode'
ATTR_MODE_READABLE = 'mode_readable'
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_MAC): cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES):
vol.Schema({cv.string: DEVICE_SCHEMA}),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the eq3 BLE thermostats."""
"""Setup the eQ-3 BLE thermostats."""
devices = []
for name, device_cfg in config[CONF_DEVICES].items():
@@ -30,14 +44,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=too-many-instance-attributes, import-error, abstract-method
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of a EQ3 Bluetooth Smart thermostat."""
"""Representation of a eQ-3 Bluetooth Smart thermostat."""
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
from bluepy_devices.devices import eq3btsmart
self._name = _name
self._thermostat = eq3btsmart.EQ3BTSmartThermostat(_mac)
@property
@@ -46,7 +59,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@@ -70,8 +83,10 @@ class EQ3BTSmartThermostat(ClimateDevice):
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {"mode": self._thermostat.mode,
"mode_readable": self._thermostat.mode_readable}
return {
ATTR_MODE: self._thermostat.mode,
ATTR_MODE_READABLE: self._thermostat.mode_readable,
}
@property
def min_temp(self):

View File

@@ -5,16 +5,19 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.generic_thermostat/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components import switch
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice)
STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import track_state_change
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['switch', 'sensor']
@@ -30,18 +33,16 @@ CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration'
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = vol.Schema({
vol.Required("platform"): "generic_thermostat",
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HEATER): cv.entity_id,
vol.Required(CONF_SENSOR): cv.entity_id,
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_AC_MODE): cv.boolean,
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_AC_MODE): vol.Coerce(bool),
vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
})
@@ -56,10 +57,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
add_devices([GenericThermostat(hass, name, heater_entity_id,
sensor_entity_id, min_temp,
max_temp, target_temp, ac_mode,
min_cycle_duration)])
add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration)])
# pylint: disable=too-many-instance-attributes, abstract-method
@@ -100,7 +100,7 @@ class GenericThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit
@@ -110,7 +110,7 @@ class GenericThermostat(ClimateDevice):
return self._cur_temp
@property
def operation(self):
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.ac_mode:
cooling = self._active and self._is_device_active

View File

@@ -1,56 +1,54 @@
"""
Support for the PRT Heatmiser themostats using the V3 protocol.
See https://github.com/andylockran/heatmiserV3 for more info on the
heatmiserV3 module dependency.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.heatmiser/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
import voluptuous as vol
CONF_IPADDRESS = 'ipaddress'
CONF_PORT = 'port'
CONF_TSTATS = 'tstats'
from homeassistant.components.climate import ClimateDevice, PLATFORM_SCHEMA
from homeassistant.const import (
TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ["heatmiserV3==0.9.1"]
REQUIREMENTS = ['heatmiserV3==0.9.1']
_LOGGER = logging.getLogger(__name__)
CONF_IPADDRESS = 'ipaddress'
CONF_TSTATS = 'tstats'
TSTATS_SCHEMA = vol.Schema({
vol.Required(CONF_ID): cv.string,
vol.Required(CONF_NAME): cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IPADDRESS): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Required(CONF_TSTATS, default={}):
vol.Schema({cv.string: TSTATS_SCHEMA}),
})
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the heatmiser thermostat."""
from heatmiserV3 import heatmiser, connection
ipaddress = str(config[CONF_IPADDRESS])
port = str(config[CONF_PORT])
if ipaddress is None or port is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_IPADDRESS, CONF_PORT)
return False
ipaddress = config.get(CONF_IPADDRESS)
port = str(config.get(CONF_PORT))
tstats = config.get(CONF_TSTATS)
serport = connection.connection(ipaddress, port)
serport.open()
tstats = []
if CONF_TSTATS in config:
tstats = config[CONF_TSTATS]
if tstats is None:
_LOGGER.error("No thermostats configured.")
return False
for tstat in tstats:
for thermostat, tstat in tstats.items():
add_devices([
HeatmiserV3Thermostat(
heatmiser,
tstat.get("id"),
tstat.get("name"),
serport)
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)
])
return
@@ -69,7 +67,7 @@ class HeatmiserV3Thermostat(ClimateDevice):
self._id = device
self.dcb = None
self.update()
self._target_temperature = int(self.dcb.get("roomset"))
self._target_temperature = int(self.dcb.get('roomset'))
@property
def name(self):
@@ -77,7 +75,7 @@ class HeatmiserV3Thermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@@ -85,9 +83,9 @@ class HeatmiserV3Thermostat(ClimateDevice):
def current_temperature(self):
"""Return the current temperature."""
if self.dcb is not None:
low = self.dcb.get("floortemplow ")
high = self.dcb.get("floortemphigh")
temp = (high*256 + low)/10.0
low = self.dcb.get('floortemplow ')
high = self.dcb.get('floortemphigh')
temp = (high * 256 + low) / 10.0
self._current_temperature = temp
else:
self._current_temperature = None

View File

@@ -41,7 +41,7 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
"""Representation of a Homematic thermostat."""
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement that is used."""
return TEMP_CELSIUS
@@ -99,6 +99,9 @@ class HMThermostat(homematic.HMDevice, ClimateDevice):
return None
if temperature is None:
return
if self.current_operation == STATE_AUTO:
return self._hmdevice.actionNodeData('MANU_MODE', temperature)
self._hmdevice.set_temperature(temperature)
def set_operation_mode(self, operation_mode):

View File

@@ -7,44 +7,67 @@ https://home-assistant.io/components/climate.honeywell/
import logging
import socket
from homeassistant.components.climate import ClimateDevice
import voluptuous as vol
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['evohomeclient==0.2.5',
'somecomfort==0.2.1']
'somecomfort==0.3.2']
_LOGGER = logging.getLogger(__name__)
CONF_AWAY_TEMP = "away_temperature"
DEFAULT_AWAY_TEMP = 16
ATTR_FAN = 'fan'
ATTR_FANMODE = 'fanmode'
ATTR_SYSTEM_MODE = 'system_mode'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_REGION = 'region'
DEFAULT_AWAY_TEMPERATURE = 16
DEFAULT_REGION = 'eu'
REGIONS = ['eu', 'us']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE):
vol.Coerce(float),
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the HoneywelL thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get(CONF_REGION)
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
def _setup_round(username, password, config, add_devices):
"""Setup rounding function."""
from evohomeclient import EvohomeClient
try:
away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP))
except ValueError:
_LOGGER.error("value entered for item %s should convert to a number",
CONF_AWAY_TEMP)
return False
away_temp = config.get(CONF_AWAY_TEMPERATURE)
evo_api = EvohomeClient(username, password)
try:
zones = evo_api.temperatures(force_refresh=True)
for i, zone in enumerate(zones):
add_devices([RoundThermostat(evo_api,
zone['id'],
i == 0,
away_temp)])
add_devices(
[RoundThermostat(evo_api, zone['id'], i == 0, away_temp)]
)
except socket.error:
_LOGGER.error(
"Connection error logging into the honeywell evohome web service"
)
"Connection error logging into the honeywell evohome web service")
return False
return True
@@ -74,26 +97,6 @@ def _setup_us(username, password, config, add_devices):
return True
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the honeywel thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get('region', 'eu').lower()
if username is None or password is None:
_LOGGER.error("Missing required configuration items %s or %s",
CONF_USERNAME, CONF_PASSWORD)
return False
if region not in ('us', 'eu'):
_LOGGER.error('Region `%s` is invalid (use either us or eu)', region)
return False
if region == 'us':
return _setup_us(username, password, config, add_devices)
else:
return _setup_round(username, password, config, add_devices)
class RoundThermostat(ClimateDevice):
"""Representation of a Honeywell Round Connected thermostat."""
@@ -103,7 +106,7 @@ class RoundThermostat(ClimateDevice):
self.device = device
self._current_temperature = None
self._target_temperature = None
self._name = "round connected"
self._name = 'round connected'
self._id = zone_id
self._master = master
self._is_dhw = False
@@ -117,7 +120,7 @@ class RoundThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@@ -143,7 +146,7 @@ class RoundThermostat(ClimateDevice):
@property
def current_operation(self: ClimateDevice) -> str:
"""Get the current operation of the system."""
return getattr(self.device, 'system_mode', None)
return getattr(self.device, ATTR_SYSTEM_MODE, None)
@property
def is_away_mode_on(self):
@@ -152,7 +155,7 @@ class RoundThermostat(ClimateDevice):
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the HVAC mode for the thermostat."""
if hasattr(self.device, 'system_mode'):
if hasattr(self.device, ATTR_SYSTEM_MODE):
self.device.system_mode = operation_mode
def turn_away_mode_on(self):
@@ -186,8 +189,8 @@ class RoundThermostat(ClimateDevice):
self._current_temperature = data['temp']
self._target_temperature = data['setpoint']
if data['thermostat'] == "DOMESTIC_HOT_WATER":
self._name = "Hot Water"
if data['thermostat'] == 'DOMESTIC_HOT_WATER':
self._name = 'Hot Water'
self._is_dhw = True
else:
self._name = data['name']
@@ -214,7 +217,7 @@ class HoneywellUSThermostat(ClimateDevice):
return self._device.name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return (TEMP_CELSIUS if self._device.temperature_unit == 'C'
else TEMP_FAHRENHEIT)
@@ -236,7 +239,7 @@ class HoneywellUSThermostat(ClimateDevice):
@property
def current_operation(self: ClimateDevice) -> str:
"""Return current operation ie. heat, cool, idle."""
return getattr(self._device, 'system_mode', None)
return getattr(self._device, ATTR_SYSTEM_MODE, None)
def set_temperature(self, **kwargs):
"""Set target temperature."""
@@ -255,9 +258,11 @@ class HoneywellUSThermostat(ClimateDevice):
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {'fan': (self.is_fan_on and 'running' or 'idle'),
'fanmode': self._device.fan_mode,
'system_mode': self._device.system_mode}
return {
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
ATTR_FANMODE: self._device.fan_mode,
ATTR_SYSTEM_MODE: self._device.system_mode,
}
def turn_away_mode_on(self):
"""Turn away on."""
@@ -269,5 +274,5 @@ class HoneywellUSThermostat(ClimateDevice):
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
if hasattr(self._device, 'system_mode'):
if hasattr(self._device, ATTR_SYSTEM_MODE):
self._device.system_mode = operation_mode

View File

@@ -2,26 +2,37 @@
Support for KNX thermostats.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/knx/
https://home-assistant.io/components/climate.knx/
"""
import logging
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
import voluptuous as vol
from homeassistant.components.knx import (
KNXConfig, KNXMultiAddressDevice)
DEPENDENCIES = ["knx"]
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice)
from homeassistant.const import (CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_ADDRESS = 'address'
CONF_SETPOINT_ADDRESS = 'setpoint_address'
CONF_TEMPERATURE_ADDRESS = 'temperature_address'
def setup_platform(hass, config, add_entities, discovery_info=None):
DEFAULT_NAME = 'KNX Thermostat'
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ADDRESS): cv.string,
vol.Required(CONF_SETPOINT_ADDRESS): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Create and add an entity based on the configuration."""
add_entities([
KNXThermostat(hass, KNXConfig(config))
])
add_devices([KNXThermostat(hass, KNXConfig(config))])
class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
@@ -39,9 +50,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
def __init__(self, hass, config):
"""Initialize the thermostat based on the given configuration."""
KNXMultiAddressDevice.__init__(self, hass, config,
["temperature", "setpoint"],
["mode"])
KNXMultiAddressDevice.__init__(
self, hass, config, ['temperature', 'setpoint'], ['mode'])
self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius
self._away = False # not yet supported
@@ -53,7 +63,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
return True
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@@ -62,14 +72,14 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
"""Return the current temperature."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value("temperature"))
return knx2_to_float(self.value('temperature'))
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
from knxip.conversion import knx2_to_float
return knx2_to_float(self.value("setpoint"))
return knx2_to_float(self.value('setpoint'))
def set_temperature(self, **kwargs):
"""Set new target temperature."""
@@ -78,7 +88,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice):
return
from knxip.conversion import float_to_knx2
self.set_value("setpoint", float_to_knx2(temperature))
self.set_value('setpoint', float_to_knx2(temperature))
_LOGGER.debug("Set target temperature to %s", temperature)
def set_operation_mode(self, operation_mode):

View File

@@ -0,0 +1,192 @@
"""
mysensors platform that offers a Climate(MySensors-HVAC) component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.mysensors
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_OFF, STATE_AUTO, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
_LOGGER = logging.getLogger(__name__)
DICT_HA_TO_MYS = {STATE_COOL: "CoolOn", STATE_HEAT: "HeatOn",
STATE_AUTO: "AutoChangeOver", STATE_OFF: "Off"}
DICT_MYS_TO_HA = {"CoolOn": STATE_COOL, "HeatOn": STATE_HEAT,
"AutoChangeOver": STATE_AUTO, "Off": STATE_OFF}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors climate."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
if float(gateway.protocol_version) < 1.5:
continue
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_HVAC: [set_req.V_HVAC_FLOW_STATE],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsHVAC))
# pylint: disable=too-many-arguments, too-many-public-methods
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):
"""Representation of a MySensorsHVAC hvac."""
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""
return self.gateway.optimistic
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return (TEMP_CELSIUS
if self.gateway.metric else TEMP_FAHRENHEIT)
@property
def current_temperature(self):
"""Return the current temperature."""
return self._values.get(self.gateway.const.SetReq.V_TEMP)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values and \
set_req.V_HVAC_SETPOINT_HEAT in self._values:
return None
temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
if temp is None:
temp = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
return temp
@property
def target_temperature_high(self):
"""Return the highbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_HEAT in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_COOL)
@property
def target_temperature_low(self):
"""Return the lowbound target temperature we try to reach."""
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SETPOINT_COOL in self._values:
return self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_FLOW_STATE)
@property
def operation_list(self):
"""List of available operation modes."""
return [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self._values.get(self.gateway.const.SetReq.V_HVAC_SPEED)
@property
def fan_list(self):
"""List of available fan modes."""
return ["Auto", "Min", "Normal", "Max"]
def set_temperature(self, **kwargs):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
temp = kwargs.get(ATTR_TEMPERATURE)
low = kwargs.get(ATTR_TARGET_TEMP_LOW)
high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
heat = self._values.get(set_req.V_HVAC_SETPOINT_HEAT)
cool = self._values.get(set_req.V_HVAC_SETPOINT_COOL)
updates = ()
if temp is not None:
if heat is not None:
# Set HEAT Target temperature
value_type = set_req.V_HVAC_SETPOINT_HEAT
elif cool is not None:
# Set COOL Target temperature
value_type = set_req.V_HVAC_SETPOINT_COOL
if heat is not None or cool is not None:
updates = [(value_type, temp)]
elif all(val is not None for val in (low, high, heat, cool)):
updates = [
(set_req.V_HVAC_SETPOINT_HEAT, low),
(set_req.V_HVAC_SETPOINT_COOL, high)]
for value_type, value in updates:
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[value_type] = value
self.update_ha_state()
def set_fan_mode(self, fan):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(self.node_id, self.child_id,
set_req.V_HVAC_SPEED, fan)
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self.update_ha_state()
def set_operation_mode(self, operation_mode):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(self.node_id, self.child_id,
set_req.V_HVAC_FLOW_STATE,
DICT_HA_TO_MYS[operation_mode])
if self.gateway.optimistic:
# optimistically assume that switch has changed state
self._values[set_req.V_HVAC_FLOW_STATE] = operation_mode
self.update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
set_req = self.gateway.const.SetReq
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.debug(
'%s: value_type %s, value = %s', self._name, value_type, value)
if value_type == set_req.V_HVAC_FLOW_STATE:
self._values[value_type] = DICT_MYS_TO_HA[value]
else:
self._values[value_type] = value
def set_humidity(self, humidity):
"""Set new target humidity."""
_LOGGER.error("Service Not Implemented yet")
def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_on(self):
"""Turn away mode on."""
_LOGGER.error("Service Not Implemented yet")
def turn_away_mode_off(self):
"""Turn away mode off."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
_LOGGER.error("Service Not Implemented yet")
def turn_aux_heat_off(self):
"""Turn auxillary heater off."""
_LOGGER.error("Service Not Implemented yet")

View File

@@ -4,15 +4,18 @@ Support for Nest thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.nest/
"""
import logging
import voluptuous as vol
import homeassistant.components.nest as nest
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE)
from homeassistant.const import (
TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE)
TEMP_CELSIUS, CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL):
@@ -22,18 +25,34 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Nest thermostat."""
add_devices([NestThermostat(structure, device)
temp_unit = hass.config.units.temperature_unit
add_devices([NestThermostat(structure, device, temp_unit)
for structure, device in nest.devices()])
# pylint: disable=abstract-method
# pylint: disable=abstract-method,too-many-public-methods
class NestThermostat(ClimateDevice):
"""Representation of a Nest thermostat."""
def __init__(self, structure, device):
def __init__(self, structure, device, temp_unit):
"""Initialize the thermostat."""
self._unit = temp_unit
self.structure = structure
self.device = device
self._fan_list = [STATE_ON, STATE_AUTO]
# Not all nest devices support cooling and heating remove unused
self._operation_list = [STATE_OFF]
# Add supported nest thermostat features
if self.device.can_heat:
self._operation_list.append(STATE_HEAT)
if self.device.can_cool:
self._operation_list.append(STATE_COOL)
if self.device.can_heat and self.device.can_cool:
self._operation_list.append(STATE_AUTO)
@property
def name(self):
@@ -49,19 +68,22 @@ class NestThermostat(ClimateDevice):
return location.capitalize() + '(' + name + ')'
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
# Move these to Thermostat Device and make them global
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
"mode": self.device.mode
}
if self.device.has_humidifier or self.device.has_dehumidifier:
# Move these to Thermostat Device and make them global
return {
"humidity": self.device.humidity,
"target_humidity": self.device.target_humidity,
}
else:
# No way to control humidity not show setting
return {}
@property
def current_temperature(self):
@@ -69,43 +91,26 @@ class NestThermostat(ClimateDevice):
return self.device.temperature
@property
def operation(self):
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.hvac_ac_state is True:
if self.device.mode == 'cool':
return STATE_COOL
elif self.device.hvac_heater_state is True:
elif self.device.mode == 'heat':
return STATE_HEAT
elif self.device.mode == 'range':
return STATE_AUTO
elif self.device.mode == 'off':
return STATE_OFF
else:
return STATE_IDLE
return STATE_UNKNOWN
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.mode == 'range':
low, high = self.target_temperature_low, \
self.target_temperature_high
if self.operation == STATE_COOL:
temp = high
elif self.operation == STATE_HEAT:
temp = low
else:
# If the outside temp is lower than the current temp, consider
# the 'low' temp to the target, otherwise use the high temp
if (self.device.structure.weather.current.temperature <
self.current_temperature):
temp = low
else:
temp = high
if self.device.mode != 'range' and not self.is_away_mode_on:
return self.device.target
else:
if self.is_away_mode_on:
# away_temperature is a low, high tuple. Only one should be set
# if not in range mode, the other will be None
temp = self.device.away_temperature[0] or \
self.device.away_temperature[1]
else:
temp = self.device.target
return temp
return None
@property
def target_temperature_low(self):
@@ -115,7 +120,8 @@ class NestThermostat(ClimateDevice):
return self.device.away_temperature[0]
if self.device.mode == 'range':
return self.device.target[0]
return self.target_temperature
else:
return None
@property
def target_temperature_high(self):
@@ -125,7 +131,8 @@ class NestThermostat(ClimateDevice):
return self.device.away_temperature[1]
if self.device.mode == 'range':
return self.device.target[1]
return self.target_temperature
else:
return None
@property
def is_away_mode_on(self):
@@ -134,19 +141,32 @@ class NestThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self.device.mode == 'range':
if self.target_temperature == self.target_temperature_low:
temperature = (temperature, self.target_temperature_high)
elif self.target_temperature == self.target_temperature_high:
temperature = (self.target_temperature_low, temperature)
self.device.target = temperature
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if target_temp_low is not None and target_temp_high is not None:
if self.device.mode == 'range':
temp = (target_temp_low, target_temp_high)
else:
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
self.device.target = temp
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
self.device.mode = operation_mode
if operation_mode == STATE_HEAT:
self.device.mode = 'heat'
elif operation_mode == STATE_COOL:
self.device.mode = 'cool'
elif operation_mode == STATE_AUTO:
self.device.mode = 'range'
elif operation_mode == STATE_OFF:
self.device.mode = 'off'
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
def turn_away_mode_on(self):
"""Turn away on."""
@@ -157,17 +177,23 @@ class NestThermostat(ClimateDevice):
self.structure.away = False
@property
def is_fan_on(self):
def current_fan_mode(self):
"""Return whether the fan is on."""
return self.device.fan
if self.device.has_fan:
# Return whether the fan is on
return STATE_ON if self.device.fan else STATE_AUTO
else:
# No Fan available so disable slider
return None
def turn_fan_on(self):
"""Turn fan on."""
self.device.fan = True
@property
def fan_list(self):
"""List of available fan modes."""
return self._fan_list
def turn_fan_off(self):
"""Turn fan off."""
self.device.fan = False
def set_fan_mode(self, fan):
"""Turn fan on/off."""
self.device.fan = fan.lower()
@property
def min_temp(self):

View File

@@ -0,0 +1,178 @@
"""
Support for Netatmo Smart Thermostat.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.netatmo/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.components.climate import (
STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.util import Throttle
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['netatmo']
_LOGGER = logging.getLogger(__name__)
CONF_RELAY = 'relay'
CONF_THERMOSTAT = 'thermostat'
DEFAULT_AWAY_TEMPERATURE = 14
# # The default offeset is 2 hours (when you use the thermostat itself)
DEFAULT_TIME_OFFSET = 7200
# # Return cached results if last scan was less then this time ago
# # NetAtmo Data is uploaded to server every hour
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_RELAY): cv.string,
vol.Optional(CONF_THERMOSTAT, default=[]):
vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_callback_devices, discovery_info=None):
"""Setup the NetAtmo Thermostat."""
netatmo = get_component('netatmo')
device = config.get(CONF_RELAY)
import lnetatmo
try:
data = ThermostatData(netatmo.NETATMO_AUTH, device)
for module_name in data.get_module_names():
if CONF_THERMOSTAT in config:
if config[CONF_THERMOSTAT] != [] and \
module_name not in config[CONF_THERMOSTAT]:
continue
add_callback_devices([NetatmoThermostat(data, module_name)])
except lnetatmo.NoDevice:
return None
# pylint: disable=abstract-method
class NetatmoThermostat(ClimateDevice):
"""Representation a Netatmo thermostat."""
def __init__(self, data, module_name, away_temp=None):
"""Initialize the sensor."""
self._data = data
self._state = None
self._name = module_name
self._target_temperature = None
self._away = None
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._target_temperature
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._data.current_temperature
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature
@property
def current_operation(self):
"""Return the current state of the thermostat."""
state = self._data.thermostatdata.relay_cmd
if state == 0:
return STATE_IDLE
elif state == 100:
return STATE_HEAT
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def turn_away_mode_on(self):
"""Turn away on."""
mode = "away"
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = True
self.update_ha_state()
def turn_away_mode_off(self):
"""Turn away off."""
mode = "program"
temp = None
self._data.thermostatdata.setthermpoint(mode, temp, endTimeOffset=None)
self._away = False
self.update_ha_state()
def set_temperature(self, endTimeOffset=DEFAULT_TIME_OFFSET, **kwargs):
"""Set new target temperature for 2 hours."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
mode = "manual"
self._data.thermostatdata.setthermpoint(
mode, temperature, endTimeOffset)
self._target_temperature = temperature
self._away = False
self.update_ha_state()
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from NetAtmo API and updates the states."""
self._data.update()
self._target_temperature = self._data.thermostatdata.setpoint_temp
self._away = self._data.setpoint_mode == 'away'
class ThermostatData(object):
"""Get the latest data from Netatmo."""
def __init__(self, auth, device=None):
"""Initialize the data object."""
self.auth = auth
self.thermostatdata = None
self.module_names = []
self.device = device
self.current_temperature = None
self.target_temperature = None
self.setpoint_mode = None
# self.operation =
def get_module_names(self):
"""Return all module available on the API as a list."""
self.update()
if not self.device:
for device in self.thermostatdata.modules:
for module in self.thermostatdata.modules[device].values():
self.module_names.append(module['module_name'])
else:
for module in self.thermostatdata.modules[self.device].values():
self.module_names.append(module['module_name'])
return self.module_names
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.thermostatdata = lnetatmo.ThermostatData(self.auth)
self.target_temperature = self.thermostatdata.setpoint_temp
self.setpoint_mode = self.thermostatdata.setpoint_mode
self.current_temperature = self.thermostatdata.temp

View File

@@ -4,12 +4,23 @@ Support for Proliphix NT10e Thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.proliphix/
"""
import voluptuous as vol
from homeassistant.components.climate import (
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice)
STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['proliphix==0.3.1']
REQUIREMENTS = ['proliphix==0.4.0']
ATTR_FAN = 'fan'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -22,9 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
pdp = proliphix.PDP(host, username, password)
add_devices([
ProliphixThermostat(pdp)
])
add_devices([ProliphixThermostat(pdp)])
# pylint: disable=abstract-method
@@ -56,11 +65,11 @@ class ProliphixThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
"fan": self._pdp.fan_state
ATTR_FAN: self._pdp.fan_state
}
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT

View File

@@ -8,15 +8,28 @@ import datetime
import logging
from urllib.error import URLError
import voluptuous as vol
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF,
ClimateDevice)
ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.2']
HOLD_TEMP = 'hold_temp'
_LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
ATTR_MODE = 'mode'
CONF_HOLD_TEMP = 'hold_temp'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Radio Thermostat."""
@@ -29,10 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
hosts.append(radiotherm.discover.discover_address())
if hosts is None:
_LOGGER.error("No radiotherm thermostats detected.")
_LOGGER.error("No Radiotherm Thermostats detected")
return False
hold_temp = config.get(HOLD_TEMP, False)
hold_temp = config.get(CONF_HOLD_TEMP)
tstats = []
for host in hosts:
@@ -60,6 +73,7 @@ class RadioThermostat(ClimateDevice):
self._name = None
self.hold_temp = hold_temp
self.update()
self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF]
@property
def name(self):
@@ -67,7 +81,7 @@ class RadioThermostat(ClimateDevice):
return self._name
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@@ -75,8 +89,8 @@ class RadioThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
"fan": self.device.fmode['human'],
"mode": self.device.tmode['human']
ATTR_FAN: self.device.fmode['human'],
ATTR_MODE: self.device.tmode['human']
}
@property
@@ -89,6 +103,11 @@ class RadioThermostat(ClimateDevice):
"""Return the current operation. head, cool idle."""
return self._current_operation
@property
def operation_list(self):
"""Return the operation modes list."""
return self._operation_list
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
@@ -124,8 +143,11 @@ class RadioThermostat(ClimateDevice):
def set_time(self):
"""Set device time."""
now = datetime.datetime.now()
self.device.time = {'day': now.weekday(),
'hour': now.hour, 'minute': now.minute}
self.device.time = {
'day': now.weekday(),
'hour': now.hour,
'minute': now.minute
}
def set_operation_mode(self, operation_mode):
"""Set operation mode (auto, cool, heat, off)."""

View File

@@ -34,6 +34,18 @@ set_temperature:
description: New target temperature for hvac
example: 25
target_temp_high:
description: New target high tempereature for hvac
example: 26
target_temp_low:
description: New target low temperature for hvac
example: 20
operation_mode:
description: Operation mode to set temperature to. This defaults to current_operation mode if not set, or set incorrectly.
example: 'Heat'
set_humidity:
description: Set target humidity of climate device

View File

@@ -0,0 +1,137 @@
"""
Support for Vera thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.vera/
"""
import logging
from homeassistant.util import convert
from homeassistant.components.climate import ClimateDevice
from homeassistant.const import TEMP_FAHRENHEIT, ATTR_TEMPERATURE
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
OPERATION_LIST = ["Heat", "Cool", "Auto Changeover", "Off"]
FAN_OPERATION_LIST = ["On", "Auto", "Cycle"]
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return Vera thermostats."""
add_devices_callback(
VeraThermostat(device, VERA_CONTROLLER) for
device in VERA_DEVICES['climate'])
# pylint: disable=abstract-method
class VeraThermostat(VeraDevice, ClimateDevice):
"""Representation of a Vera Thermostat."""
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
mode = self.vera_device.get_hvac_mode()
if mode == "HeatOn":
return OPERATION_LIST[0] # heat
elif mode == "CoolOn":
return OPERATION_LIST[1] # cool
elif mode == "AutoChangeOver":
return OPERATION_LIST[2] # auto
elif mode == "Off":
return OPERATION_LIST[3] # off
return "Off"
@property
def operation_list(self):
"""List of available operation modes."""
return OPERATION_LIST
@property
def current_fan_mode(self):
"""Return the fan setting."""
mode = self.vera_device.get_fan_mode()
if mode == "ContinuousOn":
return FAN_OPERATION_LIST[0] # on
elif mode == "Auto":
return FAN_OPERATION_LIST[1] # auto
elif mode == "PeriodicOn":
return FAN_OPERATION_LIST[2] # cycle
return "Auto"
@property
def fan_list(self):
"""List of available fan modes."""
return FAN_OPERATION_LIST
def set_fan_mode(self, mode):
"""Set new target temperature."""
if mode == FAN_OPERATION_LIST[0]:
self.vera_device.fan_on()
elif mode == FAN_OPERATION_LIST[1]:
self.vera_device.fan_auto()
elif mode == FAN_OPERATION_LIST[2]:
return self.vera_device.fan_cycle()
@property
def current_power_mwh(self):
"""Current power usage in mWh."""
power = self.vera_device.power
if power:
return convert(power, float, 0.0) * 1000
def update(self):
"""Called by the vera device callback to update state."""
self._state = self.vera_device.get_hvac_mode()
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
"""Return the current temperature."""
return self.vera_device.get_current_temperature()
@property
def operation(self):
"""Return current operation ie. heat, cool, idle."""
return self.vera_device.get_hvac_state()
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.vera_device.get_current_goal_temperature()
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE))
def set_operation_mode(self, operation_mode):
"""Set HVAC mode (auto, cool, heat, off)."""
if operation_mode == OPERATION_LIST[3]: # off
self.vera_device.turn_off()
elif operation_mode == OPERATION_LIST[2]: # auto
self.vera_device.turn_auto_on()
elif operation_mode == OPERATION_LIST[1]: # cool
self.vera_device.turn_cool_on()
elif operation_mode == OPERATION_LIST[0]: # heat
self.vera_device.turn_heat_on()
def turn_fan_on(self):
"""Turn fan on."""
self.vera_device.fan_on()
def turn_fan_off(self):
"""Turn fan off."""
self.vera_device.fan_auto()

View File

@@ -8,9 +8,9 @@ https://home-assistant.io/components/climate.zwave/
# pylint: disable=import-error
import logging
from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import (
ATTR_NODE_ID, ATTR_VALUE_ID, ZWaveDeviceEntity)
from homeassistant.components.climate import (
ClimateDevice, ATTR_OPERATION_MODE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@@ -28,12 +28,6 @@ HORSTMANN = 0x0059
HORSTMANN_HRT4_ZW = 0x3
HORSTMANN_HRT4_ZW_THERMOSTAT = (HORSTMANN, HORSTMANN_HRT4_ZW)
COMMAND_CLASS_SENSOR_MULTILEVEL = 0x31
COMMAND_CLASS_THERMOSTAT_MODE = 0x40
COMMAND_CLASS_THERMOSTAT_SETPOINT = 0x43
COMMAND_CLASS_THERMOSTAT_FAN_MODE = 0x44
COMMAND_CLASS_CONFIGURATION = 0x70
WORKAROUND_ZXT_120 = 'zxt_120'
WORKAROUND_HRT4_ZW = 'hrt4_zw'
@@ -67,19 +61,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
discovery_info, zwave.NETWORK)
return
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]]
value = node.values[discovery_info[ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
# pylint: disable=too-many-arguments, abstract-method
# pylint: disable=abstract-method
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Represents a ZWave Climate device."""
# pylint: disable=too-many-public-methods, too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
def __init__(self, value, temp_unit):
"""Initialize the zwave climate device."""
from openzwave.network import ZWaveNetwork
@@ -130,7 +124,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Callback on data change for the registered node/value pair."""
# Operation Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
self._current_operation = value.data
self._index_operation = SET_TEMP_TO_INDEX.get(
self._current_operation)
@@ -139,14 +133,16 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
_LOGGER.debug("self._current_operation=%s",
self._current_operation)
# Current Temp
for value in self._node.get_values(
class_id=COMMAND_CLASS_SENSOR_MULTILEVEL).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL)
.values()):
if value.label == 'Temperature':
self._current_temperature = int(value.data)
self._unit = value.units
# Fan Mode
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE)
.values()):
self._current_fan_mode = value.data
self._fan_list = list(value.data_items)
_LOGGER.debug("self._fan_list=%s", self._fan_list)
@@ -154,17 +150,27 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._current_fan_mode)
# Swing mode
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION)
.values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
self._current_swing_mode = value.data
self._swing_list = list(value.data_items)
_LOGGER.debug("self._swing_list=%s", self._swing_list)
_LOGGER.debug("self._current_swing_mode=%s",
self._current_swing_mode)
# Set point
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if value.data == 0:
_LOGGER.debug("Setpoint is 0, setting default to "
"current_temperature=%s",
self._current_temperature)
self._target_temperature = int(self._current_temperature)
break
if self.current_operation is not None and \
self.current_operation != 'Off':
if self._index_operation != value.index:
@@ -203,7 +209,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
return self._swing_list
@property
def unit_of_measurement(self):
def temperature_unit(self):
"""Return the unit of measurement."""
if self._unit == 'C':
return TEMP_CELSIUS
@@ -232,16 +238,26 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Return the temperature we try to reach."""
return self._target_temperature
# pylint: disable=too-many-branches, too-many-statements
def set_temperature(self, **kwargs):
"""Set new target temperature."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
temperature = kwargs.get(ATTR_TEMPERATURE)
else:
return
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
_LOGGER.debug("set_temperature operation_mode=%s", operation_mode)
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT)
.values()):
if operation_mode is not None:
setpoint_mode = SET_TEMP_TO_INDEX.get(operation_mode)
if value.index != setpoint_mode:
continue
_LOGGER.debug("setpoint_mode=%s", setpoint_mode)
value.data = temperature
break
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_SETPOINT).values():
if self.current_operation is not None:
if self._hrt4_zw and self.current_operation == 'Off':
# HRT4-ZW can change setpoint when off.
@@ -261,6 +277,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self._target_temperature = temperature
# ZXT-120 responds only to whole int
value.data = round(temperature, 0)
self.update_ha_state()
break
else:
_LOGGER.debug("Setting new setpoint for %s, "
@@ -278,17 +295,21 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
def set_fan_mode(self, fan):
"""Set new target fan mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_FAN_MODE).values():
if value.command_class == 68 and value.index == 0:
for value in (self._node.get_values(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE).
values()):
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE and \
value.index == 0:
value.data = bytes(fan, 'utf-8')
break
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == 64 and value.index == 0:
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_THERMOSTAT_MODE and value.index == 0:
value.data = bytes(operation_mode, 'utf-8')
break
@@ -296,7 +317,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
"""Set new target swing mode."""
if self._zxt_120 == 1:
for value in self._node.get_values(
class_id=COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == 112 and value.index == 33:
class_id=zwave.const.COMMAND_CLASS_CONFIGURATION).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_CONFIGURATION and \
value.index == 33:
value.data = bytes(swing_mode, 'utf-8')
break

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['fuzzywuzzy==0.11.1']
REQUIREMENTS = ['fuzzywuzzy==0.12.0']
ATTR_TEXT = 'text'
@@ -29,6 +29,10 @@ SERVICE_PROCESS_SCHEMA = vol.Schema({
vol.Required(ATTR_TEXT): vol.All(cv.string, vol.Lower),
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Register the process service."""
@@ -48,8 +52,8 @@ def setup(hass, config):
name, command = match.groups()
entities = {state.entity_id: state.name for state in hass.states.all()}
entity_ids = fuzzyExtract.extractOne(name, entities,
score_cutoff=65)[2]
entity_ids = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
if not entity_ids:
logger.error(
@@ -70,6 +74,7 @@ def setup(hass, config):
logger.error('Got unsupported command %s from text %s',
command, text)
hass.services.register(DOMAIN, SERVICE_PROCESS, process,
schema=SERVICE_PROCESS_SCHEMA)
hass.services.register(
DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA)
return True

View File

@@ -25,9 +25,8 @@ from homeassistant.const import (
DOMAIN = 'cover'
SCAN_INTERVAL = 15
GROUP_NAME_ALL_COVERS = 'all_covers'
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format(
GROUP_NAME_ALL_COVERS)
GROUP_NAME_ALL_COVERS = 'all covers'
ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers')
ENTITY_ID_FORMAT = DOMAIN + '.{}'

View File

@@ -14,7 +14,6 @@ from homeassistant.const import (
CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE,
CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template
_LOGGER = logging.getLogger(__name__)
@@ -24,7 +23,7 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_COMMAND_STATE): cv.string,
vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE, default='{{ value }}'): cv.template,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -38,6 +37,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
covers = []
for device_name, device_config in devices.items():
value_template = device_config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
covers.append(
CommandCover(
hass,
@@ -46,7 +49,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
device_config.get(CONF_COMMAND_CLOSE),
device_config.get(CONF_COMMAND_STOP),
device_config.get(CONF_COMMAND_STATE),
device_config.get(CONF_VALUE_TEMPLATE),
value_template,
)
)
@@ -136,8 +139,8 @@ class CommandCover(CoverDevice):
if self._command_state:
payload = str(self._query_state())
if self._value_template:
payload = template.render_with_possible_json_value(
self._hass, self._value_template, payload)
payload = self._value_template.render_with_possible_json_value(
payload)
self._state = int(payload)
def open_cover(self, **kwargs):

View File

@@ -0,0 +1,109 @@
"""
Support for ISY994 covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.isy994/
"""
import logging
from typing import Callable # noqa
from homeassistant.components.cover import CoverDevice, DOMAIN
import homeassistant.components.isy994 as isy
from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
VALUE_TO_STATE = {
0: STATE_CLOSED,
101: STATE_UNKNOWN,
}
UOM = ['97']
STATES = [STATE_OPEN, STATE_CLOSED, 'closing', 'opening', 'stopped']
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Setup the ISY994 cover platform."""
if isy.ISY is None or not isy.ISY.connected:
_LOGGER.error('A connection has not been made to the ISY controller.')
return False
devices = []
for node in isy.filter_nodes(isy.NODES, units=UOM,
states=STATES):
devices.append(ISYCoverDevice(node))
for program in isy.PROGRAMS.get(DOMAIN, []):
try:
status = program[isy.KEY_STATUS]
actions = program[isy.KEY_ACTIONS]
assert actions.dtype == 'program', 'Not a program'
except (KeyError, AssertionError):
pass
else:
devices.append(ISYCoverProgram(program.name, status, actions))
add_devices(devices)
class ISYCoverDevice(isy.ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device."""
def __init__(self, node: object):
"""Initialize the ISY994 cover device."""
isy.ISYDevice.__init__(self, node)
@property
def current_cover_position(self) -> int:
"""Get the current cover position."""
return sorted((0, self.value, 100))[1]
@property
def is_closed(self) -> bool:
"""Get whether the ISY994 cover device is closed."""
return self.state == STATE_CLOSED
@property
def state(self) -> str:
"""Get the state of the ISY994 cover device."""
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device."""
if not self._node.on(val=100):
_LOGGER.error('Unable to open the cover')
def close_cover(self, **kwargs) -> None:
"""Send the close cover command to the ISY994 cover device."""
if not self._node.off():
_LOGGER.error('Unable to close the cover')
class ISYCoverProgram(ISYCoverDevice):
"""Representation of an ISY994 cover program."""
def __init__(self, name: str, node: object, actions: object) -> None:
"""Initialize the ISY994 cover program."""
ISYCoverDevice.__init__(self, node)
self._name = name
self._actions = actions
@property
def state(self) -> str:
"""Get the state of the ISY994 cover program."""
return STATE_CLOSED if bool(self.value) else STATE_OPEN
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover program."""
if not self._actions.runThen():
_LOGGER.error('Unable to open the cover')
def close_cover(self, **kwargs) -> None:
"""Send the close cover command to the ISY994 cover program."""
if not self._actions.runElse():
_LOGGER.error('Unable to close the cover')

View File

@@ -15,7 +15,6 @@ from homeassistant.const import (
STATE_CLOSED)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
from homeassistant.helpers import template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -28,10 +27,10 @@ CONF_PAYLOAD_STOP = 'payload_stop'
CONF_STATE_OPEN = 'state_open'
CONF_STATE_CLOSED = 'state_closed'
DEFAULT_NAME = "MQTT Cover"
DEFAULT_PAYLOAD_OPEN = "OPEN"
DEFAULT_PAYLOAD_CLOSE = "CLOSE"
DEFAULT_PAYLOAD_STOP = "STOP"
DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = 'OPEN'
DEFAULT_PAYLOAD_CLOSE = 'CLOSE'
DEFAULT_PAYLOAD_STOP = 'STOP'
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
@@ -43,27 +42,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Add MQTT Cover."""
add_devices_callback([MqttCover(
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the MQTT Cover."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
add_devices([MqttCover(
hass,
config[CONF_NAME],
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config[CONF_COMMAND_TOPIC],
config[CONF_QOS],
config[CONF_RETAIN],
config[CONF_STATE_OPEN],
config[CONF_STATE_CLOSED],
config[CONF_PAYLOAD_OPEN],
config[CONF_PAYLOAD_CLOSE],
config[CONF_PAYLOAD_STOP],
config[CONF_OPTIMISTIC],
config.get(CONF_VALUE_TEMPLATE)
config.get(CONF_COMMAND_TOPIC),
config.get(CONF_QOS),
config.get(CONF_RETAIN),
config.get(CONF_STATE_OPEN),
config.get(CONF_STATE_CLOSED),
config.get(CONF_PAYLOAD_OPEN),
config.get(CONF_PAYLOAD_CLOSE),
config.get(CONF_PAYLOAD_STOP),
config.get(CONF_OPTIMISTIC),
value_template,
)])
@@ -93,8 +93,8 @@ class MqttCover(CoverDevice):
def message_received(topic, payload, qos):
"""A new MQTT message has been received."""
if value_template is not None:
payload = template.render_with_possible_json_value(
hass, value_template, payload)
payload = value_template.render_with_possible_json_value(
payload)
if payload == self._state_open:
self._state = False
_LOGGER.warning("state=%s", int(self._state))
@@ -111,8 +111,8 @@ class MqttCover(CoverDevice):
self.update_ha_state()
else:
_LOGGER.warning(
"Payload is not True or False or"
" integer(0-100) %s", payload)
"Payload is not True, False, or integer (0-100): %s",
payload)
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
@@ -149,7 +149,7 @@ class MqttCover(CoverDevice):
self._qos, self._retain)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = 100
self._state = False
self.update_ha_state()
def close_cover(self, **kwargs):
@@ -158,7 +158,7 @@ class MqttCover(CoverDevice):
self._qos, self._retain)
if self._optimistic:
# Optimistically assume that cover has changed state.
self._state = 0
self._state = True
self.update_ha_state()
def stop_cover(self, **kwargs):

View File

@@ -0,0 +1,103 @@
"""
Support for MySensors covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mysensors/
"""
import logging
from homeassistant.components import mysensors
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.const import STATE_ON, STATE_OFF
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for covers."""
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_COVER: [set_req.V_DIMMER, set_req.V_LIGHT],
}
if float(gateway.protocol_version) >= 1.5:
map_sv_types.update({
pres.S_COVER: [set_req.V_PERCENTAGE, set_req.V_STATUS],
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsCover))
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):
"""Representation of the value of a MySensors Cover child node."""
@property
def assumed_state(self):
"""Return True if unable to access real state of entity."""
return self.gateway.optimistic
@property
def is_closed(self):
"""Return True if cover is closed."""
set_req = self.gateway.const.SetReq
if set_req.V_DIMMER in self._values:
return self._values.get(set_req.V_DIMMER) == 0
else:
return self._values.get(set_req.V_LIGHT) == STATE_OFF
@property
def current_cover_position(self):
"""Return current position of cover.
None is unknown, 0 is closed, 100 is fully open.
"""
set_req = self.gateway.const.SetReq
return self._values.get(set_req.V_DIMMER)
def open_cover(self, **kwargs):
"""Move the cover up."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_UP, 1)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
if set_req.V_DIMMER in self._values:
self._values[set_req.V_DIMMER] = 100
else:
self._values[set_req.V_LIGHT] = STATE_ON
self.update_ha_state()
def close_cover(self, **kwargs):
"""Move the cover down."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DOWN, 1)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
if set_req.V_DIMMER in self._values:
self._values[set_req.V_DIMMER] = 0
else:
self._values[set_req.V_LIGHT] = STATE_OFF
self.update_ha_state()
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_DIMMER, position)
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
self._values[set_req.V_DIMMER] = position
self.update_ha_state()
def stop_cover(self, **kwargs):
"""Stop the device."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_STOP, 1)

View File

@@ -7,64 +7,69 @@ https://github.com/andrewshilliday/garage-door-controller
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.rpi_gpio/
"""
import logging
from time import sleep
import voluptuous as vol
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME
import homeassistant.components.rpi_gpio as rpi_gpio
import homeassistant.helpers.config_validation as cv
RELAY_TIME = 'relay_time'
STATE_PULL_MODE = 'state_pull_mode'
DEFAULT_PULL_MODE = 'UP'
DEFAULT_RELAY_TIME = .2
DEPENDENCIES = ['rpi_gpio']
_LOGGER = logging.getLogger(__name__)
CONF_COVERS = 'covers'
CONF_RELAY_PIN = 'relay_pin'
CONF_RELAY_TIME = 'relay_time'
CONF_STATE_PIN = 'state_pin'
CONF_STATE_PULL_MODE = 'state_pull_mode'
DEFAULT_RELAY_TIME = .2
DEFAULT_STATE_PULL_MODE = 'UP'
DEPENDENCIES = ['rpi_gpio']
_COVERS_SCHEMA = vol.All(
cv.ensure_list,
[
vol.Schema({
'name': str,
'relay_pin': int,
'state_pin': int,
CONF_NAME: cv.string,
CONF_RELAY_PIN: cv.positive_int,
CONF_STATE_PIN: cv.positive_int,
})
]
)
PLATFORM_SCHEMA = vol.Schema({
'platform': str,
vol.Required('covers'): _COVERS_SCHEMA,
vol.Optional(STATE_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string,
vol.Optional(RELAY_TIME, default=DEFAULT_RELAY_TIME): vol.Coerce(int),
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): _COVERS_SCHEMA,
vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE):
cv.string,
vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the cover platform."""
relay_time = config.get(RELAY_TIME)
state_pull_mode = config.get(STATE_PULL_MODE)
"""Setup the RPi cover platform."""
relay_time = config.get(CONF_RELAY_TIME)
state_pull_mode = config.get(CONF_STATE_PULL_MODE)
covers = []
covers_conf = config.get('covers')
covers_conf = config.get(CONF_COVERS)
for cover in covers_conf:
covers.append(RPiGPIOCover(cover['name'], cover['relay_pin'],
cover['state_pin'],
state_pull_mode,
relay_time))
covers.append(RPiGPIOCover(
cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN],
state_pull_mode, relay_time))
add_devices(covers)
# pylint: disable=abstract-method
class RPiGPIOCover(CoverDevice):
"""Representation of a Raspberry cover."""
"""Representation of a Raspberry GPIO cover."""
# pylint: disable=too-many-arguments
def __init__(self, name, relay_pin, state_pin,
state_pull_mode, relay_time):
def __init__(self, name, relay_pin, state_pin, state_pull_mode,
relay_time):
"""Initialize the cover."""
self._name = name
self._state = False
@@ -79,7 +84,7 @@ class RPiGPIOCover(CoverDevice):
@property
def unique_id(self):
"""Return the ID of this cover."""
return "{}.{}".format(self.__class__, self._name)
return '{}.{}'.format(self.__class__, self._name)
@property
def name(self):

View File

@@ -6,37 +6,43 @@ https://home-assistant.io/components/cover.scsgate/
"""
import logging
import voluptuous as vol
import homeassistant.components.scsgate as scsgate
from homeassistant.components.cover import CoverDevice
from homeassistant.const import CONF_NAME
from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_DEVICES, CONF_NAME)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['scsgate']
SCS_ID = 'scs_id'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}),
})
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the SCSGate cover."""
devices = config.get('devices')
devices = config.get(CONF_DEVICES)
covers = []
logger = logging.getLogger(__name__)
if devices:
for _, entity_info in devices.items():
if entity_info[SCS_ID] in scsgate.SCSGATE.devices:
if entity_info[scsgate.CONF_SCS_ID] in scsgate.SCSGATE.devices:
continue
logger.info("Adding %s scsgate.cover", entity_info[CONF_NAME])
name = entity_info[CONF_NAME]
scs_id = entity_info[SCS_ID]
cover = SCSGateCover(
name=name,
scs_id=scs_id,
logger=logger)
scs_id = entity_info[scsgate.CONF_SCS_ID]
logger.info("Adding %s scsgate.cover", name)
cover = SCSGateCover(name=name, scs_id=scs_id, logger=logger)
scsgate.SCSGATE.add_device(cover)
covers.append(cover)
add_devices_callback(covers)
add_devices(covers)
# pylint: disable=too-many-arguments, too-many-instance-attributes
@@ -91,6 +97,5 @@ class SCSGateCover(CoverDevice):
def process_event(self, message):
"""Handle a SCSGate message related with this cover."""
self._logger.debug(
"Rollershutter %s, got message %s",
self._scs_id, message.toggled)
self._logger.debug("Cover %s, got message %s",
self._scs_id, message.toggled)

View File

@@ -0,0 +1,70 @@
"""
Support for Vera cover - curtains, rollershutters etc.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.vera/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
DEPENDENCIES = ['vera']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Find and return Vera covers."""
add_devices_callback(
VeraCover(device, VERA_CONTROLLER) for
device in VERA_DEVICES['cover'])
# pylint: disable=abstract-method
class VeraCover(VeraDevice, CoverDevice):
"""Represents a Vera Cover in Home Assistant."""
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
@property
def current_cover_position(self):
"""
Return current position of cover.
0 is closed, 100 is fully open.
"""
position = self.vera_device.get_level()
if position <= 5:
return 0
if position >= 95:
return 100
return position
def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific position."""
self.vera_device.set_level(position)
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is not None:
if self.current_cover_position > 0:
return False
else:
return True
def open_cover(self, **kwargs):
"""Open the cover."""
self.vera_device.open()
def close_cover(self, **kwargs):
"""Close the cover."""
self.vera_device.close()
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.vera_device.stop()

View File

@@ -4,30 +4,17 @@ Support for Wink Covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.wink/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.wink import WinkDevice
from homeassistant.const import CONF_ACCESS_TOKEN
REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2']
DEPENDENCIES = ['wink']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Wink cover platform."""
import pywink
if discovery_info is None:
token = config.get(CONF_ACCESS_TOKEN)
if token is None:
logging.getLogger(__name__).error(
"Missing wink access_token. "
"Get one at https://winkbearertoken.appspot.com/")
return
pywink.set_bearer_token(token)
add_devices(WinkCoverDevice(shade) for shade in
pywink.get_shades())
add_devices(WinkCoverDevice(door) for door in
@@ -35,17 +22,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class WinkCoverDevice(WinkDevice, CoverDevice):
"""Representation of a Wink covers."""
"""Representation of a Wink cover device."""
def __init__(self, wink):
"""Initialize the cover."""
WinkDevice.__init__(self, wink)
@property
def should_poll(self):
"""Wink Shades don't track their position."""
return False
def close_cover(self):
"""Close the shade."""
self.wink.set_state(0)

View File

@@ -12,9 +12,6 @@ from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.cover import CoverDevice
COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38
COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37
SOMFY = 0x47
SOMFY_ZRTSI = 0x5a52
SOMFY_ZRTSI_CONTROLLER = (SOMFY, SOMFY_ZRTSI)
@@ -32,17 +29,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
if (value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL and
value.index == 0):
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL) \
and value.index == 0:
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
elif (value.command_class == zwave.COMMAND_CLASS_SWITCH_BINARY or
value.command_class == zwave.COMMAND_CLASS_BARRIER_OPERATOR):
if value.type != zwave.TYPE_BOOL and \
value.genre != zwave.GENRE_USER:
elif node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_BINARY) or \
node.has_command_class(zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
if value.type != zwave.const.TYPE_BOOL and \
value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
@@ -59,6 +56,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
ZWaveDeviceEntity.__init__(self, value, DOMAIN)
# pylint: disable=no-member
self._lozwmgr = libopenzwave.PyManager()
self._lozwmgr.create()
self._node = value.node
@@ -88,9 +86,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Callback on data change for the registered node/value pair."""
# Position value
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Level':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and \
value.label == 'Level':
self._current_position = value.data
@property
@@ -118,37 +117,40 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
def open_cover(self, **kwargs):
"""Move the roller shutter up."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.pressButton(value.value_id)
break
def close_cover(self, **kwargs):
"""Move the roller shutter down."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Up' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Close':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Up' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Close':
self._lozwmgr.pressButton(value.value_id)
break
def set_cover_position(self, position, **kwargs):
"""Move the roller shutter to a specific position."""
self._node.set_dimmer(self._value.value_id, 100 - position)
self._node.set_dimmer(self._value.value_id, position)
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
for value in self._node.get_values(
class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Open' or \
value.command_class == zwave.COMMAND_CLASS_SWITCH_MULTILEVEL \
and value.label == 'Down':
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL).values():
if value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Open' or value.command_class == \
zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL and value.label == \
'Down':
self._lozwmgr.releaseButton(value.value_id)
break

View File

@@ -67,31 +67,33 @@ def setup(hass, config):
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
media_players = sorted(hass.states.entity_ids('media_player'))
group.Group(hass, 'living room', [
group.Group.create_group(hass, 'living room', [
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights'])
group.Group(hass, 'bedroom', [
group.Group.create_group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance'])
group.Group(hass, 'kitchen', [
group.Group.create_group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door'])
group.Group(hass, 'doors', [
group.Group.create_group(hass, 'doors', [
'lock.front_door', 'lock.kitchen_door',
'garage_door.right_garage_door', 'garage_door.left_garage_door'])
group.Group(hass, 'automations', [
group.Group.create_group(hass, 'automations', [
'input_select.who_cooks', 'input_boolean.notify', ])
group.Group(hass, 'people', [
group.Group.create_group(hass, 'people', [
'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
'device_tracker.demo_paulus'])
group.Group(hass, 'thermostats', [
group.Group.create_group(hass, 'thermostats', [
'thermostat.nest', 'thermostat.thermostat'])
group.Group(hass, 'downstairs', [
group.Group.create_group(hass, 'downstairs', [
'group.living_room', 'group.kitchen',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window', 'group.doors', 'thermostat.nest',
'rollershutter.living_room_window', 'group.doors',
'thermostat.nest',
], view=True)
group.Group(hass, 'Upstairs', [
group.Group.create_group(hass, 'Upstairs', [
'thermostat.thermostat', 'group.bedroom',
], view=True)

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/device_tracker/
"""
# pylint: disable=too-many-instance-attributes, too-many-arguments
# pylint: disable=too-many-locals
import asyncio
from datetime import timedelta
import logging
import os
@@ -13,6 +14,7 @@ import threading
from typing import Any, Sequence, Callable
import voluptuous as vol
import yaml
from homeassistant.bootstrap import (
prepare_setup_platform, log_exception)
@@ -25,6 +27,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
import homeassistant.helpers.config_validation as cv
import homeassistant.util as util
from homeassistant.util.async import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_utc_time_change
@@ -46,7 +49,7 @@ CONF_TRACK_NEW = 'track_new_devices'
DEFAULT_TRACK_NEW = True
CONF_CONSIDER_HOME = 'consider_home'
DEFAULT_CONSIDER_HOME = 180 # seconds
DEFAULT_CONSIDER_HOME = 180
CONF_SCAN_INTERVAL = 'interval_seconds'
DEFAULT_SCAN_INTERVAL = 12
@@ -66,13 +69,11 @@ ATTR_ATTRIBUTES = 'attributes'
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SCAN_INTERVAL): cv.positive_int, # seconds
}, extra=vol.ALLOW_EXTRA)
_CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.All(cv.ensure_list, [
vol.Schema({
vol.Optional(CONF_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME): cv.positive_int # seconds
}, extra=vol.ALLOW_EXTRA)])}, extra=vol.ALLOW_EXTRA)
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
vol.Optional(CONF_CONSIDER_HOME,
default=timedelta(seconds=DEFAULT_CONSIDER_HOME)): vol.All(
cv.time_period, cv.positive_timedelta)
})
DISCOVERY_PLATFORMS = {
SERVICE_NETGEAR: 'netgear',
@@ -112,14 +113,14 @@ def setup(hass: HomeAssistantType, config: ConfigType):
yaml_path = hass.config.path(YAML_DEVICES)
try:
conf = _CONFIG_SCHEMA(config).get(DOMAIN, [])
conf = config.get(DOMAIN, [])
except vol.Invalid as ex:
log_exception(ex, DOMAIN, config)
return False
else:
conf = conf[0] if len(conf) > 0 else {}
consider_home = timedelta(
seconds=conf.get(CONF_CONSIDER_HOME, DEFAULT_CONSIDER_HOME))
consider_home = conf.get(CONF_CONSIDER_HOME,
timedelta(seconds=DEFAULT_CONSIDER_HOME))
track_new = conf.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
devices = load_config(yaml_path, hass, consider_home)
@@ -250,9 +251,18 @@ class DeviceTracker(object):
def setup_group(self):
"""Initialize group for all tracked devices."""
run_coroutine_threadsafe(
self.async_setup_group(), self.hass.loop).result()
@asyncio.coroutine
def async_setup_group(self):
"""Initialize group for all tracked devices.
This method must be run in the event loop.
"""
entity_ids = (dev.entity_id for dev in self.devices.values()
if dev.track)
self.group = group.Group(
self.group = yield from group.Group.async_create_group(
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
def update_stale(self, now: dt_util.dt.datetime):
@@ -282,7 +292,7 @@ class Device(Entity):
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str=None,
picture: str=None, gravatar: str=None,
away_hide: bool=False) -> None:
hide_if_away: bool=False) -> None:
"""Initialize a device."""
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@@ -307,7 +317,7 @@ class Device(Entity):
else:
self.config_picture = picture
self.away_hide = away_hide
self.away_hide = hide_if_away
@property
def name(self):
@@ -398,15 +408,34 @@ class Device(Entity):
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
"""Load devices from YAML configuration file."""
dev_schema = vol.Schema({
vol.Required('name'): cv.string,
vol.Optional('track', default=False): cv.boolean,
vol.Optional('mac', default=None): vol.Any(None, vol.All(cv.string,
vol.Upper)),
vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean,
vol.Optional('gravatar', default=None): vol.Any(None, cv.string),
vol.Optional('picture', default=None): vol.Any(None, cv.string),
vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All(
cv.time_period, cv.positive_timedelta)
})
try:
return [
Device(hass, consider_home, device.get('track', False),
str(dev_id).lower(), None if device.get('mac') is None
else str(device.get('mac')).upper(),
device.get('name'), device.get('picture'),
device.get('gravatar'),
device.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
for dev_id, device in load_yaml_config_file(path).items()]
result = []
try:
devices = load_yaml_config_file(path)
except HomeAssistantError as err:
_LOGGER.error('Unable to load %s: %s', path, str(err))
return []
for dev_id, device in devices.items():
try:
device = dev_schema(device)
device['dev_id'] = cv.slugify(dev_id)
except vol.Invalid as exp:
log_exception(exp, dev_id, devices)
else:
result.append(Device(hass, **device))
return result
except (HomeAssistantError, FileNotFoundError):
# When YAML file could not be loaded/did not contain a dict
return []
@@ -440,14 +469,15 @@ def update_config(path: str, dev_id: str, device: Device):
"""Add device to YAML configuration file."""
with open(path, 'a') as out:
out.write('\n')
out.write('{}:\n'.format(device.dev_id))
for key, value in (('name', device.name), ('mac', device.mac),
('picture', device.config_picture),
('track', 'yes' if device.track else 'no'),
(CONF_AWAY_HIDE,
'yes' if device.away_hide else 'no')):
out.write(' {}: {}\n'.format(key, '' if value is None else value))
device = {device.dev_id: {
'name': device.name,
'mac': device.mac,
'picture': device.config_picture,
'track': device.track,
CONF_AWAY_HIDE: device.away_hide
}}
yaml.dump(device, out, default_flow_style=False)
def get_gravatar_for_email(email: str):

View File

@@ -0,0 +1,82 @@
"""
Support for French FAI Bouygues Bbox routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.bbox/
"""
from collections import namedtuple
import logging
from datetime import timedelta
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=60)
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pybbox==0.0.5-alpha']
def get_scanner(hass, config):
"""Validate the configuration and return a Bbox scanner."""
scanner = BboxDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update'])
class BboxDeviceScanner(object):
"""This class scans for devices connected to the bbox."""
def __init__(self, config):
"""Initialize the scanner."""
self.last_results = [] # type: List[Device]
self.success_init = self._update_info()
_LOGGER.info('Bbox scanner initialized')
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return [device.mac for device in self.last_results]
def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results if
device.mac == mac]
if filter_named:
return filter_named[0]
else:
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
"""Check the bbox for devices.
Returns boolean if scanning successful.
"""
_LOGGER.info('Scanning')
import pybbox
box = pybbox.Bbox()
result = box.get_all_connected_devices()
now = dt_util.now()
last_results = []
for device in result:
if device['active'] != 1:
continue
last_results.append(
Device(device['macaddress'], device['hostname'],
device['ipaddress'], now))
self.last_results = last_results
_LOGGER.info('Bbox scan successful')
return True

View File

@@ -1,4 +1,4 @@
"""Tracking for bluetooth devices."""
"""Tracking for bluetooth low energy devices."""
import logging
from datetime import timedelta

View File

@@ -79,13 +79,15 @@ class FritzBoxScanner(object):
self._update_info()
active_hosts = []
for known_host in self.last_results:
if known_host['status'] == '1':
if known_host['status'] == '1' and known_host.get('mac'):
active_hosts.append(known_host['mac'])
return active_hosts
def get_device_name(self, mac):
"""Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac)['NewHostName']
ret = self.fritz_box.get_specific_host_entry(mac).get(
'NewHostName'
)
if ret == {}:
return None
return ret

View File

@@ -6,9 +6,11 @@ https://home-assistant.io/components/device_tracker.locative/
"""
import logging
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME
from homeassistant.components.http import HomeAssistantView
# pylint: disable=unused-import
from homeassistant.components.device_tracker import ( # NOQA
DOMAIN, PLATFORM_SCHEMA)
_LOGGER = logging.getLogger(__name__)
@@ -25,8 +27,8 @@ def setup_scanner(hass, config, see):
class LocativeView(HomeAssistantView):
"""View to handle locative requests."""
url = "/api/locative"
name = "api:locative"
url = '/api/locative'
name = 'api:locative'
def __init__(self, hass, see):
"""Initialize Locative url endpoints."""
@@ -43,22 +45,22 @@ class LocativeView(HomeAssistantView):
data = request.values
if 'latitude' not in data or 'longitude' not in data:
return ("Latitude and longitude not specified.",
return ('Latitude and longitude not specified.',
HTTP_UNPROCESSABLE_ENTITY)
if 'device' not in data:
_LOGGER.error("Device id not specified.")
return ("Device id not specified.",
_LOGGER.error('Device id not specified.')
return ('Device id not specified.',
HTTP_UNPROCESSABLE_ENTITY)
if 'id' not in data:
_LOGGER.error("Location id not specified.")
return ("Location id not specified.",
_LOGGER.error('Location id not specified.')
return ('Location id not specified.',
HTTP_UNPROCESSABLE_ENTITY)
if 'trigger' not in data:
_LOGGER.error("Trigger is not specified.")
return ("Trigger is not specified.",
_LOGGER.error('Trigger is not specified.')
return ('Trigger is not specified.',
HTTP_UNPROCESSABLE_ENTITY)
device = data['device'].replace('-', '')
@@ -67,15 +69,15 @@ class LocativeView(HomeAssistantView):
if direction == 'enter':
self.see(dev_id=device, location_name=location_name)
return "Setting location to {}".format(location_name)
return 'Setting location to {}'.format(location_name)
elif direction == 'exit':
current_state = self.hass.states.get(
"{}.{}".format(DOMAIN, device))
'{}.{}'.format(DOMAIN, device))
if current_state is None or current_state.state == location_name:
self.see(dev_id=device, location_name=STATE_NOT_HOME)
return "Setting location to not home"
return 'Setting location to not home'
else:
# Ignore the message if it is telling us to exit a zone that we
# aren't currently in. This occurs when a zone is entered
@@ -87,10 +89,10 @@ class LocativeView(HomeAssistantView):
elif direction == 'test':
# In the app, a test message can be sent. Just return something to
# the user to let them know that it works.
return "Received test message."
return 'Received test message.'
else:
_LOGGER.error("Received unidentified message from Locative: %s",
_LOGGER.error('Received unidentified message from Locative: %s',
direction)
return ("Received unidentified message: {}".format(direction),
return ('Received unidentified message: {}'.format(direction),
HTTP_UNPROCESSABLE_ENTITY)

View File

@@ -11,13 +11,14 @@ import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.const import CONF_DEVICES
from homeassistant.components.mqtt import CONF_QOS
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['mqtt']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
vol.Required(CONF_DEVICES): {cv.string: mqtt.valid_subscribe_topic},
})

View File

@@ -10,11 +10,13 @@ import subprocess
from collections import namedtuple
from datetime import timedelta
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.components.device_tracker import DOMAIN
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOSTS
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle, convert
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
@@ -27,18 +29,21 @@ CONF_EXCLUDE = 'exclude'
REQUIREMENTS = ['python-nmap==0.6.1']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOSTS): cv.ensure_list,
vol.Required(CONF_HOME_INTERVAL, default=0): cv.positive_int,
vol.Optional(CONF_EXCLUDE, default=[]):
vol.All(cv.ensure_list, vol.Length(min=1))
})
def get_scanner(hass, config):
"""Validate the configuration and return a Nmap scanner."""
if not validate_config(config, {DOMAIN: [CONF_HOSTS]},
_LOGGER):
return None
scanner = NmapDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
Device = namedtuple("Device", ["mac", "name", "ip", "last_update"])
Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update'])
def _arp(ip_address):
@@ -49,24 +54,26 @@ def _arp(ip_address):
match = re.search(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})', str(out))
if match:
return match.group(0)
_LOGGER.info("No MAC address found for %s", ip_address)
_LOGGER.info('No MAC address found for %s', ip_address)
return None
class NmapDeviceScanner(object):
"""This class scans for devices using nmap."""
exclude = []
def __init__(self, config):
"""Initialize the scanner."""
self.last_results = []
self.hosts = config[CONF_HOSTS]
self.exclude = config.get(CONF_EXCLUDE, [])
minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0)
minutes = config[CONF_HOME_INTERVAL]
self.home_interval = timedelta(minutes=minutes)
self.success_init = self._update_info()
_LOGGER.info("nmap scanner initialized")
_LOGGER.info('nmap scanner initialized')
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
@@ -90,21 +97,18 @@ class NmapDeviceScanner(object):
Returns boolean if scanning successful.
"""
_LOGGER.info("Scanning")
_LOGGER.info('Scanning')
from nmap import PortScanner, PortScannerError
scanner = PortScanner()
options = "-F --host-timeout 5s "
exclude = "--exclude "
options = '-F --host-timeout 5s '
if self.home_interval:
boundary = dt_util.now() - self.home_interval
last_results = [device for device in self.last_results
if device.last_update > boundary]
if last_results:
# Pylint is confused here.
# pylint: disable=no-member
exclude_hosts = self.exclude + [device.ip for device
in last_results]
else:
@@ -113,11 +117,11 @@ class NmapDeviceScanner(object):
last_results = []
exclude_hosts = self.exclude
if exclude_hosts:
exclude = " --exclude {}".format(",".join(exclude_hosts))
options += exclude
options += ' --exclude {}'.format(','.join(exclude_hosts))
try:
result = scanner.scan(hosts=self.hosts, arguments=options)
result = scanner.scan(hosts=' '.join(self.hosts),
arguments=options)
except PortScannerError:
return False
@@ -134,5 +138,5 @@ class NmapDeviceScanner(object):
self.last_results = last_results
_LOGGER.info("nmap scan successful")
_LOGGER.info('nmap scan successful')
return True

View File

@@ -7,6 +7,7 @@ https://home-assistant.io/components/device_tracker.owntracks/
import json
import logging
import threading
import base64
from collections import defaultdict
import voluptuous as vol
@@ -18,53 +19,121 @@ from homeassistant.util import convert, slugify
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REGIONS_ENTERED = defaultdict(list)
MOBILE_BEACONS_ACTIVE = defaultdict(list)
BEACON_DEV_ID = 'beacon'
LOCATION_TOPIC = 'owntracks/+/+'
EVENT_TOPIC = 'owntracks/+/+/event'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
REQUIREMENTS = ['libnacl==1.5.0']
_LOGGER = logging.getLogger(__name__)
LOCK = threading.Lock()
BEACON_DEV_ID = 'beacon'
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
DEPENDENCIES = ['mqtt']
EVENT_TOPIC = 'owntracks/+/+/event'
LOCATION_TOPIC = 'owntracks/+/+'
LOCK = threading.Lock()
MOBILE_BEACONS_ACTIVE = defaultdict(list)
REGIONS_ENTERED = defaultdict(list)
VALIDATE_LOCATION = 'location'
VALIDATE_TRANSITION = 'transition'
VALIDATE_WAYPOINTS = 'waypoints'
WAYPOINT_LAT_KEY = 'lat'
WAYPOINT_LON_KEY = 'lon'
WAYPOINT_TOPIC = 'owntracks/{}/{}/waypoint'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MAX_GPS_ACCURACY): vol.Coerce(float),
vol.Optional(CONF_WAYPOINT_IMPORT, default=True): cv.boolean,
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(cv.ensure_list, [cv.string])
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(
cv.ensure_list, [cv.string]),
vol.Optional(CONF_SECRET): vol.Any(
vol.Schema({vol.Optional(cv.string): cv.string}),
cv.string)
})
def get_cipher():
"""Return decryption function and length of key."""
from libnacl import crypto_secretbox_KEYBYTES as KEYLEN
from libnacl.secret import SecretBox
def decrypt(ciphertext, key):
"""Decrypt ciphertext using key."""
return SecretBox(key).decrypt(ciphertext)
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see):
"""Setup an OwnTracks tracker."""
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
def decrypt_payload(topic, ciphertext):
"""Decrypt encrypted payload."""
try:
keylen, decrypt = get_cipher()
except OSError:
_LOGGER.warning('Ignoring encrypted payload '
'because libsodium not installed.')
return None
if isinstance(secret, dict):
key = secret.get(topic)
else:
key = secret
if key is None:
_LOGGER.warning('Ignoring encrypted payload '
'because no decryption key known '
'for topic %s.', topic)
return None
key = key.encode("utf-8")
key = key[:keylen]
key = key.ljust(keylen, b'\0')
try:
ciphertext = base64.b64decode(ciphertext)
message = decrypt(ciphertext, key)
message = message.decode("utf-8")
_LOGGER.debug("Decrypted payload: %s", message)
return message
except ValueError:
_LOGGER.warning('Ignoring encrypted payload '
'because unable to decrypt using key '
'for topic %s.', topic)
return None
def validate_payload(topic, payload, data_type):
"""Validate the OwnTracks payload."""
# pylint: disable=too-many-return-statements
def validate_payload(payload, data_type):
"""Validate OwnTracks payload."""
try:
data = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error('Unable to parse payload as JSON: %s', payload)
return None
if isinstance(data, dict) and \
data.get('_type') == 'encrypted' and \
'data' in data:
plaintext_payload = decrypt_payload(topic, data['data'])
if plaintext_payload is None:
return None
else:
return validate_payload(topic, plaintext_payload, data_type)
if not isinstance(data, dict) or data.get('_type') != data_type:
_LOGGER.debug('Skipping %s update for following data '
'because of missing or malformatted data: %s',
@@ -90,7 +159,7 @@ def setup_scanner(hass, config, see):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
data = validate_payload(payload, VALIDATE_LOCATION)
data = validate_payload(topic, payload, VALIDATE_LOCATION)
if not data:
return
@@ -111,7 +180,7 @@ def setup_scanner(hass, config, see):
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
data = validate_payload(payload, VALIDATE_TRANSITION)
data = validate_payload(topic, payload, VALIDATE_TRANSITION)
if not data:
return
@@ -206,7 +275,7 @@ def setup_scanner(hass, config, see):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
data = validate_payload(payload, VALIDATE_WAYPOINTS)
data = validate_payload(topic, payload, VALIDATE_WAYPOINTS)
if not data:
return
@@ -218,9 +287,18 @@ def setup_scanner(hass, config, see):
lat = wayp[WAYPOINT_LAT_KEY]
lon = wayp[WAYPOINT_LON_KEY]
rad = wayp['rad']
# check zone exists
entity_id = zone_comp.ENTITY_ID_FORMAT.format(slugify(pretty_name))
# Check if state already exists
if hass.states.get(entity_id) is not None:
continue
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False, True)
zone_comp.add_zone(hass, pretty_name, zone)
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
zone.update_ha_state()
def see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location."""

View File

@@ -23,11 +23,17 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.2']
CONF_COMMUNITY = "community"
CONF_AUTHKEY = "authkey"
CONF_PRIVKEY = "privkey"
CONF_BASEOID = "baseoid"
DEFAULT_COMMUNITY = "public"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_COMMUNITY): cv.string,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
vol.Inclusive(CONF_AUTHKEY, "keys"): cv.string,
vol.Inclusive(CONF_PRIVKEY, "keys"): cv.string,
vol.Required(CONF_BASEOID): cv.string
})
@@ -43,13 +49,24 @@ def get_scanner(hass, config):
class SnmpScanner(object):
"""Queries any SNMP capable Access Point for connected devices."""
# pylint: disable=too-many-instance-attributes
def __init__(self, config):
"""Initialize the scanner."""
from pysnmp.entity.rfc3413.oneliner import cmdgen
from pysnmp.entity import config as cfg
self.snmp = cmdgen.CommandGenerator()
self.host = cmdgen.UdpTransportTarget((config[CONF_HOST], 161))
self.community = cmdgen.CommunityData(config[CONF_COMMUNITY])
if CONF_AUTHKEY not in config or CONF_PRIVKEY not in config:
self.auth = cmdgen.CommunityData(config[CONF_COMMUNITY])
else:
self.auth = cmdgen.UsmUserData(
config[CONF_COMMUNITY],
config[CONF_AUTHKEY],
config[CONF_PRIVKEY],
authProtocol=cfg.usmHMACSHAAuthProtocol,
privProtocol=cfg.usmAesCfb128Protocol
)
self.baseoid = cmdgen.MibVariable(config[CONF_BASEOID])
self.lock = threading.Lock()
@@ -95,7 +112,7 @@ class SnmpScanner(object):
devices = []
errindication, errstatus, errindex, restable = self.snmp.nextCmd(
self.community, self.host, self.baseoid)
self.auth, self.host, self.baseoid)
if errindication:
_LOGGER.error("SNMPLIB error: %s", errindication)

View File

@@ -10,9 +10,11 @@ import telnetlib
import threading
from datetime import timedelta
from homeassistant.components.device_tracker import DOMAIN
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago.
@@ -21,23 +23,24 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
_LOGGER = logging.getLogger(__name__)
_DEVICES_REGEX = re.compile(
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' +
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' +
r'(?P<status>([^\s]+))\s+' +
r'(?P<type>([^\s]+))\s+' +
r'(?P<intf>([^\s]+))\s+' +
r'(?P<hwintf>([^\s]+))\s+' +
r'(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s'
r'(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+'
r'(?P<status>([^\s]+))\s+'
r'(?P<type>([^\s]+))\s+'
r'(?P<intf>([^\s]+))\s+'
r'(?P<hwintf>([^\s]+))\s+'
r'(?P<host>([^\s]+))')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return a THOMSON scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
scanner = ThomsonDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@@ -84,7 +87,7 @@ class ThomsonDeviceScanner(object):
return False
with self.lock:
_LOGGER.info("Checking ARP")
_LOGGER.info('Checking ARP')
data = self.get_thomson_data()
if not data:
return False
@@ -108,11 +111,11 @@ class ThomsonDeviceScanner(object):
devices_result = telnet.read_until(b'=>').split(b'\r\n')
telnet.write('exit\r\n'.encode('ascii'))
except EOFError:
_LOGGER.exception("Unexpected response from router")
_LOGGER.exception('Unexpected response from router')
return
except ConnectionRefusedError:
_LOGGER.exception("Connection refused by router," +
" is telnet enabled?")
_LOGGER.exception('Connection refused by router,'
' is telnet enabled?')
return
devices = {}

View File

@@ -12,10 +12,11 @@ import threading
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.device_tracker import DOMAIN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import validate_config
from homeassistant.util import Throttle
# Return cached results if last scan was less then this time ago
@@ -23,26 +24,22 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return a TP-Link scanner."""
if not validate_config(config,
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
_LOGGER):
return None
for cls in [Tplink4DeviceScanner, Tplink3DeviceScanner,
Tplink2DeviceScanner, TplinkDeviceScanner]:
scanner = cls(config[DOMAIN])
if scanner.success_init:
return scanner
scanner = Tplink4DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink3DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = Tplink2DeviceScanner(config[DOMAIN])
if not scanner.success_init:
scanner = TplinkDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
return None
class TplinkDeviceScanner(object):

View File

@@ -0,0 +1,101 @@
"""
Support for Volvo On Call.
http://www.volvocars.com/intl/own/owner-info/volvo-on-call
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.volvooncall/
"""
import logging
from datetime import timedelta
from urllib.parse import urljoin
import voluptuous as vol
import requests
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.util import slugify
from homeassistant.const import (
CONF_PASSWORD,
CONF_SCAN_INTERVAL,
CONF_USERNAME)
from homeassistant.components.device_tracker import (
DEFAULT_SCAN_INTERVAL,
PLATFORM_SCHEMA)
MIN_TIME_BETWEEN_SCANS = timedelta(minutes=1)
_LOGGER = logging.getLogger(__name__)
SERVICE_URL = 'https://vocapi.wirelesscar.net/customerapi/rest/v3.0/'
HEADERS = {"X-Device-Id": "Device",
"X-OS-Type": "Android",
"X-Originator-Type": "App"}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
def setup_scanner(hass, config, see):
"""Validate the configuration and return a scanner."""
session = requests.Session()
session.headers.update(HEADERS)
session.auth = (config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
interval = max(MIN_TIME_BETWEEN_SCANS.seconds,
config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL))
def query(ref, rel=SERVICE_URL):
"""Perform a query to the online service."""
url = urljoin(rel, ref)
_LOGGER.debug("Request for %s", url)
res = session.get(url, timeout=15)
res.raise_for_status()
_LOGGER.debug("Received %s", res.json())
return res.json()
def update(now):
"""Update status from the online service."""
try:
_LOGGER.debug("Updating")
status = query("status", vehicle_url)
position = query("position", vehicle_url)
see(dev_id=dev_id,
host_name=host_name,
gps=(position["position"]["latitude"],
position["position"]["longitude"]),
attributes=dict(
tank_volume=attributes["fuelTankVolume"],
washer_fluid=status["washerFluidLevel"],
brake_fluid=status["brakeFluid"],
service_warning=status["serviceWarningStatus"],
fuel=status["fuelAmount"],
odometer=status["odometer"],
range=status["distanceToEmpty"]))
except requests.exceptions.RequestException as error:
_LOGGER.error("Could not query server: %s", error)
finally:
track_point_in_utc_time(hass, update,
now + timedelta(seconds=interval))
try:
_LOGGER.info('Logging in to service')
user = query("customeraccounts")
rel = query(user["accountVehicleRelations"][0])
vehicle_url = rel["vehicle"] + '/'
attributes = query("attributes", vehicle_url)
dev_id = "volvo_" + slugify(attributes["registrationNumber"])
host_name = "%s %s/%s" % (attributes["registrationNumber"],
attributes["vehicleType"],
attributes["modelYear"])
update(utcnow())
return True
except requests.exceptions.RequestException as error:
_LOGGER.error("Could not log in to service. "
"Please check configuration: "
"%s", error)
return False

View File

@@ -0,0 +1,86 @@
"""
Support for Digital Ocean.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/digital_ocean/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-digitalocean==1.10.0']
_LOGGER = logging.getLogger(__name__)
ATTR_CREATED_AT = 'created_at'
ATTR_DROPLET_ID = 'droplet_id'
ATTR_DROPLET_NAME = 'droplet_name'
ATTR_FEATURES = 'features'
ATTR_IPV4_ADDRESS = 'ipv4_address'
ATTR_IPV6_ADDRESS = 'ipv6_address'
ATTR_MEMORY = 'memory'
ATTR_REGION = 'region'
ATTR_VCPUS = 'vcpus'
CONF_DROPLETS = 'droplets'
DIGITAL_OCEAN = None
DIGITAL_OCEAN_PLATFORMS = ['switch', 'binary_sensor']
DOMAIN = 'digital_ocean'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_ACCESS_TOKEN): cv.string,
}),
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument,too-few-public-methods
def setup(hass, config):
"""Set up the Digital Ocean component."""
conf = config[DOMAIN]
access_token = conf.get(CONF_ACCESS_TOKEN)
global DIGITAL_OCEAN
DIGITAL_OCEAN = DigitalOcean(access_token)
if not DIGITAL_OCEAN.manager.get_account():
_LOGGER.error("No Digital Ocean account found for the given API Token")
return False
return True
class DigitalOcean(object):
"""Handle all communication with the Digital Ocean API."""
def __init__(self, access_token):
"""Initialize the Digital Ocean connection."""
import digitalocean
self._access_token = access_token
self.data = None
self.manager = digitalocean.Manager(token=self._access_token)
def get_droplet_id(self, droplet_name):
"""Get the status of a Digital Ocean droplet."""
droplet_id = None
all_droplets = self.manager.get_all_droplets()
for droplet in all_droplets:
if droplet_name == droplet.name:
droplet_id = droplet.id
return droplet_id
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Use the data from Digital Ocean API."""
self.data = self.manager.get_all_droplets()

View File

@@ -9,18 +9,22 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired.
import logging
import threading
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
REQUIREMENTS = ['netdisco==0.7.1']
REQUIREMENTS = ['netdisco==0.7.2']
DOMAIN = 'discovery'
SCAN_INTERVAL = 300 # seconds
SERVICE_NETGEAR = 'netgear_router'
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HASS_IOS_APP = 'hass_ios'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
SERVICE_NETGEAR: ('device_tracker', None),
SERVICE_WEMO: ('wemo', None),
'philips_hue': ('light', 'hue'),
@@ -33,6 +37,10 @@ SERVICE_HANDLERS = {
'directv': ('media_player', 'directv'),
}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Start a discovery service."""

View File

@@ -0,0 +1,95 @@
"""
A component which allows you to send data to Emoncms.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/emoncms_history/
"""
import logging
from datetime import timedelta
import voluptuous as vol
import requests
from homeassistant.const import (
CONF_API_KEY, CONF_WHITELIST,
CONF_URL, STATE_UNKNOWN,
STATE_UNAVAILABLE,
CONF_SCAN_INTERVAL)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import state as state_helper
from homeassistant.helpers.event import track_point_in_time
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
DOMAIN = "emoncms_history"
CONF_INPUTNODE = "inputnode"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_URL): cv.string,
vol.Required(CONF_INPUTNODE): cv.positive_int,
vol.Required(CONF_WHITELIST): cv.entity_ids,
vol.Optional(CONF_SCAN_INTERVAL, default=30): cv.positive_int,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup the emoncms_history component."""
conf = config[DOMAIN]
whitelist = conf.get(CONF_WHITELIST)
def send_data(url, apikey, node, payload):
"""Send payload data to emoncms."""
try:
fullurl = "{}/input/post.json".format(url)
req = requests.post(fullurl,
params={"node": node},
data={"apikey": apikey,
"data": payload},
allow_redirects=True,
timeout=5)
except requests.exceptions.RequestException:
_LOGGER.error("Error saving data '%s' to '%s'",
payload, fullurl)
else:
if req.status_code != 200:
_LOGGER.error("Error saving data '%s' to '%s'" +
"(http status code = %d)", payload,
fullurl, req.status_code)
def update_emoncms(time):
"""Send whitelisted entities states reguarly to emoncms."""
payload_dict = {}
for entity_id in whitelist:
state = hass.states.get(entity_id)
if state is None or state.state in (
STATE_UNKNOWN, "", STATE_UNAVAILABLE):
continue
try:
payload_dict[entity_id] = state_helper.state_as_number(
state)
except ValueError:
continue
if len(payload_dict) > 0:
payload = "{%s}" % ",".join("{}:{}".format(key, val)
for key, val in
payload_dict.items())
send_data(conf.get(CONF_URL), conf.get(CONF_API_KEY),
str(conf.get(CONF_INPUTNODE)), payload)
track_point_in_time(hass, update_emoncms, time +
timedelta(seconds=conf.get(
CONF_SCAN_INTERVAL)))
update_emoncms(dt_util.utcnow())
return True

1087
homeassistant/components/emulated_hue.py Executable file → Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.entity import Entity
from homeassistant.components.discovery import load_platform
REQUIREMENTS = ['pyenvisalink==1.0', 'pydispatcher==2.0.5']
REQUIREMENTS = ['pyenvisalink==1.7', 'pydispatcher==2.0.5']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'envisalink'
@@ -34,12 +34,14 @@ CONF_PARTITIONS = 'partitions'
CONF_ZONENAME = 'name'
CONF_ZONETYPE = 'type'
CONF_PARTITIONNAME = 'name'
CONF_PANIC = 'panic_type'
DEFAULT_PORT = 4025
DEFAULT_EVL_VERSION = 3
DEFAULT_KEEPALIVE = 60
DEFAULT_ZONEDUMP_INTERVAL = 30
DEFAULT_ZONETYPE = 'opening'
DEFAULT_PANIC = 'Police'
SIGNAL_ZONE_UPDATE = 'zones_updated'
SIGNAL_PARTITION_UPDATE = 'partition_updated'
@@ -60,6 +62,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASS): cv.string,
vol.Required(CONF_CODE): cv.string,
vol.Optional(CONF_PANIC, default=DEFAULT_PANIC): cv.string,
vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA},
vol.Optional(CONF_PARTITIONS): {vol.Coerce(int): PARTITION_SCHEMA},
vol.Optional(CONF_EVL_PORT, default=DEFAULT_PORT): cv.port,
@@ -89,6 +92,7 @@ def setup(hass, base_config):
_port = config.get(CONF_EVL_PORT)
_code = config.get(CONF_CODE)
_panel_type = config.get(CONF_PANEL_TYPE)
_panic_type = config.get(CONF_PANIC)
_version = config.get(CONF_EVL_VERSION)
_user = config.get(CONF_USERNAME)
_pass = config.get(CONF_PASS)
@@ -104,7 +108,8 @@ def setup(hass, base_config):
_user,
_pass,
_zone_dump,
_keep_alive)
_keep_alive,
hass.loop)
def login_fail_callback(data):
"""Callback for when the evl rejects our login."""
@@ -149,7 +154,7 @@ def setup(hass, base_config):
def start_envisalink(event):
"""Startup process for the Envisalink."""
EVL_CONTROLLER.start()
hass.loop.call_soon_threadsafe(EVL_CONTROLLER.start)
for _ in range(10):
if 'success' in _connect_status:
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_envisalink)
@@ -177,14 +182,15 @@ def setup(hass, base_config):
# Load sub-components for Envisalink
if _partitions:
load_platform(hass, 'alarm_control_panel', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
{CONF_PARTITIONS: _partitions,
CONF_CODE: _code,
CONF_PANIC: _panic_type}, base_config)
load_platform(hass, 'sensor', 'envisalink',
{'partitions': _partitions,
'code': _code}, config)
{CONF_PARTITIONS: _partitions,
CONF_CODE: _code}, base_config)
if _zones:
load_platform(hass, 'binary_sensor', 'envisalink',
{'zones': _zones}, config)
{CONF_ZONES: _zones}, base_config)
return True

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