Compare commits

...

207 Commits
0.63 ... 0.64.0

Author SHA1 Message Date
Paulus Schoutsen
84c156e8f5 Version bump to 0.64 2018-02-25 13:17:47 -08:00
Paulus Schoutsen
5dcf92fa2a Merge pull request #12609 from home-assistant/release-0-64
0.64.0
2018-02-25 12:25:02 -08:00
Paulus Schoutsen
6c614df96e Remove braviatv_psk (#12669) 2018-02-25 11:51:45 -08:00
Otto Winter
8d0d676ff2 Fix cast doing I/O in event loop (#12632) 2018-02-25 11:33:56 -08:00
Sebastian Muszynski
781b7687a4 The name of the enum must be used here because of the speed_list. (#12625)
The fan.set_speed example value is lower-case and led to confusion. Both spellings are possible now: Idle & idle
2018-02-25 11:33:56 -08:00
Pascal Vizeli
286baed9ad Hassio update timeout filter list (#12617)
* Update timeout filter list

* Update http.py
2018-02-25 11:33:55 -08:00
Anders Melchiorsen
43ad3ae2d4 Move recorder query out of event loop (#12615) 2018-02-25 11:33:55 -08:00
Scott Bradshaw
19d34daef0 OpenGarage - correctly handle offline status (#12612) (#12613) 2018-02-25 11:33:54 -08:00
Paulus Schoutsen
7c80ef714e Fix voluptuous breaking change things (#12611)
* Fix voluptuous breaking change things

* Change xiaomi aqara back
2018-02-25 11:33:54 -08:00
Anders Melchiorsen
9ca67c36cd Optimize logbook SQL query (#12608) 2018-02-25 11:33:53 -08:00
Thijs de Jong
6aa8916654 Add Tahoma scenes (#12498)
* add scenes to platform

* add scene.tahoma

* requires tahoma-api 0.0.12

* update requirements_all.txt

* hound

* fix pylint error
2018-02-25 11:33:53 -08:00
Otto Winter
2261ce30e3 Cast unique_id and async discovery (#12474)
* Cast unique_id and async discovery

* Lazily load chromecasts

* Lint

* Fixes & Improvements

* Fixes

* Improve disconnects

cast.disconnect with blocking=False does **not** do I/O; it simply sets an event for the socket client looper

* Add tests

* Remove unnecessary calls

* Lint

* Fix use of hass object
2018-02-25 11:33:52 -08:00
kennedyshead
7f5ca314ec Fix mclimate accounts with not only melissa components (#12427)
* Fixes for mclimate accounts with not only melissa components

* Fixes melissa sensor to only use HVAC

* Bumping version to 1.0.3 and remove OP_MODE that is not supported

* Removes STATE_AUTO from translation and tests
2018-02-25 11:33:52 -08:00
Paulus Schoutsen
079724be05 Merge remote-tracking branch 'origin/master' into dev 2018-02-22 15:32:07 -08:00
Jeremy Klein
f899ce8fbf Adding RoomHinting to GoogleAssistant to allow for room annotations. (#12598) 2018-02-22 15:24:41 -08:00
cdce8p
ffd3889271 Updated script/lint (#12600)
* Compare to common ancestor
* Check if no file was changed
2018-02-22 15:22:59 -08:00
Sebastian Muszynski
87c69452f9 Set speed service fixed. (#12602) 2018-02-22 15:21:36 -08:00
Gerard
1af65f8f23 Component for Sony Bravia TV with Pre-Shared Key (#12464) 2018-02-22 21:09:52 +00:00
Mike Megally
f0a1beac5d Allow ignoring call service events in mqtt_eventstream (#12519)
* [WIP] Allow ignoring call service events

This allows a setting a configuration value (False by default to continue the current behavior) which will ignore call service events.

* extra spaces

removed them

* updates from PR review

* removed print

* update spacing

* updated allowed events to allow for custom events, and included some tests

* hound fixes

* Remove unused constant

* Lint
2018-02-22 11:19:18 +01:00
JC Connell
4fdbbc497d Python spotcrime (#12460)
* Added python-spotcrime sensor

* Added python-spotcrime sensor

* Remove accidental included file

* Update indentation to match PEP8

* Remove days from const.py

* Changed default days to 1

* Incorporate changes requested by MartinHjelmare

* Remove unnecessary import and fix lint error
2018-02-21 23:47:15 -08:00
Paulus Schoutsen
de72eb8fe9 Make groups entities again (#12574) 2018-02-21 23:42:23 -08:00
Lukas Barth
2a4971dec7 Add unique_id to Xiaomi Aqara (#12372)
* Add unique_id to Xiaomi Aqara

* Slugify the unique ID

* Use the domain instead of a separate string

* Add underscore

* Remove unique ID from attributes

* Re-add removed attributes

* Remove domain from the unique ID

* Use type or data key

* Also make sure that the data key is not None

* Yes, it does have that member, if that check passes. Thanks pylint.
2018-02-21 23:36:39 -08:00
Robin
4d7fb2c7de Adds folder sensor (#12208)
* Adds folder sensor

The state of the sensor is the time that the most recently modified
file in a folder was modified.

* Address lint errors

* Edit docstrings

Makes the recommended edits to docstrings

* Update .coveragerc

Add sensor/folder.py

* Update folder.py

* Address requests

Address requests changes

* Adds folder

* Adds test, tidy up

* Tidy

* Update test_folder.py

* Update folder.py

* Fix setup

Fix setup with else statement

* Update folder.py

* Update folder.py

* Remove list of files from attributes

* Update test_folder.py

* Update folder.py

* Update test_folder.py

* Update folder.py

* Update folder.py
2018-02-21 23:21:07 -08:00
Greg Laabs
184a54cc58 Fix fix isy994 fan detection (#12595)
* Fixed 3 small issues in isy994 component

1. FanLincs have two nodes: one light and one fan motor. In order for each node to get detected as different Hass entity types, I removed the device-type check for FanLinc. The logic will now fall back on the uom checks which should work just fine. (An alternative approach here would be to special case FanLincs and handle them directly - but seeing as the newer 5.x ISY firmware already handles this much better using NodeDefs, I think this quick and dirty approach is fine for the older firmware.) Fixes #12030
2. Some non-dimming switches were appearing as `light`s in Hass due to an duplicate NodeDef being in the light domain filter. Removed! Fixes #12340
3. The `unqiue_id` property was throwing an error for certain entity types that don't have an `_id` property from the ISY. This issue has always been present, but was exposed by the entity registry which seems to be the first thing to actually try reading the `unique_id` property from the isy994 component.

* Fix ISY994 fan detection

ISY reports "med" in the uom, not "medium"

* Add special-case for FanLincs so the light node is detected properly

* Re-add insteon-type filter for fans, which dropped in a merge error
2018-02-21 22:20:40 -08:00
matthewcky2k
c6480e46c4 Add Bluetooth and NFC card/tag Alarm types (#12151)
* Add Bluetooth and NFC card/tag Alarm types

* removed white space
2018-02-21 17:03:10 -05:00
Pascal Vizeli
b228695907 Hassio cleanup part2 (#12588)
* Update handler.py

* Update handler.py

* Update __init__.py

* Update handler.py

* Update handler.py

* Update __init__.py

* Update tests
2018-02-21 22:42:55 +01:00
Paulus Schoutsen
51c06e35cf Cloud reconnect tweaks (#12586) 2018-02-21 12:55:33 -08:00
Kane610
b8df2d4042 Deconz support water sensor (#12581)
* Add support for smoke detector in deconz

* Support for water sensors in deconz
2018-02-21 12:52:05 -08:00
Anders Melchiorsen
2d36d4d9f3 Set event_id foreign key in recorded states (#12580) 2018-02-21 12:51:20 -08:00
Paulus Schoutsen
03d6071a45 Update pychromecast to 2.0.0 (#12587) 2018-02-21 12:49:56 -08:00
Paulus Schoutsen
6ce9be6b3a Update frontend to 20180221.1 2018-02-21 12:45:10 -08:00
Anders Melchiorsen
ed1a883b52 Fix sonos default errorcodes (#12582) 2018-02-21 21:11:14 +01:00
Anders Melchiorsen
f9ee29a5cd Logbook speedup (#12566)
* Optimize logbook filtering

* Avoid State construction during Events filtering

* Move tuple creation out of loop

* Add benchmark
2018-02-21 10:35:55 -08:00
Paulus Schoutsen
8d0b7adf24 Fix config 404 (#12571) 2018-02-21 13:16:08 +01:00
Oliver
28fec209e0 Basic support of post 2016 AVR-X receivers (#12569) 2018-02-20 23:09:14 -08:00
Anders Melchiorsen
5ad0baf128 Add limitlessled night effect (#12567) 2018-02-20 23:08:45 -08:00
Paulus Schoutsen
49d410546a Frontend version bump to 20180221.0 2018-02-20 17:55:15 -08:00
bottomquark
722926b315 Fix caldav component handling missing dtend (#12562) 2018-02-20 15:37:34 -08:00
Kane610
c898fb1f16 Add support for smoke detector in deconz (#12561) 2018-02-20 15:28:06 -08:00
Pascal Vizeli
4f96eeb06e Cleanup hass.io component (#12556)
* Cleanup hass.io component

* fix lint

* Fix all tests

* Fix lint

* fix lint

* fix doc lint
2018-02-20 15:24:31 -08:00
ChristianKuehnel
316eb59de2 Add new component: BMW connected drive (#12277)
* first working version of BMW connected drive sensor

* extended coveragerc

* fixed blank line

* fixed pylint

* major refactoring after major refactoring in bimmer_connected

* Update are now triggered from BMWConnectedDriveVehicle.
* removed polling from sensor and device_tracker
* backend URL is not detected automatically based on current country
* vehicles are discovered automatically
* updates are async now

resolves:
* https://github.com/ChristianKuehnel/bimmer_connected/issues/3
* https://github.com/ChristianKuehnel/bimmer_connected/issues/5

* improved exception handing

* fixed static analysis findings

* fixed review comments from @MartinHjelmare

* improved startup, data is updated right after sensors were created.

* fixed pylint issue

* updated to latest release of the bimmer_connected library

* updated requirements-all.txt

* fixed comments from @MartinHjelmare

* calling self.update from async_add_job

* removed unused attribute "account"
2018-02-20 23:02:08 +01:00
rubenverhoef
5d29d88888 Added support for milight single channel dimmer (#12558) 2018-02-20 22:30:19 +01:00
Paulus Schoutsen
210226daac Update frontend 2018-02-20 09:08:37 -08:00
Otto Winter
f2a2727a15 Fix WUnderground spamming logs (#12548) 2018-02-20 09:01:34 -08:00
Krasimir Zhelev
1d8a5147e9 Frontier silicon async (#12503)
* Moving frontier_silicon platform to afspai(async fsapi)

* updated requirements_all.txt

* uses afsapi 0.0.2, which supports timeout

* uses afsapi 0.0.3, forward(before next) and rewind(before prev) renamed

* Moving frontier_silicon platform to afspai(async fsapi)

* updated requirements_all.txt

* uses afsapi 0.0.2, which supports timeout

* uses afsapi 0.0.3, forward(before next) and rewind(before prev) renamed

* Removing debug message

* removed time import

* Update frontier_silicon.py
2018-02-20 17:14:34 +01:00
Otto Winter
3077444d62 Fix numeric_state condition spamming on unavailable (#12550) 2018-02-20 17:02:27 +01:00
cdce8p
7829e61361 Bugfix: Input Datetime config schema (#12552)
* Bugfix for Input Datatime config schema

* Has_at_least_one_key_value only works if parameter is optional

* Added default parameters
2018-02-20 16:21:35 +01:00
Anders Melchiorsen
fb985e2909 Build JSON in executor (#12536) 2018-02-20 12:37:01 +01:00
Paulus Schoutsen
39847ea651 Clarify cloud error (#12540)
* Clarify cloud error

* Fix tests
2018-02-20 12:31:43 +01:00
Robin
17bdcac61b Adds filesize component (#12211)
* Create filesize.py

* Update filesize.py

* Updates filesize

Addresses issues raised in review

* Update .coveragerc

* Addresses requested changes

Addresses the changes requested by @baloob.
Additionally the file size in bytes is now available in attributes.

* Create test_filesize.py

This isn't working yet

* Update test_filesize.py

* Update test_filesize.py

* Update test_filesize.py

* Update test_filesize.py

* Update test_filesize.py

* Update test_filesize.py

* fixed valid file test

* Update test_filesize.py

* Fix indentation

Fix incorrect indentation in setup

* Update filesize.py

* Update filesize.py
2018-02-19 23:55:54 -08:00
Paulus Schoutsen
e37974c5fc Lint 2018-02-19 23:10:44 -08:00
Arvind Prasanna
46ce114066 Clarify a comment regarding python versions (#12537) 2018-02-19 22:41:13 -08:00
Paulus Schoutsen
d68a24b3b8 Update voluptuous serialize (#12538) 2018-02-19 22:12:39 -08:00
Paulus Schoutsen
336b00765d Fix Sphinx build (#12535) 2018-02-19 20:51:05 -08:00
cdce8p
bb29f16054 Merge pull request #12532 from cdce8p/homekit-test_bugfixes
Homekit: Test bugfixes for py3.5
2018-02-20 02:50:49 +01:00
cdce8p
42ab4e1366 Homekit component test bugfixes for py3.5 2018-02-20 01:40:56 +01:00
thrawnarn
722b9ba49b Changed to async_schedule_update_ha_state (#12518)
* Changed to async schedule update ha state

* Changed to async schedule update ha state

* Fixed my fetch error

* Added new line at the end
2018-02-19 15:43:14 -08:00
Pascal Vizeli
f3748cc4fa Add password support (#12525) 2018-02-19 14:49:52 -08:00
cdce8p
eec3bad94f Add support for HomeKit (#12488)
* Basic Homekit support

* Added Temperatur Sensor
* Added Window Cover

* Code refactored

* Added class HomeAccessory(Accessory)
* Added class HomeBridge(Bridge)
* Changed homekit imports to relative, to enable use in custom_components
* Updated requirements
* Added docs
* Other smaller changes

* Changed Homekit from entity to class

* Changes based on feedback
* Updated config schema
* Add only covers that support set_cover_position

* Addressed comments, updated to pyhap==1.1.5

* For lint: added files to gen_requirements_all
* Added codeowner

* Small change to Wrapper classes

* Moved imports to import_types, small changes

* Small changes, added tests

* Homekit class: removed add_accessory since it's already covered by pyhap
* Added test requirement: HAP-python
* Added test suit for homekit setup and interaction between HA and pyhap
* Added test suit for get_accessories function

* Test bugfix

* Added validate pincode, tests for cover and sensor types
2018-02-19 14:46:22 -08:00
Anders Melchiorsen
dc21c61a44 LimitlessLED assumed state (#12475)
* Use assumed state for LimitlessLED

* Replace inheritance with feature tests

* Clean up

* Clean up conversion methods

* Clamp temperature
2018-02-19 23:11:21 +01:00
Sebastian Muszynski
da9c0a1fd7 python-miio version bumped. (Closes: #12471) (#12481) 2018-02-19 20:53:20 +01:00
Daniel Høyer Iversen
6f2ee9a34c new version of xiaomi lib (#12513) 2018-02-19 12:40:05 +01:00
Teemu R
a378e18a3f bump python-eq3bt version, fixes #12499 (#12510) 2018-02-18 15:37:42 -08:00
Russell Cloran
63fcf9d425 zha: Add support for humidity sensors (#12496)
* zha: Add support for humidity sensors

* Fix lint issue
2018-02-18 15:03:18 -08:00
Russell Cloran
17b57099ae zha: Simplify unique ID (#12495)
Also fixes entity IDs generated for devices on the same endpoint.
2018-02-18 15:02:34 -08:00
thrawnarn
1143499301 More features for the Bluesound component (#11450)
* Added support for join and unjoin

* Added support for sleep functionality

* Fixed supported features

* Removed long lines and fixed documentation strings

* Fixed D401, imperative mood

* Added shuffle support

* Removed unnecessary log row

* Removed model, modelname and brand

* Removed descriptions

* Removed polling command on method run. This change is not needed

* Fixed merge errors

* Removed unused usings

* Pylint fixes

* Hound fixes

* Remove attr Sleep and removed white space in services.xml
2018-02-18 14:59:26 -08:00
Henrik Nicolaisen
72fa170265 added smappee component (#11491)
* added smappee component

* Fixed pylint errors and a few use cases when starting up with invalid credentials

Added coverage omit

* Added support to run only locally
Added a few more sensors
Added more error handling

Better parsing and debug message

* fixed smappee switch after local/remote support was added

* Smappee - update switches for local support (#3)

* Merged with local version

* Updated smappy library with the patched one

Fixed lint, added merge missing param

Fixed missing run for requirements_all.txt

Fixed lint

* Fixed on/off based on library. Reverted change used for testing stacktrace

* Fixed switches to work with both remote and local active

Fixed lint

Fixed switches

Fixed lint

* nothing to update per switch as the states are not saved by smappee system

* added better error handling for communication errors with smappee

* fixed lint errors

* fixed comment

* fixed lint error

* fixed lint error

* update smappee module with reviewer comments
- update smappy module
- cache cloud api requests
- added actuator info
- updated return states
2018-02-18 14:34:28 -08:00
Frederik Bolding
60148f3e83 Converted shopping list to use json util and added default override for json util (#12478)
* Converted shopping list to use json util, Added default override for json util

* Reverted accidental revert

* Fixed pylint issue
2018-02-18 13:11:24 -08:00
Anders Melchiorsen
635d36c6ba Rework Sonos media player platform (#12126)
* Rework Sonos media player platform for push

* Ignore play_mode from events where it is missing

* Remove unused preload helper

* Freeze SoCo version

* Updates for entity registry

* Add codeowner

* Use real soco release
2018-02-18 20:05:20 +01:00
karlkar
2280dc2a34 Support for PTZ in Onvif cameras (#11630)
* Service PTZ added

* Removed description loading during setup

* Fixed hound issues

* Changed attribute names

* Fixed pylint error

* Cleaning up the code

* Changed access to protected member to dict

* Removed new line added by mistake

* Fixed pylint error

* Fixed minors

* Fixed pylint caused by usage of create_type function

* Code made more concise

* Fixed string intendation problem

* Service name changed

* Update code to fit with the new version

* Set ptz to None if PTZ setup failed

* more precise exception used
2018-02-18 08:08:56 -08:00
Anders Melchiorsen
0d0e0b8ba3 Avoid warnings when purging an empty database (#12494) 2018-02-18 08:06:33 -08:00
Joe Lu
a8444b22e7 Support for August doorbell (#11124)
* Add support for August doorbell

* Address PR comment for August platform

* Address PR comment for August binary sensor

* Address PR comment for August camera

* Addressed PR comment for August lock

* - Fixed houndci-bot error

* - Updated configurator description

* - Fixed stale docstring

* Added august module to .coveragerc
2018-02-18 09:24:51 +01:00
Paulus Schoutsen
e8d8b75c07 Try deflaking recorder tests (#12492)
* Try deflaking recorder tests

* Remove run_coroutine_threadsafe

* Lint
2018-02-17 23:20:28 -08:00
happyleavesaoc
02c05e2490 bump usps version (#12465) 2018-02-17 21:49:32 -08:00
Joakim Plate
92aeef82ef Enable compression when sending json to client (#11165)
* Enable compression when sending json to client

Make server compress json content when transmitting to client. Json is quite verbose and compresses well.

A real world example is history_graph requested data for in my case 4 temperature sensors updating every half a second for a graph over 10 days lead to 6MB json which compressed to 200KB using deflate compression.

* Rename variable to request

* Name the variable response instead of request
2018-02-17 21:32:08 -08:00
Philip Rosenberg-Watt
909a06566e Fail gracefully with unreachable LaMetric (#12451)
Accounts with multiple LaMetric devices at unreachable IPs (for example
at a different location, on a different/unroutable subnet, etc.) may
cause the notify.lametric service to fail. This update wraps the message
sending routine in a try/except clause and outputs log messages
indicating the problem.

Fixes #12450
2018-02-17 21:13:05 -08:00
Sergio Viudes
8840c227d2 Added doorbird_last_motion to DoorBird camera platform (#12457) 2018-02-17 21:12:11 -08:00
mjj4791
6299c054c8 Prevent error when no internet or DNS is available (#12486) 2018-02-17 16:19:27 -08:00
Paulus Schoutsen
38da81c308 Merge pull request #12484 from home-assistant/release-0-63-3
0.63.3
2018-02-17 15:29:10 -08:00
Diogo Gomes
6ce9b35e81 [SQL Sensor] always close session (#12452)
* close aborted session

* blank line
2018-02-17 13:58:56 -08:00
Anders Melchiorsen
bf67d0e650 Optimize recorder purge (#12448) 2018-02-17 13:58:56 -08:00
Andrey
fcf97524a2 Fix light template to return brightness as int (#12447) 2018-02-17 13:58:55 -08:00
Ryan McLean
b651cdd8f2 Fix for contentRating error (#12445)
* Fix for contentRating

* Use getattr instead of hasattr

* Lint
2018-02-17 13:58:55 -08:00
Daniel Høyer Iversen
6838de3786 Reduce the load on met.no servers, yr.no sensor (#12435)
* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* fix comment

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Update yr.py
2018-02-17 13:58:54 -08:00
Greg Laabs
c9300a98e0 Fixed 3 small issues in isy994 component (#12421)
1. FanLincs have two nodes: one light and one fan motor. In order for each node to get detected as different Hass entity types, I removed the device-type check for FanLinc. The logic will now fall back on the uom checks which should work just fine. (An alternative approach here would be to special case FanLincs and handle them directly - but seeing as the newer 5.x ISY firmware already handles this much better using NodeDefs, I think this quick and dirty approach is fine for the older firmware.) Fixes #12030
2. Some non-dimming switches were appearing as `light`s in Hass due to an duplicate NodeDef being in the light domain filter. Removed! Fixes #12340
3. The `unqiue_id` property was throwing an error for certain entity types that don't have an `_id` property from the ISY. This issue has always been present, but was exposed by the entity registry which seems to be the first thing to actually try reading the `unique_id` property from the isy994 component.
2018-02-17 13:58:53 -08:00
Sebastian Muszynski
de7a4b9501 python-miio version bumped. (Closes: #12389, Closes: #12298) (#12392) 2018-02-17 13:58:53 -08:00
Paulus Schoutsen
a046e2ed20 Version bump to 0.63.3 2018-02-17 13:39:30 -08:00
Daniel Høyer Iversen
eaba3b315c Reduce the load on met.no servers, yr.no sensor (#12435)
* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* fix comment

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Spread the load more for the yr.no sensor

* Update yr.py
2018-02-17 13:33:41 -08:00
Julius Mittenzwei
4837254146 KNX/Climate: Fixed platform schema min/max values. (#12477)
* Issue 10388: Fixed platform schema min/max values of CONF_SETPOINT_SHIFT_MIN and CONF_SETPOINT_SHIFT_MAX

* readded default values for CONF_SETPOINT_SHIFT_MAX and CONF_SETPOINT_SHIFT_MIN
2018-02-17 13:22:38 -08:00
Paulus Schoutsen
371fe9c78f Add example in test how to create list or object in template (#12469) 2018-02-17 11:47:20 -08:00
Adam Mills
22a007a785 Bump aioautomatic to 0.6.5 for voluptuous 0.11 (#12480) 2018-02-17 14:13:27 -05:00
Matthew Hilton
66dcb6c947 ONVIF Camera added Error handling and rtsp authentication. (#11129)
Bugfixes for several issues with hass.io and non-venv installations

Added passing of credentials to RTSP stream

Changed from ONVIFService to ONVIFCamera as ONVIFService didn't contain
the same error handling.

Changed method to get Stream URL from camera to a more compatible method

Added extra Error handling
2018-02-17 08:57:05 -05:00
Richard Lucas
fab991bbf6 Map Alexa StepVolume responses to volume_up/down (#12467)
It turns out I misunderstood which media_player services are available
when a media player supports StepVolume. This PR maps the Alexa
StepSpeaker messages to the volume_up and volume_down services.

Currently Alexa allows you to specify the number of steps but the media
player volume_up and volume_down services don't support this. For now I
just look to see if the steps are +/- and call up/down accordingly.
2018-02-17 08:54:15 -05:00
Paulus Schoutsen
3fd61d8f45 Update voluputous (#12463)
* Update voluputous

* Fix http config

* Fix optional with default=None

* Optional, default=none

* Fix defaults in voluptuous schemas

* Fix tests

* Fix update error

* Lint
2018-02-17 10:29:14 +01:00
Paulus Schoutsen
e4ef6b91d6 Typo 2018-02-16 23:24:12 -08:00
Simon Nørager Sørensen
dd7bffc28c Add the Xiaomi TV platform. (#12359)
* Added the Xiaomi TV platform.

* Implemented a more efficient default name.

* Fixed a few style errors that slipped past the eye.

* Indicate that state is assumed.
2018-02-16 15:09:20 -08:00
Paulus Schoutsen
26340fd9df Bump frontend to 20180216.0 2018-02-16 15:06:46 -08:00
Otto Winter
fe5626b927 Make WUnderground async (#12385)
* 🐎 Async WUnderground

* ∞ Them lines be too long

* Fix pylint warnings

* Changes according to comments

* Remove STATE_UNKNOWN

* 🔬 Fix tests

* Improve tests
2018-02-16 14:54:11 -08:00
Paulus Schoutsen
b3a47722f0 Initial support for Config Entries (#12079)
* Introduce Config Entries

* Rebase fail

* Address comments

* Address more comments

* RequestDataValidator moved
2018-02-16 14:07:38 -08:00
Ryan McLean
2053c8a908 Fix for contentRating error (#12445)
* Fix for contentRating

* Use getattr instead of hasattr

* Lint
2018-02-16 11:51:19 -08:00
Andrey
13d6e56106 Fix light template to return brightness as int (#12447) 2018-02-16 09:32:11 -08:00
Sebastian Muszynski
1f041d54d9 Fake the state for a short period and skip the next update. (#12446)
On state change the device doesn't provide the new state immediately.

Flag the device as unavailable if no communication is possible.
2018-02-15 22:01:34 -08:00
Jesse Hills
8d48272cbd Add effects to iGlo Lights (#12365)
* Add effects to iGlo Lights
Simplify state variables to library

* Fix lint issues
2018-02-15 21:57:58 -08:00
Tabakhase
facd833e6d Vagrant - sendfile python3.5 debian-stretch (#12454)
* vagrant dev - force hass-AIOHTTP to not use sendfile
Ref: https://www.virtualbox.org/ticket/9069
strongly needed for `home-assistant-polymer`

* vagrant dev - python 3.4 to 3.5 by upgrade to debian-stretch
2018-02-15 21:53:10 -08:00
Anders Melchiorsen
0e2d98dbf5 Optimize recorder purge (#12448) 2018-02-15 20:22:57 -08:00
Diogo Gomes
d43a8e593a [SQL Sensor] always close session (#12452)
* close aborted session

* blank line
2018-02-15 20:20:45 -08:00
Aaron Bach
c7c0df53aa AirVisual: Entity Registry updates and cleanup (#12319)
* AirVisual: Entity Registry updates and cleanup

* Small cleanup

* Owner-requested changes

* Changed hashing function

* Put a better class instatiation mechanism in place

* Small cleanup

* Reverting unintended breaking change

* Removing hashing as part of creating the unique ID

* Attempting to jumpstart Travis
2018-02-15 14:52:47 -08:00
Igor Bernstein
612dd30201 Stop mapping zigbee switches to lights & switches. (#12280)
Zigbee switches only contain client clusters that are meant to control server clusters on a different device/endpoint.
This device type -> cluster mapping can be found in https://www.nxp.com/docs/en/user-guide/JN-UG-3076.pdf.
The intended usage is of connecting client clusters to server clusters is described in https://products.currentbyge.com/sites/products.currentbyge.com/files/document_file/DT200-GE-Zigbee-Primer-Whitepaper.pdf

The lack of server clusters on switches has been verified using a GE ZigBee Lighting Switch 45856GE and a 45857GE dimmer.

Output from a 45856GE:
Device:
  NWK: 0x0cd8
  IEEE: 00:22:a3:00:00:1f:37:68
  Endpoints:
    1: profile=0x104, device_type=DeviceType.ON_OFF_LIGHT
      Input Clusters:
        Basic (0)
        Identify (3)
        Groups (4)
        Scenes (5)
        On/Off (6)
        Metering (1794)
        Diagnostic (2821)
      Output Clusters:
        Time (10)
        Ota (25)
    2: profile=0x104, device_type=DeviceType.ON_OFF_LIGHT_SWITCH
      Input Clusters:
        Basic (0)
        Identify (3)
        Diagnostic (2821)
      Output Clusters:
        Identify (3)
        On/Off (6)
2018-02-15 14:51:54 -08:00
Sergio Viudes
f0d9e5d7ff Fix: timeout data attribute now is parsed to float (#12432) 2018-02-15 14:49:46 -08:00
Diogo Gomes
d18709df5b Update CODEOWNERS (#12440)
As contributor of the components
2018-02-15 13:40:30 -08:00
Paulus Schoutsen
f32911d036 Cleanup http (#12424)
* Clean up HTTP component

* Clean up HTTP mock

* Remove unused import

* Fix test

* Lint
2018-02-15 22:06:14 +01:00
Russell Cloran
ad8fe8a93a zha: Add unique_id to entities (#12331)
* zha: Add unique_id to entities

* Lint

* fix comments

* Update __init__.py

* Update __init__.py
2018-02-15 13:38:56 +01:00
Daniel Høyer Iversen
b4dbfe9bbd Update the Tibber sensor at startup (#12428) 2018-02-15 12:33:49 +01:00
Julius Mittenzwei
ae32d208d9 Cleanup of knx component (#12408) 2018-02-14 22:10:12 -08:00
Julius Mittenzwei
f5d1f53fab Small code cleanup: (#12409)
- should_poll of base class already returns False
- there is no is_on within Scene
2018-02-14 22:07:04 -08:00
Julius Mittenzwei
96bd153c80 Added support for colored KNX lights (#12411) 2018-02-14 22:06:36 -08:00
cdce8p
7e2e82d956 Print every changed file on new line (#12412) 2018-02-14 22:01:30 -08:00
Otto Winter
5d4b1ecd3b Fix MQTT payload decode returning prematurely (#12420)
* Fix MQTT returning prematurely

* Add test
2018-02-14 22:00:49 -08:00
Greg Laabs
c25c4c85d6 Fixed 3 small issues in isy994 component (#12421)
1. FanLincs have two nodes: one light and one fan motor. In order for each node to get detected as different Hass entity types, I removed the device-type check for FanLinc. The logic will now fall back on the uom checks which should work just fine. (An alternative approach here would be to special case FanLincs and handle them directly - but seeing as the newer 5.x ISY firmware already handles this much better using NodeDefs, I think this quick and dirty approach is fine for the older firmware.) Fixes #12030
2. Some non-dimming switches were appearing as `light`s in Hass due to an duplicate NodeDef being in the light domain filter. Removed! Fixes #12340
3. The `unqiue_id` property was throwing an error for certain entity types that don't have an `_id` property from the ISY. This issue has always been present, but was exposed by the entity registry which seems to be the first thing to actually try reading the `unique_id` property from the isy994 component.
2018-02-14 21:58:49 -08:00
Paulus Schoutsen
78c44180f4 Extract data validator to own file and add tests (#12401) 2018-02-14 21:06:03 +01:00
Fabian Affolter
416f64fc70 Upgrade sphinx-autodoc-typehints to 1.2.5 (#12404) 2018-02-14 15:01:39 +01:00
Julius Mittenzwei
f25d56d666 Code cleanup of velux scene (#12390)
* Code cleanup of velux scene

* fixed review comments

* fixed review comments
2018-02-14 15:00:45 +01:00
Paulus Schoutsen
6f043f3c5c Merge branch 'master' into dev 2018-02-13 23:19:39 -08:00
Frederik Bolding
6500cb7915 File Path fixes for RPi Camera (#12338)
* Checked file path with is_allowed_path() for RPi Camera

* Used cv.isfile to verify file path instead of manual checks

* Changed default file path for RPiCamera to config_dir/image.jpg

* Used tempfiles for storing RPi Camera images, if no other path is defined

* Stopped checking for whitelisted paths on temporary files
2018-02-13 23:07:50 -08:00
Paulus Schoutsen
d85ed8d0fe Merge pull request #12402 from home-assistant/release-0-63-2
0.63.2
2018-02-13 23:00:45 -08:00
Anders Melchiorsen
c82ca62820 Downgrade limitlessled to 1.0.8 (#12403) 2018-02-13 22:58:49 -08:00
Anders Melchiorsen
28964806c5 Downgrade limitlessled to 1.0.8 (#12403) 2018-02-13 22:58:31 -08:00
Sean Dague
c414ecd4f0 Introduce zone_id to identify player+zone (#12382)
The yamaha component previously used a property named unique_id to
ensure that exactly 1 media_player was discovered per zone per
control_url. This was introduced so that hard coded devices wouldn't
be duplicated by automatically discovered devices.

In HA 0.63 unique_id became a reserved concept as part of the new
device registry, and the property was removed from the component. But
the default returns None, which had the side effect of only ever
registering a single unit + zone, the first one discovered. This was
typically the Main_Zone of the unit, but there is actually no
guaruntee of that.

This fix brings back the logic under a different property called
zone_id. This is not guarunteed to be globally stable like unique_id
is supposed to be, but it is suitable for the deduplication for yamaha
media players.
2018-02-13 22:11:05 -08:00
citruz
72f100723f Updated beacontools (#12368) 2018-02-13 22:11:04 -08:00
Otto Winter
9e4da37022 Fix WUnderground names (#12346)
* 📝 Fix WUnderground names

* 👻 Fix using event loop callback
2018-02-13 22:11:03 -08:00
Rene Nulsch
e5f000f976 Fix MercedesMe - add check for unsupported features (#12342)
* Add check for unsupported features

* Lint fix

* change to guard clause
2018-02-13 22:11:03 -08:00
Paulus Schoutsen
e2408cc804 Version bump to 0.63.2 2018-02-13 22:03:06 -08:00
Patrik Ekström
9bfeb3b5af Changed pyvera version to 0.2.41 (#12391)
* Changed pyvera version to 0.2.41

Changed required pyvera version to 0.2.41 from 0.2.39.
The 0.2.41 supports the VeraSecure built in siren. Siren is treated as switch and can now be turned on and off. Before it was armable but generated error in Vera controller.  This allows for both detecting status of Siren if triggered from within Vera and also outside controll from HA.

* Added pyvera 0.2.41 library
2018-02-13 21:55:50 -08:00
Aaron Bach
c5c409bed3 Pollen.com: Entity Registry updates and cleanup (#12361)
* Updated Pollen sensors to be entity registry-friendly

* Pollen.com: Entity Registry updates and cleanup

* Small cleanup

* Owner-requested changes
2018-02-13 16:25:10 -08:00
Kane610
8bff813014 Improve service by allowing to reference entity id instead of deconz id (#11862)
* Improve service by allowing to reference entity id instead of deconz id

* Change from having access to full entities to only store entity id together with deconz id

* Don't use eval, there is a dict type for voluptuous

* Use entity registry instead of keeping a local registry over entity ids

* Removed old code

* Add test for get_entry

* Bump dependency to v28
Fixed call to protected member

* Use chain to iterate over dict values

* Cleanup

* Fix hound comment

* Cleanup

* Follow refactoring of entity

* Revert to using a local registry

* Remove unused import

* self.hass is automatically available when entity is registered in hass
2018-02-13 16:23:03 -08:00
Sebastian Muszynski
a4944da68f python-miio version bumped. (Closes: #12389, Closes: #12298) (#12392) 2018-02-14 00:17:47 +01:00
Fabian Affolter
bc64053214 Add attributes (fixes #12332) (#12377)
* Add attributes (fixes #12332)

* Fix pylint issue
2018-02-13 23:47:59 +01:00
Paulus Schoutsen
c7416c8986 Remove usage of deprecated assert method (#12379) 2018-02-13 23:25:06 +01:00
Fabian Affolter
429628ec1d Upgrade youtube_dl to 2018.02.11 (#12383) 2018-02-13 23:24:18 +01:00
Sean Dague
16dafaa5af Introduce zone_id to identify player+zone (#12382)
The yamaha component previously used a property named unique_id to
ensure that exactly 1 media_player was discovered per zone per
control_url. This was introduced so that hard coded devices wouldn't
be duplicated by automatically discovered devices.

In HA 0.63 unique_id became a reserved concept as part of the new
device registry, and the property was removed from the component. But
the default returns None, which had the side effect of only ever
registering a single unit + zone, the first one discovered. This was
typically the Main_Zone of the unit, but there is actually no
guaruntee of that.

This fix brings back the logic under a different property called
zone_id. This is not guarunteed to be globally stable like unique_id
is supposed to be, but it is suitable for the deduplication for yamaha
media players.
2018-02-13 23:24:03 +01:00
Paulus Schoutsen
f0231c1f29 Specify algorithms for webpush jwt verification (#12378) 2018-02-13 23:23:34 +01:00
Johann Kellerman
5995c2f313 SMA sensor add SSL and upgrade to pysma 0.2 (#12354) 2018-02-13 22:03:56 +02:00
Diogo Gomes
80d2c76e85 Upgrade panasonic_viera to 0.3.1 (#12370)
* Bump panasonic_viera library to 0.3.1

Fixes media_play in hassio environment

* update requirements
2018-02-13 17:06:30 +01:00
Paulus Schoutsen
d2cea84254 Allow disabling entities in the registry (#12360) 2018-02-13 13:33:15 +01:00
citruz
a4b88fc31b Updated beacontools (#12368) 2018-02-13 11:32:44 +01:00
karlkar
f5c2e7ff68 Eq3btsmart more reliable (#11555)
* Eq3btsmart more reliable

* Fixed 'Line too long' violations

* Fixed trailing whitespaces

* Fixed indents

* Fix for disallowing external temperature setting

* Logic fix after increasing eq3bt version
2018-02-12 22:29:58 -08:00
Rene Nulsch
00ff305bd7 Fix MercedesMe - add check for unsupported features (#12342)
* Add check for unsupported features

* Lint fix

* change to guard clause
2018-02-12 21:07:20 -08:00
Fabian Affolter
66d14da5e9 Upgrade alpha_vantage to 1.9.0 (#12352) 2018-02-12 21:06:03 -08:00
happyleavesaoc
ba9fef4de6 bump fedex version (#12362) 2018-02-12 21:02:03 -08:00
Mike O'Driscoll
0a558a0e82 Add New Sensor for ISP Start.ca (#12356)
Adding a new sensor for ISP Start.ca to track download/upload usage.
2018-02-12 19:43:56 -08:00
Otto Winter
2c202690d8 Fix WUnderground names (#12346)
* 📝 Fix WUnderground names

* 👻 Fix using event loop callback
2018-02-12 13:15:28 -08:00
Sebastian Muszynski
04bde68db3 Communication reduced. Setting brightness and/or color temperature will turn on the device. (#12343) 2018-02-12 11:24:48 -08:00
Dougal Matthews
2d77a2bb39 Use the speedometer icon in the fastdotcom sensor (#12348)
This change makes the icon match the one used in the speedtest sensor.
2018-02-12 11:15:10 -08:00
Mike O'Driscoll
52f57b755e Change Unifi SSID filtering to list comprehension (#12344)
Changed from filter to list comprehension.
2018-02-12 20:02:07 +01:00
Paulus Schoutsen
870728f68f Mock Module + Platform default to async (#12347)
* Mock Module + Platform default to async

* Change checks
2018-02-12 10:59:20 -08:00
Paulus Schoutsen
48f40453f7 Merge branch 'master' into dev 2018-02-12 09:00:11 -08:00
Paulus Schoutsen
073126755c Merge pull request #12334 from home-assistant/release-0-63-1
0.63.1
2018-02-12 08:56:04 -08:00
Fabian Affolter
034eb9ae1a Upgrade Sphinx to 1.7.0 (#12335) 2018-02-12 11:24:26 +01:00
Abílio Costa
d34a4fb6e3 nmap_tracker: don't scan on setup (#12322)
* nmap_tracker: don't scan on setup

	A scan takes about 6 seconds so it delays HA from booting. Since
another scan is done by the device_tracker base component during setup,
there is no need to do two scans on boot.

* simplify setup
2018-02-12 08:52:00 +01:00
Paulus Schoutsen
7a9ceb6f54 Fix platform dependencies (#12330) 2018-02-11 23:38:47 -08:00
Richard Lucas
a06000c76d Always return lockState == LOCKED when handling Alexa.LockController (#12328) 2018-02-11 23:38:39 -08:00
Richard Lucas
2a0bd8d330 Fix Report State for Alexa Brightness Controller (#12318)
* Fix Report State for Alexa Brightness Controller

* Lint
2018-02-11 23:38:31 -08:00
Paulus Schoutsen
ead158b68c Respect entity namespace for entity registry (#12313)
* Respect entity namespace for entity registry

* Lint
2018-02-11 23:38:24 -08:00
Paulus Schoutsen
bfd9a5a863 Allow overriding name via entity registry (#12292)
* Allow overriding name via entity registry

* Update requirements
2018-02-11 23:38:15 -08:00
Paulus Schoutsen
56b185f7ab Remove unique ID from netatmo (#12317)
* Remove unique ID from netatmo

* Shame platform in error message
2018-02-11 23:31:40 -08:00
Russell Cloran
34ccfae565 zha: Update zigpy-xbee to 0.0.2
0.0.2 implements auto_form, so that configuring the radio to be a
controller is done automatically.
2018-02-11 23:31:30 -08:00
Richard Lucas
dc8a0205ee Fix Alexa Step Volume (#12314) 2018-02-11 23:30:55 -08:00
Paulus Schoutsen
7471211b60 Version bump to 0.63.1 2018-02-11 23:27:38 -08:00
Paulus Schoutsen
04b68902e3 Fix platform dependencies (#12330) 2018-02-11 23:26:52 -08:00
Albert Lee
ebe4418afe device_tracker.asuswrt: Ignore unreachable ip neigh entries (#12201) 2018-02-11 23:24:29 -08:00
Mike O'Driscoll
eaa2791539 Unifi tracking filter by SSID (#12281)
Enable unifi device tracker component to track devices only
on specific SSIDs.
2018-02-11 23:23:53 -08:00
Richard Lucas
7059b6c6c1 Always return lockState == LOCKED when handling Alexa.LockController (#12328) 2018-02-11 23:20:54 -08:00
Paulus Schoutsen
5d15b257c4 Fix line endings [skipci] (#12333) 2018-02-11 23:07:28 -08:00
Richard Lucas
669929de06 Fix Report State for Alexa Brightness Controller (#12318)
* Fix Report State for Alexa Brightness Controller

* Lint
2018-02-11 22:36:22 -08:00
Paulus Schoutsen
eb7adc74ef Respect entity namespace for entity registry (#12313)
* Respect entity namespace for entity registry

* Lint
2018-02-11 20:55:38 -08:00
Russell Cloran
3b3050434a zha: Add remove service (#11683)
* zha: Add remove service

* Lint
2018-02-11 20:34:19 -08:00
Paulus Schoutsen
7e9dcfa4c9 Revert #12316 (#12329) 2018-02-11 20:33:08 -08:00
Aaron Bach
c193d80ec5 Updated RainMachine to play better with the entity registry (#12315)
* Updated RainMachine to play better with the entity registry

* Owner-requested changes
2018-02-11 19:55:51 -08:00
Paulus Schoutsen
2e3524147c Remove unique ID from netatmo (#12317)
* Remove unique ID from netatmo

* Shame platform in error message
2018-02-11 19:33:37 -08:00
Johann Kellerman
f28fa7447e Force LF line endings for Windows (#12266)
* Force LF line endings for Windows

* include all
2018-02-11 19:23:16 -08:00
Russell Cloran
069454323e zha: Update zigpy-xbee to 0.0.2
0.0.2 implements auto_form, so that configuring the radio to be a
controller is done automatically.
2018-02-11 17:20:55 -08:00
ChristianKuehnel
ed1d6f1027 Removed default sensor configuration (#12252)
* removed default configuration, added warning if no sensor was configured

* fixed typo in currency-try icon

* changed if statement
2018-02-11 23:51:10 +01:00
Russell Cloran
28ed304c93 zha: Update zigpy-xbee to 0.0.2
0.0.2 implements auto_form, so that configuring the radio to be a
controller is done automatically.
2018-02-11 14:45:52 -08:00
Paulus Schoutsen
3e150bb2b3 Protect bloomsky platform setup (#12316) 2018-02-11 22:49:16 +01:00
Anders Melchiorsen
247edf1b69 Purge recorder data by default (#12271) 2018-02-11 13:22:59 -08:00
NovapaX
219ed7331c add friendly_name_template to template sensor (#12268)
* add friendly_name_template to template sensor.
If set, overrides friendly_name setting.

* Add test
2018-02-11 12:12:30 -08:00
Thijs de Jong
47bfef9640 Clarify tahoma errrors (#12307) 2018-02-11 20:36:03 +01:00
Richard Lucas
767d3c6206 Fix Alexa Step Volume (#12314) 2018-02-11 11:25:05 -08:00
Otto Winter
e4a826d1c1 📝 Fix fixture encoding (#12296) 2018-02-11 11:01:44 -08:00
Paulus Schoutsen
a71d5f4614 Bump frontend to 20180211.0 2018-02-11 09:45:21 -08:00
Jerad Meisner
6c358fa6a3 Migrated SABnzbd sensor to asyncio and switched to pypi library (#12290)
* Migrated SABnzbd sensor to asyncio and switched to pypi library

* bumped pysabnzbd version and refactored imports
2018-02-11 09:33:56 -08:00
Tod Schmidt
26209de2f2 Move HassIntent handler code into helpers/intent (#12181)
* Moved TurnOn/Off Intents to component

* Removed unused import

* Lint fix which my local runs dont catch apparently...

* Moved hass intent code into intent

* Added test for toggle to conversation.

* Fixed toggle tests

* Update intent.py

* Added homeassistant.helpers to gen_requirements script.

* Update intent.py

* Update intent.py

* Changed return value for _match_entity

* Moved consts and requirements

* Removed unused import

* Removed http view

* Removed http import

* Removed fuzzywuzzy dependency

* woof

* A few cleanups

* Added domain filtering to entities

* Clarified class doc string

* Added doc string

* Added test in test_init

* woof

* Cleanup entity matching

* Update intent.py

* removed uneeded setup from tests
2018-02-11 09:33:19 -08:00
Otto Winter
678f284015 Upgrade pylint to 1.8.2 (#12274)
* Upgrade pylint to 1.8.1

* Fix no-else-return

* Fix bad-whitespace

* Fix too-many-nested-blocks

* Fix raising-format-tuple

See https://github.com/PyCQA/pylint/blob/master/doc/whatsnew/1.8.rst

* Fix len-as-condition

* Fix logging-not-lazy

Not sure about that TEMP_CELSIUS though, but internally it's probably just like if you concatenated any other (variable) string

* Fix stop-iteration-return

* Fix useless-super-delegation

* Fix trailing-comma-tuple

Both of these seem to simply be bugs:
 * Nest: The value of self._humidity never seems to be used anywhere
 * Dovado: The called API method seems to expect a "normal" number

* Fix redefined-argument-from-local

* Fix consider-using-enumerate

* Fix wrong-import-order

* Fix arguments-differ

* Fix missed no-else-return

* Fix no-member and related

* Fix signatures-differ

* Revert "Upgrade pylint to 1.8.1"

This reverts commit af78aa00f125a7d34add97b9d50c14db48412211.

* Fix arguments-differ

* except for device_tracker

* Cleanup

* Fix test using positional argument

* Fix line too long

I forgot to run flake8 - shame on me... 🙃

* Fix bad-option-value for 1.6.5

* Fix arguments-differ for device_tracker

* Upgrade pylint to 1.8.2

* 👕 Fix missed no-member
2018-02-11 09:20:28 -08:00
Rene Nulsch
64c5d26a84 Fix Panel_IFrame - FTP URL not allowed in 0.63 (#12295) 2018-02-11 09:19:31 -08:00
Rene Nulsch
2edebfee0a Fix config error for FTP links, add test (#12294) 2018-02-11 09:19:00 -08:00
Otto Winter
b1c0cabe6c Fix MQTT retained message not being re-dispatched (#12004)
* Fix MQTT retained message not being re-dispatched

* Fix tests

* Use paho-mqtt for retained messages

* Improve code style

* Store list of subscribers

* Fix lint error

* Adhere to Home Assistant's logging standard

"Try to avoid brackets and additional quotes around the output to make it easier for users to parse the log."
 - https://home-assistant.io/developers/development_guidelines/

* Add reconnect tests

* Fix lint error

* Introduce Subscription

Tests still need to be updated

* Use namedtuple for MQTT messages

... And fix issues

Accessing the config manually at runtime isn't ideal

* Fix MQTT __init__.py tests

* Updated usage of Mocks
* Moved tests that were testing subscriptions out of the MQTTComponent test, because of how mock.patch was used
* Adjusted the remaining tests for the MQTT clients new behavior - e.g. self.progress was removed
* Updated the async_fire_mqtt_message helper

*  Update MQTT tests

* Re-introduce the MQTT subscriptions through the dispatcher for tests - quite ugly though...  🚧
* Update fixtures to use our new MQTT mock 🎨

* 📝 Update base code according to comments

* 🔨 Adjust MQTT test base

* 🔨 Update other MQTT tests

* 🍎 Fix carriage return in source files

Apparently test_mqtt_json.py and test_mqtt_template.py were written on Windows. In order to not mess up the diff, I'll just redo the carriage return.

* 🎨 Remove unused import

* 📝 Remove fire_mqtt_client_message

* 🐛 Fix using python 3.6 method

What's very interesting is that 3.4 didn't fail on travis...

* 🐛 Fix using assert directly
2018-02-11 09:17:58 -08:00
Paulus Schoutsen
17e5740a0c Allow overriding name via entity registry (#12292)
* Allow overriding name via entity registry

* Update requirements
2018-02-11 09:16:01 -08:00
Paulus Schoutsen
8b9eab196c Attempt fixing flakiness of check config test (#12283)
* Attempt fixing check_config script test flakiness

* Fix logging

* remove cleanup as Python will exit

* Make sure we don't enqueue magicmocks

* Lint

* Reinstate cleanup as it broke secret tests
2018-02-11 09:00:02 -08:00
Johann Kellerman
65c6f72c9d check_config check bootstrap errors (#12291) 2018-02-10 23:40:48 -08:00
Thom Troy
fe1a85047e have climate fallback to state if no ATTR_OPERATION_MODE (#12271) (#12279) 2018-02-10 15:06:24 -08:00
Paulus Schoutsen
74010fc2df Merge branch 'master' into dev 2018-02-10 14:03:55 -08:00
Slava
a9e2dd3427 Update limitlessled requirement to v1.0.9 (#12275)
* Update limitlessled requirement to v1.0.9

* trigger cla

* take back empty line
2018-02-10 12:59:04 -08:00
Paulus Schoutsen
f2296e1ff8 Retry keyset cloud (#12270)
* Use less threads in helpers.event tests

* Add helpers.event.async_call_later

* Cloud: retry fetching keyset
2018-02-10 11:40:24 +01:00
David K
134445f622 Fix some rfxtrx devices with multiple sensors (#12264)
* Fix some rfxtrx devices with multiple sensors

Some combined temperature/humidity sensors send one packet for each of
their sensors. Without this fix one of the home assistant sensors would
always display an unknown value.

* Add comment
2018-02-10 10:45:00 +01:00
escoand
cad9e9a4cb allow wildcards in subscription (#12247)
* allow wildcards in subscription

* remove whitespaces

* make function public

* also implement for mqtt_json

* avoid mqtt-outside topic matching

* add wildcard tests

* add not matching wildcard tests

* fix not-matching tests
2018-02-09 15:22:50 -08:00
Albert Lee
1db4df6d3a device_tracker.asuswrt: Clean up unused connection param (#12262) 2018-02-09 15:21:10 -08:00
Paulus Schoutsen
7c36c5d9b4 Version bump to 0.64.0.dev0 2018-02-09 14:56:46 -08:00
481 changed files with 11736 additions and 5837 deletions

View File

@@ -29,6 +29,9 @@ omit =
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/bmw_connected_drive.py
homeassistant/components/*/bmw_connected_drive.py
homeassistant/components/android_ip_webcam.py
homeassistant/components/*/android_ip_webcam.py
@@ -38,6 +41,9 @@ omit =
homeassistant/components/asterisk_mbox.py
homeassistant/components/*/asterisk_mbox.py
homeassistant/components/august.py
homeassistant/components/*/august.py
homeassistant/components/axis.py
homeassistant/components/*/axis.py
@@ -205,6 +211,9 @@ omit =
homeassistant/components/skybell.py
homeassistant/components/*/skybell.py
homeassistant/components/smappee.py
homeassistant/components/*/smappee.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
@@ -462,6 +471,7 @@ omit =
homeassistant/components/media_player/vizio.py
homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/xiaomi_tv.py
homeassistant/components/media_player/yamaha.py
homeassistant/components/media_player/yamaha_musiccast.py
homeassistant/components/media_player/ziggo_mediabox_xl.py
@@ -551,8 +561,10 @@ omit =
homeassistant/components/sensor/etherscan.py
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fedex.py
homeassistant/components/sensor/filesize.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/folder.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py
@@ -617,6 +629,7 @@ omit =
homeassistant/components/sensor/sochain.py
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/spotcrime.py
homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py

11
.gitattributes vendored
View File

@@ -1,3 +1,10 @@
# Ensure Docker script files uses LF to support Docker for Windows.
setup_docker_prereqs eol=lf
/virtualization/Docker/scripts/* eol=lf
# Ensure "git config --global core.autocrlf input" before you clone
* text eol=lf
*.py whitespace=error
*.ico binary
*.jpg binary
*.png binary
*.zip binary
*.mp3 binary

7
CODEOWNERS Normal file → Executable file
View File

@@ -43,6 +43,7 @@ homeassistant/components/hassio.py @home-assistant/hassio
# Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
homeassistant/components/climate/eq3btsmart.py @rytilahti
@@ -54,7 +55,10 @@ 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/mediaroom.py @dgomes
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/sonos.py @amelchio
homeassistant/components/media_player/xiaomi_tv.py @fattdev
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/plant.py @ChristianKuehnel
homeassistant/components/sensor/airvisual.py @bachya
@@ -63,6 +67,7 @@ homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/sql.py @dgomes
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/rainmachine.py @bachya
@@ -70,9 +75,11 @@ homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/axis.py @kane610
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/hive.py @Rendili @KJonline
homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/homekit/* @cdce8p
homeassistant/components/*/deconz.py @kane610
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/velux.py @Julius2342

View File

@@ -22,10 +22,23 @@ import os
import inspect
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)
PROJECT_NAME = 'Home Assistant'
PROJECT_PACKAGE_NAME = 'homeassistant'
PROJECT_AUTHOR = 'The Home Assistant Authors'
PROJECT_COPYRIGHT = ' 2013-2018, {}'.format(PROJECT_AUTHOR)
PROJECT_LONG_DESCRIPTION = ('Home Assistant is an open-source '
'home automation platform running on Python 3. '
'Track and control all devices at home and '
'automate control. '
'Installation in less than a minute.')
PROJECT_GITHUB_USERNAME = 'home-assistant'
PROJECT_GITHUB_REPOSITORY = 'home-assistant'
GITHUB_PATH = '{}/{}'.format(
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH)
sys.path.insert(0, os.path.abspath('_ext'))
sys.path.insert(0, os.path.abspath('../homeassistant'))

View File

@@ -12,7 +12,8 @@ from typing import Any, Optional, Dict
import voluptuous as vol
from homeassistant import (
core, config as conf_util, loader, components as core_components)
core, config as conf_util, config_entries, loader,
components as core_components)
from homeassistant.components import persistent_notification
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
@@ -35,13 +36,13 @@ FIRST_INIT_COMPONENT = set((
def from_config_dict(config: Dict[str, Any],
hass: Optional[core.HomeAssistant]=None,
config_dir: Optional[str]=None,
enable_log: bool=True,
verbose: bool=False,
skip_pip: bool=False,
log_rotate_days: Any=None,
log_file: Any=None) \
hass: Optional[core.HomeAssistant] = None,
config_dir: Optional[str] = None,
enable_log: bool = True,
verbose: bool = False,
skip_pip: bool = False,
log_rotate_days: Any = None,
log_file: Any = None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.
@@ -68,12 +69,12 @@ def from_config_dict(config: Dict[str, Any],
@asyncio.coroutine
def async_from_config_dict(config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str]=None,
enable_log: bool=True,
verbose: bool=False,
skip_pip: bool=False,
log_rotate_days: Any=None,
log_file: Any=None) \
config_dir: Optional[str] = None,
enable_log: bool = True,
verbose: bool = False,
skip_pip: bool = False,
log_rotate_days: Any = None,
log_file: Any = None) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.
@@ -123,9 +124,13 @@ def async_from_config_dict(config: Dict[str, Any],
new_config[key] = value or {}
config = new_config
hass.config_entries = config_entries.ConfigEntries(hass, config)
yield from hass.config_entries.async_load()
# Filter out the repeating and common config section [homeassistant]
components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN)
components.update(hass.config_entries.async_domains())
# setup components
# pylint: disable=not-an-iterable
@@ -163,11 +168,11 @@ def async_from_config_dict(config: Dict[str, Any],
def from_config_file(config_path: str,
hass: Optional[core.HomeAssistant]=None,
verbose: bool=False,
skip_pip: bool=True,
log_rotate_days: Any=None,
log_file: Any=None):
hass: Optional[core.HomeAssistant] = None,
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None):
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter if given,
@@ -188,10 +193,10 @@ def from_config_file(config_path: str,
@asyncio.coroutine
def async_from_config_file(config_path: str,
hass: core.HomeAssistant,
verbose: bool=False,
skip_pip: bool=True,
log_rotate_days: Any=None,
log_file: Any=None):
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None):
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter.
@@ -219,7 +224,7 @@ def async_from_config_file(config_path: str,
@core.callback
def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
def async_enable_logging(hass: core.HomeAssistant, verbose: bool = False,
log_rotate_days=None, log_file=None) -> None:
"""Set up the logging.

View File

@@ -15,6 +15,7 @@ import homeassistant.core as ha
import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.helpers import intent
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
@@ -154,6 +155,12 @@ def async_setup(hass, config):
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned on {}"))
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned off {}"))
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
@asyncio.coroutine
def async_handle_core_service(call):

View File

@@ -7,6 +7,7 @@ https://home-assistant.io/components/abode/
import asyncio
import logging
from functools import partial
from requests.exceptions import HTTPError, ConnectTimeout
import voluptuous as vol
@@ -17,7 +18,6 @@ from homeassistant.const import (
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']

View File

@@ -59,8 +59,7 @@ class CanaryAlarm(AlarmControlPanel):
return STATE_ALARM_ARMED_HOME
elif mode.name == LOCATION_MODE_NIGHT:
return STATE_ALARM_ARMED_NIGHT
else:
return None
return None
@property
def device_state_attributes(self):

View File

@@ -172,9 +172,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
self._state = self._previous_state
return self._state
self._state = self._previous_state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \
self._within_pending_time(self._state):
@@ -187,8 +186,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
"""Get the current state."""
if self.state == STATE_ALARM_PENDING:
return self._previous_state
else:
return self._state
return self._state
def _pending_time(self, state):
"""Get the pending time."""

View File

@@ -208,9 +208,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger:
return STATE_ALARM_DISARMED
else:
self._state = self._previous_state
return self._state
self._state = self._previous_state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \
self._within_pending_time(self._state):
@@ -223,8 +222,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
"""Get the current state."""
if self.state == STATE_ALARM_PENDING:
return self._previous_state
else:
return self._state
return self._state
def _pending_time(self, state):
"""Get the pending time."""

View File

@@ -1,71 +1,71 @@
# 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.
example: 'alarm_control_panel.downstairs'
code:
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.
fields:
entity_id:
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.
example: 1234
alarm_arm_away:
description: Send the alarm the command for arm away.
fields:
entity_id:
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.
example: 1234
alarm_arm_night:
description: Send the alarm the command for arm night.
fields:
entity_id:
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.
example: 1234
alarm_trigger:
description: Send the alarm the command for trigger.
fields:
entity_id:
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.
example: 1234
envisalink_alarm_keypress:
description: Send custom keypresses to the alarm.
fields:
entity_id:
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).'
example: '*71'
alarmdecoder_alarm_toggle_chime:
description: Send the alarm the toggle chime command.
fields:
entity_id:
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
code:
description: A required code to toggle the alarm control panel chime with.
example: 1234
# 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.
example: 'alarm_control_panel.downstairs'
code:
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.
fields:
entity_id:
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.
example: 1234
alarm_arm_away:
description: Send the alarm the command for arm away.
fields:
entity_id:
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.
example: 1234
alarm_arm_night:
description: Send the alarm the command for arm night.
fields:
entity_id:
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.
example: 1234
alarm_trigger:
description: Send the alarm the command for trigger.
fields:
entity_id:
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.
example: 1234
envisalink_alarm_keypress:
description: Send custom keypresses to the alarm.
fields:
entity_id:
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).'
example: '*71'
alarmdecoder_alarm_toggle_chime:
description: Send the alarm the toggle chime command.
fields:
entity_id:
description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs'
code:
description: A required code to toggle the alarm control panel chime with.
example: 1234

View File

@@ -34,7 +34,7 @@ DEFAULT_SKIP_FIRST = False
ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_DONE_MESSAGE, default=None): cv.string,
vol.Optional(CONF_DONE_MESSAGE): cv.string,
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_STATE, default=STATE_ON): cv.string,
vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]),
@@ -121,7 +121,7 @@ def async_setup(hass, config):
# Setup alerts
for entity_id, alert in alerts.items():
entity = Alert(hass, entity_id,
alert[CONF_NAME], alert[CONF_DONE_MESSAGE],
alert[CONF_NAME], alert.get(CONF_DONE_MESSAGE),
alert[CONF_ENTITY_ID], alert[CONF_STATE],
alert[CONF_REPEAT], alert[CONF_SKIP_FIRST],
alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK])

View File

@@ -31,10 +31,7 @@ ALEXA_ENTITY_SCHEMA = vol.Schema({
})
SMART_HOME_SCHEMA = vol.Schema({
vol.Optional(
CONF_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})

View File

@@ -328,8 +328,9 @@ class _AlexaBrightnessController(_AlexaInterface):
def get_property(self, name):
if name != 'brightness':
raise _UnsupportedProperty(name)
return round(self.entity.attributes['brightness'] / 255.0 * 100)
if 'brightness' in self.entity.attributes:
return round(self.entity.attributes['brightness'] / 255.0 * 100)
return 0
class _AlexaColorController(_AlexaInterface):
@@ -390,6 +391,7 @@ class _AlexaTemperatureSensor(_AlexaInterface):
@ENTITY_ADAPTERS.register(alert.DOMAIN)
@ENTITY_ADAPTERS.register(automation.DOMAIN)
@ENTITY_ADAPTERS.register(group.DOMAIN)
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
class _GenericCapabilities(_AlexaEntity):
"""A generic, on/off device.
@@ -520,16 +522,6 @@ class _ScriptCapabilities(_AlexaEntity):
supports_deactivation=can_cancel)]
@ENTITY_ADAPTERS.register(group.DOMAIN)
class _GroupCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=True)]
@ENTITY_ADAPTERS.register(sensor.DOMAIN)
class _SensorCapabilities(_AlexaEntity):
def default_display_categories(self):
@@ -772,6 +764,8 @@ def extract_entity(funct):
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
service = SERVICE_TURN_ON
if entity.domain == cover.DOMAIN:
@@ -927,10 +921,7 @@ def async_api_increase_color_temp(hass, config, request, entity):
@asyncio.coroutine
def async_api_activate(hass, config, request, entity):
"""Process an activate request."""
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
else:
domain = entity.domain
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
@@ -954,10 +945,7 @@ def async_api_activate(hass, config, request, entity):
@asyncio.coroutine
def async_api_deactivate(hass, config, request, entity):
"""Process a deactivate request."""
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
else:
domain = entity.domain
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id
@@ -1064,7 +1052,16 @@ def async_api_lock(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
return api_message(request)
# Alexa expects a lockState in the response, we don't know the actual
# lockState at this point but assume it is locked. It is reported
# correctly later when ReportState is called. The alt. to this approach
# is to implement DeferredResponse
properties = [{
'name': 'lockState',
'namespace': 'Alexa.LockController',
'value': 'LOCKED'
}]
return api_message(request, context={'properties': properties})
# Not supported by Alexa yet
@@ -1168,20 +1165,24 @@ def async_api_adjust_volume(hass, config, request, entity):
@asyncio.coroutine
def async_api_adjust_volume_step(hass, config, request, entity):
"""Process an adjust volume step request."""
volume_step = round(float(request[API_PAYLOAD]['volume'] / 100), 2)
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
volume = current_level + volume_step
# media_player volume up/down service does not support specifying steps
# each component handles it differently e.g. via config.
# For now we use the volumeSteps returned to figure out if we
# should step up/down
volume_step = request[API_PAYLOAD]['volumeSteps']
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=False)
if volume_step > 0:
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_UP,
data, blocking=False)
elif volume_step < 0:
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_DOWN,
data, blocking=False)
return api_message(request)

View File

@@ -79,7 +79,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
vol.Optional(CONF_SENSORS, default=None):
vol.Optional(CONF_SENSORS):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
})])
}, extra=vol.ALLOW_EXTRA)

View File

@@ -140,11 +140,11 @@ CONFIG_SCHEMA = vol.Schema({
cv.time_period,
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
vol.Optional(CONF_SWITCHES, default=None):
vol.Optional(CONF_SWITCHES):
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
vol.Optional(CONF_SENSORS, default=None):
vol.Optional(CONF_SENSORS):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
vol.Optional(CONF_MOTION_SENSOR, default=None): cv.boolean,
vol.Optional(CONF_MOTION_SENSOR): cv.boolean,
})])
}, extra=vol.ALLOW_EXTRA)
@@ -165,9 +165,9 @@ def async_setup(hass, config):
password = cam_config.get(CONF_PASSWORD)
name = cam_config[CONF_NAME]
interval = cam_config[CONF_SCAN_INTERVAL]
switches = cam_config[CONF_SWITCHES]
sensors = cam_config[CONF_SENSORS]
motion = cam_config[CONF_MOTION_SENSOR]
switches = cam_config.get(CONF_SWITCHES)
sensors = cam_config.get(CONF_SENSORS)
motion = cam_config.get(CONF_MOTION_SENSOR)
# Init ip webcam
cam = PyDroidIPCam(

View File

@@ -60,7 +60,7 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_CREDENTIALS, default=None): cv.string,
vol.Optional(CONF_CREDENTIALS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean,
})])

View File

@@ -0,0 +1,257 @@
"""
Support for August devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/august/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from requests import RequestException
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT)
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
REQUIREMENTS = ['py-august==0.3.0']
DEFAULT_TIMEOUT = 10
ACTIVITY_FETCH_LIMIT = 10
ACTIVITY_INITIAL_FETCH_LIMIT = 20
CONF_LOGIN_METHOD = 'login_method'
CONF_INSTALL_ID = 'install_id'
NOTIFICATION_ID = 'august_notification'
NOTIFICATION_TITLE = "August Setup"
AUGUST_CONFIG_FILE = '.august.conf'
DATA_AUGUST = 'august'
DOMAIN = 'august'
DEFAULT_ENTITY_NAMESPACE = 'august'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
DEFAULT_SCAN_INTERVAL = timedelta(seconds=5)
LOGIN_METHODS = ['phone', 'email']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_INSTALL_ID): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
})
}, extra=vol.ALLOW_EXTRA)
AUGUST_COMPONENTS = [
'camera', 'binary_sensor', 'lock'
]
def request_configuration(hass, config, api, authenticator):
"""Request configuration steps from the user."""
configurator = hass.components.configurator
def august_configuration_callback(data):
"""Run when the configuration callback is called."""
from august.authenticator import ValidationResult
result = authenticator.validate_verification_code(
data.get('verification_code'))
if result == ValidationResult.INVALID_VERIFICATION_CODE:
configurator.notify_errors(_CONFIGURING[DOMAIN],
"Invalid verification code")
elif result == ValidationResult.VALIDATED:
setup_august(hass, config, api, authenticator)
if DOMAIN not in _CONFIGURING:
authenticator.send_verification_code()
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
login_method = conf.get(CONF_LOGIN_METHOD)
_CONFIGURING[DOMAIN] = configurator.request_config(
NOTIFICATION_TITLE,
august_configuration_callback,
description="Please check your {} ({}) and enter the verification "
"code below".format(login_method, username),
submit_caption='Verify',
fields=[{
'id': 'verification_code',
'name': "Verification code",
'type': 'string'}]
)
def setup_august(hass, config, api, authenticator):
"""Set up the August component."""
from august.authenticator import AuthenticationState
authentication = None
try:
authentication = authenticator.authenticate()
except RequestException as ex:
_LOGGER.error("Unable to connect to August service: %s", str(ex))
hass.components.persistent_notification.create(
"Error: {}<br />"
"You will need to restart hass after fixing."
"".format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
state = authentication.state
if state == AuthenticationState.AUTHENTICATED:
if DOMAIN in _CONFIGURING:
hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN))
hass.data[DATA_AUGUST] = AugustData(api, authentication.access_token)
for component in AUGUST_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
elif state == AuthenticationState.BAD_PASSWORD:
return False
elif state == AuthenticationState.REQUIRES_VALIDATION:
request_configuration(hass, config, api, authenticator)
return True
return False
def setup(hass, config):
"""Set up the August component."""
from august.api import Api
from august.authenticator import Authenticator
conf = config[DOMAIN]
api = Api(timeout=conf.get(CONF_TIMEOUT))
authenticator = Authenticator(
api,
conf.get(CONF_LOGIN_METHOD),
conf.get(CONF_USERNAME),
conf.get(CONF_PASSWORD),
install_id=conf.get(CONF_INSTALL_ID),
access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE))
return setup_august(hass, config, api, authenticator)
class AugustData:
"""August data object."""
def __init__(self, api, access_token):
"""Init August data object."""
self._api = api
self._access_token = access_token
self._doorbells = self._api.get_doorbells(self._access_token) or []
self._locks = self._api.get_locks(self._access_token) or []
self._house_ids = [d.house_id for d in self._doorbells + self._locks]
self._doorbell_detail_by_id = {}
self._lock_status_by_id = {}
self._lock_detail_by_id = {}
self._activities_by_id = {}
@property
def house_ids(self):
"""Return a list of house_ids."""
return self._house_ids
@property
def doorbells(self):
"""Return a list of doorbells."""
return self._doorbells
@property
def locks(self):
"""Return a list of locks."""
return self._locks
def get_device_activities(self, device_id, *activity_types):
"""Return a list of activities."""
self._update_device_activities()
activities = self._activities_by_id.get(device_id, [])
if activity_types:
return [a for a in activities if a.activity_type in activity_types]
return activities
def get_latest_device_activity(self, device_id, *activity_types):
"""Return latest activity."""
activities = self.get_device_activities(device_id, *activity_types)
return next(iter(activities or []), None)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
"""Update data object with latest from August API."""
for house_id in self.house_ids:
activities = self._api.get_house_activities(self._access_token,
house_id,
limit=limit)
device_ids = {a.device_id for a in activities}
for device_id in device_ids:
self._activities_by_id[device_id] = [a for a in activities if
a.device_id == device_id]
def get_doorbell_detail(self, doorbell_id):
"""Return doorbell detail."""
self._update_doorbells()
return self._doorbell_detail_by_id.get(doorbell_id)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_doorbells(self):
detail_by_id = {}
for doorbell in self._doorbells:
detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail(
self._access_token, doorbell.device_id)
self._doorbell_detail_by_id = detail_by_id
def get_lock_status(self, lock_id):
"""Return lock status."""
self._update_locks()
return self._lock_status_by_id.get(lock_id)
def get_lock_detail(self, lock_id):
"""Return lock detail."""
self._update_locks()
return self._lock_detail_by_id.get(lock_id)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_locks(self):
status_by_id = {}
detail_by_id = {}
for lock in self._locks:
status_by_id[lock.device_id] = self._api.get_lock_status(
self._access_token, lock.device_id)
detail_by_id[lock.device_id] = self._api.get_lock_detail(
self._access_token, lock.device_id)
self._lock_status_by_id = status_by_id
self._lock_detail_by_id = detail_by_id
def lock(self, device_id):
"""Lock the device."""
return self._api.lock(self._access_token, device_id)
def unlock(self, device_id):
"""Unlock the device."""
return self._api.unlock(self._access_token, device_id)

View File

@@ -0,0 +1,97 @@
"""
Support for August binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.august/
"""
from datetime import timedelta, datetime
from homeassistant.components.august import DATA_AUGUST
from homeassistant.components.binary_sensor import (BinarySensorDevice)
DEPENDENCIES = ['august']
SCAN_INTERVAL = timedelta(seconds=5)
def _retrieve_online_state(data, doorbell):
"""Get the latest state of the sensor."""
detail = data.get_doorbell_detail(doorbell.device_id)
return detail.is_online
def _retrieve_motion_state(data, doorbell):
from august.activity import ActivityType
return _activity_time_based_state(data, doorbell,
[ActivityType.DOORBELL_MOTION,
ActivityType.DOORBELL_DING])
def _retrieve_ding_state(data, doorbell):
from august.activity import ActivityType
return _activity_time_based_state(data, doorbell,
[ActivityType.DOORBELL_DING])
def _activity_time_based_state(data, doorbell, activity_types):
"""Get the latest state of the sensor."""
latest = data.get_latest_device_activity(doorbell.device_id,
*activity_types)
if latest is not None:
start = latest.activity_start_time
end = latest.activity_end_time + timedelta(seconds=30)
return start <= datetime.now() <= end
return None
# Sensor types: Name, device_class, state_provider
SENSOR_TYPES = {
'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state],
'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state],
'doorbell_online': ['Online', 'connectivity', _retrieve_online_state],
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the August binary sensors."""
data = hass.data[DATA_AUGUST]
devices = []
for doorbell in data.doorbells:
for sensor_type in SENSOR_TYPES:
devices.append(AugustBinarySensor(data, sensor_type, doorbell))
add_devices(devices, True)
class AugustBinarySensor(BinarySensorDevice):
"""Representation of an August binary sensor."""
def __init__(self, data, sensor_type, doorbell):
"""Initialize the sensor."""
self._data = data
self._sensor_type = sensor_type
self._doorbell = doorbell
self._state = None
@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 this device, from component DEVICE_CLASSES."""
return SENSOR_TYPES[self._sensor_type][1]
@property
def name(self):
"""Return the name of the binary sensor."""
return "{} {}".format(self._doorbell.device_name,
SENSOR_TYPES[self._sensor_type][0])
def update(self):
"""Get the latest state of the sensor."""
state_provider = SENSOR_TYPES[self._sensor_type][2]
self._state = state_provider(self._data, self._doorbell)

View File

@@ -24,7 +24,7 @@ SENSOR_TYPES = {
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES):
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})

View File

@@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.deconz/
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz import DOMAIN as DECONZ_DATA
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
@@ -21,7 +22,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return
from pydeconz.sensor import DECONZ_BINARY_SENSOR
sensors = hass.data[DECONZ_DATA].sensors
sensors = hass.data[DATA_DECONZ].sensors
entities = []
for key in sorted(sensors.keys(), key=int):
@@ -42,6 +43,7 @@ class DeconzBinarySensor(BinarySensorDevice):
def async_added_to_hass(self):
"""Subscribe sensors events."""
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
@callback
def async_update_callback(self, reason):

View File

@@ -50,7 +50,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
self._zone_type = zone_type
self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name)
_LOGGER.debug('Setting up zone: %s', zone_name)
super().__init__(zone_name, info, controller)
@asyncio.coroutine

View File

@@ -56,7 +56,7 @@ CUSTOMIZE_SCHEMA = vol.Schema({
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean,

View File

@@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All({
vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE, default=None): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
}, validate_name)
])
@@ -43,7 +43,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
product_cfg = device['product_cfg']
product = device['product']
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg[CONF_TYPE],
product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
@@ -52,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for sensor_cfg in binary_sensors:
ihc_id = sensor_cfg[CONF_ID]
name = sensor_cfg[CONF_NAME]
sensor_type = sensor_cfg[CONF_TYPE]
sensor_type = sensor_cfg.get(CONF_TYPE)
inverting = sensor_cfg[CONF_INVERTING]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
sensor_type, inverting)
@@ -70,7 +70,7 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice):
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool,
product: Element=None) -> None:
product: Element = None) -> None:
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None

View File

@@ -35,7 +35,7 @@ DEPENDENCIES = ['knx']
AUTOMATION_SCHEMA = vol.Schema({
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
vol.Required(CONF_ACTION, default=None): cv.SCRIPT_SCHEMA
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA
})
AUTOMATIONS_SCHEMA = vol.All(
@@ -49,16 +49,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT):
cv.positive_int,
vol.Optional(CONF_AUTOMATION, default=None): AUTOMATIONS_SCHEMA,
vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up binary sensor(s) for KNX platform."""
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
else:

View File

@@ -9,7 +9,7 @@ import datetime
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.components.mercedesme import (
DATA_MME, MercedesMeEntity, BINARY_SENSORS)
DATA_MME, FEATURE_NOT_AVAILABLE, MercedesMeEntity, BINARY_SENSORS)
DEPENDENCIES = ['mercedesme']
@@ -27,8 +27,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = []
for car in data.cars:
for key, value in sorted(BINARY_SENSORS.items()):
devices.append(MercedesMEBinarySensor(
data, key, value[0], car["vin"], None))
if car['availabilities'].get(key, 'INVALID') == 'VALID':
devices.append(MercedesMEBinarySensor(
data, key, value[0], car["vin"], None))
else:
_LOGGER.warning(FEATURE_NOT_AVAILABLE, key, car["license"])
add_devices(devices, True)

View File

@@ -50,10 +50,10 @@ 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_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES):
vol.Optional(CONF_PRESENCE_SENSORS, default=list(PRESENCE_SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES):
vol.Optional(CONF_WELCOME_SENSORS, default=list(WELCOME_SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
})
@@ -131,8 +131,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._name += ' / ' + module_name
self._sensor_name = sensor
self._name += ' ' + sensor
self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._cameratype = camera_type
self._state = None
@@ -141,11 +139,6 @@ class NetatmoBinarySensor(BinarySensorDevice):
"""Return the name of the Netatmo device and this sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""

View File

@@ -27,7 +27,7 @@ SENSOR_TYPES = {
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES):
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})

View File

@@ -28,15 +28,15 @@ DEPENDENCIES = ['rfxtrx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_DEVICE_CLASS, default=None):
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS):
DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_OFF_DELAY, default=None):
vol.Optional(CONF_OFF_DELAY):
vol.Any(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_DATA_BITS, default=None): cv.positive_int,
vol.Optional(CONF_COMMAND_ON, default=None): cv.byte,
vol.Optional(CONF_COMMAND_OFF, default=None): cv.byte
vol.Optional(CONF_DATA_BITS): cv.positive_int,
vol.Optional(CONF_COMMAND_ON): cv.byte,
vol.Optional(CONF_COMMAND_OFF): cv.byte
})
},
vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean,
@@ -48,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import RFXtrx as rfxtrxmod
sensors = []
for packet_id, entity in config['devices'].items():
for packet_id, entity in config[CONF_DEVICES].items():
event = rfxtrx.get_rfx_object(packet_id)
device_id = slugify(event.device.id_string.lower())
@@ -64,10 +64,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
device = RfxtrxBinarySensor(
event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS],
entity[CONF_FIRE_EVENT], entity[CONF_OFF_DELAY],
entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON],
entity[CONF_COMMAND_OFF])
event, entity.get(CONF_NAME), entity.get(CONF_DEVICE_CLASS),
entity[CONF_FIRE_EVENT], entity.get(CONF_OFF_DELAY),
entity.get(CONF_DATA_BITS), entity.get(CONF_COMMAND_ON),
entity.get(CONF_COMMAND_OFF))
device.hass = hass
sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device

View File

@@ -26,7 +26,7 @@ DEFAULT_SETTLE_TIME = 20
DEPENDENCIES = ['rpi_pfio']
PORT_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
cv.positive_int,
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
@@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
binary_sensors = []
ports = config.get(CONF_PORTS)
for port, port_entity in ports.items():
name = port_entity[CONF_NAME]
name = port_entity.get(CONF_NAME)
settle_time = port_entity[CONF_SETTLE_TIME] / 1000
invert_logic = port_entity[CONF_INVERT_LOGIC]

View File

@@ -47,7 +47,7 @@ DEFAULT_OFFSET = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
vol.Optional(CONF_PROVINCE, default=None): cv.string,
vol.Optional(CONF_PROVINCE): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):

View File

@@ -0,0 +1,105 @@
"""
Reads vehicle status from BMW connected drive portal.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/bmw_connected_drive/
"""
import logging
import datetime
import voluptuous as vol
from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD
)
REQUIREMENTS = ['bimmer_connected==0.3.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'bmw_connected_drive'
CONF_VALUES = 'values'
CONF_COUNTRY = 'country'
ACCOUNT_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_COUNTRY): cv.string,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
cv.string: ACCOUNT_SCHEMA
},
}, extra=vol.ALLOW_EXTRA)
BMW_COMPONENTS = ['device_tracker', 'sensor']
UPDATE_INTERVAL = 5 # in minutes
def setup(hass, config):
"""Set up the BMW connected drive components."""
accounts = []
for name, account_config in config[DOMAIN].items():
username = account_config[CONF_USERNAME]
password = account_config[CONF_PASSWORD]
country = account_config[CONF_COUNTRY]
_LOGGER.debug('Adding new account %s', name)
bimmer = BMWConnectedDriveAccount(username, password, country, name)
accounts.append(bimmer)
# update every UPDATE_INTERVAL minutes, starting now
# this should even out the load on the servers
now = datetime.datetime.now()
track_utc_time_change(
hass, bimmer.update,
minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL),
second=now.second)
hass.data[DOMAIN] = accounts
for account in accounts:
account.update()
for component in BMW_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
class BMWConnectedDriveAccount(object):
"""Representation of a BMW vehicle."""
def __init__(self, username: str, password: str, country: str,
name: str) -> None:
"""Constructor."""
from bimmer_connected.account import ConnectedDriveAccount
self.account = ConnectedDriveAccount(username, password, country)
self.name = name
self._update_listeners = []
def update(self, *_):
"""Update the state of all vehicles.
Notify all listeners about the update.
"""
_LOGGER.debug('Updating vehicle state for account %s, '
'notifying %d listeners',
self.name, len(self._update_listeners))
try:
self.account.update_vehicle_states()
for listener in self._update_listeners:
listener()
except IOError as exception:
_LOGGER.error('Error updating the vehicle state.')
_LOGGER.exception(exception)
def add_update_listener(self, listener):
"""Add a listener for update notifications."""
self._update_listeners.append(listener)

View File

@@ -166,7 +166,7 @@ class WebDavCalendarData(object):
self.event = {
"summary": vevent.summary.value,
"start": self.get_hass_date(vevent.dtstart.value),
"end": self.get_hass_date(vevent.dtend.value),
"end": self.get_hass_date(self.get_end_date(vevent)),
"location": self.get_attr_value(vevent, "location"),
"description": self.get_attr_value(vevent, "description")
}
@@ -194,7 +194,7 @@ class WebDavCalendarData(object):
@staticmethod
def is_over(vevent):
"""Return if the event is over."""
return dt.now() > WebDavCalendarData.to_datetime(vevent.dtend.value)
return dt.now() > WebDavCalendarData.get_end_date(vevent)
@staticmethod
def get_hass_date(obj):
@@ -217,3 +217,17 @@ class WebDavCalendarData(object):
if hasattr(obj, attribute):
return getattr(obj, attribute).value
return None
@staticmethod
def get_end_date(obj):
"""Return the end datetime as determined by dtend or duration."""
if hasattr(obj, "dtend"):
enddate = obj.dtend.value
elif hasattr(obj, "duration"):
enddate = obj.dtstart.value + obj.duration.value
else:
enddate = obj.dtstart.value + timedelta(days=1)
return WebDavCalendarData.to_datetime(enddate)

View File

@@ -498,7 +498,7 @@ class TodoistProjectData(object):
# Organize the best tasks (so users can see all the tasks
# they have, organized)
while len(project_tasks) > 0:
while project_tasks:
best_task = self.select_best_task(project_tasks)
_LOGGER.debug("Found Todoist Task: %s", best_task[SUMMARY])
project_tasks.remove(best_task)

View File

@@ -0,0 +1,76 @@
"""
Support for August camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.august/
"""
from datetime import timedelta
import requests
from homeassistant.components.august import DATA_AUGUST, DEFAULT_TIMEOUT
from homeassistant.components.camera import Camera
DEPENDENCIES = ['august']
SCAN_INTERVAL = timedelta(seconds=5)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up August cameras."""
data = hass.data[DATA_AUGUST]
devices = []
for doorbell in data.doorbells:
devices.append(AugustCamera(data, doorbell, DEFAULT_TIMEOUT))
add_devices(devices, True)
class AugustCamera(Camera):
"""An implementation of a Canary security camera."""
def __init__(self, data, doorbell, timeout):
"""Initialize a Canary security camera."""
super().__init__()
self._data = data
self._doorbell = doorbell
self._timeout = timeout
self._image_url = None
self._image_content = None
@property
def name(self):
"""Return the name of this device."""
return self._doorbell.device_name
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._doorbell.has_subscription
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return True
@property
def brand(self):
"""Return the camera brand."""
return 'August'
@property
def model(self):
"""Return the camera model."""
return 'Doorbell'
def camera_image(self):
"""Return bytes of camera image."""
latest = self._data.get_doorbell_detail(self._doorbell.device_id)
if self._image_url is not latest.image_url:
self._image_url = latest.image_url
self._image_content = requests.get(self._image_url,
timeout=self._timeout).content
return self._image_content

View File

@@ -18,8 +18,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
DEPENDENCIES = ['doorbird']
_CAMERA_LAST_VISITOR = "DoorBird Last Ring"
_CAMERA_LAST_MOTION = "DoorBird Last Motion"
_CAMERA_LIVE = "DoorBird Live"
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
_LOGGER = logging.getLogger(__name__)
_TIMEOUT = 10 # seconds
@@ -34,6 +36,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
DoorBirdCamera(
device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR,
_LAST_VISITOR_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'motionsensor'), _CAMERA_LAST_MOTION,
_LAST_MOTION_INTERVAL),
])

View File

@@ -119,6 +119,8 @@ class MjpegCamera(Camera):
else:
req = requests.get(self._mjpeg_url, stream=True, timeout=10)
# https://github.com/PyCQA/pylint/issues/1437
# pylint: disable=no-member
with closing(req) as response:
return extract_image_from_mjpeg(response.iter_content(102400))

View File

@@ -67,8 +67,6 @@ class NetatmoCamera(Camera):
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
camera=camera_name
)
self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._cameratype = camera_type
def camera_image(self):
@@ -112,8 +110,3 @@ class NetatmoCamera(Camera):
elif self._cameratype == "NACamera":
return "Welcome"
return None
@property
def unique_id(self):
"""Return the unique ID for this camera."""
return self._unique_id

View File

@@ -6,18 +6,19 @@ https://home-assistant.io/components/camera.onvif/
"""
import asyncio
import logging
import os
import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
ATTR_ENTITY_ID)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, DOMAIN
from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream)
from homeassistant.helpers.service import extract_entity_ids
_LOGGER = logging.getLogger(__name__)
@@ -33,6 +34,22 @@ DEFAULT_USERNAME = 'admin'
DEFAULT_PASSWORD = '888888'
DEFAULT_ARGUMENTS = '-q:v 2'
ATTR_PAN = "pan"
ATTR_TILT = "tilt"
ATTR_ZOOM = "zoom"
DIR_UP = "UP"
DIR_DOWN = "DOWN"
DIR_LEFT = "LEFT"
DIR_RIGHT = "RIGHT"
ZOOM_OUT = "ZOOM_OUT"
ZOOM_IN = "ZOOM_IN"
SERVICE_PTZ = "onvif_ptz"
ONVIF_DATA = "onvif"
ENTITIES = "entities"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -42,36 +59,98 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
})
SERVICE_PTZ_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids,
ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]),
ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]),
ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN])
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a ONVIF camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)):
return
async_add_devices([ONVIFCamera(hass, config)])
def handle_ptz(service):
"""Handle PTZ service call."""
pan = service.data.get(ATTR_PAN, None)
tilt = service.data.get(ATTR_TILT, None)
zoom = service.data.get(ATTR_ZOOM, None)
all_cameras = hass.data[ONVIF_DATA][ENTITIES]
entity_ids = extract_entity_ids(hass, service)
target_cameras = []
if not entity_ids:
target_cameras = all_cameras
else:
target_cameras = [camera for camera in all_cameras
if camera.entity_id in entity_ids]
for camera in target_cameras:
camera.perform_ptz(pan, tilt, zoom)
hass.services.async_register(DOMAIN, SERVICE_PTZ, handle_ptz,
schema=SERVICE_PTZ_SCHEMA)
async_add_devices([ONVIFHassCamera(hass, config)])
class ONVIFCamera(Camera):
class ONVIFHassCamera(Camera):
"""An implementation of an ONVIF camera."""
def __init__(self, hass, config):
"""Initialize a ONVIF camera."""
from onvif import ONVIFService
import onvif
from onvif import ONVIFCamera, exceptions
super().__init__()
self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
media = ONVIFService(
'http://{}:{}/onvif/device_service'.format(
config.get(CONF_HOST), config.get(CONF_PORT)),
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD),
'{}/wsdl/media.wsdl'.format(os.path.dirname(onvif.__file__))
)
self._input = media.GetStreamUri().Uri
_LOGGER.debug("ONVIF Camera Using the following URL for %s: %s",
self._name, self._input)
self._input = None
camera = None
try:
_LOGGER.debug("Connecting with ONVIF Camera: %s on port %s",
config.get(CONF_HOST), config.get(CONF_PORT))
camera = ONVIFCamera(
config.get(CONF_HOST), config.get(CONF_PORT),
config.get(CONF_USERNAME), config.get(CONF_PASSWORD)
)
media_service = camera.create_media_service()
stream_uri = media_service.GetStreamUri(
{'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': 'RTSP'}}
)
self._input = stream_uri.Uri.replace(
'rtsp://', 'rtsp://{}:{}@'.format(
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD)), 1)
_LOGGER.debug(
"ONVIF Camera Using the following URL for %s: %s",
self._name, self._input)
except Exception as err:
_LOGGER.error("Unable to communicate with ONVIF Camera: %s", err)
raise
try:
self._ptz = camera.create_ptz_service()
except exceptions.ONVIFError as err:
self._ptz = None
_LOGGER.warning("Unable to setup PTZ for ONVIF Camera: %s", err)
def perform_ptz(self, pan, tilt, zoom):
"""Perform a PTZ action on the camera."""
if self._ptz:
pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0
tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0
zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0
req = {"Velocity": {
"PanTilt": {"_x": pan_val, "_y": tilt_val},
"Zoom": {"_x": zoom_val}}}
self._ptz.ContinuousMove(req)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
if ONVIF_DATA not in self.hass.data:
self.hass.data[ONVIF_DATA] = {}
self.hass.data[ONVIF_DATA][ENTITIES] = []
self.hass.data[ONVIF_DATA][ENTITIES].append(self)
@asyncio.coroutine
def async_camera_image(self):

View File

@@ -8,6 +8,7 @@ import os
import subprocess
import logging
import shutil
from tempfile import NamedTemporaryFile
import voluptuous as vol
@@ -36,7 +37,7 @@ DEFAULT_TIMELAPSE = 1000
DEFAULT_VERTICAL_FLIP = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FILE_PATH): cv.string,
vol.Optional(CONF_FILE_PATH): cv.isfile,
vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP):
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT):
@@ -77,25 +78,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
CONF_TIMELAPSE: config.get(CONF_TIMELAPSE),
CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP),
CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP),
CONF_FILE_PATH: config.get(CONF_FILE_PATH,
os.path.join(os.path.dirname(__file__),
'image.jpg'))
CONF_FILE_PATH: config.get(CONF_FILE_PATH)
}
)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill)
try:
# Try to create an empty file (or open existing) to ensure we have
# proper permissions.
open(setup_config[CONF_FILE_PATH], 'a').close()
file_path = setup_config[CONF_FILE_PATH]
add_devices([RaspberryCamera(setup_config)])
except PermissionError:
_LOGGER.error("File path is not writable")
return False
except FileNotFoundError:
_LOGGER.error("Could not create output file (missing directory?)")
def delete_temp_file(*args):
"""Delete the temporary file to prevent saving multiple temp images.
Only used when no path is defined
"""
os.remove(file_path)
# If no file path is defined, use a temporary file
if file_path is None:
temp_file = NamedTemporaryFile(suffix='.jpg', delete=False)
temp_file.close()
file_path = temp_file.name
setup_config[CONF_FILE_PATH] = file_path
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, delete_temp_file)
# Check whether the file path has been whitelisted
elif not hass.config.is_allowed_path(file_path):
_LOGGER.error("'%s' is not a whitelisted directory", file_path)
return False

View File

@@ -23,3 +23,20 @@ snapshot:
filename:
description: Template of a Filename. Variable is entity_id.
example: '/tmp/snapshot_{{ entity_id }}'
onvif_ptz:
description: Pan/Tilt/Zoom service for ONVIF camera.
fields:
entity_id:
description: Name(s) of entities to pan, tilt or zoom.
example: 'camera.living_room_camera'
pan:
description: "Direction of pan. Allowed values: LEFT, RIGHT."
example: 'LEFT'
tilt:
description: "Direction of tilt. Allowed values: DOWN, UP."
example: 'DOWN'
zoom:
description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT"
example: "ZOOM_IN"

View File

@@ -188,7 +188,7 @@ class UnifiVideoCamera(Camera):
self._nvr.set_recordmode(self._uuid, set_mode)
self._motion_status = mode
except NvrError as err:
_LOGGER.error("Unable to set recordmode to " + set_mode)
_LOGGER.error("Unable to set recordmode to %s", set_mode)
_LOGGER.debug(err)
def enable_motion_detection(self):

View File

@@ -33,7 +33,7 @@ CAMERAS_SCHEMA = vol.Schema({
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_CAMERAS, default={}):
vol.Optional(CONF_CAMERAS):
vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])),
vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean,
vol.Optional(CONF_PASSWORD): cv.string,
@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Discover and setup Xeoma Cameras."""
from pyxeoma.xeoma import Xeoma, XeomaError
@@ -68,7 +67,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for image_name, username, pw in discovered_image_names
]
for cam in config[CONF_CAMERAS]:
for cam in config.get(CONF_CAMERAS, []):
# https://github.com/PyCQA/pylint/issues/1830
# pylint: disable=stop-iteration-return
camera = next(
(dc for dc in discovered_cameras
if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None)

View File

@@ -669,16 +669,16 @@ class ClimateDevice(Entity):
"""
return self.hass.async_add_job(self.set_humidity, humidity)
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
raise NotImplementedError()
def async_set_fan_mode(self, fan):
def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.set_fan_mode, fan)
return self.hass.async_add_job(self.set_fan_mode, fan_mode)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""

View File

@@ -28,7 +28,7 @@ _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=None): cv.string,
vol.Optional(CONF_NAME): cv.string,
})
HA_STATE_TO_DAIKIN = {
@@ -236,9 +236,9 @@ class DaikinClimate(ClimateDevice):
"""Return the fan setting."""
return self.get(ATTR_FAN_MODE)
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set fan mode."""
self.set({ATTR_FAN_MODE: fan})
self.set({ATTR_FAN_MODE: fan_mode})
@property
def fan_list(self):

View File

@@ -195,9 +195,9 @@ class DemoClimate(ClimateDevice):
self._current_swing_mode = swing_mode
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set new target temperature."""
self._current_fan_mode = fan
self._current_fan_mode = fan_mode
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
@@ -225,9 +225,9 @@ class DemoClimate(ClimateDevice):
self._away = False
self.schedule_update_ha_state()
def set_hold_mode(self, hold):
"""Update hold mode on."""
self._hold = hold
def set_hold_mode(self, hold_mode):
"""Update hold_mode on."""
self._hold = hold_mode
self.schedule_update_ha_state()
def turn_aux_heat_on(self):

View File

@@ -98,8 +98,7 @@ class EphEmberThermostat(ClimateDevice):
"""Return current operation ie. heat, cool, idle."""
if self._zone['isCurrentlyActive']:
return STATE_HEAT
else:
return STATE_IDLE
return STATE_IDLE
@property
def is_aux_heat_on(self):

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.8']
REQUIREMENTS = ['python-eq3bt==0.1.9']
_LOGGER = logging.getLogger(__name__)
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices)
# pylint: disable=import-error
# pylint: disable=import-error, no-name-in-module
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of an eQ-3 Bluetooth Smart thermostat."""
@@ -75,6 +75,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._name = _name
self._thermostat = eq3.Thermostat(_mac)
self._target_temperature = None
self._target_mode = None
@property
def supported_features(self):
@@ -116,6 +118,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
self._target_temperature = temperature
self._thermostat.target_temperature = temperature
@property
@@ -132,6 +135,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
self._target_mode = operation_mode
self._thermostat.mode = self.reverse_modes[operation_mode]
def turn_away_mode_off(self):
@@ -177,3 +181,15 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._thermostat.update()
except BTLEException as ex:
_LOGGER.warning("Updating the state failed: %s", ex)
if (self._target_temperature and
self._thermostat.target_temperature
!= self._target_temperature):
self.set_temperature(temperature=self._target_temperature)
else:
self._target_temperature = None
if (self._target_mode and
self.modes[self._thermostat.mode] != self._target_mode):
self.set_operation_mode(operation_mode=self._target_mode)
else:
self._target_mode = None

View File

@@ -152,6 +152,6 @@ class Flexit(ClimateDevice):
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
self.unit.set_temp(self._target_temperature)
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set new fan mode."""
self.unit.set_fan_speed(self._fan_list.index(fan))
self.unit.set_fan_speed(self._fan_list.index(fan_mode))

View File

@@ -190,11 +190,9 @@ class GenericThermostat(ClimateDevice):
"""Return the current state."""
if self._is_device_active:
return self.current_operation
else:
if self._enabled:
return STATE_IDLE
else:
return STATE_OFF
if self._enabled:
return STATE_IDLE
return STATE_OFF
@property
def should_poll(self):

View File

@@ -48,9 +48,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
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_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN):
vol.All(int, vol.Range(min=-32, max=0)),
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,
@@ -64,9 +64,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up climate(s) for KNX platform."""
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
else:

View File

@@ -26,7 +26,7 @@ SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
OP_MODES = [
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
]
FAN_MODES = [
@@ -42,8 +42,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
all_devices = []
for device in devices:
all_devices.append(MelissaClimate(
api, device['serial_number'], device))
if device['type'] == 'melissa':
all_devices.append(MelissaClimate(
api, device['serial_number'], device))
add_devices(all_devices)
@@ -146,10 +147,10 @@ class MelissaClimate(ClimateDevice):
temp = kwargs.get(ATTR_TEMPERATURE)
self.send({self._api.TEMP: temp})
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set fan mode."""
fan_mode = self.hass_fan_to_melissa(fan)
self.send({self._api.FAN: fan_mode})
melissa_fan_mode = self.hass_fan_to_melissa(fan_mode)
self.send({self._api.FAN: melissa_fan_mode})
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
@@ -174,8 +175,7 @@ class MelissaClimate(ClimateDevice):
if not self._api.send(self._serial_number, self._cur_settings):
self._cur_settings = old_value
return False
else:
return True
return True
def update(self):
"""Get latest data from Melissa."""
@@ -196,14 +196,11 @@ class MelissaClimate(ClimateDevice):
return STATE_OFF
elif state == self._api.STATE_IDLE:
return STATE_IDLE
else:
return None
return None
def melissa_op_to_hass(self, mode):
"""Translate Melissa modes to hass states."""
if mode == self._api.MODE_AUTO:
return STATE_AUTO
elif mode == self._api.MODE_HEAT:
if mode == self._api.MODE_HEAT:
return STATE_HEAT
elif mode == self._api.MODE_COOL:
return STATE_COOL
@@ -211,10 +208,9 @@ class MelissaClimate(ClimateDevice):
return STATE_DRY
elif mode == self._api.MODE_FAN:
return STATE_FAN_ONLY
else:
_LOGGER.warning(
"Operation mode %s could not be mapped to hass", mode)
return None
_LOGGER.warning(
"Operation mode %s could not be mapped to hass", mode)
return None
def melissa_fan_to_hass(self, fan):
"""Translate Melissa fan modes to hass modes."""
@@ -226,15 +222,12 @@ class MelissaClimate(ClimateDevice):
return SPEED_MEDIUM
elif fan == self._api.FAN_HIGH:
return SPEED_HIGH
else:
_LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
return None
_LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
return None
def hass_mode_to_melissa(self, mode):
"""Translate hass states to melissa modes."""
if mode == STATE_AUTO:
return self._api.MODE_AUTO
elif mode == STATE_HEAT:
if mode == STATE_HEAT:
return self._api.MODE_HEAT
elif mode == STATE_COOL:
return self._api.MODE_COOL

View File

@@ -482,15 +482,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_set_fan_mode(self, fan):
def async_set_fan_mode(self, fan_mode):
"""Set new target temperature."""
if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish(
self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC],
fan, self._qos, self._retain)
fan_mode, self._qos, self._retain)
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = fan
self._current_fan_mode = fan_mode
self.async_schedule_update_ha_state()
@asyncio.coroutine
@@ -552,15 +552,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
@asyncio.coroutine
def async_set_hold_mode(self, hold):
def async_set_hold_mode(self, hold_mode):
"""Update hold mode on."""
if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_HOLD_COMMAND_TOPIC],
hold, self._qos, self._retain)
hold_mode, self._qos, self._retain)
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
self._hold = hold
self._hold = hold_mode
self.async_schedule_update_ha_state()
@asyncio.coroutine

View File

@@ -143,14 +143,14 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self._values[value_type] = value
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan)
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan_mode)
if self.gateway.optimistic:
# Optimistically assume that device has changed state
self._values[set_req.V_HVAC_SPEED] = fan
self._values[set_req.V_HVAC_SPEED] = fan_mode
self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode):

View File

@@ -207,9 +207,9 @@ class NestThermostat(ClimateDevice):
"""List of available fan modes."""
return self._fan_list
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Turn fan on/off."""
self.device.fan = fan.lower()
self.device.fan = fan_mode.lower()
@property
def min_temp(self):
@@ -225,7 +225,7 @@ class NestThermostat(ClimateDevice):
"""Cache value from Python-nest."""
self._location = self.device.where
self._name = self.device.name
self._humidity = self.device.humidity,
self._humidity = self.device.humidity
self._temperature = self.device.temperature
self._mode = self.device.mode
self._target_temperature = self.device.target

View File

@@ -185,7 +185,7 @@ class NuHeatThermostat(ClimateDevice):
self._thermostat.resume_schedule()
self._force_update = True
def set_hold_mode(self, hold_mode, **kwargs):
def set_hold_mode(self, hold_mode):
"""Update the hold mode of the thermostat."""
if hold_mode == MODE_AUTO:
schedule_mode = SCHEDULE_RUN

View File

@@ -183,17 +183,16 @@ class RadioThermostat(ClimateDevice):
"""List of available fan modes."""
if self._is_model_ct80:
return CT80_FAN_OPERATION_LIST
else:
return CT30_FAN_OPERATION_LIST
return CT30_FAN_OPERATION_LIST
@property
def current_fan_mode(self):
"""Return whether the fan is on."""
return self._fmode
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Turn fan on/off."""
code = FAN_MODE_TO_CODE.get(fan, None)
code = FAN_MODE_TO_CODE.get(fan_mode, None)
if code is not None:
self.device.fmode = code

View File

@@ -240,13 +240,13 @@ class SensiboClimate(ClimateDevice):
def min_temp(self):
"""Return the minimum temperature."""
return self._temperatures_list[0] \
if len(self._temperatures_list) else super().min_temp()
if self._temperatures_list else super().min_temp()
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._temperatures_list[-1] \
if len(self._temperatures_list) else super().max_temp()
if self._temperatures_list else super().max_temp()
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
@@ -273,11 +273,11 @@ class SensiboClimate(ClimateDevice):
self._id, 'targetTemperature', temperature, self._ac_states)
@asyncio.coroutine
def async_set_fan_mode(self, fan):
def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
with async_timeout.timeout(TIMEOUT):
yield from self._client.async_set_ac_state_property(
self._id, 'fanLevel', fan, self._ac_states)
self._id, 'fanLevel', fan_mode, self._ac_states)
@asyncio.coroutine
def async_set_operation_mode(self, operation_mode):

View File

@@ -213,6 +213,7 @@ class TadoClimate(ClimateDevice):
self._target_temp = temperature
self._control_heating()
# pylint: disable=arguments-differ
def set_operation_mode(self, readable_operation_mode):
"""Set new operation mode."""
operation_mode = CONST_MODE_SMART_SCHEDULE

View File

@@ -51,8 +51,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
mode = self.tesla_device.is_hvac_enabled()
if mode:
return OPERATION_LIST[0] # On
else:
return OPERATION_LIST[1] # Off
return OPERATION_LIST[1] # Off
@property
def operation_list(self):

View File

@@ -111,8 +111,7 @@ class VenstarThermostat(ClimateDevice):
"""Return the unit of measurement, as defined by the API."""
if self._client.tempunits == self._client.TEMPUNITS_F:
return TEMP_FAHRENHEIT
else:
return TEMP_CELSIUS
return TEMP_CELSIUS
@property
def fan_list(self):
@@ -143,16 +142,14 @@ class VenstarThermostat(ClimateDevice):
return STATE_COOL
elif self._client.mode == self._client.MODE_AUTO:
return STATE_AUTO
else:
return STATE_OFF
return STATE_OFF
@property
def current_fan_mode(self):
"""Return the fan setting."""
if self._client.fan == self._client.FAN_AUTO:
return STATE_AUTO
else:
return STATE_ON
return STATE_ON
@property
def device_state_attributes(self):
@@ -169,24 +166,21 @@ class VenstarThermostat(ClimateDevice):
return self._client.heattemp
elif self._client.mode == self._client.MODE_COOL:
return self._client.cooltemp
else:
return None
return None
@property
def target_temperature_low(self):
"""Return the lower bound temp if auto mode is on."""
if self._client.mode == self._client.MODE_AUTO:
return self._client.heattemp
else:
return None
return None
@property
def target_temperature_high(self):
"""Return the upper bound temp if auto mode is on."""
if self._client.mode == self._client.MODE_AUTO:
return self._client.cooltemp
else:
return None
return None
@property
def target_humidity(self):
@@ -245,9 +239,9 @@ class VenstarThermostat(ClimateDevice):
if not success:
_LOGGER.error("Failed to change the temperature")
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
if fan == STATE_ON:
if fan_mode == STATE_ON:
success = self._client.set_fan(self._client.FAN_ON)
else:
success = self._client.set_fan(self._client.FAN_AUTO)

View File

@@ -85,13 +85,13 @@ class VeraThermostat(VeraDevice, ClimateDevice):
"""Return a list of available fan modes."""
return FAN_OPERATION_LIST
def set_fan_mode(self, mode):
def set_fan_mode(self, fan_mode):
"""Set new target temperature."""
if mode == FAN_OPERATION_LIST[0]:
if fan_mode == FAN_OPERATION_LIST[0]:
self.vera_device.fan_on()
elif mode == FAN_OPERATION_LIST[1]:
elif fan_mode == FAN_OPERATION_LIST[1]:
self.vera_device.fan_auto()
elif mode == FAN_OPERATION_LIST[2]:
elif fan_mode == FAN_OPERATION_LIST[2]:
return self.vera_device.fan_cycle()
@property

View File

@@ -324,9 +324,9 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return self.wink.fan_modes()
return None
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Turn fan on/off."""
self.wink.set_fan_mode(fan.lower())
self.wink.set_fan_mode(fan_mode.lower())
def turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
@@ -486,26 +486,25 @@ class WinkAC(WinkDevice, ClimateDevice):
return SPEED_LOW
elif speed <= 0.66:
return SPEED_MEDIUM
else:
return SPEED_HIGH
return SPEED_HIGH
@property
def fan_list(self):
"""Return a list of available fan modes."""
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""
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:
if fan_mode == SPEED_LOW:
speed = 0.33
elif fan == SPEED_MEDIUM:
elif fan_mode == SPEED_MEDIUM:
speed = 0.66
elif fan == SPEED_HIGH:
elif fan_mode == SPEED_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)

View File

@@ -198,10 +198,10 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self.values.primary.data = temperature
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
if self.values.fan_mode:
self.values.fan_mode.data = fan
self.values.fan_mode.data = fan_mode
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""

View File

@@ -56,10 +56,7 @@ GOOGLE_ENTITY_SCHEMA = vol.Schema({
})
ASSISTANT_SCHEMA = vol.Schema({
vol.Optional(
CONF_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
})
ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({
@@ -222,7 +219,7 @@ class Cloud:
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.async_call_later(5, self.async_start)
self.hass.helpers.event.async_call_later(5, self.async_start)
return
def load_config():

View File

@@ -1,7 +1,4 @@
"""Package to communicate with the authentication API."""
import logging
_LOGGER = logging.getLogger(__name__)
class CloudError(Exception):
@@ -31,6 +28,8 @@ class InvalidCode(CloudError):
class PasswordChangeRequired(CloudError):
"""Raised when a password change is required."""
# https://github.com/PyCQA/pylint/issues/1085
# pylint: disable=useless-super-delegation
def __init__(self, message='Password change required.'):
"""Initialize a password change required error."""
super().__init__(message)

View File

@@ -6,8 +6,9 @@ import logging
import async_timeout
import voluptuous as vol
from homeassistant.components.http import (
HomeAssistantView, RequestDataValidator)
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import (
RequestDataValidator)
from . import auth_api
from .const import DOMAIN, REQUEST_TIMEOUT

View File

@@ -44,20 +44,13 @@ class CloudIoT:
@asyncio.coroutine
def connect(self):
"""Connect to the IoT broker."""
if self.state != STATE_DISCONNECTED:
raise RuntimeError('Connect called while not disconnected')
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.close_requested = False
self.state = STATE_CONNECTING
self.tries = 0
@asyncio.coroutine
def _handle_hass_stop(event):
@@ -66,17 +59,60 @@ class CloudIoT:
remove_hass_stop_listener = None
yield from self.disconnect()
self.state = STATE_CONNECTING
self.close_requested = False
remove_hass_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _handle_hass_stop)
while True:
try:
yield from self._handle_connection()
except Exception: # pylint: disable=broad-except
# Safety net. This should never hit.
# Still adding it here to make sure we can always reconnect
_LOGGER.exception("Unexpected error")
if self.close_requested:
break
self.state = STATE_CONNECTING
self.tries += 1
try:
# Sleep 0, 5, 10, 15 ... 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
except asyncio.CancelledError:
# Happens if disconnect called
break
self.state = STATE_DISCONNECTED
if remove_hass_stop_listener is not None:
remove_hass_stop_listener()
@asyncio.coroutine
def _handle_connection(self):
"""Connect to the IoT broker."""
hass = self.cloud.hass
try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
except auth_api.CloudError as err:
_LOGGER.warning("Unable to connect: %s", err)
return
if self.cloud.subscription_expired:
hass.components.persistent_notification.async_create(
MESSAGE_EXPIRATION, 'Subscription expired',
'cloud_subscription_expired')
self.close_requested = True
return
session = async_get_clientsession(self.cloud.hass)
client = None
disconnect_warn = None
try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
self.client = client = yield from session.ws_connect(
self.cloud.relayer, heartbeat=55, headers={
hdrs.AUTHORIZATION:
@@ -90,9 +126,11 @@ class CloudIoT:
while not client.closed:
msg = yield from client.receive()
if msg.type in (WSMsgType.ERROR, WSMsgType.CLOSED,
WSMsgType.CLOSING):
disconnect_warn = 'Connection cancelled.'
if msg.type in (WSMsgType.CLOSED, WSMsgType.CLOSING):
break
elif msg.type == WSMsgType.ERROR:
disconnect_warn = 'Connection error'
break
elif msg.type != WSMsgType.TEXT:
@@ -131,9 +169,6 @@ class CloudIoT:
_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.'
@@ -145,38 +180,11 @@ class CloudIoT:
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
if disconnect_warn is None:
_LOGGER.info("Connection closed")
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
_LOGGER.warning("Connection closed: %s", disconnect_warn)
@asyncio.coroutine
def disconnect(self):

View File

@@ -14,15 +14,23 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script')
ON_DEMAND = ('zwave')
ON_DEMAND = ('zwave',)
FEATURE_FLAGS = ('config_entries',)
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the config component."""
global SECTIONS
yield from hass.components.frontend.async_register_built_in_panel(
'config', 'config', 'mdi:settings')
# Temporary way of allowing people to opt-in for unreleased config sections
for key, value in config.get(DOMAIN, {}).items():
if key in FEATURE_FLAGS and value:
SECTIONS += (key,)
@asyncio.coroutine
def setup_panel(panel_name):
"""Set up a panel."""
@@ -151,7 +159,7 @@ class EditKeyBasedConfigView(BaseEditConfigView):
def _get_value(self, hass, data, config_key):
"""Get value."""
return data.get(config_key, {})
return data.get(config_key)
def _write_value(self, hass, data, config_key, new_value):
"""Set value."""

View File

@@ -0,0 +1,182 @@
"""Http views to control the config manager."""
import asyncio
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
REQUIREMENTS = ['voluptuous-serialize==1']
@asyncio.coroutine
def async_setup(hass):
"""Enable the Home Assistant views."""
hass.http.register_view(ConfigManagerEntryIndexView)
hass.http.register_view(ConfigManagerEntryResourceView)
hass.http.register_view(ConfigManagerFlowIndexView)
hass.http.register_view(ConfigManagerFlowResourceView)
hass.http.register_view(ConfigManagerAvailableFlowView)
return True
def _prepare_json(result):
"""Convert result for JSON."""
if result['type'] != config_entries.RESULT_TYPE_FORM:
return result
import voluptuous_serialize
data = result.copy()
schema = data['data_schema']
if schema is None:
data['data_schema'] = []
else:
data['data_schema'] = voluptuous_serialize.convert(schema)
return data
class ConfigManagerEntryIndexView(HomeAssistantView):
"""View to get available config entries."""
url = '/api/config/config_entries/entry'
name = 'api:config:config_entries:entry'
@asyncio.coroutine
def get(self, request):
"""List flows in progress."""
hass = request.app['hass']
return self.json([{
'entry_id': entry.entry_id,
'domain': entry.domain,
'title': entry.title,
'source': entry.source,
'state': entry.state,
} for entry in hass.config_entries.async_entries()])
class ConfigManagerEntryResourceView(HomeAssistantView):
"""View to interact with a config entry."""
url = '/api/config/config_entries/entry/{entry_id}'
name = 'api:config:config_entries:entry:resource'
@asyncio.coroutine
def delete(self, request, entry_id):
"""Delete a config entry."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.async_remove(entry_id)
except config_entries.UnknownEntry:
return self.json_message('Invalid entry specified', 404)
return self.json(result)
class ConfigManagerFlowIndexView(HomeAssistantView):
"""View to create config flows."""
url = '/api/config/config_entries/flow'
name = 'api:config:config_entries:flow'
@asyncio.coroutine
def get(self, request):
"""List flows that are in progress but not started by a user.
Example of a non-user initiated flow is a discovered Hue hub that
requires user interaction to finish setup.
"""
hass = request.app['hass']
return self.json([
flow for flow in hass.config_entries.flow.async_progress()
if flow['source'] != config_entries.SOURCE_USER])
@asyncio.coroutine
@RequestDataValidator(vol.Schema({
vol.Required('domain'): str,
}))
def post(self, request, data):
"""Handle a POST request."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.flow.async_init(
data['domain'])
except config_entries.UnknownHandler:
return self.json_message('Invalid handler specified', 404)
except config_entries.UnknownStep:
return self.json_message('Handler does not support init', 400)
result = _prepare_json(result)
return self.json(result)
class ConfigManagerFlowResourceView(HomeAssistantView):
"""View to interact with the flow manager."""
url = '/api/config/config_entries/flow/{flow_id}'
name = 'api:config:config_entries:flow:resource'
@asyncio.coroutine
def get(self, request, flow_id):
"""Get the current state of a flow."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.flow.async_configure(
flow_id)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
result = _prepare_json(result)
return self.json(result)
@asyncio.coroutine
@RequestDataValidator(vol.Schema(dict), allow_empty=True)
def post(self, request, flow_id, data):
"""Handle a POST request."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.flow.async_configure(
flow_id, data)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
except vol.Invalid:
return self.json_message('User input malformed', 400)
result = _prepare_json(result)
return self.json(result)
@asyncio.coroutine
def delete(self, request, flow_id):
"""Cancel a flow in progress."""
hass = request.app['hass']
try:
hass.config_entries.async_abort(flow_id)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
return self.json_message('Flow aborted')
class ConfigManagerAvailableFlowView(HomeAssistantView):
"""View to query available flows."""
url = '/api/config/config_entries/flow_handlers'
name = 'api:config:config_entries:flow_handlers'
@asyncio.coroutine
def get(self, request):
"""List available flow handlers."""
return self.json(config_entries.FLOWS)

View File

@@ -0,0 +1,102 @@
"""Example component to show how config entries work."""
import asyncio
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.util import slugify
DOMAIN = 'config_entry_example'
@asyncio.coroutine
def async_setup(hass, config):
"""Setup for our example component."""
return True
@asyncio.coroutine
def async_setup_entry(hass, entry):
"""Initialize an entry."""
entity_id = '{}.{}'.format(DOMAIN, entry.data['object_id'])
hass.states.async_set(entity_id, 'loaded', {
ATTR_FRIENDLY_NAME: entry.data['name']
})
# Indicate setup was successful.
return True
@asyncio.coroutine
def async_unload_entry(hass, entry):
"""Unload an entry."""
entity_id = '{}.{}'.format(DOMAIN, entry.data['object_id'])
hass.states.async_remove(entity_id)
# Indicate unload was successful.
return True
@config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(config_entries.ConfigFlowHandler):
"""Handle an example configuration flow."""
VERSION = 1
def __init__(self):
"""Initialize a Hue config handler."""
self.object_id = None
@asyncio.coroutine
def async_step_init(self, user_input=None):
"""Start config flow."""
errors = None
if user_input is not None:
object_id = user_input['object_id']
if object_id != '' and object_id == slugify(object_id):
self.object_id = user_input['object_id']
return (yield from self.async_step_name())
errors = {
'object_id': 'Invalid object id.'
}
return self.async_show_form(
title='Pick object id',
step_id='init',
description="Please enter an object_id for the test entity.",
data_schema=vol.Schema({
'object_id': str
}),
errors=errors
)
@asyncio.coroutine
def async_step_name(self, user_input=None):
"""Ask user to enter the name."""
errors = None
if user_input is not None:
name = user_input['name']
if name != '':
return self.async_create_entry(
title=name,
data={
'name': name,
'object_id': self.object_id,
}
)
return self.async_show_form(
title='Name of the entity',
step_id='name',
description="Please enter a name for the test entity.",
data_schema=vol.Schema({
'name': str
}),
errors=errors
)

View File

@@ -7,19 +7,17 @@ https://home-assistant.io/components/conversation/
import asyncio
import logging
import re
import warnings
import voluptuous as vol
from homeassistant import core
from homeassistant.components import http
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
from homeassistant.components.http.data_validator import (
RequestDataValidator)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import intent
from homeassistant.loader import bind_hass
REQUIREMENTS = ['fuzzywuzzy==0.16.0']
from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__)
@@ -28,9 +26,6 @@ ATTR_TEXT = 'text'
DEPENDENCIES = ['http']
DOMAIN = 'conversation'
INTENT_TURN_OFF = 'HassTurnOff'
INTENT_TURN_ON = 'HassTurnOn'
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REGEX_TYPE = type(re.compile(''))
@@ -50,7 +45,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
@core.callback
@bind_hass
def async_register(hass, intent_type, utterances):
"""Register an intent.
"""Register utterances and any custom intents.
Registrations don't require conversations to be loaded. They will become
active once the conversation component is loaded.
@@ -75,8 +70,6 @@ def async_register(hass, intent_type, utterances):
@asyncio.coroutine
def async_setup(hass, config):
"""Register the process service."""
warnings.filterwarnings('ignore', module='fuzzywuzzy')
config = config.get(DOMAIN, {})
intents = hass.data.get(DOMAIN)
@@ -102,12 +95,12 @@ def async_setup(hass, config):
hass.http.register_view(ConversationProcessView)
hass.helpers.intent.async_register(TurnOnIntent())
hass.helpers.intent.async_register(TurnOffIntent())
async_register(hass, INTENT_TURN_ON,
async_register(hass, intent.INTENT_TURN_ON,
['Turn {name} on', 'Turn on {name}'])
async_register(hass, INTENT_TURN_OFF, [
'Turn {name} off', 'Turn off {name}'])
async_register(hass, intent.INTENT_TURN_OFF,
['Turn {name} off', 'Turn off {name}'])
async_register(hass, intent.INTENT_TOGGLE,
['Toggle {name}', '{name} toggle'])
return True
@@ -151,86 +144,13 @@ def _process(hass, text):
return response
@core.callback
def _match_entity(hass, name):
"""Match a name to an entity."""
from fuzzywuzzy import process as fuzzyExtract
entities = {state.entity_id: state.name for state
in hass.states.async_all()}
entity_id = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
return hass.states.get(entity_id) if entity_id else None
class TurnOnIntent(intent.IntentHandler):
"""Handle turning item on intents."""
intent_type = INTENT_TURN_ON
slot_schema = {
'name': cv.string,
}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle turn on intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name = slots['name']['value']
entity = _match_entity(hass, name)
if not entity:
_LOGGER.error("Could not find entity id for %s", name)
return None
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
}, blocking=True)
response = intent_obj.create_response()
response.async_set_speech(
'Turned on {}'.format(entity.name))
return response
class TurnOffIntent(intent.IntentHandler):
"""Handle turning item off intents."""
intent_type = INTENT_TURN_OFF
slot_schema = {
'name': cv.string,
}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle turn off intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name = slots['name']['value']
entity = _match_entity(hass, name)
if not entity:
_LOGGER.error("Could not find entity id for %s", name)
return None
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id,
}, blocking=True)
response = intent_obj.create_response()
response.async_set_speech(
'Turned off {}'.format(entity.name))
return response
class ConversationProcessView(http.HomeAssistantView):
"""View to retrieve shopping list content."""
url = '/api/conversation/process'
name = "api:conversation:process"
@http.RequestDataValidator(vol.Schema({
@RequestDataValidator(vol.Schema({
vol.Required('text'): str,
}))
@asyncio.coroutine

View File

@@ -5,7 +5,8 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.cover import (
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE)
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION,
ATTR_TILT_POSITION)
from homeassistant.helpers.event import track_utc_time_change
@@ -137,8 +138,9 @@ class DemoCover(CoverDevice):
self._listen_cover_tilt()
self._requested_closing_tilt = False
def set_cover_position(self, position, **kwargs):
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
self._set_position = round(position, -1)
if self._position == position:
return
@@ -146,8 +148,9 @@ class DemoCover(CoverDevice):
self._listen_cover()
self._requested_closing = position < self._position
def set_cover_tilt_position(self, tilt_position, **kwargs):
def set_cover_tilt_position(self, **kwargs):
"""Move the cover til to a specific position."""
tilt_position = kwargs.get(ATTR_TILT_POSITION)
self._set_tilt_position = round(tilt_position, -1)
if self._tilt_position == tilt_position:
return

View File

@@ -201,21 +201,21 @@ class GaradgetCover(CoverDevice):
"""Check the state of the service during an operation."""
self.schedule_update_ha_state(True)
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the cover."""
if self._state not in ['close', 'closing']:
ret = self._put_command('setState', 'close')
self._start_watcher('close')
return ret.get('return_value') == 1
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the cover."""
if self._state not in ['open', 'opening']:
ret = self._put_command('setState', 'open')
self._start_watcher('open')
return ret.get('return_value') == 1
def stop_cover(self):
def stop_cover(self, **kwargs):
"""Stop the door where it is."""
if self._state not in ['stopped']:
ret = self._put_command('setState', 'stop')

View File

@@ -42,10 +42,6 @@ def setup_platform(hass, config: ConfigType,
class ISYCoverDevice(ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device."""
def __init__(self, node: object) -> None:
"""Initialize the ISY994 cover device."""
super().__init__(node)
@property
def current_cover_position(self) -> int:
"""Return the current cover position."""
@@ -61,8 +57,7 @@ class ISYCoverDevice(ISYDevice, CoverDevice):
"""Get the state of the ISY994 cover device."""
if self.is_unknown():
return None
else:
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device."""

View File

@@ -53,9 +53,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up cover(s) for KNX platform."""
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
else:

View File

@@ -65,9 +65,9 @@ TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_COMMAND_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_POSITION_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_SET_POSITION_TEMPLATE, default=None): cv.template,
vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_POSITION_TOPIC): valid_publish_topic,
vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
@@ -78,8 +78,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_COMMAND_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_TILT_STATUS_TOPIC, default=None): valid_subscribe_topic,
vol.Optional(CONF_TILT_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_TILT_STATUS_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_TILT_CLOSED_POSITION,
default=DEFAULT_TILT_CLOSED_POSITION): int,
vol.Optional(CONF_TILT_OPEN_POSITION,

View File

@@ -84,11 +84,11 @@ class MyQDevice(CoverDevice):
"""Return true if cover is closed, else False."""
return self._status == STATE_CLOSED
def close_cover(self):
def close_cover(self, **kwargs):
"""Issue close command to cover."""
self.myq.close_device(self.device_id)
def open_cover(self):
def open_cover(self, **kwargs):
"""Issue open command to cover."""
self.myq.open_device(self.device_id)

View File

@@ -115,18 +115,18 @@ class OpenGarageCover(CoverDevice):
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._state == STATE_UNKNOWN:
if self._state in [STATE_UNKNOWN, STATE_OFFLINE]:
return None
return self._state in [STATE_CLOSED, STATE_OPENING]
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the cover."""
if self._state not in [STATE_CLOSED, STATE_CLOSING]:
self._state_before_move = self._state
self._state = STATE_CLOSING
self._push_button()
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the cover."""
if self._state not in [STATE_OPEN, STATE_OPENING]:
self._state_before_move = self._state

View File

@@ -109,12 +109,12 @@ class RPiGPIOCover(CoverDevice):
sleep(self._relay_time)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the cover."""
if not self.is_closed:
self._trigger()
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the cover."""
if self.is_closed:
self._trigger()

View File

@@ -1,63 +1,63 @@
# 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.
example: 'cover.living_room'
close_cover:
description: Close all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to close.
example: 'cover.living_room'
set_cover_position:
description: Move to specific position all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to set cover position.
example: 'cover.living_room'
position:
description: Position of the cover (0 to 100).
example: 30
stop_cover:
description: Stop all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to stop.
example: 'cover.living_room'
open_cover_tilt:
description: Open all or specified cover tilt.
fields:
entity_id:
description: Name(s) of cover(s) tilt to open.
example: 'cover.living_room'
close_cover_tilt:
description: Close all or specified cover tilt.
fields:
entity_id:
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.
fields:
entity_id:
description: Name(s) of cover(s) to set cover tilt position.
example: 'cover.living_room'
tilt_position:
description: Tilt position of the cover (0 to 100).
example: 30
stop_cover_tilt:
description: Stop all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to stop.
example: 'cover.living_room'
# 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.
example: 'cover.living_room'
close_cover:
description: Close all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to close.
example: 'cover.living_room'
set_cover_position:
description: Move to specific position all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to set cover position.
example: 'cover.living_room'
position:
description: Position of the cover (0 to 100).
example: 30
stop_cover:
description: Stop all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to stop.
example: 'cover.living_room'
open_cover_tilt:
description: Open all or specified cover tilt.
fields:
entity_id:
description: Name(s) of cover(s) tilt to open.
example: 'cover.living_room'
close_cover_tilt:
description: Close all or specified cover tilt.
fields:
entity_id:
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.
fields:
entity_id:
description: Name(s) of cover(s) to set cover tilt position.
example: 'cover.living_room'
tilt_position:
description: Tilt position of the cover (0 to 100).
example: 30
stop_cover_tilt:
description: Stop all or specified cover.
fields:
entity_id:
description: Name(s) of cover(s) to stop.
example: 'cover.living_room'

View File

@@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.tahoma/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.tahoma import (
DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
@@ -49,9 +49,9 @@ class TahomaCover(TahomaDevice, CoverDevice):
except KeyError:
return None
def set_cover_position(self, position, **kwargs):
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
self.apply_action('setPosition', 100 - position)
self.apply_action('setPosition', 100 - kwargs.get(ATTR_POSITION))
@property
def is_closed(self):
@@ -64,8 +64,7 @@ class TahomaCover(TahomaDevice, CoverDevice):
"""Return the class of the device."""
if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent':
return 'window'
else:
return None
return None
def open_cover(self, **kwargs):
"""Open the cover."""

View File

@@ -6,7 +6,8 @@ https://home-assistant.io/components/cover.vera/
"""
import logging
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT, \
ATTR_POSITION
from homeassistant.components.vera import (
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
@@ -44,9 +45,9 @@ class VeraCover(VeraDevice, CoverDevice):
return 100
return position
def set_cover_position(self, position, **kwargs):
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
self.vera_device.set_level(position)
self.vera_device.set_level(kwargs.get(ATTR_POSITION))
self.schedule_update_ha_state()
@property

View File

@@ -6,7 +6,8 @@ https://home-assistant.io/components/cover.wink/
"""
import asyncio
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, \
ATTR_POSITION
from homeassistant.components.wink import WinkDevice, DOMAIN
DEPENDENCIES = ['wink']
@@ -42,17 +43,17 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
"""Open the cover."""
self.wink.set_state(1)
def set_cover_position(self, position, **kwargs):
def set_cover_position(self, **kwargs):
"""Move the cover shutter to a specific position."""
self.wink.set_state(float(position)/100)
position = kwargs.get(ATTR_POSITION)
self.wink.set_state(position/100)
@property
def current_cover_position(self):
"""Return the current position of cover shutter."""
if self.wink.state() is not None:
return int(self.wink.state()*100)
else:
return STATE_UNKNOWN
return STATE_UNKNOWN
@property
def is_closed(self):

View File

@@ -1,7 +1,7 @@
"""Support for Xiaomi curtain."""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY,
XiaomiDevice)
@@ -55,8 +55,9 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice):
"""Stop the cover."""
self._write_to_hub(self._sid, **{self._data_key['status']: 'stop'})
def set_cover_position(self, position, **kwargs):
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)})
def parse_data(self, data, raw_data):

View File

@@ -8,7 +8,7 @@ https://home-assistant.io/components/cover.zwave/
# pylint: disable=import-error
import logging
from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE)
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
@@ -97,9 +97,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Move the roller shutter down."""
self._network.manager.pressButton(self._close_id)
def set_cover_position(self, position, **kwargs):
def set_cover_position(self, **kwargs):
"""Move the roller shutter to a specific position."""
self.node.set_dimmer(self.values.primary.value_id, position)
self.node.set_dimmer(self.values.primary.value_id,
kwargs.get(ATTR_POSITION))
def stop_cover(self, **kwargs):
"""Stop the roller shutter."""
@@ -139,11 +140,11 @@ class ZwaveGarageDoorSwitch(ZwaveGarageDoorBase):
"""Return the current position of Zwave garage door."""
return not self._state
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the garage door."""
self.values.primary.data = False
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the garage door."""
self.values.primary.data = True
@@ -166,10 +167,10 @@ class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase):
"""Return the current position of Zwave garage door."""
return self._state == "Closed"
def close_cover(self):
def close_cover(self, **kwargs):
"""Close the garage door."""
self.values.primary.data = "Closed"
def open_cover(self):
def open_cover(self, **kwargs):
"""Open the garage door."""
self.values.primary.data = "Opened"

View File

@@ -4,6 +4,7 @@ Support for deCONZ devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/deconz/
"""
import asyncio
import logging
@@ -17,11 +18,12 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['pydeconz==27']
REQUIREMENTS = ['pydeconz==30']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'deconz'
DATA_DECONZ_ID = 'deconz_entities'
CONFIG_FILE = 'deconz.conf'
@@ -34,13 +36,16 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
SERVICE_FIELD = 'field'
SERVICE_ENTITY = 'entity'
SERVICE_DATA = 'data'
SERVICE_SCHEMA = vol.Schema({
vol.Required(SERVICE_FIELD): cv.string,
vol.Exclusive(SERVICE_FIELD, 'deconz_id'): cv.string,
vol.Exclusive(SERVICE_ENTITY, 'deconz_id'): cv.entity_id,
vol.Required(SERVICE_DATA): dict,
})
CONFIG_INSTRUCTIONS = """
Unlock your deCONZ gateway to register with Home Assistant.
@@ -100,6 +105,7 @@ def async_setup_deconz(hass, config, deconz_config):
return False
hass.data[DOMAIN] = deconz
hass.data[DATA_DECONZ_ID] = {}
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
hass.async_add_job(discovery.async_load_platform(
@@ -112,6 +118,7 @@ def async_setup_deconz(hass, config, deconz_config):
Field is a string representing a specific device in deCONZ
e.g. field='/lights/1/state'.
Entity_id can be used to retrieve the proper field.
Data is a json object with what data you want to alter
e.g. data={'on': true}.
{
@@ -121,9 +128,17 @@ def async_setup_deconz(hass, config, deconz_config):
See Dresden Elektroniks REST API documentation for details:
http://dresden-elektronik.github.io/deconz-rest-doc/rest/
"""
deconz = hass.data[DOMAIN]
field = call.data.get(SERVICE_FIELD)
entity_id = call.data.get(SERVICE_ENTITY)
data = call.data.get(SERVICE_DATA)
deconz = hass.data[DOMAIN]
if entity_id:
entities = hass.data.get(DATA_DECONZ_ID)
if entities:
field = entities.get(entity_id)
if field is None:
_LOGGER.error('Could not find the entity %s', entity_id)
return
yield from deconz.async_put_state(field, data)
hass.services.async_register(
DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA)

View File

@@ -1,10 +1,13 @@
configure:
description: Set attribute of device in Deconz. See Dresden Elektroniks REST API documentation for details http://dresden-elektronik.github.io/deconz-rest-doc/rest/
description: Set attribute of device in deCONZ. See https://home-assistant.io/components/deconz/#device-services for details.
fields:
field:
description: Field is a string representing a specific device in Deconz.
description: Field is a string representing a specific device in deCONZ.
example: '/lights/1/state'
entity:
description: Entity id representing a specific device in deCONZ.
example: 'light.rgb_light'
data:
description: Data is a json object with what data you want to alter.
example: '{"on": true}'

View File

@@ -99,17 +99,17 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
@bind_hass
def is_on(hass: HomeAssistantType, entity_id: str=None):
def is_on(hass: HomeAssistantType, entity_id: str = None):
"""Return the state if any or a specified device is home."""
entity = entity_id or ENTITY_ID_ALL_DEVICES
return hass.states.is_state(entity, STATE_HOME)
def see(hass: HomeAssistantType, mac: str=None, dev_id: str=None,
host_name: str=None, location_name: str=None,
gps: GPSType=None, gps_accuracy=None,
battery=None, attributes: dict=None):
def see(hass: HomeAssistantType, mac: str = None, dev_id: str = None,
host_name: str = None, location_name: str = None,
gps: GPSType = None, gps_accuracy=None,
battery=None, attributes: dict = None):
"""Call service to notify you see device."""
data = {key: value for key, value in
((ATTR_MAC, mac),
@@ -239,11 +239,11 @@ class DeviceTracker(object):
_LOGGER.warning('Duplicate device MAC addresses detected %s',
dev.mac)
def see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None, gps_accuracy=None,
battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS, picture: str=None,
icon: str=None):
def see(self, mac: str = None, dev_id: str = None, host_name: str = None,
location_name: str = None, gps: GPSType = None, gps_accuracy=None,
battery: str = None, attributes: dict = None,
source_type: str = SOURCE_TYPE_GPS, picture: str = None,
icon: str = None):
"""Notify the device tracker that you see a device."""
self.hass.add_job(
self.async_see(mac, dev_id, host_name, location_name, gps,
@@ -252,11 +252,11 @@ class DeviceTracker(object):
)
@asyncio.coroutine
def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None,
location_name: str=None, gps: GPSType=None,
gps_accuracy=None, battery: str=None, attributes: dict=None,
source_type: str=SOURCE_TYPE_GPS, picture: str=None,
icon: str=None):
def async_see(self, mac: str = None, dev_id: str = None,
host_name: str = None, location_name: str = None,
gps: GPSType = None, gps_accuracy=None, battery: str = None,
attributes: dict = None, source_type: str = SOURCE_TYPE_GPS,
picture: str = None, icon: str = None):
"""Notify the device tracker that you see a device.
This method is a coroutine.
@@ -396,9 +396,9 @@ class Device(Entity):
_state = STATE_NOT_HOME
def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str=None,
picture: str=None, gravatar: str=None, icon: str=None,
hide_if_away: bool=False, vendor: str=None) -> None:
track: bool, dev_id: str, mac: str, name: str = None,
picture: str = None, gravatar: str = None, icon: str = None,
hide_if_away: bool = False, vendor: str = None) -> None:
"""Initialize a device."""
self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@@ -475,9 +475,10 @@ class Device(Entity):
return self.away_hide and self.state != STATE_HOME
@asyncio.coroutine
def async_seen(self, host_name: str=None, location_name: str=None,
gps: GPSType=None, gps_accuracy=0, battery: str=None,
attributes: dict=None, source_type: str=SOURCE_TYPE_GPS):
def async_seen(self, host_name: str = None, location_name: str = None,
gps: GPSType = None, gps_accuracy=0, battery: str = None,
attributes: dict = None,
source_type: str = SOURCE_TYPE_GPS):
"""Mark the device as seen."""
self.source_type = source_type
self.last_seen = dt_util.utcnow()
@@ -504,7 +505,7 @@ class Device(Entity):
# pylint: disable=not-an-iterable
yield from self.async_update()
def stale(self, now: dt_util.dt.datetime=None):
def stale(self, now: dt_util.dt.datetime = None):
"""Return if device state is stale.
Async friendly.
@@ -621,16 +622,16 @@ class DeviceScanner(object):
"""
return self.hass.async_add_job(self.scan_devices)
def get_device_name(self, mac: str) -> str:
"""Get device name from mac."""
def get_device_name(self, device: str) -> str:
"""Get the name of a device."""
raise NotImplementedError()
def async_get_device_name(self, mac: str) -> Any:
"""Get device name from mac.
def async_get_device_name(self, device: str) -> Any:
"""Get the name of a device.
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.get_device_name, mac)
return self.hass.async_add_job(self.get_device_name, device)
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
@@ -648,8 +649,7 @@ def async_load_config(path: str, hass: HomeAssistantType,
"""
dev_schema = vol.Schema({
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_ICON, default=False):
vol.Any(None, cv.icon),
vol.Optional(CONF_ICON, default=None): vol.Any(None, cv.icon),
vol.Optional('track', default=False): cv.boolean,
vol.Optional(CONF_MAC, default=None):
vol.Any(None, vol.All(cv.string, vol.Upper)),

View File

@@ -63,6 +63,7 @@ _IP_NEIGH_REGEX = re.compile(
r'\w+\s'
r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s'
r'\s?(router)?'
r'\s?(nud)?'
r'(?P<status>(\w+))')
_ARP_CMD = 'arp -n'
@@ -118,11 +119,10 @@ class AsusWrtDeviceScanner(DeviceScanner):
if self.protocol == 'ssh':
self.connection = SshConnection(
self.host, self.port, self.username, self.password,
self.ssh_key, self.mode == 'ap')
self.ssh_key)
else:
self.connection = TelnetConnection(
self.host, self.port, self.username, self.password,
self.mode == 'ap')
self.host, self.port, self.username, self.password)
self.last_results = {}
@@ -212,6 +212,9 @@ class AsusWrtDeviceScanner(DeviceScanner):
result = _parse_lines(lines, _IP_NEIGH_REGEX)
devices = {}
for device in result:
status = device['status']
if status is None or status.upper() != 'REACHABLE':
continue
if device['mac'] is not None:
mac = device['mac'].upper()
old_device = cur_devices.get(mac)
@@ -226,7 +229,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
result = _parse_lines(lines, _ARP_REGEX)
devices = {}
for device in result:
if device['mac']:
if device['mac'] is not None:
mac = device['mac'].upper()
devices[mac] = Device(mac, device['ip'], None)
return devices
@@ -253,7 +256,7 @@ class _Connection:
class SshConnection(_Connection):
"""Maintains an SSH connection to an ASUS-WRT router."""
def __init__(self, host, port, username, password, ssh_key, ap):
def __init__(self, host, port, username, password, ssh_key):
"""Initialize the SSH connection properties."""
super().__init__()
@@ -263,7 +266,6 @@ class SshConnection(_Connection):
self._username = username
self._password = password
self._ssh_key = ssh_key
self._ap = ap
def run_command(self, command):
"""Run commands through an SSH connection.
@@ -323,7 +325,7 @@ class SshConnection(_Connection):
class TelnetConnection(_Connection):
"""Maintains a Telnet connection to an ASUS-WRT router."""
def __init__(self, host, port, username, password, ap):
def __init__(self, host, port, username, password):
"""Initialize the Telnet connection properties."""
super().__init__()
@@ -332,7 +334,6 @@ class TelnetConnection(_Connection):
self._port = port
self._username = username
self._password = password
self._ap = ap
self._prompt_string = None
def run_command(self, command):

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.4']
REQUIREMENTS = ['aioautomatic==0.6.5']
_LOGGER = logging.getLogger(__name__)
@@ -49,8 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string,
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
vol.Optional(CONF_DEVICES, default=None):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]),
})
@@ -109,7 +108,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
_write_refresh_token_to_file, hass, filename,
session.refresh_token)
data = AutomaticData(
hass, client, session, config[CONF_DEVICES], async_see)
hass, client, session, config.get(CONF_DEVICES), async_see)
# Load the initial vehicle data
vehicles = yield from session.get_vehicles()
@@ -177,10 +176,9 @@ class AutomaticAuthCallbackView(HomeAssistantView):
_LOGGER.error(
"Error authorizing Automatic: %s", params['error'])
return response
else:
_LOGGER.error(
"Error authorizing Automatic. Invalid response returned")
return response
_LOGGER.error(
"Error authorizing Automatic. Invalid response returned")
return response
if DATA_CONFIGURING not in hass.data or \
params['state'] not in hass.data[DATA_CONFIGURING]:

View File

@@ -45,10 +45,10 @@ class BboxDeviceScanner(DeviceScanner):
return [device.mac for device in self.last_results]
def get_device_name(self, mac):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results if
device.mac == mac]
filter_named = [result.name for result in self.last_results if
result.mac == device]
if filter_named:
return filter_named[0]

View File

@@ -102,7 +102,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Lookup Bluetooth LE devices and update status."""
devs = discover_ble_devices()
for mac in devs_to_track:
_LOGGER.debug("Checking " + mac)
_LOGGER.debug("Checking %s", mac)
result = mac in devs
if not result:
# Could not lookup device name

View File

@@ -41,7 +41,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
result = bluetooth.discover_devices(
duration=8, lookup_names=True, flush_cache=True,
lookup_class=False)
_LOGGER.debug("Bluetooth devices discovered = " + str(len(result)))
_LOGGER.debug("Bluetooth devices discovered = %d", len(result))
return result
yaml_path = hass.config.path(YAML_DEVICES)

View File

@@ -0,0 +1,51 @@
"""Device tracker for BMW Connected Drive vehicles.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.bmw_connected_drive/
"""
import logging
from homeassistant.components.bmw_connected_drive import DOMAIN \
as BMW_DOMAIN
from homeassistant.util import slugify
DEPENDENCIES = ['bmw_connected_drive']
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the BMW tracker."""
accounts = hass.data[BMW_DOMAIN]
_LOGGER.debug('Found BMW accounts: %s',
', '.join([a.name for a in accounts]))
for account in accounts:
for vehicle in account.account.vehicles:
tracker = BMWDeviceTracker(see, vehicle)
account.add_update_listener(tracker.update)
tracker.update()
return True
class BMWDeviceTracker(object):
"""BMW Connected Drive device tracker."""
def __init__(self, see, vehicle):
"""Initialize the Tracker."""
self._see = see
self.vehicle = vehicle
def update(self) -> None:
"""Update the device info."""
dev_id = slugify(self.vehicle.modelName)
_LOGGER.debug('Updating %s', dev_id)
attrs = {
'trackr_id': dev_id,
'id': dev_id,
'name': self.vehicle.modelName
}
self._see(
dev_id=dev_id, host_name=self.vehicle.modelName,
gps=self.vehicle.state.gps_position, attributes=attrs,
icon='mdi:car'
)

View File

@@ -75,9 +75,9 @@ class FritzBoxScanner(DeviceScanner):
active_hosts.append(known_host['mac'])
return active_hosts
def get_device_name(self, mac):
def get_device_name(self, device):
"""Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac).get(
ret = self.fritz_box.get_specific_host_entry(device).get(
'NewHostName'
)
if ret == {}:

View File

@@ -120,8 +120,7 @@ class GeofencyView(HomeAssistantView):
"""Return name of device tracker."""
if 'beaconUUID' in data:
return "{}_{}".format(BEACON_DEV_PREFIX, data['name'])
else:
return data['device']
return data['device']
@asyncio.coroutine
def _set_location(self, hass, data, location_name):

View File

@@ -60,11 +60,11 @@ class HitronCODADeviceScanner(DeviceScanner):
return [device.mac for device in self.last_results]
def get_device_name(self, mac):
def get_device_name(self, device):
"""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)
result.name for result in self.last_results
if result.mac == device), None)
return name
def _login(self):

View File

@@ -86,6 +86,7 @@ class HuaweiDeviceScanner(DeviceScanner):
active_clients = [client for client in data if client.state]
self.last_results = active_clients
# pylint: disable=logging-not-lazy
_LOGGER.debug("Active clients: " + "\n"
.join((client.mac + " " + client.name)
for client in active_clients))

View File

@@ -67,10 +67,10 @@ class KeeneticNDMS2DeviceScanner(DeviceScanner):
return [device.mac for device in self.last_results]
def get_device_name(self, mac):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results
if device.mac == mac]
filter_named = [result.name for result in self.last_results
if result.mac == device]
if filter_named:
return filter_named[0]

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