Compare commits

...

437 Commits
0.63 ... 0.65.0

Author SHA1 Message Date
Paulus Schoutsen
ca973b68e0 Merge pull request #12995 from home-assistant/release-0-65
0.65
2018-03-09 09:47:11 -08:00
Ryan McLean
d19a8ec7da Updated to plexapi 3.0.6 (#13005) 2018-03-09 09:31:15 -08:00
Otto Winter
4152ac4aa2 Clean up Light Groups (#12962)
* Clean up Light Groups

* Fix tests

* Remove light group from .coveragerc

* async_schedule_update_ha_state called anyway
2018-03-09 09:31:14 -08:00
mueslo
efdc7042df Add consider_home and source_type to device_tracker.see service (#12849)
* Add consider_home and source_type to device_tracker.see service

* Use schema instead of manual validation

* Extend schema to validate all keys

* Fix style

* Set battery level to int
2018-03-09 09:31:14 -08:00
Ryan McLean
ebf4be3711 Plex mark devices unavailable if they 'vanish' and clear media (#12811)
* Marks Devices unavailable if they 'vanish' and clears media

* Fixed PEP8 complaint

* Fixed Linting

* Lint Fix

* Fix redine of id

* More lint fixes

* Removed redundant loop for setting availability of client
Renamed '_is_device_available' to '_available'
Renamed 'available_ids' to 'available_client_ids'

* removed whitespace per houndCI
2018-03-09 09:31:13 -08:00
Johann Kellerman
16d72d2351 check_config script evolution (#12792)
* Initial async_check_ha_config_file

* check_ha_config_file

* Various fixes

* feedback - return the config

* move_to_check_config
2018-03-09 09:31:13 -08:00
PhracturedBlue
b2210f429e Add camera proxy (#12006)
* Add camera proxy

* Fix additional tox linting issues

* Trivial cleanup

* update to new async/await methods rather than decorators.  Other minor fixes from code review
2018-03-09 09:31:12 -08:00
Steve Easley
7410bc90f0 Get zha switch and binary_sensor state on startup (#11672)
* Get zha switch and binary_sensor state on startup

* Removed unused var

* Make zha switch report status

* Use right method name

* Formatting fix

* Updates to match latest dev

* PR feedback updates

* Use async for cluster commands
2018-03-09 09:31:12 -08:00
Paulus Schoutsen
7e15f179c6 Bump release 0.65 2018-03-08 17:52:41 -08:00
Boyi C
321eb2ec6f Move HomeAssistantView to separate file. Convert http to async syntax. [skip ci] (#12982)
* Move HomeAssistantView to separate file. Convert http to async syntax.

* pylint

* websocket api

* update emulated_hue for async/await

* Lint
2018-03-08 17:51:49 -08:00
corneyl
2ee73ca911 Fixes notify.html5 for notifications on FireFox (#12993)
* Only pass the gcm_key when using Google Cloud Messaging as endpoint.

* Test if the gcm_key is only included for GCM endpoints.
2018-03-08 17:50:17 -08:00
Anders Melchiorsen
19a529e917 Fix limitlessled color temperature (#12971)
* Adjust limitlessled mired range

* Do temperature conversion on a Kelvin scale
2018-03-08 17:47:53 -08:00
Paulus Schoutsen
7f065e38a7 Check color temp range for google assistant (#12994) 2018-03-08 17:43:41 -08:00
Paulus Schoutsen
c4a4802a8c Bump frontend to 20180309.0 2018-03-08 16:43:23 -08:00
Anders Melchiorsen
44e4f8d1ba Fix Sonos group discovery (#12970)
* Avoid iterating sonos devices that are not yet added

* Rebuild zone topology for each new device
2018-03-08 15:39:31 -08:00
cdce8p
eaf525d41d Script/gen_requirements: Ignore package families (#12963)
* Added fnmatch for IGNORE_PACKAGE_FAMILIES
2018-03-09 00:28:49 +01:00
Jacob Mansfield
6d7dbe5536 Show the error message when Zabbix fails to log in (#12985)
* Show the error message when Zabbix fails to log in

* More verbose name for exception variable
2018-03-08 15:25:10 -08:00
koolsb
ab397e2b1a Update pyalarmdotcom version (#12987)
* Update pyalarmdotcom version

* Update pyalarmdotcom version
2018-03-08 15:24:28 -08:00
John Mihalic
6be81feb2d Bump pyEmby version to support aiohttp => 3 (#12986) 2018-03-08 15:23:51 -08:00
Paulus Schoutsen
9b1a75a74b Refactor Google Assistant (#12959)
* Refactor Google Assistant

* Fix cloud test

* Fix supported features media player demo

* Fix query

* Fix execute

* Fix demo media player tests

* Add tests for traits

* Lint

* Lint

* Add integration tests

* Add more tests

* update logging

* Catch out of range temp errrors

* Fix cloud error

* Lint
2018-03-08 14:39:10 -08:00
Anders Melchiorsen
8792fd22b9 IMAP sensor async/await conversion (#12988) 2018-03-08 21:30:50 +01:00
Anders Melchiorsen
5dd0193ba6 LIFX async/await conversion (#12973)
* LIFX async/await conversion

* async with
2018-03-08 07:24:35 +01:00
Paulus Schoutsen
b159e8acee Hue: Don't change brightness when changing just color (#12940) 2018-03-07 09:51:36 -08:00
Adam Mills
a99c8eb6c6 Pin lokalise script to working version (#12965) 2018-03-07 09:46:21 -05:00
maxclaey
4218b31e7b Add support for alarm system, switch and thermostat to homekit (#12819)
* Added support for security system, switch and thermostat
* Processing review
* Only perform set call when the call didn't come from HomeKit
* Added support for alarm_code
* Take into account review remarks
* Provide tests for HomeKit security systems, switches and thermostats
* Support STATE_AUTO
* Guard if state exists
* Improve support for thermostat auto mode
* Provide both high and low at the same time for home assistant
* Set default values within accepted ranges
* Added tests for auto mode
* Fix thermostat test error
* Use attributes.get instead of indexing for safety
* Avoid hardcoded attributes in tests
2018-03-07 13:17:52 +01:00
ruohan.chen
35bae1eef2 Telegram_bot three platform support proxy_url and proxy_params (#12878)
* telegram_bot three platform support proxy_url and proxy_params

* add telegram_bot._initialize_bot to create the bot instance

* rename _initialize_bot to initialize_bot
2018-03-07 12:44:07 +01:00
Jon Maddox
d119610cf1 Add a Media Player Component for Channels (#12937)
* add Channels media player

* add Channels' services

* style 💄

* more 💄

* make up your mind robot

* 💄 💄 💄

* dump client and pull it in via a package

* ChannelsApp -> ChannelsPlayer

* load the lib

* add pychannels in requirements

* not using requests anymore

* extra line 💄

* move this here

* move this up

* 🔥

* use constants for these

* add a platform schema

* force update here

* get defaults to None

* break out after finding it

* use None for state if offline or errored

* pull in CONF_NAME

* fix syntax

* update requirements_all.txt

* :lipstick:💄💄

* 💄

* docs

* like this? ¯\(°_o)/¯
2018-03-07 00:33:13 -08:00
John Arild Berentsen
4aed41cbe8 BugFix Popp strike lock not discovered in homeassistant. (#12951)
* Add Secure lockbox to lock discovery

* Add Entry_control devices to binary sensors
2018-03-07 00:29:24 -08:00
Anders Melchiorsen
c462292e4d Fix LIFX color conversions (#12957) 2018-03-07 05:55:04 +01:00
Diogo Gomes
b04e7bba9f [SQL Sensor] partial revert of #12452 (#12956)
* partial revert of #12452

* return missing
2018-03-06 16:34:24 -08:00
Christoph Gerneth
e6364b4ff6 optional displaying the sensors location on the map (#12375)
* Changed sensor attributes for GPS

the sensor is now using the correct attributes (latitued and
longitued) which enables them to show up on the map.

* added option to display the sensor on the map

the configuration will be extended by the optional 'show_on_map' flag.
Default is not display the sensor on the map.

* making the change non-breaking: old default behaviour

* removed doubled attributes for lat and lon
2018-03-06 22:53:51 +01:00
Paulus Schoutsen
36b9c0a946 Remove weird tests (#12936)
* Remove mediaroom test
* Fix meraki test doing mac lookups
* Fix flaky unknown device config
* Move more device tracker I/O testing into memory
2018-03-06 21:53:02 +02:00
Michael Pusterhofer
9086119082 Add add_devices back to rpi_camera (#12947)
* Add add_devices back to rpi_camera
2018-03-06 21:44:37 +02:00
Ryan Mounce
dc94079d74 Make ubus dhcp name resolution optional (#12658)
For the case that a separate DHCP server is used or the device is a dumb
WiFi access point, allow device name resolution to be disabled.
2018-03-06 19:16:18 +01:00
karlkar
78c27b99bd Added support for multiple onvif profiles (#11651)
* Added support for multiple profiles

* Removed attributes, setup made synchronous, as it performs I/O
2018-03-05 19:56:15 -08:00
Aaron Bach
f054e9ee54 Updated to enforce quoted ZIP codes for Pollen (#12934) 2018-03-05 19:47:45 -08:00
Anders Melchiorsen
205e83a6d5 Fix netatmo sensor warning from invalid Voluptuous default (#12933) 2018-03-05 19:45:40 -08:00
Diogo Gomes
5063464d5e Support for queries with no results (fix for #12856) (#12888)
* Addresses issue #12856

* error -> warning

* added edge case and test

* uff uff

* Added SELECT validation

* Improved tests
2018-03-05 19:44:04 -08:00
Paulus Schoutsen
38af04c6ce Reinstate our old virtual env check in favor of pip (#12932) 2018-03-05 15:51:37 -08:00
Nicko van Someren
03225cf20f Added checks for empty replies from REST calls and supporting tests (#12904) 2018-03-05 15:30:28 -08:00
Paulus Schoutsen
3682080da2 Bump frontend to 20180305.0 2018-03-05 15:22:44 -08:00
Julius Mittenzwei
13cb9cb07b Bumped (minor) version of xknx within knx-component. This fixes a bug with inverted percentage within sensors. (#12929) 2018-03-05 15:10:45 -08:00
maxclaey
05204a982e Set supported features based on capabilities of device (#12922)
Catch nest APIErrors when setting temperature
2018-03-05 14:57:52 -08:00
Aaron Bach
18b288dcfe Addresses issues with Pollen.com API troubles (#12930)
* Addresses issues with Pollen.com API troubles (#12916)

* Reverted some unnecessary style changes
2018-03-05 14:15:54 -08:00
Paulus Schoutsen
60d7e32f81 Flaky tests (#12931)
* Skip flaky DDWRT tests

* Import APNS before running tests
2018-03-05 14:13:18 -08:00
Paulus Schoutsen
6a5c7ef43f Upgrade to aiohttp 3 (#12921)
* Upgrade aiohttp to 3.0.6

* Fix tests

* Fix aiohttp client stream test

* Lint

* Remove drain
2018-03-05 13:28:41 -08:00
Paulus Schoutsen
e5c4bba906 Remove unused cloud APIs (#12913) 2018-03-05 22:28:15 +01:00
Paulus Schoutsen
e07fb24987 Update python-coinbase to 2.1.0 (#12925) 2018-03-05 22:26:37 +01:00
Sebastian Muszynski
e7b84432f9 Xiaomi MiIO Switch: Allow unavailable devices at startup by model setting (#12626)
* Unavailable state introduced if the device isn't reachable.

* Redundancy removed.

* Pylint fixed.

* Missing space added.

* Pylint fixed.

* Use format instead of concatenation.
2018-03-05 08:25:12 +01:00
Sebastian Muszynski
b0e062b2f8 Xiaomi MiIO Remote: Lazy discover disabled (#12710)
* Lazy discovery disabled: The Chuang Mi IR Remote Controller wants to be re-discovered every 5 minutes. As long as polling is disabled the device should be re-discovered in front of every command.

* Use a unique data key per domain.

* Named argument used and comment added.
2018-03-05 07:06:00 +01:00
Per Osbäck
259121c7a7 update notify html5 dependencies (#12898) 2018-03-04 18:46:09 -08:00
Dan Nixon
326241d9d8 Add empty unit to systemmonitor load averages (#12900)
Adds an empty unit to load averages reported by the systemmonitor
component in order to correctly identify the state as numeric.

A similar workaroud to this is already used for packet counts in the
same component.

Re #11022
2018-03-04 18:41:27 -08:00
Daniel Høyer Iversen
7c7da9df05 Tibber: Check if the current electricity price is available before we… (#12905)
* Tibber: Check if the current electricity price is available before we ask for new prices. await syntax

* tibber
2018-03-04 18:38:21 -08:00
Eduardo Fonseca
cf3f1c3081 Fixing small naming bug (#12911) 2018-03-04 18:37:54 -08:00
Per Osbäck
f00d5cb8ca update html5 to async/await tests (#12896)
* update html5 to async/await tests

* removed paranthesis
2018-03-04 18:35:07 -08:00
Paulus Schoutsen
3920de7119 Fix async method call in sync context (#12890) 2018-03-04 18:31:29 -08:00
Anders Melchiorsen
fd409a16a1 Remove dynamic controls from sonos (#12908) 2018-03-04 18:30:15 -08:00
Richard Lucas
7145afe729 Apple TV should return all supported features (#12167) 2018-03-05 00:04:11 +01:00
Jason Albert
70760b5d3b Fix for moisture sensors in isy994 (#12734)
* Fix for moisture sensors - previously never triggered

* Change quotes to be consistent
2018-03-04 13:59:54 -08:00
Anders Melchiorsen
d418355d4d InfluxDB cleanups (#12903)
* Close influxdb on shutdown

* Ignore inf as an influxdb value

* Remove deprecated CONF_RETRY_QUEUE
2018-03-04 21:01:16 +01:00
Andrei Pop
81ba666db7 Fix Edimax new firmware auth error and move to pyedimax fork (#12873)
* Fixed Edimax switch authentication error for newer firmware.

* pyedimax moved to pypi

* Added pyedimax to gen_requirements_all.py

* Cleanup
2018-03-04 20:36:38 +01:00
Anders Melchiorsen
a147401034 Additional radio schemes for sonos (#12886) 2018-03-04 14:02:31 +01:00
Alan Tse
36e9f523d1 Adding additional switches and sensors for Tesla (#12241)
* Adding switch for maxrange charging and sensors for odometer and range

* Fixing style errors
2018-03-04 11:35:38 +01:00
Per Osbäck
ae257651bf update html5 to async/await (#12895) 2018-03-04 11:20:03 +01:00
Joe Lu
53cc3262bd Upgrade to py-canary 0.4.1 (#12894) 2018-03-04 10:19:12 +01:00
Joe Lu
2e5b4946e1 Fix issue with guest August lock being included (#12893) 2018-03-04 10:14:47 +01:00
Paulus Schoutsen
67c49a7662 Add config flow for Hue (#12830)
* Add config flow for Hue

* Upgrade to aiohue 0.2

* Fix tests

* Add tests

* Add aiohue to test requirements

* Bump aiohue dependency

* Lint

* Lint

* Fix aiohttp mock

* Lint

* Fix tests
2018-03-03 21:28:04 -08:00
Anders Melchiorsen
d06807c634 Improve influxdb throughput (#12882)
* Batch influxdb events for writing

* Name constants
2018-03-03 21:22:31 -08:00
Kevin Tuhumury
2321603eb7 Add the Gamerscore and Tier of the account (#12867)
* Added the Gamerscore and Tier of the account.

The tier is the subscription type of the Xbox Live account, so Silver (which is now called Free) or Gold (paid).

* Don't add the gamerscore and tier inside of the loop, since they're always the same.
2018-03-04 02:38:51 +01:00
a-andre
ba20ffdde7 Fix interaction with hyperion on NodeMCU (#12872)
* Hyperion on NodeMCU has no 'activeEffects' entry

* Hyperion on NodeMCU has non-empty 'activeLedColor' when light is turned off
2018-03-03 17:04:32 -08:00
Paulus Schoutsen
cf8907ed0f Fix aggressive scan intervals (#12885) 2018-03-03 14:03:06 -08:00
Boris K
95176b0666 Fix 0 value when home-assistant restarts (#12874) 2018-03-03 22:59:25 +01:00
Julius Mittenzwei
f0d9844dfb await syntax knx scene (#12879) 2018-03-03 13:55:24 -08:00
Anders Melchiorsen
54f8f1223f Optimize logbook SQL query (#12881) 2018-03-03 13:54:55 -08:00
Anders Melchiorsen
339a839dbe Add SQL index to states.event_id (#12825) 2018-03-03 22:54:38 +01:00
Anders Melchiorsen
e2e10b91a7 Grammar fix 'an unique' (#12870) 2018-03-03 19:23:55 +01:00
JC Connell
a9d242a213 Add support for Zillow Zestimate sensor (#12597)
* Added Zillow Zestimate sensor.

* Add zestimate.py to .coveragerc

* Fix line 167 81>80

* Incorporate tinloaf changes.

* Saving work

* Incorporate changes requested by MartinHjelmare

* Remove unnecessary import

* Add a blank line between standard library and 3rd party imports
2018-03-03 17:41:33 +01:00
Daniel Høyer Iversen
49581a4a2a Add unique id for Tibber sensor (#12864)
Upgrade Tibber libary
2018-03-03 13:18:45 +01:00
Jens Østergaard Nielsen
d8a11fd706 Updated to use latest ihcsdk version (#12865)
* Updated to used latest ihcsdk version

* Updated requirements_all.txt
2018-03-03 12:48:58 +01:00
Anders Melchiorsen
d63cf94d6d Fix dead Sonos web interface even more (#12851) 2018-03-03 00:19:22 -08:00
Sebastian Muszynski
7d8a309017 IndexError (list index out of range) fixed. (#12858) 2018-03-03 00:15:19 -08:00
Otto Winter
99eeb01525 Fix light group update before add (#12844)
* Fix light group update before add.

* Revert pytest skip
2018-03-02 15:04:32 -08:00
Sebastian Muszynski
92b07ba8d1 PyXiaomiGateway version bumped. (#12828) 2018-03-02 12:00:17 -08:00
Paulus Schoutsen
c2b06b9e55 Merge branch 'master' into dev 2018-03-02 11:34:33 -08:00
Anders Melchiorsen
dd67192057 Keep auto groups during group reload (#12841)
* Keep auto groups during group reload

* Make protected member public

* Add test
2018-03-02 11:29:49 -08:00
Paulus Schoutsen
a5ffe0f72b Merge pull request #12848 from home-assistant/release-0-64-3
0.64.3
2018-03-02 11:26:26 -08:00
Ville Skyttä
7937064fb7 Address upcloud post-merge comments (#12011) (#12835) 2018-03-02 11:23:53 -08:00
Daniel Høyer Iversen
0762c7caef Update volvooncall.py (#12834) 2018-03-02 11:23:12 -08:00
Paulus Schoutsen
32b6fb60d8 Handle Hue errors better (#12845)
* Handle Hue errors better

* Lint
2018-03-02 11:22:26 -08:00
Paulus Schoutsen
4a85ab1ecb Cloud unauth (#12840)
* Handle expired refresh token better

* Retry less aggressive

* Newline
2018-03-02 11:22:26 -08:00
Andrey
981f6fa027 Fix sensibo default IDs to be according to schema (#12837) 2018-03-02 11:22:26 -08:00
Johann Kellerman
125088449a is_allowed_path: Also unit test folder #12788 #12807 (#12810) 2018-03-02 11:22:25 -08:00
Paulus Schoutsen
fec7c87ff3 Version bump to 0.64.3 2018-03-02 11:22:05 -08:00
Paulus Schoutsen
d333593aa6 Handle Hue errors better (#12845)
* Handle Hue errors better

* Lint
2018-03-02 11:21:30 -08:00
Paulus Schoutsen
4e03176634 Skip flaky light.group test [skipci] (#12847) 2018-03-02 11:19:19 -08:00
Andrey
e20e0425b1 Fix sensibo default IDs to be according to schema (#12837) 2018-03-02 11:16:48 -08:00
Paulus Schoutsen
228b030c82 Cloud unauth (#12840)
* Handle expired refresh token better

* Retry less aggressive

* Newline
2018-03-02 10:33:05 -08:00
Jeroen ter Heerdt
7a979e9f72 Egardia redesign - generic component and sensor support (#11994)
* Egardia redesign - generic component and sensor support

* Updating .coveragerc and requirements_all

* Fixing linting errors

* Fixing linting errors (again)

* Fixing linting errors

* Responding to review

* Responding to review.

* Updating requirements_all.txt

* Responding to review.

* Responding to review

* Removing unnessesary logging line.

* Responding to review

* Responding to review.

* Fixing copying mistake.

* Responding to review.

* Improving validation.

* Updating package requirement to .38

* Fixing syntax error.

* Updating requirements_all.txt

* Fixing bug handling alarm status.

* Updating requirements_all.txt

* Updating requirements_all.txt

* Changing parsing of configuration.

* Changing code lookup.

* Fixing linting error.
2018-03-02 12:50:00 +01:00
Thijs de Jong
25c4c9b63c Add icons to Xiaomi Aqara sensors (#12814)
* Update xiaomi_aqara.py

* Update xiaomi_aqara.py
2018-03-01 17:15:08 -08:00
Otto Winter
03970764d8 Add light.group platform (#12229)
* Add grouped_light platform

* 📝 Fix Lint issues

* 🎨 Reformat code with yapf

* A Few changes

*  Python 3.5 magic

* Improvements

Included the comments from #11323

* Fixes

* Updates

* Fixes & Tests

* Fix bad-whitespace

* Domain Config Validation

... by rebasing onto #12592

* Style changes & Improvements

* Lint

* Changes according to Review Comments

* Use blocking light.async_turn_*

* Revert "Use blocking light.async_turn_*"

This reverts commit 9e83198552.

* Update service calls and state reporting

* Add group service call tests

* Remove unused constant.
2018-03-01 17:14:26 -08:00
cdce8p
168e1f0e2d Improved Homekit tests (#12800)
* Added test for temperature fahrenheit

* Restructured tests to use more mocks

* Rearanged homekit constants

* Improved 'test_homekit_class'

* Added import statements

* Fix Pylint Test errors
2018-03-01 15:20:02 -08:00
Otto Winter
d3386907a4 MQTT Python 3.5 Async Await Syntax (#12815)
* MQTT Async Await

* Remove unused decorator.
2018-03-01 15:06:26 -08:00
Diogo Gomes
de3c76983a Filter Sensor (#12650)
* filter sensor platform implementation

* added tests

* default arguments

* Fix for unavailable units during initial startup

* unused variable

* Addresses code review by @MartinHjelmare

* fix

* don't need hass in this test

* Various Improvements

* Added Throttle Filter

* hound fixes

* test throttle filter

* fix

* Address comments by @balloob

* added test, reformulated filter tests

* Precision handling

* address comments from @balloob

* Revert "Precision handling"

This reverts commit f4abdd3702.

* removed stats

* only round floats

* Registry decorator usage

* Tries to address remaining comments
2018-03-01 15:03:01 -08:00
Otto Winter
b9d8789771 Cast Python Async Await Syntax (#12816) 2018-03-01 23:25:52 +01:00
Daniel Høyer Iversen
b186b27600 Tibber: retry if we fail to connect at startup (#12620)
* Tibber: retry if we fail to connect at startup

* Tibber: retry if we fail to connect at startup

* Tibber: retry if we fail to connect at startup

* Tibber: retry if we fail to connect at startup

* Update tibber.py

* Update tibber.py
2018-03-01 23:15:27 +01:00
Paulus Schoutsen
3ca446dda1 Version bump to 0.64.2 2018-03-01 11:52:00 -08:00
Paulus Schoutsen
1bc5042bf9 Merge pull request #12808 from home-assistant/release-0-64-2
0.64.2
2018-03-01 11:48:39 -08:00
Paulus Schoutsen
23c39ebefd Fix flakiness in tests (#12806) 2018-03-01 11:47:56 -08:00
Johann Kellerman
ff83efe376 is_allowed_path: Also unit test folder #12788 #12807 (#12810) 2018-03-01 18:55:58 +01:00
cdce8p
17ba813a6d Changed default from all to changed (#12660)
* Default option now '--changed'
* to check all files, use: '--all' or 'tox -e lint'
2018-03-01 18:02:19 +01:00
Greg Dowling
78dd010a04 Merge pull request #12817 from home-assistant/bump_pyloopenergy
Bump pyloopenergy to 0.0.18. Fixes hassio connect issues.
2018-03-01 16:31:10 +00:00
pavoni
88021ba404 Bump pyloopenergy to 0.0.18. Fixes hassio connect issues. 2018-03-01 16:05:18 +00:00
Paulus Schoutsen
491b3d707c Add optional words to conversation utterances (#12772)
* Add optional words to conversation utterances

* Conversation to handle singular/plural

* Remove print

* Add pronounce detection to shopping list

* Lint

* fix tests

* Add optional 2 words

* Fix tests

* Conversation: coroutine -> async/await

* Replace \s with space
2018-03-01 07:35:12 -08:00
Ryan McLean
59141a4063 Unique IDs for Plex Clients (#12799)
* Unique IDs for Clients

* HoundCI cleanup

* debug output removal

* Updates from feedback

* More Updates from feedback

* More Updates from feedback

* Lint Fixes
2018-02-28 23:01:35 -08:00
Anders Melchiorsen
b6805853f1 Fix dead Sonos web interface with some music sources (#12796)
* Get data from soco, not event

* Patch soco.events.parse_event_xml to ignore exceptions
2018-02-28 23:01:34 -08:00
Aaron Bach
7511286a25 Fixed Pollen.com bugs with ZIP codes and invalid API responses (#12790) 2018-02-28 23:01:34 -08:00
Bas Schipper
70979d855d Fixed missing optional keyerror data_bits (#12789) 2018-02-28 23:01:33 -08:00
uchagani
a1010cc63a Update samsungctl library to latest version (#12769)
* update samsungctl library to latest version

* add websocket dependency
2018-02-28 23:01:33 -08:00
Anders Melchiorsen
3a2c3fe589 Silence harmless sonos data structure warnings (#12767) 2018-02-28 23:01:32 -08:00
happyleavesaoc
eeb9992fde bump fedex version (#12764) 2018-02-28 23:01:32 -08:00
Anders Melchiorsen
191f24b2b9 Revert optimized logbook SQL (#12762) 2018-02-28 23:01:31 -08:00
ChristianKuehnel
761b4181c0 updated to bimmer_connected 0.4.1 (#12759)
fixes https://github.com/home-assistant/home-assistant/issues/12698
2018-02-28 23:01:30 -08:00
Paulus Schoutsen
7d8ca2010b Merge remote-tracking branch 'origin/master' into dev 2018-02-28 23:00:44 -08:00
Adam Mills
dbef8f0b78 Only run deploy from lint branch (#12805) 2018-02-28 20:37:40 -08:00
John Mihalic
ed85e368e3 Bump pyHik version, digest auth, more device support (#12801) 2018-02-28 20:33:03 -08:00
John Mihalic
4867ed23dc Take ownership of Emby, Eight Sleep, Hikvision (#12803) 2018-02-28 20:05:37 -08:00
Adam Mills
9f35d4dfca Translation cleanup (#12804)
* Inline load/save JSON

* Skip cleanup on travis deploy
2018-02-28 20:04:20 -08:00
Adam Mills
b434ffba2d Support serving of backend translations (#12453)
* Add view to support backend translation fetching

* Load backend translations from component json

* Translations for season sensor

* Scripts to merge and unpack Lokalise translations

* Fix copy paste error

* Serve post-lokalise translations to frontend

* Linting

* Auto-deploy translations with Travis

* Commit post-lokalise translation files

* Split logic into more helper functions

* Fall back to English for missing keys

* Move local translation copies to `.translations`

* Linting

* Initial tests

* Remove unnecessary file check

* Convert translation helper to async/await

* Convert translation helper tests to async/await

* Use set subtraction to find missing_components

* load_translation_files use component->file mapping

* Remove duplicated resources fetching

Get to take advantage of the slick Python 3.5 dict merging here.

* Switch to live project ID
2018-02-28 19:31:38 -08:00
Ryan McLean
a60712d826 Unique IDs for Plex Clients (#12799)
* Unique IDs for Clients

* HoundCI cleanup

* debug output removal

* Updates from feedback

* More Updates from feedback

* More Updates from feedback

* Lint Fixes
2018-02-28 17:53:51 -08:00
Reed Riley
53078f3069 iCloud location tracking improvements (#12399)
* Add an error message when there are name collisions in iCloud

* Teach icloud component to set interval based on proximity to nearest zone.
2018-02-28 16:54:19 -08:00
Mike O'Driscoll
3416d3f5f1 TekSavvy Sensor unlimited bandwidth support (#12325)
* Support TekSavvy Unlimited Plans

Support TekSavvy account usage for unlimited plans.
Seeing cap limit to 0 will now provide unlimited behaviour on usage calculations.

* Add unit tests to sensor.teksavvy

Add coverage unit tests to TekSavvy Sensor component, none existing previously.
2018-02-28 16:21:10 -08:00
Anders Melchiorsen
b1cc9bf452 Fix dead Sonos web interface with some music sources (#12796)
* Get data from soco, not event

* Patch soco.events.parse_event_xml to ignore exceptions
2018-02-28 16:10:58 -08:00
kbickar
6fe6dcfac9 Added Sense energy monitor sensor (#11580)
* Added Sense energy monitor sensor

* Added missing =

* Style updates

* Added newline, but not blank line at end of file

* Updated sense API to 0.2.2

* Moved import in function

* Fixed tabs

* Updated requirements

* Removed bare except

* Longer update times and more stats

* sense api update

* Updated to use string formatting

* Setup to use monitored_conditions

* Fix syntax

* API update

* blank space fixes

* More blank fixes

* API version update

* Fixed comment format

* removed unneeded function call
2018-02-28 14:29:24 -08:00
Maximilian Früh
001515bdc4 Add "headers" config parameter to rest switch (#12706)
* Add "headers" config parameter to rest switch

* Minor fix: line length
2018-02-28 14:00:51 -08:00
Otto Winter
c1aaef28a9 MQTT Static Typing (#12433)
* MQTT Typing

* Tiny style change

* Fixes

I should've probably really sticked to limiting myself to static typing...

* Small fix 😩

Ok, this seriously shouldn't have happened.
2018-02-28 22:59:14 +01:00
Paulus Schoutsen
f7e9215f5e Fix when 2 states match with same name (#12771) 2018-02-28 13:39:01 -08:00
Paulus Schoutsen
e2a2fe36fc Bump frontend to 20180228.1 (#12786)
* Bump frontend to 20180228.1

* Update reqs
2018-02-28 13:26:49 -08:00
Sean Wilson
3628fcf083 Add 'lock' device class (#11640)
* Add 'lock' device class

* Invert lock settings as per https://github.com/home-assistant/home-assistant/pull/11640
2018-02-28 13:26:25 -08:00
Bertbert
44cad7df30 Add Unit System Option For Fitbit (#11817)
* Add Unit System Option For Fitbit

* Update fitbit.py

* Update fitbit.py
2018-02-28 13:18:50 -08:00
Ville Skyttä
bbd58d7357 Add UpCloud platform (#12011)
* Add UpCloud platform

* Update upcloud-api to 0.4.1

* Update upcloud-api to 0.4.2

* Convert UpCloud to use Entity, helpers.dispatcher

* Lint
2018-02-28 13:17:12 -08:00
Sebastian Muszynski
222748dfbf Xiaomi MiIO Vacuum: Use a unique data key per domain (#12743)
* Use a unique data key per domain.

* Tests fixed.
2018-02-28 13:15:45 -08:00
Aaron Bach
a0eca9c6d1 Fixed Pollen.com bugs with ZIP codes and invalid API responses (#12790) 2018-02-28 13:09:57 -08:00
Bas Schipper
b556b86301 Fixed missing optional keyerror data_bits (#12789) 2018-02-28 13:07:23 -08:00
Philip Rosenberg-Watt
e82b358831 Round humidity for display purposes (#12766)
Humidity was not being rounded as temperature was. This change fixes
that.
2018-02-28 10:59:47 -08:00
uchagani
9658f4383c Update samsungctl library to latest version (#12769)
* update samsungctl library to latest version

* add websocket dependency
2018-02-28 07:37:12 +01:00
James Marsh
f6c504610b Add custom header support for rest_command (#12646)
* Add support for specifying custom headers for rest_command.
* Added headers configuration to behave similarly to the rest sensor.
* Replaced test_rest_command_content_type which only validated the
  configuration with test_rest_command_headers which tests several
  combinations of parameters that affect the request headers.
2018-02-28 08:16:31 +02:00
Paulus Schoutsen
f5c633415d Bump frontend to 20180228.0 2018-02-27 20:49:59 -08:00
Aaron Bach
c5157c1027 Update Yi platform to make use of async/await (#12713) 2018-02-27 18:06:45 -08:00
ChristianKuehnel
bba1e2adc9 updated to bimmer_connected 0.4.1 (#12759)
fixes https://github.com/home-assistant/home-assistant/issues/12698
2018-02-27 18:05:38 -08:00
Anders Melchiorsen
a63714dc86 Revert optimized logbook SQL (#12762) 2018-02-27 18:04:55 -08:00
happyleavesaoc
ab74ac8eca bump fedex version (#12764) 2018-02-27 18:04:30 -08:00
Anders Melchiorsen
f19b934869 Silence harmless sonos data structure warnings (#12767) 2018-02-27 18:04:00 -08:00
Paulus Schoutsen
efd155dd3c Intent: Set light color (#12633)
* Make color_name_to_rgb raise

* Add Light Set Color intent

* Move some methods around

* Cleanup

* Prevent 1 more func call

* Make a generic Set intent for light

* Lint

* lint
2018-02-27 18:02:21 -08:00
Anders Melchiorsen
14d052d242 Quote services.yaml string (#12763) 2018-02-28 00:16:49 +01:00
Teemu R
39cee987d9 Add Songpal ("Sony Audio Control API") platform (#12143)
* Add Songpal ("Sony Audio Control API") platform

This adds support for a variety of Sony soundbars and potentially
many more devices using the same API.
See http://vssupport.sony.net/en_ww/device.html for list of
supported devices.

* add songpal requirement

* update coveragerc

* fix linting

* add service description to yaml

* add entity_id

* make pylint also happy.

* raise PlatformNotReady when initialization fails, bump requirement for better exception handling

* use noqa instead of pylint's disable, fix newline error

* use async defs and awaits

* Make linter happy

* Changes based on code review, fix set_sound_setting.

* define set_sound_setting schema on top of the file

* Move initialization back to async_setup_platform

* Fix linting

* Fixes based on code review

* Fix coveragerc ordering
* Do not print out the whole exception trace when failing to update
* Do not bail out when receiving more than one volume control
* Set to unavailable when no volume controls are available
2018-02-27 13:21:56 -08:00
Johann Kellerman
6ed765698a Check_config await error (#12722)
* not awaited

* hounded
2018-02-27 12:29:17 -08:00
Sebastian Muszynski
138350fe3d Xiaomi MiIO Light: Flag the device as unavailable if not reachable (#12449)
* Unavailable state introduced if the device isn't reachable.
A new configuration option "model" can be used to define the device type.

```
light:
  - platform: xiaomi_miio
    name: Xiaomi Philips Smart LED Ball
    host: 192.168.130.67
    token: da548d86f55996413d82eea94279d2ff
    # Bypass of the device model detection.
    # Optional but required to add an unavailable device
    model: philips.light.bulb
```

New attribute "scene" and "delay_off_countdown" added.
New service xiaomi_miio_set_delay_off introduced.

* Service xiaomi_miio_set_delayed_turn_off updated. The attribute "delayed_turn_off" is a timestamp now.

* None must be a valid model.

* Math.

* Microseconds removed because of the low resolution.

* Comment updated.

* Update the ATTR_DELAYED_TURN_OFF on a deviation of 4 seconds (max latency) only.

* Import of datetime fixed.

* Typo fixed.

* pylint issues fixed.

* Naming.

* Service parameter renamed.

* New ceiling lamp model (philips.light.zyceiling) added.

* Use positive timedelta instead of seconds.

* Use a unique data key per domain.
2018-02-27 12:27:52 -08:00
Paulus Schoutsen
88b7f429c8 Merge pull request #12742 from home-assistant/release-0-64-1
0.64.1
2018-02-27 11:24:37 -08:00
Philip Rosenberg-Watt
992516ba86 Fix DarkSky floating-point math (#12753)
DarkSky delivers relative humidity from 0-100% as a 0-1 decimal value,
so we convert it by multiplying by 100.0. Unfortunately, due to floating
point math, the display of a raw value of 0.29 ends up looking like
28.999999999999996% relative humidity.

This change rounds the value to two decimal places.
2018-02-27 11:22:16 -08:00
Otto Winter
484841c890 Fix MQTT async_add_job in sync context (#12744) 2018-02-27 11:22:16 -08:00
Philip Rosenberg-Watt
4e522448b1 Fix DarkSky floating-point math (#12753)
DarkSky delivers relative humidity from 0-100% as a 0-1 decimal value,
so we convert it by multiplying by 100.0. Unfortunately, due to floating
point math, the display of a raw value of 0.29 ends up looking like
28.999999999999996% relative humidity.

This change rounds the value to two decimal places.
2018-02-27 11:19:02 -08:00
Otto Winter
8645f244da Fix MQTT async_add_job in sync context (#12744) 2018-02-27 11:18:20 -08:00
Lev Aronsky
be64422d1c Fix Citybikes naming (#12661)
* Fix Citybikes naming.

* Semantic fixes upon requests.

Entity ID generation now includes the base name (again),
resulting in preservation of the entity ID format (the change is
non-breaking)

* Use `async_generate_entity_id`.
2018-02-27 14:22:52 +01:00
Paulus Schoutsen
a0997bd214 Update ZHA deps (#12737) 2018-02-27 02:01:57 -08:00
Anders Melchiorsen
802bc322e8 Fix harmony duplicate detection (#12729) 2018-02-27 02:01:56 -08:00
ChristianKuehnel
ed8eda86e2 fix for https://github.com/home-assistant/home-assistant/issues/12673 (#12726) 2018-02-27 02:01:56 -08:00
Thijs de Jong
bbc2f1d808 Unbreak tahoma (#12719)
* Update requirements_all.txt

* Update tahoma.py
2018-02-27 02:01:55 -08:00
Pascal Vizeli
83203a10a7 Don't allow to use a old unsecure library (#12715)
* Don't allow to use a old unsecury library

* Update gen_requirements_all.py

* Cryptodome fix for python-broadlink

* Coinbase cryptodome fix
2018-02-27 02:01:54 -08:00
Oliver
113ea2d1dc Bugfix: Update of sources for non AVR-X devices always fails (#12711)
* Basic support of post 2016 AVR-X receivers

* Bugfix: Update of sources for non AVR-X devices always fails
2018-02-27 02:01:54 -08:00
tumik
d128f4e51f Component deconz: Fix dark attribute on presence sensors (#12691)
pydeconz changed PRESENCE to be an array in v25, so this code hasn't worked since that change.
2018-02-27 02:01:54 -08:00
Paulus Schoutsen
de3a9d552a Fix mysensor defaults (#12687) 2018-02-27 02:01:53 -08:00
Jesse Hills
948ef7523e Fix getting state from iglo (#12685) 2018-02-27 02:01:53 -08:00
Paulus Schoutsen
9e8340432c Harmony: make activity optional (#12679) 2018-02-27 02:01:52 -08:00
Lev Aronsky
0a067f4cc7 Fix a problem with calling deconz.close (#12657)
* Fix a problem with calling `deconz.close`

The event object (`EVENT_HOMEASSISTANT_STOP`) is sent as an argument to
the callable passed to `async_listen_once`. However, `deconz.close` is a bound method
that takes no arguments. Therefore, it needs a wrapper to discard the event
object.

* Removed unnecessary code and added a docstring.

* Fix the docstring according to guidelines.

* Removed unnecessary whitespace.
2018-02-27 02:01:52 -08:00
Johann Kellerman
68f92d2e7c Roomba timeout (#12645)
* Roomba timeout

* PlatformNotReady
2018-02-27 02:01:51 -08:00
Otto Winter
e14893416f Cast automatically drop connection (#12635) 2018-02-27 02:01:51 -08:00
Pascal Vizeli
9751fed493 Don't allow to use a old unsecure library (#12715)
* Don't allow to use a old unsecury library

* Update gen_requirements_all.py

* Cryptodome fix for python-broadlink

* Coinbase cryptodome fix
2018-02-27 01:58:45 -08:00
Paulus Schoutsen
dc8c331c68 Update ZHA deps (#12737) 2018-02-27 00:05:29 -08:00
Jesse Hills
d052d45712 Fix getting state from iglo (#12685) 2018-02-26 23:07:39 -08:00
Adam Mills
4242411089 Disable asuswrt tests (#12663) 2018-02-26 22:53:54 -08:00
tumik
f396266c74 Component deconz: Fix dark attribute on presence sensors (#12691)
pydeconz changed PRESENCE to be an array in v25, so this code hasn't worked since that change.
2018-02-26 22:44:57 -08:00
Oliver
21d8ecdacd Bugfix: Update of sources for non AVR-X devices always fails (#12711)
* Basic support of post 2016 AVR-X receivers

* Bugfix: Update of sources for non AVR-X devices always fails
2018-02-26 22:44:11 -08:00
ChristianKuehnel
6bdb2fe5a0 fix for https://github.com/home-assistant/home-assistant/issues/12673 (#12726) 2018-02-26 22:42:32 -08:00
Anders Melchiorsen
c1c23bb4b6 Remove automatic sqlite vacuum (#12728) 2018-02-26 22:41:37 -08:00
Anders Melchiorsen
98fef81d19 Fix harmony duplicate detection (#12729) 2018-02-26 22:40:46 -08:00
Thiago Oliveira
71cab65df6 correct air index unit (#12730)
AirVisual's air index unit is AQI (Air Quality Index), not PSI (Pressure per Square Inch). More details can be found at https://airvisual.com/

Cheers,
2018-02-26 22:39:26 -08:00
Lev Aronsky
111e515da7 Fix a problem with calling deconz.close (#12657)
* Fix a problem with calling `deconz.close`

The event object (`EVENT_HOMEASSISTANT_STOP`) is sent as an argument to
the callable passed to `async_listen_once`. However, `deconz.close` is a bound method
that takes no arguments. Therefore, it needs a wrapper to discard the event
object.

* Removed unnecessary code and added a docstring.

* Fix the docstring according to guidelines.

* Removed unnecessary whitespace.
2018-02-27 07:31:47 +01:00
Paulus Schoutsen
f7fc4c6f15 Frontend bump to 20180227.0 2018-02-26 22:24:31 -08:00
Paulus Schoutsen
a783006f92 Version bump to 0.64.1 2018-02-26 22:23:53 -08:00
Paulus Schoutsen
5b8aeafdb9 Frontend bump to 20180227.0 2018-02-26 22:21:10 -08:00
Adam Mills
c1a6131aa8 Update core HSV color scaling to standard scales: (#12649)
Hue is scaled 0-360
Sat is scaled 0-100
Val is scaled 0-100
2018-02-26 22:20:24 -05:00
Johann Kellerman
4821858afb Homekit schema gracefully fail with integer (#12725)
* Homekit schema gracefully fail with integer
* Fix return value
* Added test
* Fix 2
2018-02-27 01:09:49 +01:00
Johann Kellerman
446390a8d1 AsusWRT log exceptions (#12668)
* logexception

* Improve err message #2978

* not quiet

* tests
2018-02-26 16:08:37 -08:00
cdce8p
6a665ffb84 Fix homekit: temperature calculation (#12720) 2018-02-26 22:29:52 +01:00
Thijs de Jong
10570f5ad6 Unbreak tahoma (#12719)
* Update requirements_all.txt

* Update tahoma.py
2018-02-26 22:27:20 +01:00
Sebastian Muszynski
2c1083bda1 Next generation of Xiaomi Aqara devices added (#12659)
* Next generation of Xiaomi Aqara devices added: ctrl_neutral1.aq1, ctrl_neutral2.aq1, ctrl_ln1.aq1, ctrl_ln2.aq1, ctrl_86plug.aq1

* The Aqara wireless button (3rd gen, sensor_switch.aq3) supports a click_type called "shake".

* Warning added to spot new features.
2018-02-26 21:54:06 +01:00
Paulus Schoutsen
f8a0a0ba59 Fix mysensor defaults (#12687) 2018-02-26 17:01:00 +01:00
Paulus Schoutsen
1d14a17ffd Harmony: make activity optional (#12679) 2018-02-26 01:11:28 -08:00
Paulus Schoutsen
a8c9303892 Add history_graph component to demo (#12681) 2018-02-26 00:28:25 -08:00
Robin
bf41674e06 Adds simulated sensor (#12539)
* Create simulated.py

* Create test_simulated.py

* Update .coveragerc

* Drop numpy and fix attributes

Drop numpy and fix attributes to be machine readble

* Update test_simulated.py

* Update simulated.py

* Update test_simulated.py

* Update simulated.py

* Update test_simulated.py

* Update simulated.py

* Update simulated.py

* Update test_simulated.py

* Update simulated.py

* Fix default random seed error

* Update simulated.py

* Addresses balloob comments

* Update simulated.py
2018-02-26 00:01:01 -08:00
cdce8p
6e6ae173fd Added config validator for future group platforms (#12592)
* Added cv.EntitiesDoamin(domain) validator

* Check if all entities in string or list belong to domain
* Added tests

* Use factory function and entity_ids

* Different error message

* Typo

* Added entity_domain validator for a single entity_id

* Image_processing platform now uses cv.entity_domain for source validation
2018-02-25 23:48:21 -08:00
Julius Mittenzwei
7d5c1581f1 KNX Component: Scene support and expose sensor values (#11978)
* XKNX improvements: Added Scene support, added support for exposing sensors to KNX bus, added reset value option for binary switches

* fixed import

* Bumped version of KNX library (minor upgrade with two important bugfixes)

* bumped version of xknx (now without python requirement *sigh*)

* Issue #11978: fixed review comments

* Issue #11978: hound suggestion fixed:

* review comments

* made async functions async

* Addressed issues mentined by @MartinHjelmare

* removed default=None from validation schema

* ATTR_ENTITY_ID->CONF_ENTITY_ID

* moved missing function to async syntax

* pylint

* Trigger notification

* Trigger notification

* fixed review comment
2018-02-25 23:44:09 -08:00
Johann Kellerman
6d5fb49687 Roomba timeout (#12645)
* Roomba timeout

* PlatformNotReady
2018-02-25 23:43:26 -08:00
awkwardDuck
e96ac74b11 Fix formatting of minutes for sleep start in the fitbit sensor (#12664)
* fix formatting of minutes for sleep start

https://github.com/home-assistant/home-assistant/issues/12594

* Update fitbit.py
2018-02-25 19:28:41 -08:00
cdce8p
27b1d448a3 Homekit Update, Support for TempSensor (°F) (#12676)
* Changed version of "HAP-python" to "v1.1.7"

* Updated acc file to simplify init calls

* Code refactored and '°F' temp Sensors added

* Changed call to 'HomeAccessory' and 'HomeBridge'
* Extended function of 'add_preload_service' to add additional characteristics
* Added function to override characteristic property values

* TemperatureSensor
  * Added unit
  * Added calc_temperature

* Updated tests
2018-02-25 19:27:40 -08:00
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
Paulus Schoutsen
347ba1a2d8 Remove braviatv_psk (#12669) 2018-02-25 11:50:06 -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
Tom Harris
32166fd56a Upgrade insteonplm to 0.8.2 (required refactoring) (#12534)
* Merge from current dev

* Update for Sensor approach

* Update reference to state classes

* Reference stateKey correctly

* Reference stateKey

* Change deviceInfo to a dict

* Pass state to properties method

* Add state info to device_state_attributes

* Update entity name to include state name

* Update for on() off() vs light_on/off

* Flag newnames option

* Update configuration schema

* Update configuration schema

* Spell False correctly

* Rename state to statekey

* Rename statekey to stateKey

* Call new device with stateKey and newname

* Simplify use of newnames

* Add workdir to save devices

* Fix newnames config setup

* Propogate OnOffSensor to VariableSensor change

* Upgrade insteonplm version to 0.8.0

* Pass address rather than device object to platform

* Set inteon_plm data variable to PLM

* Consistant use of conn and plm

* Consistant use of conn and plm

* Proper reference to device and address

* Fixed platform setup issues

* Correct issue with missing _supported_features attr

* Properly reference self._state vs self.state

* Bump insteonplm version to 0.8.1

* Remove subplatform and map based on state name

* Correct refrence to State

* Correct reference to device.states

* Bump insteonplm to 0.8.2

* Fix format errors

* Fix format issues

* Fix trailing whitespace

* Correct format issues

* Fix format issues

* Fix format issues

* Fixed format issues

* Fixed format issues

* Move imports inside classes

* Simplify import of modules

* Correct reference to OnOffSwitch_OutletTop and bottom

* Remove unnessary references

* Fix format issues

* Code review adjustments

* Fix format issue

* Use new nameing format for groups that are not group 0x01

* Remove newname feature

* Fix binary_sensor type to default to None

* Fix device_class property to return the sensor type correctly.

* rename _device and _state to avoid conflicts with Entity

* Format long lines

* Pylint clean up

* Insteon_PLM

* lint cleanup

* Check device_override has address

* 3.4 lint clean up

* Changes per code review

* Change discovered from a list of dict to a dict

* Correct common_attributes usage

* Change discovered from a list of dict to a dict

* Add debugging message to confirm platform setup

* Debug messages

* Debug messages

* Debug async_added_to_hass

* Debug async_added_to_hass async_add_job

* Debug async_added_to_hass

* Debug devices not loading in hass

* Debug new entities not being added to hass

* Debug adding devices to hass

* Revert "3.4 lint clean up"

This reverts commit 0d8fb992b1.

* 3.4 lint clean up

* Revert "Debug adding devices to hass"

This reverts commit ec306773d4.

* Revert "Debug new entities not being added to hass"

This reverts commit 55fb724d06.

* Revert "Debug devices not loading in hass"

This reverts commit 07814b4f14.

* Revert "Debug async_added_to_hass"

This reverts commit 4963a255d8.

* Revert "Debug async_added_to_hass async_add_job"

This reverts commit 22cadff91f.

* Revert "Debug async_added_to_hass"

This reverts commit 12c5651fe4.

* Pylint clean up

* pylint cleanup

* Clean up naming

* Enhance config schema. Fix logging issue

* Reapply changes after squash
2018-02-25 20:13:39 +01:00
Lewis Juggins
e8173fbc16 Enable pytradfri during build, and include in Docker (#12662) 2018-02-25 18:20:43 +00:00
Mike Megally
63552abce5 Synology Chat as a notification platform (#12596)
* first attempt at synology chat as a notification platform

* quick fix

* houndci and coverage

* Cleanup

Some cleanup of the file

* Ugh underscore

* Use string formatting

* Remove `CONF_NAME`
2018-02-25 14:47:46 +01:00
Julius Mittenzwei
16cb7388ee Removing asyncio.coroutine syntax from HASS core (#12509)
* changed asyncio.coroutine syntax to new async def/await

* removed py34 from tox environment

* reverted some changes within entity.py

* -

* reverted changes within bootstrap.py

* reverted changes within discovery.py

* switched decorators

* Reverted change within aiohttp_client.py

* reverted change within logging.py

* switched decorators

* Await lock properly

* removed asyncio.coroutine from test
2018-02-25 03:38:46 -08:00
cdce8p
eacfbc048a Improved Homekit tests (#12647)
* Spelling and typos
* Updated 'test_homekit_pyhap_interaction'
* Patch ip_address
2018-02-25 10:58:13 +01:00
cdce8p
da832dbda2 Removed py34 (#12648) 2018-02-25 08:05:20 +01:00
Adam Mills
f51a3738aa Check if $files is empty, don't try to execute it (#12651) 2018-02-24 22:11:49 -05:00
Paulus Schoutsen
6d431c3fc3 Allow renaming entities in entity registry (#12636)
* Allow renaming entities in entity registry

* Lint
2018-02-24 10:53:59 -08:00
Otto Winter
2821820281 Cast automatically drop connection (#12635) 2018-02-24 10:27:44 -08:00
Julius Mittenzwei
3713dfe139 Removing asyncio.coroutine syntax from some components (#12507)
* Removing asyncio.coroutine syntax (first steps)

* merge conflict

* fixed small bug

* pylint
2018-02-24 10:24:33 -08:00
Otto Winter
c076b805e7 Fix cast doing I/O in event loop (#12632) 2018-02-23 15:13:48 -08:00
kennedyshead
7a44eee093 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-23 23:33:12 +01:00
Sebastian Muszynski
485979cd86 Xiaomi Aqara Gateway: Service descriptions added (#12631)
* Service descriptions of the Xiaomi Aqara Gateway added.

* Descriptions added.
2018-02-23 22:33:51 +01:00
Pascal Vizeli
5a80d4e5ea Hassio update timeout filter list (#12617)
* Update timeout filter list

* Update http.py
2018-02-23 10:13:04 -08:00
Sebastian Muszynski
c3d322f26c 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-23 10:11:54 -08:00
Otto Winter
230b73d14a 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-23 09:31:22 -08:00
Otto Winter
042f292e4f Fix CODEOWNERS permissions (#12621) 2018-02-23 14:57:49 +01:00
Thijs de Jong
daa00bc65a 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-23 13:03:00 +01:00
Anders Melchiorsen
1b22f2d8b8 Move recorder query out of event loop (#12615) 2018-02-23 10:12:40 +01:00
Paulus Schoutsen
1e672b93e7 Fix voluptuous breaking change things (#12611)
* Fix voluptuous breaking change things

* Change xiaomi aqara back
2018-02-22 23:29:42 -08:00
Paulus Schoutsen
6ee3c1b3e5 Hello Python 3.5 (#12610)
* Hello Python 3.5

* Fix test

* Fix tests

* Fix never awaited block till done warnings
2018-02-22 23:22:27 -08:00
Scott Bradshaw
156206dfee OpenGarage - correctly handle offline status (#12612) (#12613) 2018-02-22 21:53:08 -08:00
Anders Melchiorsen
7dcb2ae24c Optimize logbook SQL query (#12608) 2018-02-22 15:40:58 -08:00
Paulus Schoutsen
6b03cb913c Version bump to 0.65.0.dev0 2018-02-22 15:35:24 -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
650 changed files with 22594 additions and 9291 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
@@ -56,6 +62,9 @@ omit =
homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py
homeassistant/components/daikin.py
homeassistant/components/*/daikin.py
homeassistant/components/deconz/*
homeassistant/components/*/deconz.py
@@ -76,6 +85,9 @@ omit =
homeassistant/components/ecobee.py
homeassistant/components/*/ecobee.py
homeassistant/components/egardia.py
homeassistant/components/*/egardia.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
@@ -175,6 +187,9 @@ omit =
homeassistant/components/opencv.py
homeassistant/components/*/opencv.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
homeassistant/components/qwikswitch.py
homeassistant/components/*/qwikswitch.py
@@ -205,6 +220,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
@@ -235,6 +253,9 @@ omit =
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/upcloud.py
homeassistant/components/*/upcloud.py
homeassistant/components/usps.py
homeassistant/components/*/usps.py
@@ -284,13 +305,9 @@ omit =
homeassistant/components/zoneminder.py
homeassistant/components/*/zoneminder.py
homeassistant/components/daikin.py
homeassistant/components/*/daikin.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/egardia.py
homeassistant/components/alarm_control_panel/ialarm.py
homeassistant/components/alarm_control_panel/manual_mqtt.py
homeassistant/components/alarm_control_panel/nx584.py
@@ -303,7 +320,6 @@ omit =
homeassistant/components/binary_sensor/hikvision.py
homeassistant/components/binary_sensor/iss.py
homeassistant/components/binary_sensor/mystrom.py
homeassistant/components/binary_sensor/pilight.py
homeassistant/components/binary_sensor/ping.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py
@@ -344,12 +360,14 @@ omit =
homeassistant/components/cover/scsgate.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
homeassistant/components/device_tracker/asuswrt.py
homeassistant/components/device_tracker/automatic.py
homeassistant/components/device_tracker/bbox.py
homeassistant/components/device_tracker/bluetooth_le_tracker.py
homeassistant/components/device_tracker/bluetooth_tracker.py
homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/hitron_coda.py
@@ -455,6 +473,7 @@ omit =
homeassistant/components/media_player/russound_rio.py
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/songpal.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
homeassistant/components/media_player/squeezebox.py
@@ -462,6 +481,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
@@ -498,6 +518,7 @@ omit =
homeassistant/components/notify/simplepush.py
homeassistant/components/notify/slack.py
homeassistant/components/notify/smtp.py
homeassistant/components/notify/synology_chat.py
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/telstra.py
@@ -551,8 +572,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
@@ -607,16 +630,19 @@ omit =
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sense.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/shodan.py
homeassistant/components/sensor/simulated.py
homeassistant/components/sensor/skybeacon.py
homeassistant/components/sensor/sma.py
homeassistant/components/sensor/snmp.py
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
@@ -626,7 +652,6 @@ omit =
homeassistant/components/sensor/sytadin.py
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/teksavvy.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py
@@ -645,6 +670,7 @@ omit =
homeassistant/components/sensor/worxlandroid.py
homeassistant/components/sensor/xbox_live.py
homeassistant/components/sensor/zamg.py
homeassistant/components/sensor/zestimate.py
homeassistant/components/shiftr.py
homeassistant/components/spc.py
homeassistant/components/switch/acer_projector.py
@@ -662,7 +688,6 @@ omit =
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pilight.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainbird.py
homeassistant/components/switch/rainmachine.py

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

3
.gitignore vendored
View File

@@ -103,3 +103,6 @@ desktop.ini
# mypy
/.mypy_cache/*
# Secrets
.lokalise_token

View File

@@ -6,12 +6,10 @@ addons:
matrix:
fast_finish: true
include:
- python: "3.4.2"
- python: "3.5.3"
env: TOXENV=lint
- python: "3.4.2"
- python: "3.5.3"
env: TOXENV=pylint
- python: "3.4.2"
env: TOXENV=py34
# - python: "3.5"
# env: TOXENV=typing
- python: "3.5.3"
@@ -30,4 +28,15 @@ cache:
install: pip install -U tox coveralls
language: python
script: travis_wait 30 tox --develop
services:
- docker
before_deploy:
- docker pull lokalise/lokalise-cli@sha256:79b3108211ed1fcc9f7b09a011bfc53c240fc2f3b7fa7f0c8390f593271b4cd7
deploy:
skip_cleanup: true
provider: script
script: script/travis_deploy
on:
branch: dev
condition: $TOXENV = lint
after_success: coveralls

View File

@@ -43,6 +43,8 @@ homeassistant/components/hassio.py @home-assistant/hassio
# Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/binary_sensor/hikvision.py @mezz64
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
@@ -53,8 +55,12 @@ homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
homeassistant/components/media_player/emby.py @mezz64
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 +69,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 +77,13 @@ 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/eight_sleep.py @mezz64
homeassistant/components/*/eight_sleep.py @mezz64
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

@@ -15,7 +15,6 @@ from homeassistant.const import (
__version__,
EVENT_HOMEASSISTANT_START,
REQUIRED_PYTHON_VER,
REQUIRED_PYTHON_VER_WIN,
RESTART_EXIT_CODE,
)
@@ -33,12 +32,7 @@ def attempt_use_uvloop():
def validate_python() -> None:
"""Validate that the right Python version is running."""
if sys.platform == "win32" and \
sys.version_info[:3] < REQUIRED_PYTHON_VER_WIN:
print("Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER_WIN))
sys.exit(1)
elif sys.version_info[:3] < REQUIRED_PYTHON_VER:
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
print("Home Assistant requires at least Python {}.{}.{}".format(
*REQUIRED_PYTHON_VER))
sys.exit(1)

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.
@@ -111,21 +112,20 @@ def async_from_config_dict(config: Dict[str, Any],
if not loader.PREPARED:
yield from hass.async_add_job(loader.prepare, hass)
# Make a copy because we are mutating it.
config = OrderedDict(config)
# Merge packages
conf_util.merge_packages_config(
config, core_config.get(conf_util.CONF_PACKAGES, {}))
# Make a copy because we are mutating it.
# Use OrderedDict in case original one was one.
# Convert values to dictionaries if they are None
new_config = OrderedDict()
for key, value in config.items():
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 +163,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 +188,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 +219,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,13 @@ 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

@@ -17,7 +17,7 @@ from homeassistant.const import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyalarmdotcom==0.3.0']
REQUIREMENTS = ['pyalarmdotcom==0.3.1']
_LOGGER = logging.getLogger(__name__)

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

@@ -26,7 +26,7 @@ DEFAULT_HOST = 'localhost'
DEFAULT_NAME = 'CONCORD232'
DEFAULT_PORT = 5007
SCAN_INTERVAL = timedelta(seconds=1)
SCAN_INTERVAL = timedelta(seconds=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,

View File

@@ -4,130 +4,65 @@ Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.egardia/
"""
import asyncio
import logging
import requests
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
import homeassistant.exceptions as exc
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pythonegardia==1.0.26']
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_TRIGGERED)
from homeassistant.components.egardia import (
EGARDIA_DEVICE, EGARDIA_SERVER,
REPORT_SERVER_CODES_IGNORE, CONF_REPORT_SERVER_CODES,
CONF_REPORT_SERVER_ENABLED, CONF_REPORT_SERVER_PORT
)
REQUIREMENTS = ['pythonegardia==1.0.38']
_LOGGER = logging.getLogger(__name__)
CONF_REPORT_SERVER_CODES = 'report_server_codes'
CONF_REPORT_SERVER_ENABLED = 'report_server_enabled'
CONF_REPORT_SERVER_PORT = 'report_server_port'
CONF_REPORT_SERVER_CODES_IGNORE = 'ignore'
CONF_VERSION = 'version'
DEFAULT_NAME = 'Egardia'
DEFAULT_PORT = 80
DEFAULT_REPORT_SERVER_ENABLED = False
DEFAULT_REPORT_SERVER_PORT = 52010
DEFAULT_VERSION = 'GATE-01'
DOMAIN = 'egardia'
D_EGARDIASRV = 'egardiaserver'
NOTIFICATION_ID = 'egardia_notification'
NOTIFICATION_TITLE = 'Egardia'
STATES = {
'ARM': STATE_ALARM_ARMED_AWAY,
'DAY HOME': STATE_ALARM_ARMED_HOME,
'DISARM': STATE_ALARM_DISARMED,
'HOME': STATE_ALARM_ARMED_HOME,
'TRIGGERED': STATE_ALARM_TRIGGERED,
'UNKNOWN': STATE_UNKNOWN,
'ARMHOME': STATE_ALARM_ARMED_HOME,
'TRIGGERED': STATE_ALARM_TRIGGERED
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_VERSION, default=DEFAULT_VERSION): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_REPORT_SERVER_CODES): vol.All(cv.ensure_list),
vol.Optional(CONF_REPORT_SERVER_ENABLED,
default=DEFAULT_REPORT_SERVER_ENABLED): cv.boolean,
vol.Optional(CONF_REPORT_SERVER_PORT, default=DEFAULT_REPORT_SERVER_PORT):
cv.port,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Egardia platform."""
from pythonegardia import egardiadevice
from pythonegardia import egardiaserver
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
rs_enabled = config.get(CONF_REPORT_SERVER_ENABLED)
rs_port = config.get(CONF_REPORT_SERVER_PORT)
rs_codes = config.get(CONF_REPORT_SERVER_CODES)
version = config.get(CONF_VERSION)
try:
egardiasystem = egardiadevice.EgardiaDevice(
host, port, username, password, '', version)
except requests.exceptions.RequestException:
raise exc.PlatformNotReady()
except egardiadevice.UnauthorizedError:
_LOGGER.error("Unable to authorize. Wrong password or username")
return
eg_dev = EgardiaAlarm(
name, egardiasystem, rs_enabled, rs_codes)
if rs_enabled:
# Set up the egardia server
_LOGGER.info("Setting up EgardiaServer")
try:
if D_EGARDIASRV not in hass.data:
server = egardiaserver.EgardiaServer('', rs_port)
bound = server.bind()
if not bound:
raise IOError(
"Binding error occurred while starting EgardiaServer")
hass.data[D_EGARDIASRV] = server
server.start()
except IOError:
return
hass.data[D_EGARDIASRV].register_callback(eg_dev.handle_status_event)
def handle_stop_event(event):
"""Call function for Home Assistant stop event."""
hass.data[D_EGARDIASRV].stop()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
add_devices([eg_dev], True)
device = EgardiaAlarm(
discovery_info['name'],
hass.data[EGARDIA_DEVICE],
discovery_info[CONF_REPORT_SERVER_ENABLED],
discovery_info.get(CONF_REPORT_SERVER_CODES),
discovery_info[CONF_REPORT_SERVER_PORT])
# add egardia alarm device
add_devices([device], True)
class EgardiaAlarm(alarm.AlarmControlPanel):
"""Representation of a Egardia alarm."""
def __init__(self, name, egardiasystem, rs_enabled=False, rs_codes=None):
def __init__(self, name, egardiasystem,
rs_enabled=False, rs_codes=None, rs_port=52010):
"""Initialize the Egardia alarm."""
self._name = name
self._egardiasystem = egardiasystem
self._status = None
self._rs_enabled = rs_enabled
if rs_codes is not None:
self._rs_codes = rs_codes[0]
else:
self._rs_codes = rs_codes
self._rs_codes = rs_codes
self._rs_port = rs_port
@asyncio.coroutine
def async_added_to_hass(self):
"""Add Egardiaserver callback if enabled."""
if self._rs_enabled:
_LOGGER.debug("Registering callback to Egardiaserver")
self.hass.data[EGARDIA_SERVER].register_callback(
self.handle_status_event)
@property
def name(self):
@@ -156,31 +91,20 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
def lookupstatusfromcode(self, statuscode):
"""Look at the rs_codes and returns the status from the code."""
status = 'UNKNOWN'
if self._rs_codes is not None:
statuscode = str(statuscode).strip()
for i in self._rs_codes:
val = str(self._rs_codes[i]).strip()
if ',' in val:
splitted = val.split(',')
for code in splitted:
code = str(code).strip()
if statuscode == code:
status = i.upper()
break
elif statuscode == val:
status = i.upper()
break
status = next((
status_group.upper() for status_group, codes
in self._rs_codes.items() for code in codes
if statuscode == code), 'UNKNOWN')
return status
def parsestatus(self, status):
"""Parse the status."""
_LOGGER.debug("Parsing status %s", status)
# Ignore the statuscode if it is IGNORE
if status.lower().strip() != CONF_REPORT_SERVER_CODES_IGNORE:
_LOGGER.debug("Not ignoring status")
newstatus = ([v for k, v in STATES.items()
if status.upper() == k][0])
if status.lower().strip() != REPORT_SERVER_CODES_IGNORE:
_LOGGER.debug("Not ignoring status %s", status)
newstatus = STATES.get(status.upper())
_LOGGER.debug("newstatus %s", newstatus)
self._status = newstatus
else:
_LOGGER.error("Ignoring status")

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

@@ -131,8 +131,7 @@ class APIEventStream(HomeAssistantView):
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
response.write(msg.encode("UTF-8"))
yield from response.drain()
yield from response.write(msg.encode("UTF-8"))
except asyncio.TimeoutError:
yield from to_write.put(STREAM_PING_PAYLOAD)

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.4.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_operable_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

@@ -4,7 +4,7 @@ Component to interface with binary sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor/
"""
import asyncio
from datetime import timedelta
import logging
@@ -28,6 +28,7 @@ DEVICE_CLASSES = [
'gas', # On means gas detected, Off means no gas (clear)
'heat', # On means hot, Off means normal
'light', # On means light detected, Off means no light
'lock', # On means open (unlocked), Off means closed (locked)
'moisture', # On means wet, Off means dry
'motion', # On means motion detected, Off means no motion (clear)
'moving', # On means moving, Off means not moving (stopped)
@@ -47,13 +48,12 @@ DEVICE_CLASSES = [
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Track states and offer events for binary sensors."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
yield from component.async_setup(config)
await component.async_setup(config)
return True

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

@@ -27,7 +27,7 @@ DEFAULT_NAME = 'Alarm'
DEFAULT_PORT = '5007'
DEFAULT_SSL = False
SCAN_INTERVAL = datetime.timedelta(seconds=1)
SCAN_INTERVAL = datetime.timedelta(seconds=10)
ZONE_TYPES_SCHEMA = vol.Schema({
cv.positive_int: vol.In(DEVICE_CLASSES),

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):
@@ -97,6 +99,6 @@ class DeconzBinarySensor(BinarySensorDevice):
attr = {
ATTR_BATTERY_LEVEL: self._sensor.battery,
}
if self._sensor.type == PRESENCE:
if self._sensor.type in PRESENCE:
attr['dark'] = self._sensor.dark
return attr

View File

@@ -0,0 +1,78 @@
"""
Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.egardia/
"""
import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.egardia import (
EGARDIA_DEVICE, ATTR_DISCOVER_DEVICES)
_LOGGER = logging.getLogger(__name__)
EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion',
'Door Contact': 'opening',
'IR': 'motion'}
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Initialize the platform."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None):
return
disc_info = discovery_info[ATTR_DISCOVER_DEVICES]
# multiple devices here!
async_add_devices(
(
EgardiaBinarySensor(
sensor_id=disc_info[sensor]['id'],
name=disc_info[sensor]['name'],
egardia_system=hass.data[EGARDIA_DEVICE],
device_class=EGARDIA_TYPE_TO_DEVICE_CLASS.get(
disc_info[sensor]['type'], None)
)
for sensor in disc_info
), True)
class EgardiaBinarySensor(BinarySensorDevice):
"""Represents a sensor based on an Egardia sensor (IR, Door Contact)."""
def __init__(self, sensor_id, name, egardia_system, device_class):
"""Initialize the sensor device."""
self._id = sensor_id
self._name = name
self._state = None
self._device_class = device_class
self._egardia_system = egardia_system
def update(self):
"""Update the status."""
egardia_input = self._egardia_system.getsensorstate(self._id)
self._state = STATE_ON if egardia_input else STATE_OFF
@property
def name(self):
"""The name of the device."""
return self._name
@property
def is_on(self):
"""Whether the device is switched on."""
return self._state == STATE_ON
@property
def hidden(self):
"""Whether the device is hidden by default."""
# these type of sensors are probably mainly used for automations
return True
@property
def device_class(self):
"""The device class."""
return self._device_class

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

@@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.1.4']
REQUIREMENTS = ['pyhik==0.1.8']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
@@ -48,6 +48,9 @@ DEVICE_CLASS_MAP = {
'Face Detection': 'motion',
'Scene Change Detection': 'motion',
'I/O': None,
'Unattended Baggage': 'motion',
'Attended Baggage': 'motion',
'Recording Failure': None,
}
CUSTOMIZE_SCHEMA = vol.Schema({
@@ -56,7 +59,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,
@@ -211,7 +214,7 @@ class HikvisionBinarySensor(BinarySensorDevice):
@property
def unique_id(self):
"""Return an unique ID."""
"""Return a unique ID."""
return self._id
@property

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

@@ -2,86 +2,56 @@
Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon_plm/
https://home-assistant.io/components/binary_sensor.insteon_plm/
"""
import logging
import asyncio
import logging
from homeassistant.core import callback
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
from homeassistant.components.insteon_plm import InsteonPLMEntity
DEPENDENCIES = ['insteon_plm']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {'openClosedSensor': 'opening',
'motionSensor': 'motion',
'doorSensor': 'door',
'leakSensor': 'moisture'}
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the INSTEON PLM device class for the hass platform."""
plm = hass.data['insteon_plm']
device_list = []
for device in discovery_info:
name = device.get('address')
address = device.get('address_hex')
address = discovery_info['address']
device = plm.devices[address]
state_key = discovery_info['state_key']
_LOGGER.info('Registered %s with binary_sensor platform.', name)
_LOGGER.debug('Adding device %s entity %s to Binary Sensor platform',
device.address.hex, device.states[state_key].name)
device_list.append(
InsteonPLMBinarySensorDevice(hass, plm, address, name)
)
new_entity = InsteonPLMBinarySensor(device, state_key)
async_add_devices(device_list)
async_add_devices([new_entity])
class InsteonPLMBinarySensorDevice(BinarySensorDevice):
"""A Class for an Insteon device."""
class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
"""A Class for an Insteon device entity."""
def __init__(self, hass, plm, address, name):
"""Initialize the binarysensor."""
self._hass = hass
self._plm = plm.protocol
self._address = address
self._name = name
self._plm.add_update_callback(
self.async_binarysensor_update, {'address': self._address})
def __init__(self, device, state_key):
"""Initialize the INSTEON PLM binary sensor."""
super().__init__(device, state_key)
self._sensor_type = SENSOR_TYPES.get(self._insteon_device_state.name)
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def address(self):
"""Return the address of the node."""
return self._address
@property
def name(self):
"""Return the name of the node."""
return self._name
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_type
@property
def is_on(self):
"""Return the boolean response if the node is on."""
sensorstate = self._plm.get_device_attr(self._address, 'sensorstate')
_LOGGER.info("Sensor state for %s is %s", self._address, sensorstate)
sensorstate = self._insteon_device_state.value
return bool(sensorstate)
@property
def device_state_attributes(self):
"""Provide attributes for display on device card."""
insteon_plm = get_component('insteon_plm')
return insteon_plm.common_attributes(self)
def get_attr(self, key):
"""Return specified attribute for this device."""
return self._plm.get_device_attr(self.address, key)
@callback
def async_binarysensor_update(self, message):
"""Receive notification from transport that new data exists."""
_LOGGER.info("Received update callback from PLM for %s", self._address)
self._hass.async_add_job(self.async_update_ha_state())

View File

@@ -56,24 +56,17 @@ def setup_platform(hass, config: ConfigType,
else:
device_type = _detect_device_type(node)
subnode_id = int(node.nid[-1])
if device_type == 'opening':
# Door/window sensors use an optional "negative" node
if subnode_id == 4:
if (device_type == 'opening' or device_type == 'moisture'):
# These sensors use an optional "negative" subnode 2 to snag
# all state changes
if subnode_id == 2:
parent_device.add_negative_node(node)
elif subnode_id == 4:
# Subnode 4 is the heartbeat node, which we will represent
# as a separate binary_sensor
device = ISYBinarySensorHeartbeat(node, parent_device)
parent_device.add_heartbeat_device(device)
devices.append(device)
elif subnode_id == 2:
parent_device.add_negative_node(node)
elif device_type == 'moisture':
# Moisture nodes have a subnode 2, but we ignore it because
# it's just the inverse of the primary node.
if subnode_id == 4:
# Heartbeat node
device = ISYBinarySensorHeartbeat(node, parent_device)
parent_device.add_heartbeat_device(device)
devices.append(device)
else:
# We don't yet have any special logic for other sensor types,
# so add the nodes as individual devices

View File

@@ -4,7 +4,6 @@ Support for KNX/IP binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.knx/
"""
import asyncio
import voluptuous as vol
@@ -26,6 +25,7 @@ CONF_DEFAULT_HOOK = 'on'
CONF_COUNTER = 'counter'
CONF_DEFAULT_COUNTER = 1
CONF_ACTION = 'action'
CONF_RESET_AFTER = 'reset_after'
CONF__ACTION = 'turn_off_action'
@@ -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,14 @@ 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_RESET_AFTER): cv.positive_int,
vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async 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:
@@ -85,7 +83,8 @@ def async_add_devices_config(hass, config, async_add_devices):
name=name,
group_address=config.get(CONF_ADDRESS),
device_class=config.get(CONF_DEVICE_CLASS),
significant_bit=config.get(CONF_SIGNIFICANT_BIT))
significant_bit=config.get(CONF_SIGNIFICANT_BIT),
reset_after=config.get(CONF_RESET_AFTER))
hass.data[DATA_KNX].xknx.devices.add(binary_sensor)
entity = KNXBinarySensor(hass, binary_sensor)
@@ -114,11 +113,10 @@ class KNXBinarySensor(BinarySensorDevice):
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
async def after_update_callback(device):
"""Call after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property

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,26 +48,26 @@ 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())
if device_id in rfxtrx.RFX_DEVICES:
continue
if entity[CONF_DATA_BITS] is not None:
if entity.get(CONF_DATA_BITS) is not None:
_LOGGER.debug(
"Masked device id: %s", rfxtrx.get_pt2262_deviceid(
device_id, entity[CONF_DATA_BITS]))
device_id, entity.get(CONF_DATA_BITS)))
_LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)",
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
entity[ATTR_NAME], entity.get(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

@@ -0,0 +1,38 @@
"""
Support for monitoring the state of UpCloud servers.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.upcloud/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.upcloud import (
UpCloudServerEntity, CONF_SERVERS, DATA_UPCLOUD)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['upcloud']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the UpCloud server binary sensor."""
upcloud = hass.data[DATA_UPCLOUD]
servers = config.get(CONF_SERVERS)
devices = [UpCloudBinarySensor(upcloud, uuid) for uuid in servers]
add_devices(devices, True)
class UpCloudBinarySensor(UpCloudServerEntity, BinarySensorDevice):
"""Representation of an UpCloud server sensor."""

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

@@ -319,7 +319,10 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'double'
elif value == 'both_click':
click_type = 'both'
elif value == 'shake':
click_type = 'shake'
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)
return False
self._hass.bus.fire('click', {

View File

@@ -4,7 +4,6 @@ Binary sensors on Zigbee Home Automation networks.
For more details on this platform, please refer to the documentation
at https://home-assistant.io/components/binary_sensor.zha/
"""
import asyncio
import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
@@ -25,8 +24,8 @@ CLASS_MAPPING = {
}
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Zigbee Home Automation binary sensors."""
discovery_info = zha.get_discovery_info(hass, discovery_info)
if discovery_info is None:
@@ -39,19 +38,19 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
device_class = None
cluster = in_clusters[IasZone.cluster_id]
if discovery_info['new_join']:
yield from cluster.bind()
await cluster.bind()
ieee = cluster.endpoint.device.application.ieee
yield from cluster.write_attributes({'cie_addr': ieee})
await cluster.write_attributes({'cie_addr': ieee})
try:
zone_type = yield from cluster['zone_type']
zone_type = await cluster['zone_type']
device_class = CLASS_MAPPING.get(zone_type, None)
except Exception: # pylint: disable=broad-except
# If we fail to read from the device, use a non-specific class
pass
sensor = BinarySensor(device_class, **discovery_info)
async_add_devices([sensor])
async_add_devices([sensor], update_before_add=True)
class BinarySensor(zha.Entity, BinarySensorDevice):
@@ -66,6 +65,11 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
from zigpy.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
@@ -83,7 +87,18 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
if command_id == 0:
self._state = args[0] & 3
_LOGGER.debug("Updated alarm state: %s", self._state)
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
elif command_id == 1:
_LOGGER.debug("Enroll requested")
self.hass.add_job(self._ias_zone_cluster.enroll_response(0, 0))
res = self._ias_zone_cluster.enroll_response(0, 0)
self.hass.async_add_job(res)
async def async_update(self):
"""Retrieve latest state."""
from bellows.types.basic import uint16_t
result = await zha.safe_read(self._endpoint.ias_zone,
['zone_status'])
state = result.get('zone_status', self._state)
if isinstance(state, (int, uint16_t)):
self._state = result.get('zone_status', self._state) & 3

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.4.1']
_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

@@ -264,9 +264,9 @@ class Camera(Entity):
'boundary=--frameboundary')
yield from response.prepare(request)
def write(img_bytes):
async def write(img_bytes):
"""Write image to stream."""
response.write(bytes(
await response.write(bytes(
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
@@ -282,15 +282,14 @@ class Camera(Entity):
break
if img_bytes and img_bytes != last_image:
write(img_bytes)
yield from write(img_bytes)
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
write(img_bytes)
yield from write(img_bytes)
last_image = img_bytes
yield from response.drain()
yield from asyncio.sleep(.5)

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__)
@@ -32,6 +33,25 @@ DEFAULT_PORT = 5000
DEFAULT_USERNAME = 'admin'
DEFAULT_PASSWORD = '888888'
DEFAULT_ARGUMENTS = '-q:v 2'
DEFAULT_PROFILE = 0
CONF_PROFILE = "profile"
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,
@@ -40,38 +60,108 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
vol.Optional(CONF_PROFILE, default=DEFAULT_PROFILE):
vol.All(vol.Coerce(int), vol.Range(min=0)),
})
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):
def setup_platform(hass, config, 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)
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()
self._profiles = media_service.GetProfiles()
self._profile_index = config.get(CONF_PROFILE)
if self._profile_index >= len(self._profiles):
_LOGGER.warning("ONVIF Camera '%s' doesn't provide profile %d."
" Using the last profile.",
self._name, self._profile_index)
self._profile_index = -1
req = media_service.create_type('GetStreamUri')
# pylint: disable=protected-access
req.ProfileToken = self._profiles[self._profile_index]._token
self._input = media_service.GetStreamUri(req).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

@@ -0,0 +1,262 @@
"""
Proxy camera platform that enables image processing of camera data.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/proxy
"""
import logging
import asyncio
import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.util.async import run_coroutine_threadsafe
from homeassistant.helpers import config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.const import (
CONF_NAME, CONF_ENTITY_ID, HTTP_HEADER_HA_AUTH)
from homeassistant.components.camera import (
PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web)
REQUIREMENTS = ['pillow==5.0.0']
_LOGGER = logging.getLogger(__name__)
CONF_MAX_IMAGE_WIDTH = "max_image_width"
CONF_IMAGE_QUALITY = "image_quality"
CONF_IMAGE_REFRESH_RATE = "image_refresh_rate"
CONF_FORCE_RESIZE = "force_resize"
CONF_MAX_STREAM_WIDTH = "max_stream_width"
CONF_STREAM_QUALITY = "stream_quality"
CONF_CACHE_IMAGES = "cache_images"
DEFAULT_BASENAME = "Camera Proxy"
DEFAULT_QUALITY = 75
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MAX_IMAGE_WIDTH): int,
vol.Optional(CONF_IMAGE_QUALITY): int,
vol.Optional(CONF_IMAGE_REFRESH_RATE): float,
vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean,
vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean,
vol.Optional(CONF_MAX_STREAM_WIDTH): int,
vol.Optional(CONF_STREAM_QUALITY): int,
})
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Proxy camera platform."""
async_add_devices([ProxyCamera(hass, config)])
async def _read_frame(req):
"""Read a single frame from an MJPEG stream."""
# based on https://gist.github.com/russss/1143799
import cgi
# Read in HTTP headers:
stream = req.content
# multipart/x-mixed-replace; boundary=--frameboundary
_mimetype, options = cgi.parse_header(req.headers['content-type'])
boundary = options.get('boundary').encode('utf-8')
if not boundary:
_LOGGER.error("Malformed MJPEG missing boundary")
raise Exception("Can't find content-type")
line = await stream.readline()
# Seek ahead to the first chunk
while line.strip() != boundary:
line = await stream.readline()
# Read in chunk headers
while line.strip() != b'':
parts = line.split(b':')
if len(parts) > 1 and parts[0].lower() == b'content-length':
# Grab chunk length
length = int(parts[1].strip())
line = await stream.readline()
image = await stream.read(length)
return image
def _resize_image(image, opts):
"""Resize image."""
from PIL import Image
import io
if not opts:
return image
quality = opts.quality or DEFAULT_QUALITY
new_width = opts.max_width
img = Image.open(io.BytesIO(image))
imgfmt = str(img.format)
if imgfmt != 'PNG' and imgfmt != 'JPEG':
_LOGGER.debug("Image is of unsupported type: %s", imgfmt)
return image
(old_width, old_height) = img.size
old_size = len(image)
if old_width <= new_width:
if opts.quality is None:
_LOGGER.debug("Image is smaller-than / equal-to requested width")
return image
new_width = old_width
scale = new_width / float(old_width)
new_height = int((float(old_height)*float(scale)))
img = img.resize((new_width, new_height), Image.ANTIALIAS)
imgbuf = io.BytesIO()
img.save(imgbuf, "JPEG", optimize=True, quality=quality)
newimage = imgbuf.getvalue()
if not opts.force_resize and len(newimage) >= old_size:
_LOGGER.debug("Using original image(%d bytes) "
"because resized image (%d bytes) is not smaller",
old_size, len(newimage))
return image
_LOGGER.debug("Resized image "
"from (%dx%d - %d bytes) "
"to (%dx%d - %d bytes)",
old_width, old_height, old_size,
new_width, new_height, len(newimage))
return newimage
class ImageOpts():
"""The representation of image options."""
def __init__(self, max_width, quality, force_resize):
"""Initialize image options."""
self.max_width = max_width
self.quality = quality
self.force_resize = force_resize
def __bool__(self):
"""Bool evalution rules."""
return bool(self.max_width or self.quality)
class ProxyCamera(Camera):
"""The representation of a Proxy camera."""
def __init__(self, hass, config):
"""Initialize a proxy camera component."""
super().__init__()
self.hass = hass
self._proxied_camera = config.get(CONF_ENTITY_ID)
self._name = (
config.get(CONF_NAME) or
"{} - {}".format(DEFAULT_BASENAME, self._proxied_camera))
self._image_opts = ImageOpts(
config.get(CONF_MAX_IMAGE_WIDTH),
config.get(CONF_IMAGE_QUALITY),
config.get(CONF_FORCE_RESIZE))
self._stream_opts = ImageOpts(
config.get(CONF_MAX_STREAM_WIDTH),
config.get(CONF_STREAM_QUALITY),
True)
self._image_refresh_rate = config.get(CONF_IMAGE_REFRESH_RATE)
self._cache_images = bool(
config.get(CONF_IMAGE_REFRESH_RATE)
or config.get(CONF_CACHE_IMAGES))
self._last_image_time = 0
self._last_image = None
self._headers = (
{HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password}
if self.hass.config.api.api_password is not None
else None)
def camera_image(self):
"""Return camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
async def async_camera_image(self):
"""Return a still image response from the camera."""
now = dt_util.utcnow()
if (self._image_refresh_rate and
now < self._last_image_time + self._image_refresh_rate):
return self._last_image
self._last_image_time = now
url = "{}/api/camera_proxy/{}".format(
self.hass.config.api.base_url, self._proxied_camera)
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
response = await websession.get(url, headers=self._headers)
image = await response.read()
except asyncio.TimeoutError:
_LOGGER.error("Timeout getting camera image")
return self._last_image
except aiohttp.ClientError as err:
_LOGGER.error("Error getting new camera image: %s", err)
return self._last_image
image = await self.hass.async_add_job(
_resize_image, image, self._image_opts)
if self._cache_images:
self._last_image = image
return image
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from camera images."""
websession = async_get_clientsession(self.hass)
url = "{}/api/camera_proxy_stream/{}".format(
self.hass.config.api.base_url, self._proxied_camera)
stream_coro = websession.get(url, headers=self._headers)
if not self._stream_opts:
await async_aiohttp_proxy_web(self.hass, request, stream_coro)
return
response = aiohttp.web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--frameboundary')
await response.prepare(request)
def write(img_bytes):
"""Write image to stream."""
response.write(bytes(
'--frameboundary\r\n'
'Content-Type: {}\r\n'
'Content-Length: {}\r\n\r\n'.format(
self.content_type, len(img_bytes)),
'utf-8') + img_bytes + b'\r\n')
with async_timeout.timeout(10, loop=self.hass.loop):
req = await stream_coro
try:
while True:
image = await _read_frame(req)
if not image:
break
image = await self.hass.async_add_job(
_resize_image, image, self._stream_opts)
write(image)
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
req.close()
response = None
finally:
if response is not None:
await response.write_eof()
@property
def name(self):
"""Return the name of this camera."""
return self._name

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,27 +78,36 @@ 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
add_devices([RaspberryCamera(setup_config)])
class RaspberryCamera(Camera):
"""Representation of a Raspberry Pi camera."""

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

@@ -38,8 +38,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async def async_setup_platform(hass,
config,
async_add_devices,
discovery_info=None):
"""Set up a Yi Camera."""
_LOGGER.debug('Received configuration: %s', config)
async_add_devices([YiCamera(hass, config)], True)
@@ -107,31 +109,29 @@ class YiCamera(Camera):
self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1])
@asyncio.coroutine
def async_camera_image(self):
async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
url = yield from self.hass.async_add_job(self.get_latest_video_url)
url = await self.hass.async_add_job(self.get_latest_video_url)
if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = yield from asyncio.shield(ffmpeg.get_image(
self._last_image = await asyncio.shield(ffmpeg.get_image(
url, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
self._last_url = url
return self._last_image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
yield from stream.open_camera(
await stream.open_camera(
self._last_url, extra_cmd=self._extra_arguments)
yield from async_aiohttp_proxy_stream(
await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
await stream.close()

View File

@@ -15,7 +15,7 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
REQUIREMENTS = ['py-canary==0.4.0']
REQUIREMENTS = ['py-canary==0.4.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -237,14 +237,12 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Set up climate devices."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
yield from component.async_setup(config)
await component.async_setup(config)
@asyncio.coroutine
def async_away_mode_set_service(service):
async def async_away_mode_set_service(service):
"""Set away mode on target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -253,23 +251,22 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
if away_mode:
yield from climate.async_turn_away_mode_on()
await climate.async_turn_away_mode_on()
else:
yield from climate.async_turn_away_mode_off()
await climate.async_turn_away_mode_off()
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_AWAY_MODE, async_away_mode_set_service,
schema=SET_AWAY_MODE_SCHEMA)
@asyncio.coroutine
def async_hold_mode_set_service(service):
async def async_hold_mode_set_service(service):
"""Set hold mode on target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -277,21 +274,20 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
yield from climate.async_set_hold_mode(hold_mode)
await climate.async_set_hold_mode(hold_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_HOLD_MODE, async_hold_mode_set_service,
schema=SET_HOLD_MODE_SCHEMA)
@asyncio.coroutine
def async_aux_heat_set_service(service):
async def async_aux_heat_set_service(service):
"""Set auxiliary heater on target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -300,23 +296,22 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
if aux_heat:
yield from climate.async_turn_aux_heat_on()
await climate.async_turn_aux_heat_on()
else:
yield from climate.async_turn_aux_heat_off()
await climate.async_turn_aux_heat_off()
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_AUX_HEAT, async_aux_heat_set_service,
schema=SET_AUX_HEAT_SCHEMA)
@asyncio.coroutine
def async_temperature_set_service(service):
async def async_temperature_set_service(service):
"""Set temperature on the target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -333,21 +328,20 @@ def async_setup(hass, config):
else:
kwargs[value] = temp
yield from climate.async_set_temperature(**kwargs)
await climate.async_set_temperature(**kwargs)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_TEMPERATURE, async_temperature_set_service,
schema=SET_TEMPERATURE_SCHEMA)
@asyncio.coroutine
def async_humidity_set_service(service):
async def async_humidity_set_service(service):
"""Set humidity on the target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -355,20 +349,19 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
yield from climate.async_set_humidity(humidity)
await climate.async_set_humidity(humidity)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_HUMIDITY, async_humidity_set_service,
schema=SET_HUMIDITY_SCHEMA)
@asyncio.coroutine
def async_fan_mode_set_service(service):
async def async_fan_mode_set_service(service):
"""Set fan mode on target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -376,20 +369,19 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
yield from climate.async_set_fan_mode(fan)
await climate.async_set_fan_mode(fan)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_FAN_MODE, async_fan_mode_set_service,
schema=SET_FAN_MODE_SCHEMA)
@asyncio.coroutine
def async_operation_set_service(service):
async def async_operation_set_service(service):
"""Set operating mode on the target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -397,20 +389,19 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
yield from climate.async_set_operation_mode(operation_mode)
await climate.async_set_operation_mode(operation_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_OPERATION_MODE, async_operation_set_service,
schema=SET_OPERATION_MODE_SCHEMA)
@asyncio.coroutine
def async_swing_set_service(service):
async def async_swing_set_service(service):
"""Set swing mode on the target climate devices."""
target_climate = component.async_extract_from_service(service)
@@ -418,36 +409,35 @@ def async_setup(hass, config):
update_tasks = []
for climate in target_climate:
yield from climate.async_set_swing_mode(swing_mode)
await climate.async_set_swing_mode(swing_mode)
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SET_SWING_MODE, async_swing_set_service,
schema=SET_SWING_MODE_SCHEMA)
@asyncio.coroutine
def async_on_off_service(service):
async def async_on_off_service(service):
"""Handle on/off calls."""
target_climate = component.async_extract_from_service(service)
update_tasks = []
for climate in target_climate:
if service.service == SERVICE_TURN_ON:
yield from climate.async_turn_on()
await climate.async_turn_on()
elif service.service == SERVICE_TURN_OFF:
yield from climate.async_turn_off()
await climate.async_turn_off()
if not climate.should_poll:
continue
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_TURN_OFF, async_on_off_service,
@@ -669,16 +659,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

@@ -4,7 +4,6 @@ Support for KNX/IP climate devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.knx/
"""
import asyncio
import voluptuous as vol
@@ -48,9 +47,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,
@@ -61,12 +60,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async 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:
@@ -138,11 +134,10 @@ class KNXClimate(ClimateDevice):
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
async def after_update_callback(device):
"""Call after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
@@ -190,14 +185,13 @@ class KNXClimate(ClimateDevice):
"""Return the maximum temperature."""
return self.device.target_temperature_max
@asyncio.coroutine
def async_set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
yield from self.device.set_target_temperature(temperature)
yield from self.async_update_ha_state()
await self.device.set_target_temperature(temperature)
await self.async_update_ha_state()
@property
def current_operation(self):
@@ -213,10 +207,9 @@ class KNXClimate(ClimateDevice):
operation_mode in
self.device.get_supported_operation_modes()]
@asyncio.coroutine
def async_set_operation_mode(self, operation_mode):
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
if self.device.supports_operation_mode:
from xknx.knx import HVACOperationMode
knx_operation_mode = HVACOperationMode(operation_mode)
yield from self.device.set_operation_mode(knx_operation_mode)
await self.device.set_operation_mode(knx_operation_mode)

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

@@ -29,10 +29,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
NEST_MODE_HEAT_COOL = 'heat-cool'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE | SUPPORT_FAN_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest thermostat."""
@@ -58,6 +54,10 @@ class NestThermostat(ClimateDevice):
self.device = device
self._fan_list = [STATE_ON, STATE_AUTO]
# Set the default supported features
self._support_flags = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE)
# Not all nest devices support cooling and heating remove unused
self._operation_list = [STATE_OFF]
@@ -70,11 +70,16 @@ class NestThermostat(ClimateDevice):
if self.device.can_heat and self.device.can_cool:
self._operation_list.append(STATE_AUTO)
self._support_flags = (self._support_flags |
SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
self._operation_list.append(STATE_ECO)
# feature of device
self._has_fan = self.device.has_fan
if self._has_fan:
self._support_flags = (self._support_flags | SUPPORT_FAN_MODE)
# data attributes
self._away = None
@@ -95,7 +100,7 @@ class NestThermostat(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return self._support_flags
@property
def unique_id(self):
@@ -162,6 +167,7 @@ class NestThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
import nest
target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW)
target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if self._mode == NEST_MODE_HEAT_COOL:
@@ -170,7 +176,10 @@ class NestThermostat(ClimateDevice):
else:
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
self.device.target = temp
try:
self.device.target = temp
except nest.nest.APIError:
_LOGGER.error("An error occured while setting the temperature")
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
@@ -205,11 +214,14 @@ class NestThermostat(ClimateDevice):
@property
def fan_list(self):
"""List of available fan modes."""
return self._fan_list
if self._has_fan:
return self._fan_list
return None
def set_fan_mode(self, fan):
def set_fan_mode(self, fan_mode):
"""Turn fan on/off."""
self.device.fan = fan.lower()
if self._has_fan:
self.device.fan = fan_mode.lower()
@property
def min_temp(self):
@@ -225,7 +237,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

@@ -29,7 +29,7 @@ REQUIREMENTS = ['pysensibo==1.0.2']
_LOGGER = logging.getLogger(__name__)
ALL = 'all'
ALL = ['all']
TIMEOUT = 10
SERVICE_ASSUME_STATE = 'sensibo_assume_state'
@@ -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

@@ -15,12 +15,12 @@ import async_timeout
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE)
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME)
from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import smart_home as ga_sh
from homeassistant.components.google_assistant import helpers as ga_h
from . import http_api, iot
from .const import CONFIG_DIR, DOMAIN, SERVERS
@@ -51,15 +51,11 @@ ALEXA_ENTITY_SCHEMA = vol.Schema({
GOOGLE_ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE): vol.In(ga_sh.MAPPING_COMPONENT),
vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string])
})
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({
@@ -178,7 +174,7 @@ class Cloud:
"""If an entity should be exposed."""
return conf['filter'](entity.entity_id)
self._gactions_config = ga_sh.Config(
self._gactions_config = ga_h.Config(
should_expose=should_expose,
agent_user_id=self.claims['cognito:username'],
entity_config=conf.get(CONF_ENTITY_CONFIG),
@@ -222,7 +218,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):
@@ -20,17 +17,11 @@ class UserNotConfirmed(CloudError):
"""Raised when a user has not confirmed email yet."""
class ExpiredCode(CloudError):
"""Raised when an expired code is encountered."""
class InvalidCode(CloudError):
"""Raised when an invalid code is submitted."""
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)
@@ -43,10 +34,8 @@ class UnknownError(CloudError):
AWS_EXCEPTIONS = {
'UserNotFoundException': UserNotFound,
'NotAuthorizedException': Unauthenticated,
'ExpiredCodeException': ExpiredCode,
'UserNotConfirmedException': UserNotConfirmed,
'PasswordResetRequiredException': PasswordChangeRequired,
'CodeMismatchException': InvalidCode,
}
@@ -70,17 +59,6 @@ def register(cloud, email, password):
raise _map_aws_exception(err)
def confirm_register(cloud, confirmation_code, email):
"""Confirm confirmation code after registration."""
from botocore.exceptions import ClientError
cognito = _cognito(cloud)
try:
cognito.confirm_sign_up(confirmation_code, email)
except ClientError as err:
raise _map_aws_exception(err)
def resend_email_confirm(cloud, email):
"""Resend email confirmation."""
from botocore.exceptions import ClientError
@@ -108,18 +86,6 @@ def forgot_password(cloud, email):
raise _map_aws_exception(err)
def confirm_forgot_password(cloud, confirmation_code, email, new_password):
"""Confirm forgotten password code and change password."""
from botocore.exceptions import ClientError
cognito = _cognito(cloud, username=email)
try:
cognito.confirm_forgot_password(confirmation_code, new_password)
except ClientError as err:
raise _map_aws_exception(err)
def login(cloud, email, password):
"""Log user in and fetch certificate."""
cognito = _authenticate(cloud, email, password)

View File

@@ -16,3 +16,9 @@ MESSAGE_EXPIRATION = """
It looks like your Home Assistant Cloud subscription has expired. Please check
your [account page](/config/cloud/account) to continue using the service.
"""
MESSAGE_AUTH_FAIL = """
You have been logged out of Home Assistant Cloud because we have been unable
to verify your credentials. Please [log in](/config/cloud) again to continue
using the service.
"""

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
@@ -22,10 +23,8 @@ def async_setup(hass):
hass.http.register_view(CloudLogoutView)
hass.http.register_view(CloudAccountView)
hass.http.register_view(CloudRegisterView)
hass.http.register_view(CloudConfirmRegisterView)
hass.http.register_view(CloudResendConfirmView)
hass.http.register_view(CloudForgotPasswordView)
hass.http.register_view(CloudConfirmForgotPasswordView)
_CLOUD_ERRORS = {
@@ -33,8 +32,6 @@ _CLOUD_ERRORS = {
auth_api.UserNotConfirmed: (400, 'Email not confirmed.'),
auth_api.Unauthenticated: (401, 'Authentication failed.'),
auth_api.PasswordChangeRequired: (400, 'Password change required.'),
auth_api.ExpiredCode: (400, 'Confirmation code has expired.'),
auth_api.InvalidCode: (400, 'Invalid confirmation code.'),
asyncio.TimeoutError: (502, 'Unable to reach the Home Assistant cloud.')
}
@@ -148,31 +145,6 @@ class CloudRegisterView(HomeAssistantView):
return self.json_message('ok')
class CloudConfirmRegisterView(HomeAssistantView):
"""Confirm registration on the Home Assistant cloud."""
url = '/api/cloud/confirm_register'
name = 'api:cloud:confirm_register'
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('confirmation_code'): str,
vol.Required('email'): str,
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle registration confirmation request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.confirm_register, cloud, data['confirmation_code'],
data['email'])
return self.json_message('ok')
class CloudResendConfirmView(HomeAssistantView):
"""Resend email confirmation code."""
@@ -219,33 +191,6 @@ class CloudForgotPasswordView(HomeAssistantView):
return self.json_message('ok')
class CloudConfirmForgotPasswordView(HomeAssistantView):
"""View to finish Forgot Password flow.."""
url = '/api/cloud/confirm_forgot_password'
name = 'api:cloud:confirm_forgot_password'
@_handle_cloud_errors
@RequestDataValidator(vol.Schema({
vol.Required('confirmation_code'): str,
vol.Required('email'): str,
vol.Required('new_password'): vol.All(str, vol.Length(min=6))
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle forgot password confirm request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
auth_api.confirm_forgot_password, cloud,
data['confirmation_code'], data['email'],
data['new_password'])
return self.json_message('ok')
def _account_data(cloud):
"""Generate the auth data JSON response."""
claims = cloud.claims

View File

@@ -1,6 +1,7 @@
"""Module to handle messages from Home Assistant cloud."""
import asyncio
import logging
import pprint
from aiohttp import hdrs, client_exceptions, WSMsgType
@@ -10,7 +11,7 @@ from homeassistant.components.google_assistant import smart_home as ga
from homeassistant.util.decorator import Registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import auth_api
from .const import MESSAGE_EXPIRATION
from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
@@ -44,20 +45,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 +60,70 @@ 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 2^tries seconds between retries
self.retry_task = hass.async_add_job(asyncio.sleep(
2**min(9, self.tries), 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.Unauthenticated as err:
_LOGGER.error('Unable to refresh token: %s', err)
hass.components.persistent_notification.async_create(
MESSAGE_AUTH_FAIL, 'Home Assistant Cloud',
'cloud_subscription_expired')
# Don't await it because it will cancel this task
hass.async_add_job(self.cloud.logout())
return
except auth_api.CloudError as err:
_LOGGER.warning("Unable to refresh token: %s", err)
return
if self.cloud.subscription_expired:
hass.components.persistent_notification.async_create(
MESSAGE_EXPIRATION, 'Home Assistant Cloud',
'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 +137,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:
@@ -106,7 +155,9 @@ class CloudIoT:
disconnect_warn = 'Received invalid JSON.'
break
_LOGGER.debug("Received message: %s", msg)
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Received message:\n%s\n",
pprint.pformat(msg))
response = {
'msgid': msg['msgid'],
@@ -128,12 +179,11 @@ class CloudIoT:
_LOGGER.exception("Error handling message")
response['error'] = 'exception'
_LOGGER.debug("Publishing message: %s", response)
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Publishing message:\n%s\n",
pprint.pformat(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 +195,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,7 +14,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = ['coinbase==2.0.7']
REQUIREMENTS = ['coinbase==2.1.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -13,16 +13,25 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script')
ON_DEMAND = ('zwave')
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script',
'entity_registry')
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 +160,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])
@RequestDataValidator(vol.Schema({
vol.Required('domain'): str,
}))
@asyncio.coroutine
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)
@RequestDataValidator(vol.Schema(dict), allow_empty=True)
@asyncio.coroutine
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.flow.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,55 @@
"""HTTP views to interact with the entity registry."""
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.helpers.entity_registry import async_get_registry
async def async_setup(hass):
"""Enable the Entity Registry views."""
hass.http.register_view(ConfigManagerEntityView)
return True
class ConfigManagerEntityView(HomeAssistantView):
"""View to interact with an entity registry entry."""
url = '/api/config/entity_registry/{entity_id}'
name = 'api:config:entity_registry:entity'
async def get(self, request, entity_id):
"""Get the entity registry settings for an entity."""
hass = request.app['hass']
registry = await async_get_registry(hass)
entry = registry.entities.get(entity_id)
if entry is None:
return self.json_message('Entry not found', 404)
return self.json(_entry_dict(entry))
@RequestDataValidator(vol.Schema({
# If passed in, we update value. Passing None will remove old value.
vol.Optional('name'): vol.Any(str, None),
}))
async def post(self, request, entity_id, data):
"""Update the entity registry settings for an entity."""
hass = request.app['hass']
registry = await async_get_registry(hass)
if entity_id not in registry.entities:
return self.json_message('Entry not found', 404)
entry = registry.async_update_entity(entity_id, **data)
return self.json(_entry_dict(entry))
@callback
def _entry_dict(entry):
"""Helper to convert entry to API format."""
return {
'entity_id': entry.entity_id,
'name': entry.name
}

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

@@ -4,22 +4,19 @@ Support for functionality to have conversations with Home Assistant.
For more details about this component, please refer to the documentation at
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 +25,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 +44,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.
@@ -72,11 +66,8 @@ def async_register(hass, intent_type, utterances):
conf.append(_create_matcher(utterance))
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Register the process service."""
warnings.filterwarnings('ignore', module='fuzzywuzzy')
config = config.get(DOMAIN, {})
intents = hass.data.get(DOMAIN)
@@ -91,49 +82,73 @@ def async_setup(hass, config):
conf.extend(_create_matcher(utterance) for utterance in utterances)
@asyncio.coroutine
def process(service):
async def process(service):
"""Parse text into commands."""
text = service.data[ATTR_TEXT]
yield from _process(hass, text)
try:
await _process(hass, text)
except intent.IntentHandleError as err:
_LOGGER.error('Error processing %s: %s', text, err)
hass.services.async_register(
DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA)
hass.http.register_view(ConversationProcessView)
hass.helpers.intent.async_register(TurnOnIntent())
hass.helpers.intent.async_register(TurnOffIntent())
async_register(hass, INTENT_TURN_ON,
['Turn {name} on', 'Turn on {name}'])
async_register(hass, INTENT_TURN_OFF, [
'Turn {name} off', 'Turn off {name}'])
# We strip trailing 's' from name because our state matcher will fail
# if a letter is not there. By removing 's' we can match singular and
# plural names.
async_register(hass, intent.INTENT_TURN_ON, [
'Turn [the] [a] {name}[s] on',
'Turn on [the] [a] [an] {name}[s]',
])
async_register(hass, intent.INTENT_TURN_OFF, [
'Turn [the] [a] [an] {name}[s] off',
'Turn off [the] [a] [an] {name}[s]',
])
async_register(hass, intent.INTENT_TOGGLE, [
'Toggle [the] [a] [an] {name}[s]',
'[the] [a] [an] {name}[s] toggle',
])
return True
def _create_matcher(utterance):
"""Create a regex that matches the utterance."""
parts = re.split(r'({\w+})', utterance)
# Split utterance into parts that are type: NORMAL, GROUP or OPTIONAL
# Pattern matches (GROUP|OPTIONAL): Change light to [the color] {name}
parts = re.split(r'({\w+}|\[[\w\s]+\] *)', utterance)
# Pattern to extract name from GROUP part. Matches {name}
group_matcher = re.compile(r'{(\w+)}')
# Pattern to extract text from OPTIONAL part. Matches [the color]
optional_matcher = re.compile(r'\[([\w ]+)\] *')
pattern = ['^']
for part in parts:
match = group_matcher.match(part)
group_match = group_matcher.match(part)
optional_match = optional_matcher.match(part)
if match is None:
# Normal part
if group_match is None and optional_match is None:
pattern.append(part)
continue
pattern.append('(?P<{}>{})'.format(match.groups()[0], r'[\w ]+'))
# Group part
if group_match is not None:
pattern.append(
r'(?P<{}>[\w ]+?)\s*'.format(group_match.groups()[0]))
# Optional part
elif optional_match is not None:
pattern.append(r'(?:{} *)?'.format(optional_match.groups()[0]))
pattern.append('$')
return re.compile(''.join(pattern), re.I)
@asyncio.coroutine
def _process(hass, text):
async def _process(hass, text):
"""Process a line of text."""
intents = hass.data.get(DOMAIN, {})
@@ -144,101 +159,31 @@ def _process(hass, text):
if not match:
continue
response = yield from hass.helpers.intent.async_handle(
response = await hass.helpers.intent.async_handle(
DOMAIN, intent_type,
{key: {'value': value} for key, value
in match.groupdict().items()}, 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
def post(self, request, data):
async def post(self, request, data):
"""Send a request for processing."""
hass = request.app['hass']
intent_result = yield from _process(hass, data['text'])
try:
intent_result = await _process(hass, data['text'])
except intent.IntentHandleError as err:
intent_result = intent.IntentResponse()
intent_result.async_set_speech(str(err))
if intent_result is None:
intent_result = intent.IntentResponse()

View File

@@ -150,16 +150,14 @@ def stop_cover_tilt(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_STOP_COVER_TILT, data)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Track states and offer events for covers."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS)
yield from component.async_setup(config)
await component.async_setup(config)
@asyncio.coroutine
def async_handle_cover_service(service):
async def async_handle_cover_service(service):
"""Handle calls to the cover services."""
covers = component.async_extract_from_service(service)
method = SERVICE_TO_METHOD.get(service.service)
@@ -169,13 +167,13 @@ def async_setup(hass, config):
# call method
update_tasks = []
for cover in covers:
yield from getattr(cover, method['method'])(**params)
await getattr(cover, method['method'])(**params)
if not cover.should_poll:
continue
update_tasks.append(cover.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
for service_name in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service_name].get(

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

@@ -4,7 +4,6 @@ Support for KNX/IP covers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.knx/
"""
import asyncio
import voluptuous as vol
@@ -50,12 +49,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async 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:
@@ -109,11 +105,10 @@ class KNXCover(CoverDevice):
@callback
def async_register_callbacks(self):
"""Register callbacks to update hass after device was changed."""
@asyncio.coroutine
def after_update_callback(device):
async def after_update_callback(device):
"""Call after device was updated."""
# pylint: disable=unused-argument
yield from self.async_update_ha_state()
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
@property
@@ -150,32 +145,28 @@ class KNXCover(CoverDevice):
"""Return if the cover is closed."""
return self.device.is_closed()
@asyncio.coroutine
def async_close_cover(self, **kwargs):
async def async_close_cover(self, **kwargs):
"""Close the cover."""
if not self.device.is_closed():
yield from self.device.set_down()
await self.device.set_down()
self.start_auto_updater()
@asyncio.coroutine
def async_open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs):
"""Open the cover."""
if not self.device.is_open():
yield from self.device.set_up()
await self.device.set_up()
self.start_auto_updater()
@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
yield from self.device.set_position(position)
await self.device.set_position(position)
self.start_auto_updater()
@asyncio.coroutine
def async_stop_cover(self, **kwargs):
async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
yield from self.device.stop()
await self.device.stop()
self.stop_auto_updater()
@property
@@ -185,12 +176,11 @@ class KNXCover(CoverDevice):
return None
return self.device.current_angle()
@asyncio.coroutine
def async_set_cover_tilt_position(self, **kwargs):
async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
if ATTR_TILT_POSITION in kwargs:
tilt_position = kwargs[ATTR_TILT_POSITION]
yield from self.device.set_angle(tilt_position)
await self.device.set_angle(tilt_position)
def start_auto_updater(self):
"""Start the autoupdater to update HASS while cover is moving."""

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,

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