Compare commits

...

542 Commits
0.55 ... 0.58

Author SHA1 Message Date
Paulus Schoutsen
d9a6d9ee73 Merge pull request #10630 from home-assistant/release-0-58
0.58
2017-11-17 22:27:35 -08:00
Robbie Trencheny
425c027085 Implement entity and domain exclude/include for Alexa (#10647)
* Implement entity and domain exclude/include for Alexa

* Switch to using generate_filter

* Use proper domain for turn on/off calls except for groups where we must use the generic homeassistant.turn_on/off

* travis fixes

* Untangle

* Lint
2017-11-17 22:00:11 -08:00
Pierre Ståhl
35699273da Bump pyatv to 0.3.8 (#10643)
Fixes AirPlay issues on newer versions of tvOS.
2017-11-17 22:00:11 -08:00
Andrey
b86110a15d Print entity type in "too slow" warnings (#10641)
* Update entity.py

* Update entity.py
2017-11-17 22:00:10 -08:00
Robbie Trencheny
e449ceeeff Alexa improvements (#10632)
* Initial scene support

* Initial fan support

* ordering

* Initial lock support

* Scenes cant be deactivated; Correct the scene display category

* Initial input_boolean support

* Support customization of Alexa discovered entities

* Initial media player support

* Add input_boolean to tests

* Add play/pause/stop/next/previous to media player

* Add missing functions and pylint

* Set manufacturerName to Home Assistant since the value is displayed in app

* Add scene test

* Add fan tests

* Add lock test

* Fix volume logic

* Add volume tests

* settup -> setup

* Remove unused variable

* Set required scene description as per docs

* Allow setting scene category (ACTIVITY_TRIGGER/SCENE_TRIGGER)

* Add alert, automation and group support/tests

* Change display categories to match docs

* simplify down the display category props into a single prop which can be used on any entity

* Fix tests to expect proper display categories

* Add cover support

* sort things

* Use generic homeassistant domain for turn on/off
2017-11-17 22:00:10 -08:00
Cezar Sá Espinola
bf8e2bd77e Make MQTT reconnection logic more resilient and fix race condition (#10133) 2017-11-17 22:00:10 -08:00
Aaron Bach
0202e966ea Fixes AirVisual bug regarding incorrect location data (#10054)
* Fixes AirVisual bug regarding incorrect location data

* Owner-requested changes
2017-11-17 22:00:09 -08:00
Paulus Schoutsen
b3d66e5881 Update frontend to 20171118.0 2017-11-17 19:09:47 -08:00
Paulus Schoutsen
eb8a8f6d0b Version bump to 0.58.0 2017-11-16 22:10:40 -08:00
Paulus Schoutsen
62c8843956 Update frontend to 20171117.1 2017-11-16 22:08:19 -08:00
John Arild Berentsen
1bb37aff0c Add loglinefetch for frontend API call (#10579)
* Add loglinefetch for frontend API call

* Too many blank lines

* Review changes

* review changes

* Only return a text

* Use aiohttp

* Don't do I/O in event loop

* Move lines to query and default to 0

* Small fixes
2017-11-16 22:07:08 -08:00
Egor Tsinko
f052a0926b Added sorted() to python_script (#10621)
* added sorted() to python_script

* fixed lint errors
2017-11-16 22:06:02 -08:00
Anders Melchiorsen
24aeea5ca3 Adjust logging in downloader component (#10622) 2017-11-16 22:05:08 -08:00
Paulus Schoutsen
5c20cc32b5 Update frontend to 20171117.0 2017-11-16 22:03:31 -08:00
Corey Pauley
6cf2e758a8 Alexa slot synonym fix (#10614)
* Added logic to the alexa component for handling slot synonyms

* Moved note with long url to the top of the file

* Just made a tiny url instead of messing with Flake8

* Refactored to be more Pythonic

* Put trailing comma back
2017-11-16 21:09:00 -08:00
Adam Mills
aa6b37912a Fix async missing decorators (#10628) 2017-11-16 21:03:05 -08:00
Jan Losinski
693d32fa68 Snapcast: bump version and enable reconnect. (#10626)
This bumps the used snapcast version to 2.0.8 and enables the new
reconnect feature that causes the component to reconnect to a server if
the connection was lost.

This fixes the ned to restart Home Assstant after a snapcast reboot, as
described in issue #10264.

Signed-off-by: Jan Losinski <losinski@wh2.tu-dresden.de>
2017-11-16 20:32:26 -05:00
Andrey
072ed7ea13 Allow to pass YandexTTS options via sevice call (#10578) 2017-11-16 09:10:25 -08:00
Mitko Masarliev
bd5a16d70b update hbmqtt to 0.9.1 (#10611) 2017-11-16 07:47:37 -08:00
Milan V
eb7643e163 Improve WUnderground config validation (#10573)
* Fix WUnderground config validation

* Fix indentation
2017-11-16 10:26:23 -05:00
Milan V
79ca93f892 Change generic thermostat to control heating on mode change Off -> Auto (#10601)
* Change generic thermostat to control heating on mode change Off -> Auto

* Fix typo
2017-11-16 13:11:46 +01:00
Colin Dunn
3dbae5ca5b Correct input_datetime initial value parsing (#10417)
* Correct input_datetime initial value parsing

* Correct input_datetime initial value parsing
2017-11-15 23:16:22 -08:00
Pascal Vizeli
1719fa7008 Cleanup old stale restore feature (#10593)
* Cleanup old stale restore feature

* cleanup

* Update __init__.py

* Update test_demo.py

* Lint
2017-11-15 23:03:41 -08:00
Per Osbäck
d4bd4c114b add support for color temperature and color to Google Assistant (#10039)
* add support for color temperature and color; also add some extra deviceInfo attributes

* change so that default behaviour doesn't turn off device if the action isn't handled

* add tests

* fix lint

* more lint

* use attributes were applicable

* removed debug logging

* fix unassigned if only None returned

* report more data in QUERY

* better tests for color and temperature

* fixes after dev merge

* remove deviceInfo as not part of a device state (PR #10399)

* fix after merge
2017-11-15 23:00:43 -08:00
boltgolt
f494c32866 Small fix to be able to use mac and vendor in "device_tracker_new_device" event. (#10537)
* Small fix to be able to use mac and vendor in EVENT_NEW_DEVICE event

* Missed device_tracker test
2017-11-15 22:41:39 -08:00
Fabian Affolter
e20fd3b973 Upgrade mypy to 0.550 (#10591) 2017-11-15 22:35:18 -08:00
ziotibia81
270846c2f5 Modbus switch register support (#10563)
* Update modbus.py

* Fix blank linea and whitespaces

* Fix visual indent

* Fix visual indent

* fix multiple statements on one line

* Typo

* Disable pylint check

# pylint: disable=super-init-not-called

* Fix code style
2017-11-15 22:17:10 -08:00
Fabrizio Furnari
b2ab4443a7 New sensor viaggiatreno. (#10522)
* New sensor viaggiatreno.

I've messed up the previous PR so here it is in a new one.
Should include also all corrections from @pvizeli

* fixes from PR 10522

* fixed import order

* requested changes from MartinHjelmare
2017-11-15 22:07:16 -08:00
Craig J. Ward
17cd64966d bump client version (#10610) 2017-11-15 22:04:26 -08:00
Michael Chang
48181a9388 Support script execution for Alexa (#10517)
* Support script execution for Alexa

* Use PowerController for the script component
2017-11-15 21:44:27 -08:00
Andrey
d5cba0b716 Allow unicode when dumping yaml (#10607) 2017-11-15 18:24:08 -08:00
Alok Saboo
3a0c749a12 Fix Hikvision (motion) switch bug (#10608)
* Fix Hikvision switch bug

* Added comment about last working version
2017-11-16 01:15:45 +01:00
ziotibia81
d652d793f3 Fix ValueError exception (#10596)
* Fix ValueError exception

structure = '>{:c}'.format(data_types[register.get(CONF_DATA_TYPE)][register.get(CONF_COUNT)])
give:
ValueError: Unknown format code 'c' for object of type 'str'

* Minor typo
2017-11-15 18:17:17 -05:00
Pierre Ståhl
87995ad62c Do not add panel from system_log (#10600)
The frontend will not have this panel.
2017-11-15 23:45:08 +01:00
Jeremy Williams
c2d0c8fba4 Arlo - Fixes for updated library (#9892)
* Reduce update calls to API. Add signal strength monitor.

* Fix lint errors

* Fix indent

* Update pyarlo version and review fixes

* Fix lint errors

* Remove staticmethod

* Clean up attributes

* Update arlo.py
2017-11-15 23:33:50 +01:00
On Freund
c7b0f25eae Fix Yahoo Weather icons over SSL (#10602) 2017-11-15 22:27:26 +02:00
Fabian Affolter
d5b170f761 Upgrade youtube_dl to 2017.11.15 (#10592) 2017-11-15 12:41:25 +01:00
Paulus Schoutsen
ea7ffff0ca Cloud updates (#10567)
* Update cloud

* Fix tests

* Lint
2017-11-15 08:16:19 +01:00
Paulus Schoutsen
0cd3271dfa Update frontend to 20171115.0 2017-11-14 22:48:31 -08:00
Paulus Schoutsen
7920ddda9d Add panel build type (#10589) 2017-11-14 22:39:06 -08:00
NovapaX
1e493dcb8a Tradfri unique identities (#10414)
* Unique identity
Use unique ID for generating keys and store them in config. Fallback to
old id so existing installs will still work.

* Remove Timeouts
they don't really work. this should be fixed in pytradfri I think.

* import uuid only when necessary

* more selective import

* lint

* use load_json and save_json from util.json

* remove unnecessary imports

* use async configurator functions

* async configurator calls

* thou shalt not mixup the (a)syncs

* again: no asyncs in the syncs!
last warning...

* Update tradfri.py
2017-11-14 22:16:21 -08:00
Pierre Ståhl
8111e3944c Add basic backend support for a system log (#10492)
Everything logged with "warning" or "error" is stored and exposed via
the HTTP API, that can be used by the frontend.
2017-11-14 20:35:56 -08:00
NovapaX
8d91de877a turn service call handler into coroutine (#10576) 2017-11-14 20:32:48 -08:00
Eitan Mosenkis
0b4de54725 Google Assistant for climate entities: Support QUERY and respect system-wide unit_system setting. (#10346) 2017-11-14 20:19:42 -08:00
marthoc
309e493e76 Add code to enable discovery for mqtt cover (#10580)
* Add code to enable discovery for mqtt cover

* Fix pylint error
2017-11-14 20:19:15 -08:00
Marcelo Moreira de Mello
95c831d5bc Bump ring_doorbell to 0.1.7 (#10566) 2017-11-14 15:56:42 +01:00
Andreas Björshammar
061253fded Verisure: Added option to set installation giid (#10504)
* Added option to set installation giid

* Changed where giid config var is being checked

* Style fix

* Fix style
2017-11-14 15:53:26 +01:00
Eugenio Panadero
e947e6a143 Use a template for the Universal media player state (#10395)
* Implementation of `state_template` for the Universal media_player

* add tracking to entities in state template

* use normal config_validation

* fix tests, use defaults in platform schema, remove extra keys

* and test the new option `state_template`

* lint fixes

* no need to check attributes against None

* use `async_added_to_hass` and call `async_track_state_change` from `hass.helpers`
2017-11-14 11:41:19 +01:00
Martin Hjelmare
dc6e50c39d Fix lametric sound (#10562)
* Fix sound for lametric notify

* Remove not used method
2017-11-14 10:40:44 +01:00
Abílio Costa
637b058a7e webostv: Reduce default timeout to prevent log spamming (#10564)
With the default timeout of 10 seconds, the log gets filled up with "component is taking more than 10 seconds" errors.
This should probably be fixed in some other way, but for now this reduces the problem a bit.
2017-11-14 10:37:52 +01:00
Fabian Affolter
d25f676711 Move temperature display helper from components to helpers (#10555) 2017-11-14 10:36:18 +01:00
Steve Edson
b1afed9e52 pad packets to multiple of 4 characters (#10560)
* pad packets to multiple of 4 characters

This fixes sending commands, see #7669

* Update broadlink.py

* removed whitespace
2017-11-14 09:18:06 +01:00
Kenny Millington
7c24d77031 Don't use the 'id' field since it can be autogenerated (fixes #10551). (#10554) 2017-11-13 22:46:26 -08:00
ziotibia81
e33451e2b9 Better support for int types (#10409)
* Better int types support

* type

* Added optional register order

* Fix white spaces

* Fix line length

* Fix line too long

* Fix trailing whitespace

* Stylistc code fixes
2017-11-13 23:27:15 +01:00
Ari Lotter
2dcde12d38 Support presence detection using Hitron Coda router (#9682)
* Support presence detection using Hitron Coda router

* at least 2 spaces before inline comment

* Update hitron_coda.py

* rewrote authentication code, it actually works now

* make line slightly shorter to comply with hound

* Removed hardcoded IP address

* Fix string formatting, add timeout, and use generator

* Update hitron_coda.py

* Update hitron_coda.py

* Update hitron_coda.py

* typo

* update .coveragerc

* Update stale URL
2017-11-13 23:10:39 +01:00
Martin Hjelmare
3c135deec8 Fix and clean lametric (#10391)
* Fix and clean lametric

* Add missing DEPENDENCIES in notify platform.
* Remove not needed method in component manager class.
* Don't overwrite notify DOMAIN.
* Return consistently depending on found devices in setup of component.

* Get new token if token expired

* Add debug log for getting new token

* Clean up
2017-11-13 21:12:15 +01:00
Fabian Affolter
6974f2366d Upgrade pysnmp to 4.4.2 (#10539) 2017-11-13 09:24:07 -08:00
Ruslan Sayfutdinov
a6d9c7a621 webostv: set current source correctly (#10548) 2017-11-13 09:23:42 -08:00
Anders Melchiorsen
46fe9ed200 Optimize concurrent access to media player image cache (#10345)
We now do locking to ensure that an image is only downloaded and added
once, even when requested by multiple media players at the same time.
2017-11-13 09:03:12 -08:00
r4nd0mbr1ck
f6d511ac1a Google Assistant request sync service (#10165)
* Initial commit for request_sync functionality

* Fixes for Tox results

* Fixed all tox issues and tested locally with GA

* Review comments - api_key, conditional read descriptions

* Add test for service
2017-11-13 08:32:23 -08:00
Diogo Gomes
bc23799c71 Change to device state attributes (#10536)
* Following the suggestion of @MartinHjelmare
2017-11-12 15:25:44 +01:00
Per Osbäck
59e943b3c1 notify.html5: use new json save and load functions (#10416)
* update to use new save_json and load_json

* it is no longer possible to determine if the json file contains valid or empty data.

* fix lint
2017-11-11 15:57:11 -08:00
Paulus Schoutsen
c8648fbfb8 Pre-construct frontend index.html (#10520)
* Pre-construct frontend index.html

* Cache templates

* Update frontend to 20171111.0

* Fix iframe panel test
2017-11-11 15:22:05 -08:00
Vignesh Venkat
96e7944fa8 telegram_bot: Support for sending videos (#10470)
* telegram_bot: Support for sending videos

Telegram python library has a sendVideo function that can be used
similar to sending photos and documents.

* fix lint issue

* fix grammar
2017-11-12 00:13:35 +01:00
Aaron Bach
79001fc361 Adds support for Tile® Bluetooth trackers (#10478)
* Initial work in place

* Added new attributes + client UUID storage

* Wrapped up

* Collaborator-requested changes
2017-11-11 23:21:03 +01:00
Paulus Schoutsen
2310b791f9 Merge branch 'master' into dev 2017-11-11 13:04:55 -08:00
Paulus Schoutsen
d814d40330 Merge pull request #10534 from home-assistant/release-0-57-3
0.57.3
2017-11-11 13:00:19 -08:00
William Scanlon
b6e098d1c2 Fixed Wink Quirky Aros bugs. (#10533)
* Fixed Wink Quirky Aros bugs.
2017-11-11 15:49:20 -05:00
Martin Berg
db56748d88 Add attribute to show who last un/set alarm (SPC) (#9906)
* Add attribute to show who last un/set alarm.

This allows showing the name of the SPC user who last
issued an arm/disarm command and also allows for
automations to depend on this value.

* Optimize

* Update spc.py

* Update spc.py

* fix

* Fix test.

* Fix for removed is_state_attr.
2017-11-11 12:36:03 -08:00
Kane610
68fb995c63 Update axis.py (#10412) 2017-11-11 12:30:18 -08:00
Andrey
4420f11d9d Fix import in tests (#10525) 2017-11-11 22:24:43 +02:00
Erik Eriksson
75836affbe Support configuration of region (no service url neccessary (#10513) 2017-11-11 12:21:25 -08:00
Lukas Barth
b284cc54df Pin yarl (#10528)
* Pin yarl

* Update requirements
2017-11-11 12:15:13 -08:00
Paulus Schoutsen
547e089185 Version bump to 0.57.3 2017-11-11 12:14:28 -08:00
Marcelo Moreira de Mello
fe2e0c44c8 Fixed update() method and removed ding feature from stickupcams/floodlight (#10428)
* Simplified URL expiration calculation and fixed refresh method

* Remove support from Ring from StickupCams or floodlight cameras

* Makes lint happy

* Removed unecessary attributes
2017-11-11 12:12:58 -08:00
Stefan Jonasson
30bd92c851 Tellstick Duo acync callback fix (#10384)
* Reverted commit 1c8f179690. This fixes issue: #10329

* convert callback to async

* fix lint

* cleanup

* cleanup

* cleanups

* optimize initial handling

* Update tellstick.py

* Update tellstick.py

* fix lint

* fix lint

* Update tellstick.py

* Fixed code errors and lint problems.

* fix bug

* Reduce logic, migrate to dispatcher

* Update tellstick.py

* Update tellstick.py

* fix lint

* fix lint
2017-11-11 12:12:57 -08:00
Paulus Schoutsen
78afbd4292 Pin YARL to 0.13 2017-11-11 12:12:31 -08:00
Hmmbob
f3a90d6994 Update nederlandse_spoorwegen.py to include platform information (#10494)
* Update nederlandse_spoorwegen.py

Make departure and arrival platforms available as state attributes

* Update nederlandse_spoorwegen.py

* Update nederlandse_spoorwegen.py
2017-11-11 11:51:26 -08:00
Lukas Barth
44506ce15f Adapt to new yarl API (#10527) 2017-11-11 08:36:37 -08:00
Andrey
5e92fa3404 Add an option to serve ES6 JS to clients (#10474)
* Add an option to serve ES6 JS to clients

* Rename es6 to latest

* Fixes

* Serve JS vrsions from separate dirs

* Revert websocket API change

* Update frontend to 20171110.0

* websocket: move request to constructor
2017-11-10 23:02:06 -08:00
Jan Almeroth
1c36e2f586 Introduce media progress for Yamaha Musiccast devices (#10256)
* Introduce update_hass()

* Introduce media_positions

* Version bump pymusiccast

* Fix: Unnecessary "else" after "return"

* FIX D400: First line should end with a period

* Version bump

Fixes https://github.com/home-assistant/home-assistant/issues/10411
2017-11-10 23:41:02 +01:00
Kenny Millington
16dd90ac78 Add support for Alexa intent slot synonyms. (#10469) 2017-11-10 09:35:57 -08:00
Eric Hagan
7d9d299d5a OwnTracks Message Handling (#10489)
* Improve handling and logging of unsupported owntracks message types

Added generic handlers for message types that are valid but not
supported by the HA component (lwt, beacon, etc.) and for
message types which are invalid. Valid but not supported
messages will now be logged as DEBUG. Invalid messages will
be logged as WARNING.

Supporting single "waypoint" messages in addition to the
roll-up "waypoints" messages.

Added tests around these features.

* Style fixes
2017-11-10 09:29:21 -08:00
Fabian Affolter
0490ca67d1 Bump dev to 0.58.0.dev0 (#10510) 2017-11-10 09:25:31 -08:00
Matthew Donoughe
e7dc96397c upgrade to new pylutron_caseta with TLS (#10286)
* upgrade to new pylutron with TLS

* rename configuration options

* change more methods to coroutines

* use async_add_devices
2017-11-10 12:17:25 +01:00
Diogo Gomes
9bfdff0be1 add JSON processing capabilities to sensor_serial (#10476)
* add JSON processing capabilities

* format fixes

* format fixes

* Fix according to @fabaff comment

* reverting last commit to a more sane approach

* docstring...

* still docstring...

* passed script/lint

* downgrade exception

JSONDecodeError was only introduced in Python3.5

Since we are still supporting 3.4 ValueError is the parent class of
JSONDecodeError
2017-11-10 10:49:30 +01:00
sander76
143d9492b2 Fix for telegram polling. (added pausing when error occurs) (#10214)
* Fix for telegram polling. (added pausing when error occurs)

* fix pylint error.
invalid variable name ( Exception as _e)). Don't understand why as
removing the underscore fails with my local pylint..

* fixing too short variable name.

* moved logic to `check_incoming`

* fix line too long error.

* Simplify
2017-11-09 21:17:23 +01:00
Fabian Affolter
8e1a73dd0f Upgrade youtube_dl to 2017.11.06 (#10491) 2017-11-09 20:18:29 +01:00
Fabian Affolter
8878eccb7b Upgrade psutil to 5.4.1 (#10490) 2017-11-09 20:17:31 +01:00
cgtobi
37eae7fb8a Improve error handling. (#10482)
* Improve error handling.

* Fix import of core requirements.

* cleanup
2017-11-09 20:17:01 +01:00
Anders Melchiorsen
dd16b7cac3 Remove lag from Harmony remote platform (#10218)
* Simplify kwargs handling

* Move Harmony remote to a persistent connection with push feedback

* Make default delay_secs configurable on the harmony platform

* Remove lint

* Fix delay_secs with discovery

* Temporary location for updated pyharmony

* Remove lint

* Update pyharmony to 1.0.17

* Remove lint

* Return an Optional marker

* Update pyharmony to 1.0.18
2017-11-09 17:57:41 +01:00
David Grant
68986e9143 Updated gc100 package requirement to 1.0.3a (#10484)
* Updated gc100 package requirement to 1.0.3a

* Update requirements_all.txt
2017-11-09 17:54:45 +01:00
Stefan Jonasson
62c1b542ed Tellstick Duo acync callback fix (#10384)
* Reverted commit 1c8f179690. This fixes issue: #10329

* convert callback to async

* fix lint

* cleanup

* cleanup

* cleanups

* optimize initial handling

* Update tellstick.py

* Update tellstick.py

* fix lint

* fix lint

* Update tellstick.py

* Fixed code errors and lint problems.

* fix bug

* Reduce logic, migrate to dispatcher

* Update tellstick.py

* Update tellstick.py

* fix lint

* fix lint
2017-11-09 15:03:35 +01:00
Julius Mittenzwei
ee265394a6 Improvement of KNX climate component (#10388)
* Added myself to codeowners

* Improved climate support with setpoint shift for KNX. (https://github.com/XKNX/xknx/issues/48)

* requirements_all.txt

* typo

* flake

* Changes requested by @pvizeli
2017-11-09 11:49:19 +01:00
TopdRob
9297a9cbb4 Upgrade apns2 to 0.3.0 (#10347) 2017-11-08 21:09:19 -08:00
Marcelo Moreira de Mello
2118ab2503 Fixed update() method and removed ding feature from stickupcams/floodlight (#10428)
* Simplified URL expiration calculation and fixed refresh method

* Remove support from Ring from StickupCams or floodlight cameras

* Makes lint happy

* Removed unecessary attributes
2017-11-09 01:01:20 +01:00
Pascal Vizeli
2fff065b2c Remove useless temp converting (#10465) 2017-11-09 00:46:33 +01:00
TopdRob
ed9abe3fa2 Upgrade pyatv to 0.3.6 (#10349)
Fix string conversion for idle state
2017-11-08 16:13:05 +01:00
TopdRob
f5ea7d3c9c Upgrade to 0.1.2 (#10348)
Fix an insecure request warning when not using verify=True. Contributed by @nalepae
2017-11-08 16:11:12 +01:00
Matt White
148a7ddda9 Add include/exclude filter to mqtt_statestream (#10354)
* Add publish filter to mqtt_statestream

* Add tests for include/excludes in mqtt_statestream
2017-11-08 15:54:12 +01:00
Milan V
2f0920e4fb Fix recorder stop on SQLite vacuuming error (#10405)
* Fix recorder stop on SQLite vacuuming error

* Move import to function
2017-11-08 14:43:15 +01:00
Paulus Schoutsen
2e5b1e76ef Fix slow WOL switch test (#10455) 2017-11-08 12:38:17 +01:00
Per Osbäck
db8510f110 update pywebpush==1.3.0 (#10374) 2017-11-08 12:02:28 +01:00
Daniel Høyer Iversen
e49278cc7d update tibber library (#10460) 2017-11-08 11:18:35 +01:00
Paulus Schoutsen
50f6790a27 Remove model info from state (#10399) 2017-11-07 21:28:11 -08:00
Diogo Gomes
a5aa111893 Add baudrate option to Serial sensor (#10439)
* Add baudrate option

Baudrate is essential!

* line too long

line too long (82 > 79 characters)

* trailing whitespace

* Rename const

* Fix the missing one
2017-11-07 22:06:19 +01:00
Robin
119fb08198 Fixes issue #10425 (#10426)
Fixes an error reported resulting from Hammersmith no longer supplying
data.
2017-11-07 18:19:54 +01:00
Adam Mills
11ecc2c171 Remove extra info from zwave entity states (#10413)
* Remove extra info from zwave entity states

* Show initializing for nodes that haven't completed queries
2017-11-07 10:13:39 -05:00
Mister Wil
07f073361f Bump to 0.12.2 to fix urllib3 dependency (#10420) 2017-11-07 01:39:13 +01:00
John Arild Berentsen
5410700708 Zwave save cache to file now. (#10381)
* Add save config

* Add API to save Z-Wave cache to file immediatley.

* lint

* remove none assignment

* docstring
2017-11-06 15:15:52 +01:00
Sebastian Muszynski
131af1fece Device model identification of the Xiaomi Philips Ceiling Lamp fixed. (#10401) 2017-11-06 09:20:31 +01:00
Pascal Vizeli
a9a3e24bde Update aiohttp to 2.3.1 (#10139)
* Update aiohttp to 2.3.1

* set timeout 10sec

* fix freeze with new middleware handling

* Convert middleware auth

* Convert mittleware ipban

* convert middleware static

* fix lint

* Update ban.py

* Update auth.py

* fix lint

* Fix tests
2017-11-05 18:42:31 -08:00
Paulus Schoutsen
39de557c4c Update frontend 2017-11-05 18:26:16 -08:00
Paulus Schoutsen
4742899369 Merge remote-tracking branch 'origin/master' into dev 2017-11-05 18:23:08 -08:00
Fabian Affolter
f3511d615e Upgrae simplepush to 1.1.4 (#10365) 2017-11-05 22:52:58 +01:00
Paulus Schoutsen
8f8772093d Merge pull request #10385 from home-assistant/release-0-57-2
0.57.2
2017-11-05 13:50:20 -08:00
Paulus Schoutsen
210bbc53a4 Update version to 0.57.2 2017-11-05 12:50:06 -08:00
Paulus Schoutsen
ce0537ef7f Update frontend to 20171105.0 2017-11-05 12:49:54 -08:00
Patrik
73cd902857 Fix tradfri problem with brightness (#10359)
* Fix problem with brightness

* Fix typo

* Typo
2017-11-05 12:49:24 -08:00
Simon
5d4514652d Addition of new binary sensor class 'plug' (#10336)
* Addition of new binary sensor class 'plug'

* use term "unplugged"

* add the entry to the right place
2017-11-05 10:25:44 -08:00
John Arild Berentsen
c07e651013 Add heal_node and test_node services. (#10369)
* Add heal_node and test_node services.

* lint
2017-11-05 09:19:19 -08:00
Patrik
bc51bd93f4 Fix tradfri problem with brightness (#10359)
* Fix problem with brightness

* Fix typo

* Typo
2017-11-05 17:43:45 +01:00
Adam Cooper
72ce9ec321 Add platform and sensors for Vultr VPS (#9928)
* Initial commit of Vultr components

Have a working Vultr hub and binary sensor which pulls down the
following attributes of your VPS:
 - Date created
 - Subscription id (server id)
 - Cost per month (in US$)
 - Operating System installed
 - IPv4 address
 - label (human readable name)
 - region
 - number of vcpus
 - which storage package chosen
 - IPV6 address (if applicable)
 - RAM amount

Working next on sensor and then testing / coverage.

* Added Vultr sensor for pending charges and current bandwidth. Refactored binary_sensor and hub too

* Corrected is_on bases

* Added basic tests for Vultr binary & platform

* Updated require files

* Changing test fixture to highlight different cases

* Written basic test for sensor.vultr

* Resolved linting errors and broken test

* Increase test coverage and corrected docs

* Resolved hound issues

* Revert back negative binary test

* Another hound resolve

* Refactoring and adding is switch, moving over to vultr branch

* Made Vultr components more resiliant to invalid configs

* Added negetive test for vultr binary sensor

* Added better testing of vultr sensor

* Resolved vultr platform test affecting subsequent vultr tests

* Moving VULTR components to single use design

* Added in sensor name config

* Added missing sensors var

* Resolved init data setting of sensors, added in name conf to switch

* Made the Vultr component more resiliant to startup failure with better alerting

* Various Vultr component changes

- Refactored sensor, binary_sensor, and switch to reference one subscription
- Renamed CURRENT_BANDWIDTH_GB monitored condition to CURRENT_BANDWIDTH_USED
- Improved test coverage

* Resolved local tox linting issue

* Added more testing for Vultr switch

* Improved test coverage for Vultr components

* Made PR comment changes to vultr binary sensor

* Made PR comment changes to Vultr sensor

* resolved PR comments for Vultr Switch

* Resolved vultr sensor name and improved tests

* Improved Vultr switch testing (default name formatting)

* Removed vultr hub failure checking
2017-11-05 14:10:14 +01:00
Fabian Affolter
a5d5f3f727 Move counter component (#10332)
* Fix docstring

* Add comment

* Move counter to folder

* Fix missing parts

* Commit it when file is saved
2017-11-05 13:51:52 +01:00
Fabian Affolter
5be6f8ff36 Upgrade sqlalchemy to 1.1.15 (#10330) 2017-11-05 13:51:03 +01:00
Per Osbäck
28ef564974 fix a import in test causing vs code to fail to discover (#10358)
* fix a import in test causing vs code to fail to discover

* Change style
2017-11-05 13:50:46 +01:00
Paulus Schoutsen
aae9697d9a Merge pull request #10351 from home-assistant/release-0-57-1
0.57.1
2017-11-04 12:56:38 -07:00
Paulus Schoutsen
af3d9d8245 Update frontend to 20171104.0 2017-11-04 12:53:09 -07:00
Paulus Schoutsen
640729f312 Version bump to 0.57.1 2017-11-04 12:52:39 -07:00
Fabian Affolter
de9d19d6f4 Use constants for HTTP headers (#10313)
* Use constants for HTTP headers

* Fix ordering

* Move 'no-cache' to platform
2017-11-04 12:04:05 -07:00
marconfus
e64803e701 Fix for API change of new enocean package (#10328)
* Fix API change of new enocean package

* Fix lint issue
2017-11-04 12:58:02 +01:00
Paulus Schoutsen
37bb626dd2 Merge pull request #10293 from home-assistant/release-0-57
0.57
2017-11-03 21:59:03 -07:00
Pascal Vizeli
21273de6a1 Move timer into correct folder (#10324)
* Move timer into correct folder

* Rename tests/components/test_timer.py to tests/components/timer/test_timer.py

* create init for test component

* Fix services.yaml loading
2017-11-03 21:18:36 -07:00
Craig J. Ward
fe271749c2 Tc update (#10322)
* use updated client

* update requirements
2017-11-03 21:18:35 -07:00
Paulus Schoutsen
af0253b2eb Fix formatting invalid config text (#10319) 2017-11-03 21:18:35 -07:00
Pascal Vizeli
986bcfef21 Fix recorder purge (#10318)
* Fix recorder purge

* Fix lint

* fix utc convert
2017-11-03 21:18:35 -07:00
William Scanlon
96f19c7205 Strip white space from configurator input (#10317) 2017-11-03 21:18:34 -07:00
Pascal Vizeli
cdc2df012c TellStick / Remove async flavor / add hassio (#10315)
* Remove unused async flavor

* Add tellcore-net support

* Update tellstick.py

* Update requirements_all.txt

* fix lint
2017-11-03 21:18:34 -07:00
Fabian Affolter
8dd790e745 Upgrade credstash to 1.14.0 (#10310) 2017-11-03 21:18:33 -07:00
Sebastian Muszynski
e90e94b667 Allow an empty MAC address at the Xiaomi Aqara Gateway configuration. (#10307) 2017-11-03 21:18:33 -07:00
Paulus Schoutsen
52f40b3370 Cloud: Authenticate with id token (#10304) 2017-11-03 21:18:32 -07:00
Paulus Schoutsen
8ed75217e1 Fix panel_custom (#10303)
* Fix panel_custom

* lint
2017-11-03 21:18:32 -07:00
Paulus Schoutsen
1e92417804 Cleanup Xiaomi Aqara (#10302) 2017-11-03 21:18:31 -07:00
NovapaX
be9cdf51d9 update mask-icon to a working mask-icon.svg (#10290)
* update mask-icon to favicon.svg

* change name of icon to mask-icon.svg
2017-11-03 21:18:31 -07:00
PeteBa
0e1a3c0665 Maintain recorder purge schedule (#10279)
* Maintain automated purge schedule

* Updates from review feedback
2017-11-03 21:18:31 -07:00
Pascal Vizeli
0f7a4b1d6f Move timer into correct folder (#10324)
* Move timer into correct folder

* Rename tests/components/test_timer.py to tests/components/timer/test_timer.py

* create init for test component

* Fix services.yaml loading
2017-11-03 21:10:08 -07:00
Craig J. Ward
acfee385fb Tc update (#10322)
* use updated client

* update requirements
2017-11-03 20:46:40 -07:00
Alok Saboo
96657841c8 Add option to overwrite file to the downloader component (#10298)
* Add option to overwrite file to the downloader component

* Cleanup

* Address Paulus's comments
2017-11-03 13:02:38 -07:00
Pascal Vizeli
a4dec0b6d2 Fix recorder purge (#10318)
* Fix recorder purge

* Fix lint

* fix utc convert
2017-11-03 12:55:00 -07:00
Pascal Vizeli
06d3d8b827 TellStick / Remove async flavor / add hassio (#10315)
* Remove unused async flavor

* Add tellcore-net support

* Update tellstick.py

* Update requirements_all.txt

* fix lint
2017-11-03 20:31:48 +01:00
Paulus Schoutsen
0877ea07b3 Fix formatting invalid config text (#10319) 2017-11-03 18:12:45 +01:00
William Scanlon
31b89f602a Strip white space from configurator input (#10317) 2017-11-03 08:58:03 -07:00
PeteBa
1ffccfc91c Maintain recorder purge schedule (#10279)
* Maintain automated purge schedule

* Updates from review feedback
2017-11-03 08:28:16 -07:00
Fabian Affolter
81324806d5 Move constants to setup.py (#10312)
* Remove unused import

* Move setup relevant consts to 'setup.py'

* remove blank line

* Set source
2017-11-03 07:43:30 -07:00
Sebastian Muszynski
a43f99a71c Allow an empty MAC address at the Xiaomi Aqara Gateway configuration. (#10307) 2017-11-03 07:38:15 -07:00
Hugo Dupras
1347c3191f Refactor Neato botvac components as a vacuum (#9946)
* Refactor Neato botvac components as a vacuum

A switch is still use to enable/disable the schedule

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

* CI Hound fixes

* Fix lint errors

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

* [Neato vacumm] Add sensor attributes to vacuum

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

* Remove line breaks and fix docstring

* PR fixes
2017-11-03 14:25:26 +01:00
Paulus Schoutsen
4e8e04fe66 Clean up core (#10305)
* Clean up core

* Lint

* Fix tests

* Address comment

* Update entity.py

* romve test for forward update to async_update

* fix lint
2017-11-03 14:19:36 +01:00
Fabian Affolter
9b8c64c8b6 Upgrade credstash to 1.14.0 (#10310) 2017-11-03 13:51:17 +01:00
Paulus Schoutsen
a943b207ba Fix panel_custom (#10303)
* Fix panel_custom

* lint
2017-11-03 10:28:31 +01:00
Heiko Thiery
23809bff64 Add LaCrosse sensor platform (#10195)
* Initial commit of LaCrosse sensor component

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix review comments from houndci-bot

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix review comments from houndci-bot

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add pylacrosse version to REQUIREMENTS

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add lacrosse to .coveragerc

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* import 3rd party libraries inside methods

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add pylacrosse to requirements_all.txt

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* add missing docstring

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix pylint warning

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix pylint warning

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* fix pylint warnings

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove too many blank lines

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* some minor cleanup

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* change to single quote

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* incorporate review comments

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* remove type check as validation only allows TYPES

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>

* Adjust log level and update ordering
2017-11-03 08:59:11 +01:00
Paulus Schoutsen
a4f7828363 Cloud: Authenticate with id token (#10304) 2017-11-03 07:30:05 +01:00
Paulus Schoutsen
2598770b49 Update frontend 2017-11-02 22:52:19 -07:00
Paulus Schoutsen
b77df372d6 Update frontend 2017-11-02 22:51:53 -07:00
Paulus Schoutsen
8f774e9c53 Cleanup Xiaomi Aqara (#10302) 2017-11-02 22:18:10 -07:00
NovapaX
47d9403e3a update mask-icon to a working mask-icon.svg (#10290)
* update mask-icon to favicon.svg

* change name of icon to mask-icon.svg
2017-11-02 20:55:09 -07:00
Markus
4d19092722 pyLoad download sensor (#10089)
* Create pyload.py

* tabs and whitespaces removed

* code style fix

* code style fixes

* code style fix

* fixed standard import order

* classname fixed

* Added homeassistant/components/sensor/pyload.py

* code formatting

* implemented @fabaff recommendations

* Update pyload.py

* Use string formatting

* Make host optional
2017-11-02 22:17:44 +01:00
Sebastian Muszynski
f2a38677fc Bump python-miio for improved device support (#10294)
* Bump python-miio for improved device support.

* Requirements defines updated.
2017-11-02 21:38:18 +01:00
Paulus Schoutsen
8c525b3087 Version bump to 0.57 2017-11-02 09:31:30 -07:00
Paulus Schoutsen
5359001c04 Merge remote-tracking branch 'origin/master' into dev 2017-11-02 09:27:56 -07:00
Paulus Schoutsen
2481cd2012 Update frontend 2017-11-02 09:18:06 -07:00
Paulus Schoutsen
e4ddb00086 Update frontend 2017-11-02 08:59:26 -07:00
vatir
f9a019ea82 Follow-up: Replace emulated_hue: with emulated_hue_hidden (#9894)
When lights in the hue component are used with the emulated hue component ATTR_EMULATED_HUE is still being used, which was deprecated by #9382. This updates ATTR_EMULATED_HUE to ATTR_EMULATED_HUE_HIDDEN to improve consistency and stop the deprecation warnings.
2017-11-02 12:45:02 +01:00
Joaquín
417240ee3e Better scene handling (#10213) 2017-11-02 12:02:03 +01:00
cgtobi
ffc2541ba5 Improve unit tests for season sensor component. (#10288)
* Add setup platform tests for the season sensor component.

* Improve tests to cover 97% of the code.

* Improve tests to cover 97% of the code.
2017-11-02 11:08:17 +01:00
lichtteil
d74dbc35f2 Luftdaten sensor (#10274)
* Add sensor platform for luftdaten.info

* Add monitored conditions to platform schema

* Make monitored conditions config obligatory

* Improve inline documentation

* Import first standard libs then 3rd party libs

* Combine resource url using format

* Remove unnecessary try..except block

* Use state “None” instead of STATE_UNKNOWN

* Minor changes incl. removal of unused vars

* Add missing spaces
2017-11-02 10:32:06 +01:00
Maciej Sokołowski
1c2224cc5c Fixed Tradfri whitebulbs handling after #9703 (#10040) 2017-11-02 08:18:20 +00:00
dominikandreas
56c66a19f0 Update plant for dealing with float values (#10246)
Value parsing in plant component throws an ValueError if values are given as floats. This commit changes int(value) to int(float(value)) to avoid this error.
2017-11-02 09:17:26 +01:00
Lewis Juggins
79da44a6b3 Support new tradfri individual DTLS identification method (#10282) 2017-11-02 06:23:06 +00:00
William Scanlon
d9805160bc Removed username/password auth and moved to new save/load json (#10277) 2017-11-01 21:58:46 +01:00
Pascal Vizeli
f463f4d8c6 WIP: Cleanup async stuff on templates (#10275)
Cleanup async stuff on templates
2017-11-01 15:48:09 +01:00
Eitan Mosenkis
4da8ec0a05 Add Google Assistant support for setting climate temperature and operation mode. (#10174)
Fixes #10025.
2017-11-01 07:44:59 -07:00
Pascal Vizeli
fb34f94d9c Update template.py 2017-11-01 14:20:29 +01:00
cgtobi
513c2b03c9 Add setup platform tests for the season sensor component. (#10270) 2017-11-01 14:06:43 +01:00
Paulus Schoutsen
4dc9ac820f Remove http.development (#10267)
* Remove http.development

* Remove development

* Remove development from tests

* Remove constant
2017-11-01 13:07:16 +01:00
Pascal Vizeli
619d329a16 Add xy support to Alexa HomeAPI v3 (#10268)
* Add xy support to Alexa HomeAPI v3

* Update smart_home.py

* Update smart_home.py

* fix lint

* fix copy paste

* Update smart_home.py

* simplify

* Add test for xy/rgb

* Update test_smart_home.py

* Update smart_home.py

* add test
2017-11-01 12:16:05 +01:00
Fabian Affolter
e2c6f538a8 Add link to docs (#10272) 2017-11-01 12:11:32 +01:00
cgtobi
8739991676 Add unit test for wake on lan component. (#10262)
* Add unit test for wake on lan component.

* Remove unneccessary imports and print calls.

* Clean up lint complaints.
2017-11-01 11:15:24 +01:00
Paulus Schoutsen
26b097b860 Change introduction card to use persistent notification (#10265)
* Change introduction card to use persistent notification

* Lint
2017-11-01 09:10:36 +01:00
Erik Eriksson
4e8723f345 Extracted json saving and loading (#10216) 2017-11-01 09:08:28 +01:00
Paulus Schoutsen
85f30b893e Update netdisco to 1.2.3 (#10266) 2017-11-01 09:05:55 +01:00
Anders Melchiorsen
6cadb796bc Avoid Sonos error during startup (#10146)
* Add Sonos coordinators before slaves
2017-11-01 08:28:13 +01:00
Matt White
9eaa057739 Add EntityFilter helper (#10221)
* Add EntityFilter helper

* Changes in entityfilter after code review

* Convert recorder to use EntityFilter

* Fix flake/lint errors in recorder

* Update entity filter helper to return function

* Update recorder to use updated entity filter

* Better docstrings in entityfilter

* Update entityfilter.py
2017-10-31 21:54:50 -07:00
Per Osbäck
b6324b511c Google Assistant: make it possible to set a custom domain/type (#10188)
* Google Assistant: make it possible to set a custom domain/type

* add test for switch platform

* update custom type test
2017-10-31 20:38:34 -07:00
ChristianKuehnel
80a9539f97 integration with Remember The Milk. (#9803)
* MVP integration with Remember The Milk.

This version offers a service allowing you to create new issues in Remember The Milk.

* fixed pylint issue with import path

* - added files to .coveragerc as the server inerface is hard to test
- added tests for config file handling

* fixed lint error

* added missing docstrings

* removed stray edit

* fixed minor issues reported by @fabaff

* changed naming of the service, so that serveral accounts can be used

* added disclaimer

* moved service description to services.yaml

* fixed blank lines

* fixed structure of configuration

* added comment about httplib2

* renamed internal config file

* improved logging statements

* moved entry in services.yaml into separate folder.

Had to move the component itself as well.

* fixed static analysis findings

* mocked first test case

* fixed bug in config handling, fixed unit tests

* mocked second test case

* fixed line length

* fixed static analysis findings and failing test case

* also renamed file in .coveragerc

* control flow changes as requested by  @balloob
2017-10-31 20:33:47 -07:00
Pascal Vizeli
8c266f9266 Alexa SmartHome API extend (#10251)
* Implement adjustment

* Add color support

* fix lint

* Fix lint & use only RGB

* fix HSB + Test

* Add tests & fix bugs

* add rgb test

* add setColorTemperature

* Add color light support + tests

* Fix color temp

* use kelvin for converting

* use correct calculation
2017-10-31 20:28:17 -07:00
Adam Mills
5043b85c58 Use translated panel names on frontend (#10197)
* Use translated panel names on frontend

* Revert hassio translation
2017-10-31 20:22:04 -07:00
Daniel Perna
890c11cc7c WIP: Timer component (#9889)
* Added timer component

* Reworked functionality a bit

* Fixed requested change

* Fixed state updates when finished

* Removing expired listeners, added events, changed services

* Added finish service

* Using timedelta parameters in start-service

* Cleanup

* Lint

* Updating state for remaining time

* Removed duration from cancel method

* Renamed service to fix disabled lint

* Some tests (incomplete)

* Relocated service descriptions

* Addressed requested changes

* Adjusted tests, added methods and events

* Added test for finish service, lint

* Code cleanp, using string states

* tzzz... one char...

* Proper usage of restore_state

* Some more cleanup
2017-10-31 20:20:56 -07:00
Ted Drain
253c8aee1f Mqtt light options to fix #9330 and #7810 (#9829)
* Added ability to control when the on command is sent.

* Changed to allow only brightness command

* Code cleanup

* Added test cases for on command mode.

* Added addition test

* Changed brightness options to lower case.

* Fixed case of default value

* Remove default
2017-10-31 23:18:49 +01:00
cdce8p
12e1602a81 input_text - Added service doc (#10238)
* * Added service doc

* * Deleted print statement
2017-10-31 14:51:12 +01:00
GTH
25a25dde7a Add support for 'Send Current Position' feature in Geofency 5.1 (#10012) 2017-10-31 14:17:14 +01:00
biggms
c0eaf0386c Changed single tolerance value to COLD and HOT tolerances. Allows on and off states to have different error bands. (#9843) 2017-10-31 14:06:34 +01:00
Audric Schiltknecht
6b96bc3859 Add support for odhcpd DHCP server (#9858)
This commit adds support for the odhcp DHCP server in addition to
dnsmasq. A new configuration option 'dhcp_software' has been added and
allows the user to set the server used (defaults to dnsmasq to not break
existing installations).
2017-10-31 13:40:12 +01:00
Fabian Affolter
6d94c121a7 Move constant to 'const.py' (#10249) 2017-10-31 13:31:12 +01:00
Andrey
ae34640a80 Use theme color in loading screen. (#10248) 2017-10-31 13:30:50 +01:00
Greg. A
8832de80bc Sytadin default value must be a list #10233 (#10234)
* Sytadin default value must be a list #10233

* Sytadin default value must be a list #10233 v2
2017-10-31 08:32:26 +01:00
Eric Hagan
ed3f7d1581 OwnTracks work. Beacon logic and testcases (#10183)
* OwnTracks work. Beacon logic and testcases

The existing test cases don't really make clear what is being
tested and the iBeacon / Region / Zone / Tracker thing is all
a bit confused.

I'm distinguishing a fixed-place beacon used to trigger entrance
into an HA zone (as a Region Beacon) from a beacon affixed to a
portable or mobile object (as a Mobile Beacon). The behaviors
and test cases for those usages should be different. A Region Beacon
will be named the same as a Home Assistant Zone and seeing an event
from that beacon should trigger a device tracker update related
to that zone. It would be appropriate, though unnecessary, to
configure the Region Beacon with the GPS coordinates of its static
physical location.

A Mobile Beacon is not named after any HA Zone and seeing the beacon
triggers an update in HA setting the location of the beacon to the
current device_tracker location. In this way, when my_phone sees
the beacon on my_keys, the location of my_keys is set to where
my_phone is. And when my_phone stops seeing my_keys, my_keys location
is the location of my_phone the last time it saw them.

A Mobile Beacon's GPS information should be ignored because it's
almost certain to be incorrect because the beacon moves. In fact,
beacons typcially come configured with lat/lon as 0.0/0.0 so using
the location of the beacon in an update has a nasty habit of
setting you and your keys on the bottom of the Atlantic Ocean.

Leave message handling is changed to treat mobile beacons
differently from region beacons and gps regions.

active beacons should be a set. you shouldn't end up
with multiple "active" entries for the same beacon. Let's
enforce that with the correct data structure.

Added test for real-world bug that is fixed.
A series of mobile beacon and region beacon
enter and leave events could cause a mobile
beacon to stick to the tracking device even
though it had tracked through a "leave" event.

Changed two tests to look at the size
of the 'mobile_beacons_active' structure rather
than at the object which will allow this test
to work with any sort of list, set, etc.

* Removing excess logging and unnecessary try catch.

From review on PR #10183 I've removed some info logging
that was unnecessary and I've made the suggested changes
to an if block and a try/catch.
2017-10-31 00:18:45 -07:00
Mark Jozefiak
062fb7ac4c Add priority attribute for hyperion (#10102)
* light.hyperion: Add priority attribute

Allows to set the priority of the hyperion remote instance.

* fix lint errors

* Remove whitespace
2017-10-30 21:48:42 +01:00
Fabian Affolter
cc293db5ab Update services.yaml files (#10229)
* Add period to the description

* Update abbreviation
2017-10-30 21:39:12 +01:00
Sean Dague
c9c102815a Clarify yamaha play_media parameters (#10228)
The play_media parameters are a little bit black magic for the yamaha
platform, this explains what they are in a code comment instead of
having to go look at the yamaha platform itself.

Fixes Bug #10180
2017-10-30 21:38:52 +01:00
Jan Almeroth
5d23afdc9e Introducing multi-zone support for yamaha_musiccast devices (#9968)
* Introduce zones to yamaha_musiccast
* Introducing new config 'interval_seconds'
* Version bump pymusiccast
* Removing name parameter
2017-10-30 13:50:20 -04:00
Sebastian Muszynski
e95b48ca44 Xiaomi Aqara: Remove/Add device service added (#10150)
* First draft of a remove device service. Fixes https://github.com/home-assistant/home-assistant/issues/9571.

* Add device service introduced. Enables the join permission of the gateway to pair a new sub device within the next 30 seconds.

* Schema validation added and some refactoring.

* A more precise validation of the gw_mac (ffffffffffff vs. ff:ff:ff:ff:ff:ff).

* Persistent notification added to provide some feedback.

* Pylint warning disabled. The methods are used indirectly.

* CODEOWNERS reference updated.
2017-10-30 17:33:16 +01:00
Aaron Bach
646c03eea1 Add entity_picture_template options to Template Cover, Template Light, Template Sensor, and Template Switch (#9854)
* Re-adding cover

* Re-adding light

* Re-adding sensor

* Re-adding switch

* Re-added tests

* Fixing missing imports in rebased test

* Fixing broken tests

* Owner-requested changes

* Owner-requested changes

* Fixed exception
2017-10-30 09:28:37 -07:00
Nicolae Vlădescu
05ece53ec2 Librouteros capsman fix (#10217)
* Catch MultiTrapError exceptions also

* Expect librouteros api exceptions on every call and also set fallbacks

* Do not re raise

* Fix lint issue
2017-10-30 08:41:37 +01:00
David Grant
b5214af762 Add gc100 platforms and component (#10159)
* Initial commit of gc100 platforms and component

* Fixed removed temporary code to set import path

* Fixed removed unused import

* Implementing changes requested by @fabaff

* Fixed linter errors

* Remove reference to binary sensor

* Move const

* Add const

* Fix pylint
2017-10-30 08:40:14 +01:00
Marcelo Moreira de Mello
3630dc7ff3 Raincloudy version bump (#10225) 2017-10-30 08:36:38 +01:00
Peter Epley
e7fc8a1890 Google Assistant Script Support (#10148)
* Add script support as scene

* Add script support as scene

* Corrected missing script definition
2017-10-30 00:11:23 -07:00
Fabian Affolter
2891b0cb2e Upgrade restrictedpython to 4.0b2 (#10179)
* Upgrade restrictedpython to 4.0b2

* Update test
2017-10-30 00:02:15 -07:00
Paulus Schoutsen
fc44a4ed99 Update frontend 2017-10-29 23:29:49 -07:00
Pascal Vizeli
444b7c5ee7 Add new service 'snapshot' for camera (#10207)
* Add new service 'snapshot' for camera

* Fix lint

* fix arguments

* Add test and fix bugs

* Fix lint

* Fix typo
2017-10-29 23:14:26 +01:00
Fabian Affolter
690760404b Move constant to 'const.py' and use already defined ones (#10211) 2017-10-29 17:28:07 +01:00
Greg Dowling
6a9968ccb9 Bump pyvera to 0.2.38. (#10206) 2017-10-29 13:50:21 +01:00
Fabian Affolter
e91ed1f2a4 Upgrade youtube_dl to 2017.10.29 (#10202) 2017-10-29 12:32:33 +01:00
Fabian Affolter
115c59d88c Move constant to 'const.py' and use already definied ones (#10204) 2017-10-29 12:32:02 +01:00
Fabian Affolter
97bb252d23 Upgrade pylast to 2.0.0 (#10200) 2017-10-29 11:54:11 +01:00
cgtobi
20a1a52bd5 Add unit test for hddtemp sensor. (#10154)
* Add unit test for hddtemp sensor.

* Remove sample fixture and include it in the code.

* Add test to raise coverage.

* Fix bug when host is not reachable.

* Minor code cleanups.

* More code cleanups.
2017-10-29 11:53:53 +01:00
Patrik
9e27e05a84 Update CODEOWNERS (#10198)
Adding myself to monitor tradfri
2017-10-29 11:19:34 +01:00
Fabian Affolter
67c48736a2 Add clickatell (#10199) 2017-10-29 11:19:04 +01:00
Fabian Affolter
35805e51a3 Add Random binary sensor (#10164) 2017-10-29 11:15:57 +01:00
TopdRob
6057b41151 update boto3 to 1.4.7 and botocore to 1.7.34 (#10121) 2017-10-29 10:24:56 +01:00
David Lloyd
2374659984 Added new Clickatell SMS messaging Notify Platform (#9775)
* Added new Clickatell SMS messaging Notify Platform

* Added new Clickatell SMS platform with layout corrections

* Added new Clickatell platform with additional  layout corrections

* Added new Clickatell platform with exception handling removed

* Added new Clickatell platform with poor reference removed

* Reversed changes to dev_docker file

* Minor changes
2017-10-29 09:14:40 +01:00
Egor Tsinko
2c3195522f media_title property now returns current source (#10120) 2017-10-29 02:26:55 +02:00
Greg. A
b3e88d1f8f Add Sytadin Traffic component (#9524)
* Add Sytadin Traffic component

* Add Sytadin Traffic component, update

* Add Sytadin Traffic component, update

* Add Sytadin Traffic component, update

* Add Sytadin Traffic component, update

* Add Sytadin Traffic component, update

* implements @fabaff comments

* Formatting

* formatting

* formatting

* split monitored condition to be independent

* formatting

* add version

* fix requirements part based on gen_requirements_all.py result

* formatting

* requirements stuff file

* formatting

* add missing file into .coveragerc

* add me for code review

* configuration management updated

* implements @fabaff and @pvizeli comments

* indentation

* indentation

* indentation

* indentation

* add const DEFAULT_UPDATE_INTERVAL

* use const DEFAULT_UPDATE_INTERVAL

* remove blank line

* fixes debug call

* clean for loop

* use BeautifulSoup for html parsing

* Update requirements

* Add throttle and fix remaining issues

* Don't add const
2017-10-29 01:34:55 +02:00
Florian Klien
dd7d8d56bb added Yesss SMS platform (#10177)
* added Yesss SMS component

requires YesssSMS==0.1.1b

* requirements_all

* coveragerc

* fix

* docstring fix

* added Exception handling

* requirements_all

* fixing lint error, version bump for YesssSMS
2017-10-29 01:05:56 +02:00
Sebastian Muszynski
df19172e56 Limits of the favorite level updated. Values between 0 and 16 will be accepted. (#10186) 2017-10-28 10:54:11 +02:00
Marcelo Moreira de Mello
f060dcc0aa Added capability to pass a filename to the downloader component (#10059)
* Added capability to pass the filename to the downloader component

* Simplified filename conditions
2017-10-27 22:50:02 +02:00
Menno Blom
5c168ab551 Nederlandse spoorwegen (#10136)
* Add Nederlandse Spoorwegen sensor

* Remove unused 'delay' option.

* Apply requested code reviewed changes

- use constants from const.py
- ensure the configuration is a list
- verify credentials upon platform setup
- verify station input
- add True as second param to add_device
- only call add_device if at least 1 sensor was setup
2017-10-27 22:19:12 +02:00
Sebastian Muszynski
fe9b45c964 Xiaomi MiIO Light: Philips Eyecare Lamp 2 support (#10007)
* Xiaomi Philips Eyecare Lamp 2 support introduced.

* Code clean-up.

* Make hound happy again (idents).

* Revert "Code clean-up."

This reverts commit ea637602ff.

* Unused platform constant removed.

* Nice compromise of the code clean-up implemented.
2017-10-27 22:04:48 +02:00
Fabian Affolter
38c189ecf4 Revert "Upgrade restrictedpython to 4.0b2"
This reverts commit 8e4f0ea5ae.
2017-10-27 21:52:44 +02:00
Fabian Affolter
8e4f0ea5ae Upgrade restrictedpython to 4.0b2 2017-10-27 21:50:22 +02:00
Lukas Barth
248d974ded Cast attribute values to string before publishing to MQTT (#9872)
* Cast attribute values to string before publishing to MQTT

* Simplify

* Use JSON serialization, add test
2017-10-27 08:55:04 -07:00
Ryan McLean
2d93285689 Linode (#9936)
* Fix: Last Played Media Title in plex would stay even when player was idle/off
     Primary Fix is in the "if self._device" portion.
     code in "if self._session" is a catch all but i'm not 100% if it is needed.

* Fixed lint issues with previous commit

* 1st Pass at refactoring plex refresh
Moved _media** into clearMedia() which is called in _init_ and
at start of refresh.

Removed redunant _media_* = None entries

Grouped TV Show and Music under single if rather than testing
seperately for now.

* Fixed invalid name for _clearMedia()
Removed another media_* = None entry

* Removed print() statements used for debug

* Removed unneeded "if" statement

* Added Base Support for Linode

* Removed unused Attr

* Corrected some Typos

* updated requirements & coveragerc

* added import to prevent var not declared errors in linter

* Fixed Typo

* Added Switch Component for Linode and corrected some errors if data was blank

* Updated api lib to hopefully remove dependancy on enum34

* Fixed Reference error

* fix pylint errors

* Update linode.py

* Update linode.py

* Update linode.py

* Update linode.py
2017-10-27 16:19:47 +02:00
bastshoes
68390373e5 Fix for issue #9240 (#10173)
Changes for respecting mqtt fan config. If fan speed and oscillation is not configured they wouldn't be displayed in UI.
2017-10-27 16:04:18 +02:00
randellhodges
c0f8e6c5c5 Updated denon component to play nice with the 3808CI that doesn't support NSFRN command (#10157) 2017-10-27 14:22:23 +02:00
Casper Weiss Bang
85d7377beb MPD now uses the filename if song doesn't have metadata (#10085)
* added support for filename

* used the getter instead - minor mistake

* changed how the filename is generated
2017-10-27 11:21:47 +02:00
Anders Melchiorsen
f17cf1d26b Avoid Sonos errors for tracks with no artist information (#10160)
Rather than adding guards to the string formatting, just remove this
unused attribute.
2017-10-27 10:50:03 +02:00
Anders Melchiorsen
e50b59a56c Reduce album art flickering in media player UI (#10163)
* Add HTTP cache header to proxied media player images

With the resource actually being cacheable, preemptively extend the cache
buster key to prevent hash collisions.

While at it, change the hash from md5 to sha256 for consistency with the
access_token method.

* Remove lint
2017-10-27 10:49:20 +02:00
Fabian Affolter
e43fefa8f6 Support for NO-IP (#10155)
* Support for NO-IP

* Update URL
2017-10-27 10:15:47 +02:00
Yannick POLLART
e819678e27 Rfxtrx binary sensor rewrite (#10152)
* Refactored Lighting4-specific code (issue #8907)

* Fixed lint problem and removed unneeded function call.

* fixed malformed if

* removed backslash

* fixed code for pylint

* removed useless assignment

* using hasattr() instead of try/catch
2017-10-27 10:01:46 +02:00
Florian Klien
9df7302603 New DTLSSocket version that fixes Cython dependency for Trådfri (#10123) 2017-10-27 07:53:36 +01:00
Fabian Affolter
afe88dfa0f Upgrade libnacl to 1.6.1 (#10161) 2017-10-27 08:01:32 +02:00
Fabian Affolter
027ce2f555 Upgrade python_opendata_transport to 0.0.3 (#10162) 2017-10-27 08:01:12 +02:00
Fabian Affolter
63a10233c5 Upgrade sendgrid to 5.3.0 (#10166) 2017-10-27 08:00:53 +02:00
Egor Tsinko
acbf45d5f8 added platform discovery code back into device_tracker (#10169) 2017-10-27 07:57:31 +02:00
Paulus Schoutsen
d13f3eca92 Update frontend to 20171027.1 2017-10-26 22:29:59 -07:00
Andrey
fc291dd5ab Don't use pypi package in dev mode. Allow non-fingerprinted mdi. (#10144) 2017-10-26 22:28:07 -07:00
Adam Mills
583e57042b Core POC support for polymer i18n (#6344)
* Core POC support for polymer i18n

* Add gulp to build_frontend

* Remove frontend build

* Updated translations format

* Eliminate translation namespace from panel names

* Only register translations path in dev mode
2017-10-26 21:46:21 -07:00
Sebastian Muszynski
9d0c2a8dae Xiaomi MiIO Switch: Support for different device types (#9836)
* Support for different device types of MIIO switches: Xiaomi Smart WiFi Socket (called Plug), Xiaomi Smart Power Strip and Xiaomi Chuang Mi Plug V1.

* Line too long fixed.

* Trailing whitespace removed.

* Changes based on review.

* Line too long fixed.

* No blank lines allowed after function docstring fixed.

* The underlying library is called python-miio now. Imports and requirements updated.

* TODO comment removed. Travis complains about.

* Blank line removed.

* Code clean-up.

* Revert "Code clean-up."

This reverts commit 96b191c7a6.

* Unused platform constant removed.
2017-10-26 23:37:30 +02:00
Richard Leurs
c2ef22bd08 Add display currency setting to CoinMarketCap sensor (#10093)
* Add support for different display currencies in CoinMarkerCap sensor.

* Add test for CoinMarketCap sensor.

* Add test dependency to gen_requirements_all.

* Fix review comments: use string formatting and less string case chanes.
2017-10-26 18:49:17 +02:00
Daniel Høyer Iversen
2561efe45d Add last action to xiaomi aqara button (#10131) 2017-10-26 18:46:29 +02:00
Rasmus
c191c13f3a Telldus Live: Device without methods is a binary sensor (#10106)
Telldus Live reports binary sensors as devices without methods.
2017-10-26 15:54:49 +02:00
Fabian Affolter
b1291e572e Upgrade pysnmp to 4.4.1 (#10138) 2017-10-26 10:38:27 +02:00
Jeroen ter Heerdt
d1416056cd Microsoft tts (#9973)
* Microsoft Cognitive Services text-to-speech

* Adding microsoft.py to .coveragerc

* Update microsoft.py

* Update microsoft.py

* Update requirements_all.txt

* fix order
2017-10-25 19:43:21 +02:00
milanvo
7987065ad7 Fix recorder crash for long state string - enforce at core level (#9696)
* Recorder exception catch for long state string

* Revert - Recorder exception catch for long state string

* Validate state length at core level

* Revert - this reverts commit 9d6bd017d9.

* Revert - Recorder exception catch for long state string

* Fix state TypeError

* Test for long state exception
2017-10-25 09:05:30 -07:00
Sam Birch
fc8940111d Binary sensor for detecting linear trends (#9808)
* Trend sensor now uses linear regression to calculate trend

* Added numpy to trend sensor test requirements

* Added trendline tests

* Trend sensor now has max_samples attribute

* Trend sensor uses utcnow from HA utils

* Trend sensor now completes setup in async_added_to_hass

* Fixed linter issues

* Fixed broken import

* Trend tests make use of max_samples

* Added @asyncio.coroutine decorator to trend update callback

* Update trend.py
2017-10-25 17:33:17 +02:00
Georgi Kirichkov
63c9d59d54 SNMP switch (#9840)
* Initial support for SNMP switch

Supports setting integer values

* Resolves styling issues

* Updates requirements

* Remove commented out import

* Changes default community to private. Fixes linting errors.

* More linter fixes and bugfix in #state()

* Refactors the code to make it simpler.
2017-10-25 16:09:29 +02:00
Anders Melchiorsen
61ccbb59ce Fire numeric_state action when first state change matches criteria (#10125)
* Fire numeric_state action when first state change matches criteria

* Remove lint

* Update numeric_state.py
2017-10-25 16:01:09 +02:00
Pascal Vizeli
5fabfced38 Fix lint google-domains (#10135)
* Fix lint google-domains

* Update google_domains.py
2017-10-25 15:43:02 +02:00
Daniel Perna
6c39e1ef19 Added increment + decrement to input_number (#9870)
* Added increment + decrement to input_number

* Lint

* Fix tests

* Another lint

* Additional testing

* Added service descriptions

* Consolidated service registration

* Shortened service registration

* Fixed service descriptions

* Fix Lint
2017-10-25 15:25:33 +02:00
Hydreliox
632466bb56 Add Deluge Sensor (#10117)
Add a sensor to provide upload and download speed of the Deluge Bittorrent Client
2017-10-25 15:13:11 +02:00
Trevor
7784c40f12 Add Google Domains component (#9996)
* Add Google Domains component

* Fixes for hound

* Add Google Domains tests

* Fixes for hound

* Clean up Google Domains

* Add timeout to Google Domains

* Remove whitespace from blank lines

* Update google_domains.py

* Update google_domains.py
2017-10-25 11:42:53 +02:00
Ryan McLean
41fa8cc8f2 Plex refactor Part 1 - Update plexapi to 3.0.3 (#9988)
* Fix: Last Played Media Title in plex would stay even when player was idle/off
     Primary Fix is in the "if self._device" portion.
     code in "if self._session" is a catch all but i'm not 100% if it is needed.

* Fixed lint issues with previous commit

* 1st Pass at refactoring plex refresh
Moved _media** into clearMedia() which is called in _init_ and
at start of refresh.

Removed redunant _media_* = None entries

Grouped TV Show and Music under single if rather than testing
seperately for now.

* Fixed invalid name for _clearMedia()
Removed another media_* = None entry

* Removed print() statements used for debug

* Removed unneeded "if" statement

* Changes
* Updated Requests to 2.18.4
* Updated plexapi to 3.0.3
* Removed function to convert_NA_to_None
* Removed function get_thumb_url

Type changes
* _session.player is now a list players
* na_type deprecated and to be removed
* plexapi has change na to None

Known Issues:
* Player controls currently broken
* Last location (library) stays while player idle

* Username is now Usernames and a list

* Fix for broken controls

* Removed errant print statement

* Removed depecrated na_type

* Updated Plex Sensor to use plexapi 3.0.3
Added support for Token to be used in sensor

Known Issues:
Username and Password broken for Plex Sensor use Token instead for now

* removed TODOs

* Fixes for private access violations

* Removed need for _local_client_fix

* Removed unused import and fixed parens
2017-10-25 11:42:13 +02:00
Trevor
2a2a106e62 Fix Sonarr and Radarr divide by zero (#10101)
* Fix Sonarr and Radarr divide by zero

* Fixes for hound

* Clean up Radarr diskspace
2017-10-25 11:37:08 +02:00
Anders Melchiorsen
45e140149b Allow folder selection for IMAP unread sensor (#10126) 2017-10-25 11:36:00 +02:00
Kane610
34368a6b69 WIP: Refactor Axis component removing external dependencies (#9791)
* Async rewrite

* Device and events now working with async core

* More async than before

* Methods have moved

* Remove check if parameter serial number is available since library handles reconnection anyway and we can expect user to set up configuration correctly

* Async rewrite

* Device and events now working with async core

* More async than before

* Methods have moved

* Remove check if parameter serial number is available since library handles reconnection anyway and we can expect user to set up configuration correctly

* Serial number is the only unique identifier, checks are still needed

* No async for setup_device

* Bump axis to 13 in preparation of friday the 13th

* Fix review comments

* Removed async after some discussions with @armills.
It wasn't possible to get past that aiohttp doesnt support digest auth, which makes it impossible to convert the full library at this point in time.
2017-10-25 00:04:30 -07:00
Sebastian Muszynski
e8f5445acc Xiaomi MiIO Fan: Xiaomi Air Purifier 2 integration (#9837)
* Xiaomi Air Purifier 2 integration

* Flake8 errors fixed.
Changes based on review.

* Service domain ("fan") updated and services properly prefixed by xiaomi_miio.

* The underlying library is called python-miio now. Imports and requirements updated.

* Version bumped. The underlying library is called python-miio now.
2017-10-24 23:50:01 -07:00
Daniel Høyer Iversen
00b9297082 Rfxtrx fix (#10128)
Rfxtrx fix (#10128)
2017-10-25 08:41:02 +02:00
Paulus Schoutsen
2bdad5388b Consolidate frontend (#9915)
* Consolidate frontend

* Remove home-assistant-polymer submodule

* Convert to using a pypi package for frontend

* fix release script

* Lint

* Remove unused file

* Remove frontend related scripts

* Move hass_frontend to frontend REQUIREMENTS

* Fix tests

* lint

* Address comments

* Lint + fix tests in py34

* Fix py34 tests again

* fix typo
2017-10-24 19:36:27 -07:00
Sergey Isachenko
29fb65b224 Fixes #10030. Extented Network Exceptions handling. (#10116)
* Fixes #10030. Extented Network Exceptions handling.

* Remove unused import. Replace ex.reason to ex.message to use custom exception instead of HTTPError
2017-10-24 19:15:25 +02:00
Fabian Affolter
560a4ef5eb Fix PEP8 and PEP257 issues (#10108) 2017-10-24 18:36:08 +02:00
Hydreliox
186f8f6996 Add Deluge Switch Component (#9979)
* Add Deluge Switch Component

* Update deluge.py
2017-10-24 16:44:12 +02:00
Paulus Schoutsen
238884dfe2 Revert gactions in Docker (#10115) 2017-10-24 07:30:24 -07:00
Pascal Vizeli
6da08deabf Merge pull request #9118 from jbarrancos/dev
Rain Bird LNK WiFi Irrigation Implementation
2017-10-24 15:01:15 +02:00
Markus
e970edbf20 fixed typo (#10110) 2017-10-24 14:44:38 +02:00
Pascal Vizeli
7c69941f13 cleanup 2017-10-24 12:25:12 +02:00
Pascal Vizeli
179655b6b0 Merge pull request #10079 from home-assistant/scrape-auth
Add support for HTTP Basic/Digest authentication
2017-10-24 12:21:44 +02:00
Pascal Vizeli
6ebff3cda4 Merge pull request #10105 from home-assistant/bayesian-sensor
Use constants and update docstrings
2017-10-24 12:18:02 +02:00
Daniel Høyer Iversen
70eaa5f10e Update CODEOWNERS (#10103) 2017-10-24 09:34:42 +02:00
Adam Cooper
485e81db79 whois domain lookup sensor (#10000)
* Init commit of new whois sensor

* Updated requirements

* Resolved updated showing expired, added expired attr

* Added missing attribute in init, added whois to coveragerc

* Various PR comment changes

- Now more resiliant to invalid hostnames
- Removed various assumed STATE_UNKOWN setting
- Upfront check for valid hostname preventing the sensor starting with dud
- Resolved unit of measurement Day, Days, None issue
- Datetime formatting now done to iso 8601 standard
- Removed all expired usage, not really that useful
- Unused hass assignment

* More PR comment resolutions

- Resolved the dilemma with hosts / single host per sensor. Now running
single domain per sensor.
- Renamed host(s) to domain

* Moved coveragerc sensor location

* Re-phrased the expiration_date warning

* Resolved assumed updated_date existence

* Resolved missing indent

* Resolved discover_info typo

* Update whois.py
2017-10-24 09:34:06 +02:00
Fabian Affolter
fc2f41fe8a Use constants and update docstrings 2017-10-24 09:12:01 +02:00
Pascal Vizeli
a4b0e8f897 Merge pull request #10082 from arsaboo/uptimefix
Add minutes to Uptime sensor
2017-10-24 08:32:46 +02:00
Pascal Vizeli
3cf99e29be Merge pull request #10100 from home-assistant/additional-event-tests
Additional event data tests to cover recent bugs
2017-10-24 08:28:09 +02:00
Adam Mills
5f8eb08cd9 Additional event data tests to cover recent bugs 2017-10-23 19:44:07 -04:00
pezinek
d1424714c7 Support for Entity.available in sensor/rest (#10073) 2017-10-23 23:29:41 +02:00
Fabian Affolter
74e93e5853 Upgrade Sphinx to 1.6.5 (#10090) 2017-10-23 23:15:36 +02:00
arsaboo
bd72f45788 Added minutes to uptime sensor 2017-10-23 14:38:16 -04:00
arsaboo
845fd532f0 Reverse tests 2017-10-23 14:08:38 -04:00
arsaboo
46404a84ec Update tests 2017-10-23 13:45:20 -04:00
arsaboo
ebce666264 Fix decimals in uptime sensor 2017-10-23 13:05:20 -04:00
Fabian Affolter
15cf34f45f Add support for HTTP Basic/Digest authentication 2017-10-23 17:48:51 +02:00
Pascal Vizeli
e620479cc8 Merge pull request #10069 from home-assistant/release-0-56-2
0.56.2
2017-10-23 17:40:58 +02:00
Pascal Vizeli
b292a4af3f EntityComponent: revert warning (#10078)
* Add warning back

* fix lint
2017-10-23 17:39:50 +02:00
Fabian Affolter
79d71c6727 Version bump to 0.56.2 2017-10-23 16:09:26 +02:00
Thom Troy
0b850b555f add eph ember controls (#9721)
* add eph ember controls

* updates based on review

* remove unused import

* update to new version of pyephember

* added myself to codeowners as requested

* make codeowners alphabetical

* run fixed gen_requirements_all

* Update ephember.py
2017-10-23 15:52:39 +02:00
R1chardTM
176c99f0cd Change deprecated use of maintainer tag in Dockerfile. (#10068) 2017-10-23 15:25:55 +02:00
Fabian Affolter
42e59b465e Make host optional (#10063)
* Make host optional

* Update test to reflect code changes
2017-10-23 15:24:04 +02:00
Daniel Høyer Iversen
e8a701ffd0 update library for xiaomi_aqara, change from pyCrypto to cryptography (#10066) 2017-10-23 14:55:36 +02:00
Fabian Affolter
32f58baa85 Fix merge conflict 2017-10-23 13:49:45 +02:00
Pascal Vizeli
9794336113 Remove warning 2017-10-23 13:48:25 +02:00
Teemu R
ed82f23da3 switch.tplink: fix overlooked issue with statically defined names (#10053) 2017-10-23 13:46:00 +02:00
Maciej Bieniek
48c86e07fa fix gateway illumination sensor value (#10045) 2017-10-23 13:45:34 +02:00
Fabian Affolter
76a0763cbc Remove STATE_UNKNOWN (#10064) 2017-10-23 13:12:14 +02:00
Fabian Affolter
f4f36a3662 Add link to docs and update ordering (#10062) 2017-10-23 12:18:23 +02:00
Philipp Schmitt
e201bcad14 Show current program thumbnail as media_image (#10033)
* Do not include program data in media_title if program data is undefined (None)

* Show thumbnail of currently playing program

* async setup

* Update requirements
2017-10-23 12:04:23 +02:00
Fabian Affolter
5182f76aea Merge pull request #10060 from home-assistant/some-cleanups
Remove warning component / Update event trigger for UI created
2017-10-23 11:54:50 +02:00
Fabian Affolter
53b1c75d81 Merge pull request #10061 from cgtobi/glances_icons
Add icons according to sensor types.
2017-10-23 11:12:17 +02:00
Tobias Sauerwein
fdc769abf7 Add icons according to sensor types. 2017-10-23 07:54:57 +00:00
Teemu R
f57e307c7a switch.tplink: fix overlooked issue with statically defined names (#10053) 2017-10-23 09:41:47 +02:00
Kevin Fronczak
f9d89a016e Add fail2ban sensor (#9975)
* Initial revision of fail2ban sensor

* Verified working, added tests

* Re-factored code so that log reading isn't called for each sensor

* Lint fixes

* Removed errant reset of last ban, added test to verify bans persist through update

* Removed for loop in read_log and replaced with regex per review request

* Refactored update to use current ban array for last ban state

- also was missing return False in timer for default behavior

* Removed CONF_SCAN_INTERVAL from PLATFORM_SCHEMA.extend

- renamed DEFAULT_SCAN_INTERVAL to SCAN_INTERVAL

* SCAN_INTERVAL changed to timedelta

* Force travis rebuild (last build timed out)

* Using compiled regex now
2017-10-23 09:20:45 +02:00
Pascal Vizeli
205f24c070 Trigger also with orderdict 2017-10-23 09:01:59 +02:00
Pascal Vizeli
4bf1972393 Remove warning 2017-10-23 08:58:02 +02:00
sander76
4fa0119245 fixing a typo in the old library which broke hub gen2 compatibility (#9990)
* fixing a typo in the old library.
Should now work with both version 1 and version 2 hub

* version bump

* fix Scene shadowing

* fix requirements. (not sure whether I should commit the other generated files as well ?)
2017-10-22 23:34:50 -07:00
Maciej Bieniek
ccde371a9d fix gateway illumination sensor value (#10045) 2017-10-23 08:02:20 +02:00
Adam Mills
4e7cc110d9 Fix no data event triggers (#10049)
* Test including extra data on a no data trigger

* Match any dicts for default schema for event data

* Fix indentation

* Only check schema if one was configured
2017-10-22 20:20:38 -04:00
Adam Mills
05ba78d886 Aioautomatic bump and scope update (#10043)
* Bump aioautomatic version

* Include vehicle:events scope for automatic

* Sort scopes
2017-10-22 17:11:35 -04:00
Adam Mills
ee56e33193 Add regression test for entity ID update bug (#10037) 2017-10-22 14:23:20 -04:00
Paulus Schoutsen
106bf467f8 0.56.1 (#10035)
* Version bump to 0.56.1

* Fix device update / entity_id with names (#10029)

* Fix device update

* Add tests

* add test for disabled warning

* fix temperature/humidity sensors valid values (#10024)
2017-10-22 19:56:20 +02:00
Pascal Vizeli
56cbfb5f2a Fix device update / entity_id with names (#10029)
* Fix device update

* Add tests

* add test for disabled warning
2017-10-22 08:40:00 -07:00
Maciej Sokołowski
193188b965 RGB Tradfri simple support (#9703) 2017-10-22 16:22:51 +01:00
Thom Troy
4197c9ee85 add irish rail transport sensor (#9883)
* add irish rail transport sensor

* Add True as last device

* Update irish_rail_transport.py
2017-10-22 14:18:34 +02:00
Klaas Hoekema
9418c61b25 Use feed name assigned in EmonCMS if there is one (#10021)
The default names for the feeds created by the EmonCMS component are
like 'emoncms1_feedid_10', but
- This is the display name. The ID should be lowercase and underscored,
  but the display name should be readable. The ID gets derived from it
  and comes out formatted correctly.
- EmonCMS lets you assign names to feeds, so it makes sense to use those
  if they exist, rather than feed IDs. The ID is pretty meaningless and
  basically means you have to override every name to make it readable.
- Including the ID identifying the EmonCMS instance (i.e. the '1') makes
  the name clunkier and would only be useful for people with multiple
  EmonCMS instances, which is likely to be an extremely small group since
  one hub can run as many feeds as you need it to.

This changes the default behavior but still uses configured 'name' if
it's set, so it won't break the configuration of people who have
customized their feed names in HA config.
2017-10-22 12:12:36 +02:00
Maciej Bieniek
62caea6bfb fix temperature/humidity sensors valid values (#10024) 2017-10-22 11:59:24 +02:00
Abílio Costa
80053ef21b switch.flux: add interval and transition attributes (#9700) 2017-10-22 11:27:04 +02:00
Fabian Affolter
bd4304e838 Upgrade youtube_dl to 2017.10.20 (#10014) 2017-10-22 11:24:07 +02:00
Sebastian Muszynski
c08c8c7996 Xiaomi Aqara: New xiaomi wireless button (sensor_switch.aq3) introduced (#10008)
* New xiaomi wireless button (sensor_switch.aq3) introduced.

* The next version of PyXiaomiGateway (0.5.3) is needed.
2017-10-22 11:02:01 +02:00
Paulus Schoutsen
9d39a5ced3 Call correct script [skip ci] 2017-10-22 01:04:10 -07:00
Fabian Affolter
816b69c807 Upgrade mypy to 0.540 (#10013) 2017-10-22 00:45:40 -07:00
Alan Fischer
9f62d5e3cf Json api fix (#10017)
* Dont let json parsing errors result in a 500

* Fixed error description
2017-10-22 00:44:46 -07:00
Chris Kacerguis
796a3ff49d Added gaction script to support the new Google Assistant component in Docker (#10019)
* added gactions install script

* added gaction setup step

* added ability to not install gaction

* updated dev docker file
2017-10-22 08:09:49 +02:00
Hydreliox
089e1ab6f4 Add xy attribute to Yeelight (#9957)
Allows using light profiles with yeelight bulbs
2017-10-22 02:59:55 +02:00
Fabian Affolter
5ad715507b Merge branch 'master' into dev 2017-10-22 00:46:47 +02:00
Fabian Affolter
ead4e44cd6 Merge pull request #9969 from home-assistant/release-0-56
0.56
2017-10-22 00:37:23 +02:00
Paulus Schoutsen
2a4c5466ef Merge remote-tracking branch 'origin/master' into release-0-56 2017-10-21 14:50:22 -07:00
Fabian Affolter
2ab14bbabc Bump dev to 0.57.0.dev0 (#10010) 2017-10-21 13:12:57 -07:00
Fabian Affolter
28b7a3da32 Renaming API.AI to Dialogflow (#10006)
* Rename API.AI to Dialogflow

* Rename API.AI to Dialogflow
2017-10-21 13:12:23 -07:00
milanvo
bf26b75d27 Change persistent notification to avoid long text in entity state (#9967)
* Change persistent notification to avoid long text in entity state

* Tests for changed persistent notification

* Persistent notification state

* Test for component state
2017-10-21 21:59:05 +02:00
Adam
3ea4691fce Fix spelling error (#10009) 2017-10-21 21:56:19 +02:00
Pascal Vizeli
f27ad76230 Remove async_update (#9997) 2017-10-21 21:51:58 +02:00
William Scanlon
60053a642c Moved siren to Wink from switch (#9879) 2017-10-21 21:51:57 +02:00
Lewis Juggins
d9f5398c56 [tradfri] Update pytradfri, simplify dependencies. (#9875)
* Update pytradfri

* Process dep links

* Process dep links

* Process dep links

* Install all deps

* Update requirements

* Exclude aiocoap

* Install cython

* Remove cython

* Exclude DTLSSocket

* Add cython
2017-10-21 21:51:50 +02:00
Gerardo Castillo
5df985a510 Update the Russound RNET component to use enhanced Russound.py (#9739)
* Updated RussoundRNETDevice.update() to call and enhanced function that reduces network traffic

Refer to issue #6 on the Russound project

* Updated RussoundRNETDevice.update() to invoke an enhanced function to reduce network traffic

PLease see issue #6 on the russound project

* Updated REQUIREMENTS to use version 0.1.9 of the Russound component

Please refer to issue #6 on the Russound rnet project

* Corrected some minor style details to satisfy Houndbot

* Update requirements_all.txt
2017-10-21 14:56:37 -04:00
cgtobi
789929d445 Add support for multiple disks to be monitored. (#9977)
* Add support for multiple disks.

* Fix LINT error.

* Make disk config optional to not break existing installations.

* Change state handling as per request by @fabaff.
2017-10-21 20:45:53 +02:00
Marcelo Moreira de Mello
51a65ee8e9 Introducing Ring Door Bell Camera (including StickUp cameras) and WiFi sensors (#9962)
*   Extended Ring DoorBell to support camera playback and wifi sensors

   * Bump python-ringdoorbell to version 0.1.6
   * Support to camera playback via ffmpeg
   * Extended ringdoorbell sensors to report WiFi attributes
   * Extended unittests

* Makes lint happy

* Added support to stickup cameras and fixed logic

* Fixed unittests for stickup cameras

* Makes lint happy

* Refactored attributions and removed extra refresh method.
2017-10-21 16:08:40 +02:00
Martin Eberhardt
222cc4c393 Add optional attribute option to scrape sensor (#10001)
* Add optional attribute option to scrape sensor

* Rename attribute variable to attr
2017-10-21 16:03:29 +02:00
Pascal Vizeli
ce1a2cc2a6 Remove async_update (#9997) 2017-10-21 09:45:05 +02:00
Fabian Affolter
aab7442cc5 Upgrade gitterpy to 0.1.6 (#9983) 2017-10-20 20:28:34 +02:00
Fabian Affolter
4f1eab138c Upgrade speedtest-cli to 1.0.7 (#9984) 2017-10-20 20:28:11 +02:00
Kamil Warguła
53df3fadd7 Update screenshot-components.png file. (#9987) 2017-10-20 20:26:34 +02:00
William Scanlon
41c2bdb4fb Moved siren to Wink from switch (#9879) 2017-10-20 10:18:32 -04:00
Lewis Juggins
d16c5f9046 [tradfri] Update pytradfri, simplify dependencies. (#9875)
* Update pytradfri

* Process dep links

* Process dep links

* Process dep links

* Install all deps

* Update requirements

* Exclude aiocoap

* Install cython

* Remove cython

* Exclude DTLSSocket

* Add cython
2017-10-19 23:20:33 -07:00
TopdRob
29d4dca56a Update requests requirement (#9876)
* Update request requirement

Update request requirement from version v2.14.2 to v2.18.4

* Fix dependency vizio integration

3rd patry packages were removed from requests. Changed dependency from requests to urllib3

* forgot =

forgot = when adding the requirement

* re-run script/gen_requirements_all.py

re-run script/gen_requirements_all.py

* Unvendoring urllib3 from requests

In v2.16.0 and newer of requests they unverdored urllib3.

* undefined name 'InsecureRequestWarning'

* Removed requirement to 'urllib3==1.22

* removed import requests

* removed urllib3.exceptions.InsecureRequestWarning

removed urllib3.exceptions.InsecureRequestWarning travis lint
2017-10-19 19:24:49 -07:00
Paulus Schoutsen
9722125234 Version bump to 0.56 2017-10-19 09:01:03 -07:00
boltgolt
78c302855a Add Toon support (#9483)
* Added Toon support again

* Forgot about .coveragerc

* Fixed style issues

* More styling and importing fixes

* Implemented the suggestions made by @pvizeli

* The smallest fix possible

* Removed custom names for Toon states

* Fix last push with 2 outdated lines

* Removed HOME and NOT_HOME, moved to just climate states

* Bumped dependency for better handling of smartplugs that don't report power consumption

* Implemented changes as suggested by @balloob

* Rebase, gen_requirements_all.py finally working
2017-10-19 08:59:57 -07:00
Pascal Vizeli
c1b197419d Fix async probs (#9924)
* Update entity.py

* Update entity_component.py

* Update entity_component.py

* Update __init__.py

* Update entity_component.py

* Update entity_component.py

* Update entity.py

* cleanup entity

* Update entity_component.py

* Update entity_component.py

* Fix names & comments / fix tests

* Revert deadlock protection

* Add tests for entity

* Add test fix name

* Update other code

* Fix lint

* Remove restore state from template entities

* Lint
2017-10-19 10:56:25 +02:00
Paulus Schoutsen
6cce934f72 Improve SSL certs used by aiohttp (#9958)
* Improve SSL certs used by aiohttp

* Add certifi package

* Lint
2017-10-19 10:47:57 +02:00
Pascal Vizeli
38cb32afd6 Update ffmpeg 1.9 (#9963) 2017-10-19 10:46:32 +02:00
Pascal Vizeli
c96c283293 Update ffmpeg.py 2017-10-19 10:36:09 +02:00
Pascal Vizeli
2fb4709a94 Update requirements_test_all.txt 2017-10-19 10:35:45 +02:00
Joe Lu
42f450d4e6 Use default clientsession to stream synology video (#9959) 2017-10-19 07:02:43 +02:00
Sean Gollschewsky
6ea866c7f7 Add emeter attributes (#9903)
* Add emeter attributes.

* Remove unused attributes.

* Rework supported features so it only queries the bulb once.

* Used cached supported_features, catch errors if energy usage not reported.
2017-10-18 21:52:44 -07:00
Daniel Perna
429b637885 Upgraded pyhomematic (#9956) 2017-10-19 01:31:25 +02:00
Alok Saboo
f05a8bfa2a Update fritzconnection to 0.6.5 (#9950) 2017-10-18 20:58:26 +02:00
Alok Saboo
96e3dfeb53 Update fritzhome to 1.0.3 (#9951) 2017-10-18 20:57:53 +02:00
Alok Saboo
520de0d278 Update hikvision to 1.2 (#9953) 2017-10-18 20:57:13 +02:00
Alok Saboo
2cacfb5477 Update enocean to 0.40 (#9949) 2017-10-18 19:04:44 +02:00
Alok Saboo
4960892256 Update directpy to 0.2 (#9948) 2017-10-18 19:04:01 +02:00
Fabian Affolter
834d0e489e Move 'lights' to const.py (#9929) 2017-10-18 18:41:14 +02:00
Derek
1e1d593ef7 Changed returned attribute from "Game" to "game" (#9945)
I noticed the steam component "game" attribute is capitalized. This should be lowercase if I'm not mistaken.

From:
        return {'Game': self._game}
To:
        return {'game': self._game}

Not sure if i'm doing this correctly... apologizes if I'm not!
2017-10-18 18:27:02 +02:00
Pascal Vizeli
8a93cc147a FFmpeg 1.8 (#9944)
* Update requirements_all.txt

* Update requirements_test_all.txt

* Update ffmpeg.py

* Update ffmpeg.py

* Update yi.py

* Update onvif.py

* Update yi.py
2017-10-18 17:11:22 +02:00
Ludovic
628b9bd8d8 notify.xmpp - Add support for MUC (#9931)
* Add support for MUC

* Fix two spaces before inline comment
2017-10-18 16:28:37 +02:00
Daniel Welch
1bec2c005d using defusedxml ElementTree for safer parsing of untrusted XML data (#9934)
* using defusexml ElementTree for safer parsing of untrusted XML data

* move from core dependency to platform specific dependency

* style difference: put back end of list comma in setup.py
2017-10-18 16:21:46 +02:00
Daniel Høyer Iversen
587948ec06 Xiaomi config validation (#9941)
* validate xiaomi config

* Update xiaomi_aqara.py

* check for valid config

* use consts
2017-10-18 14:57:27 +02:00
Hugo Dupras
f641a6aad3 Fix missing timeout for Netatmo binary sensor (#9850)
* Fix missing timeout for Netatmo binary sensor

This fix also merges timeout and offset because there were the same thing

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

* Fix lint errors

* Fix style
2017-10-18 14:56:24 +02:00
jbarrancos
a1d5daee53 Merge pull request #7 from jbarrancos/rainbird
Fixed comments from @fabaff
2017-10-18 12:58:04 +02:00
Fabian Affolter
8a2134b3a8 Add serial sensor (#9861)
* Add serial sensor

* Rename config variable and cancel
2017-10-18 11:20:19 +02:00
PeteBa
c06d92900a Align away state tag with device_trackers (#9884) 2017-10-18 11:19:09 +02:00
J.J.Barrancos
a628112e4c lint ws 2017-10-18 11:17:29 +02:00
Egor Tsinko
6e0efbe35e A new platform for controlling Monoprice 6-Zone amplifier (#9662)
* added implementation for monoprice 6-zone amplifier. This implementation is based on and very similar to russoun_rnet implementaion

* updated comments and cleaned up code

* updated comments and cleaned up code

* added unit tests

* removed 'name' attribute from platform schema.

* added monoprice.py to .coveragerc

* fixed lint

* fixed lint errors

* fixed lint errors

* added monoprice to requirements_all.txt

* fixed lint errors again

* implemented change requests

* fixed lint error

* added exception handling to setup_platform()

* replaced catchall with SerialException only

* added myself to CODEOWNERS

* fixed weird merge to CODEOWNERS
2017-10-18 11:11:36 +02:00
J.J.Barrancos
778761ebce lint error 2017-10-18 10:40:38 +02:00
J.J.Barrancos
76a3a4892d Fix req 2017-10-18 10:18:37 +02:00
TopdRob
bef4ae3e35 Update aioimaplib from v0.7.12 to v0.7.13 (#9930)
* Update aioimaplib from v0.7.12 to v0.7.13

Changelog v0.7.13:
[aiolib] adds a connection lost callback [test] imapserver : added APPENDUID response for APPEND cmd [test][fix] imapserver append should add to the connected user mb [test] imapserver : more accurate building of message headers (using python email module)

* run script/gen_requirements_all.py
2017-10-18 10:00:00 +02:00
Alok Saboo
818a52508e Bump py-synology to 0.1.5 (#9932) 2017-10-18 09:58:49 +02:00
J.J.Barrancos
02f8779de8 Fixed comments from @fabaff
Fxied issues raides
2017-10-18 09:58:32 +02:00
TopdRob
33f8ca5abc update async_timeout from v1.4.0 tp v2.0.0 (#9938) 2017-10-18 09:48:00 +02:00
Paulus Schoutsen
3700fce859 Allow flexible relayer url (#9939) 2017-10-17 23:00:36 -07:00
Phil Kates
9d20a53d63 Google Actions for Assistant (#9632)
* http: Add headers key to json[_message]

* Add google_assistant component

This component provides API endpoints for the Actions on Google Smart
Home API to interact with Google Assistant.

* google_assistant: Re-add fan support

* google_assistant: Fix Scene handling

- The way I originally wrote the MAPPING_COMPONENT and the way it's actual
  used changed so the comment was updated to match that.
- Use const's in more places
- Handle the ActivateScene command correctly

* google_assistant: Fix flakey compare test

Was failing on 3.4.2 and 3.5, this is more correct anyway.

* google_assistant: Use volume attr for media_player
2017-10-17 22:00:59 -07:00
mclem
1d68777981 Add transmission sensor: number of active torrents (#9914)
* Add transmission sensor: number of active torrents

* Make variable name shorter
2017-10-17 22:45:37 +02:00
cgtobi
f5b305c980 Fix the resource naming in the UI (#9927)
Use proper English for the UI representation without breaking the component.
2017-10-17 21:32:01 +02:00
Daniel Høyer Iversen
382f9a8f49 Update xiaomi_aqara.py (#9920) 2017-10-17 18:04:19 +02:00
cgtobi
778c3bb83d Fix the resource naming in the UI (#9916)
Use proper English for the UI representation without breaking the component.
2017-10-17 14:07:05 +02:00
Eugenio Panadero
e57d0f345e Recorder: Extra check to incoming connections which could be not sqlite3 ones (#9867)
* Extra check to incoming connections

The incoming connection could be other than self.db_url, because
some 'custom_component' could be making these, and then, if they're not
sqlite3 connections, an error will raise because those haven't the
`dbapi_connection.isolation_level` attrib.

* lint fix

* simplify check: isinstance test only
2017-10-17 10:06:49 +02:00
Oliver
ed70fc9322 Added support for Denon AVR-4810. (#9887) 2017-10-17 10:04:35 +02:00
Daniel Høyer Iversen
82c7195484 add last_action for xiaomi cube (#9897) 2017-10-17 10:03:46 +02:00
Aaron Bach
9be7763144 Fixes (#9911) 2017-10-17 10:02:03 +02:00
Eugenio Panadero
875edef3f0 Fix load of components without any config from packages (#9901)
* Fix load of components without any config from packages

- Add 'None' to the packages config schema validation, to be able to
load components without any more configuration from yaml package files,
like `wake_on_lan`, `media_extractor` and so on.

* test the ability to load components without configuration from packages
2017-10-17 09:59:33 +02:00
Aaron Bach
3de95c068a Fixes (#9912) 2017-10-17 09:24:52 +02:00
Bahnburner
51c5534c2a Update osramlightify.py (#9905) 2017-10-16 22:09:19 +02:00
Sergey Isachenko
d95b75a10c Dependemcy version bump. (#9899)
Closes #8213.
Closes #7575.
2017-10-16 21:46:21 +02:00
Pascal Vizeli
1f25aa74dd Release 0.55.2 (#9904)
* Do not auto-install credstash (#9844)

* Pump release to 0.55.2
2017-10-16 21:01:25 +02:00
William Scanlon
5986d9ff5b Added super attributes to Wink binary sensors (#9824)
* Added super attributes to Wink binary sensors

* Removed unused import.
2017-10-16 14:58:23 +02:00
Jeroen ter Heerdt
eb6fb5549f Changing clicksendaudio to clicksend_tts in .coveragerc (#9900) 2017-10-16 13:46:24 +02:00
Russell Cloran
7596ac23fc zha: Update to bellows 0.4.0 (#9890)
Fixes: #8822
2017-10-15 21:41:16 -07:00
Julius Mittenzwei
c37883c9a9 Xknx improvements (#9871)
* Issue https://github.com/XKNX/xknx/issues/65 Make state_updater adjustable by config file (On/OFF)

* Issue https://github.com/XKNX/xknx/issues/48 updated home assistant plugin: added support for setpoint shift

* bumped version

* added missing docstrings.

* Bumped version.

* Fixed requirements_all.txt

* added new options to PLATFORM_SCHEMA
2017-10-15 23:46:55 +02:00
Paulus Schoutsen
c6b285c666 Merge pull request #9885 from home-assistant/release-0-55-1
0.55.1
2017-10-15 14:30:45 -07:00
Eugenio Panadero
b1dc48822d Upgrade python-telegram-bot to 8.1.1 (#9882)
* update python-telegram-bot to v8.1.1

* update python-telegram-bot to v8.1.1
2017-10-15 21:22:51 +02:00
Philipp Schmitt
ff6f5cc116 Fix #9839 (#9880)
* Fix #9839

* Update requirements

* Default state: STATE_UNKNOWN -> None

* Default the state to None in the constructor as well
2017-10-15 12:20:22 -07:00
Philipp Schmitt
da8be253bc Fix #9839 (#9880)
* Fix #9839

* Update requirements

* Default state: STATE_UNKNOWN -> None

* Default the state to None in the constructor as well
2017-10-15 21:16:23 +02:00
Adam Cooper
2547a235c1 Bugfix/9811 jinja autoescape (#9842)
* Added autoescape kwarg to Jinja environment

* Removed extra comma
2017-10-15 11:53:27 -07:00
Adam Cooper
fdb698bef0 Changed yaml.load into yaml.safe_load (#9841) 2017-10-15 11:53:26 -07:00
Paulus Schoutsen
586e54f8bf OwnTracks: Fix handler is None checking (#9794)
* OwnTracks: Fix handler is None checking

* Update owntracks.py
2017-10-15 11:53:25 -07:00
Lewis Juggins
431201cb9b [light.tradfri] Fix transition time (#9785)
* Fix transition time, set a default

* Wrong default

* Use int for safety

* Revert default.
2017-10-15 11:53:25 -07:00
pascal
9b43388093 missing is_closed ( rflink cover fix ) (#9776)
* Added is_closed

* whitespaces --

* removed whitespace
2017-10-15 11:53:24 -07:00
Joe Lu
45620d6892 Fix for TypeError in synology camera (#9754) 2017-10-15 11:53:24 -07:00
Paulus Schoutsen
7ed21d90aa Version bump to 0.55.1 2017-10-15 11:51:21 -07:00
Fabian Affolter
959a7b2d59 Upgrade paho-mqtt to 1.3.1 (#9874) 2017-10-15 10:12:43 -07:00
Eugenio Panadero
ac256d5943 handle OWM API error calls (#9865) 2017-10-15 10:31:34 +02:00
Paulus Schoutsen
0362a76cd6 Cloud connection via aiohttp (#9860)
* Cloud: connect to cloud

* Fix tests in py34

* Update warrant to 0.5.0

* Differentiate errors between unknown handler vs exception

* Lint

* Respond to cloud message to logout

* Refresh token exception handling

* Swap out bare exception for RuntimeError

* Add more tests

* Fix tests py34
2017-10-14 19:43:14 -07:00
Eugenio Panadero
26cb67dec2 minimal fixes in the owntracks mqtt device tracker (#9866)
* fix UnboundLocalError when unable to parse payload, and show bad topics that cannot be parsed ok

* Update owntracks.py
2017-10-14 15:46:06 -07:00
Fabian Affolter
00244380a8 Upgrade psutil to 5.4.0 (#9869) 2017-10-14 23:07:31 +02:00
Ryan Bahm
f807a3a890 Darksky enhancements (#9851)
* Correct capitalization inconsistency in DarkSky

All two-word sensors ("Precip Intensity," "Nearest Storm Bearing," etc) in Darksky uses title case for the friendly name of the sensor, with the exception of "Dew point."

* Implement UV Index in Darksky

* Fixed whitespace for Tox compliance

* Add unit for UV Index.

Per recommendation of reviewer, added 'UV Index' as a CONST in const.py, then used that const in both DarkSky and ISY994. It looks like BloomSky might also support UV Index and it should probably be standardized.
2017-10-14 14:45:32 -04:00
Kevin Fronczak
fd6c2598a7 Uptime sensor (#9856)
* Added uptime sensor for homeassistant

* Fixed pylint and flake8 errors

* Made requested changes from PR

- Fixed stale docstrings
- Changed default state to None
- Added ability for user to use hours or days

* Fixed typo

* Added unit_of_measurement check to test

* Converted to async

- Changed tests to work with async

* Minor updates
2017-10-14 20:06:44 +02:00
Fabian Affolter
79d1a0ab37 Upgrade youtube_dl to 2017.10.12 (#9862) 2017-10-14 19:07:28 +03:00
Jeroen ter Heerdt
a787ab6d3c Changing name of clicksendaudio component to clicksend_tts (#9859) 2017-10-14 15:08:28 +02:00
Pascal Vizeli
8456cd0313 HassIO - TimeZone / Host services (#9846)
* HassIO - TimeZone / Host services

* Update hassio.py

* Update test_hassio.py
2017-10-13 15:45:22 +02:00
Charles Garwood
fa37d9800e File permissions fix (#9847)
* Fixing file permissions

* Fixing file permissions
2017-10-13 14:22:41 +02:00
icovada
80826bc985 Add CAPSman master to mikrotik presence detection (#9729)
* Add CAPSman master to mikrotik presence detection

Automatically prefer caps-man registered clients over locally connected

* Remove blank line

* Trailing whitespace removed
2017-10-13 10:54:58 +02:00
rbflurry
b00d0a1253 Use the Last Seen attribute in unify (#8998)
* Uses the Last Seen attribute in unify

* Update unifi.py

fix format

* Update unifi.py

formatting again

* update test_unifi to call CONF_CONSIDER_HOME

Updated.

* Update test_unifi.py

* Update test_unifi.py

* More unit test test

* Update where consider_home comes from.

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* fix hound

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

Fix the butcher of tests.

* Update unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update test_unifi.py

* Update unifi.py

* Update test_unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py

* Update unifi.py
2017-10-13 10:13:58 +02:00
Paulus Schoutsen
f7545fe85c Remove namecheap dns service (#9845) 2017-10-13 09:47:13 +02:00
Martin Treml
c69e9c1d49 Add namecheap DNS component (#9821)
* Add namecheap DNS component

* Updates for pull-request

* remove unused import in test file

* Update .coveragerc
2017-10-12 23:58:23 -07:00
Paulus Schoutsen
79b029a680 Do not auto-install credstash (#9844) 2017-10-12 23:57:45 -07:00
Aaron Bach
9891320e7c New PR (#9787) 2017-10-12 22:20:30 -07:00
Adam Cooper
64853bae32 Changed yaml.load into yaml.safe_load (#9841) 2017-10-12 22:05:33 -07:00
Adam Cooper
a7f4bcc410 Bugfix/9811 jinja autoescape (#9842)
* Added autoescape kwarg to Jinja environment

* Removed extra comma
2017-10-12 22:01:29 -07:00
Lukas Barth
bbb406626b Bugfix: Include MQTT schema (#9802) 2017-10-12 22:00:09 -07:00
Charles Garwood
c5c594ba7d Add service descriptions (#9806)
* Added descriptions for services under homeassistant domain

* lint fixes

* Fixing file permissions
2017-10-12 21:59:07 -07:00
Adam Mills
8d83912649 Run initial generation for development mode (#9826)
* Run initial generation for development mode

* Use yarn dev
2017-10-12 21:56:38 -07:00
Teemu R
2c1f0f3449 fix climate services (missing indentation, wrongly formatted example) (#9805) 2017-10-12 21:29:17 +03:00
Kane610
c85b5561ee Update CODEOWNERS */axis.py (#9823)
Add code owner for */axis.py
2017-10-12 21:26:07 +03:00
Alan Fischer
4cf300a710 Fixed reporting of vera UV sensors (#9838) 2017-10-12 20:51:25 +03:00
Fabian Affolter
3bdb7052b8 Upgrade libnacl (#9769)
* Upgrade libnacl to 1.6.0

* Small style updates
2017-10-12 18:13:43 +02:00
Paulus Schoutsen
3b5a9e7796 OwnTracks: Handle lwt message (#9831)
* OwnTracks: Handle lwt message

* Update owntracks.py
2017-10-12 08:25:18 -07:00
Charles Garwood
5fcb0990c3 Adds image attribute to html5 notify (#9832) (#9835) 2017-10-12 17:01:12 +02:00
cdce8p
be5c0b2d92 Wait_template - support for 'trigger.entity_id' and data_template values (#9807)
* *Added support for use of 'trigger.entity_id' and service->data_template->script in wait_template

* * Fixed style violations

* * Fixed regular expression (_RE_GET_POSSIBLE_ENTITIES)

* * combined 'extract_entities' and 'extract_entities_with_variables'
* fixed regular expression

* * Added first test for extract_entities_with_variables

* * Added Unittests (tests/helpers/test_template.py test_extract_entities_with_variables)

* * Added Unittests (tests/helpers/test_script.py test_wait_template_variables)

* * Added Unittests (tests/components/automation/test_template.py test_wait_template_with_trigger)

* * Added Unittests (tests/components/automation/test_state.py test_wait_template_with_trigger)

* * Added Unittests (tests/components/automation/test_numeric_state.py test_wait_template_with_trigger)

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Fixed style violations

* * Updated regular expression and delete whitespaces
2017-10-12 16:57:18 +02:00
jbarrancos
38e02a057d Merge pull request #6 from jbarrancos/rainbird
Removed requirement
2017-10-12 10:43:35 +02:00
J.J.Barrancos
fad9e607c3 Removed requirement 2017-10-12 10:22:22 +02:00
Paulus Schoutsen
c33b179fb8 Fix ISY994 fan platform overwriting state property (#9817)
* ISY994 platform overwrote state

* Update isy994.py

* Update isy994.py
2017-10-12 00:36:24 -07:00
Adam Mills
765560e87a Restore home-assistant-polymer pointer from #9720 (#9825) 2017-10-11 21:53:12 -04:00
Charles Garwood
f837302194 Split map panel out into its own component (#9814) 2017-10-11 17:45:55 +02:00
jbarrancos
47d8601f30 Merge pull request #5 from jbarrancos/rainbird
Rainbird
2017-10-11 17:24:07 +02:00
J.J.Barrancos
bddb424b0d Requirements updated 2017-10-11 17:01:14 +02:00
J.J.Barrancos
8db4b4f303 typo 2017-10-11 16:54:08 +02:00
J.J.Barrancos
cc4ec228b5 Removed requirement 2017-10-11 16:12:01 +02:00
jbarrancos
c6e6496000 Merge pull request #4 from jbarrancos/dev
Merge in latest
2017-10-11 16:05:54 +02:00
jbarrancos
2c9010d661 Merge pull request #3 from home-assistant/dev
Get latest
2017-10-11 16:04:37 +02:00
J.J.Barrancos
24826c2770 Revert "Dependency breaks build"
This reverts commit c1aaed250a.
2017-10-11 16:02:32 +02:00
J.J.Barrancos
c1aaed250a Dependency breaks build 2017-10-11 16:01:25 +02:00
J.J.Barrancos
59fcef39ff Split requirements per line 2017-10-11 15:56:18 +02:00
J.J.Barrancos
d0ff45500b Fixed dependency version 2017-10-11 15:45:07 +02:00
J.J.Barrancos
0ace832166 Requirements updated 2017-10-11 15:35:58 +02:00
Fabian Affolter
19887f8742 Upgrade pyasn1 to 0.3.7 and pyasn1-modules to 0.1.5 (#9810) 2017-10-11 16:26:34 +03:00
J.J.Barrancos
7f97d166bf Added dependency on pycrypto
Crypto Dependency missing found on virtualenv install. Added to dependecy rainbird.py
2017-10-11 15:15:50 +02:00
Adam Cooper
0de2266a72 Resolving bug that prevents ssl_verify option for Unifi device_tracker (#9788)
* Added TODO to illustrate my intentions

* Resolved linting issue

* Resolved bool or file validation and updated tests

The tests have been updated to include mocks to assert a temp
ca cert exists as it should for the positive tests with an
additional negative test for a file not existing being tested.

* Resolved flake8 linting issues (test docstrings)
2017-10-11 00:08:36 +02:00
Paulus Schoutsen
8f06b35dfc Optimize event matcher (#9798)
* Optimize event matcher

* Tweak order of checks

* Add a benchmark for time_changed helper

* Add state change benchmark

* fix lint
2017-10-10 22:26:03 +02:00
Paulus Schoutsen
a97e7bb22d Simplify track_same_state (#9795) 2017-10-10 21:16:19 +02:00
Paulus Schoutsen
fc47e9443b OwnTracks: Fix handler is None checking (#9794)
* OwnTracks: Fix handler is None checking

* Update owntracks.py
2017-10-10 10:39:25 +02:00
Lewis Juggins
e144b0f0f9 [light.tradfri] Fix transition time (#9785)
* Fix transition time, set a default

* Wrong default

* Use int for safety

* Revert default.
2017-10-10 00:35:28 -07:00
ziotibia81
a024c1b162 Communication timeout support in modbus hub. (#9780)
* Communication timeout support in modbus hub.

Timeout parameter are taken from configuration and passed to pymodbus constructor.

* CONF_TYPE and CONF_TIMEOUT imported from const.py
2017-10-09 23:51:18 +02:00
Sean Dague
581e2f22d5 Bump rxv library to 0.5.1 (#9784)
This fixes some bugs with interfacing with yamaha receivers, including
closing bug #5209.
2017-10-09 17:58:53 +02:00
William Scanlon
5232f2abdd Wink dome siren support (#9667)
* Support for Wink Dome siren/chimes
2017-10-09 11:16:36 -04:00
pascal
cb52b80f7d missing is_closed ( rflink cover fix ) (#9776)
* Added is_closed

* whitespaces --

* removed whitespace
2017-10-09 16:57:44 +02:00
Sean Dague
d0ec9301ab Fix off by one error in arwn platform (#9781)
There is an off by one error that causes period exceptions. Fix this.
2017-10-09 15:41:18 +02:00
Sergey Isachenko
9abd0fb92f Tesla bug fixes. (#9774)
* Tesla bug fixes.

* Added myself to CODEOWNERS for tesla.
2017-10-09 14:38:00 +03:00
Aaron Bach
43d77729c5 WIP: Fix Arlo Camera blocking IO (#9758)
* WIP: Fix Arlo Camera blocking IO

* Accidental undo

* Linting issues

* Owner-requested changes

* Bumped pyarlo version and added Throttle

* Fix

* Update requirements_all.txt
2017-10-09 11:35:05 +02:00
Jeroen ter Heerdt
04b3c89cf5 Adding myself as codeowner for egardia alarm control panel. (#9772)
Adding jeroenterheerdt as codeowner for egardia alarm control panel.
2017-10-09 11:52:51 +03:00
Jeroen ter Heerdt
09e2075c68 Updating pythonegardia package requirement to .22 because of fixed bug in passing default value for parameter SSL for egardiaserver (#9770) 2017-10-09 10:37:51 +02:00
Rob Connolly
3bd9684ca5 Add notification platform for Rocket.Chat. (#9553)
* Add notification platform for Rocket.Chat.

* Changes to Rocket.Chat notification platform based on feedback.

* Implement better error handling for Rocket.Chat platform.

* Return None if Rocket.Chat notify platform init fails.

* Refactor Rocket.Chat notifications.

Refactor Rocket.Chat notification platform to remove async and
simplify error handling.

* fix url
2017-10-09 09:38:48 +02:00
Paulus Schoutsen
414900fefb Expose time module in Python Scripts (#9736)
* Expose time module in Python Scripts

* Make dt_util available in Python Scripts

* Limit methods in time module

* Add time.mktime

* Limit access to datetime

* Add warning to time.sleep

* Lint
2017-10-09 08:51:32 +02:00
Egor Tsinko
35484ca086 fix for LocationParseError in netgear platform (#9683)
* fix for LocationParseError in netgear platform

* added unit tests for get_scanner()

* fixed houndci-bot warnings

* fixed lint warnings

* fixed lint warnings

* fixed broken test

* removed guard clause from netgear.py
removed all discovery related code from device_tracker
removed unnecessary unit test

* removed discovery related tests

* removed unused import

* removed unused import
2017-10-08 22:14:39 -07:00
Sebastian Muszynski
603765fe92 Xiaomi Smart WiFi Socket and Smart Power Strip integration (#9138)
* Xiaomi Smart WiFi Socket and Smart Power Strip integration

* Comment updated.

* Blank line removed.

* Typo fixed.

* Version of python-mirobo bumped.

* Version of python-mirobo bumped: Lightweight API changes.

* Additional API changes.

* Library version properly pinned again.

* Platform not ready behavior fixed.
Expose the device model as sensor attribute.
Device initialized log message added. Provides device model, firmware and hardware version.

* Component renamed: switch.xiaomi_plug -> switch.xiaomi_miio

* Revise based on review: Unused code removed. Filename updated.
2017-10-08 22:11:11 -07:00
Marcelo Moreira de Mello
80140732c3 Bump raincloudy version 0.0.3 (#9767)
* Bump raincloudy version 0.0.3

* Fix logic for raincloudy status binary_sensor

* Simplified binary_sensor logic

* Simplify
2017-10-08 21:08:40 -07:00
Teemu R
c00647ace0 yeelight: implement min_mireds and max_mireds, fixes #9509 (#9763)
* yeelight: implement min_mireds and max_mireds, fixes #9509

thanks to @amelchio for pointing this out!

* remove typing infos
2017-10-08 21:05:49 -07:00
Andrey
2a2ee81957 Match test requirements by full package name. (#9764) 2017-10-08 20:49:51 -07:00
ChristianKuehnel
b620c433c0 Initializing statistics sensor with data from database (#9753)
* Initializing statistics sensor with data from database

* fixed broken test case

* usage of recorder component is now optional, thx to @andrey-git

* added test case for initialize_from_database
2017-10-08 23:45:12 +02:00
Aaron Bach
b80f00900d Adding my contributions (#9761) 2017-10-08 22:56:58 +02:00
Daniel Høyer Iversen
a32fc10f1b Update CODEOWNERS (#9760) 2017-10-08 22:32:42 +02:00
Teemu R
672ff96754 add myself to yeelight owners, too (#9759) 2017-10-08 22:36:17 +03:00
Mister Wil
8132989f91 Skybell (#9681)
* New Skybell platform with components

* Added skybell components to omit.

* Preemptively fixing lint issues (hopefully).

* Removed unused variable.

* Requested changes.

* Additional CRs

* Hopefully the last of the CR's!
2017-10-08 20:14:39 +02:00
Paulus Schoutsen
ca54bbfcc9 RFC: Use bind_hass for helpers (#9745)
* Add Helpers bind_hass functionality

* Update other helpers
2017-10-08 08:17:54 -07:00
Teemu R
e19e9a1f2b switch.tplink, light.tplink: bump the pyhs100 version and adapt to api changes (#9454)
* bump the pyhs100 version and fix api changes

* switch.tplink: avoid I/O during __init__

* initialize _name to None in __init__

* update requirements_all.txt for the new version
2017-10-08 17:31:32 +03:00
Joe Lu
e89e64263c Fix for TypeError in synology camera (#9754) 2017-10-08 13:31:00 +03:00
Marcelo Moreira de Mello
f56bdd29ff Make Arlo battery_level icon dynamic (#9747)
* Make Arlo battery_level icon dynamic

* makes lint happy
2017-10-08 10:05:41 +02:00
Paulus Schoutsen
9eff9fa703 Fix I/O in event loop by Arlo alarm control panel (#9738) 2017-10-08 09:26:16 +02:00
Pascal Vizeli
c1f156fd2b Rewrite Alexa Smart-Home skill to v3 (#9699)
* Rewrite Alexa Smart-Home skill to v3

* add discovery & fix brigness

* Rewrite Tests

* fix lint

* fix lint p2

* fix version

* fix tests

* fix test message generator

* Update smart_home.py

* fix test

* fix set bug

* fix list

* fix response name for discovery

* fix flucky tests
2017-10-07 13:31:57 -07:00
Adam Mills
4342d7aa17 Event trigger nested conditions (#9732)
* Test to supported nested event triggers

* Update event trigger to allow nested data tests
2017-10-07 13:13:32 -07:00
Ryan McLean
af3ea5a321 Fix: Last Played Media Title persists in plex (#9664)
* Fix: Last Played Media Title in plex would stay even when player was idle/off
     Primary Fix is in the "if self._device" portion.
     code in "if self._session" is a catch all but i'm not 100% if it is needed.

* Fixed lint issues with previous commit

* 1st Pass at refactoring plex refresh
Moved _media** into clearMedia() which is called in _init_ and
at start of refresh.

Removed redunant _media_* = None entries

Grouped TV Show and Music under single if rather than testing
seperately for now.

* Fixed invalid name for _clearMedia()
Removed another media_* = None entry

* Removed print() statements used for debug

* Removed unneeded "if" statement
2017-10-07 15:31:01 -04:00
Andrey
c09b7b5d6d Add andrey-git to codeowners (#9718) 2017-10-07 17:58:45 +02:00
Lewis Juggins
710454119f [light.tradfri] Clone all of aiocoap to ensure pinned commit will be present (#9713) 2017-10-07 08:54:51 -07:00
Fabian Affolter
25e6d694e1 Bump release to 0.56.0dev (#9726) 2017-10-07 16:07:49 +02:00
Fabian Affolter
19a20b3b13 Move 'show_on_map' to const (#9727) 2017-10-07 15:11:41 +02:00
Aaron Bach
bd5b70c3cd Add show_on_map config option to AirVisual (#9654)
* Removed lat/long attributes

* Linting

* Revised PR to focus on show_on_map configuration
2017-10-07 13:38:52 +02:00
Marcelo Moreira de Mello
ec5439e4d4 Introducing support to Travis-CI (#9701)
* Introduced support to Travis CI

* Added Last Build Started sensor and simplified code

* Fixed logic error

* Simplified _LOGGER.debug statement

* Introduced support to Travis CI

* Added Last Build Started sensor and simplified code

* Fixed logic error

* Simplified _LOGGER.debug statement

* Renamed parameter since the repository_names expects a list

* Refactoring code to synchronous

* Simplified variables names
2017-10-07 11:02:40 +02:00
Fabian Affolter
fd509e188a Arlo clean-up (#9725)
* Fix remaining isses from #9711

* More clean-up
2017-10-07 10:59:46 +02:00
Mister Wil
a5a839e72a Abode Temp, Humidity, and Light Sensor (#9709)
* Update to 0.12.1 and sensor implementation.

* Removing unnecessary dict gets.

* Added name property to actually use the _name variable.

* Update docstring
2017-10-07 10:25:53 +02:00
Vignesh Venkat
3b53952dbe arlo: Add alarm control panel component (#9711)
* arlo: Add alarm control panel component

Allows importing arlo base stations as an alarm control panel
component in HA. Lets the users configure a custom home mode since
arlo does not have a built-in home mode.

* fix lint and houndci comments

* Use async_update to update the state

Move the state updating code from state() to update() since it does
I/O.

* Do not set state in __init__

Make sure that update is called by passing the second parameter to
async_add_devices.

* Order imports and fix dos-strings
2017-10-07 10:07:38 +02:00
Fabian Affolter
e502202de7 Upgrade pysnmp to 4.3.10 (#9722) 2017-10-07 09:47:52 +02:00
Kevin Fronczak
2479ce9123 More netdata sensors (#9719)
* Added more netdata sensors

* Changed precision on counts, packets, and uptime
2017-10-07 00:22:40 +02:00
Teemu R
d3772d4abd bump the version and catch all exceptions to avoid showing backtraces… (#9720)
* bump the version and catch all exceptions to avoid showing backtraces but a more sane error message

* catch only BTLEExceptions, fix logging strings
2017-10-07 00:21:34 +02:00
jbarrancos
e9f36a7e45 Merge pull request #2 from jbarrancos/rainbird
Limited to switch on/off
2017-10-06 15:38:37 +02:00
J.J.Barrancos
f036bf9353 Limited to switch on/off
Limited to switch on/off
Lowered loglevel
2017-10-06 15:22:22 +02:00
Daniel Perna
f4679cc870 Upgrade pyhomematic, add path setting and HM-CC-VG-1 support (#9707)
* Bump pyhomematic, add path setting, HM-CC-VG-1 support

* Added requirement
2017-10-06 11:09:50 +02:00
Marcelo Moreira de Mello
7b116b0207 Updating helper's icon_for_battery_level location (#9594) 2017-10-06 09:17:18 +03:00
Paulus Schoutsen
ffb19381f1 Deprecate Python 3.4 support (#9684)
* Deprecate Python 3.4 support

* Update text
2017-10-05 21:47:51 -07:00
Paulus Schoutsen
1525cbfb93 Fix coap commit (#9712) 2017-10-05 21:12:49 -07:00
happyleavesaoc
b83059c828 move icon battery function from util to helpers (#9708) 2017-10-05 20:55:19 -07:00
Florian Klien
c7226ec28f fixed duplicate words (#9705) 2017-10-05 21:55:09 +02:00
jbarrancos
c95c8a04ef Merge pull request #1 from home-assistant/dev
Update Fork
2017-09-27 16:17:28 +02:00
J.J.Barrancos
d2d28fd419 Moved all code into the switch component
Per request moved all the code inside the switch
2017-08-30 16:11:40 +02:00
J.J.Barrancos
67007aed40 Updated requirements_all.txt 2017-08-29 14:08:55 +02:00
J.J.Barrancos
df1c3dfb67 Hound issue "whitespace" 2017-08-29 13:47:47 +02:00
J.J.Barrancos
689484216d Using latest module and fixed state issue
- pyrainbird 0.0.9 allows the override (if ever needed) connection retry/sleep
- Forces state towards the Entity when switching the switches. Gives better UI experience.
2017-08-29 13:45:18 +02:00
J.J.Barrancos
51c6029fe5 Fixed issue with missing key 2017-08-28 21:07:40 +02:00
J.J.Barrancos
0eee544d17 Changed component to use entity and switch
Changed component to use entity and switch.
2017-08-28 17:57:45 +02:00
J.J.Barrancos
21cca21124 Recommit corrected
Recommit corrected
2017-08-25 17:04:38 +02:00
J.J.Barrancos
f837451633 St*pid mistake.. wrong place 2017-08-25 16:59:37 +02:00
J.J.Barrancos
cce4a569e4 Changed doc and added default schema 2017-08-25 16:06:49 +02:00
J.J.Barrancos
4feea9d7ec pydocstyle fixes
pydocstyle fixes
2017-08-25 13:26:38 +02:00
J.J.Barrancos
7aff588bf0 pylint, coverage and requirement fix
pylint, coverage and requirement fix
2017-08-25 12:01:32 +02:00
jbarrancos
41a046a69d Typo 2017-08-25 00:08:40 +02:00
jbarrancos
e548bd5312 PIP8 Fixes
PIP8 Fixes
2017-08-24 22:49:54 +02:00
jbarrancos
88098283c7 Markup fixes
Markup fixes
2017-08-24 22:40:45 +02:00
jbarrancos
6c6ed29329 Fixed some markup and removed not yet needed class
Fixed issues during hound run (markup) and a class which is obsolete
2017-08-24 22:35:30 +02:00
jbarrancos
c4f4e492e5 Removed some whitelines
Removed some whitelines
2017-08-24 18:41:25 +02:00
jbarrancos
c286e2c434 Rainbird WiFi LNK Irrigation Implementation
This is a component which adds support for the Rainbird WiFi LNK Irragation system.
2017-08-24 18:30:36 +02:00
684 changed files with 24704 additions and 11781 deletions

View File

@@ -71,6 +71,9 @@ omit =
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
homeassistant/components/gc100.py
homeassistant/components/*/gc100.py
homeassistant/components/google.py
homeassistant/components/*/google.py
@@ -107,6 +110,9 @@ omit =
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
homeassistant/components/linode.py
homeassistant/components/*/linode.py
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
@@ -170,6 +176,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/skybell.py
homeassistant/components/*/skybell.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
@@ -187,6 +196,9 @@ omit =
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/toon.py
homeassistant/components/*/toon.py
homeassistant/components/tradfri.py
homeassistant/components/*/tradfri.py
@@ -217,7 +229,7 @@ omit =
homeassistant/components/wemo.py
homeassistant/components/*/wemo.py
homeassistant/components/wink.py
homeassistant/components/wink/*
homeassistant/components/*/wink.py
homeassistant/components/xiaomi_aqara.py
@@ -266,7 +278,10 @@ omit =
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/onvif.py
homeassistant/components/camera/ring.py
homeassistant/components/camera/synology.py
homeassistant/components/camera/yi.py
homeassistant/components/climate/ephember.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/flexit.py
homeassistant/components/climate/heatmiser.py
@@ -294,6 +309,7 @@ omit =
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/hitron_coda.py
homeassistant/components/device_tracker/huawei_router.py
homeassistant/components/device_tracker/icloud.py
homeassistant/components/device_tracker/keenetic_ndms2.py
@@ -310,6 +326,7 @@ omit =
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tado.py
homeassistant/components/device_tracker/tile.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
@@ -317,6 +334,7 @@ omit =
homeassistant/components/emoncms_history.py
homeassistant/components/emulated_hue/upnp.py
homeassistant/components/fan/mqtt.py
homeassistant/components/fan/xiaomi_miio.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
homeassistant/components/ifttt.py
@@ -404,8 +422,9 @@ omit =
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clickatell.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/clicksendaudio.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
@@ -427,6 +446,7 @@ omit =
homeassistant/components/notify/pushover.py
homeassistant/components/notify/pushsafer.py
homeassistant/components/notify/rest.py
homeassistant/components/notify/rocketchat.py
homeassistant/components/notify/sendgrid.py
homeassistant/components/notify/simplepush.py
homeassistant/components/notify/slack.py
@@ -436,8 +456,10 @@ omit =
homeassistant/components/notify/telstra.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/notify/yessssms.py
homeassistant/components/nuimo_controller.py
homeassistant/components/prometheus.py
homeassistant/components/remember_the_milk/__init__.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/scene/hunterdouglas_powerview.py
@@ -454,7 +476,6 @@ omit =
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/citybikes.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cert_expiry.py
homeassistant/components/sensor/comed_hourly_pricing.py
homeassistant/components/sensor/cpuspeed.py
@@ -462,6 +483,7 @@ omit =
homeassistant/components/sensor/cups.py
homeassistant/components/sensor/currencylayer.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deluge.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dnsip.py
@@ -489,17 +511,19 @@ omit =
homeassistant/components/sensor/gpsd.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hddtemp.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
homeassistant/components/sensor/hydroquebec.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/irish_rail_transport.py
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lacrosse.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
homeassistant/components/sensor/lyft.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
@@ -507,6 +531,7 @@ omit =
homeassistant/components/sensor/mopar.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/mvglive.py
homeassistant/components/sensor/nederlandse_spoorwegen.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nut.py
@@ -523,12 +548,14 @@ omit =
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/pyload.py
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/shodan.py
homeassistant/components/sensor/skybeacon.py
@@ -542,6 +569,7 @@ omit =
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/synologydsm.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/sytadin.py
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
@@ -549,12 +577,15 @@ omit =
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/travisci.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/ups.py
homeassistant/components/sensor/vasttrafik.py
homeassistant/components/sensor/viaggiatreno.py
homeassistant/components/sensor/waqi.py
homeassistant/components/sensor/whois.py
homeassistant/components/sensor/worldtidesinfo.py
homeassistant/components/sensor/worxlandroid.py
homeassistant/components/sensor/xbox_live.py
@@ -566,6 +597,7 @@ omit =
homeassistant/components/switch/anel_pwrctrl.py
homeassistant/components/switch/arest.py
homeassistant/components/switch/broadlink.py
homeassistant/components/switch/deluge.py
homeassistant/components/switch/digitalloggers.py
homeassistant/components/switch/dlink.py
homeassistant/components/switch/edimax.py
@@ -578,16 +610,19 @@ omit =
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pilight.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainbird.py
homeassistant/components/switch/rainmachine.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/snmp.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/telnet.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/wake_on_lan.py
homeassistant/components/switch/xiaomi_miio.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py
homeassistant/components/tts/amazon_polly.py
homeassistant/components/tts/microsoft.py
homeassistant/components/tts/picotts.py
homeassistant/components/vacuum/roomba.py
homeassistant/components/weather/bom.py

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "homeassistant/components/frontend/www_static/home-assistant-polymer"]
path = homeassistant/components/frontend/www_static/home-assistant-polymer
url = https://github.com/home-assistant/home-assistant-polymer.git

View File

@@ -29,6 +29,9 @@ homeassistant/components/weblink.py @home-assistant/core
homeassistant/components/websocket_api.py @home-assistant/core
homeassistant/components/zone.py @home-assistant/core
# To monitor non-pypi additions
requirements_all.txt @andrey-git
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
@@ -36,10 +39,37 @@ homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave
# Indiviudal components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
homeassistant/components/climate/eq3btsmart.py @rytilahti
homeassistant/components/climate/sensibo.py @andrey-git
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/media_player/kodi.py @armills
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen
homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/rainmachine.py @bachya
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/climate/eq3btsmart.py @rytilahti
homeassistant/components/*/xiaomi_miio.py @rytilahti
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/velux.py @Julius2342
homeassistant/components/*/velux.py @Julius2342
homeassistant/components/knx.py @Julius2342
homeassistant/components/*/knx.py @Julius2342
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/*/tradfri.py @ggravlingen
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi

View File

@@ -3,7 +3,7 @@
# This way, the development image and the production image are kept in sync.
FROM python:3.6
MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
# Uncomment any of the following lines to disable the installation.
#ENV INSTALL_TELLSTICK no
@@ -11,10 +11,8 @@ MAINTAINER Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>
#ENV INSTALL_FFMPEG no
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_COAP no
#ENV INSTALL_SSOCR no
VOLUME /config
RUN mkdir -p /usr/src/app
@@ -26,10 +24,10 @@ RUN virtualization/Docker/setup_docker_prereqs
# Install hass component dependencies
COPY requirements_all.txt requirements_all.txt
# Uninstall enum34 because some depenndecies install it but breaks Python 3.4+.
# Uninstall enum34 because some dependencies install it but breaks Python 3.4+.
# See PR #8103 for more info.
RUN pip3 install --no-cache-dir -r requirements_all.txt && \
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet
pip3 install --no-cache-dir mysqlclient psycopg2 uvloop cchardet cython
# Copy source
COPY . .

View File

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

BIN
docs/screenshot-components.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -19,15 +19,13 @@
#
import sys
import os
from os.path import relpath
import inspect
from homeassistant.const import (__version__, __short_version__, PROJECT_NAME,
PROJECT_LONG_DESCRIPTION,
PROJECT_COPYRIGHT, PROJECT_AUTHOR,
PROJECT_GITHUB_USERNAME,
PROJECT_GITHUB_REPOSITORY,
GITHUB_PATH, GITHUB_URL)
from homeassistant.const import __version__, __short_version__
from setup import (
PROJECT_NAME, PROJECT_LONG_DESCRIPTION, PROJECT_COPYRIGHT, PROJECT_AUTHOR,
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY, GITHUB_PATH,
GITHUB_URL)
sys.path.insert(0, os.path.abspath('_ext'))
sys.path.insert(0, os.path.abspath('../homeassistant'))
@@ -87,9 +85,7 @@ edit_on_github_src_path = 'docs/source/'
def linkcode_resolve(domain, info):
"""
Determine the URL corresponding to Python object
"""
"""Determine the URL corresponding to Python object."""
if domain != 'py':
return None
modname = info['module']

View File

@@ -11,13 +11,11 @@ from typing import Any, Optional, Dict
import voluptuous as vol
import homeassistant.components as core_components
from homeassistant import (
core, config as conf_util, loader, components as core_components)
from homeassistant.components import persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
import homeassistant.loader as loader
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, get_user_site
from homeassistant.util.yaml import clear_secret_cache
@@ -32,8 +30,8 @@ ERROR_LOG_FILENAME = 'home-assistant.log'
DATA_LOGGING = 'logging'
FIRST_INIT_COMPONENT = set((
'recorder', 'mqtt', 'mqtt_eventstream', 'logger', 'introduction',
'frontend', 'history'))
'system_log', 'recorder', 'mqtt', 'mqtt_eventstream', 'logger',
'introduction', 'frontend', 'history'))
def from_config_dict(config: Dict[str, Any],
@@ -90,7 +88,7 @@ def async_from_config_dict(config: Dict[str, Any],
if sys.version_info[:2] < (3, 5):
_LOGGER.warning(
'Python 3.4 support has been deprecated and will be removed in '
'the begining of 2018. Please upgrade Python or your operating '
'the beginning of 2018. Please upgrade Python or your operating '
'system. More info: https://home-assistant.io/blog/2017/10/06/'
'deprecating-python-3.4-support/'
)

View File

@@ -10,6 +10,7 @@ Component design guidelines:
import asyncio
import itertools as it
import logging
import os
import homeassistant.core as ha
import homeassistant.config as conf_util
@@ -110,6 +111,11 @@ def async_reload_core_config(hass):
@asyncio.coroutine
def async_setup(hass, config):
"""Set up general services related to Home Assistant."""
descriptions = yield from hass.async_add_job(
conf_util.load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml')
)
@asyncio.coroutine
def async_handle_turn_service(service):
"""Handle calls to homeassistant.turn_on/off."""
@@ -149,11 +155,14 @@ def async_setup(hass, config):
yield from asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service,
descriptions[ha.DOMAIN][SERVICE_TURN_OFF])
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service,
descriptions[ha.DOMAIN][SERVICE_TURN_ON])
hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service,
descriptions[ha.DOMAIN][SERVICE_TOGGLE])
@asyncio.coroutine
def async_handle_core_service(call):
@@ -178,11 +187,14 @@ def async_setup(hass, config):
hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service,
descriptions[ha.DOMAIN][SERVICE_HOMEASSISTANT_STOP])
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service,
descriptions[ha.DOMAIN][SERVICE_HOMEASSISTANT_RESTART])
hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service,
descriptions[ha.DOMAIN][SERVICE_CHECK_CONFIG])
@asyncio.coroutine
def async_handle_reload_config(call):
@@ -197,6 +209,7 @@ def async_setup(hass, config):
hass, conf.get(ha.DOMAIN) or {})
hass.services.async_register(
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config)
ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config,
descriptions[ha.DOMAIN][SERVICE_RELOAD_CORE_CONFIG])
return True

View File

@@ -10,24 +10,23 @@ from functools import partial
from os import path
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import discovery
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME,
ATTR_ENTITY_ID, CONF_USERNAME, CONF_PASSWORD,
CONF_EXCLUDE, CONF_NAME,
EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_START)
REQUIREMENTS = ['abodepy==0.11.9']
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME, ATTR_ENTITY_ID, CONF_USERNAME,
CONF_PASSWORD, CONF_EXCLUDE, CONF_NAME, CONF_LIGHTS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from requests.exceptions import HTTPError, ConnectTimeout
REQUIREMENTS = ['abodepy==0.12.2']
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by goabode.com"
CONF_LIGHTS = "lights"
CONF_POLLING = "polling"
CONF_POLLING = 'polling'
DOMAIN = 'abode'
@@ -93,10 +92,9 @@ class AbodeSystem(object):
def __init__(self, username, password, name, polling, exclude, lights):
"""Initialize the system."""
import abodepy
self.abode = abodepy.Abode(username, password,
auto_login=True,
get_devices=True,
get_automations=True)
self.abode = abodepy.Abode(
username, password, auto_login=True, get_devices=True,
get_automations=True)
self.name = name
self.polling = polling
self.exclude = exclude
@@ -210,7 +208,7 @@ def setup_hass_services(hass):
def setup_hass_events(hass):
"""Home assistant start and stop callbacks."""
"""Home Assistant start and stop callbacks."""
def startup(event):
"""Listen for push events."""
hass.data[DOMAIN].abode.events.start()

View File

@@ -124,20 +124,13 @@ def async_setup(hass, config):
method = "async_{}".format(SERVICE_TO_METHOD[service.service])
update_tasks = []
for alarm in target_alarms:
yield from getattr(alarm, method)(code)
update_tasks = []
for alarm in target_alarms:
if not alarm.should_poll:
continue
update_coro = hass.async_add_job(
alarm.async_update_ha_state(True))
if hasattr(alarm, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(alarm.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)

View File

@@ -0,0 +1,121 @@
"""
Support for Arlo Alarm Control Panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.arlo/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.alarm_control_panel import (
AlarmControlPanel, PLATFORM_SCHEMA)
from homeassistant.components.arlo import (DATA_ARLO, CONF_ATTRIBUTION)
from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
_LOGGER = logging.getLogger(__name__)
ARMED = 'armed'
CONF_HOME_MODE_NAME = 'home_mode_name'
DEPENDENCIES = ['arlo']
DISARMED = 'disarmed'
ICON = 'mdi:security'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Arlo Alarm Control Panels."""
data = hass.data[DATA_ARLO]
if not data.base_stations:
return
home_mode_name = config.get(CONF_HOME_MODE_NAME)
base_stations = []
for base_station in data.base_stations:
base_stations.append(ArloBaseStation(base_station, home_mode_name))
async_add_devices(base_stations, True)
class ArloBaseStation(AlarmControlPanel):
"""Representation of an Arlo Alarm Control Panel."""
def __init__(self, data, home_mode_name):
"""Initialize the alarm control panel."""
self._base_station = data
self._home_mode_name = home_mode_name
self._state = None
@property
def icon(self):
"""Return icon."""
return ICON
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Update the state of the device."""
# PyArlo sometimes returns None for mode. So retry 3 times before
# returning None.
num_retries = 3
i = 0
while i < num_retries:
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
return
i += 1
self._state = None
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""
self._base_station.mode = DISARMED
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
self._base_station.mode = ARMED
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
"""Send arm home command. Uses custom mode."""
self._base_station.mode = self._home_mode_name
@property
def name(self):
"""Return the name of the base station."""
return self._base_station.name
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'device_id': self._base_station.device_id
}
def _get_state_from_mode(self, mode):
"""Convert Arlo mode to Home Assistant state."""
if mode == ARMED:
return STATE_ALARM_ARMED_AWAY
elif mode == DISARMED:
return STATE_ALARM_DISARMED
elif mode == self._home_mode_name:
return STATE_ALARM_ARMED_HOME
return None

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_NAME, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
REQUIREMENTS = ['pythonegardia==1.0.21']
REQUIREMENTS = ['pythonegardia==1.0.22']
_LOGGER = logging.getLogger(__name__)

View File

@@ -1,65 +1,61 @@
alarm_disarm:
description: Send the alarm the command for disarm
# Describes the format for available alarm control panel services
alarm_disarm:
description: Send the alarm the command for disarm.
fields:
entity_id:
description: Name of alarm control panel to disarm
description: Name of alarm control panel to disarm.
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to disarm the alarm control panel with
description: An optional code to disarm the alarm control panel with.
example: 1234
alarm_arm_home:
description: Send the alarm the command for arm home
description: Send the alarm the command for arm home.
fields:
entity_id:
description: Name of alarm control panel to arm home
description: Name of alarm control panel to arm home.
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm home the alarm control panel with
description: An optional code to arm home the alarm control panel with.
example: 1234
alarm_arm_away:
description: Send the alarm the command for arm away
description: Send the alarm the command for arm away.
fields:
entity_id:
description: Name of alarm control panel to arm away
description: Name of alarm control panel to arm away.
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm away the alarm control panel with
description: An optional code to arm away the alarm control panel with.
example: 1234
alarm_arm_night:
description: Send the alarm the command for arm night
description: Send the alarm the command for arm night.
fields:
entity_id:
description: Name of alarm control panel to arm night
description: Name of alarm control panel to arm night.
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to arm night the alarm control panel with
description: An optional code to arm night the alarm control panel with.
example: 1234
alarm_trigger:
description: Send the alarm the command for trigger
description: Send the alarm the command for trigger.
fields:
entity_id:
description: Name of alarm control panel to trigger
description: Name of alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
code:
description: An optional code to trigger the alarm control panel with
description: An optional code to trigger the alarm control panel with.
example: 1234
envisalink_alarm_keypress:
description: Send custom keypresses to the alarm
description: Send custom keypresses to the alarm.
fields:
entity_id:
description: Name of the alarm control panel to trigger
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
keypress:
description: 'String to send to the alarm panel (1-6 characters)'
description: 'String to send to the alarm panel (1-6 characters).'
example: '*71'

View File

@@ -34,10 +34,8 @@ def async_setup_platform(hass, config, async_add_devices,
discovery_info[ATTR_DISCOVER_AREAS] is None):
return
devices = [SpcAlarm(hass=hass,
area_id=area['id'],
name=area['name'],
state=_get_alarm_state(area['mode']))
api = hass.data[DATA_API]
devices = [SpcAlarm(api, area)
for area in discovery_info[ATTR_DISCOVER_AREAS]]
async_add_devices(devices)
@@ -46,21 +44,29 @@ def async_setup_platform(hass, config, async_add_devices,
class SpcAlarm(alarm.AlarmControlPanel):
"""Represents the SPC alarm panel."""
def __init__(self, hass, area_id, name, state):
def __init__(self, api, area):
"""Initialize the SPC alarm panel."""
self._hass = hass
self._area_id = area_id
self._name = name
self._state = state
self._api = hass.data[DATA_API]
hass.data[DATA_REGISTRY].register_alarm_device(area_id, self)
self._area_id = area['id']
self._name = area['name']
self._state = _get_alarm_state(area['mode'])
if self._state == STATE_ALARM_DISARMED:
self._changed_by = area.get('last_unset_user_name', 'unknown')
else:
self._changed_by = area.get('last_set_user_name', 'unknown')
self._api = api
@asyncio.coroutine
def async_update_from_spc(self, state):
def async_added_to_hass(self):
"""Calbback for init handlers."""
self.hass.data[DATA_REGISTRY].register_alarm_device(
self._area_id, self)
@asyncio.coroutine
def async_update_from_spc(self, state, extra):
"""Update the alarm panel with a new state."""
self._state = state
yield from self.async_update_ha_state()
self._changed_by = extra.get('changed_by', 'unknown')
self.async_schedule_update_ha_state()
@property
def should_poll(self):
@@ -72,6 +78,11 @@ class SpcAlarm(alarm.AlarmControlPanel):
"""Return the name of the device."""
return self._name
@property
def changed_by(self):
"""Return the user the last change was triggered by."""
return self._changed_by
@property
def state(self):
"""Return the state of the device."""

View File

@@ -16,7 +16,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME)
REQUIREMENTS = ['total_connect_client==0.11']
REQUIREMENTS = ['total_connect_client==0.13']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,4 +15,6 @@ ATTR_STREAM_URL = 'streamUrl'
ATTR_MAIN_TEXT = 'mainText'
ATTR_REDIRECTION_URL = 'redirectionURL'
SYN_RESOLUTION_MATCH = 'ER_SUCCESS_MATCH'
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z'

View File

@@ -3,6 +3,7 @@ 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 asyncio
import enum
@@ -13,7 +14,7 @@ from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import intent
from homeassistant.components import http
from .const import DOMAIN
from .const import DOMAIN, SYN_RESOLUTION_MATCH
INTENTS_API_ENDPOINT = '/api/alexa'
@@ -123,6 +124,43 @@ class AlexaIntentsView(http.HomeAssistantView):
return self.json(alexa_response)
def resolve_slot_synonyms(key, request):
"""Check slot request for synonym resolutions."""
# Default to the spoken slot value if more than one or none are found. For
# reference to the request object structure, see the Alexa docs:
# https://tinyurl.com/ybvm7jhs
resolved_value = request['value']
if ('resolutions' in request and
'resolutionsPerAuthority' in request['resolutions'] and
len(request['resolutions']['resolutionsPerAuthority']) >= 1):
# Extract all of the possible values from each authority with a
# successful match
possible_values = []
for entry in request['resolutions']['resolutionsPerAuthority']:
if entry['status']['code'] != SYN_RESOLUTION_MATCH:
continue
possible_values.extend([item['value']['name']
for item
in entry['values']])
# If there is only one match use the resolved value, otherwise the
# resolution cannot be determined, so use the spoken slot value
if len(possible_values) == 1:
resolved_value = possible_values[0]
else:
_LOGGER.debug(
'Found multiple synonym resolutions for slot value: {%s: %s}',
key,
request['value']
)
return resolved_value
class AlexaResponse(object):
"""Help generating the response for Alexa."""
@@ -135,12 +173,17 @@ class AlexaResponse(object):
self.session_attributes = {}
self.should_end_session = True
self.variables = {}
# Intent is None if request was a LaunchRequest or SessionEndedRequest
if intent_info is not None:
for key, value in intent_info.get('slots', {}).items():
if 'value' in value:
underscored_key = key.replace('.', '_')
self.variables[underscored_key] = value['value']
# Only include slots with values
if 'value' not in value:
continue
_key = key.replace('.', '_')
self.variables[_key] = resolve_slot_synonyms(key, value)
def add_card(self, card_type, title, content):
"""Add a card to the response."""

View File

@@ -1,177 +1,643 @@
"""Support for alexa Smart Home Skill API."""
import asyncio
from collections import namedtuple
import logging
import math
from uuid import uuid4
import homeassistant.core as ha
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
from homeassistant.components import switch, light
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET)
from homeassistant.components import (
alert, automation, cover, fan, group, input_boolean, light, lock,
media_player, scene, script, switch)
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
ATTR_HEADER = 'header'
ATTR_NAME = 'name'
ATTR_NAMESPACE = 'namespace'
ATTR_MESSAGE_ID = 'messageId'
ATTR_PAYLOAD = 'payload'
ATTR_PAYLOAD_VERSION = 'payloadVersion'
API_DIRECTIVE = 'directive'
API_ENDPOINT = 'endpoint'
API_EVENT = 'event'
API_HEADER = 'header'
API_PAYLOAD = 'payload'
ATTR_ALEXA_DESCRIPTION = 'alexa_description'
ATTR_ALEXA_DISPLAY_CATEGORIES = 'alexa_display_categories'
ATTR_ALEXA_HIDDEN = 'alexa_hidden'
ATTR_ALEXA_NAME = 'alexa_name'
MAPPING_COMPONENT = {
switch.DOMAIN: ['SWITCH', ('turnOff', 'turnOn'), None],
light.DOMAIN: [
'LIGHT', ('turnOff', 'turnOn'), {
light.SUPPORT_BRIGHTNESS: 'setPercentage'
alert.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
automation.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
cover.DOMAIN: [
'DOOR', ('Alexa.PowerController',), {
cover.SUPPORT_SET_POSITION: 'Alexa.PercentageController',
}
],
fan.DOMAIN: [
'OTHER', ('Alexa.PowerController',), {
fan.SUPPORT_SET_SPEED: 'Alexa.PercentageController',
}
],
group.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
input_boolean.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
light.DOMAIN: [
'LIGHT', ('Alexa.PowerController',), {
light.SUPPORT_BRIGHTNESS: 'Alexa.BrightnessController',
light.SUPPORT_RGB_COLOR: 'Alexa.ColorController',
light.SUPPORT_XY_COLOR: 'Alexa.ColorController',
light.SUPPORT_COLOR_TEMP: 'Alexa.ColorTemperatureController',
}
],
lock.DOMAIN: ['SMARTLOCK', ('Alexa.LockController',), None],
media_player.DOMAIN: [
'TV', ('Alexa.PowerController',), {
media_player.SUPPORT_VOLUME_SET: 'Alexa.Speaker',
media_player.SUPPORT_PLAY: 'Alexa.PlaybackController',
media_player.SUPPORT_PAUSE: 'Alexa.PlaybackController',
media_player.SUPPORT_STOP: 'Alexa.PlaybackController',
media_player.SUPPORT_NEXT_TRACK: 'Alexa.PlaybackController',
media_player.SUPPORT_PREVIOUS_TRACK: 'Alexa.PlaybackController',
}
],
scene.DOMAIN: ['ACTIVITY_TRIGGER', ('Alexa.SceneController',), None],
script.DOMAIN: ['OTHER', ('Alexa.PowerController',), None],
switch.DOMAIN: ['SWITCH', ('Alexa.PowerController',), None],
}
Config = namedtuple('AlexaConfig', 'filter')
@asyncio.coroutine
def async_handle_message(hass, message):
def async_handle_message(hass, config, message):
"""Handle incoming API messages."""
assert int(message[ATTR_HEADER][ATTR_PAYLOAD_VERSION]) == 2
assert message[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3'
# Read head data
message = message[API_DIRECTIVE]
namespace = message[API_HEADER]['namespace']
name = message[API_HEADER]['name']
# Do we support this API request?
funct_ref = HANDLERS.get(message[ATTR_HEADER][ATTR_NAME])
funct_ref = HANDLERS.get((namespace, name))
if not funct_ref:
_LOGGER.warning(
"Unsupported API request %s", message[ATTR_HEADER][ATTR_NAME])
"Unsupported API request %s/%s", namespace, name)
return api_error(message)
return (yield from funct_ref(hass, message))
return (yield from funct_ref(hass, config, message))
def api_message(name, namespace, payload=None):
def api_message(request, name='Response', namespace='Alexa', payload=None):
"""Create a API formatted response message.
Async friendly.
"""
payload = payload or {}
return {
ATTR_HEADER: {
ATTR_MESSAGE_ID: str(uuid4()),
ATTR_NAME: name,
ATTR_NAMESPACE: namespace,
ATTR_PAYLOAD_VERSION: '2',
},
ATTR_PAYLOAD: payload,
response = {
API_EVENT: {
API_HEADER: {
'namespace': namespace,
'name': name,
'messageId': str(uuid4()),
'payloadVersion': '3',
},
API_PAYLOAD: payload,
}
}
# If a correlation token exsits, add it to header / Need by Async requests
token = request[API_HEADER].get('correlationToken')
if token:
response[API_EVENT][API_HEADER]['correlationToken'] = token
def api_error(request, exc='DriverInternalError'):
# Extend event with endpoint object / Need by Async requests
if API_ENDPOINT in request:
response[API_EVENT][API_ENDPOINT] = request[API_ENDPOINT].copy()
return response
def api_error(request, error_type='INTERNAL_ERROR', error_message=""):
"""Create a API formatted error response.
Async friendly.
"""
return api_message(exc, request[ATTR_HEADER][ATTR_NAMESPACE])
payload = {
'type': error_type,
'message': error_message,
}
return api_message(request, name='ErrorResponse', payload=payload)
@HANDLERS.register('DiscoverAppliancesRequest')
@HANDLERS.register(('Alexa.Discovery', 'Discover'))
@asyncio.coroutine
def async_api_discovery(hass, request):
def async_api_discovery(hass, config, request):
"""Create a API formatted discovery response.
Async friendly.
"""
discovered_appliances = []
discovery_endpoints = []
for entity in hass.states.async_all():
if not config.filter(entity.entity_id):
_LOGGER.debug("Not exposing %s because filtered by config",
entity.entity_id)
continue
if entity.attributes.get(ATTR_ALEXA_HIDDEN, False):
_LOGGER.debug("Not exposing %s because alexa_hidden is true",
entity.entity_id)
continue
class_data = MAPPING_COMPONENT.get(entity.domain)
if not class_data:
continue
appliance = {
'actions': [],
'applianceTypes': [class_data[0]],
friendly_name = entity.attributes.get(ATTR_ALEXA_NAME, entity.name)
description = entity.attributes.get(ATTR_ALEXA_DESCRIPTION,
entity.entity_id)
# Required description as per Amazon Scene docs
if entity.domain == scene.DOMAIN:
scene_fmt = '%s (Scene connected via Home Assistant)'
description = scene_fmt.format(description)
cat_key = ATTR_ALEXA_DISPLAY_CATEGORIES
display_categories = entity.attributes.get(cat_key, class_data[0])
endpoint = {
'displayCategories': [display_categories],
'additionalApplianceDetails': {},
'applianceId': entity.entity_id.replace('.', '#'),
'friendlyDescription': '',
'friendlyName': entity.name,
'isReachable': True,
'manufacturerName': 'Unknown',
'modelName': 'Unknown',
'version': 'Unknown',
'endpointId': entity.entity_id.replace('.', '#'),
'friendlyName': friendly_name,
'description': description,
'manufacturerName': 'Home Assistant',
}
actions = set()
# static actions
if class_data[1]:
appliance['actions'].extend(list(class_data[1]))
actions |= set(class_data[1])
# dynamic actions
if class_data[2]:
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
for feature, action_name in class_data[2].items():
if feature & supported > 0:
appliance['actions'].append(action_name)
actions.add(action_name)
discovered_appliances.append(appliance)
# Write action into capabilities
capabilities = []
for action in actions:
capabilities.append({
'type': 'AlexaInterface',
'interface': action,
'version': 3,
})
endpoint['capabilities'] = capabilities
discovery_endpoints.append(endpoint)
return api_message(
'DiscoverAppliancesResponse', 'Alexa.ConnectedHome.Discovery',
payload={'discoveredAppliances': discovered_appliances})
request, name='Discover.Response', namespace='Alexa.Discovery',
payload={'endpoints': discovery_endpoints})
def extract_entity(funct):
"""Decorator for extract entity object from request."""
@asyncio.coroutine
def async_api_entity_wrapper(hass, request):
def async_api_entity_wrapper(hass, config, request):
"""Process a turn on request."""
entity_id = \
request[ATTR_PAYLOAD]['appliance']['applianceId'].replace('#', '.')
entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.')
# extract state object
entity = hass.states.get(entity_id)
if not entity:
_LOGGER.error("Can't process %s for %s",
request[ATTR_HEADER][ATTR_NAME], entity_id)
return api_error(request)
request[API_HEADER]['name'], entity_id)
return api_error(request, error_type='NO_SUCH_ENDPOINT')
return (yield from funct(hass, request, entity))
return (yield from funct(hass, config, request, entity))
return async_api_entity_wrapper
@HANDLERS.register('TurnOnRequest')
@HANDLERS.register(('Alexa.PowerController', 'TurnOn'))
@extract_entity
@asyncio.coroutine
def async_api_turn_on(hass, request, entity):
def async_api_turn_on(hass, config, request, entity):
"""Process a turn on request."""
domain = entity.domain
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PowerController', 'TurnOff'))
@extract_entity
@asyncio.coroutine
def async_api_turn_off(hass, config, request, entity):
"""Process a turn off request."""
domain = entity.domain
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness'))
@extract_entity
@asyncio.coroutine
def async_api_set_brightness(hass, config, request, entity):
"""Process a set brightness request."""
brightness = int(request[API_PAYLOAD]['brightness'])
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness,
}, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_brightness(hass, config, request, entity):
"""Process a adjust brightness request."""
brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
# read current state
try:
current = math.floor(
int(entity.attributes.get(light.ATTR_BRIGHTNESS)) / 255 * 100)
except ZeroDivisionError:
current = 0
# set brightness
brightness = max(0, brightness_delta + current)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness,
}, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.ColorController', 'SetColor'))
@extract_entity
@asyncio.coroutine
def async_api_set_color(hass, config, request, entity):
"""Process a set color request."""
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES)
rgb = color_util.color_hsb_to_RGB(
float(request[API_PAYLOAD]['color']['hue']),
float(request[API_PAYLOAD]['color']['saturation']),
float(request[API_PAYLOAD]['color']['brightness'])
)
if supported & light.SUPPORT_RGB_COLOR > 0:
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_RGB_COLOR: rgb,
}, blocking=True)
else:
xyz = color_util.color_RGB_to_xy(*rgb)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_XY_COLOR: (xyz[0], xyz[1]),
light.ATTR_BRIGHTNESS: xyz[2],
}, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature'))
@extract_entity
@asyncio.coroutine
def async_api_set_color_temperature(hass, config, request, entity):
"""Process a set color temperature request."""
kelvin = int(request[API_PAYLOAD]['colorTemperatureInKelvin'])
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_KELVIN: kelvin,
}, blocking=True)
return api_message(request)
@HANDLERS.register(
('Alexa.ColorTemperatureController', 'DecreaseColorTemperature'))
@extract_entity
@asyncio.coroutine
def async_api_decrease_color_temp(hass, config, request, entity):
"""Process a decrease color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS))
value = min(max_mireds, current + 50)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value,
}, blocking=True)
return api_message(request)
@HANDLERS.register(
('Alexa.ColorTemperatureController', 'IncreaseColorTemperature'))
@extract_entity
@asyncio.coroutine
def async_api_increase_color_temp(hass, config, request, entity):
"""Process a increase color temperature request."""
current = int(entity.attributes.get(light.ATTR_COLOR_TEMP))
min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS))
value = max(min_mireds, current - 50)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value,
}, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.SceneController', 'Activate'))
@extract_entity
@asyncio.coroutine
def async_api_activate(hass, config, request, entity):
"""Process a activate request."""
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message('TurnOnConfirmation', 'Alexa.ConnectedHome.Control')
return api_message(request)
@HANDLERS.register('TurnOffRequest')
@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage'))
@extract_entity
@asyncio.coroutine
def async_api_turn_off(hass, request, entity):
"""Process a turn off request."""
yield from hass.services.async_call(entity.domain, SERVICE_TURN_OFF, {
def async_api_set_percentage(hass, config, request, entity):
"""Process a set percentage request."""
percentage = int(request[API_PAYLOAD]['percentage'])
service = None
data = {ATTR_ENTITY_ID: entity.entity_id}
if entity.domain == fan.DOMAIN:
service = fan.SERVICE_SET_SPEED
speed = "off"
if percentage <= 33:
speed = "low"
elif percentage <= 66:
speed = "medium"
elif percentage <= 100:
speed = "high"
data[fan.ATTR_SPEED] = speed
elif entity.domain == cover.DOMAIN:
service = SERVICE_SET_COVER_POSITION
data[cover.ATTR_POSITION] = percentage
yield from hass.services.async_call(entity.domain, service,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_percentage(hass, config, request, entity):
"""Process a adjust percentage request."""
percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
service = None
data = {ATTR_ENTITY_ID: entity.entity_id}
if entity.domain == fan.DOMAIN:
service = fan.SERVICE_SET_SPEED
speed = entity.attributes.get(fan.ATTR_SPEED)
if speed == "off":
current = 0
elif speed == "low":
current = 33
elif speed == "medium":
current = 66
elif speed == "high":
current = 100
# set percentage
percentage = max(0, percentage_delta + current)
speed = "off"
if percentage <= 33:
speed = "low"
elif percentage <= 66:
speed = "medium"
elif percentage <= 100:
speed = "high"
data[fan.ATTR_SPEED] = speed
elif entity.domain == cover.DOMAIN:
service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION)
data[cover.ATTR_POSITION] = max(0, percentage_delta + current)
yield from hass.services.async_call(entity.domain, service,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.LockController', 'Lock'))
@extract_entity
@asyncio.coroutine
def async_api_lock(hass, config, request, entity):
"""Process a lock request."""
yield from hass.services.async_call(entity.domain, SERVICE_LOCK, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message('TurnOffConfirmation', 'Alexa.ConnectedHome.Control')
return api_message(request)
@HANDLERS.register('SetPercentageRequest')
# Not supported by Alexa yet
@HANDLERS.register(('Alexa.LockController', 'Unlock'))
@extract_entity
@asyncio.coroutine
def async_api_set_percentage(hass, request, entity):
"""Process a set percentage request."""
if entity.domain == light.DOMAIN:
brightness = request[ATTR_PAYLOAD]['percentageState']['value']
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS: brightness,
}, blocking=True)
else:
return api_error(request)
def async_api_unlock(hass, config, request, entity):
"""Process a unlock request."""
yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
return api_message(
'SetPercentageConfirmation', 'Alexa.ConnectedHome.Control')
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'SetVolume'))
@extract_entity
@asyncio.coroutine
def async_api_set_volume(hass, config, request, entity):
"""Process a set volume request."""
volume = round(float(request[API_PAYLOAD]['volume'] / 100), 2)
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
yield from hass.services.async_call(entity.domain, SERVICE_VOLUME_SET,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_volume(hass, config, request, entity):
"""Process a adjust volume request."""
volume_delta = int(request[API_PAYLOAD]['volume'])
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
# read current state
try:
current = math.floor(int(current_level * 100))
except ZeroDivisionError:
current = 0
volume = float(max(0, volume_delta + current) / 100)
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
yield from hass.services.async_call(entity.domain,
media_player.SERVICE_VOLUME_SET,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'SetMute'))
@extract_entity
@asyncio.coroutine
def async_api_set_mute(hass, config, request, entity):
"""Process a set mute request."""
mute = bool(request[API_PAYLOAD]['mute'])
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
}
yield from hass.services.async_call(entity.domain,
media_player.SERVICE_VOLUME_MUTE,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Play'))
@extract_entity
@asyncio.coroutine
def async_api_play(hass, config, request, entity):
"""Process a play request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain, SERVICE_MEDIA_PLAY,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Pause'))
@extract_entity
@asyncio.coroutine
def async_api_pause(hass, config, request, entity):
"""Process a pause request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain, SERVICE_MEDIA_PAUSE,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Stop'))
@extract_entity
@asyncio.coroutine
def async_api_stop(hass, config, request, entity):
"""Process a stop request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain, SERVICE_MEDIA_STOP,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Next'))
@extract_entity
@asyncio.coroutine
def async_api_next(hass, config, request, entity):
"""Process a next request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain,
SERVICE_MEDIA_NEXT_TRACK,
data, blocking=True)
return api_message(request)
@HANDLERS.register(('Alexa.PlaybackController', 'Previous'))
@extract_entity
@asyncio.coroutine
def async_api_previous(hass, config, request, entity):
"""Process a previous request."""
data = {
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain,
SERVICE_MEDIA_PREVIOUS_TRACK,
data, blocking=True)
return api_message(request)

View File

@@ -262,7 +262,11 @@ class APIEventView(HomeAssistantView):
def post(self, request, event_type):
"""Fire events."""
body = yield from request.text()
event_data = json.loads(body) if body else None
try:
event_data = json.loads(body) if body else None
except ValueError:
return self.json_message('Event data should be valid JSON',
HTTP_BAD_REQUEST)
if event_data is not None and not isinstance(event_data, dict):
return self.json_message('Event data should be a JSON object',
@@ -309,7 +313,11 @@ class APIDomainServicesView(HomeAssistantView):
"""
hass = request.app['hass']
body = yield from request.text()
data = json.loads(body) if body else None
try:
data = json.loads(body) if body else None
except ValueError:
return self.json_message('Data should be valid JSON',
HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states:
yield from hass.services.async_call(domain, service, data, True)

View File

@@ -18,7 +18,7 @@ from homeassistant.helpers import discovery
from homeassistant.components.discovery import SERVICE_APPLE_TV
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.5']
REQUIREMENTS = ['pyatv==0.3.8']
_LOGGER = logging.getLogger(__name__)

View File

@@ -1,5 +1,5 @@
"""
This component provides basic support for Netgear Arlo IP cameras.
This component provides support for Netgear Arlo IP cameras.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arlo/
@@ -12,7 +12,7 @@ from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
REQUIREMENTS = ['pyarlo==0.0.6']
REQUIREMENTS = ['pyarlo==0.1.0']
_LOGGER = logging.getLogger(__name__)
@@ -23,7 +23,7 @@ DEFAULT_BRAND = 'Netgear Arlo'
DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Camera Setup'
NOTIFICATION_TITLE = 'Arlo Component Setup'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({

View File

@@ -29,18 +29,27 @@ TRIGGER_SCHEMA = vol.Schema({
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)
event_data_schema = vol.Schema(
config.get(CONF_EVENT_DATA),
extra=vol.ALLOW_EXTRA) if config.get(CONF_EVENT_DATA) else None
@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()):
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
if event_data_schema:
# Check that the event data matches the configured
# schema if one was provided
try:
event_data_schema(event.data)
except vol.Invalid:
# If event data doesn't match requested schema, skip event
return
hass.async_run_job(action, {
'trigger': {
'platform': 'event',
'event': event,
},
})
return hass.bus.async_listen(event_type, handle_event)

View File

@@ -38,13 +38,14 @@ def async_trigger(hass, config, action):
time_delta = config.get(CONF_FOR)
value_template = config.get(CONF_VALUE_TEMPLATE)
async_remove_track_same = None
already_triggered = False
if value_template is not None:
value_template.hass = hass
@callback
def check_numeric_state(entity, from_s, to_s):
"""Return True if they should trigger."""
"""Return True if criteria are now met."""
if to_s is None:
return False
@@ -56,51 +57,39 @@ def async_trigger(hass, config, action):
'above': above,
}
}
# If new one doesn't match, nothing to do
if not condition.async_numeric_state(
hass, to_s, below, above, value_template, variables):
return False
return True
return condition.async_numeric_state(
hass, to_s, below, above, value_template, variables)
@callback
def state_automation_listener(entity, from_s, to_s):
"""Listen for state changes and calls action."""
nonlocal async_remove_track_same
if not check_numeric_state(entity, from_s, to_s):
return
variables = {
'trigger': {
'platform': 'numeric_state',
'entity_id': entity,
'below': below,
'above': above,
'from_state': from_s,
'to_state': to_s,
}
}
# 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.async_numeric_state(
hass, from_s, below, above, value_template, variables):
return
nonlocal already_triggered, async_remove_track_same
@callback
def call_action():
"""Call action with right context."""
hass.async_run_job(action, variables)
hass.async_run_job(action, {
'trigger': {
'platform': 'numeric_state',
'entity_id': entity,
'below': below,
'above': above,
'from_state': from_s,
'to_state': to_s,
}
})
if not time_delta:
call_action()
return
matching = check_numeric_state(entity, from_s, to_s)
async_remove_track_same = async_track_same_state(
hass, True, time_delta, call_action, entity_ids=entity_id,
async_check_func=check_numeric_state)
if matching and not already_triggered:
if time_delta:
async_remove_track_same = async_track_same_state(
hass, time_delta, call_action, entity_ids=entity_id,
async_check_same_func=check_numeric_state)
else:
call_action()
already_triggered = matching
unsub = async_track_state_change(
hass, entity_id, state_automation_listener)

View File

@@ -1,6 +1,7 @@
# Describes the format for available automation services
turn_on:
description: Enable an automation.
fields:
entity_id:
description: Name of the automation to turn on.
@@ -8,7 +9,6 @@ turn_on:
turn_off:
description: Disable an automation.
fields:
entity_id:
description: Name of the automation to turn off.
@@ -16,7 +16,6 @@ turn_off:
toggle:
description: Toggle an automation.
fields:
entity_id:
description: Name of the automation to toggle on/off.
@@ -24,7 +23,6 @@ toggle:
trigger:
description: Trigger the action of an automation.
fields:
entity_id:
description: Name of the automation to trigger.

View File

@@ -65,7 +65,9 @@ def async_trigger(hass, config, action):
return
async_remove_track_same = async_track_same_state(
hass, to_s.state, time_delta, call_action, entity_ids=entity_id)
hass, time_delta, call_action,
lambda _, _2, to_state: to_state.state == to_s.state,
entity_ids=entity_id)
unsub = async_track_state_change(
hass, entity_id, state_automation_listener, from_state, to_state)

View File

@@ -11,19 +11,20 @@ import os
import voluptuous as vol
from homeassistant.components.discovery import SERVICE_AXIS
from homeassistant.config import load_yaml_config_file
from homeassistant.const import (ATTR_LOCATION, ATTR_TRIPPED,
CONF_HOST, CONF_INCLUDE, CONF_NAME,
CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME,
CONF_USERNAME, EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.discovery import SERVICE_AXIS
CONF_EVENT, CONF_HOST, CONF_INCLUDE,
CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_TRIGGER_TIME, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['axis==12']
REQUIREMENTS = ['axis==14']
_LOGGER = logging.getLogger(__name__)
@@ -87,10 +88,13 @@ def request_configuration(hass, config, name, host, serialnumber):
configurator.notify_errors(request_id,
"Functionality mandatory.")
return False
callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split()
callback_data[CONF_HOST] = host
if CONF_NAME not in callback_data:
callback_data[CONF_NAME] = name
try:
device_config = DEVICE_SCHEMA(callback_data)
except vol.Invalid:
@@ -101,7 +105,6 @@ def request_configuration(hass, config, name, host, serialnumber):
if setup_device(hass, config, device_config):
config_file = _read_config(hass)
config_file[serialnumber] = dict(device_config)
del config_file[serialnumber]['hass']
_write_config(hass, config_file)
configurator.request_done(request_id)
else:
@@ -146,10 +149,10 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config):
"""Common setup for Axis devices."""
def _shutdown(call): # pylint: disable=unused-argument
"""Stop the metadatastream on shutdown."""
"""Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items():
_LOGGER.info("Stopping metadatastream for %s.", serialnumber)
device.stop_metadatastream()
_LOGGER.info("Stopping event stream for %s.", serialnumber)
device.stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown)
@@ -162,7 +165,7 @@ def setup(hass, config):
if serialnumber not in AXIS_DEVICES:
config_file = _read_config(hass)
if serialnumber in config_file:
# Device config saved to file
# Device config previously saved to file
try:
device_config = DEVICE_SCHEMA(config_file[serialnumber])
device_config[CONF_HOST] = host
@@ -178,10 +181,8 @@ def setup(hass, config):
else:
# Device already registered, but on a different IP
device = AXIS_DEVICES[serialnumber]
device.url = host
async_dispatcher_send(hass,
DOMAIN + '_' + device.name + '_new_ip',
host)
device.config.host = host
dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host)
# Register discovery service
discovery.listen(hass, SERVICE_AXIS, axis_device_discovered)
@@ -202,10 +203,11 @@ def setup(hass, config):
"""Service to send a message."""
for _, device in AXIS_DEVICES.items():
if device.name == call.data[CONF_NAME]:
response = device.do_request(call.data[SERVICE_CGI],
call.data[SERVICE_ACTION],
call.data[SERVICE_PARAM])
hass.bus.async_fire(SERVICE_VAPIX_CALL_RESPONSE, response)
response = device.vapix.do_request(
call.data[SERVICE_CGI],
call.data[SERVICE_ACTION],
call.data[SERVICE_PARAM])
hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response)
return True
_LOGGER.info("Couldn\'t find device %s", call.data[CONF_NAME])
return False
@@ -216,7 +218,6 @@ def setup(hass, config):
vapix_service,
descriptions[DOMAIN][SERVICE_VAPIX_CALL],
schema=SERVICE_SCHEMA)
return True
@@ -224,9 +225,28 @@ def setup_device(hass, config, device_config):
"""Set up device."""
from axis import AxisDevice
device_config['hass'] = hass
device = AxisDevice(device_config) # Initialize device
enable_metadatastream = False
def signal_callback(action, event):
"""Callback to configure events when initialized on event stream."""
if action == 'add':
event_config = {
CONF_EVENT: event,
CONF_NAME: device_config[CONF_NAME],
ATTR_LOCATION: device_config[ATTR_LOCATION],
CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME]
}
component = event.event_platform
discovery.load_platform(hass,
component,
DOMAIN,
event_config,
config)
event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE],
EVENT_TYPES))
device_config['events'] = event_types
device_config['signal'] = signal_callback
device = AxisDevice(hass.loop, **device_config)
device.name = device_config[CONF_NAME]
if device.serial_number is None:
# If there is no serial number a connection could not be made
@@ -234,16 +254,10 @@ def setup_device(hass, config, device_config):
return False
for component in device_config[CONF_INCLUDE]:
if component in EVENT_TYPES:
# Sensors are created by device calling event_initialized
# when receiving initialize messages on metadatastream
device.add_event_topic(convert(component, 'type', 'subscribe'))
if not enable_metadatastream:
enable_metadatastream = True
else:
if component == 'camera':
camera_config = {
CONF_HOST: device_config[CONF_HOST],
CONF_NAME: device_config[CONF_NAME],
CONF_HOST: device_config[CONF_HOST],
CONF_PORT: device_config[CONF_PORT],
CONF_USERNAME: device_config[CONF_USERNAME],
CONF_PASSWORD: device_config[CONF_PASSWORD]
@@ -254,17 +268,9 @@ def setup_device(hass, config, device_config):
camera_config,
config)
if enable_metadatastream:
device.initialize_new_event = event_initialized
if not device.initiate_metadatastream():
hass.components.persistent_notification.create(
'Dependency missing for sensors, '
'please check documentation',
title=DOMAIN,
notification_id='axis_notification')
AXIS_DEVICES[device.serial_number] = device
if event_types:
hass.add_job(device.start)
return True
@@ -287,25 +293,16 @@ def _write_config(hass, config):
outfile.write(data)
def event_initialized(event):
"""Register event initialized on metadatastream here."""
hass = event.device_config('hass')
discovery.load_platform(hass,
convert(event.topic, 'topic', 'platform'),
DOMAIN, {'axis_event': event})
class AxisDeviceEvent(Entity):
"""Representation of a Axis device event."""
def __init__(self, axis_event):
def __init__(self, event_config):
"""Initialize the event."""
self.axis_event = axis_event
self._event_class = convert(self.axis_event.topic, 'topic', 'class')
self._name = '{}_{}_{}'.format(self.axis_event.device_name,
convert(self.axis_event.topic,
'topic', 'type'),
self.axis_event = event_config[CONF_EVENT]
self._name = '{}_{}_{}'.format(event_config[CONF_NAME],
self.axis_event.event_type,
self.axis_event.id)
self.location = event_config[ATTR_LOCATION]
self.axis_event.callback = self._update_callback
def _update_callback(self):
@@ -321,7 +318,7 @@ class AxisDeviceEvent(Entity):
@property
def device_class(self):
"""Return the class of the event."""
return self._event_class
return self.axis_event.event_class
@property
def should_poll(self):
@@ -336,52 +333,6 @@ class AxisDeviceEvent(Entity):
tripped = self.axis_event.is_tripped
attr[ATTR_TRIPPED] = 'True' if tripped else 'False'
location = self.axis_event.device_config(ATTR_LOCATION)
if location:
attr[ATTR_LOCATION] = location
attr[ATTR_LOCATION] = self.location
return attr
def convert(item, from_key, to_key):
"""Translate between Axis and HASS syntax."""
for entry in REMAP:
if entry[from_key] == item:
return entry[to_key]
REMAP = [{'type': 'motion',
'class': 'motion',
'topic': 'tns1:VideoAnalytics/tnsaxis:MotionDetection',
'subscribe': 'onvif:VideoAnalytics/axis:MotionDetection',
'platform': 'binary_sensor'},
{'type': 'vmd3',
'class': 'motion',
'topic': 'tns1:RuleEngine/tnsaxis:VMD3/vmd3_video_1',
'subscribe': 'onvif:RuleEngine/axis:VMD3/vmd3_video_1',
'platform': 'binary_sensor'},
{'type': 'pir',
'class': 'motion',
'topic': 'tns1:Device/tnsaxis:Sensor/PIR',
'subscribe': 'onvif:Device/axis:Sensor/axis:PIR',
'platform': 'binary_sensor'},
{'type': 'sound',
'class': 'sound',
'topic': 'tns1:AudioSource/tnsaxis:TriggerLevel',
'subscribe': 'onvif:AudioSource/axis:TriggerLevel',
'platform': 'binary_sensor'},
{'type': 'daynight',
'class': 'light',
'topic': 'tns1:VideoSource/tnsaxis:DayNightVision',
'subscribe': 'onvif:VideoSource/axis:DayNightVision',
'platform': 'binary_sensor'},
{'type': 'tampering',
'class': 'safety',
'topic': 'tns1:VideoSource/tnsaxis:Tampering',
'subscribe': 'onvif:VideoSource/axis:Tampering',
'platform': 'binary_sensor'},
{'type': 'input',
'class': 'input',
'topic': 'tns1:Device/tnsaxis:IO/Port',
'subscribe': 'onvif:Device/axis:IO/Port',
'platform': 'binary_sensor'}, ]

View File

@@ -30,6 +30,7 @@ DEVICE_CLASSES = [
'moving', # On means moving, Off means stopped
'occupancy', # On means occupied, Off means not occupied
'opening', # Door, window, etc.
'plug', # On means plugged in, Off means unplugged
'power', # Power, over-current, etc
'safety', # Generic on=unsafe, off=safe
'smoke', # Smoke detector

View File

@@ -7,25 +7,32 @@ https://home-assistant.io/components/binary_sensor.aurora/
from datetime import timedelta
import logging
from aiohttp.hdrs import USER_AGENT
import requests
import voluptuous as vol
from homeassistant.components.binary_sensor \
import (BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME)
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
CONF_THRESHOLD = "forecast_threshold"
_LOGGER = logging.getLogger(__name__)
CONF_ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric" \
"Administration"
CONF_THRESHOLD = 'forecast_threshold'
DEFAULT_DEVICE_CLASS = 'visible'
DEFAULT_NAME = 'Aurora Visibility'
DEFAULT_DEVICE_CLASS = "visible"
DEFAULT_THRESHOLD = 75
HA_USER_AGENT = "Home Assistant Aurora Tracker v.0.1.0"
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
URL = "http://services.swpc.noaa.gov/text/aurora-nowcast-map.txt"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_THRESHOLD, default=DEFAULT_THRESHOLD): cv.positive_int,
@@ -43,10 +50,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try:
aurora_data = AuroraData(
hass.config.latitude,
hass.config.longitude,
threshold
)
hass.config.latitude, hass.config.longitude, threshold)
aurora_data.update()
except requests.exceptions.HTTPError as error:
_LOGGER.error(
@@ -85,9 +89,9 @@ class AuroraSensor(BinarySensorDevice):
attrs = {}
if self.aurora_data:
attrs["visibility_level"] = self.aurora_data.visibility_level
attrs["message"] = self.aurora_data.is_visible_text
attrs['visibility_level'] = self.aurora_data.visibility_level
attrs['message'] = self.aurora_data.is_visible_text
attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
return attrs
def update(self):
@@ -104,10 +108,7 @@ class AuroraData(object):
self.longitude = longitude
self.number_of_latitude_intervals = 513
self.number_of_longitude_intervals = 1024
self.api_url = \
"http://services.swpc.noaa.gov/text/aurora-nowcast-map.txt"
self.headers = {"User-Agent": "Home Assistant Aurora Tracker v.0.1.0"}
self.headers = {USER_AGENT: HA_USER_AGENT}
self.threshold = int(threshold)
self.is_visible = None
self.is_visible_text = None
@@ -132,14 +133,14 @@ class AuroraData(object):
def get_aurora_forecast(self):
"""Get forecast data and parse for given long/lat."""
raw_data = requests.get(self.api_url, headers=self.headers).text
raw_data = requests.get(URL, headers=self.headers, timeout=5).text
forecast_table = [
row.strip(" ").split(" ")
for row in raw_data.split("\n")
if not row.startswith("#")
]
# convert lat and long for data points in table
# Convert lat and long for data points in table
converted_latitude = round((self.latitude / 180)
* self.number_of_latitude_intervals)
converted_longitude = round((self.longitude / 360)

View File

@@ -21,19 +21,19 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup Axis device event."""
add_devices([AxisBinarySensor(discovery_info['axis_event'], hass)], True)
add_devices([AxisBinarySensor(hass, discovery_info)], True)
class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice):
"""Representation of a binary Axis event."""
def __init__(self, axis_event, hass):
def __init__(self, hass, event_config):
"""Initialize the binary sensor."""
self.hass = hass
self._state = False
self._delay = axis_event.device_config(CONF_TRIGGER_TIME)
self._delay = event_config[CONF_TRIGGER_TIME]
self._timer = None
AxisDeviceEvent.__init__(self, axis_event)
AxisDeviceEvent.__init__(self, event_config)
@property
def is_on(self):

View File

@@ -22,6 +22,10 @@ from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
ATTR_OBSERVATIONS = 'observations'
ATTR_PROBABILITY = 'probability'
ATTR_PROBABILITY_THRESHOLD = 'probability_threshold'
CONF_OBSERVATIONS = 'observations'
CONF_PRIOR = 'prior'
CONF_PROBABILITY_THRESHOLD = 'probability_threshold'
@@ -29,7 +33,8 @@ CONF_P_GIVEN_F = 'prob_given_false'
CONF_P_GIVEN_T = 'prob_given_true'
CONF_TO_STATE = 'to_state'
DEFAULT_NAME = 'BayesianBinary'
DEFAULT_NAME = "Bayesian Binary Sensor"
DEFAULT_PROBABILITY_THRESHOLD = 0.5
NUMERIC_STATE_SCHEMA = vol.Schema({
CONF_PLATFORM: 'numeric_state',
@@ -49,16 +54,14 @@ STATE_SCHEMA = vol.Schema({
}, required=True)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME):
cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Required(CONF_OBSERVATIONS): vol.Schema(
vol.All(cv.ensure_list, [vol.Any(NUMERIC_STATE_SCHEMA,
STATE_SCHEMA)])
),
vol.Required(CONF_OBSERVATIONS):
vol.Schema(vol.All(cv.ensure_list,
[vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA)])),
vol.Required(CONF_PRIOR): vol.Coerce(float),
vol.Optional(CONF_PROBABILITY_THRESHOLD):
vol.Coerce(float),
vol.Optional(CONF_PROBABILITY_THRESHOLD,
default=DEFAULT_PROBABILITY_THRESHOLD): vol.Coerce(float),
})
@@ -73,16 +76,16 @@ def update_probability(prior, prob_true, prob_false):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Threshold sensor."""
"""Set up the Bayesian Binary sensor."""
name = config.get(CONF_NAME)
observations = config.get(CONF_OBSERVATIONS)
prior = config.get(CONF_PRIOR)
probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD, 0.5)
probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD)
device_class = config.get(CONF_DEVICE_CLASS)
async_add_devices([
BayesianBinarySensor(name, prior, observations, probability_threshold,
device_class)
BayesianBinarySensor(
name, prior, observations, probability_threshold, device_class)
], True)
@@ -107,7 +110,7 @@ class BayesianBinarySensor(BinarySensorDevice):
self.entity_obs = dict.fromkeys(to_observe, [])
for ind, obs in enumerate(self._observations):
obs["id"] = ind
obs['id'] = ind
self.entity_obs[obs['entity_id']].append(obs)
self.watchers = {
@@ -117,7 +120,7 @@ class BayesianBinarySensor(BinarySensorDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Call when entity about to be added to hass."""
"""Call when entity about to be added."""
@callback
# pylint: disable=invalid-name
def async_threshold_sensor_state_listener(entity, old_state,
@@ -135,8 +138,8 @@ class BayesianBinarySensor(BinarySensorDevice):
prior = self.prior
for obs in self.current_obs.values():
prior = update_probability(prior, obs['prob_true'],
obs['prob_false'])
prior = update_probability(
prior, obs['prob_true'], obs['prob_false'])
self.probability = prior
self.hass.async_add_job(self.async_update_ha_state, True)
@@ -206,9 +209,9 @@ class BayesianBinarySensor(BinarySensorDevice):
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
return {
'observations': [val for val in self.current_obs.values()],
'probability': round(self.probability, 2),
'probability_threshold': self._probability_threshold
ATTR_OBSERVATIONS: [val for val in self.current_obs.values()],
ATTR_PROBABILITY: round(self.probability, 2),
ATTR_PROBABILITY_THRESHOLD: self._probability_threshold,
}
@asyncio.coroutine

View File

@@ -0,0 +1,69 @@
"""
Support for binary sensor using GC100.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.gc100/
"""
import voluptuous as vol
from homeassistant.components.gc100 import DATA_GC100, CONF_PORTS
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['gc100']
_SENSORS_SCHEMA = vol.Schema({
cv.string: cv.string,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SENSORS_SCHEMA])
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the GC100 devices."""
binary_sensors = []
ports = config.get(CONF_PORTS)
for port in ports:
for port_addr, port_name in port.items():
binary_sensors.append(GC100BinarySensor(
port_name, port_addr, hass.data[DATA_GC100]))
add_devices(binary_sensors, True)
class GC100BinarySensor(BinarySensorDevice):
"""Representation of a binary sensor from GC100."""
def __init__(self, name, port_addr, gc100):
"""Initialize the GC100 binary sensor."""
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME
self._port_addr = port_addr
self._gc100 = gc100
self._state = None
# Subscribe to be notified about state changes (PUSH)
self._gc100.subscribe(self._port_addr, self.set_state)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the entity."""
return self._state
def update(self):
"""Update the sensor state."""
self._gc100.read_sensor(self._port_addr, self.set_state)
def set_state(self, state):
"""Set the current state."""
self._state = state == 1
self.schedule_update_ha_state()

View File

@@ -13,7 +13,8 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (CONF_NAME, ATTR_LONGITUDE, ATTR_LATITUDE)
from homeassistant.const import (
CONF_NAME, ATTR_LONGITUDE, ATTR_LATITUDE, CONF_SHOW_ON_MAP)
from homeassistant.util import Throttle
REQUIREMENTS = ['pyiss==1.0.1']
@@ -23,8 +24,6 @@ _LOGGER = logging.getLogger(__name__)
ATTR_ISS_NEXT_RISE = 'next_rise'
ATTR_ISS_NUMBER_PEOPLE_SPACE = 'number_of_people_in_space'
CONF_SHOW_ON_MAP = 'show_on_map'
DEFAULT_NAME = 'ISS'
DEFAULT_DEVICE_CLASS = 'visible'

View File

@@ -0,0 +1,96 @@
"""
Support for monitoring the state of Linode Nodes.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.linode/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.linode import (
CONF_NODES, ATTR_CREATED, ATTR_NODE_ID, ATTR_NODE_NAME,
ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS, DATA_LINODE)
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Node'
DEFAULT_DEVICE_CLASS = 'moving'
DEPENDENCIES = ['linode']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NODES): vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Linode droplet sensor."""
linode = hass.data.get(DATA_LINODE)
nodes = config.get(CONF_NODES)
dev = []
for node in nodes:
node_id = linode.get_node_id(node)
if node_id is None:
_LOGGER.error("Node %s is not available", node)
return
dev.append(LinodeBinarySensor(linode, node_id))
add_devices(dev, True)
class LinodeBinarySensor(BinarySensorDevice):
"""Representation of a Linode droplet sensor."""
def __init__(self, li, node_id):
"""Initialize a new Linode sensor."""
self._linode = li
self._node_id = node_id
self._state = None
self.data = None
@property
def name(self):
"""Return the name of the sensor."""
if self.data is not None:
return self.data.label
@property
def is_on(self):
"""Return true if the binary sensor is on."""
if self.data is not None:
return self.data.status == 'running'
return False
@property
def device_class(self):
"""Return the class of this sensor."""
return DEFAULT_DEVICE_CLASS
@property
def device_state_attributes(self):
"""Return the state attributes of the Linode Node."""
if self.data:
return {
ATTR_CREATED: self.data.created,
ATTR_NODE_ID: self.data.id,
ATTR_NODE_NAME: self.data.label,
ATTR_IPV4_ADDRESS: self.data.ipv4,
ATTR_IPV6_ADDRESS: self.data.ipv6,
ATTR_MEMORY: self.data.specs.memory,
ATTR_REGION: self.data.region.country,
ATTR_VCPUS: self.data.specs.vcpus,
}
return {}
def update(self):
"""Update state of sensor."""
self._linode.update()
if self._linode.data is not None:
for node in self._linode.data:
if node.id == self._node_id:
self.data = node

View File

@@ -14,7 +14,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.netatmo import CameraData
from homeassistant.loader import get_component
from homeassistant.const import CONF_TIMEOUT, CONF_OFFSET
from homeassistant.const import CONF_TIMEOUT
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -44,14 +44,12 @@ CONF_WELCOME_SENSORS = 'welcome_sensors'
CONF_PRESENCE_SENSORS = 'presence_sensors'
CONF_TAG_SENSORS = 'tag_sensors'
DEFAULT_TIMEOUT = 15
DEFAULT_OFFSET = 90
DEFAULT_TIMEOUT = 90
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): cv.positive_int,
vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES):
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
@@ -66,7 +64,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
netatmo = get_component('netatmo')
home = config.get(CONF_HOME)
timeout = config.get(CONF_TIMEOUT)
offset = config.get(CONF_OFFSET)
if timeout is None:
timeout = DEFAULT_TIMEOUT
module_name = None
@@ -94,7 +93,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for variable in welcome_sensors:
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout,
offset, camera_type, variable)], True)
camera_type, variable)], True)
if camera_type == 'NOC':
if CONF_CAMERAS in config:
if config[CONF_CAMERAS] != [] and \
@@ -102,14 +101,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
continue
for variable in presence_sensors:
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout, offset,
data, camera_name, module_name, home, timeout,
camera_type, variable)], True)
for module_name in data.get_module_names(camera_name):
for variable in tag_sensors:
camera_type = None
add_devices([NetatmoBinarySensor(
data, camera_name, module_name, home, timeout, offset,
data, camera_name, module_name, home, timeout,
camera_type, variable)], True)
@@ -117,14 +116,13 @@ class NetatmoBinarySensor(BinarySensorDevice):
"""Represent a single binary sensor in a Netatmo Camera device."""
def __init__(self, data, camera_name, module_name, home,
timeout, offset, camera_type, sensor):
timeout, camera_type, sensor):
"""Set up for access to the Netatmo camera events."""
self._data = data
self._camera_name = camera_name
self._module_name = module_name
self._home = home
self._timeout = timeout
self._offset = offset
if home:
self._name = '{} / {}'.format(home, camera_name)
else:
@@ -173,40 +171,39 @@ class NetatmoBinarySensor(BinarySensorDevice):
if self._sensor_name == "Someone known":
self._state =\
self._data.camera_data.someoneKnownSeen(
self._home, self._camera_name, self._timeout*60)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Someone unknown":
self._state =\
self._data.camera_data.someoneUnknownSeen(
self._home, self._camera_name, self._timeout*60)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Motion":
self._state =\
self._data.camera_data.motionDetected(
self._home, self._camera_name, self._timeout*60)
self._home, self._camera_name, self._timeout)
elif self._cameratype == 'NOC':
if self._sensor_name == "Outdoor motion":
self._state =\
self._data.camera_data.outdoormotionDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Outdoor human":
self._state =\
self._data.camera_data.humanDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Outdoor animal":
self._state =\
self._data.camera_data.animalDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
elif self._sensor_name == "Outdoor vehicle":
self._state =\
self._data.camera_data.carDetected(
self._home, self._camera_name, self._offset)
self._home, self._camera_name, self._timeout)
if self._sensor_name == "Tag Vibration":
self._state =\
self._data.camera_data.moduleMotionDetected(
self._home, self._module_name, self._camera_name,
self._timeout*60)
self._timeout)
elif self._sensor_name == "Tag Open":
self._state =\
self._data.camera_data.moduleOpened(
self._home, self._module_name, self._camera_name)
else:
return None
self._home, self._module_name, self._camera_name,
self._timeout)

View File

@@ -59,6 +59,8 @@ class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice):
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating RainCloud sensor: %s", self._name)
self._state = getattr(self.data, self._sensor_type)
if self._sensor_type == 'status':
self._state = self._state == 'Online'
@property
def icon(self):

View File

@@ -0,0 +1,64 @@
"""
Support for showing random states.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.random/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Random Binary Sensor'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Random binary sensor."""
name = config.get(CONF_NAME)
device_class = config.get(CONF_DEVICE_CLASS)
async_add_devices([RandomSensor(name, device_class)], True)
class RandomSensor(BinarySensorDevice):
"""Representation of a Random binary sensor."""
def __init__(self, name, device_class):
"""Initialize the Random binary sensor."""
self._name = name
self._device_class = device_class
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
@property
def device_class(self):
"""Return the sensor class of the sensor."""
return self._device_class
@asyncio.coroutine
def async_update(self):
"""Get new state and update the sensor's state."""
from random import getrandbits
self._state = bool(getrandbits(1))

View File

@@ -62,7 +62,6 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
device.hass = hass
device.is_lighting4 = (packet_id[2:4] == '13')
sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device
@@ -86,17 +85,16 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
if not config[ATTR_AUTOMATIC_ADD]:
return
poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
if poss_dev is not None:
poss_id = slugify(poss_dev.event.device.id_string.lower())
_LOGGER.info("Found possible matching deviceid %s.",
poss_id)
if event.device.packettype == 0x13:
poss_dev = rfxtrx.find_possible_pt2262_device(device_id)
if poss_dev is not None:
poss_id = slugify(poss_dev.event.device.id_string.lower())
_LOGGER.info("Found possible matching deviceid %s.",
poss_id)
pkt_id = "".join("{0:02x}".format(x) for x in event.data)
sensor = RfxtrxBinarySensor(event, pkt_id)
sensor.hass = hass
sensor.is_lighting4 = (pkt_id[2:4] == '13')
rfxtrx.RFX_DEVICES[device_id] = sensor
add_devices_callback([sensor])
_LOGGER.info("Added binary sensor %s "
@@ -114,6 +112,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
slugify(event.device.id_string.lower()),
event.device.__class__.__name__,
event.device.subtype)
if sensor.is_lighting4:
if sensor.data_bits is not None:
cmd = rfxtrx.get_pt2262_cmd(device_id, sensor.data_bits)
@@ -154,7 +153,7 @@ class RfxtrxBinarySensor(BinarySensorDevice):
self._device_class = device_class
self._off_delay = off_delay
self._state = False
self.is_lighting4 = False
self.is_lighting4 = (event.device.packettype == 0x13)
self.delay_listener = None
self._data_bits = data_bits
self._cmd_on = cmd_on

View File

@@ -11,7 +11,7 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.ring import (
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE)
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING)
from homeassistant.const import (
ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
@@ -28,20 +28,20 @@ SCAN_INTERVAL = timedelta(seconds=5)
# Sensor types: Name, category, device_class
SENSOR_TYPES = {
'ding': ['Ding', ['doorbell'], 'occupancy'],
'motion': ['Motion', ['doorbell'], 'motion'],
'motion': ['Motion', ['doorbell', 'stickup_cams'], 'motion'],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a Ring device."""
ring = hass.data.get('ring')
ring = hass.data[DATA_RING]
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
@@ -50,6 +50,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors.append(RingBinarySensor(hass,
device,
sensor_type))
for device in ring.stickup_cams:
if 'stickup_cams' in SENSOR_TYPES[sensor_type][1]:
sensors.append(RingBinarySensor(hass,
device,
sensor_type))
add_devices(sensors, True)
return True

View File

@@ -0,0 +1,97 @@
"""
Binary sensor support for the Skybell HD Doorbell.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.skybell/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.skybell import (
DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice)
from homeassistant.const import (
CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['skybell']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
# Sensor types: Name, device_class, event
SENSOR_TYPES = {
'button': ['Button', 'occupancy', 'device:sensor:button'],
'motion': ['Motion', 'motion', 'device:sensor:motion'],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
cv.string,
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the platform for a Skybell device."""
skybell = hass.data.get(SKYBELL_DOMAIN)
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
for device in skybell.get_devices():
sensors.append(SkybellBinarySensor(device, sensor_type))
add_devices(sensors, True)
class SkybellBinarySensor(SkybellDevice, BinarySensorDevice):
"""A binary sensor implementation for Skybell devices."""
def __init__(self, device, sensor_type):
"""Initialize a binary sensor for a Skybell device."""
super().__init__(device)
self._sensor_type = sensor_type
self._name = "{0} {1}".format(self._device.name,
SENSOR_TYPES[self._sensor_type][0])
self._device_class = SENSOR_TYPES[self._sensor_type][1]
self._event = {}
self._state = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = super().device_state_attributes
attrs['event_date'] = self._event.get('createdAt')
return attrs
def update(self):
"""Get the latest data and updates the state."""
super().update()
event = self._device.latest(SENSOR_TYPES[self._sensor_type][2])
self._state = bool(event and event.get('id') != self._event.get('id'))
self._event = event

View File

@@ -67,7 +67,7 @@ class SpcBinarySensor(BinarySensorDevice):
spc_registry.register_sensor_device(zone_id, self)
@asyncio.coroutine
def async_update_from_spc(self, state):
def async_update_from_spc(self, state, extra):
"""Update the state of the device."""
self._state = state
yield from self.async_update_ha_state()

View File

@@ -0,0 +1,34 @@
"""
Support for binary sensors using Tellstick Net.
This platform uses the Telldus Live online service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.tellduslive/
"""
import logging
from homeassistant.components.tellduslive import TelldusLiveEntity
from homeassistant.components.binary_sensor import BinarySensorDevice
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Tellstick sensors."""
if discovery_info is None:
return
add_devices(
TelldusLiveSensor(hass, binary_sensor)
for binary_sensor in discovery_info
)
class TelldusLiveSensor(TelldusLiveEntity, BinarySensorDevice):
"""Representation of a Tellstick sensor."""
@property
def is_on(self):
"""Return true if switch is on."""
return self.device.is_on

View File

@@ -15,13 +15,12 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START, STATE_ON)
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import (
async_track_state_change, async_track_same_state)
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
@@ -94,10 +93,6 @@ class BinarySensorTemplate(BinarySensorDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Handle the target device state changes."""
@@ -135,7 +130,7 @@ class BinarySensorTemplate(BinarySensorDevice):
return False
@callback
def _async_render(self, *args):
def _async_render(self):
"""Get the state of template."""
try:
return self._template.async_render().lower() == 'true'
@@ -171,5 +166,5 @@ class BinarySensorTemplate(BinarySensorDevice):
period = self._delay_on if state else self._delay_off
async_track_same_state(
self.hass, state, period, set_state, entity_ids=self._entities,
async_check_func=self._async_render)
self.hass, period, set_state, entity_ids=self._entities,
async_check_same_func=lambda *args: self._async_render() == state)

View File

@@ -30,7 +30,6 @@ class TeslaBinarySensor(TeslaDevice, BinarySensorDevice):
def __init__(self, tesla_device, controller, sensor_type):
"""Initialisation of binary sensor."""
super().__init__(tesla_device, controller)
self._name = self.tesla_device.name
self._state = False
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
self._sensor_type = sensor_type

View File

@@ -1,11 +1,13 @@
"""
A sensor that monitors trands in other components.
A sensor that monitors trends in other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.trend/
"""
import asyncio
from collections import deque
import logging
import math
import voluptuous as vol
@@ -16,21 +18,40 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA,
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, STATE_UNKNOWN)
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME,
CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_FRIENDLY_NAME,
STATE_UNKNOWN)
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util import utcnow
REQUIREMENTS = ['numpy==1.13.3']
_LOGGER = logging.getLogger(__name__)
ATTR_ATTRIBUTE = 'attribute'
ATTR_GRADIENT = 'gradient'
ATTR_MIN_GRADIENT = 'min_gradient'
ATTR_INVERT = 'invert'
ATTR_SAMPLE_DURATION = 'sample_duration'
ATTR_SAMPLE_COUNT = 'sample_count'
CONF_SENSORS = 'sensors'
CONF_ATTRIBUTE = 'attribute'
CONF_MAX_SAMPLES = 'max_samples'
CONF_MIN_GRADIENT = 'min_gradient'
CONF_INVERT = 'invert'
CONF_SAMPLE_DURATION = 'sample_duration'
SENSOR_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_ATTRIBUTE): cv.string,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_INVERT, default=False): cv.boolean,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Optional(CONF_MAX_SAMPLES, default=2): cv.positive_int,
vol.Optional(CONF_MIN_GRADIENT, default=0.0): vol.Coerce(float),
vol.Optional(CONF_INVERT, default=False): cv.boolean,
vol.Optional(CONF_SAMPLE_DURATION, default=0): cv.positive_int,
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -43,17 +64,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the trend sensors."""
sensors = []
for device, device_config in config[CONF_SENSORS].items():
for device_id, device_config in config[CONF_SENSORS].items():
entity_id = device_config[ATTR_ENTITY_ID]
attribute = device_config.get(CONF_ATTRIBUTE)
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
device_class = device_config.get(CONF_DEVICE_CLASS)
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device_id)
invert = device_config[CONF_INVERT]
max_samples = device_config[CONF_MAX_SAMPLES]
min_gradient = device_config[CONF_MIN_GRADIENT]
sample_duration = device_config[CONF_SAMPLE_DURATION]
sensors.append(
SensorTrend(
hass, device, friendly_name, entity_id, attribute,
device_class, invert)
hass, device_id, friendly_name, entity_id, attribute,
device_class, invert, max_samples, min_gradient,
sample_duration)
)
if not sensors:
_LOGGER.error("No sensors added")
@@ -65,30 +90,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class SensorTrend(BinarySensorDevice):
"""Representation of a trend Sensor."""
def __init__(self, hass, device_id, friendly_name,
target_entity, attribute, device_class, invert):
def __init__(self, hass, device_id, friendly_name, entity_id,
attribute, device_class, invert, max_samples,
min_gradient, sample_duration):
"""Initialize the sensor."""
self._hass = hass
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, device_id, hass=hass)
self._name = friendly_name
self._target_entity = target_entity
self._entity_id = entity_id
self._attribute = attribute
self._device_class = device_class
self._invert = invert
self._sample_duration = sample_duration
self._min_gradient = min_gradient
self._gradient = None
self._state = None
self.from_state = None
self.to_state = None
@callback
def trend_sensor_state_listener(entity, old_state, new_state):
"""Handle the target device state changes."""
self.from_state = old_state
self.to_state = new_state
hass.async_add_job(self.async_update_ha_state(True))
track_state_change(hass, target_entity,
trend_sensor_state_listener)
self.samples = deque(maxlen=max_samples)
@property
def name(self):
@@ -105,33 +123,77 @@ class SensorTrend(BinarySensorDevice):
"""Return the sensor class of the sensor."""
return self._device_class
@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
return {
ATTR_ENTITY_ID: self._entity_id,
ATTR_FRIENDLY_NAME: self._name,
ATTR_INVERT: self._invert,
ATTR_GRADIENT: self._gradient,
ATTR_MIN_GRADIENT: self._min_gradient,
ATTR_SAMPLE_DURATION: self._sample_duration,
ATTR_SAMPLE_COUNT: len(self.samples),
}
@property
def should_poll(self):
"""No polling needed."""
return False
@asyncio.coroutine
def async_added_to_hass(self):
"""Complete device setup after being added to hass."""
@callback
def trend_sensor_state_listener(entity, old_state, new_state):
"""Handle state changes on the observed device."""
try:
if self._attribute:
state = new_state.attributes.get(self._attribute)
else:
state = new_state.state
if state != STATE_UNKNOWN:
sample = (utcnow().timestamp(), float(state))
self.samples.append(sample)
self.async_schedule_update_ha_state(True)
except (ValueError, TypeError) as ex:
_LOGGER.error(ex)
async_track_state_change(
self.hass, self._entity_id,
trend_sensor_state_listener)
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states."""
if self.from_state is None or self.to_state is None:
return
if (self.from_state.state == STATE_UNKNOWN or
self.to_state.state == STATE_UNKNOWN):
return
try:
if self._attribute:
from_value = float(
self.from_state.attributes.get(self._attribute))
to_value = float(
self.to_state.attributes.get(self._attribute))
else:
from_value = float(self.from_state.state)
to_value = float(self.to_state.state)
# Remove outdated samples
if self._sample_duration > 0:
cutoff = utcnow().timestamp() - self._sample_duration
while self.samples and self.samples[0][0] < cutoff:
self.samples.popleft()
self._state = to_value > from_value
if self._invert:
self._state = not self._state
if len(self.samples) < 2:
return
except (ValueError, TypeError) as ex:
self._state = None
_LOGGER.error(ex)
# Calculate gradient of linear trend
yield from self.hass.async_add_job(self._calculate_gradient)
# Update state
self._state = (
abs(self._gradient) > abs(self._min_gradient) and
math.copysign(self._gradient, self._min_gradient) == self._gradient
)
if self._invert:
self._state = not self._state
def _calculate_gradient(self):
"""Compute the linear trend gradient of the current samples.
This need run inside executor.
"""
import numpy as np
timestamps = np.array([t for t, _ in self.samples])
values = np.array([s for _, s in self.samples])
coeffs = np.polyfit(timestamps, values, 1)
self._gradient = coeffs[0]

View File

@@ -0,0 +1,103 @@
"""
Support for monitoring the state of Vultr subscriptions (VPS).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.vultr/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.vultr import (
CONF_SUBSCRIPTION, ATTR_AUTO_BACKUPS, ATTR_ALLOWED_BANDWIDTH,
ATTR_CREATED_AT, ATTR_SUBSCRIPTION_ID, ATTR_SUBSCRIPTION_NAME,
ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_DISK,
ATTR_COST_PER_MONTH, ATTR_OS, ATTR_REGION, ATTR_VCPUS, DATA_VULTR)
_LOGGER = logging.getLogger(__name__)
DEFAULT_DEVICE_CLASS = 'power'
DEFAULT_NAME = 'Vultr {}'
DEPENDENCIES = ['vultr']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SUBSCRIPTION): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Vultr subscription (server) sensor."""
vultr = hass.data[DATA_VULTR]
subscription = config.get(CONF_SUBSCRIPTION)
name = config.get(CONF_NAME)
if subscription not in vultr.data:
_LOGGER.error("Subscription %s not found", subscription)
return False
add_devices([VultrBinarySensor(vultr, subscription, name)], True)
class VultrBinarySensor(BinarySensorDevice):
"""Representation of a Vultr subscription sensor."""
def __init__(self, vultr, subscription, name):
"""Initialize a new Vultr sensor."""
self._vultr = vultr
self._name = name
self.subscription = subscription
self.data = None
@property
def name(self):
"""Return the name of the sensor."""
try:
return self._name.format(self.data['label'])
except (KeyError, TypeError):
return self._name
@property
def icon(self):
"""Return the icon of this server."""
return 'mdi:server' if self.is_on else 'mdi:server-off'
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.data['power_status'] == 'running'
@property
def device_class(self):
"""Return the class of this sensor."""
return DEFAULT_DEVICE_CLASS
@property
def device_state_attributes(self):
"""Return the state attributes of the Vultr subscription."""
return {
ATTR_ALLOWED_BANDWIDTH: self.data.get('allowed_bandwidth_gb'),
ATTR_AUTO_BACKUPS: self.data.get('auto_backups'),
ATTR_COST_PER_MONTH: self.data.get('cost_per_month'),
ATTR_CREATED_AT: self.data.get('date_created'),
ATTR_DISK: self.data.get('disk'),
ATTR_IPV4_ADDRESS: self.data.get('main_ip'),
ATTR_IPV6_ADDRESS: self.data.get('v6_main_ip'),
ATTR_MEMORY: self.data.get('ram'),
ATTR_OS: self.data.get('os'),
ATTR_REGION: self.data.get('location'),
ATTR_SUBSCRIPTION_ID: self.data.get('SUBID'),
ATTR_SUBSCRIPTION_NAME: self.data.get('label'),
ATTR_VCPUS: self.data.get('vcpu_count')
}
def update(self):
"""Update state of sensor."""
self._vultr.update()
self.data = self._vultr.data[self.subscription]

View File

@@ -9,7 +9,6 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
@@ -87,7 +86,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.info("Device isn't a sensor, skipping")
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
"""Representation of a Wink binary sensor."""
def __init__(self, wink, hass):
@@ -117,6 +116,11 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice, Entity):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return SENSOR_TYPES.get(self.capability)
@property
def device_state_attributes(self):
"""Return the state attributes."""
return super().device_state_attributes
class WinkSmokeDetector(WinkBinarySensorDevice):
"""Representation of a Wink Smoke detector."""
@@ -124,9 +128,9 @@ class WinkSmokeDetector(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'test_activated': self.wink.test_activated()
}
_attributes = super().device_state_attributes
_attributes['test_activated'] = self.wink.test_activated()
return _attributes
class WinkHub(WinkBinarySensorDevice):
@@ -135,11 +139,11 @@ class WinkHub(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'update_needed': self.wink.update_needed(),
'firmware_version': self.wink.firmware_version(),
'pairing_mode': self.wink.pairing_mode()
}
_attributes = super().device_state_attributes
_attributes['update_needed'] = self.wink.update_needed()
_attributes['firmware_version'] = self.wink.firmware_version()
_attributes['pairing_mode'] = self.wink.pairing_mode()
return _attributes
class WinkRemote(WinkBinarySensorDevice):
@@ -148,12 +152,12 @@ class WinkRemote(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'button_on_pressed': self.wink.button_on_pressed(),
'button_off_pressed': self.wink.button_off_pressed(),
'button_up_pressed': self.wink.button_up_pressed(),
'button_down_pressed': self.wink.button_down_pressed()
}
_attributes = super().device_state_attributes
_attributes['button_on_pressed'] = self.wink.button_on_pressed()
_attributes['button_off_pressed'] = self.wink.button_off_pressed()
_attributes['button_up_pressed'] = self.wink.button_up_pressed()
_attributes['button_down_pressed'] = self.wink.button_down_pressed()
return _attributes
@property
def device_class(self):
@@ -167,10 +171,10 @@ class WinkButton(WinkBinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'pressed': self.wink.pressed(),
'long_pressed': self.wink.long_pressed()
}
_attributes = super().device_state_attributes
_attributes['pressed'] = self.wink.pressed()
_attributes['long_pressed'] = self.wink.long_pressed()
return _attributes
class WinkGang(WinkBinarySensorDevice):

View File

@@ -12,6 +12,7 @@ ATTR_OPEN_SINCE = 'Open since'
MOTION = 'motion'
NO_MOTION = 'no_motion'
ATTR_LAST_ACTION = 'last_action'
ATTR_NO_MOTION_SINCE = 'No motion since'
DENSITY = 'density'
@@ -24,13 +25,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items():
for device in gateway.devices['binary_sensor']:
model = device['model']
if model == 'motion':
if model in ['motion', 'sensor_motion.aq2']:
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model == 'sensor_motion.aq2':
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model == 'magnet':
devices.append(XiaomiDoorSensor(device, gateway))
elif model == 'sensor_magnet.aq2':
elif model in ['magnet', 'sensor_magnet.aq2']:
devices.append(XiaomiDoorSensor(device, gateway))
elif model == 'sensor_wleak.aq1':
devices.append(XiaomiWaterLeakSensor(device, gateway))
@@ -38,10 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices.append(XiaomiSmokeSensor(device, gateway))
elif model == 'natgas':
devices.append(XiaomiNatgasSensor(device, gateway))
elif model == 'switch':
devices.append(XiaomiButton(device, 'Switch', 'status',
hass, gateway))
elif model == 'sensor_switch.aq2':
elif model in ['switch', 'sensor_switch.aq2', 'sensor_switch.aq3']:
devices.append(XiaomiButton(device, 'Switch', 'status',
hass, gateway))
elif model == '86sw1':
@@ -288,9 +282,17 @@ class XiaomiButton(XiaomiBinarySensor):
def __init__(self, device, name, data_key, hass, xiaomi_hub):
"""Initialize the XiaomiButton."""
self._hass = hass
self._last_action = None
XiaomiBinarySensor.__init__(self, device, name, xiaomi_hub,
data_key, None)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_LAST_ACTION: self._last_action}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
@@ -316,6 +318,8 @@ class XiaomiButton(XiaomiBinarySensor):
'entity_id': self.entity_id,
'click_type': click_type
})
self._last_action = click_type
if value in ['long_click_press', 'long_click_release']:
return True
return False
@@ -327,10 +331,18 @@ class XiaomiCube(XiaomiBinarySensor):
def __init__(self, device, hass, xiaomi_hub):
"""Initialize the Xiaomi Cube."""
self._hass = hass
self._last_action = None
self._state = False
XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub,
None, None)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_LAST_ACTION: self._last_action}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data):
"""Parse data sent by gateway."""
if 'status' in data:
@@ -338,6 +350,7 @@ class XiaomiCube(XiaomiBinarySensor):
'entity_id': self.entity_id,
'action_type': data['status']
})
self._last_action = data['status']
if 'rotate' in data:
self._hass.bus.fire('cube_action', {
@@ -345,4 +358,6 @@ class XiaomiCube(XiaomiBinarySensor):
'action_type': 'rotate',
'action_value': float(data['rotate'].replace(",", "."))
})
return False
self._last_action = 'rotate'
return True

View File

@@ -4,16 +4,17 @@ Support for BloomSky weather station.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/bloomsky/
"""
import logging
from datetime import timedelta
import logging
from aiohttp.hdrs import AUTHORIZATION
import requests
import voluptuous as vol
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
@@ -68,7 +69,7 @@ class BloomSky(object):
"""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}, timeout=10)
self.API_URL, headers={AUTHORIZATION: self._api_key}, timeout=10)
if response.status_code == 401:
raise RuntimeError("Invalid API_KEY")
elif response.status_code != 200:

View File

@@ -1,19 +1,21 @@
# Describes the format for available calendar services
todoist:
new_task:
description: Create a new task and add it to a project.
fields:
content:
description: The name of the task. [Required]
description: The name of the task (Required).
example: Pick up the mail
project:
description: The name of the project this task should belong to. Defaults to Inbox. [Optional]
description: The name of the project this task should belong to. Defaults to Inbox (Optional).
example: Errands
labels:
description: Any labels that you want to apply to this task, separated by a comma. [Optional]
description: Any labels that you want to apply to this task, separated by a comma (Optional).
example: Chores,Deliveries
priority:
description: The priority of this task, from 1 (normal) to 4 (urgent). [Optional]
description: The priority of this task, from 1 (normal) to 4 (urgent) (Optional).
example: 2
due_date:
description: The day this task is due, in format YYYY-MM-DD. [Optional]
description: The day this task is due, in format YYYY-MM-DD (Optional).
example: "2018-04-01"

View File

@@ -29,18 +29,22 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
from homeassistant.helpers.event import async_track_time_interval
import homeassistant.helpers.config_validation as cv
DOMAIN = 'camera'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
SERVICE_EN_MOTION = 'enable_motion_detection'
SERVICE_DISEN_MOTION = 'disable_motion_detection'
DOMAIN = 'camera'
DEPENDENCIES = ['http']
SERVICE_ENABLE_MOTION = 'enable_motion_detection'
SERVICE_DISABLE_MOTION = 'disable_motion_detection'
SERVICE_SNAPSHOT = 'snapshot'
SCAN_INTERVAL = timedelta(seconds=30)
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ATTR_FILENAME = 'filename'
STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
@@ -55,13 +59,17 @@ CAMERA_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({
vol.Required(ATTR_FILENAME): cv.template
})
@bind_hass
def enable_motion_detection(hass, entity_id=None):
"""Enable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_EN_MOTION, data))
DOMAIN, SERVICE_ENABLE_MOTION, data))
@bind_hass
@@ -69,9 +77,20 @@ def disable_motion_detection(hass, entity_id=None):
"""Disable Motion Detection."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_DISEN_MOTION, data))
DOMAIN, SERVICE_DISABLE_MOTION, data))
@bind_hass
def async_snapshot(hass, filename, entity_id=None):
"""Make a snapshot from a camera."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
data[ATTR_FILENAME] = filename
hass.async_add_job(hass.services.async_call(
DOMAIN, SERVICE_SNAPSHOT, data))
@bind_hass
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
"""Fetch a image from a camera entity."""
@@ -119,44 +138,72 @@ def async_setup(hass, config):
entity.async_update_token()
hass.async_add_job(entity.async_update_ha_state())
async_track_time_interval(hass, update_tokens, TOKEN_CHANGE_INTERVAL)
hass.helpers.event.async_track_time_interval(
update_tokens, TOKEN_CHANGE_INTERVAL)
@asyncio.coroutine
def async_handle_camera_service(service):
"""Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service)
for camera in target_cameras:
if service.service == SERVICE_EN_MOTION:
yield from camera.async_enable_motion_detection()
elif service.service == SERVICE_DISEN_MOTION:
yield from camera.async_disable_motion_detection()
update_tasks = []
for camera in target_cameras:
if service.service == SERVICE_ENABLE_MOTION:
yield from camera.async_enable_motion_detection()
elif service.service == SERVICE_DISABLE_MOTION:
yield from camera.async_disable_motion_detection()
if not camera.should_poll:
continue
update_coro = hass.async_add_job(
camera.async_update_ha_state(True))
if hasattr(camera, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(camera.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_handle_snapshot_service(service):
"""Handle snapshot services calls."""
target_cameras = component.async_extract_from_service(service)
filename = service.data[ATTR_FILENAME]
filename.hass = hass
for camera in target_cameras:
snapshot_file = filename.async_render(
variables={ATTR_ENTITY_ID: camera})
# check if we allow to access to that file
if not hass.config.is_allowed_path(snapshot_file):
_LOGGER.error(
"Can't write %s, no access to path!", snapshot_file)
continue
image = yield from camera.async_camera_image()
def _write_image(to_file, image_data):
"""Executor helper to write image."""
with open(to_file, 'wb') as img_file:
img_file.write(image_data)
try:
yield from hass.async_add_job(
_write_image, snapshot_file, image)
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)
descriptions = yield from hass.async_add_job(
load_yaml_config_file, os.path.join(
os.path.dirname(__file__), 'services.yaml'))
hass.services.async_register(
DOMAIN, SERVICE_EN_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_EN_MOTION), schema=CAMERA_SERVICE_SCHEMA)
DOMAIN, SERVICE_ENABLE_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_ENABLE_MOTION), schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_DISEN_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_DISEN_MOTION), schema=CAMERA_SERVICE_SCHEMA)
DOMAIN, SERVICE_DISABLE_MOTION, async_handle_camera_service,
descriptions.get(SERVICE_DISABLE_MOTION), schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_SNAPSHOT, async_handle_snapshot_service,
descriptions.get(SERVICE_SNAPSHOT),
schema=CAMERA_SERVICE_SNAPSHOT)
return True

View File

@@ -1,37 +1,41 @@
"""
This component provides basic support for Netgear Arlo IP cameras.
Support for Netgear Arlo IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.arlo/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
import homeassistant.helpers.config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_BATTERY_LEVEL
DEPENDENCIES = ['arlo', 'ffmpeg']
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
ATTR_BRIGHTNESS = 'brightness'
ATTR_FLIPPED = 'flipped'
ATTR_MIRRORED = 'mirrored'
ATTR_MOTION_SENSITIVITY = 'motion_detection_sensitivity'
ATTR_POWER_SAVE_MODE = 'power_save_mode'
ATTR_MOTION = 'motion_detection_sensitivity'
ATTR_POWERSAVE = 'power_save_mode'
ATTR_SIGNAL_STRENGTH = 'signal_strength'
ATTR_UNSEEN_VIDEOS = 'unseen_videos'
ATTR_LAST_REFRESH = 'last_refresh'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
DEPENDENCIES = ['arlo', 'ffmpeg']
POWERSAVE_MODE_MAPPING = {
1: 'best_battery_life',
@@ -40,7 +44,8 @@ POWERSAVE_MODE_MAPPING = {
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
})
@@ -69,6 +74,9 @@ class ArloCam(Camera):
self._motion_status = False
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None
self._camera.base_station.refresh_rate = SCAN_INTERVAL.total_seconds()
self.attrs = {}
def camera_image(self):
"""Return a still image response from the camera."""
@@ -100,32 +108,27 @@ class ArloCam(Camera):
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_BATTERY_LEVEL:
self._camera.get_battery_level,
ATTR_BRIGHTNESS:
self._camera.get_brightness,
ATTR_FLIPPED:
self._camera.get_flip_state,
ATTR_MIRRORED:
self._camera.get_mirror_state,
ATTR_MOTION_SENSITIVITY:
self._camera.get_motion_detection_sensitivity,
ATTR_POWER_SAVE_MODE:
POWERSAVE_MODE_MAPPING[self._camera.get_powersave_mode],
ATTR_SIGNAL_STRENGTH:
self._camera.get_signal_strength,
ATTR_UNSEEN_VIDEOS:
self._camera.unseen_videos
name: value for name, value in (
(ATTR_BATTERY_LEVEL, self._camera.battery_level),
(ATTR_BRIGHTNESS, self._camera.brightness),
(ATTR_FLIPPED, self._camera.flip_state),
(ATTR_MIRRORED, self._camera.mirror_state),
(ATTR_MOTION, self._camera.motion_detection_sensitivity),
(ATTR_POWERSAVE, POWERSAVE_MODE_MAPPING.get(
self._camera.powersave_mode)),
(ATTR_SIGNAL_STRENGTH, self._camera.signal_strength),
(ATTR_UNSEEN_VIDEOS, self._camera.unseen_videos),
) if value is not None
}
@property
def model(self):
"""Camera model."""
"""Return the camera model."""
return self._camera.model_id
@property
def brand(self):
"""Camera brand."""
"""Return the camera brand."""
return DEFAULT_BRAND
@property
@@ -135,7 +138,7 @@ class ArloCam(Camera):
@property
def motion_detection_enabled(self):
"""Camera Motion Detection Status."""
"""Return the camera motion detection status."""
return self._motion_status
def set_base_station_mode(self, mode):
@@ -143,7 +146,7 @@ class ArloCam(Camera):
# Get the list of base stations identified by library
base_stations = self.hass.data[DATA_ARLO].base_stations
# Some Arlo cameras does not have basestation
# Some Arlo cameras does not have base station
# So check if there is base station detected first
# if yes, then choose the primary base station
# Set the mode on the chosen base station
@@ -160,3 +163,7 @@ class ArloCam(Camera):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED)
def update(self):
"""Add an attribute-update task to the executor pool."""
self._camera.update()

View File

@@ -11,7 +11,7 @@ from homeassistant.const import (
CONF_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.dispatcher import dispatcher_connect
_LOGGER = logging.getLogger(__name__)
@@ -52,9 +52,9 @@ class AxisCamera(MjpegCamera):
"""Initialize Axis Communications camera component."""
super().__init__(hass, config)
self.port = port
async_dispatcher_connect(hass,
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
self._new_ip)
dispatcher_connect(hass,
DOMAIN + '_' + config[CONF_NAME] + '_new_ip',
self._new_ip)
def _new_ip(self, host):
"""Set new IP for video stream."""

View File

@@ -55,9 +55,9 @@ class FFmpegCamera(Camera):
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
image = yield from ffmpeg.get_image(
image = yield from asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments)
extra_cmd=self._extra_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine

View File

@@ -78,9 +78,9 @@ class ONVIFCamera(Camera):
ffmpeg = ImageFrame(
self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop)
image = yield from ffmpeg.get_image(
image = yield from asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments)
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine

View File

@@ -0,0 +1,143 @@
"""
This component provides support to the Ring Door Bell camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.ring/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.helpers import config_validation as cv
from homeassistant.components.ring import DATA_RING, CONF_ATTRIBUTION
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.util import dt as dt_util
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEPENDENCIES = ['ring', 'ffmpeg']
FORCE_REFRESH_INTERVAL = timedelta(minutes=45)
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a Ring Door Bell and StickUp Camera."""
ring = hass.data[DATA_RING]
cams = []
for camera in ring.doorbells:
cams.append(RingCam(hass, camera, config))
for camera in ring.stickup_cams:
cams.append(RingCam(hass, camera, config))
async_add_devices(cams, True)
return True
class RingCam(Camera):
"""An implementation of a Ring Door Bell camera."""
def __init__(self, hass, camera, device_info):
"""Initialize a Ring Door Bell camera."""
super(RingCam, self).__init__()
self._camera = camera
self._hass = hass
self._name = self._camera.name
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_video_id = self._camera.last_recording_id
self._video_url = self._camera.recording_url(self._last_video_id)
self._utcnow = dt_util.utcnow()
self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
'device_id': self._camera.id,
'firmware': self._camera.firmware,
'kind': self._camera.kind,
'timezone': self._camera.timezone,
'type': self._camera.family,
'video_url': self._video_url,
'video_id': self._last_video_id
}
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
if self._video_url is None:
return
image = yield from asyncio.shield(ffmpeg.get_image(
self._video_url, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
if self._video_url is None:
return
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
yield from stream.open_camera(
self._video_url, extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@property
def should_poll(self):
"""Update the image periodically."""
return True
def update(self):
"""Update camera entity and refresh attributes."""
_LOGGER.debug("Checking if Ring DoorBell needs to refresh video_url")
self._camera.update()
self._utcnow = dt_util.utcnow()
last_recording_id = self._camera.last_recording_id
if self._last_video_id != last_recording_id or \
self._utcnow >= self._expires_at:
_LOGGER.info("Ring DoorBell properties refreshed")
# update attributes if new video or if URL has expired
self._last_video_id = self._camera.last_recording_id
self._video_url = self._camera.recording_url(self._last_video_id)
self._expires_at = FORCE_REFRESH_INTERVAL + self._utcnow

View File

@@ -1,17 +1,25 @@
# Describes the format for available camera services
enable_motion_detection:
description: Enable the motion detection in a camera
description: Enable the motion detection in a camera.
fields:
entity_id:
description: Name(s) of entities to enable motion detection
description: Name(s) of entities to enable motion detection.
example: 'camera.living_room_camera'
disable_motion_detection:
description: Disable the motion detection in a camera
description: Disable the motion detection in a camera.
fields:
entity_id:
description: Name(s) of entities to disable motion detection
description: Name(s) of entities to disable motion detection.
example: 'camera.living_room_camera'
snapshot:
description: Take a snapshot from a camera.
fields:
entity_id:
description: Name(s) of entities to create snapshots from.
example: 'camera.living_room_camera'
filename:
description: Template of a Filename. Variable is entity_id.
example: '/tmp/snapshot_{{ entity_id }}'

View File

@@ -0,0 +1,67 @@
"""
Camera support for the Skybell HD Doorbell.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.skybell/
"""
from datetime import timedelta
import logging
import requests
from homeassistant.components.camera import Camera
from homeassistant.components.skybell import (
DOMAIN as SKYBELL_DOMAIN, SkybellDevice)
DEPENDENCIES = ['skybell']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the platform for a Skybell device."""
skybell = hass.data.get(SKYBELL_DOMAIN)
sensors = []
for device in skybell.get_devices():
sensors.append(SkybellCamera(device))
add_devices(sensors, True)
class SkybellCamera(SkybellDevice, Camera):
"""A camera implementation for Skybell devices."""
def __init__(self, device):
"""Initialize a camera for a Skybell device."""
SkybellDevice.__init__(self, device)
Camera.__init__(self)
self._name = self._device.name
self._url = None
self._response = None
@property
def name(self):
"""Return the name of the sensor."""
return self._name
def camera_image(self):
"""Get the latest camera image."""
super().update()
if self._url != self._device.image:
self._url = self._device.image
try:
self._response = requests.get(
self._url, stream=True, timeout=10)
except requests.HTTPError as err:
_LOGGER.warning("Failed to get camera image: %s", err)
self._response = None
if not self._response:
return None
return self._response.content

View File

@@ -16,11 +16,11 @@ from homeassistant.const import (
from homeassistant.components.camera import (
Camera, PLATFORM_SCHEMA)
from homeassistant.helpers.aiohttp_client import (
async_create_clientsession,
async_aiohttp_proxy_web)
async_aiohttp_proxy_web,
async_get_clientsession)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['py-synology==0.1.1']
REQUIREMENTS = ['py-synology==0.1.5']
_LOGGER = logging.getLogger(__name__)
@@ -58,13 +58,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return False
cameras = surveillance.get_all_cameras()
websession = async_create_clientsession(hass, verify_ssl)
# add cameras
devices = []
for camera in cameras:
if not config.get(CONF_WHITELIST):
device = SynologyCamera(websession, surveillance, camera.camera_id)
device = SynologyCamera(surveillance, camera.camera_id, verify_ssl)
devices.append(device)
async_add_devices(devices)
@@ -73,12 +72,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class SynologyCamera(Camera):
"""An implementation of a Synology NAS based IP camera."""
def __init__(self, websession, surveillance, camera_id):
def __init__(self, surveillance, camera_id, verify_ssl):
"""Initialize a Synology Surveillance Station camera."""
super().__init__()
self._websession = websession
self._surveillance = surveillance
self._camera_id = camera_id
self._verify_ssl = verify_ssl
self._camera = self._surveillance.get_camera(camera_id)
self._motion_setting = self._surveillance.get_motion_setting(camera_id)
self.is_streaming = self._camera.is_enabled
@@ -91,7 +90,9 @@ class SynologyCamera(Camera):
def handle_async_mjpeg_stream(self, request):
"""Return a MJPEG stream image response directly from the camera."""
streaming_url = self._camera.video_stream_url
stream_coro = self._websession.get(streaming_url)
websession = async_get_clientsession(self.hass, self._verify_ssl)
stream_coro = websession.get(streaming_url)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)

View File

@@ -0,0 +1,137 @@
"""
This component provides support for Xiaomi Cameras (HiSilicon Hi3518e V200).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.yi/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
DEFAULT_BRAND = 'YI Home Camera'
DEFAULT_PASSWORD = ''
DEFAULT_PATH = '/tmp/sd/record'
DEFAULT_PORT = 21
DEFAULT_USERNAME = 'root'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a Yi Camera."""
_LOGGER.debug('Received configuration: %s', config)
async_add_devices([YiCamera(hass, config)], True)
class YiCamera(Camera):
"""Define an implementation of a Yi Camera."""
def __init__(self, hass, config):
"""Initialize."""
super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._last_image = None
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config.get(CONF_NAME)
self.host = config.get(CONF_HOST)
self.port = config.get(CONF_PORT)
self.path = config.get(CONF_PATH)
self.user = config.get(CONF_USERNAME)
self.passwd = config.get(CONF_PASSWORD)
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def brand(self):
"""Camera brand."""
return DEFAULT_BRAND
def get_latest_video_url(self):
"""Retrieve the latest video file from the customized Yi FTP server."""
from ftplib import FTP, error_perm
ftp = FTP(self.host)
try:
ftp.login(self.user, self.passwd)
except error_perm as exc:
_LOGGER.error('There was an error while logging into the camera')
_LOGGER.debug(exc)
return False
try:
ftp.cwd(self.path)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s', self.path)
_LOGGER.debug(exc)
return False
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
latest_dir = dirs[-1]
ftp.cwd(latest_dir)
videos = ftp.nlst()
if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
return False
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1])
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
url = yield from self.hass.async_add_job(self.get_latest_video_url)
if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = yield from asyncio.shield(ffmpeg.get_image(
url, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
self._last_url = url
return self._last_image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
yield from stream.open_camera(
self._last_url, extra_cmd=self._extra_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()

View File

@@ -9,12 +9,12 @@ from datetime import timedelta
import logging
import os
import functools as ft
from numbers import Number
import voluptuous as vol
from homeassistant.config import load_yaml_config_file
from homeassistant.loader import bind_hass
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
@@ -22,7 +22,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN,
TEMP_CELSIUS)
TEMP_CELSIUS, PRECISION_WHOLE, PRECISION_TENTHS)
DOMAIN = 'climate'
@@ -71,11 +71,6 @@ ATTR_OPERATION_LIST = 'operation_list'
ATTR_SWING_MODE = 'swing_mode'
ATTR_SWING_LIST = 'swing_list'
# The degree of precision for each platform
PRECISION_WHOLE = 1
PRECISION_HALVES = 0.5
PRECISION_TENTHS = 0.1
CONVERTIBLE_ATTRIBUTE = [
ATTR_TEMPERATURE,
ATTR_TARGET_TEMP_LOW,
@@ -236,24 +231,6 @@ def async_setup(hass, config):
load_yaml_config_file,
os.path.join(os.path.dirname(__file__), 'services.yaml'))
@asyncio.coroutine
def _async_update_climate(target_climate):
"""Update climate entity after service stuff."""
update_tasks = []
for climate in target_climate:
if not climate.should_poll:
continue
update_coro = hass.async_add_job(
climate.async_update_ha_state(True))
if hasattr(climate, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_away_mode_set_service(service):
"""Set away mode on target climate devices."""
@@ -261,13 +238,19 @@ def async_setup(hass, config):
away_mode = service.data.get(ATTR_AWAY_MODE)
update_tasks = []
for climate in target_climate:
if away_mode:
yield from climate.async_turn_away_mode_on()
else:
yield from climate.async_turn_away_mode_off()
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service,
@@ -281,10 +264,16 @@ def async_setup(hass, config):
hold_mode = service.data.get(ATTR_HOLD_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_hold_mode(hold_mode)
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service,
@@ -298,13 +287,19 @@ def async_setup(hass, config):
aux_heat = service.data.get(ATTR_AUX_HEAT)
update_tasks = []
for climate in target_climate:
if aux_heat:
yield from climate.async_turn_aux_heat_on()
else:
yield from climate.async_turn_aux_heat_off()
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service,
@@ -316,6 +311,7 @@ def async_setup(hass, config):
"""Set temperature on the target climate devices."""
target_climate = component.async_extract_from_service(service)
update_tasks = []
for climate in target_climate:
kwargs = {}
for value, temp in service.data.items():
@@ -330,7 +326,12 @@ def async_setup(hass, config):
yield from climate.async_set_temperature(**kwargs)
yield from _async_update_climate(target_climate)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service,
@@ -344,10 +345,15 @@ def async_setup(hass, config):
humidity = service.data.get(ATTR_HUMIDITY)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_humidity(humidity)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service,
@@ -361,10 +367,15 @@ def async_setup(hass, config):
fan = service.data.get(ATTR_FAN_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_fan_mode(fan)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service,
@@ -378,10 +389,15 @@ def async_setup(hass, config):
operation_mode = service.data.get(ATTR_OPERATION_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_operation_mode(operation_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service,
@@ -395,10 +411,15 @@ def async_setup(hass, config):
swing_mode = service.data.get(ATTR_SWING_MODE)
update_tasks = []
for climate in target_climate:
yield from climate.async_set_swing_mode(swing_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
yield from _async_update_climate(target_climate)
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service,
@@ -430,12 +451,18 @@ class ClimateDevice(Entity):
def state_attributes(self):
"""Return the optional state attributes."""
data = {
ATTR_CURRENT_TEMPERATURE:
self._convert_for_display(self.current_temperature),
ATTR_MIN_TEMP: self._convert_for_display(self.min_temp),
ATTR_MAX_TEMP: self._convert_for_display(self.max_temp),
ATTR_TEMPERATURE:
self._convert_for_display(self.target_temperature),
ATTR_CURRENT_TEMPERATURE: show_temp(
self.hass, self.current_temperature, self.temperature_unit,
self.precision),
ATTR_MIN_TEMP: show_temp(
self.hass, self.min_temp, self.temperature_unit,
self.precision),
ATTR_MAX_TEMP: show_temp(
self.hass, self.max_temp, self.temperature_unit,
self.precision),
ATTR_TEMPERATURE: show_temp(
self.hass, self.target_temperature, self.temperature_unit,
self.precision),
}
if self.target_temperature_step is not None:
@@ -443,10 +470,12 @@ class ClimateDevice(Entity):
target_temp_high = self.target_temperature_high
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
self.precision)
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
humidity = self.target_humidity
if humidity is not None:
@@ -707,24 +736,3 @@ class ClimateDevice(Entity):
def max_humidity(self):
"""Return the maximum humidity."""
return 99
def _convert_for_display(self, temp):
"""Convert temperature into preferred units for display purposes."""
if temp is None:
return temp
# if the temperature is not a number this can cause issues
# with polymer components, so bail early there.
if not isinstance(temp, Number):
raise TypeError("Temperature is not a number: %s" % temp)
if self.temperature_unit != self.unit_of_measurement:
temp = convert_temperature(
temp, self.temperature_unit, self.unit_of_measurement)
# Round in the units appropriate
if self.precision == PRECISION_HALVES:
return round(temp * 2) / 2.0
elif self.precision == PRECISION_TENTHS:
return round(temp, 1)
# PRECISION_WHOLE as a fall back
return round(temp)

View File

@@ -0,0 +1,117 @@
"""
Support for the EPH Controls Ember themostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.ephember/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE)
from homeassistant.const import (
TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyephember==0.1.1']
_LOGGER = logging.getLogger(__name__)
# Return cached results if last scan was less then this time ago
SCAN_INTERVAL = timedelta(seconds=120)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the ephember thermostat."""
from pyephember.pyephember import EphEmber
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
try:
ember = EphEmber(username, password)
zones = ember.get_zones()
for zone in zones:
add_devices([EphEmberThermostat(ember, zone)])
except RuntimeError:
_LOGGER.error("Cannot connect to EphEmber")
return
return
class EphEmberThermostat(ClimateDevice):
"""Representation of a HeatmiserV3 thermostat."""
def __init__(self, ember, zone):
"""Initialize the thermostat."""
self._ember = ember
self._zone_name = zone['name']
self._zone = zone
self._hot_water = zone['isHotWater']
@property
def name(self):
"""Return the name of the thermostat, if any."""
return self._zone_name
@property
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
return self._zone['currentTemperature']
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._zone['targetTemperature']
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self._zone['isCurrentlyActive']:
return STATE_HEAT
else:
return STATE_IDLE
@property
def is_aux_heat_on(self):
"""Return true if aux heater."""
return self._zone['isBoostActive']
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
self._ember.activate_boost_by_name(
self._zone_name, self._zone['targetTemperature'])
def turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
self._ember.deactivate_boost_by_name(self._zone_name)
def set_temperature(self, **kwargs):
"""Set new target temperature."""
return
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._zone['targetTemperature']
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._zone['targetTemperature']
def update(self):
"""Get the latest data."""
self._zone = self._ember.get_zone(self._zone_name)

View File

@@ -9,15 +9,12 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, PRECISION_HALVES,
STATE_AUTO, STATE_ON, STATE_OFF,
)
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice)
from homeassistant.const import (
CONF_MAC, TEMP_CELSIUS, CONF_DEVICES, ATTR_TEMPERATURE)
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.5']
REQUIREMENTS = ['python-eq3bt==0.1.6']
_LOGGER = logging.getLogger(__name__)
@@ -58,15 +55,17 @@ class EQ3BTSmartThermostat(ClimateDevice):
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
# we want to avoid name clash with this module..
# We want to avoid name clash with this module.
import eq3bt as eq3
self.modes = {eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY}
self.modes = {
eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY,
}
self.reverse_modes = {v: k for k, v in self.modes.items()}
@@ -153,15 +152,19 @@ class EQ3BTSmartThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
dev_specific = {
ATTR_STATE_AWAY_END: self._thermostat.away_end,
ATTR_STATE_LOCKED: self._thermostat.locked,
ATTR_STATE_LOW_BAT: self._thermostat.low_battery,
ATTR_STATE_VALVE: self._thermostat.valve_state,
ATTR_STATE_WINDOW_OPEN: self._thermostat.window_open,
ATTR_STATE_AWAY_END: self._thermostat.away_end,
}
return dev_specific
def update(self):
"""Update the data from the thermostat."""
self._thermostat.update()
from bluepy.btle import BTLEException
try:
self._thermostat.update()
except BTLEException as ex:
_LOGGER.warning("Updating the state failed: %s", ex)

View File

@@ -36,7 +36,8 @@ CONF_MAX_TEMP = 'max_temp'
CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration'
CONF_TOLERANCE = 'tolerance'
CONF_COLD_TOLERANCE = 'cold_tolerance'
CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
@@ -48,7 +49,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
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_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float),
vol.Optional(CONF_COLD_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(
float),
vol.Optional(CONF_HOT_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(
float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_KEEP_ALIVE): vol.All(
cv.time_period, cv.positive_timedelta),
@@ -66,12 +70,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
target_temp = config.get(CONF_TARGET_TEMP)
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
cold_tolerance = config.get(CONF_COLD_TOLERANCE)
hot_tolerance = config.get(CONF_HOT_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, tolerance, keep_alive)])
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
hot_tolerance, keep_alive)])
class GenericThermostat(ClimateDevice):
@@ -79,14 +85,15 @@ class GenericThermostat(ClimateDevice):
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
tolerance, keep_alive):
cold_tolerance, hot_tolerance, keep_alive):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
self.heater_entity_id = heater_entity_id
self.ac_mode = ac_mode
self.min_cycle_duration = min_cycle_duration
self._tolerance = tolerance
self._cold_tolerance = cold_tolerance
self._hot_tolerance = hot_tolerance
self._keep_alive = keep_alive
self._enabled = True
@@ -156,6 +163,7 @@ class GenericThermostat(ClimateDevice):
"""Set operation mode."""
if operation_mode == STATE_AUTO:
self._enabled = True
self._async_control_heating()
elif operation_mode == STATE_OFF:
self._enabled = False
if self._is_device_active:
@@ -261,25 +269,29 @@ class GenericThermostat(ClimateDevice):
if self.ac_mode:
is_cooling = self._is_device_active
if is_cooling:
too_cold = self._target_temp - self._cur_temp > self._tolerance
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
if too_cold:
_LOGGER.info('Turning off AC %s', self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_hot = self._cur_temp - self._target_temp > self._tolerance
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
if too_hot:
_LOGGER.info('Turning on AC %s', self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)
else:
is_heating = self._is_device_active
if is_heating:
too_hot = self._cur_temp - self._target_temp > self._tolerance
too_hot = self._cur_temp - self._target_temp >= \
self._hot_tolerance
if too_hot:
_LOGGER.info('Turning off heater %s',
self.heater_entity_id)
switch.async_turn_off(self.hass, self.heater_entity_id)
else:
too_cold = self._target_temp - self._cur_temp > self._tolerance
too_cold = self._target_temp - self._cur_temp >= \
self._cold_tolerance
if too_cold:
_LOGGER.info('Turning on heater %s', self.heater_entity_id)
switch.async_turn_on(self.hass, self.heater_entity_id)

View File

@@ -7,7 +7,6 @@ https://home-assistant.io/components/climate.homematic/
import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
from homeassistant.util.temperature import convert
from homeassistant.const import TEMP_CELSIUS, STATE_UNKNOWN, ATTR_TEMPERATURE
DEPENDENCIES = ['homematic']
@@ -121,12 +120,12 @@ class HMThermostat(HMDevice, ClimateDevice):
@property
def min_temp(self):
"""Return the minimum temperature - 4.5 means off."""
return convert(4.5, TEMP_CELSIUS, self.unit_of_measurement)
return 4.5
@property
def max_temp(self):
"""Return the maximum temperature - 30.5 means on."""
return convert(30.5, TEMP_CELSIUS, self.unit_of_measurement)
return 30.5
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""

View File

@@ -11,16 +11,15 @@ import datetime
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, ATTR_FAN_MODE, ATTR_FAN_LIST,
ATTR_OPERATION_MODE, ATTR_OPERATION_LIST)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
import homeassistant.helpers.config_validation as cv
ATTR_TEMPERATURE, CONF_REGION)
REQUIREMENTS = ['evohomeclient==0.2.5',
'somecomfort==0.4.1']
REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.4.1']
_LOGGER = logging.getLogger(__name__)
@@ -31,7 +30,6 @@ ATTR_CURRENT_OPERATION = 'equipment_output_status'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature'
CONF_REGION = 'region'
DEFAULT_AWAY_TEMPERATURE = 16
DEFAULT_COOL_AWAY_TEMPERATURE = 30

View File

@@ -13,7 +13,11 @@ from homeassistant.const import CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
CONF_SETPOINT_ADDRESS = 'setpoint_address'
CONF_SETPOINT_SHIFT_ADDRESS = 'setpoint_shift_address'
CONF_SETPOINT_SHIFT_STATE_ADDRESS = 'setpoint_shift_state_address'
CONF_SETPOINT_SHIFT_STEP = 'setpoint_shift_step'
CONF_SETPOINT_SHIFT_MAX = 'setpoint_shift_max'
CONF_SETPOINT_SHIFT_MIN = 'setpoint_shift_min'
CONF_TEMPERATURE_ADDRESS = 'temperature_address'
CONF_TARGET_TEMPERATURE_ADDRESS = 'target_temperature_address'
CONF_OPERATION_MODE_ADDRESS = 'operation_mode_address'
@@ -26,13 +30,24 @@ CONF_OPERATION_MODE_NIGHT_ADDRESS = 'operation_mode_night_address'
CONF_OPERATION_MODE_COMFORT_ADDRESS = 'operation_mode_comfort_address'
DEFAULT_NAME = 'KNX Climate'
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEPENDENCIES = ['knx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_SETPOINT_ADDRESS): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Required(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_STEP,
default=DEFAULT_SETPOINT_SHIFT_STEP): vol.All(
float, vol.Range(min=0, max=2)),
vol.Optional(CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX):
vol.All(int, vol.Range(min=-32, max=0)),
vol.Optional(CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN):
vol.All(int, vol.Range(min=0, max=32)),
vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
@@ -73,6 +88,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
def async_add_devices_config(hass, config, async_add_devices):
"""Set up climate for KNX platform configured within plattform."""
import xknx
climate = xknx.devices.Climate(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
@@ -80,8 +96,16 @@ def async_add_devices_config(hass, config, async_add_devices):
CONF_TEMPERATURE_ADDRESS),
group_address_target_temperature=config.get(
CONF_TARGET_TEMPERATURE_ADDRESS),
group_address_setpoint=config.get(
CONF_SETPOINT_ADDRESS),
group_address_setpoint_shift=config.get(
CONF_SETPOINT_SHIFT_ADDRESS),
group_address_setpoint_shift_state=config.get(
CONF_SETPOINT_SHIFT_STATE_ADDRESS),
setpoint_shift_step=config.get(
CONF_SETPOINT_SHIFT_STEP),
setpoint_shift_max=config.get(
CONF_SETPOINT_SHIFT_MAX),
setpoint_shift_min=config.get(
CONF_SETPOINT_SHIFT_MIN),
group_address_operation_mode=config.get(
CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
@@ -110,8 +134,6 @@ class KNXClimate(ClimateDevice):
self.async_register_callbacks()
self._unit_of_measurement = TEMP_CELSIUS
self._away = False # not yet supported
self._is_fan_on = False # not yet supported
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@@ -140,14 +162,27 @@ class KNXClimate(ClimateDevice):
@property
def current_temperature(self):
"""Return the current temperature."""
return self.device.temperature
return self.device.temperature.value
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self.device.setpoint_shift_step
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self.device.supports_target_temperature:
return self.device.target_temperature
return None
return self.device.target_temperature.value
@property
def min_temp(self):
"""Return the minimum temperature."""
return self.device.target_temperature_min
@property
def max_temp(self):
"""Return the maximum temperature."""
return self.device.target_temperature_max
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
@@ -155,8 +190,8 @@ class KNXClimate(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
if self.device.supports_target_temperature:
yield from self.device.set_target_temperature(temperature)
yield from self.device.set_target_temperature(temperature)
yield from self.async_update_ha_state()
@property
def current_operation(self):

View File

@@ -18,7 +18,8 @@ from homeassistant.components.climate import (
ATTR_OPERATION_MODE)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME)
from homeassistant.components.mqtt import (CONF_QOS, CONF_RETAIN)
from homeassistant.components.mqtt import (CONF_QOS, CONF_RETAIN,
MQTT_BASE_PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH)
@@ -57,7 +58,8 @@ CONF_SWING_MODE_LIST = 'swing_modes'
CONF_INITIAL = 'initial'
CONF_SEND_IF_OFF = 'send_if_off'
PLATFORM_SCHEMA = CLIMATE_PLATFORM_SCHEMA.extend({
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic,

View File

@@ -1,132 +1,102 @@
# Describes the format for available climate services
set_aux_heat:
description: Turn auxiliary heater on/off for climate device
description: Turn auxiliary heater on/off for climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
aux_heat:
description: New value of axillary heater
description: New value of axillary heater.
example: true
set_away_mode:
description: Turn away mode on/off for climate device
description: Turn away mode on/off for climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
away_mode:
description: New value of away mode
description: New value of away mode.
example: true
set_hold_mode:
description: Turn hold mode for climate device
description: Turn hold mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
hold_mode:
description: New value of hold mode
example: 'away'
set_temperature:
description: Set target temperature of climate device
description: Set target temperature of climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
temperature:
description: New target temperature for hvac
description: New target temperature for HVAC.
example: 25
target_temp_high:
description: New target high tempereature for hvac
description: New target high tempereature for HVAC.
example: 26
target_temp_low:
description: New target low temperature for hvac
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
description: Set target humidity of climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
humidity:
description: New target humidity for climate device
description: New target humidity for climate device.
example: 60
set_fan_mode:
description: Set fan operation for climate device
description: Set fan operation for climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.nest'
fan_mode:
description: New value of fan mode
description: New value of fan mode.
example: On Low
set_operation_mode:
description: Set operation mode for climate device
description: Set operation mode for climate device.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.nest'
operation_mode:
description: New value of operation mode
description: New value of operation mode.
example: Heat
set_swing_mode:
description: Set swing operation for climate device
description: Set swing operation for climate device.
fields:
entity_id:
description: Name(s) of entities to change
example: '.nest'
description: Name(s) of entities to change.
example: 'climate.nest'
swing_mode:
description: New value of swing mode
example: 1
description: New value of swing mode.
example: 1
ecobee_set_fan_min_on_time:
description: Set the minimum fan on time
description: Set the minimum fan on time.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
fan_min_on_time:
description: New value of fan min on time
description: New value of fan min on time.
example: 5
ecobee_resume_program:
description: Resume the programmed schedule
description: Resume the programmed schedule.
fields:
entity_id:
description: Name(s) of entities to change
description: Name(s) of entities to change.
example: 'climate.kitchen'
resume_all:
description: Resume all events and return to the scheduled program. This default to false which removes only the top event.
example: true

View File

@@ -35,7 +35,6 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
self.entity_id = ENTITY_ID_FORMAT.format(self.tesla_id)
self._target_temperature = None
self._temperature = None
self._name = self.tesla_device.name
@property
def current_operation(self):

View File

@@ -0,0 +1,90 @@
"""
Toon van Eneco Thermostat Support.
This provides a component for the rebranded Quby thermostat as provided by
Eneco.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.toon/
"""
import homeassistant.components.toon as toon_main
from homeassistant.components.climate import (
ClimateDevice, ATTR_TEMPERATURE, STATE_PERFORMANCE, STATE_HEAT, STATE_ECO,
STATE_COOL)
from homeassistant.const import TEMP_CELSIUS
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Toon thermostat."""
add_devices([ThermostatDevice(hass)], True)
class ThermostatDevice(ClimateDevice):
"""Interface class for the toon module and HA."""
def __init__(self, hass):
"""Initialize the device."""
self._name = 'Toon van Eneco'
self.hass = hass
self.thermos = hass.data[toon_main.TOON_HANDLE]
self._state = None
self._temperature = None
self._setpoint = None
self._operation_list = [
STATE_PERFORMANCE,
STATE_HEAT,
STATE_ECO,
STATE_COOL,
]
@property
def name(self):
"""Name of this Thermostat."""
return self._name
@property
def temperature_unit(self):
"""The unit of measurement used by the platform."""
return TEMP_CELSIUS
@property
def current_operation(self):
"""Return current operation i.e. comfort, home, away."""
state = self.thermos.get_data('state')
return state
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def current_temperature(self):
"""Return the current temperature."""
return self.thermos.get_data('temp')
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.thermos.get_data('setpoint')
def set_temperature(self, **kwargs):
"""Change the setpoint of the thermostat."""
temp = kwargs.get(ATTR_TEMPERATURE)
self.thermos.set_temp(temp)
def set_operation_mode(self, operation_mode):
"""Set new operation mode as toonlib requires it."""
toonlib_values = {
STATE_PERFORMANCE: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep',
}
self.thermos.set_state(toonlib_values[operation_mode])
def update(self):
"""Update local state."""
self.thermos.update()

View File

@@ -4,46 +4,51 @@ Support for Wink thermostats, Air Conditioners, and Water Heaters.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
"""
import logging
import asyncio
import logging
from homeassistant.components.wink import WinkDevice, DOMAIN
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, ClimateDevice,
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE, STATE_FAN_ONLY,
ATTR_CURRENT_HUMIDITY, STATE_ECO, STATE_ELECTRIC,
STATE_PERFORMANCE, STATE_HIGH_DEMAND,
STATE_HEAT_PUMP, STATE_GAS)
STATE_ECO, STATE_GAS, STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ELECTRIC,
STATE_FAN_ONLY, STATE_HEAT_PUMP, ATTR_TEMPERATURE, STATE_HIGH_DEMAND,
STATE_PERFORMANCE, ATTR_TARGET_TEMP_LOW, ATTR_CURRENT_HUMIDITY,
ATTR_TARGET_TEMP_HIGH, ClimateDevice)
from homeassistant.components.wink import DOMAIN, WinkDevice
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON,
STATE_OFF, STATE_UNKNOWN)
STATE_ON, STATE_OFF, TEMP_CELSIUS, STATE_UNKNOWN, PRECISION_TENTHS)
from homeassistant.helpers.temperature import display_temp as show_temp
_LOGGER = logging.getLogger(__name__)
ATTR_ECO_TARGET = 'eco_target'
ATTR_EXTERNAL_TEMPERATURE = 'external_temperature'
ATTR_OCCUPIED = 'occupied'
ATTR_RHEEM_TYPE = 'rheem_type'
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
ATTR_SMART_TEMPERATURE = 'smart_temperature'
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_VACATION_MODE = 'vacation_mode'
DEPENDENCIES = ['wink']
SPEED_LOW = 'low'
SPEED_MEDIUM = 'medium'
SPEED_HIGH = 'high'
HA_STATE_TO_WINK = {STATE_AUTO: 'auto',
STATE_ECO: 'eco',
STATE_FAN_ONLY: 'fan_only',
STATE_HEAT: 'heat_only',
STATE_COOL: 'cool_only',
STATE_PERFORMANCE: 'performance',
STATE_HIGH_DEMAND: 'high_demand',
STATE_HEAT_PUMP: 'heat_pump',
STATE_ELECTRIC: 'electric_only',
STATE_GAS: 'gas',
STATE_OFF: 'off'}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
HA_STATE_TO_WINK = {
STATE_AUTO: 'auto',
STATE_COOL: 'cool_only',
STATE_ECO: 'eco',
STATE_ELECTRIC: 'electric_only',
STATE_FAN_ONLY: 'fan_only',
STATE_GAS: 'gas',
STATE_HEAT: 'heat_only',
STATE_HEAT_PUMP: 'heat_pump',
STATE_HIGH_DEMAND: 'high_demand',
STATE_OFF: 'off',
STATE_PERFORMANCE: 'performance',
}
ATTR_EXTERNAL_TEMPERATURE = "external_temperature"
ATTR_SMART_TEMPERATURE = "smart_temperature"
ATTR_ECO_TARGET = "eco_target"
ATTR_OCCUPIED = "occupied"
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -85,15 +90,18 @@ class WinkThermostat(WinkDevice, ClimateDevice):
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
if self.external_temperature:
data[ATTR_EXTERNAL_TEMPERATURE] = self._convert_for_display(
self.external_temperature)
data[ATTR_EXTERNAL_TEMPERATURE] = show_temp(
self.hass, self.external_temperature, self.temperature_unit,
PRECISION_TENTHS)
if self.smart_temperature:
data[ATTR_SMART_TEMPERATURE] = self.smart_temperature
@@ -139,7 +147,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@property
def eco_target(self):
"""Return status of eco target (Is the termostat in eco mode)."""
"""Return status of eco target (Is the thermostat in eco mode)."""
return self.wink.eco_target()
@property
@@ -249,7 +257,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
error = "Invalid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
@@ -297,7 +305,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
minimum = 7 # Default minimum
min_min = self.wink.min_min_set_point()
min_max = self.wink.min_max_set_point()
return_value = minimum
if self.current_operation == STATE_HEAT:
if min_min:
return_value = min_min
@@ -323,7 +330,6 @@ class WinkThermostat(WinkDevice, ClimateDevice):
maximum = 35 # Default maximum
max_min = self.wink.max_min_set_point()
max_max = self.wink.max_max_set_point()
return_value = maximum
if self.current_operation == STATE_HEAT:
if max_min:
return_value = max_min
@@ -360,13 +366,15 @@ class WinkAC(WinkDevice, ClimateDevice):
target_temp_high = self.target_temperature_high
target_temp_low = self.target_temperature_low
if target_temp_high is not None:
data[ATTR_TARGET_TEMP_HIGH] = self._convert_for_display(
self.target_temperature_high)
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
self.hass, self.target_temperature_high, self.temperature_unit,
PRECISION_TENTHS)
if target_temp_low is not None:
data[ATTR_TARGET_TEMP_LOW] = self._convert_for_display(
self.target_temperature_low)
data["total_consumption"] = self.wink.total_consumption()
data["schedule_enabled"] = self.wink.schedule_enabled()
data[ATTR_TARGET_TEMP_LOW] = show_temp(
self.hass, self.target_temperature_low, self.temperature_unit,
PRECISION_TENTHS)
data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption()
data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled()
return data
@@ -377,11 +385,14 @@ class WinkAC(WinkDevice, ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
"""Return current operation ie. auto_eco, cool_only, fan_only."""
if not self.wink.is_on():
current_op = STATE_OFF
else:
current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode())
wink_mode = self.wink.current_mode()
if wink_mode == "auto_eco":
wink_mode = "eco"
current_op = WINK_STATE_TO_HA.get(wink_mode)
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
@@ -392,11 +403,13 @@ class WinkAC(WinkDevice, ClimateDevice):
op_list = ['off']
modes = self.wink.modes()
for mode in modes:
if mode == "auto_eco":
mode = "eco"
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
error = "Invalid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
@@ -420,15 +433,19 @@ class WinkAC(WinkDevice, ClimateDevice):
@property
def current_fan_mode(self):
"""Return the current fan mode."""
"""
Return the current fan mode.
The official Wink app only supports 3 modes [low, medium, high]
which are equal to [0.33, 0.66, 1.0] respectively.
"""
speed = self.wink.current_fan_speed()
if speed <= 0.4 and speed > 0.3:
if speed <= 0.33:
return SPEED_LOW
elif speed <= 0.8 and speed > 0.5:
elif speed <= 0.66:
return SPEED_MEDIUM
elif speed <= 1.0 and speed > 0.8:
else:
return SPEED_HIGH
return STATE_UNKNOWN
@property
def fan_list(self):
@@ -436,11 +453,16 @@ class WinkAC(WinkDevice, ClimateDevice):
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, fan):
"""Set fan speed."""
"""
Set fan speed.
The official Wink app only supports 3 modes [low, medium, high]
which are equal to [0.33, 0.66, 1.0] respectively.
"""
if fan == SPEED_LOW:
speed = 0.4
speed = 0.33
elif fan == SPEED_MEDIUM:
speed = 0.8
speed = 0.66
elif fan == SPEED_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)
@@ -459,8 +481,8 @@ class WinkWaterHeater(WinkDevice, ClimateDevice):
def device_state_attributes(self):
"""Return the optional state attributes."""
data = {}
data["vacation_mode"] = self.wink.vacation_mode_enabled()
data["rheem_type"] = self.wink.rheem_type()
data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
data[ATTR_RHEEM_TYPE] = self.wink.rheem_type()
return data
@@ -492,7 +514,7 @@ class WinkWaterHeater(WinkDevice, ClimateDevice):
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invaid operation mode mapping. " + mode + \
error = "Invalid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list

View File

@@ -1,47 +1,188 @@
"""Component to integrate the Home Assistant cloud."""
import asyncio
from datetime import datetime
import json
import logging
import os
import voluptuous as vol
from . import http_api, auth_api
from .const import DOMAIN
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE)
from homeassistant.helpers import entityfilter
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home
from . import http_api, iot
from .const import CONFIG_DIR, DOMAIN, SERVERS
REQUIREMENTS = ['warrant==0.5.0']
_LOGGER = logging.getLogger(__name__)
CONF_ALEXA = 'alexa'
CONF_ALEXA_FILTER = 'filter'
CONF_COGNITO_CLIENT_ID = 'cognito_client_id'
CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id'
REQUIREMENTS = ['warrant==0.2.0']
DEPENDENCIES = ['http']
CONF_MODE = 'mode'
MODE_DEV = 'development'
MODE_STAGING = 'staging'
MODE_PRODUCTION = 'production'
DEFAULT_MODE = MODE_DEV
DEPENDENCIES = ['http']
ALEXA_SCHEMA = vol.Schema({
vol.Optional(
CONF_ALEXA_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_MODE, default=DEFAULT_MODE):
vol.In([MODE_DEV, MODE_STAGING, MODE_PRODUCTION]),
vol.In([MODE_DEV] + list(SERVERS)),
# Change to optional when we include real servers
vol.Required(CONF_COGNITO_CLIENT_ID): str,
vol.Required(CONF_USER_POOL_ID): str,
vol.Required(CONF_REGION): str,
vol.Required(CONF_RELAYER): str,
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA
}),
}, extra=vol.ALLOW_EXTRA)
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup(hass, config):
"""Initialize the Home Assistant cloud."""
mode = MODE_PRODUCTION
if DOMAIN in config:
mode = config[DOMAIN].get(CONF_MODE)
kwargs = config[DOMAIN]
else:
kwargs = {CONF_MODE: DEFAULT_MODE}
if mode != 'development':
_LOGGER.error('Only development mode is currently allowed.')
return False
if CONF_ALEXA not in kwargs:
kwargs[CONF_ALEXA] = ALEXA_SCHEMA({})
data = hass.data[DOMAIN] = {
'mode': mode
}
kwargs[CONF_ALEXA] = smart_home.Config(**kwargs[CONF_ALEXA])
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
data['auth'] = yield from hass.async_add_job(auth_api.load_auth, hass)
@asyncio.coroutine
def init_cloud(event):
"""Initialize connection."""
yield from cloud.initialize()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, init_cloud)
yield from http_api.async_setup(hass)
return True
class Cloud:
"""Store the configuration of the cloud connection."""
def __init__(self, hass, mode, cognito_client_id=None, user_pool_id=None,
region=None, relayer=None, alexa=None):
"""Create an instance of Cloud."""
self.hass = hass
self.mode = mode
self.alexa_config = alexa
self.id_token = None
self.access_token = None
self.refresh_token = None
self.iot = iot.CloudIoT(self)
if mode == MODE_DEV:
self.cognito_client_id = cognito_client_id
self.user_pool_id = user_pool_id
self.region = region
self.relayer = relayer
else:
info = SERVERS[mode]
self.cognito_client_id = info['cognito_client_id']
self.user_pool_id = info['user_pool_id']
self.region = info['region']
self.relayer = info['relayer']
@property
def is_logged_in(self):
"""Get if cloud is logged in."""
return self.id_token is not None
@property
def subscription_expired(self):
"""Return a boolen if the subscription has expired."""
# For now, don't enforce subscriptions to exist
if 'custom:sub-exp' not in self.claims:
return False
return dt_util.utcnow() > self.expiration_date
@property
def expiration_date(self):
"""Return the subscription expiration as a UTC datetime object."""
return datetime.combine(
dt_util.parse_date(self.claims['custom:sub-exp']),
datetime.min.time()).replace(tzinfo=dt_util.UTC)
@property
def claims(self):
"""Get the claims from the id token."""
from jose import jwt
return jwt.get_unverified_claims(self.id_token)
@property
def user_info_path(self):
"""Get path to the stored auth."""
return self.path('{}_auth.json'.format(self.mode))
@asyncio.coroutine
def initialize(self):
"""Initialize and load cloud info."""
def load_config():
"""Load the configuration."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
os.mkdir(path)
user_info = self.user_info_path
if os.path.isfile(user_info):
with open(user_info, 'rt') as file:
info = json.loads(file.read())
self.id_token = info['id_token']
self.access_token = info['access_token']
self.refresh_token = info['refresh_token']
yield from self.hass.async_add_job(load_config)
if self.id_token is not None:
yield from self.iot.connect()
def path(self, *parts):
"""Get config path inside cloud dir.
Async friendly.
"""
return self.hass.config.path(CONFIG_DIR, *parts)
@asyncio.coroutine
def logout(self):
"""Close connection and remove all credentials."""
yield from self.iot.disconnect()
self.id_token = None
self.access_token = None
self.refresh_token = None
yield from self.hass.async_add_job(
lambda: os.remove(self.user_info_path))
def write_user_info(self):
"""Write user info to a file."""
with open(self.user_info_path, 'wt') as file:
file.write(json.dumps({
'id_token': self.id_token,
'access_token': self.access_token,
'refresh_token': self.refresh_token,
}, indent=4))

View File

@@ -1,10 +1,7 @@
"""Package to offer tools to authenticate with the cloud."""
import json
"""Package to communicate with the authentication API."""
import hashlib
import logging
import os
from .const import AUTH_FILE, SERVERS
from .util import get_mode
_LOGGER = logging.getLogger(__name__)
@@ -61,210 +58,119 @@ def _map_aws_exception(err):
return ex(err.response['Error']['Message'])
def load_auth(hass):
"""Load authentication from disk and verify it."""
info = _read_info(hass)
if info is None:
return Auth(hass)
auth = Auth(hass, _cognito(
hass,
id_token=info['id_token'],
access_token=info['access_token'],
refresh_token=info['refresh_token'],
))
if auth.validate_auth():
return auth
return Auth(hass)
def _generate_username(email):
"""Generate a username from an email address."""
return hashlib.sha512(email.encode('utf-8')).hexdigest()
def register(hass, email, password):
def register(cloud, email, password):
"""Register a new account."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud)
try:
cognito.register(email, password)
cognito.register(_generate_username(email), password, email=email)
except ClientError as err:
raise _map_aws_exception(err)
def confirm_register(hass, confirmation_code, email):
def confirm_register(cloud, confirmation_code, email):
"""Confirm confirmation code after registration."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud)
try:
cognito.confirm_sign_up(confirmation_code, email)
cognito.confirm_sign_up(confirmation_code, _generate_username(email))
except ClientError as err:
raise _map_aws_exception(err)
def forgot_password(hass, email):
def forgot_password(cloud, email):
"""Initiate forgotten password flow."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud, username=_generate_username(email))
try:
cognito.initiate_forgot_password()
except ClientError as err:
raise _map_aws_exception(err)
def confirm_forgot_password(hass, confirmation_code, email, new_password):
def confirm_forgot_password(cloud, confirmation_code, email, new_password):
"""Confirm forgotten password code and change password."""
from botocore.exceptions import ClientError
cognito = _cognito(hass, username=email)
cognito = _cognito(cloud, username=_generate_username(email))
try:
cognito.confirm_forgot_password(confirmation_code, new_password)
except ClientError as err:
raise _map_aws_exception(err)
class Auth(object):
"""Class that holds Cloud authentication."""
def __init__(self, hass, cognito=None):
"""Initialize Hass cloud info object."""
self.hass = hass
self.cognito = cognito
self.account = None
@property
def is_logged_in(self):
"""Return if user is logged in."""
return self.account is not None
def validate_auth(self):
"""Validate that the contained auth is valid."""
from botocore.exceptions import ClientError
try:
self._refresh_account_info()
except ClientError as err:
if err.response['Error']['Code'] != 'NotAuthorizedException':
_LOGGER.error('Unexpected error verifying auth: %s', err)
return False
try:
self.renew_access_token()
self._refresh_account_info()
except ClientError:
_LOGGER.error('Unable to refresh auth token: %s', err)
return False
return True
def login(self, username, password):
"""Login using a username and password."""
from botocore.exceptions import ClientError
from warrant.exceptions import ForceChangePasswordException
cognito = _cognito(self.hass, username=username)
try:
cognito.authenticate(password=password)
self.cognito = cognito
self._refresh_account_info()
_write_info(self.hass, self)
except ForceChangePasswordException as err:
raise PasswordChangeRequired
except ClientError as err:
raise _map_aws_exception(err)
def _refresh_account_info(self):
"""Refresh the account info.
Raises boto3 exceptions.
"""
self.account = self.cognito.get_user()
def renew_access_token(self):
"""Refresh token."""
from botocore.exceptions import ClientError
try:
self.cognito.renew_access_token()
_write_info(self.hass, self)
return True
except ClientError as err:
_LOGGER.error('Error refreshing token: %s', err)
return False
def logout(self):
"""Invalidate token."""
from botocore.exceptions import ClientError
try:
self.cognito.logout()
self.account = None
_write_info(self.hass, self)
except ClientError as err:
raise _map_aws_exception(err)
def login(cloud, email, password):
"""Log user in and fetch certificate."""
cognito = _authenticate(cloud, email, password)
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.refresh_token = cognito.refresh_token
cloud.write_user_info()
def _read_info(hass):
"""Read auth file."""
path = hass.config.path(AUTH_FILE)
def check_token(cloud):
"""Check that the token is valid and verify if needed."""
from botocore.exceptions import ClientError
if not os.path.isfile(path):
return None
cognito = _cognito(
cloud,
access_token=cloud.access_token,
refresh_token=cloud.refresh_token)
with open(path) as file:
return json.load(file).get(get_mode(hass))
try:
if cognito.check_token():
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.write_user_info()
except ClientError as err:
raise _map_aws_exception(err)
def _write_info(hass, auth):
"""Write auth info for specified mode.
def _authenticate(cloud, email, password):
"""Log in and return an authenticated Cognito instance."""
from botocore.exceptions import ClientError
from warrant.exceptions import ForceChangePasswordException
Pass in None for data to remove authentication for that mode.
"""
path = hass.config.path(AUTH_FILE)
mode = get_mode(hass)
assert not cloud.is_logged_in, 'Cannot login if already logged in.'
if os.path.isfile(path):
with open(path) as file:
content = json.load(file)
else:
content = {}
cognito = _cognito(cloud, username=email)
if auth.is_logged_in:
content[mode] = {
'id_token': auth.cognito.id_token,
'access_token': auth.cognito.access_token,
'refresh_token': auth.cognito.refresh_token,
}
else:
content.pop(mode, None)
try:
cognito.authenticate(password=password)
return cognito
with open(path, 'wt') as file:
file.write(json.dumps(content, indent=4, sort_keys=True))
except ForceChangePasswordException as err:
raise PasswordChangeRequired
except ClientError as err:
raise _map_aws_exception(err)
def _cognito(hass, **kwargs):
def _cognito(cloud, **kwargs):
"""Get the client credentials."""
import botocore
import boto3
from warrant import Cognito
mode = get_mode(hass)
info = SERVERS.get(mode)
if info is None:
raise ValueError('Mode {} is not supported.'.format(mode))
cognito = Cognito(
user_pool_id=info['identity_pool_id'],
client_id=info['client_id'],
user_pool_region=info['region'],
access_key=info['access_key_id'],
secret_key=info['secret_access_key'],
user_pool_id=cloud.user_pool_id,
client_id=cloud.cognito_client_id,
user_pool_region=cloud.region,
**kwargs
)
cognito.client = boto3.client(
'cognito-idp',
region_name=cloud.region,
config=botocore.config.Config(
signature_version=botocore.UNSIGNED
)
)
return cognito

View File

@@ -1,14 +1,19 @@
"""Constants for the cloud component."""
DOMAIN = 'cloud'
CONFIG_DIR = '.cloud'
REQUEST_TIMEOUT = 10
AUTH_FILE = '.cloud'
SERVERS = {
'development': {
'client_id': '3k755iqfcgv8t12o4pl662mnos',
'identity_pool_id': 'us-west-2_vDOfweDJo',
'region': 'us-west-2',
'access_key_id': 'AKIAJGRK7MILPRJTT2ZQ',
'secret_access_key': 'lscdYBApxrLWL0HKuVqVXWv3ou8ZVXgG7rZBu/Sz'
}
# Example entry:
# 'production': {
# 'cognito_client_id': '',
# 'user_pool_id': '',
# 'region': '',
# 'relayer': ''
# }
}
MESSAGE_EXPIRATION = """
It looks like your Home Assistant Cloud subscription has expired. Please check
your [account page](/config/cloud/account) to continue using the service.
"""

View File

@@ -10,7 +10,7 @@ from homeassistant.components.http import (
HomeAssistantView, RequestDataValidator)
from . import auth_api
from .const import REQUEST_TIMEOUT
from .const import DOMAIN, REQUEST_TIMEOUT
_LOGGER = logging.getLogger(__name__)
@@ -74,13 +74,16 @@ class CloudLoginView(HomeAssistantView):
def post(self, request, data):
"""Handle login request."""
hass = request.app['hass']
auth = hass.data['cloud']['auth']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(auth.login, data['email'],
yield from hass.async_add_job(auth_api.login, cloud, data['email'],
data['password'])
return self.json(_auth_data(auth))
hass.async_add_job(cloud.iot.connect)
# Allow cloud to start connecting.
yield from asyncio.sleep(0, loop=hass.loop)
return self.json(_account_data(cloud))
class CloudLogoutView(HomeAssistantView):
@@ -94,10 +97,10 @@ class CloudLogoutView(HomeAssistantView):
def post(self, request):
"""Handle logout request."""
hass = request.app['hass']
auth = hass.data['cloud']['auth']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(auth.logout)
yield from cloud.logout()
return self.json_message('ok')
@@ -112,12 +115,12 @@ class CloudAccountView(HomeAssistantView):
def get(self, request):
"""Get account info."""
hass = request.app['hass']
auth = hass.data['cloud']['auth']
cloud = hass.data[DOMAIN]
if not auth.is_logged_in:
if not cloud.is_logged_in:
return self.json_message('Not logged in', 400)
return self.json(_auth_data(auth))
return self.json(_account_data(cloud))
class CloudRegisterView(HomeAssistantView):
@@ -135,10 +138,11 @@ class CloudRegisterView(HomeAssistantView):
def post(self, request, data):
"""Handle registration request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.register, hass, data['email'], data['password'])
auth_api.register, cloud, data['email'], data['password'])
return self.json_message('ok')
@@ -158,10 +162,11 @@ class CloudConfirmRegisterView(HomeAssistantView):
def post(self, request, data):
"""Handle registration confirmation request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.confirm_register, hass, data['confirmation_code'],
auth_api.confirm_register, cloud, data['confirmation_code'],
data['email'])
return self.json_message('ok')
@@ -181,10 +186,11 @@ class CloudForgotPasswordView(HomeAssistantView):
def post(self, request, data):
"""Handle forgot password request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.forgot_password, hass, data['email'])
auth_api.forgot_password, cloud, data['email'])
return self.json_message('ok')
@@ -205,18 +211,23 @@ class CloudConfirmForgotPasswordView(HomeAssistantView):
def post(self, request, data):
"""Handle forgot password confirm request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.confirm_forgot_password, hass,
auth_api.confirm_forgot_password, cloud,
data['confirmation_code'], data['email'],
data['new_password'])
return self.json_message('ok')
def _auth_data(auth):
def _account_data(cloud):
"""Generate the auth data JSON response."""
claims = cloud.claims
return {
'email': auth.account.email
'email': claims['email'],
'sub_exp': claims.get('custom:sub-exp'),
'cloud': cloud.iot.state,
}

View File

@@ -0,0 +1,227 @@
"""Module to handle messages from Home Assistant cloud."""
import asyncio
import logging
from aiohttp import hdrs, client_exceptions, WSMsgType
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.alexa import smart_home
from homeassistant.util.decorator import Registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import auth_api
from .const import MESSAGE_EXPIRATION
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
STATE_CONNECTING = 'connecting'
STATE_CONNECTED = 'connected'
STATE_DISCONNECTED = 'disconnected'
class UnknownHandler(Exception):
"""Exception raised when trying to handle unknown handler."""
class CloudIoT:
"""Class to manage the IoT connection."""
def __init__(self, cloud):
"""Initialize the CloudIoT class."""
self.cloud = cloud
# The WebSocket client
self.client = None
# Scheduled sleep task till next connection retry
self.retry_task = None
# Boolean to indicate if we wanted the connection to close
self.close_requested = False
# The current number of attempts to connect, impacts wait time
self.tries = 0
# Current state of the connection
self.state = STATE_DISCONNECTED
@asyncio.coroutine
def connect(self):
"""Connect to the IoT broker."""
hass = self.cloud.hass
if self.cloud.subscription_expired:
# Try refreshing the token to see if it is still expired.
yield from hass.async_add_job(auth_api.check_token, self.cloud)
if self.cloud.subscription_expired:
hass.components.persistent_notification.async_create(
MESSAGE_EXPIRATION, 'Subscription expired',
'cloud_subscription_expired')
self.state = STATE_DISCONNECTED
return
if self.state == STATE_CONNECTED:
raise RuntimeError('Already connected')
self.state = STATE_CONNECTING
self.close_requested = False
remove_hass_stop_listener = None
session = async_get_clientsession(self.cloud.hass)
client = None
disconnect_warn = None
@asyncio.coroutine
def _handle_hass_stop(event):
"""Handle Home Assistant shutting down."""
nonlocal remove_hass_stop_listener
remove_hass_stop_listener = None
yield from self.disconnect()
try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
self.client = client = yield from session.ws_connect(
self.cloud.relayer, headers={
hdrs.AUTHORIZATION:
'Bearer {}'.format(self.cloud.id_token)
})
self.tries = 0
remove_hass_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _handle_hass_stop)
_LOGGER.info('Connected')
self.state = STATE_CONNECTED
while not client.closed:
msg = yield from client.receive()
if msg.type in (WSMsgType.ERROR, WSMsgType.CLOSED,
WSMsgType.CLOSING):
disconnect_warn = 'Connection cancelled.'
break
elif msg.type != WSMsgType.TEXT:
disconnect_warn = 'Received non-Text message: {}'.format(
msg.type)
break
try:
msg = msg.json()
except ValueError:
disconnect_warn = 'Received invalid JSON.'
break
_LOGGER.debug('Received message: %s', msg)
response = {
'msgid': msg['msgid'],
}
try:
result = yield from async_handle_message(
hass, self.cloud, msg['handler'], msg['payload'])
# No response from handler
if result is None:
continue
response['payload'] = result
except UnknownHandler:
response['error'] = 'unknown-handler'
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error handling message')
response['error'] = 'exception'
_LOGGER.debug('Publishing message: %s', response)
yield from client.send_json(response)
except auth_api.CloudError:
_LOGGER.warning('Unable to connect: Unable to refresh token.')
except client_exceptions.WSServerHandshakeError as err:
if err.code == 401:
disconnect_warn = 'Invalid auth.'
self.close_requested = True
# Should we notify user?
else:
_LOGGER.warning('Unable to connect: %s', err)
except client_exceptions.ClientError as err:
_LOGGER.warning('Unable to connect: %s', err)
except Exception: # pylint: disable=broad-except
if not self.close_requested:
_LOGGER.exception('Unexpected error')
finally:
if disconnect_warn is not None:
_LOGGER.warning('Connection closed: %s', disconnect_warn)
if remove_hass_stop_listener is not None:
remove_hass_stop_listener()
if client is not None:
self.client = None
yield from client.close()
if self.close_requested:
self.state = STATE_DISCONNECTED
else:
self.state = STATE_CONNECTING
self.tries += 1
try:
# Sleep 0, 5, 10, 15 … up to 30 seconds between retries
self.retry_task = hass.async_add_job(asyncio.sleep(
min(30, (self.tries - 1) * 5), loop=hass.loop))
yield from self.retry_task
self.retry_task = None
hass.async_add_job(self.connect())
except asyncio.CancelledError:
# Happens if disconnect called
pass
@asyncio.coroutine
def disconnect(self):
"""Disconnect the client."""
self.close_requested = True
if self.client is not None:
yield from self.client.close()
elif self.retry_task is not None:
self.retry_task.cancel()
@asyncio.coroutine
def async_handle_message(hass, cloud, handler_name, payload):
"""Handle incoming IoT message."""
handler = HANDLERS.get(handler_name)
if handler is None:
raise UnknownHandler()
return (yield from handler(hass, cloud, payload))
@HANDLERS.register('alexa')
@asyncio.coroutine
def async_handle_alexa(hass, cloud, payload):
"""Handle an incoming IoT message for Alexa."""
return (yield from smart_home.async_handle_message(hass,
cloud.alexa_config,
payload))
@HANDLERS.register('cloud')
@asyncio.coroutine
def async_handle_cloud(hass, cloud, payload):
"""Handle an incoming IoT message for cloud component."""
action = payload['action']
if action == 'logout':
yield from cloud.logout()
_LOGGER.error('You have been logged out from Home Assistant cloud: %s',
payload['reason'])
else:
_LOGGER.warning('Received unknown cloud action: %s', action)
return None

View File

@@ -1,10 +0,0 @@
"""Utilities for the cloud integration."""
from .const import DOMAIN
def get_mode(hass):
"""Return the current mode of the cloud component.
Async friendly.
"""
return hass.data[DOMAIN]['mode']

View File

@@ -8,7 +8,6 @@ from homeassistant.core import callback
from homeassistant.const import EVENT_COMPONENT_LOADED, CONF_ID
from homeassistant.setup import (
async_prepare_setup_platform, ATTR_COMPONENT)
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
from homeassistant.util.yaml import load_yaml, dump
@@ -21,7 +20,8 @@ ON_DEMAND = ('zwave')
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the config component."""
register_built_in_panel(hass, 'config', 'Configuration', 'mdi:settings')
yield from hass.components.frontend.async_register_built_in_panel(
'config', 'config', 'mdi:settings')
@asyncio.coroutine
def setup_panel(panel_name):

View File

@@ -1,17 +1,19 @@
"""Provide configuration end points for Z-Wave."""
import asyncio
import logging
from collections import deque
from aiohttp.web import Response
import homeassistant.core as ha
from homeassistant.const import HTTP_NOT_FOUND
from homeassistant.const import HTTP_NOT_FOUND, HTTP_OK
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.zwave import const, DEVICE_CONFIG_SCHEMA_ENTRY
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONFIG_PATH = 'zwave_device_config.yaml'
OZW_LOG_FILENAME = 'OZW_Log.txt'
URL_API_OZW_LOG = '/api/zwave/ozwlog'
@asyncio.coroutine
@@ -25,12 +27,64 @@ def async_setup(hass):
hass.http.register_view(ZWaveNodeGroupView)
hass.http.register_view(ZWaveNodeConfigView)
hass.http.register_view(ZWaveUserCodeView)
hass.http.register_static_path(
URL_API_OZW_LOG, hass.config.path(OZW_LOG_FILENAME), False)
hass.http.register_view(ZWaveLogView)
hass.http.register_view(ZWaveConfigWriteView)
return True
class ZWaveLogView(HomeAssistantView):
"""View to read the ZWave log file."""
url = "/api/zwave/ozwlog"
name = "api:zwave:ozwlog"
# pylint: disable=no-self-use
@asyncio.coroutine
def get(self, request):
"""Retrieve the lines from ZWave log."""
try:
lines = int(request.query.get('lines', 0))
except ValueError:
return Response(text='Invalid datetime', status=400)
hass = request.app['hass']
response = yield from hass.async_add_job(self._get_log, hass, lines)
return Response(text='\n'.join(response))
def _get_log(self, hass, lines):
"""Retrieve the logfile content."""
logfilepath = hass.config.path(OZW_LOG_FILENAME)
with open(logfilepath, 'r') as logfile:
data = (line.rstrip() for line in logfile)
if lines == 0:
loglines = list(data)
else:
loglines = deque(data, lines)
return loglines
class ZWaveConfigWriteView(HomeAssistantView):
"""View to save the ZWave configuration to zwcfg_xxxxx.xml."""
url = "/api/zwave/saveconfig"
name = "api:zwave:saveconfig"
@ha.callback
def post(self, request):
"""Save cache configuration to zwcfg_xxxxx.xml."""
hass = request.app['hass']
network = hass.data.get(const.DATA_NETWORK)
if network is None:
return self.json_message('No Z-Wave network data found',
HTTP_NOT_FOUND)
_LOGGER.info("Z-Wave configuration written to file.")
network.write_config()
return self.json_message('Z-Wave configuration saved to file.',
HTTP_OK)
class ZWaveNodeValueView(HomeAssistantView):
"""View to return the node values."""

View File

@@ -207,7 +207,7 @@ class Configurator(object):
self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove)
@async_callback
@asyncio.coroutine
def async_handle_service_call(self, call):
"""Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID)
@@ -220,7 +220,8 @@ class Configurator(object):
# field validation goes here?
if callback:
self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {}))
yield from self.hass.async_add_job(callback,
call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self):
"""Generate a unique configurator ID."""

View File

@@ -140,13 +140,13 @@ def async_setup(hass, config):
hass.services.async_register(
DOMAIN, SERVICE_INCREMENT, async_handler_service,
descriptions[DOMAIN][SERVICE_INCREMENT], SERVICE_SCHEMA)
descriptions[SERVICE_INCREMENT], SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_DECREMENT, async_handler_service,
descriptions[DOMAIN][SERVICE_DECREMENT], SERVICE_SCHEMA)
descriptions[SERVICE_DECREMENT], SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_RESET, async_handler_service,
descriptions[DOMAIN][SERVICE_RESET], SERVICE_SCHEMA)
descriptions[SERVICE_RESET], SERVICE_SCHEMA)
yield from component.async_add_entities(entities)
return True

View File

@@ -0,0 +1,20 @@
# Describes the format for available counter services
decrement:
description: Decrement a counter.
fields:
entity_id:
description: Entity id of the counter to decrement.
example: 'counter.count0'
increment:
description: Increment a counter.
fields:
entity_id:
description: Entity id of the counter to increment.
example: 'counter.count0'
reset:
description: Reset a counter.
fields:
entity_id:
description: Entity id of the counter to reset.
example: 'counter.count0'

View File

@@ -169,21 +169,12 @@ def async_setup(hass, config):
params.pop(ATTR_ENTITY_ID, None)
# call method
update_tasks = []
for cover in covers:
yield from getattr(cover, method['method'])(**params)
update_tasks = []
for cover in covers:
if not cover.should_poll:
continue
update_coro = hass.async_add_job(
cover.async_update_ha_state(True))
if hasattr(cover, 'async_update'):
update_tasks.append(update_coro)
else:
yield from update_coro
update_tasks.append(cover.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)

View File

@@ -4,6 +4,7 @@ Support for Lutron Caseta shades.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.lutron_caseta/
"""
import asyncio
import logging
from homeassistant.components.cover import (
@@ -18,7 +19,8 @@ DEPENDENCIES = ['lutron_caseta']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Lutron Caseta shades as a cover device."""
devs = []
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
@@ -27,7 +29,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
dev = LutronCasetaCover(cover_device, bridge)
devs.append(dev)
add_devices(devs, True)
async_add_devices(devs, True)
class LutronCasetaCover(LutronCasetaDevice, CoverDevice):
@@ -48,21 +50,25 @@ class LutronCasetaCover(LutronCasetaDevice, CoverDevice):
"""Return the current position of cover."""
return self._state['current_state']
def close_cover(self, **kwargs):
@asyncio.coroutine
def async_close_cover(self, **kwargs):
"""Close the cover."""
self._smartbridge.set_value(self._device_id, 0)
def open_cover(self, **kwargs):
@asyncio.coroutine
def async_open_cover(self, **kwargs):
"""Open the cover."""
self._smartbridge.set_value(self._device_id, 100)
def set_cover_position(self, **kwargs):
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
"""Move the shade to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
self._smartbridge.set_value(self._device_id, position)
def update(self):
@asyncio.coroutine
def async_update(self):
"""Call when forcing a refresh of the device."""
self._state = self._smartbridge.get_device_by_id(self._device_id)
_LOGGER.debug(self._state)

View File

@@ -104,6 +104,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MQTT Cover."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass

View File

@@ -103,6 +103,11 @@ class RflinkCover(RflinkCommand, CoverDevice):
"""No polling available in RFlink cover."""
return False
@property
def is_closed(self):
"""Return if the cover is closed."""
return None
def async_close_cover(self, **kwargs):
"""Turn the device close."""
return self._async_handle_command("close_cover")

View File

@@ -1,71 +1,63 @@
open_cover:
description: Open all or specified cover
# Describes the format for available cover services
open_cover:
description: Open all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to open
description: Name(s) of cover(s) to open.
example: 'cover.living_room'
close_cover:
description: Close all or specified cover
description: Close all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to close
description: Name(s) of cover(s) to close.
example: 'cover.living_room'
set_cover_position:
description: Move to specific position all or specified cover
description: Move to specific position all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to set cover position
description: Name(s) of cover(s) to set cover position.
example: 'cover.living_room'
position:
description: Position of the cover (0 to 100)
description: Position of the cover (0 to 100).
example: 30
stop_cover:
description: Stop all or specified cover
description: Stop all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to stop
description: Name(s) of cover(s) to stop.
example: 'cover.living_room'
open_cover_tilt:
description: Open all or specified cover tilt
description: Open all or specified cover tilt.
fields:
entity_id:
description: Name(s) of cover(s) tilt to open
description: Name(s) of cover(s) tilt to open.
example: 'cover.living_room'
close_cover_tilt:
description: Close all or specified cover tilt
description: Close all or specified cover tilt.
fields:
entity_id:
description: Name(s) of cover(s) to close tilt
description: Name(s) of cover(s) to close tilt.
example: 'cover.living_room'
set_cover_tilt_position:
description: Move to specific position all or specified cover tilt
description: Move to specific position all or specified cover tilt.
fields:
entity_id:
description: Name(s) of cover(s) to set cover tilt position
description: Name(s) of cover(s) to set cover tilt position.
example: 'cover.living_room'
position:
description: Position of the cover (0 to 100)
description: Position of the cover (0 to 100).
example: 30
stop_cover_tilt:
description: Stop all or specified cover
description: Stop all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to stop
description: Name(s) of cover(s) to stop.
example: 'cover.living_room'

View File

@@ -19,12 +19,12 @@ from homeassistant.const import (
CONF_FRIENDLY_NAME, CONF_ENTITY_ID,
EVENT_HOMEASSISTANT_START, MATCH_ALL,
CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE,
CONF_OPTIMISTIC, STATE_OPEN, STATE_CLOSED)
CONF_ENTITY_PICTURE_TEMPLATE, CONF_OPTIMISTIC,
STATE_OPEN, STATE_CLOSED)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.script import Script
_LOGGER = logging.getLogger(__name__)
@@ -58,6 +58,7 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_TILT_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
vol.Optional(CONF_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean,
vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA,
@@ -82,6 +83,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
position_template = device_config.get(CONF_POSITION_TEMPLATE)
tilt_template = device_config.get(CONF_TILT_TEMPLATE)
icon_template = device_config.get(CONF_ICON_TEMPLATE)
entity_picture_template = device_config.get(
CONF_ENTITY_PICTURE_TEMPLATE)
open_action = device_config.get(OPEN_ACTION)
close_action = device_config.get(CLOSE_ACTION)
stop_action = device_config.get(STOP_ACTION)
@@ -115,6 +118,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if entity_picture_template is not None:
temp_ids = entity_picture_template.extract_entities()
if str(temp_ids) != MATCH_ALL:
template_entity_ids |= set(temp_ids)
if not template_entity_ids:
template_entity_ids = MATCH_ALL
@@ -125,8 +133,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
hass,
device, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action,
entity_picture_template, open_action, close_action,
stop_action, position_action, tilt_action,
optimistic, tilt_optimistic, entity_ids
)
)
@@ -134,7 +142,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error("No covers added")
return False
async_add_devices(covers, True)
async_add_devices(covers)
return True
@@ -143,8 +151,8 @@ class CoverTemplate(CoverDevice):
def __init__(self, hass, device_id, friendly_name, state_template,
position_template, tilt_template, icon_template,
open_action, close_action, stop_action,
position_action, tilt_action,
entity_picture_template, open_action, close_action,
stop_action, position_action, tilt_action,
optimistic, tilt_optimistic, entity_ids):
"""Initialize the Template cover."""
self.hass = hass
@@ -155,6 +163,7 @@ class CoverTemplate(CoverDevice):
self._position_template = position_template
self._tilt_template = tilt_template
self._icon_template = icon_template
self._entity_picture_template = entity_picture_template
self._open_script = None
if open_action is not None:
self._open_script = Script(hass, open_action)
@@ -174,6 +183,7 @@ class CoverTemplate(CoverDevice):
(not state_template and not position_template))
self._tilt_optimistic = tilt_optimistic or not tilt_template
self._icon = None
self._entity_picture = None
self._position = None
self._tilt_value = None
self._entities = entity_ids
@@ -186,14 +196,12 @@ class CoverTemplate(CoverDevice):
self._tilt_template.hass = self.hass
if self._icon_template is not None:
self._icon_template.hass = self.hass
if self._entity_picture_template is not None:
self._entity_picture_template.hass = self.hass
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._position = 100 if state.state == STATE_OPEN else 0
@callback
def template_cover_state_listener(entity, old_state, new_state):
"""Handle target device state changes."""
@@ -241,6 +249,11 @@ class CoverTemplate(CoverDevice):
"""Return the icon to use in the frontend, if any."""
return self._icon
@property
def entity_picture(self):
"""Return the entity picture to use in the frontend, if any."""
return self._entity_picture
@property
def supported_features(self):
"""Flag supported features."""
@@ -288,7 +301,7 @@ class CoverTemplate(CoverDevice):
def async_stop_cover(self, **kwargs):
"""Fire the stop action."""
if self._stop_script:
self.hass.async_add_job(self._stop_script.async_run())
yield from self._stop_script.async_run()
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
@@ -374,16 +387,28 @@ class CoverTemplate(CoverDevice):
except ValueError as ex:
_LOGGER.error(ex)
self._tilt_value = None
if self._icon_template is not None:
for property_name, template in (
('_icon', self._icon_template),
('_entity_picture', self._entity_picture_template)):
if template is None:
continue
try:
self._icon = self._icon_template.async_render()
setattr(self, property_name, template.async_render())
except TemplateError as ex:
friendly_property_name = property_name[1:].replace('_', ' ')
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning('Could not render icon template %s,'
' the state is unknown.', self._name)
_LOGGER.warning('Could not render %s template %s,'
' the state is unknown.',
friendly_property_name, self._name)
return
self._icon = super().icon
_LOGGER.error('Could not render icon template %s: %s',
self._name, ex)
try:
setattr(self, property_name,
getattr(super(), property_name))
except AttributeError:
_LOGGER.error('Could not render %s template %s: %s',
friendly_property_name, self._name, ex)

View File

@@ -18,7 +18,6 @@ from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -77,6 +76,7 @@ ATTR_LOCATION_NAME = 'location_name'
ATTR_MAC = 'mac'
ATTR_NAME = 'name'
ATTR_SOURCE_TYPE = 'source_type'
ATTR_VENDOR = 'vendor'
SOURCE_TYPE_GPS = 'gps'
SOURCE_TYPE_ROUTER = 'router'
@@ -89,10 +89,6 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
cv.time_period, cv.positive_timedelta)
})
DISCOVERY_PLATFORMS = {
SERVICE_NETGEAR: 'netgear',
}
@bind_hass
def is_on(hass: HomeAssistantType, entity_id: str=None):
@@ -180,15 +176,6 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
tracker.async_setup_group()
@callback
def async_device_tracker_discovered(service, info):
"""Handle the discovery of device tracker platforms."""
hass.async_add_job(
async_setup_platform(DISCOVERY_PLATFORMS[service], {}, info))
discovery.async_listen(
hass, DISCOVERY_PLATFORMS.keys(), async_device_tracker_discovered)
@asyncio.coroutine
def async_platform_discovered(platform, info):
"""Load a platform."""
@@ -299,11 +286,6 @@ class DeviceTracker(object):
if device.track:
yield from device.async_update_ha_state()
self.hass.bus.async_fire(EVENT_NEW_DEVICE, {
ATTR_ENTITY_ID: device.entity_id,
ATTR_HOST_NAME: device.host_name,
})
# During init, we ignore the group
if self.group and self.track_new:
self.group.async_set_group(
@@ -313,6 +295,13 @@ class DeviceTracker(object):
# lookup mac vendor string to be stored in config
yield from device.set_vendor_for_mac()
self.hass.bus.async_fire(EVENT_NEW_DEVICE, {
ATTR_ENTITY_ID: device.entity_id,
ATTR_HOST_NAME: device.host_name,
ATTR_MAC: device.mac,
ATTR_VENDOR: device.vendor,
})
# update known_devices.yaml
self.hass.async_add_job(
self.async_update_config(

View File

@@ -12,18 +12,17 @@ from collections import namedtuple
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
import homeassistant.helpers.config_validation as cv
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE,
CONF_PROTOCOL)
REQUIREMENTS = ['pexpect==4.0.1']
_LOGGER = logging.getLogger(__name__)
CONF_MODE = 'mode'
CONF_PROTOCOL = 'protocol'
CONF_PUB_KEY = 'pub_key'
CONF_SSH_KEY = 'ssh_key'
@@ -36,10 +35,8 @@ PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PROTOCOL, default='ssh'):
vol.In(['ssh', 'telnet']),
vol.Optional(CONF_MODE, default='router'):
vol.In(['router', 'ap']),
vol.Optional(CONF_PROTOCOL, default='ssh'): vol.In(['ssh', 'telnet']),
vol.Optional(CONF_MODE, default='router'): vol.In(['router', 'ap']),
vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port,
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
@@ -102,21 +99,18 @@ class AsusWrtDeviceScanner(DeviceScanner):
self.success_init = False
return
self.connection = SshConnection(self.host, self.port,
self.username,
self.password,
self.ssh_key,
self.mode == "ap")
self.connection = SshConnection(
self.host, self.port, self.username, self.password,
self.ssh_key, self.mode == 'ap')
else:
if not self.password:
_LOGGER.error("No password specified")
self.success_init = False
return
self.connection = TelnetConnection(self.host, self.port,
self.username,
self.password,
self.mode == "ap")
self.connection = TelnetConnection(
self.host, self.port, self.username, self.password,
self.mode == 'ap')
self.last_results = {}

View File

@@ -23,7 +23,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.6.3']
REQUIREMENTS = ['aioautomatic==0.6.4']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +35,7 @@ CONF_CURRENT_LOCATION = 'current_location'
DEFAULT_TIMEOUT = 5
DEFAULT_SCOPE = ['location', 'vehicle:profile', 'trip']
DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile']
FULL_SCOPE = DEFAULT_SCOPE + ['current_location']
ATTR_FUEL_LEVEL = 'fuel_level'

View File

@@ -13,7 +13,7 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
REQUIREMENTS = ['fritzconnection==0.6.3']
REQUIREMENTS = ['fritzconnection==0.6.5']
_LOGGER = logging.getLogger(__name__)

View File

@@ -21,6 +21,9 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
ATTR_CURRENT_LATITUDE = 'currentLatitude'
ATTR_CURRENT_LONGITUDE = 'currentLongitude'
BEACON_DEV_PREFIX = 'beacon'
CONF_MOBILE_BEACONS = 'mobile_beacons'
@@ -72,6 +75,9 @@ class GeofencyView(HomeAssistantView):
location_name = data['name']
else:
location_name = STATE_NOT_HOME
if ATTR_CURRENT_LATITUDE in data:
data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE]
data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE]
return (yield from self._set_location(hass, data, location_name))
@@ -96,8 +102,12 @@ class GeofencyView(HomeAssistantView):
data['device'] = slugify(data['device'])
data['name'] = slugify(data['name'])
data[ATTR_LATITUDE] = float(data[ATTR_LATITUDE])
data[ATTR_LONGITUDE] = float(data[ATTR_LONGITUDE])
gps_attributes = [ATTR_LATITUDE, ATTR_LONGITUDE,
ATTR_CURRENT_LATITUDE, ATTR_CURRENT_LONGITUDE]
for attribute in gps_attributes:
if attribute in data:
data[attribute] = float(data[attribute])
return data

View File

@@ -0,0 +1,138 @@
"""
Support for the Hitron CODA-4582U, provided by Rogers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.hitron_coda/
"""
import logging
from collections import namedtuple
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME
)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def get_scanner(_hass, config):
"""Validate the configuration and return a Nmap scanner."""
scanner = HitronCODADeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
Device = namedtuple('Device', ['mac', 'name'])
class HitronCODADeviceScanner(DeviceScanner):
"""This class scans for devices using the CODA's web interface."""
def __init__(self, config):
"""Initialize the scanner."""
self.last_results = []
host = config[CONF_HOST]
self._url = 'http://{}/data/getConnectInfo.asp'.format(host)
self._loginurl = 'http://{}/goform/login'.format(host)
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
self._userid = None
self.success_init = self._update_info()
_LOGGER.info("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 device with the given MAC address."""
name = next((
device.name for device in self.last_results
if device.mac == mac), None)
return name
def _login(self):
"""Log in to the router. This is required for subsequent api calls."""
_LOGGER.info("Logging in to CODA...")
try:
data = [
('user', self._username),
('pws', self._password),
]
res = requests.post(self._loginurl, data=data, timeout=10)
except requests.exceptions.Timeout:
_LOGGER.error(
"Connection to the router timed out at URL %s", self._url)
return False
if res.status_code != 200:
_LOGGER.error(
"Connection failed with http code %s", res.status_code)
return False
try:
self._userid = res.cookies['userid']
return True
except KeyError:
_LOGGER.error("Failed to log in to router")
return False
def _update_info(self):
"""Get ARP from router."""
_LOGGER.info("Fetching...")
if self._userid is None:
if not self._login():
_LOGGER.error("Could not obtain a user ID from the router")
return False
last_results = []
# doing a request
try:
res = requests.get(self._url, timeout=10, cookies={
'userid': self._userid
})
except requests.exceptions.Timeout:
_LOGGER.error(
"Connection to the router timed out at URL %s", self._url)
return False
if res.status_code != 200:
_LOGGER.error(
"Connection failed with http code %s", res.status_code)
return False
try:
result = res.json()
except ValueError:
# If json decoder could not parse the response
_LOGGER.error("Failed to parse response from router")
return False
# parsing response
for info in result:
mac = info['macAddr']
name = info['hostName']
# No address = no item :)
if mac is None:
continue
last_results.append(Device(mac.upper(), name))
self.last_results = last_results
_LOGGER.info("Request successful")
return True

View File

@@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==1.0.2']
REQUIREMENTS = ['librouteros==1.0.4']
MTK_DEFAULT_API_PORT = '8728'
@@ -76,16 +76,47 @@ class MikrotikScanner(DeviceScanner):
port=int(self.port)
)
routerboard_info = self.client(cmd='/system/routerboard/getall')
try:
routerboard_info = self.client(
cmd='/system/routerboard/getall')
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionError):
routerboard_info = None
raise
if routerboard_info:
_LOGGER.info("Connected to Mikrotik %s with IP %s",
routerboard_info[0].get('model', 'Router'),
self.host)
self.connected = True
self.wireless_exist = self.client(
cmd='/interface/wireless/getall'
)
try:
self.capsman_exist = self.client(
cmd='/caps-man/interface/getall'
)
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionError):
self.capsman_exist = False
if not self.capsman_exist:
_LOGGER.info(
'Mikrotik %s: Not a CAPSman controller. Trying '
'local interfaces ',
self.host
)
try:
self.wireless_exist = self.client(
cmd='/interface/wireless/getall'
)
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionError):
self.wireless_exist = False
if not self.wireless_exist:
_LOGGER.info(
'Mikrotik %s: Wireless adapters not found. Try to '
@@ -95,6 +126,7 @@ class MikrotikScanner(DeviceScanner):
)
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionError) as api_error:
_LOGGER.error("Connection error: %s", api_error)
@@ -111,7 +143,9 @@ class MikrotikScanner(DeviceScanner):
def _update_info(self):
"""Retrieve latest information from the Mikrotik box."""
if self.wireless_exist:
if self.capsman_exist:
devices_tracker = 'capsman'
elif self.wireless_exist:
devices_tracker = 'wireless'
else:
devices_tracker = 'ip'
@@ -123,7 +157,11 @@ class MikrotikScanner(DeviceScanner):
)
device_names = self.client(cmd='/ip/dhcp-server/lease/getall')
if self.wireless_exist:
if devices_tracker == 'capsman':
devices = self.client(
cmd='/caps-man/registration-table/getall'
)
elif devices_tracker == 'wireless':
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
)

View File

@@ -5,23 +5,22 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/
"""
import asyncio
import base64
import json
import logging
import base64
from collections import defaultdict
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
from homeassistant.util import slugify, decorator
import homeassistant.helpers.config_validation as cv
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import STATE_HOME
from homeassistant.core import callback
from homeassistant.util import slugify, decorator
DEPENDENCIES = ['mqtt']
REQUIREMENTS = ['libnacl==1.5.2']
REQUIREMENTS = ['libnacl==1.6.1']
_LOGGER = logging.getLogger(__name__)
@@ -34,6 +33,8 @@ CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
DEPENDENCIES = ['mqtt']
OWNTRACKS_TOPIC = 'owntracks/#'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -74,6 +75,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
except ValueError:
# If invalid JSON
_LOGGER.error("Unable to parse payload as JSON: %s", payload)
return
message['topic'] = topic
@@ -90,7 +92,11 @@ def _parse_topic(topic):
Async friendly.
"""
_, user, device, *_ = topic.split('/', 3)
try:
_, user, device, *_ = topic.split('/', 3)
except ValueError:
_LOGGER.error("Can't parse topic: '%s'", topic)
raise
return user, device
@@ -193,7 +199,7 @@ class OwnTracksContext:
self.async_see = async_see
self.secret = secret
self.max_gps_accuracy = max_gps_accuracy
self.mobile_beacons_active = defaultdict(list)
self.mobile_beacons_active = defaultdict(set)
self.regions_entered = defaultdict(list)
self.import_waypoints = import_waypoints
self.waypoint_whitelist = waypoint_whitelist
@@ -228,10 +234,25 @@ class OwnTracksContext:
return True
@asyncio.coroutine
def async_see_beacons(self, dev_id, kwargs_param):
def async_see_beacons(self, hass, dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# Mobile beacons should always be set to the location of the
# tracking device. I get the device state and make the necessary
# changes to kwargs.
device_tracker_state = hass.states.get(
"device_tracker.{}".format(dev_id))
if device_tracker_state is not None:
acc = device_tracker_state.attributes.get("gps_accuracy")
lat = device_tracker_state.attributes.get("latitude")
lon = device_tracker_state.attributes.get("longitude")
kwargs['gps_accuracy'] = acc
kwargs['gps'] = (lat, lon)
# the battery state applies to the tracking device, not the beacon
# kwargs location is the beacon's configured lat/lon
kwargs.pop('battery', None)
for beacon in self.mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
@@ -255,7 +276,7 @@ def async_handle_location_message(hass, context, message):
return
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
yield from context.async_see_beacons(hass, dev_id, kwargs)
@asyncio.coroutine
@@ -265,11 +286,15 @@ def _async_transition_message_enter(hass, context, message, location):
dev_id, kwargs = _parse_see_args(message)
if zone is None and message.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
# Not a HA zone, and a beacon so mobile beacon.
# kwargs will contain the lat/lon of the beacon
# which is not where the beacon actually is
# and is probably set to 0/0
beacons = context.mobile_beacons_active[dev_id]
if location not in beacons:
beacons.append(location)
beacons.add(location)
_LOGGER.info("Added beacon %s", location)
yield from context.async_see_beacons(hass, dev_id, kwargs)
else:
# Normal region
regions = context.regions_entered[dev_id]
@@ -277,9 +302,8 @@ def _async_transition_message_enter(hass, context, message, location):
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(hass, dev_id, kwargs)
@asyncio.coroutine
@@ -291,30 +315,29 @@ def _async_transition_message_leave(hass, context, message, location):
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
return
beacons = context.mobile_beacons_active[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
yield from context.async_see_beacons(hass, dev_id, kwargs)
else:
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(hass, dev_id, kwargs)
return
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
if context.async_valid_accuracy(message):
yield from context.async_see(**kwargs)
yield from context.async_see_beacons(dev_id, kwargs)
beacons = context.mobile_beacons_active[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
yield from context.async_see_beacons(hass, dev_id, kwargs)
@HANDLERS.register('transition')
@@ -344,6 +367,29 @@ def async_handle_transition_message(hass, context, message):
message['event'])
@asyncio.coroutine
def async_handle_waypoint(hass, name_base, waypoint):
"""Handle a waypoint."""
name = waypoint['desc']
pretty_name = '{} - {}'.format(name_base, name)
lat = waypoint['lat']
lon = waypoint['lon']
rad = waypoint['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:
return
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
yield from zone.async_update_ha_state()
@HANDLERS.register('waypoint')
@HANDLERS.register('waypoints')
@asyncio.coroutine
def async_handle_waypoints_message(hass, context, message):
@@ -357,30 +403,17 @@ def async_handle_waypoints_message(hass, context, message):
if user not in context.waypoint_whitelist:
return
wayps = message['waypoints']
if 'waypoints' in message:
wayps = message['waypoints']
else:
wayps = [message]
_LOGGER.info("Got %d waypoints from %s", len(wayps), message['topic'])
name_base = ' '.join(_parse_topic(message['topic']))
for wayp in wayps:
name = wayp['desc']
pretty_name = '{} - {}'.format(name_base, name)
lat = wayp['lat']
lon = wayp['lon']
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)
zone.entity_id = entity_id
yield from zone.async_update_ha_state()
yield from async_handle_waypoint(hass, name_base, wayp)
@HANDLERS.register('encrypted')
@@ -399,15 +432,30 @@ def async_handle_encrypted_message(hass, context, message):
yield from async_handle_message(hass, context, decrypted)
@HANDLERS.register('lwt')
@HANDLERS.register('configuration')
@HANDLERS.register('beacon')
@HANDLERS.register('cmd')
@HANDLERS.register('steps')
@HANDLERS.register('card')
@asyncio.coroutine
def async_handle_not_impl_msg(hass, context, message):
"""Handle valid but not implemented message types."""
_LOGGER.debug('Not handling %s message: %s', message.get("_type"), message)
@asyncio.coroutine
def async_handle_unsupported_msg(hass, context, message):
"""Handle an unsupported or invalid message type."""
_LOGGER.warning('Received unsupported message type: %s.',
message.get('_type'))
@asyncio.coroutine
def async_handle_message(hass, context, message):
"""Handle an OwnTracks message."""
msgtype = message.get('_type')
handler = HANDLERS.get(msgtype)
if handler is None:
error = 'Received unsupported message type: {}.'.format(msgtype)
_LOGGER.warning(error)
handler = HANDLERS.get(msgtype, async_handle_unsupported_msg)
yield from handler(hass, context, message)

View File

@@ -1,41 +1,33 @@
# Describes the format for available device tracker services
see:
description: Control tracked device
description: Control tracked device.
fields:
mac:
description: MAC address of device
example: 'FF:FF:FF:FF:FF:FF'
dev_id:
description: Id of device (find id in known_devices.yaml)
description: Id of device (find id in known_devices.yaml).
example: 'phonedave'
host_name:
description: Hostname of device
example: 'Dave'
location_name:
description: Name of location where device is located (not_home is away)
description: Name of location where device is located (not_home is away).
example: 'home'
gps:
description: GPS coordinates where device is located (latitude, longitude)
description: GPS coordinates where device is located (latitude, longitude).
example: '[51.509802, -0.086692]'
gps_accuracy:
description: Accuracy of GPS coordinates
description: Accuracy of GPS coordinates.
example: '80'
battery:
description: Battery level of device
description: Battery level of device.
example: '100'
icloud:
icloud_lost_iphone:
description: Service to play the lost iphone sound on an iDevice
description: Service to play the lost iphone sound on an iDevice.
fields:
account_name:
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
@@ -43,10 +35,8 @@ icloud:
device_name:
description: Name of the device that will play the sound. This is optional, if it isn't given it will play on all devices for the given account.
example: 'iphonebart'
icloud_set_interval:
description: Service to set the interval of an iDevice
description: Service to set the interval of an iDevice.
fields:
account_name:
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
@@ -57,10 +47,8 @@ icloud:
interval:
description: The interval (in minutes) that the iDevice will have until the according device_tracker entity changes from zone or until this service is used again. This is optional, if it isn't given the interval of the device will revert back to the original interval based on the current state.
example: 1
icloud_update:
description: Service to ask for an update of an iDevice.
fields:
account_name:
description: Name of the account in the config that will be used to look for the device. This is optional, if it isn't given it will use all accounts.
@@ -68,10 +56,8 @@ icloud:
device_name:
description: Name of the device that will be updated. This is optional, if it isn't given it will update all devices for the given account.
example: 'iphonebart'
icloud_reset_account:
description: Service to restart an iCloud account. Helpful when not all devices are found after initializing or when you add a new device.
fields:
account_name:
description: Name of the account in the config that will be restarted. This is optional, if it isn't given it will restart all accounts.

View File

@@ -14,29 +14,29 @@ from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
REQUIREMENTS = ['pysnmp==4.4.2']
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.9']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
CONF_PRIVKEY = 'privkey'
CONF_BASEOID = 'baseoid'
CONF_COMMUNITY = 'community'
CONF_PRIVKEY = 'privkey'
DEFAULT_COMMUNITY = 'public'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_BASEOID): cv.string,
vol.Required(CONF_HOST): 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
})
# pylint: disable=unused-argument
def get_scanner(hass, config):
"""Validate the configuration and return an snmp scanner."""
"""Validate the configuration and return an SNMP scanner."""
scanner = SnmpScanner(config[DOMAIN])
return scanner if scanner.success_init else None

View File

@@ -6,13 +6,14 @@ https://home-assistant.io/components/device_tracker.swisscom/
"""
import logging
from aiohttp.hdrs import CONTENT_TYPE
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -77,7 +78,7 @@ class SwisscomDeviceScanner(DeviceScanner):
def get_swisscom_data(self):
"""Retrieve data from Swisscom and return parsed result."""
url = 'http://{}/ws'.format(self.host)
headers = {'Content-Type': 'application/x-sah-ws-4-call+json'}
headers = {CONTENT_TYPE: 'application/x-sah-ws-4-call+json'}
data = """
{"service":"Devices", "method":"get",
"parameters":{"expression":"lan and not self"}}"""

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