Compare commits

..

464 Commits

Author SHA1 Message Date
Paulus Schoutsen
a507ed0af8 Version bump to 0.66.0.beta1 2018-03-25 18:24:16 -07:00
Beat
068b037944 Fix encoding errors in mikrotik device tracker (#13464) 2018-03-25 18:23:32 -07:00
Cedric Van Goethem
38d2702e3c Add extra check for ESSID field in case there's a wired connection (#13459) 2018-03-25 18:23:32 -07:00
Paulus Schoutsen
93b9ec0b0f Add version bump script (#13447)
* Add version bump script

* Lint
2018-03-25 18:23:31 -07:00
Anders Melchiorsen
22cefc7e64 Improve detection of entity names in templates (#13432)
* Improve detection of entity names in templates

* Only test variables
2018-03-25 18:23:31 -07:00
cdce8p
60f6109cbf HomeKit: Bugfix & improved logging (#13431)
* Bugfix & improved logging

* Removed logging statements

* Removed logging test
2018-03-25 18:23:30 -07:00
a-andre
24d299e266 Hyperion: fix typo (#13429) 2018-03-25 18:23:30 -07:00
Anders Melchiorsen
444805df10 LimitlessLED hs_color fixes (#13425) 2018-03-25 18:23:30 -07:00
Anders Melchiorsen
a08293cff7 Log invalid templates in script delays (#13423)
* Log invalid templates in script delays

* Abort on error

* Remove unused import
2018-03-25 18:23:29 -07:00
Patrick Hofmann
0d48a8eec6 Security fix & lock for HomeMatic (#11980)
* HomeMatic KeyMatic device become a real lock component

* Adds supported features to lock component.

Locks may are capable to open the door latch.
If component is support it, the SUPPORT_OPENING bitmask can be supplied in the supported_features property.

* hound improvements.

* Travis improvements.

* Improvements from review process

* Simplifies is_locked method

* Adds an openable lock in the lock demo component

* removes blank line

* Adds test for openable demo lock and lint and reviewer improvements.

* adds new line...

* Comment end with a period.

* Additional blank line.

* Mock service based testing, lint fixes

* Update description
2018-03-25 18:23:29 -07:00
Paulus Schoutsen
8a204fd15b Bump frontend to 20180326.0 2018-03-25 18:10:59 -07:00
Paulus Schoutsen
8e14e803cb Fix release script 2018-03-23 14:27:21 -07:00
Paulus Schoutsen
101b39300b Version bump to 0.66.0.beta0 2018-03-23 14:17:36 -07:00
Paulus Schoutsen
725e1ddfc1 Update translations 2018-03-23 14:15:44 -07:00
Otto Winter
6a625bdb37 Cast Integration Cleanup (#13275)
* Cast Integration Cleanup

* Fix long line

* Fixes and logging

* Fix tests

* Lint

* Report unknown state with None

* Lint

* Switch to async_add_job

Gets rid of those pesky "Setup of platform cast is taking over 10 seconds." messages.

* Re-introduce PlatformNotReady

* Add tests

* Remove unnecessary checks

* Test PlatformNotReady

* Fix async in sync context

* Blocking update

It's not using async anyway

* Upgrade pychromecast to 2.1.0

* Make reviewing easier

I like "protected" access, but I like reviewing more :)

* Make reviewing even easier :)

* Comment tests
2018-03-23 14:02:52 -07:00
Matt Hamrick
630734ca15 Switched values to downcase. (#13406) 2018-03-23 13:54:36 -07:00
cdce8p
7fd687f59c Fix current_cover_position (#13135) 2018-03-23 13:54:19 -07:00
Paulus Schoutsen
4bd6776443 Google assistant sync (#13392)
* Add Google Assistant Sync API

* Update const.py

* Async/await
2018-03-23 12:13:52 -07:00
Sebastian Muszynski
2532d67b9a Add send sticker service to telegram bot (#13387)
* Add send sticker service to telegram bot

* A caption is not supported
2018-03-23 19:16:57 +01:00
Mattias Welponer
df8596e896 Cleanup homematicip_cloud (#13356)
* Cleanup and proposed changes from MartinHjelmare

* Removed coroutine decorator from async_added_to_hass

* Added blank line

* Fix of component url

* Fix of component url

* Fix url of the sensor component
2018-03-23 11:05:02 -07:00
Adam Mills
2497dd5e33 Hue: Use the currently active color mode (#13376)
* Hue: Use the currently active color mode

* Round hue/sat colors before reporting to API

* .gitignore cache fix
2018-03-23 11:01:40 -07:00
cdce8p
553920780f Added default return value for HS_Color (#13395) 2018-03-23 10:22:48 -07:00
Jerad Meisner
8852e52601 Switched to async/await. Bumped pyxeoma version (#13404) 2018-03-23 10:22:01 -07:00
Martin Hjelmare
23165cbd1a Enhance mysensors binary sensor device classes (#13367) 2018-03-23 18:11:53 +01:00
Philip Rosenberg-Watt
23f06b0040 Cache LaMetric devices for offline use (#13379)
If the connection to the LaMetric server fails, we should still be able
to send notifications to known and reachable devices.
2018-03-23 18:10:52 +01:00
Anders Melchiorsen
5ec6f25d4e Fix Sonos playing Sveriges Radio (#13401) 2018-03-23 18:09:17 +01:00
cgtobi
79c9d3ba10 Fix incorrect unit of measurement for precip_intensity. (#13415) 2018-03-23 18:08:16 +01:00
Martin Hjelmare
ba7178dc0c Enhance mysensors sensor units and icons (#13365) 2018-03-23 18:06:07 +01:00
Erik Eriksson
2c7bc6eaf8 Support setting icon when configuring MQTT entity (#13304) 2018-03-23 11:30:44 +01:00
Gerard
c50b00226c Avoid breaking change for BMW ConnectedDrive sensors in #12591 (#13380) 2018-03-23 07:32:33 +01:00
Paulus Schoutsen
fb1fafefab Include all config flow translations with backend translations (#13394) 2018-03-22 12:21:33 -07:00
Jeroen ter Heerdt
98620d8ce8 Fixing Egardia 'home armed' state not shown correctly. (#13335)
* Fixing Egardia 'home armed' state not shown correctly.

* Updating requirements_all.

* Adding DEPEDENCY list to Egardia components.

* updating requirements_all
2018-03-22 10:53:52 -07:00
Paulus Schoutsen
d385e9645d Merge remote-tracking branch 'origin/master' into dev 2018-03-22 10:20:35 -07:00
cgtobi
e9cdbe5d8c Add language parameter to darksky sensor (#13297) 2018-03-22 13:34:02 +01:00
Paulus Schoutsen
6e75c5427c Update frontend to 20180322.0 2018-03-21 21:22:16 -07:00
Diogo Gomes
1676df6a5f Mediaroom async (#13321)
* Initial commit on new asyncio based version

* Async version

* Lint

* updated to lasted pymediaroom version

* bump version

* optimistic state updates

* bump version

* Moved class import to method import

* async schedule and name correction

* Addresses @balloob comments

* missed fixes

* no unique_id for configuration based STB

* hound

* handle 2 mediaroom platforms
2018-03-21 18:06:41 -07:00
Martin Hjelmare
17cbd0f3c9 Add watt to mysensors switch attributes (#13370) 2018-03-21 23:55:49 +01:00
Martin Hjelmare
2d7d8848cb Fix mysensors RGBW (#13364)
Tuple doesn't have append method.
2018-03-21 23:48:50 +01:00
Thomas Svedberg
3939460814 Add the possibility to filter on line(s) in Västtrafik Public Transport sensor (#13317)
* Add the possibility to filter on line(s) in Västtrafik Public Transport sensor

Add a config entry "lines" to be able to filter departures on line(s) for the
vasttrafik sensor.

* Change log level to debug if no departures found.

* Remove extra None argument from dict().get() calls as it is already the default.

* Ensure "lines" is a list of strings. Also fix an indentation error.

* Correct to long line
2018-03-21 15:21:51 -07:00
maxclaey
36bc7f8175 Configuration options for IFTTT alarm control panel (#13352)
* Improvements

* Use optimistic instead of await callback

* Fix default value for optimistic
2018-03-21 12:29:58 -07:00
maxclaey
0396725fe9 Homekit Bugfix: Use get instead of indexing (#13353)
Fixes bug for alarm_control_panel if not code is required.
2018-03-21 19:06:46 +01:00
Paulus Schoutsen
c3974e540b Merge pull request #13369 from home-assistant/release-0-65-6
0.65.6
2018-03-21 10:59:58 -07:00
Scott Reston
74c249e57d Fix retrieval of track URL into medi_content_id (#13333)
'current.item' was returning blank.
2018-03-21 10:44:53 -07:00
Paulus Schoutsen
da4e630f54 Fix tests 2018-03-21 10:38:06 -07:00
Paulus Schoutsen
ffbafa687a Do not include unavailable entities in Google Assistant SYNC (#13358) 2018-03-21 10:14:56 -07:00
Anders Melchiorsen
7718f70c5f Fix Sonos radio stations with ampersand (#13293) 2018-03-21 10:14:56 -07:00
Anders Melchiorsen
0de2681783 Fix Sonos join/unjoin in scripts (#13248) 2018-03-21 10:14:55 -07:00
Paulus Schoutsen
7e08e8bd51 Tado: don't reference unset hass var (#13237)
Tado: don't reference unset hass var
2018-03-21 10:14:54 -07:00
PhracturedBlue
2388d62755 More robust MJPEG parser. Fixes #13138. (#13226)
* More robust MJPEG parser. Fixes ##13138.

* Reimplement image extraction from mjpeg without ascy generator to support python 3.5
2018-03-21 10:14:54 -07:00
Paulus Schoutsen
fab958d789 Version bump to 0.65.6 2018-03-21 10:14:28 -07:00
Anders Melchiorsen
f8127a3902 Add a polling fallback for Sonos (#13310)
* Prepare for poll

* Add a polling fallback for Sonos
2018-03-20 19:27:07 -07:00
Martin Hjelmare
3426487277 Fix mysensors light turn on hs color (#13349) 2018-03-20 18:26:56 -07:00
Paulus Schoutsen
cfb0b00c0c Do not include unavailable entities in Google Assistant SYNC (#13358) 2018-03-20 18:09:34 -07:00
Anders Melchiorsen
852eef8046 Fix Sonos radio stations with ampersand (#13293) 2018-03-20 18:05:03 -07:00
Daniel Perna
05c9c57500 Update pyhomematic to 0.1.40 (#13354)
* Update __init__.py

* Update requirements_all.txt
2018-03-20 20:32:59 +01:00
John Arild Berentsen
5c4529d044 Bugfix: Zwave set_config_parameter failed when config list contained int (#13301)
* Cast list and bool options to STR

* Add button options to STR

* Add TYPE_BUTTON to value types

* Adjust comparison

* Remove Logging

* Remove Empty line

* Update tests

* Update tests

* Mistake
2018-03-20 14:04:24 +01:00
Daniel Høyer Iversen
3fa080a795 Add min and max price as attribute for Tibber sensor (#13313) 2018-03-20 08:46:10 +01:00
Sergio Viudes
0977be1842 Added switch for DoorBird second relay (#13339) 2018-03-20 08:43:31 +01:00
Johann Kellerman
4270bc7abb Perform check_config service in current process (#13017)
* Perform check_config service in current process

* feedback
2018-03-19 14:20:04 -07:00
Ryan McLean
a04c6d5830 Plex unavailable client cleanup (#13156)
* Added timestamp for when device was marked unavailable

* protect time 1st marked

* client removal, no errors

* Optional removal interval added

* Linting error fix

* Implemented guard to prevent indentation
Removed vars in favour of inline calcs

* Using hass.add_job() for cleanup

* Lint

* Revert removal interval to 600sec

* Changed datetime to hass implementation

* Forgot to include one of the references to dt
2018-03-19 14:15:21 -07:00
Per Osbäck
f287955422 zha: catch the exception from bellows if a device isn't available. (#13314)
* catch the exception from bellows if a device isn't available.

* fix import of zigpy exception

* fix lint import
2018-03-19 14:12:53 -07:00
Qxlkdr
2bc7e58780 Add trafikverket_weatherstation sensor platform (#12115)
* Create trafikverket_weatherstation.py

Created PR 12111 but due to permission issue I'm creating a new fork and PR.

* Added dot

Added dot to the first (second) row of the file, after the description.

* Corrections based on feedback

Done:
- Run flake8 before this commit
- Fixed invalid variables
- Shortened the xml variable/query via if statement (air_vs_road)
- Moved imports if update() to top of the file
- Imported CONF_API_KEY and CONF_TYPE
- Updated documentation (api_key): home-assistant/home-assistant.github.io#4562

Actions left:
- Error handling
- Request timeout
- Add sensor (file) to .coveragerc

* Multiple corrections

Done:
- Executed pylint and flake8 tests before commit
- Fixed import order
- Implemented request timeout
- Used variable air_vs_road in the return as well

Actions left:
- Error handling
- Add sensor (file) to .coveragerc

* Error handling

Done:
- Error handling for network
- Error handling for JSON response

* Added trafikverket_weatherstation.py

Added trafikverket_weatherstation.py in correct order.

* Road as default

Changed if statement to check for 'road' which means it will always defaulting to 'air' in other cases. Even if it will only accept 'air' and 'road' from the PLATFORM_SCHEMA.

* Updated variable names

Updated variable names to be more understandable as requested by @MartinHjelmare

* Standard Libraries

Grouped Standard Libraries

* Return None

Changed return None to only return as suggested.
2018-03-19 21:38:07 +01:00
Otto Winter
947218d51c pytest 3.4.0 cache gitignore (#13308) 2018-03-19 10:47:10 +01:00
Diogo Gomes
49683181d1 Superfluous None (#13326) 2018-03-18 20:42:23 -07:00
Adam Mills
89c7c80e42 Use hue/sat as internal light color interface (#11288)
* Accept and report both xy and RGB color for lights

* Fix demo light supported_features

* Add new XY color util functions

* Always make color changes available as xy and RGB

* Always expose color as RGB and XY

* Consolidate color supported_features

* Test fixes

* Additional test fix

* Use hue/sat as the hass core color interface

* Tests updates

* Assume MQTT RGB devices need full RGB brightness

* Convert new platforms

* More migration

* Use float for HS API

* Fix backwards conversion for KNX lights

* Adjust limitless min saturation for new scale
2018-03-18 15:00:29 -07:00
nielstron
6b059489a6 Adding a discoverable Samsung Syncthru Printer sensor platform (#13134)
* Added a simple component to support the BLNET

Adds a component based on pyblnet, that hooks up the blnet to home assistant

* Adds support for custimzation of blnet sensor devices

* Setting up blnet as a platfrom

* Updated use of state_attributes

Now the friendly_name (and for digital values the mode) is set in the state_attributes whereas the name is defined as "blnet_(analog|digital)_{sensornumber}" so you can reliably add them to groups.

* Added support for the SyncThru printer web service

* Added pysyncthru to the requirements

* Changed to Dependencis, import inside setup_platform

* Switch back to REQUIREMENTS

Looks like DEPENDENCIES is not meant for python packages but for other HA components

* Fixed access to _attributes

* Final fix

* Several Bugfixes

When the printer goes offline, the last state will be kept.
Also now checks if the printer is reachable upon setup

* Register syncthru as discoverable

* Included possible conditions to monitor

* Split the printer sensor in several seperate sensor entities

* Fixed bug at sensor creation, pep8 conform

* Bugfix

* Bugfix

* Removed Blnet components

* Fixed unused import

* Renamed discoverable to samsung_printer

* Removed unused Attribute _friendly_name

* Inserted missing space

* Pinned requirements and added to coveragerc

* Reduced redundancy by condensing into multiple sub-classes

* Fixed indentation

* Fixed super constructor calls

* Fixed super constructor calls

* Fixed format

* Resolving style issues and using name instead of friendly_name

* Pinned pysyncthru in requirements_all, having trouble with friendly_name

* Iterating over dictionary instead of dict.keys()

* ran gen_reqirements_all.py

* Fixed flake 8 issues

* Added a simple component to support the BLNET

Adds a component based on pyblnet, that hooks up the blnet to home assistant

* Implemented requested changes

* raised dependecies to pysyncthru version that has timeouts

* Raised required version for full timeout support

* Adds support for custimzation of blnet sensor devices

* Setting up blnet as a platfrom

* Updated use of state_attributes

Now the friendly_name (and for digital values the mode) is set in the state_attributes whereas the name is defined as "blnet_(analog|digital)_{sensornumber}" so you can reliably add them to groups.

* Added support for the SyncThru printer web service

* Added pysyncthru to the requirements

* Removed Blnet components

* Pinned requirements and added to coveragerc

* Fixed indentation

* Fixed format

* Pinned pysyncthru in requirements_all, having trouble with friendly_name

* ran gen_reqirements_all.py

* Updated requirements_all

* Renamed sensor objects, removed passing of hass entity

* Removed merge artifacts

* Reset syncthru to newest state

* Updated requirements_all

* switched to using the newest version of pysyncthru

* Sorted coveragerc
2018-03-18 09:26:33 -07:00
Erik Eriksson
1cbf9792d7 Support MQTT Lock discovery (#13303) 2018-03-18 09:26:07 -07:00
Kevin Raddatz
437ffc8337 Update plex.py (#12157)
* Update plex.py

show information about media depending if it is a movie or an episode
set time_between_scans to 10 s to match with plex media_player component

* Update plex.py

lint

* Update plex.py

linting

* Update plex.py

linting

* Update plex.py

linting

* Update plex.py

added catch  for tracks and everything else
if no release year is given, instead of () it now show nothing

* Update plex.py

Remove the album year to match with the Plex UI

* Update README.rst

* Update README.rst

* Update plex.py

reformat code to make it more readable
recorded tv shows might not have episode numbers assigned -> check before adding to title

* Update plex.py

cleanup excessive whitespace

* Update plex.py

cleanup excessive whitespace
2018-03-18 09:25:25 -07:00
Igor Bernstein
9cb3c9034f Zigbee fan (#12289)
* wip: initial control

* fix initial state

* cosmetic cleanup

* doc typo

* lint

* fixes

* fix unknown bug

* Lint
2018-03-18 09:17:56 -07:00
uchagani
1dcc51cbdf Add ecobee fan mode (#12732)
* add ability to set fan on

* add tests and change "not on" status to "auto"

* hound fix

* more hounds

* I don't understand new lines

* fix linting errors

* more linting fixes

* change method signature

* lint fixes

* hopefully last lint fix

* correct temp ranges according to ecobee API docs

* update dependency to latest version

* update tests with values from new temp logic

* fix linting issue

* more linting fixes

* add SUPPORT_FAN_MODE to capabilities

* add fan_list to attributes.
restore current fan state to OFF if fan is not running.
change target high/low temps from null to target temp when not in auto mode.
change target temp from null to high/low temp when in auto mode
change mode attribute to climate_mode for consistency with other lists.

* remove unused import

* simplify logic

* lint fixes

* revert change for target temps
2018-03-18 09:02:07 -07:00
maxclaey
022d8fb816 Support for security systems controlled by IFTTT (#12975)
* Add IFTTT alarm control panel

* Update .coveragerc

* Add support for code

* Bugfix

* Fix logging problem

* Pin requirements

* Update requirements_all.txt

* Fix lint errors

* Use ifttt component as a dependency instead of interacting with ifttt manually
Take into account review comments

* No default value for code

* Take into account review comments

* Provide a "push_alarm_state" service to change the state from IFTTT

* Add service description

* Fix @balloob review comments. Thanks!

* Fix service description name
2018-03-18 09:00:08 -07:00
Diogo Gomes
1e17b2fd63 Added Time based SMA to Filter Sensor (#13104)
* Added Time based SMA

* move "now" to _filter_state()

* Addressed comments

* fix long line

* type and name

* # pylint: disable=redefined-builtin

* added test
2018-03-18 08:58:52 -07:00
Mattias Welponer
b45dad507a Add initial support fo HomematicIP components (#12761)
* Add initial support fo HomematicIP components

* Fix module import

* Update reqirments file as well

* Added HomematicIP files

* Update to homematicip

* Code cleanup based on highligted issues

* Update of reqiremnets file as well

* Fix dispatcher usage

* Rename homematicip to homematicip_cloud
2018-03-18 08:57:53 -07:00
Fabian Affolter
8ed3024026 Upgrade async_timeout to 2.0.1 (#13290) 2018-03-17 17:37:31 -07:00
cburgess
d042b3d7d1 Update to latest python-nest (#12590)
Due to an upstream bug some devices will be assigned a where_id that is not
visible in the nest API. As a result we can't get a friendly name for the
where_id.

A workaround has been released for python-nest in version 3.7.0. Update the
home assistant requirements to python-nest==3.7.0 to work around this issue.

References:
https://nestdevelopers.io/t/missing-where-name-from-some-devices/1202
https://github.com/jkoelker/python-nest/issues/127
https://github.com/jkoelker/python-nest/pull/128

Fixes #12589
Fixes #12950
Fixes #13074
2018-03-17 17:35:16 -07:00
Pascal Vizeli
4d3743f3f7 Delete .gitmodules (#13295) 2018-03-17 19:08:52 +01:00
cgtobi
181eca4b45 Upgrade python-forecastio to 1.4.0 (#13282)
* Upgrade python-forecastio to 1.4.0

* Upgrade python-forecastio to 1.4.0 for sensor as well.
2018-03-17 17:43:07 +01:00
Fabian Affolter
dbc59ad1a7 Upgrade python-telegram-bot to 10.0.1 (#13294) 2018-03-17 17:41:10 +01:00
Fabian Affolter
82f59ba984 Upgrade numpy to 1.14.2 (#13291) 2018-03-17 17:40:31 +01:00
Fabian Affolter
d35077271d Upgrade TwitterAPI to 2.5.0 (#13287) 2018-03-17 17:40:03 +01:00
Fabian Affolter
aec61b7c86 Upgrade sqlalchemy to 1.2.5 (#13292) 2018-03-17 17:39:24 +01:00
Fabian Affolter
e01a0f91d6 Upgrade aiohttp_cors to 0.7.0 (#13289) 2018-03-17 17:37:53 +01:00
Fabian Affolter
8fed405da7 Upgrade aiohttp to 3.0.9 (#13288) 2018-03-17 17:37:09 +01:00
Otto Winter
3442b6741d Fix WUnderground duplicate entity ids (#13285)
* Fix WUnderground duplicate entity ids

* Entity Namespace
2018-03-17 13:14:53 +01:00
Otto Winter
f5093b474a Python 3.5 async with (#13283) 2018-03-17 12:27:21 +01:00
thrawnarn
05676ba18b Changed to async/await (#13246)
* Changed to async/await

* Hound fixes

* Lint fixes

* Changed sleep
2018-03-17 12:14:01 +01:00
Sebastian Muszynski
66c6f9cdd6 Unused xiaomi miio sensor method removed (#13281)
* Unused method removed.

* remove unused import
2018-03-17 11:51:40 +01:00
Paulus Schoutsen
5a9013cda5 Refactor Hue: phue -> aiohue (#13043)
* phue -> aiohue

* Clean up

* Fix config

* Address comments

* Typo

* Fix rebase error

* Mark light as unavailable when bridge is disconnected

* Tests

* Make Throttle work with double delay and async

* Rework update logic

* Don't resolve host to IP

* Clarify comment

* No longer do unnecessary updates

* Add more doc

* Another comment update

* Wrap up tests

* Lint

* Fix tests

* PyLint does not like mix 'n match async and coroutine

* Lint

* Update aiohue to 1.2

* Lint

* Fix await MagicMock
2018-03-16 20:27:05 -07:00
Paulus Schoutsen
d78e75db66 Bump frontend to 20180316.0 2018-03-16 15:39:30 -07:00
Sebastian Muszynski
d04ba3f86d Xiaomi MiIO Sensor: Xiaomi Air Quality Monitor (PM2.5) integration (#13264)
* Xiaomi MiIO Sensor: Xiaomi Air Quality Monitor (PM2.5) integration.

* Missing newline added.

* Use a unique data key per domain.

* turn_{on,off} service moved to __init__.py.

* All sensors group added.

* Sensor is a ToggleEntity now.

* is_on property added.

* Use Async / await syntax.

* Make hound happy.

* Unique id added.

* Turn on/off service removed from abstract sensor.

* Turn on/off methods removed.
Device unavailable handling improved.

* Unused import removed.

* Sensor migrated back to an entity.

* Rebased and requirements updated.
2018-03-16 22:13:04 +01:00
Sebastian Muszynski
ed6cd0ccfa Xiaomi MiIO Remote: Unique id added (#13266)
* Unique id added.

* Provide the exception as "ex"
2018-03-16 21:15:23 +01:00
Jens Timmerman
88d2a6ab80 Fix guide link in CONTRIBUTING.md (#13272) 2018-03-16 21:13:32 +01:00
Sebastian Muszynski
fe7012549e Xiaomi MiIO light: Philips Eyecare Smart Lamp 2 integration (#12883)
* Xiaomi Philips Eyecare Smart Lamp 2 support added.

* Blank lines removed.

* Pylint errors fixed.

* Abstract light introduced.

* Smart night light mode renamed.

* Use the conventional power on/off methods again.

* Eyecare mode service added.

* Eyecare mode attribute added.

* Name of the ambient light entity fixed.

* Reuse of the same local variable name within the same scope fixed.

* Use Async / await syntax.

* Missed method fixed.

* Make hound happy.

* Don't abuse the system property supported_features anymore.

* Make hound happy.

* Wrong hanging indentation fixed.
Unnecessary parens after 'return' keyword fixed.

* Refactoring.

* Additional supported features bit mask removed as long as the differences of the supported devices are simple.

* Support for Xiaomi Philips Zhirui Smart LED Bulb E14 Candle Lamp added.

* Docstrings updated.
Refactoring.

* Unique id added.

* Filter service calls. Dummy methods removed.

* Device available handling improved.

* super() used for calling the parent class

* Self removed from super().
2018-03-16 20:59:18 +01:00
Sebastian Muszynski
f013619e69 Xiaomi MiIO Switch: Power Strip support improved (#12917)
* Xiaomi MiIO Switch: Power Strip support improved.

* New service descriptions added.

* Make hound happy.

* Pylint fixed.

* Use Async / await syntax.

* Missed method fixed.

* Make hound happy.

* Don't abuse the system property supported_features anymore.

* Check the correct method.

* Refactoring.

* Make hound happy.

* pythion-miio version bumped.

* Clean-up.

* Unique id added.

* Filter service calls.
Device unavailable handling improved.
2018-03-16 19:58:03 +01:00
Teemu R
78144bc6de Use the first, not the last volume controller when multiple are available on songpal (#13222)
* use the first, not the last volume controller

* Do not mutate the list but simply pick the first by index
2018-03-16 12:14:21 +01:00
cdce8p
f6ae2d338d Homekit: Use util functions for unit conversion (#13253)
* Updated to util/color for conversion
* Updated temperature sensor to use util/temperature conversion
2018-03-16 11:38:44 +01:00
BioSehnsucht
99f7e2bd97 Added Stride notification component (#13221)
* Added Stride notification component

* Fix trailing whitespace in Stride notify

* More whitespace fixes and rogue comment for Stride notify

* More whitespace fixing for Stride notify

* Correcting hanging indents for Stride notify
2018-03-15 20:36:03 -07:00
karlkar
b1079cb493 Fix for not setting up the camera if it is offline during setup phase (#13082)
* Fix for not setting up the camera if it is offline during setup phase

* async/await and modified service creation

* Properly handle not supported PTZ

* setup platform made synchronous as ONVIFService constructors do I/O

* Fix intendation issue
2018-03-15 20:30:41 -07:00
Fabien Piuzzi
0deef34881 Adding Foobot device sensor (#12417)
* Added Foobot device sensor

* Added error handling tests

* Corrections after PR review.

* Migrated to async/await syntax

 * lint fixes
 * stop raising HomeAssistantError
 * debug log for number of requests

* Moved shared data between sensors from a class attribute to a separate class

* Made test more async-aware

disabled setup error test for now as it's not working

* Working failure scenarios tests
2018-03-15 19:50:58 -07:00
cdce8p
2350ce96a6 Homekit: New supported devices (#13244)
* Fixed log message
* Added support for scripts
* Added support for lights
* Small refactoring
* Added support for humidity sensor
* Added tests
2018-03-16 01:05:28 +01:00
Anders Melchiorsen
de1ff1e952 Fix Sonos join/unjoin in scripts (#13248) 2018-03-15 16:12:43 -07:00
Gerard
d13bcf8412 Add extra sensors for BMW ConnectedDrive (#12591)
* Added extra sensors for BMW ConnectedDrive

* Updates based on review of @MartinHjelmare

* Updates based on 2nd review of @MartinHjelmare

* Changed control flow for updates to support updates triggered by remote services.

* updated library version number

* Changed order of commands so that the UI looks consistent.

State of lock is now set optimisitcally before getting proper update
from the server. So that the state does not toggle in the UI.

* Added comment on optimistic state

* Updated requirements_all.txt

* Revert access permission changes

* Fix for Travis

* Changes based on review by @MartinHjelmare
2018-03-15 22:56:35 +01:00
Paulus Schoutsen
456ff4e84b Tado: don't reference unset hass var (#13237)
Tado: don't reference unset hass var
2018-03-15 13:53:59 -07:00
Paulus Schoutsen
89a19c89a7 Fix aiohttp deprecation warnings (#13240)
* Fix aiohttp deprecation warnings

* Fix Ring deprecation warning

* Lint
2018-03-15 21:49:49 +01:00
Juggels
a86bf81768 Fix 'dict' object has no attribute 'strftime' (#13215)
* Fix 'dict' object has no attribute 'strftime'

* Clear existing list instead of new object
2018-03-15 21:43:20 +01:00
Anders Melchiorsen
5e675677ad Cleanup Sonos platform setup (#13225)
* Cleanup Sonos platform setup

* Remove unneeded lists
2018-03-15 20:43:28 +01:00
maxlaverse
ff416c0e7a Try to fix caldav (#13236)
* Fix device attribute type for event end

* Fix is_over and add tests
2018-03-15 10:58:11 -07:00
Paulus Schoutsen
170b8671b9 Fix logbook JSON serialize issue (#13229)
* Fix logbook JSON serialize issue

* Address flakiness

* Lint

* deflake ?

* Deflake 2
2018-03-15 10:54:22 -07:00
Maximilien Cuony
ee6d6a8859 myStrom: Add RGB support to Wifi bulbs (#13194) 2018-03-15 16:45:27 +01:00
Andrei Pop
1d2fd8a2e9 Edimax component reports wrong power values (#13011)
* Fixed Edimax switch authentication error for newer firmware.

* pyedimax moved to pypi

* Added pyedimax to gen_requirements_all.py

* Cleanup

* Fixed https://github.com/home-assistant/home-assistant/issues/13008

* Only ValueError now

* Trivial error.
2018-03-15 16:27:42 +01:00
Eugene Kuzin
92f13ff60d media_content_type attribute display fix (#13204)
* media_content_type fix

Kodi media_content_type attribute display fix

* media_content_type fix (#6989)

fixes attribute display for unknown media

* code cleanup

* trailing whitespaces

* comments correction

* redundant "else:" removed
2018-03-15 08:43:29 -04:00
Clement Wong
5c434f143e Tibber use appNickname as name (#13231) 2018-03-15 13:16:52 +01:00
cdce8p
646ed5de52 Added cover.group platform (replaces #12303) (#12692)
* Added cover.group platform
* Added async/await, smaller changes
* Made (async_update) methods regular methods
* Small improvements
* Changed classname
* Changes based on feedback
* Service calls
* update_supported_features is now a callback method
* combined all 'update_attr_*' methods in 'async_update'
* Small changes
* Fixes
   * is_closed
   * current_position
   * current_tilt_position
* Updated tests
* Small changes 2
2018-03-15 12:31:31 +01:00
Otto Winter
27c1806897 Python 3.5 adjustments (#13173) 2018-03-15 13:10:54 +02:00
Fabian Affolter
6909be1cc7 Add docstring (#13232) 2018-03-15 11:45:54 +01:00
PhracturedBlue
223bc187dc More robust MJPEG parser. Fixes #13138. (#13226)
* More robust MJPEG parser. Fixes ##13138.

* Reimplement image extraction from mjpeg without ascy generator to support python 3.5
2018-03-14 21:44:13 -07:00
c727
c971d61422 Change Hass.io icon to home-assistant (#13230) 2018-03-14 20:56:56 -07:00
Kane610
e122692b46 deCONZ - Add support for consumption and power sensors (#13218)
* Add support for consumption and power sensors

* Keep attr_current inside component
2018-03-14 20:07:37 -07:00
Paulus Schoutsen
76874e1cbc Update translations 2018-03-14 19:47:31 -07:00
cdce8p
d348f09d3d HomeKit Restructure (new config options) (#12997)
* Restructure
* Pincode will now be autogenerated and display using a persistence notification
* Added 'homekit.start' service
* Added config options
* Renamed files for types
* Improved tests
* Changes (based on feedback)
* Removed CONF_PIN_CODE
* Added services.yaml
* Service will only be registered if auto_start=False
* Bugfix names, changed default port
* Generate aids with zlib.adler32
* Added entity filter, minor changes
* Small changes
2018-03-15 02:48:21 +01:00
Paulus Schoutsen
64f18c62f4 Update frontend 2018-03-14 16:39:15 -07:00
Paulus Schoutsen
be2e202618 Bump frontend to 20180315.0 2018-03-14 16:13:46 -07:00
engrbm87
07f20676cb Add notifications to downloader.py (#12961)
* Update downloader.py

Add persistent notification to alert when download is finished or in case of download failure.

* Update downloader.py

* Update downloader.py

* Update downloader.py

* Fire and event when download is requested

Added 2 events to represent download completed and download failed. This will allow the user to trigger an automation based on the status of the download.

* Update downloader.py

* Update downloader.py

replaced . with _

* Update downloader.py

fixed linting errors
2018-03-14 16:03:40 -07:00
Paulus Schoutsen
a30ca4307b Merge branch 'master' into dev 2018-03-14 15:58:55 -07:00
Paulus Schoutsen
8d52eba484 Merge pull request #13223 from home-assistant/release-0-65-5
0.65.5
2018-03-14 15:55:50 -07:00
Paulus Schoutsen
8e05a5c12b Version bump to 0.65.6 2018-03-14 15:08:34 -07:00
Paulus Schoutsen
25fe6ec536 Fix input_boolean Google Assistant serialize error (#13220) 2018-03-14 15:08:23 -07:00
Anders Melchiorsen
30a1fedce8 Avoid Sonos error when joining with self (#13196) 2018-03-14 15:08:23 -07:00
Anders Melchiorsen
4e569ac0c3 Ignore unsupported Sonos favorites (#13195) 2018-03-14 15:08:22 -07:00
Alok Saboo
8a6370f7c9 Revert throttle Arlo api calls (#13174) 2018-03-14 15:08:21 -07:00
cdce8p
874cccd530 Bugfix HomeKit: Error string values for temperature (#13162) 2018-03-14 15:08:21 -07:00
Paulus Schoutsen
e1a5e5a8ba Fix input_boolean Google Assistant serialize error (#13220) 2018-03-14 15:07:37 -07:00
Paulus Schoutsen
a9917e7a56 Fix history API (#13214) 2018-03-14 22:29:51 +01:00
Anders Melchiorsen
ef7ce5eb1b Ignore unsupported Sonos favorites (#13195) 2018-03-14 12:08:41 -07:00
Anders Melchiorsen
7fc9ac0931 Avoid Sonos error when joining with self (#13196) 2018-03-14 12:07:50 -07:00
Paulus Schoutsen
e2029e3970 Add vesync to coveragerc 2018-03-14 12:05:17 -07:00
Paulus Schoutsen
7e2fc19f5a Sort coveragerc 2018-03-14 11:39:38 -07:00
cdce8p
c48c8710b7 Bugfix HomeKit: Error string values for temperature (#13162) 2018-03-14 13:22:38 +01:00
Dan Nixon
b6bed1dfab Report swap in MiB (#13148)
It makes sense to report swap and memory in the same unit and MiB is
more useful considering Home Assistant may be running on lower end
hardware (Raspberry Pi for example) where 100MiB resolution is not
adequate.
2018-03-14 08:47:45 +01:00
Vincent Van Den Berghe
948f29544a Fixed SI units for current consumption (#13190) 2018-03-14 08:14:35 +01:00
Mark Perdue
6310deb5c2 Add new platform for VeSync switches (#13000)
* Added vesync platform
Support for power toggling, current power, and daily energy kWh

* Adds vesync to requirements file.

* Reorder vesync item in requirements_all.txt from gen_requirements_all

* Removes unnecessary global values that are not used in this component

* Removes try/catch from setup_platform -no throws. Guard check login()

* Remove unnecessary boolean convert

* Fix indentation of log messages
2018-03-14 00:10:47 -07:00
JC Connell
cfded7eab9 Python Spotcrime sensor requires API key, fixes include/exclude (#12926)
* Add spotcrime.py to dev

* Modify sensor to accept user API key

* Update Spotcrime to 1.0.3 in requirements_all.txt

* Fix line 76 (97 > 79 characters)

* Fix lint errors
2018-03-14 08:01:10 +01:00
Pascal Vizeli
0ef4340099 Fix freegeoip (#13193) 2018-03-13 20:50:08 -07:00
Jon Maddox
24a9da85c0 Channels clean ups (#12967)
* already in the default schema

* these are already globally disabled

* require a single entity id

* remove unused import

* w h i t e s p a c e

* actually keep it

* it is a string

* use a generator expression

* 💄

* Revert "💄"

This reverts commit 81c08bb732.

* Revert "actually keep it"

This reverts commit 0d92d3afb2.

* Revert "remove unused import"

This reverts commit 8a166208e4.

* Revert "already in the default schema"

This reverts commit 9173de4fd3.

* we're already ensuring defaults with the platform schema
2018-03-13 14:14:02 -07:00
Alok Saboo
71baa6532e Revert throttle Arlo api calls (#13174) 2018-03-13 14:12:28 -07:00
Otto Winter
4c9e7c2da4 Upgrade pytest to 3.4.2 (#13169)
* Upgrade pytest to 3.4.2

* Upgrade pytest-sugar to 0.9.1
2018-03-13 13:57:04 -07:00
Otto Winter
5958e6a60f Improve MQTT failed connection error message (#13184) 2018-03-13 13:56:16 -07:00
Andreas Wolter
3e7a737bff Added IPAreaThermostat and an exception-list for HM_IGNORE_DISCOVERY_NODE (#13171) 2018-03-13 21:54:09 +01:00
Ville Skyttä
dd48fb04a3 upcloud: Provide unique ID for server entities (#13181) 2018-03-13 13:51:10 -07:00
Otto Winter
d5612b5ccc Upgrade holidays to 0.9.4 (#13167) 2018-03-13 17:30:31 +01:00
Kane610
8a1687accb deConz rewrite to use async await syntax (#13151)
* Rewrite to use async await syntax

* Fix hound comment
2018-03-13 08:47:45 +01:00
Jesse Hills
53351423dd Change iglo port to cv.port validator (#13163) 2018-03-13 08:29:20 +01:00
Daniel Høyer Iversen
75fb8ef98b upgrade tibber libary to 0.4.0 to use aiohttp 3.0 (#13164) 2018-03-13 07:04:27 +01:00
Fabian Affolter
989638b266 Upgrade Sphinx to 1.7.1 (#13127) 2018-03-12 18:22:48 -07:00
Otto Winter
d028c33e7f Disable Monkey Patch for 3.6.3+ (#13150) 2018-03-12 16:12:21 -07:00
Paulus Schoutsen
0a2e949e0a Remove crazy JSON encoding things that are no longer used (#13029)
Catch JSON encoding errors in HTTP view
2018-03-12 23:22:08 +01:00
Paulus Schoutsen
1bea8747ac Merge pull request #13149 from home-assistant/release-0-65-4
0.65.4
2018-03-12 14:52:01 -07:00
Alok Saboo
e54394e906 Throttle Arlo api calls (#13143) 2018-03-12 14:14:43 -07:00
Jeroen ter Heerdt
c384fd9653 Adding check for empty discovery info in alarm control panel Egardia. (#13114) 2018-03-12 14:14:42 -07:00
Paulus Schoutsen
3560fa754c Catch if bridge goes unavailable (#13109) 2018-03-12 14:14:42 -07:00
Paulus Schoutsen
101a6ab07c Fix unavailable property for wemo switch (#13106)
* Fix unavailable property for wemo switch

* Have subscriptions respect the lock

* Move subscription callback to added to hass section
2018-03-12 14:14:42 -07:00
Paulus Schoutsen
eb1ca20cfc Version bump to 0.65.4 2018-03-12 14:14:36 -07:00
Jeroen ter Heerdt
ae286a550b Adding check for empty discovery info in alarm control panel Egardia. (#13114) 2018-03-12 14:03:05 -07:00
Fabian Affolter
c5330a13b6 Upgrade schiene to 0.22 (#13121) 2018-03-12 14:02:36 -07:00
Fabian Affolter
6ab4a408d2 Upgrade zeroconf to 0.20.0 (#13123) 2018-03-12 14:02:03 -07:00
Fabian Affolter
1202134964 Upgrade youtube_dl to 2018.03.10 (#13124) 2018-03-12 14:01:32 -07:00
Fabian Affolter
bbbb44b999 Upgrade TwitterAPI to 2.4.10 (#13126) 2018-03-12 14:01:05 -07:00
Fabian Affolter
54e0cc1304 Upgrade mypy to 0.570 (#13128) 2018-03-12 14:00:10 -07:00
Per Osbäck
f9e07e617c update to async/await (#13137) 2018-03-12 13:57:13 -07:00
Alok Saboo
95a528a75f Throttle Arlo api calls (#13143) 2018-03-12 13:56:33 -07:00
Paulus Schoutsen
51b0cbefe3 Catch if bridge goes unavailable (#13109) 2018-03-12 13:55:22 -07:00
Paulus Schoutsen
8d8b07abd5 Fix unavailable property for wemo switch (#13106)
* Fix unavailable property for wemo switch

* Have subscriptions respect the lock

* Move subscription callback to added to hass section
2018-03-12 13:54:56 -07:00
Federico Zivolo
15d345c4ef fix: Support different JointSpace API versions (#13084) 2018-03-12 12:33:04 -07:00
Fabian Affolter
676c94561b Upgrade astral to 1.6 (#13120) 2018-03-12 12:28:27 -07:00
Fabian Affolter
02ad9c3574 Upgrade aiohttp to 3.0.7 (#13119) 2018-03-12 12:26:51 -07:00
Otto Winter
890197e407 asyncio.ensure_future Python 3.5 (#13141) 2018-03-12 19:42:08 +01:00
Paulus Schoutsen
9ee123f5ce Version bump to 0.66.0.dev0 2018-03-11 13:20:47 -07:00
Adam Mills
14aa4e7694 Lint script tweaks (#13093)
* Also lint working tree files

When performing a git diff of upstream/dev..., git is diffing against
the current HEAD, but does not include working tree files. By manually
calculating a merge-base SHA to diff against, git will still diff those
files.

* Don't pylint tests files, since we don't in CI

* Use merge base for lazytox

* Simplify files changed header
2018-03-11 16:15:09 -04:00
Paulus Schoutsen
49d51e5040 Merge pull request #13098 from home-assistant/release-0-65-3
0.65.3
2018-03-11 13:14:18 -07:00
tadly
31130f902b Updated jsonrpc-websocket to 0.6 (#13096)
Fix Kodi by updating jsonrpc-websocket to 0.6
2018-03-11 12:52:02 -07:00
Greg Dowling
8603f1a047 Bump pyvera to 0.2.42. Improve event loop robustness. (#13095) 2018-03-11 12:52:01 -07:00
Otto Winter
a34786fb2d Revert "Cast automatically drop connection (#12635)" (#13094)
This reverts commit e14893416f.
2018-03-11 12:52:00 -07:00
Joe Lu
8e51c12010 Integrated with py-synology:0.2.0 which has fix to auto-renew session when it's expired (#13079) 2018-03-11 12:52:00 -07:00
Paulus Schoutsen
e3d176f479 Fix Tado doing async wrong (#13078)
* Fix Tado doing async wrong

* Remove last coroutine decorator
2018-03-11 12:51:59 -07:00
Diogo Gomes
e37619acc1 Convert decimals from SQL results 2018-03-11 12:51:59 -07:00
Jesse Hills
85fa88c8b3 - Bump iGlo Version (#13063)
- Use effect list as a method
2018-03-11 12:51:58 -07:00
Julius Mittenzwei
7935c2504e Fixes KNX fire event problem, issue https://github.com/home-assistant/home-assistant/issues/13049 (#13062) 2018-03-11 12:51:58 -07:00
Paulus Schoutsen
7018806802 Run asyncio event loop in debug mode during tests (#13058)
* Run asyncio event loop in debug mode during tests

* Remove debug mode again
2018-03-11 12:51:57 -07:00
Paulus Schoutsen
a7f34bbce9 Implement Hue available property (#12939) 2018-03-11 12:51:57 -07:00
Paulus Schoutsen
e6683b4c84 Bump version to 0.65.3 2018-03-11 12:51:40 -07:00
tadly
991c457430 Updated jsonrpc-websocket to 0.6 (#13096)
Fix Kodi by updating jsonrpc-websocket to 0.6
2018-03-11 12:46:16 -07:00
Greg Dowling
401e92f84e Bump pyvera to 0.2.42. Improve event loop robustness. (#13095) 2018-03-11 12:43:28 -07:00
Otto Winter
1dc5fa145f Revert "Cast automatically drop connection (#12635)" (#13094)
This reverts commit e14893416f.
2018-03-11 12:42:58 -07:00
Joe Lu
dff4f6ce48 Integrated with py-synology:0.2.0 which has fix to auto-renew session when it's expired (#13079) 2018-03-11 12:33:36 -07:00
Paulus Schoutsen
56b3cb0583 Fix Tado doing async wrong (#13078)
* Fix Tado doing async wrong

* Remove last coroutine decorator
2018-03-11 12:33:07 -07:00
Paulus Schoutsen
d0f089975d Run asyncio event loop in debug mode during tests (#13058)
* Run asyncio event loop in debug mode during tests

* Remove debug mode again
2018-03-11 12:32:12 -07:00
Adam Mills
26960283a0 Config flow translations (#13066)
* Development script for testing translation strings

* Localize backend of config flow

* Fix hue tests

* Update hue.en.json

* Move components to individual directories

* Bridge -> bridge
2018-03-11 12:04:34 -07:00
Matthias Urlichs
f5cc40024d Rename homeassistant.util.async to .async_ (#13088)
"async" is (going to be) a reserved word.
2018-03-11 10:01:12 -07:00
Paulus Schoutsen
d42b5a93dd Implement Hue available property (#12939) 2018-03-11 09:49:28 -07:00
Andrey
3dfc49d311 Make Sensibo climate registry_entity compliant (#13086) 2018-03-11 17:19:25 +01:00
Adam Mills
dc8424032b Remove Z-Wave old/new entity_id attributes (#12652) 2018-03-11 10:30:03 -04:00
Johann Kellerman
f164a5a65f Better errors for unknown secrets (#13072) 2018-03-11 12:51:03 +02:00
Otto Winter
d74a2b68c1 Sensor template don't exit early on TemplateError (#13041)
* Sensor template don't exit early on TemplateError

* Add friendly name unknown state test

* Also track entites from attribute templates

* Use set instead of list
2018-03-10 20:45:32 -08:00
Jesse Hills
458598546d - Bump iGlo Version (#13063)
- Use effect list as a method
2018-03-10 20:31:57 -08:00
Julius Mittenzwei
3f6d30ed06 Fixes KNX fire event problem, issue https://github.com/home-assistant/home-assistant/issues/13049 (#13062) 2018-03-10 20:26:21 -08:00
Diogo Gomes
28ff1f7ac2 Convert decimals from SQL results 2018-03-11 00:27:58 +02:00
Paulus Schoutsen
60aacff827 Merge pull request #13061 from home-assistant/release-0-65-2
0.65.2
2018-03-10 11:11:02 -08:00
Paulus Schoutsen
42359b3b48 Convert decimals from SQL results (#13059) 2018-03-10 10:41:41 -08:00
Paulus Schoutsen
b98d2e2485 Don't call async from sync (#13057) 2018-03-10 10:41:40 -08:00
Sebastian Muszynski
5281892e69 Yeelight version bumped. (#13056) 2018-03-10 10:41:40 -08:00
Anders Melchiorsen
70064e4c69 Fix async lifx_set_state (#13045) 2018-03-10 10:41:40 -08:00
Jerad Meisner
b2d8feb979 Bump pysabnzbd version (#13042) 2018-03-10 10:41:39 -08:00
Johann Kellerman
9a7a0f28b1 Ensure we have valid config AFTER merging packages #13015 (#13038)
* Ensure we have valid config AFTER merging packages #13015

* also fix packages
2018-03-10 10:41:39 -08:00
Paul Tarjan
c7c47a18a9 Use request.query (#13037)
Fixes #13036
2018-03-10 10:41:38 -08:00
John Allen
5726159dd4 Fix sensibo's min/max_temp properties (#12996)
The super class has these as properties, not regular methods
2018-03-10 10:41:38 -08:00
Paulus Schoutsen
0e00de8a33 Convert decimals from SQL results (#13059) 2018-03-10 10:40:28 -08:00
Paulus Schoutsen
266b13b3cb Version bump to 0.65.2 2018-03-10 10:20:30 -08:00
Paulus Schoutsen
c1bb7d5cc2 Update frontend to 20180310.0 2018-03-10 10:20:14 -08:00
Paulus Schoutsen
ae47da7bce Update frontend to 20180310.0 2018-03-10 10:19:49 -08:00
Paulus Schoutsen
f01b5b0040 Don't call async from sync (#13057) 2018-03-10 10:02:16 -08:00
Johann Kellerman
40485a6e89 Ensure we have valid config AFTER merging packages #13015 (#13038)
* Ensure we have valid config AFTER merging packages #13015

* also fix packages
2018-03-10 10:02:04 -08:00
cdce8p
7ea7fc8d38 Script/lint, Lazytox: Fix issue to ignore delete files (#13051)
* Fix issue to ignore delete files

* Updated lazytox
2018-03-10 09:12:23 -08:00
Timmo
86baed4e52 Glances Docker Sensors (#13026)
* Added container count

* Added container count

* Change Name

* Fix if

* Added Docker cpu use and memory use
2018-03-10 18:11:53 +01:00
Sebastian Muszynski
b4b779c49d python-miio version bumped. (#13055) 2018-03-10 09:11:10 -08:00
Sebastian Muszynski
0143752d94 Yeelight version bumped. (#13056) 2018-03-10 09:10:50 -08:00
Anders Melchiorsen
e910ecfd5f Fix async lifx_set_state (#13045) 2018-03-10 11:07:02 +01:00
John Allen
4d74fc2d07 Fix sensibo's min/max_temp properties (#12996)
The super class has these as properties, not regular methods
2018-03-10 10:52:45 +02:00
Paul Tarjan
f9c1675c95 Use request.query (#13037)
Fixes #13036
2018-03-10 00:36:20 -08:00
Jerad Meisner
76fb2447a5 Bump pysabnzbd version (#13042) 2018-03-10 09:27:13 +01:00
Otto Winter
2fae86bbd3 Make lazytox script executable (#13040) 2018-03-10 08:39:49 +01:00
Paulus Schoutsen
905d71c9e3 Merge pull request #13033 from home-assistant/release-0-65-1
0.65.1
2018-03-09 20:17:02 -08:00
Paulus Schoutsen
bf430ad14b Version bump to 0.65.1 2018-03-09 19:42:52 -08:00
cdce8p
a58d8fc68b HomeKit Bugfix: names (#13031)
* Fix display_names, changed default port (+1)

* Revert port change
2018-03-09 19:42:36 -08:00
Paulus Schoutsen
3c41c0c46e Add support for input boolean to Google Assistant (#13030) 2018-03-09 19:42:36 -08:00
Paulus Schoutsen
6ffc53b290 Make Throttle async aware (#13027)
* Make Throttle async aware

* Lint
2018-03-09 19:42:35 -08:00
Johann Kellerman
8f807a3006 Safe fix for #13015 (#13024) 2018-03-09 19:42:35 -08:00
Paulus Schoutsen
34c694c20e allow ios device tracker see calls to go through (#13020) 2018-03-09 19:42:35 -08:00
cdce8p
3ca139e21e HomeKit Bugfix: names (#13031)
* Fix display_names, changed default port (+1)

* Revert port change
2018-03-09 19:41:59 -08:00
Paulus Schoutsen
a8a895a61b allow ios device tracker see calls to go through (#13020) 2018-03-09 19:39:50 -08:00
Paulus Schoutsen
36361d623d Make Throttle async aware (#13027)
* Make Throttle async aware

* Lint
2018-03-09 19:38:51 -08:00
Paulus Schoutsen
652e0d45a9 Add support for input boolean to Google Assistant (#13030) 2018-03-09 19:38:33 -08:00
Heiko Thiery
556901ea48 remove rounding of temperature reading (#13018)
With homeassistant 0.65.0 the filter sensor is introduced. Now there
is a common way to filter the peaks comming from the readings.

Signed-off-by: Heiko Thiery <heiko.thiery@gmail.com>
2018-03-09 15:53:28 -08:00
Johann Kellerman
05255b9c3f Safe fix for #13015 (#13024) 2018-03-09 15:52:21 -08:00
Paulus Schoutsen
11e1b8a19d Update netdisco to 1.3.0 (#13007) 2018-03-09 13:04:23 -08:00
Johann Kellerman
37d8cd7b75 New lazytox.py script (#12862) 2018-03-09 22:27:39 +02:00
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
Ryan McLean
d8a7c547df Updated to plexapi 3.0.6 (#13005) 2018-03-09 08:50:39 -08:00
Ryan McLean
ecaf0189cc 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 08:50:21 -08:00
Otto Winter
ca5f470956 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 06:15:39 -08:00
mueslo
3ba19c52d5 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-08 23:57:21 -08:00
Johann Kellerman
6734c966b3 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-08 19:34:24 -08:00
Steve Easley
5e2296f2a4 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-08 19:31:52 -08:00
PhracturedBlue
c5228cbc64 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-08 18:23:52 -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
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
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
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
347ba1a2d8 Remove braviatv_psk (#12669) 2018-02-25 11:50:06 -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
572 changed files with 21832 additions and 8476 deletions

View File

@@ -62,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
@@ -82,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
@@ -103,6 +109,9 @@ omit =
homeassistant/components/homematic/__init__.py
homeassistant/components/*/homematic.py
homeassistant/components/homematicip_cloud.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
@@ -181,6 +190,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
@@ -244,6 +256,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
@@ -293,14 +308,11 @@ 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/ifttt.py
homeassistant/components/alarm_control_panel/manual_mqtt.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
@@ -312,7 +324,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
@@ -325,6 +336,7 @@ omit =
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
homeassistant/components/camera/onvif.py
homeassistant/components/camera/proxy.py
homeassistant/components/camera/ring.py
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py
@@ -353,12 +365,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
@@ -394,20 +408,20 @@ omit =
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
homeassistant/components/image_processing/seven_segments.py
homeassistant/components/keyboard.py
homeassistant/components/keyboard_remote.py
homeassistant/components/keyboard.py
homeassistant/components/light/avion.py
homeassistant/components/light/blinksticklight.py
homeassistant/components/light/blinkt.py
homeassistant/components/light/decora.py
homeassistant/components/light/decora_wifi.py
homeassistant/components/light/decora.py
homeassistant/components/light/flux_led.py
homeassistant/components/light/greenwave.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
homeassistant/components/light/iglo.py
homeassistant/components/light/lifx.py
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/mystrom.py
homeassistant/components/light/osramlightify.py
@@ -433,6 +447,7 @@ omit =
homeassistant/components/media_player/bluesound.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/channels.py
homeassistant/components/media_player/clementine.py
homeassistant/components/media_player/cmus.py
homeassistant/components/media_player/denon.py
@@ -464,6 +479,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
@@ -472,8 +488,8 @@ omit =
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/yamaha.py
homeassistant/components/media_player/ziggo_mediabox_xl.py
homeassistant/components/mycroft.py
homeassistant/components/notify/aws_lambda.py
@@ -481,8 +497,8 @@ omit =
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clickatell.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
@@ -507,7 +523,9 @@ omit =
homeassistant/components/notify/sendgrid.py
homeassistant/components/notify/simplepush.py
homeassistant/components/notify/slack.py
homeassistant/components/notify/stride.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
@@ -543,7 +561,6 @@ omit =
homeassistant/components/sensor/crimereports.py
homeassistant/components/sensor/cups.py
homeassistant/components/sensor/currencylayer.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deluge.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
@@ -565,6 +582,7 @@ omit =
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/folder.py
homeassistant/components/sensor/foobot.py
homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py
@@ -577,8 +595,8 @@ omit =
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/irish_rail_transport.py
homeassistant/components/sensor/kwb.py
@@ -619,10 +637,12 @@ 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/serial.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
@@ -634,16 +654,17 @@ omit =
homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/syncthru.py
homeassistant/components/sensor/synologydsm.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/sytadin.py
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/teksavvy.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/trafikverket_weatherstation.py
homeassistant/components/sensor/transmission.py
homeassistant/components/sensor/travisci.py
homeassistant/components/sensor/twitch.py
@@ -658,6 +679,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
@@ -675,7 +697,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
@@ -685,6 +706,7 @@ omit =
homeassistant/components/switch/telnet.py
homeassistant/components/switch/tplink.py
homeassistant/components/switch/transmission.py
homeassistant/components/switch/vesync.py
homeassistant/components/switch/xiaomi_miio.py
homeassistant/components/telegram_bot/*
homeassistant/components/thingspeak.py

View File

@@ -12,19 +12,18 @@
## Checklist:
- [ ] The code change is tested and works locally.
- [ ] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass**
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io)
If the code communicates with devices, web services, or third-party tools:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New files were added to `.coveragerc`.
If the code does not interact with devices:
- [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass**
- [ ] Tests have been added to verify that the new code works.
[ex-requir]: https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/keyboard.py#L14

4
.gitignore vendored
View File

@@ -21,6 +21,7 @@ Icon
*.iml
# pytest
.pytest_cache
.cache
# GITHUB Proposed Python stuff:
@@ -103,3 +104,6 @@ desktop.ini
# mypy
/.mypy_cache/*
# Secrets
.lokalise_token

0
.gitmodules vendored
View File

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

5
CODEOWNERS Executable file → Normal file
View File

@@ -43,17 +43,20 @@ 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
homeassistant/components/climate/sensibo.py @andrey-git
homeassistant/components/cover/group.py @cdce8p
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
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
@@ -77,6 +80,8 @@ 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

View File

@@ -4,7 +4,7 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot
The process is straight-forward.
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/devel/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
- Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0)
- Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant).
- Write the code for your device, notification service, sensor, or IoT thing.
- Ensure tests work.

View File

@@ -4,10 +4,10 @@ homeassistant.util package
Submodules
----------
homeassistant.util.async module
homeassistant.util.async_ module
-------------------------------
.. automodule:: homeassistant.util.async
.. automodule:: homeassistant.util.async_
:members:
:undoc-members:
:show-inheritance:

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)
@@ -278,7 +272,7 @@ def setup_and_run_hass(config_dir: str,
if args.open_ui:
# Imported here to avoid importing asyncio before monkey patch
from homeassistant.util.async import run_callback_threadsafe
from homeassistant.util.async_ import run_callback_threadsafe
def open_browser(event):
"""Open the webinterface in a browser."""
@@ -341,7 +335,8 @@ def main() -> int:
"""Start Home Assistant."""
validate_python()
if os.environ.get('HASS_NO_MONKEY') != '1':
monkey_patch_needed = sys.version_info[:3] < (3, 6, 3)
if monkey_patch_needed and os.environ.get('HASS_NO_MONKEY') != '1':
if sys.version_info[:2] >= (3, 6):
monkey_patch.disable_c_asyncio()
monkey_patch.patch_weakref_tasks()

View File

@@ -86,14 +86,6 @@ def async_from_config_dict(config: Dict[str, Any],
if enable_log:
async_enable_logging(hass, verbose, log_rotate_days, log_file)
if sys.version_info[:2] < (3, 5):
_LOGGER.warning(
'Python 3.4 support has been deprecated and will be removed in '
'the beginning of 2018. Please upgrade Python or your operating '
'system. More info: https://home-assistant.io/blog/2017/10/06/'
'deprecating-python-3.4-support/'
)
core_config = config.get(core.DOMAIN, {})
try:
@@ -112,17 +104,17 @@ 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()
# Ensure we have no None values after merge
for key, value in config.items():
new_config[key] = value or {}
config = new_config
if not value:
config[key] = {}
hass.config_entries = config_entries.ConfigEntries(hass, config)
yield from hass.config_entries.async_load()

View File

@@ -156,9 +156,10 @@ def async_setup(hass, config):
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 {}"))
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 {}"))
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 {}"))

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

@@ -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,70 @@ 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,
STATE_ALARM_ARMED_NIGHT)
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
)
DEPENDENCIES = ['egardia']
_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,
'ARMHOME': STATE_ALARM_ARMED_HOME,
'HOME': STATE_ALARM_ARMED_HOME,
'TRIGGERED': STATE_ALARM_TRIGGERED,
'UNKNOWN': STATE_UNKNOWN,
'NIGHT HOME': STATE_ALARM_ARMED_NIGHT,
'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")
if discovery_info is None:
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 +96,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

@@ -0,0 +1,170 @@
"""
Interfaces with alarm control panels that have to be controlled through IFTTT.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ifttt/
"""
import logging
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import (
DOMAIN, PLATFORM_SCHEMA)
from homeassistant.components.ifttt import (
ATTR_EVENT, DOMAIN as IFTTT_DOMAIN, SERVICE_TRIGGER)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_STATE, CONF_NAME, CONF_CODE,
CONF_OPTIMISTIC, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['ifttt']
_LOGGER = logging.getLogger(__name__)
ALLOWED_STATES = [
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME]
DATA_IFTTT_ALARM = 'ifttt_alarm'
DEFAULT_NAME = "Home"
CONF_EVENT_AWAY = "event_arm_away"
CONF_EVENT_HOME = "event_arm_home"
CONF_EVENT_NIGHT = "event_arm_night"
CONF_EVENT_DISARM = "event_disarm"
DEFAULT_EVENT_AWAY = "alarm_arm_away"
DEFAULT_EVENT_HOME = "alarm_arm_home"
DEFAULT_EVENT_NIGHT = "alarm_arm_night"
DEFAULT_EVENT_DISARM = "alarm_disarm"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
vol.Optional(CONF_EVENT_AWAY, default=DEFAULT_EVENT_AWAY): cv.string,
vol.Optional(CONF_EVENT_HOME, default=DEFAULT_EVENT_HOME): cv.string,
vol.Optional(CONF_EVENT_NIGHT, default=DEFAULT_EVENT_NIGHT): cv.string,
vol.Optional(CONF_EVENT_DISARM, default=DEFAULT_EVENT_DISARM): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
})
SERVICE_PUSH_ALARM_STATE = "ifttt_push_alarm_state"
PUSH_ALARM_STATE_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_STATE): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a control panel managed through IFTTT."""
if DATA_IFTTT_ALARM not in hass.data:
hass.data[DATA_IFTTT_ALARM] = []
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
event_away = config.get(CONF_EVENT_AWAY)
event_home = config.get(CONF_EVENT_HOME)
event_night = config.get(CONF_EVENT_NIGHT)
event_disarm = config.get(CONF_EVENT_DISARM)
optimistic = config.get(CONF_OPTIMISTIC)
alarmpanel = IFTTTAlarmPanel(name, code, event_away, event_home,
event_night, event_disarm, optimistic)
hass.data[DATA_IFTTT_ALARM].append(alarmpanel)
add_devices([alarmpanel])
async def push_state_update(service):
"""Set the service state as device state attribute."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
state = service.data.get(ATTR_STATE)
devices = hass.data[DATA_IFTTT_ALARM]
if entity_ids:
devices = [d for d in devices if d.entity_id in entity_ids]
for device in devices:
device.push_alarm_state(state)
device.async_schedule_update_ha_state()
hass.services.register(DOMAIN, SERVICE_PUSH_ALARM_STATE, push_state_update,
schema=PUSH_ALARM_STATE_SERVICE_SCHEMA)
class IFTTTAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an alarm control panel controlled throught IFTTT."""
def __init__(self, name, code, event_away, event_home, event_night,
event_disarm, optimistic):
"""Initialize the alarm control panel."""
self._name = name
self._code = code
self._event_away = event_away
self._event_home = event_home
self._event_night = event_night
self._event_disarm = event_disarm
self._optimistic = optimistic
self._state = None
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def assumed_state(self):
"""Notify that this platform return an assumed state."""
return True
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
def alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._check_code(code):
return
self.set_alarm_state(self._event_disarm, STATE_ALARM_DISARMED)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
if not self._check_code(code):
return
self.set_alarm_state(self._event_away, STATE_ALARM_ARMED_AWAY)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
if not self._check_code(code):
return
self.set_alarm_state(self._event_home, STATE_ALARM_ARMED_HOME)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
if not self._check_code(code):
return
self.set_alarm_state(self._event_night, STATE_ALARM_ARMED_NIGHT)
def set_alarm_state(self, event, state):
"""Call the IFTTT trigger service to change the alarm state."""
data = {ATTR_EVENT: event}
self.hass.services.call(IFTTT_DOMAIN, SERVICE_TRIGGER, data)
_LOGGER.debug("Called IFTTT component to trigger event %s", event)
if self._optimistic:
self._state = state
def push_alarm_state(self, value):
"""Push the alarm state to the given value."""
if value in ALLOWED_STATES:
_LOGGER.debug("Pushed the alarm state to %s", value)
self._state = value
def _check_code(self, code):
return self._code is None or self._code == code

View File

@@ -69,3 +69,13 @@ alarmdecoder_alarm_toggle_chime:
code:
description: A required code to toggle the alarm control panel chime with.
example: 1234
ifttt_push_alarm_state:
description: Update the alarm state to the specified value.
fields:
entity_id:
description: Name of the alarm control panel which state has to be updated.
example: 'alarm_control_panel.downstairs'
state:
description: The state to which the alarm control panel has to be set.
example: 'armed_night'

View File

@@ -438,9 +438,7 @@ class _LightCapabilities(_AlexaEntity):
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & light.SUPPORT_BRIGHTNESS:
yield _AlexaBrightnessController(self.entity)
if supported & light.SUPPORT_RGB_COLOR:
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_XY_COLOR:
if supported & light.SUPPORT_COLOR:
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_COLOR_TEMP:
yield _AlexaColorTemperatureController(self.entity)
@@ -842,25 +840,16 @@ def async_api_adjust_brightness(hass, config, request, entity):
@asyncio.coroutine
def async_api_set_color(hass, config, request, entity):
"""Process a set color request."""
supported = entity.attributes.get(ATTR_SUPPORTED_FEATURES)
rgb = color_util.color_hsb_to_RGB(
float(request[API_PAYLOAD]['color']['hue']),
float(request[API_PAYLOAD]['color']['saturation']),
float(request[API_PAYLOAD]['color']['brightness'])
)
if supported & light.SUPPORT_RGB_COLOR > 0:
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_RGB_COLOR: rgb,
}, blocking=False)
else:
xyz = color_util.color_RGB_to_xy(*rgb)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_XY_COLOR: (xyz[0], xyz[1]),
light.ATTR_BRIGHTNESS: xyz[2],
}, blocking=False)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_RGB_COLOR: rgb,
}, blocking=False)
return api_message(request)

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

@@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
REQUIREMENTS = ['py-august==0.3.0']
REQUIREMENTS = ['py-august==0.4.0']
DEFAULT_TIMEOUT = 10
ACTIVITY_FETCH_LIMIT = 10
@@ -159,7 +159,7 @@ class AugustData:
self._api = api
self._access_token = access_token
self._doorbells = self._api.get_doorbells(self._access_token) or []
self._locks = self._api.get_locks(self._access_token) or []
self._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 = {}

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,118 @@
"""
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/binary_sensor.bmw_connected_drive/
"""
import asyncio
import logging
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['bmw_connected_drive']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'lids': ['Doors', 'opening'],
'windows': ['Windows', 'opening'],
'door_lock_state': ['Door lock state', 'safety']
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the BMW sensors."""
accounts = hass.data[BMW_DOMAIN]
_LOGGER.debug('Found BMW accounts: %s',
', '.join([a.name for a in accounts]))
devices = []
for account in accounts:
for vehicle in account.account.vehicles:
for key, value in sorted(SENSOR_TYPES.items()):
device = BMWConnectedDriveSensor(account, vehicle, key,
value[0], value[1])
devices.append(device)
add_devices(devices, True)
class BMWConnectedDriveSensor(BinarySensorDevice):
"""Representation of a BMW vehicle binary sensor."""
def __init__(self, account, vehicle, attribute: str, sensor_name,
device_class):
"""Constructor."""
self._account = account
self._vehicle = vehicle
self._attribute = attribute
self._name = '{} {}'.format(self._vehicle.modelName, self._attribute)
self._sensor_name = sensor_name
self._device_class = device_class
self._state = None
@property
def should_poll(self) -> bool:
"""Data update is triggered from BMWConnectedDriveEntity."""
return False
@property
def name(self):
"""Return the name of the binary sensor."""
return self._name
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def is_on(self):
"""Return the state of the binary sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes of the binary sensor."""
vehicle_state = self._vehicle.state
result = {
'car': self._vehicle.modelName
}
if self._attribute == 'lids':
for lid in vehicle_state.lids:
result[lid.name] = lid.state.value
elif self._attribute == 'windows':
for window in vehicle_state.windows:
result[window.name] = window.state.value
elif self._attribute == 'door_lock_state':
result['door_lock_state'] = vehicle_state.door_lock_state.value
return result
def update(self):
"""Read new state data from the library."""
vehicle_state = self._vehicle.state
# device class opening: On means open, Off means closed
if self._attribute == 'lids':
_LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed)
self._state = not vehicle_state.all_lids_closed
if self._attribute == 'windows':
self._state = not vehicle_state.all_windows_closed
# device class safety: On means unsafe, Off means safe
if self._attribute == 'door_lock_state':
# Possible values: LOCKED, SECURED, SELECTIVELOCKED, UNLOCKED
self._state = bool(vehicle_state.door_lock_state.value
in ('SELECTIVELOCKED', 'UNLOCKED'))
def update_callback(self):
"""Schedule a state update."""
self.schedule_update_ha_state(True)
@asyncio.coroutine
def async_added_to_hass(self):
"""Add callback after being added to hass.
Show latest data after startup.
"""
self._account.add_update_listener(self.update_callback)

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

@@ -4,8 +4,6 @@ Support for deCONZ binary sensor.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.deconz/
"""
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
@@ -15,8 +13,8 @@ from homeassistant.core import callback
DEPENDENCIES = ['deconz']
@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 deCONZ binary sensor."""
if discovery_info is None:
return
@@ -25,8 +23,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
sensors = hass.data[DATA_DECONZ].sensors
entities = []
for key in sorted(sensors.keys(), key=int):
sensor = sensors[key]
for sensor in sensors.values():
if sensor and sensor.type in DECONZ_BINARY_SENSOR:
entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True)
@@ -39,8 +36,7 @@ class DeconzBinarySensor(BinarySensorDevice):
"""Set up sensor and add update callback to get data from websocket."""
self._sensor = sensor
@asyncio.coroutine
def async_added_to_hass(self):
async 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
@@ -96,9 +92,9 @@ class DeconzBinarySensor(BinarySensorDevice):
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
from pydeconz.sensor import PRESENCE
attr = {
ATTR_BATTERY_LEVEL: self._sensor.battery,
}
if self._sensor.type in PRESENCE:
attr = {}
if self._sensor.battery:
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
if self._sensor.type in PRESENCE and self._sensor.dark:
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__)
DEPENDENCIES = ['egardia']
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

@@ -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({
@@ -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

@@ -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'
@@ -49,12 +49,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT):
cv.positive_int,
vol.Optional(CONF_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 discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
@@ -82,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)
@@ -111,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,6 +9,17 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES, DOMAIN, BinarySensorDevice)
from homeassistant.const import STATE_ON
SENSORS = {
'S_DOOR': 'door',
'S_MOTION': 'motion',
'S_SMOKE': 'smoke',
'S_SPRINKLER': 'safety',
'S_WATER_LEAK': 'safety',
'S_SOUND': 'sound',
'S_VIBRATION': 'vibration',
'S_MOISTURE': 'moisture',
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the MySensors platform for binary sensors."""
@@ -29,18 +40,7 @@ class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
pres = self.gateway.const.Presentation
class_map = {
pres.S_DOOR: 'opening',
pres.S_MOTION: 'motion',
pres.S_SMOKE: 'smoke',
}
if float(self.gateway.protocol_version) >= 1.5:
class_map.update({
pres.S_SPRINKLER: 'sprinkler',
pres.S_WATER_LEAK: 'leak',
pres.S_SOUND: 'sound',
pres.S_VIBRATION: 'vibration',
pres.S_MOISTURE: 'moisture',
})
if class_map.get(self.child_type) in DEVICE_CLASSES:
return class_map.get(self.child_type)
device_class = SENSORS.get(pres(self.child_type).name)
if device_class in DEVICE_CLASSES:
return device_class
return None

View File

@@ -55,13 +55,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
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.get(CONF_NAME), entity.get(CONF_DEVICE_CLASS),

View File

@@ -23,7 +23,7 @@ from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.util import utcnow
REQUIREMENTS = ['numpy==1.14.0']
REQUIREMENTS = ['numpy==1.14.2']
_LOGGER = logging.getLogger(__name__)

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

@@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.9.3']
REQUIREMENTS = ['holidays==0.9.4']
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime

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

@@ -16,7 +16,7 @@ from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD
)
REQUIREMENTS = ['bimmer_connected==0.3.0']
REQUIREMENTS = ['bimmer_connected==0.4.1']
_LOGGER = logging.getLogger(__name__)
@@ -37,7 +37,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
BMW_COMPONENTS = ['device_tracker', 'sensor']
BMW_COMPONENTS = ['binary_sensor', 'device_tracker', 'lock', 'sensor']
UPDATE_INTERVAL = 5 # in minutes

View File

@@ -194,7 +194,9 @@ class WebDavCalendarData(object):
@staticmethod
def is_over(vevent):
"""Return if the event is over."""
return dt.now() > WebDavCalendarData.get_end_date(vevent)
return dt.now() >= WebDavCalendarData.to_datetime(
WebDavCalendarData.get_end_date(vevent)
)
@staticmethod
def get_hass_date(obj):
@@ -230,4 +232,4 @@ class WebDavCalendarData(object):
else:
enddate = obj.dtstart.value + timedelta(days=1)
return WebDavCalendarData.to_datetime(enddate)
return enddate

View File

@@ -496,6 +496,10 @@ class TodoistProjectData(object):
# We had no valid tasks
return True
# Make sure the task collection is reset to prevent an
# infinite collection repeating the same tasks
self.all_project_tasks.clear()
# Organize the best tasks (so users can see all the tasks
# they have, organized)
while project_tasks:

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

@@ -49,8 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@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 an Arlo IP Camera."""
arlo = hass.data.get(DATA_ARLO)
if not arlo:
@@ -60,7 +59,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for camera in arlo.cameras:
cameras.append(ArloCam(hass, camera, config))
async_add_devices(cameras, True)
add_devices(cameras, True)
class ArloCam(Camera):

View File

@@ -21,7 +21,7 @@ from homeassistant.components.camera import (
PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, Camera)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_validation as cv
from homeassistant.util.async import run_coroutine_threadsafe
from homeassistant.util.async_ import run_coroutine_threadsafe
_LOGGER = logging.getLogger(__name__)

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/camera.onvif/
"""
import asyncio
import logging
import os
import voluptuous as vol
@@ -33,6 +34,9 @@ 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"
@@ -57,6 +61,8 @@ 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({
@@ -67,8 +73,7 @@ SERVICE_PTZ_SCHEMA = vol.Schema({
})
@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
@@ -91,7 +96,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
hass.services.async_register(DOMAIN, SERVICE_PTZ, handle_ptz,
schema=SERVICE_PTZ_SCHEMA)
async_add_devices([ONVIFHassCamera(hass, config)])
add_devices([ONVIFHassCamera(hass, config)])
class ONVIFHassCamera(Camera):
@@ -99,85 +104,128 @@ class ONVIFHassCamera(Camera):
def __init__(self, hass, config):
"""Initialize a ONVIF camera."""
from onvif import ONVIFCamera, exceptions
super().__init__()
import onvif
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
self._host = config.get(CONF_HOST)
self._port = config.get(CONF_PORT)
self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
self._profile_index = config.get(CONF_PROFILE)
self._input = None
camera = None
self._media_service = \
onvif.ONVIFService('http://{}:{}/onvif/device_service'.format(
self._host, self._port),
self._username, self._password,
'{}/wsdl/media.wsdl'.format(os.path.dirname(
onvif.__file__)))
self._ptz_service = \
onvif.ONVIFService('http://{}:{}/onvif/device_service'.format(
self._host, self._port),
self._username, self._password,
'{}/wsdl/ptz.wsdl'.format(os.path.dirname(
onvif.__file__)))
def obtain_input_uri(self):
"""Set the input uri for the camera."""
from onvif import exceptions
_LOGGER.debug("Connecting with ONVIF Camera: %s on port %s",
self._host, self._port)
try:
_LOGGER.debug("Connecting with ONVIF Camera: %s on port %s",
config.get(CONF_HOST), config.get(CONF_PORT))
camera = ONVIFCamera(
config.get(CONF_HOST), config.get(CONF_PORT),
config.get(CONF_USERNAME), config.get(CONF_PASSWORD)
)
media_service = camera.create_media_service()
stream_uri = media_service.GetStreamUri(
{'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': 'RTSP'}}
)
self._input = stream_uri.Uri.replace(
'rtsp://', 'rtsp://{}:{}@'.format(
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD)), 1)
profiles = self._media_service.GetProfiles()
if self._profile_index >= len(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 = self._media_service.create_type('GetStreamUri')
# pylint: disable=protected-access
req.ProfileToken = profiles[self._profile_index]._token
uri_no_auth = self._media_service.GetStreamUri(req).Uri
uri_for_log = uri_no_auth.replace(
'rtsp://', 'rtsp://<user>:<password>@', 1)
self._input = uri_no_auth.replace(
'rtsp://', 'rtsp://{}:{}@'.format(self._username,
self._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()
self._name, uri_for_log)
# we won't need the media service anymore
self._media_service = None
except exceptions.ONVIFError as err:
self._ptz = None
_LOGGER.warning("Unable to setup PTZ for ONVIF Camera: %s", err)
_LOGGER.debug("Couldn't setup camera '%s'. Error: %s",
self._name, err)
return
def perform_ptz(self, pan, tilt, zoom):
"""Perform a PTZ action on the camera."""
if self._ptz:
from onvif import exceptions
if self._ptz_service:
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)
try:
self._ptz_service.ContinuousMove(req)
except exceptions.ONVIFError as err:
if "Bad Request" in err.reason:
self._ptz_service = None
_LOGGER.debug("Camera '%s' doesn't support PTZ.",
self._name)
else:
_LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name)
@asyncio.coroutine
def async_added_to_hass(self):
async 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):
async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
if not self._input:
await self.hass.async_add_job(self.obtain_input_uri)
if not self._input:
return None
ffmpeg = ImageFrame(
self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop)
image = yield from asyncio.shield(ffmpeg.get_image(
image = await asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return 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
if not self._input:
await self.hass.async_add_job(self.obtain_input_uri)
if not self._input:
return None
stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary,
loop=self.hass.loop)
yield from stream.open_camera(
await stream.open_camera(
self._input, extra_cmd=self._ffmpeg_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()
@property
def name(self):

View File

@@ -0,0 +1,244 @@
"""
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)])
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)
async def write(img_bytes):
"""Write image to stream."""
await 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:
# This would be nicer as an async generator
# But that would only be supported for python >=3.6
data = b''
stream = req.content
while True:
chunk = await stream.read(102400)
if not chunk:
break
data += chunk
jpg_start = data.find(b'\xff\xd8')
jpg_end = data.find(b'\xff\xd9')
if jpg_start != -1 and jpg_end != -1:
image = data[jpg_start:jpg_end + 2]
image = await self.hass.async_add_job(
_resize_image, image, self._stream_opts)
await write(image)
data = data[jpg_end + 2:]
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

@@ -106,6 +106,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_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

@@ -20,7 +20,7 @@ from homeassistant.helpers.aiohttp_client import (
async_get_clientsession)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['py-synology==0.1.5']
REQUIREMENTS = ['py-synology==0.2.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -4,7 +4,6 @@ Support for Xeoma Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.xeoma/
"""
import asyncio
import logging
import voluptuous as vol
@@ -14,7 +13,7 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pyxeoma==1.3']
REQUIREMENTS = ['pyxeoma==1.4.0']
_LOGGER = logging.getLogger(__name__)
@@ -41,8 +40,8 @@ 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):
"""Discover and setup Xeoma Cameras."""
from pyxeoma.xeoma import Xeoma, XeomaError
@@ -53,8 +52,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
xeoma = Xeoma(host, login, password)
try:
yield from xeoma.async_test_connection()
discovered_image_names = yield from xeoma.async_get_image_names()
await xeoma.async_test_connection()
discovered_image_names = await xeoma.async_get_image_names()
discovered_cameras = [
{
CONF_IMAGE_NAME: image_name,
@@ -103,12 +102,11 @@ class XeomaCamera(Camera):
self._password = password
self._last_image = None
@asyncio.coroutine
def async_camera_image(self):
async def async_camera_image(self):
"""Return a still image response from the camera."""
from pyxeoma.xeoma import XeomaError
try:
image = yield from self._xeoma.async_get_camera_image(
image = await self._xeoma.async_get_camera_image(
self._image, self._username, self._password)
self._last_image = image
except XeomaError as err:

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,

View File

@@ -14,10 +14,10 @@ from homeassistant.components.climate import (
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE_LOW, STATE_OFF)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
_CONFIGURING = {}
@@ -50,7 +50,7 @@ SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
@@ -122,6 +122,7 @@ class Thermostat(ClimateDevice):
self._climate_list = self.climate_list
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
self._fan_list = ['auto', 'on']
self.update_without_throttle = False
def update(self):
@@ -180,24 +181,29 @@ class Thermostat(ClimateDevice):
return self.thermostat['runtime']['desiredCool'] / 10.0
return None
@property
def desired_fan_mode(self):
"""Return the desired fan mode of operation."""
return self.thermostat['runtime']['desiredFanMode']
@property
def fan(self):
"""Return the current fan state."""
"""Return the current fan status."""
if 'fan' in self.thermostat['equipmentStatus']:
return STATE_ON
return STATE_OFF
@property
def current_fan_mode(self):
"""Return the fan setting."""
return self.thermostat['runtime']['desiredFanMode']
@property
def current_hold_mode(self):
"""Return current hold mode."""
mode = self._current_hold_mode
return None if mode == AWAY_MODE else mode
@property
def fan_list(self):
"""Return the available fan modes."""
return self._fan_list
@property
def _current_hold_mode(self):
events = self.thermostat['events']
@@ -206,7 +212,7 @@ class Thermostat(ClimateDevice):
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
int(event['startDate'][0:4]) <= 1:
# A temporary hold from away climate is a hold
return 'away'
# A permanent hold from away climate
@@ -228,7 +234,7 @@ class Thermostat(ClimateDevice):
def current_operation(self):
"""Return current operation."""
if self.operation_mode == 'auxHeatOnly' or \
self.operation_mode == 'heatPump':
self.operation_mode == 'heatPump':
return STATE_HEAT
return self.operation_mode
@@ -271,10 +277,11 @@ class Thermostat(ClimateDevice):
operation = STATE_HEAT
else:
operation = status
return {
"actual_humidity": self.thermostat['runtime']['actualHumidity'],
"fan": self.fan,
"mode": self.mode,
"climate_mode": self.mode,
"operation": operation,
"climate_list": self.climate_list,
"fan_min_on_time": self.fan_min_on_time
@@ -342,25 +349,46 @@ class Thermostat(ClimateDevice):
cool_temp_setpoint, heat_temp_setpoint,
self.hold_preference())
_LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, "
"cool=%s, is=%s", heat_temp, isinstance(
heat_temp, (int, float)), cool_temp,
"cool=%s, is=%s", heat_temp,
isinstance(heat_temp, (int, float)), cool_temp,
isinstance(cool_temp, (int, float)))
self.update_without_throttle = True
def set_fan_mode(self, fan_mode):
"""Set the fan mode. Valid values are "on" or "auto"."""
if (fan_mode.lower() != STATE_ON) and (fan_mode.lower() != STATE_AUTO):
error = "Invalid fan_mode value: Valid values are 'on' or 'auto'"
_LOGGER.error(error)
return
cool_temp = self.thermostat['runtime']['desiredCool'] / 10.0
heat_temp = self.thermostat['runtime']['desiredHeat'] / 10.0
self.data.ecobee.set_fan_mode(self.thermostat_index, fan_mode,
cool_temp, heat_temp,
self.hold_preference())
_LOGGER.info("Setting fan mode to: %s", fan_mode)
def set_temp_hold(self, temp):
"""Set temperature hold in modes other than auto."""
# Set arbitrary range when not in auto mode
if self.current_operation == STATE_HEAT:
"""Set temperature hold in modes other than auto.
Ecobee API: It is good practice to set the heat and cool hold
temperatures to be the same, if the thermostat is in either heat, cool,
auxHeatOnly, or off mode. If the thermostat is in auto mode, an
additional rule is required. The cool hold temperature must be greater
than the heat hold temperature by at least the amount in the
heatCoolMinDelta property.
https://www.ecobee.com/home/developer/api/examples/ex5.shtml
"""
if self.current_operation == STATE_HEAT or self.current_operation == \
STATE_COOL:
heat_temp = temp
cool_temp = temp + 20
elif self.current_operation == STATE_COOL:
heat_temp = temp - 20
cool_temp = temp
else:
# In auto mode set temperature between
heat_temp = temp - 10
cool_temp = temp + 10
delta = self.thermostat['settings']['heatCoolMinDelta'] / 10
heat_temp = temp - delta
cool_temp = temp + delta
self.set_auto_temp_hold(heat_temp, cool_temp)
def set_temperature(self, **kwargs):
@@ -369,8 +397,8 @@ class Thermostat(ClimateDevice):
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
temp = kwargs.get(ATTR_TEMPERATURE)
if self.current_operation == STATE_AUTO and (low_temp is not None or
high_temp is not None):
if self.current_operation == STATE_AUTO and \
(low_temp is not None or high_temp is not None):
self.set_auto_temp_hold(low_temp, high_temp)
elif temp is not None:
self.set_temp_hold(temp)

View File

@@ -229,7 +229,7 @@ class GenericThermostat(ClimateDevice):
"""List of available operation modes."""
return self._operation_list
def set_operation_mode(self, operation_mode):
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
self._current_operation = STATE_HEAT

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
@@ -61,8 +60,8 @@ 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 discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
@@ -135,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
@@ -187,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):
@@ -210,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

@@ -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_mode):
"""Turn fan on/off."""
self.device.fan = fan_mode.lower()
if self._has_fan:
self.device.fan = fan_mode.lower()
@property
def min_temp(self):

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,18 @@ class SensiboClimate(ClimateDevice):
def min_temp(self):
"""Return the minimum temperature."""
return self._temperatures_list[0] \
if 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 self._temperatures_list else super().max_temp()
if self._temperatures_list else super().max_temp
@property
def unique_id(self):
"""Return unique ID based on Sensibo ID."""
return self._id
@asyncio.coroutine
def async_set_temperature(self, **kwargs):

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
@@ -37,6 +37,7 @@ CONF_FILTER = 'filter'
CONF_GOOGLE_ACTIONS = 'google_actions'
CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id'
CONF_GOOGLE_ACTIONS_SYNC_URL = 'google_actions_sync_url'
DEFAULT_MODE = 'production'
DEPENDENCIES = ['http']
@@ -51,7 +52,6 @@ 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])
})
@@ -76,6 +76,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_USER_POOL_ID): str,
vol.Optional(CONF_REGION): str,
vol.Optional(CONF_RELAYER): str,
vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): str,
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA,
vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA,
}),
@@ -111,7 +112,7 @@ class Cloud:
def __init__(self, hass, mode, alexa, google_actions,
cognito_client_id=None, user_pool_id=None, region=None,
relayer=None):
relayer=None, google_actions_sync_url=None):
"""Create an instance of Cloud."""
self.hass = hass
self.mode = mode
@@ -129,6 +130,7 @@ class Cloud:
self.user_pool_id = user_pool_id
self.region = region
self.relayer = relayer
self.google_actions_sync_url = google_actions_sync_url
else:
info = SERVERS[mode]
@@ -137,6 +139,7 @@ class Cloud:
self.user_pool_id = info['user_pool_id']
self.region = info['region']
self.relayer = info['relayer']
self.google_actions_sync_url = info['google_actions_sync_url']
@property
def is_logged_in(self):
@@ -175,7 +178,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),

View File

@@ -17,14 +17,6 @@ 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."""
@@ -42,10 +34,8 @@ class UnknownError(CloudError):
AWS_EXCEPTIONS = {
'UserNotFoundException': UserNotFound,
'NotAuthorizedException': Unauthenticated,
'ExpiredCodeException': ExpiredCode,
'UserNotConfirmedException': UserNotConfirmed,
'PasswordResetRequiredException': PasswordChangeRequired,
'CodeMismatchException': InvalidCode,
}
@@ -69,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
@@ -107,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

@@ -8,7 +8,9 @@ SERVERS = {
'cognito_client_id': '60i2uvhvbiref2mftj7rgcrt9u',
'user_pool_id': 'us-east-1_87ll5WOP8',
'region': 'us-east-1',
'relayer': 'wss://cloud.hass.io:8000/websocket'
'relayer': 'wss://cloud.hass.io:8000/websocket',
'google_actions_sync_url': ('https://24ab3v80xd.execute-api.us-east-1.'
'amazonaws.com/prod/smart_home_sync'),
}
}
@@ -16,3 +18,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

@@ -16,17 +16,15 @@ from .const import DOMAIN, REQUEST_TIMEOUT
_LOGGER = logging.getLogger(__name__)
@asyncio.coroutine
def async_setup(hass):
async def async_setup(hass):
"""Initialize the HTTP API."""
hass.http.register_view(GoogleActionsSyncView)
hass.http.register_view(CloudLoginView)
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 = {
@@ -34,20 +32,17 @@ _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.')
}
def _handle_cloud_errors(handler):
"""Handle auth errors."""
@asyncio.coroutine
@wraps(handler)
def error_handler(view, request, *args, **kwargs):
async def error_handler(view, request, *args, **kwargs):
"""Handle exceptions that raise from the wrapped request handler."""
try:
result = yield from handler(view, request, *args, **kwargs)
result = await handler(view, request, *args, **kwargs)
return result
except (auth_api.CloudError, asyncio.TimeoutError) as err:
@@ -61,6 +56,31 @@ def _handle_cloud_errors(handler):
return error_handler
class GoogleActionsSyncView(HomeAssistantView):
"""Trigger a Google Actions Smart Home Sync."""
url = '/api/cloud/google_actions/sync'
name = 'api:cloud:google_actions/sync'
@_handle_cloud_errors
async def post(self, request):
"""Trigger a Google Actions sync."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
websession = hass.helpers.aiohttp_client.async_get_clientsession()
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
await hass.async_add_job(auth_api.check_token, cloud)
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
req = await websession.post(
cloud.google_actions_sync_url, headers={
'authorization': cloud.id_token
})
return self.json({}, status_code=req.status)
class CloudLoginView(HomeAssistantView):
"""Login to Home Assistant cloud."""
@@ -72,19 +92,18 @@ class CloudLoginView(HomeAssistantView):
vol.Required('email'): str,
vol.Required('password'): str,
}))
@asyncio.coroutine
def post(self, request, data):
async def post(self, request, data):
"""Handle login 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.login, cloud, data['email'],
data['password'])
await hass.async_add_job(auth_api.login, cloud, data['email'],
data['password'])
hass.async_add_job(cloud.iot.connect)
# Allow cloud to start connecting.
yield from asyncio.sleep(0, loop=hass.loop)
await asyncio.sleep(0, loop=hass.loop)
return self.json(_account_data(cloud))
@@ -95,14 +114,13 @@ class CloudLogoutView(HomeAssistantView):
name = 'api:cloud:logout'
@_handle_cloud_errors
@asyncio.coroutine
def post(self, request):
async def post(self, request):
"""Handle logout request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from cloud.logout()
await cloud.logout()
return self.json_message('ok')
@@ -113,8 +131,7 @@ class CloudAccountView(HomeAssistantView):
url = '/api/cloud/account'
name = 'api:cloud:account'
@asyncio.coroutine
def get(self, request):
async def get(self, request):
"""Get account info."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
@@ -136,44 +153,18 @@ class CloudRegisterView(HomeAssistantView):
vol.Required('email'): str,
vol.Required('password'): vol.All(str, vol.Length(min=6)),
}))
@asyncio.coroutine
def post(self, request, data):
async def post(self, request, data):
"""Handle registration request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
await hass.async_add_job(
auth_api.register, cloud, data['email'], data['password'])
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."""
@@ -184,14 +175,13 @@ class CloudResendConfirmView(HomeAssistantView):
@RequestDataValidator(vol.Schema({
vol.Required('email'): str,
}))
@asyncio.coroutine
def post(self, request, data):
async def post(self, request, data):
"""Handle resending confirm email code request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
await hass.async_add_job(
auth_api.resend_email_confirm, cloud, data['email'])
return self.json_message('ok')
@@ -207,46 +197,18 @@ class CloudForgotPasswordView(HomeAssistantView):
@RequestDataValidator(vol.Schema({
vol.Required('email'): str,
}))
@asyncio.coroutine
def post(self, request, data):
async def post(self, request, data):
"""Handle forgot password request."""
hass = request.app['hass']
cloud = hass.data[DOMAIN]
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
yield from hass.async_add_job(
await hass.async_add_job(
auth_api.forgot_password, cloud, data['email'])
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__)
@@ -77,9 +78,9 @@ class CloudIoT:
self.tries += 1
try:
# Sleep 0, 5, 10, 15 ... 30 seconds between retries
# Sleep 2^tries seconds between retries
self.retry_task = hass.async_add_job(asyncio.sleep(
min(30, (self.tries - 1) * 5), loop=hass.loop))
2**min(9, self.tries), loop=hass.loop))
yield from self.retry_task
self.retry_task = None
except asyncio.CancelledError:
@@ -97,13 +98,23 @@ class CloudIoT:
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 connect: %s", err)
_LOGGER.warning("Unable to refresh token: %s", err)
return
if self.cloud.subscription_expired:
hass.components.persistent_notification.async_create(
MESSAGE_EXPIRATION, 'Subscription expired',
MESSAGE_EXPIRATION, 'Home Assistant Cloud',
'cloud_subscription_expired')
self.close_requested = True
return
@@ -144,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'],
@@ -166,7 +179,9 @@ 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 client_exceptions.WSServerHandshakeError as err:

View File

@@ -14,9 +14,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = [
'https://github.com/balloob/coinbase-python/archive/'
'3a35efe13ef728a1cc18204b4f25be1fcb1c6006.zip#coinbase==2.0.8a1']
REQUIREMENTS = ['coinbase==2.1.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -13,7 +13,8 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script')
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script',
'entity_registry')
ON_DEMAND = ('zwave',)
FEATURE_FLAGS = ('config_entries',)

View File

@@ -97,10 +97,10 @@ class ConfigManagerFlowIndexView(HomeAssistantView):
flow for flow in hass.config_entries.flow.async_progress()
if flow['source'] != config_entries.SOURCE_USER])
@asyncio.coroutine
@RequestDataValidator(vol.Schema({
vol.Required('domain'): str,
}))
@asyncio.coroutine
def post(self, request, data):
"""Handle a POST request."""
hass = request.app['hass']
@@ -139,8 +139,8 @@ class ConfigManagerFlowResourceView(HomeAssistantView):
return self.json(result)
@asyncio.coroutine
@RequestDataValidator(vol.Schema(dict), allow_empty=True)
@asyncio.coroutine
def post(self, request, flow_id, data):
"""Handle a POST request."""
hass = request.app['hass']
@@ -163,7 +163,7 @@ class ConfigManagerFlowResourceView(HomeAssistantView):
hass = request.app['hass']
try:
hass.config_entries.async_abort(flow_id)
hass.config_entries.flow.async_abort(flow_id)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)

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,24 @@
{
"config": {
"error": {
"invalid_object_id": "Ung\u00fcltige Objekt-ID"
},
"step": {
"init": {
"data": {
"object_id": "Objekt-ID"
},
"description": "Bitte gib eine Objekt_ID f\u00fcr das Test-Entity ein.",
"title": "W\u00e4hle eine Objekt-ID"
},
"name": {
"data": {
"name": "Name"
},
"description": "Bitte gib einen Namen f\u00fcr das Test-Entity ein",
"title": "Name des Test-Entity"
}
},
"title": "Beispiel Konfig-Eintrag"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "Invalid object ID"
},
"step": {
"init": {
"data": {
"object_id": "Object ID"
},
"description": "Please enter an object_id for the test entity.",
"title": "Pick object id"
},
"name": {
"data": {
"name": "Name"
},
"description": "Please enter a name for the test entity.",
"title": "Name of the entity"
}
},
"title": "Config Entry Example"
}
}

View File

@@ -0,0 +1,11 @@
{
"config": {
"step": {
"name": {
"data": {
"name": "Nimi"
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "\uc624\ube0c\uc81d\ud2b8 ID\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4"
},
"step": {
"init": {
"data": {
"object_id": "\uc624\ube0c\uc81d\ud2b8 ID"
},
"description": "\ud14c\uc2a4\ud2b8 \uad6c\uc131\uc694\uc18c\uc758 \uc624\ube0c\uc81d\ud2b8 ID \ub97c \uc785\ub825\ud558\uc138\uc694",
"title": "\uc624\ube0c\uc81d\ud2b8 ID \uc120\ud0dd"
},
"name": {
"data": {
"name": "\uc774\ub984"
},
"description": "\ud14c\uc2a4\ud2b8 \uad6c\uc131\uc694\uc18c\uc758 \uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694.",
"title": "\uad6c\uc131\uc694\uc18c \uc774\ub984"
}
},
"title": "\uc785\ub825 \uc608\uc81c \uad6c\uc131"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "Ongeldig object ID"
},
"step": {
"init": {
"data": {
"object_id": "Object ID"
},
"description": "Voer een object_id in voor het testen van de entiteit.",
"title": "Kies object id"
},
"name": {
"data": {
"name": "Naam"
},
"description": "Voer een naam in voor het testen van de entiteit.",
"title": "Naam van de entiteit"
}
},
"title": "Voorbeeld van de config vermelding"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "Ugyldig objekt ID"
},
"step": {
"init": {
"data": {
"object_id": "Objekt ID"
},
"description": "Vennligst skriv inn en object_id for testenheten.",
"title": "Velg objekt ID"
},
"name": {
"data": {
"name": "Navn"
},
"description": "Vennligst skriv inn et navn for testenheten.",
"title": "Navn p\u00e5 enheten"
}
},
"title": "Konfigureringseksempel"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "Nieprawid\u0142owy identyfikator obiektu"
},
"step": {
"init": {
"data": {
"object_id": "Identyfikator obiektu"
},
"description": "Prosz\u0119 wprowadzi\u0107 identyfikator obiektu (object_id) dla jednostki testowej.",
"title": "Wybierz identyfikator obiektu"
},
"name": {
"data": {
"name": "Nazwa"
},
"description": "Prosz\u0119 wprowadzi\u0107 nazw\u0119 dla jednostki testowej.",
"title": "Nazwa jednostki"
}
},
"title": "Przyk\u0142ad wpisu do konfiguracji"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"step": {
"init": {
"description": "Introduce\u021bi un obiect_id pentru entitatea testat\u0103.",
"title": "Alege\u021bi id-ul obiectului"
},
"name": {
"data": {
"name": "Nume"
}
}
}
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "Neveljaven ID objekta"
},
"step": {
"init": {
"data": {
"object_id": "ID objekta"
},
"description": "Prosimo, vnesite Id_objekta za testni subjekt.",
"title": "Izberite ID objekta"
},
"name": {
"data": {
"name": "Ime"
},
"description": "Vnesite ime za testni subjekt.",
"title": "Ime subjekta"
}
},
"title": "Primer nastavitve"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "ID \u0111\u1ed1i t\u01b0\u1ee3ng kh\u00f4ng h\u1ee3p l\u1ec7"
},
"step": {
"init": {
"data": {
"object_id": "ID \u0111\u1ed1i t\u01b0\u1ee3ng"
},
"description": "Xin vui l\u00f2ng nh\u1eadp m\u1ed9t object_id cho th\u1eed nghi\u1ec7m th\u1ef1c th\u1ec3.",
"title": "Ch\u1ecdn id \u0111\u1ed1i t\u01b0\u1ee3ng"
},
"name": {
"data": {
"name": "T\u00ean"
},
"description": "Xin vui l\u00f2ng nh\u1eadp t\u00ean cho th\u1eed nghi\u1ec7m th\u1ef1c th\u1ec3.",
"title": "T\u00ean c\u1ee7a th\u1ef1c th\u1ec3"
}
},
"title": "V\u00ed d\u1ee5 v\u1ec1 c\u1ea5u h\u00ecnh th\u1ef1c th\u1ec3"
}
}

View File

@@ -0,0 +1,24 @@
{
"config": {
"error": {
"invalid_object_id": "\u65e0\u6548\u7684\u5bf9\u8c61 ID"
},
"step": {
"init": {
"data": {
"object_id": "\u5bf9\u8c61 ID"
},
"description": "\u8bf7\u4e3a\u6d4b\u8bd5\u8bbe\u5907\u8f93\u5165\u5bf9\u8c61 ID",
"title": "\u8bf7\u9009\u62e9\u5bf9\u8c61 ID"
},
"name": {
"data": {
"name": "\u540d\u79f0"
},
"description": "\u8bf7\u4e3a\u6d4b\u8bd5\u8bbe\u5907\u8f93\u5165\u540d\u79f0",
"title": "\u8bbe\u5907\u540d\u79f0"
}
},
"title": "\u6837\u4f8b\u914d\u7f6e\u6761\u76ee"
}
}

View File

@@ -62,13 +62,11 @@ class ExampleConfigFlow(config_entries.ConfigFlowHandler):
return (yield from self.async_step_name())
errors = {
'object_id': 'Invalid object id.'
'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
}),
@@ -92,9 +90,7 @@ class ExampleConfigFlow(config_entries.ConfigFlowHandler):
)
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
}),

View File

@@ -0,0 +1,24 @@
{
"config": {
"title": "Config Entry Example",
"step": {
"init": {
"title": "Pick object id",
"description": "Please enter an object_id for the test entity.",
"data": {
"object_id": "Object ID"
}
},
"name": {
"title": "Name of the entity",
"description": "Please enter a name for the test entity.",
"data": {
"name": "Name"
}
}
},
"error": {
"invalid_object_id": "Invalid object ID"
}
}
}

View File

@@ -15,7 +15,7 @@ from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \
ATTR_ENTITY_PICTURE
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.util.async import run_callback_threadsafe
from homeassistant.util.async_ import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
_KEY_INSTANCE = 'configurator'

View File

@@ -4,7 +4,6 @@ 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
@@ -67,8 +66,7 @@ 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."""
config = config.get(DOMAIN, {})
intents = hass.data.get(DOMAIN)
@@ -84,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)
async_register(hass, intent.INTENT_TURN_ON,
['Turn {name} on', 'Turn on {name}'])
async_register(hass, intent.INTENT_TURN_OFF,
['Turn {name} off', 'Turn off {name}'])
async_register(hass, intent.INTENT_TOGGLE,
['Toggle {name}', '{name} toggle'])
# 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, {})
@@ -137,7 +159,7 @@ 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)
@@ -153,12 +175,15 @@ class ConversationProcessView(http.HomeAssistantView):
@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

@@ -0,0 +1,271 @@
"""
This platform allows several cover to be grouped into one cover.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.group/
"""
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.cover import (
DOMAIN, PLATFORM_SCHEMA, CoverDevice, ATTR_POSITION,
ATTR_CURRENT_POSITION, ATTR_TILT_POSITION, ATTR_CURRENT_TILT_POSITION,
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION,
SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT,
SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION,
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION,
SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT,
SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION)
from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES,
CONF_ENTITIES, CONF_NAME, STATE_CLOSED)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_state_change
_LOGGER = logging.getLogger(__name__)
KEY_OPEN_CLOSE = 'open_close'
KEY_STOP = 'stop'
KEY_POSITION = 'position'
DEFAULT_NAME = 'Cover Group'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN),
})
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Group Cover platform."""
async_add_devices(
[CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])])
class CoverGroup(CoverDevice):
"""Representation of a CoverGroup."""
def __init__(self, name, entities):
"""Initialize a CoverGroup entity."""
self._name = name
self._is_closed = False
self._cover_position = 100
self._tilt_position = None
self._supported_features = 0
self._assumed_state = True
self._entities = entities
self._covers = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(),
KEY_POSITION: set()}
self._tilts = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(),
KEY_POSITION: set()}
@callback
def update_supported_features(self, entity_id, old_state, new_state,
update_state=True):
"""Update dictionaries with supported features."""
if not new_state:
for values in self._covers.values():
values.discard(entity_id)
for values in self._tilts.values():
values.discard(entity_id)
if update_state:
self.async_schedule_update_ha_state(True)
return
features = new_state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if features & (SUPPORT_OPEN | SUPPORT_CLOSE):
self._covers[KEY_OPEN_CLOSE].add(entity_id)
else:
self._covers[KEY_OPEN_CLOSE].discard(entity_id)
if features & (SUPPORT_STOP):
self._covers[KEY_STOP].add(entity_id)
else:
self._covers[KEY_STOP].discard(entity_id)
if features & (SUPPORT_SET_POSITION):
self._covers[KEY_POSITION].add(entity_id)
else:
self._covers[KEY_POSITION].discard(entity_id)
if features & (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT):
self._tilts[KEY_OPEN_CLOSE].add(entity_id)
else:
self._tilts[KEY_OPEN_CLOSE].discard(entity_id)
if features & (SUPPORT_STOP_TILT):
self._tilts[KEY_STOP].add(entity_id)
else:
self._tilts[KEY_STOP].discard(entity_id)
if features & (SUPPORT_SET_TILT_POSITION):
self._tilts[KEY_POSITION].add(entity_id)
else:
self._tilts[KEY_POSITION].discard(entity_id)
if update_state:
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register listeners."""
for entity_id in self._entities:
new_state = self.hass.states.get(entity_id)
self.update_supported_features(entity_id, None, new_state,
update_state=False)
async_track_state_change(self.hass, self._entities,
self.update_supported_features)
await self.async_update()
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def assumed_state(self):
"""Enable buttons even if at end position."""
return self._assumed_state
@property
def should_poll(self):
"""Disable polling for cover group."""
return False
@property
def supported_features(self):
"""Flag supported features for the cover."""
return self._supported_features
@property
def is_closed(self):
"""Return if all covers in group are closed."""
return self._is_closed
@property
def current_cover_position(self):
"""Return current position for all covers."""
return self._cover_position
@property
def current_cover_tilt_position(self):
"""Return current tilt position for all covers."""
return self._tilt_position
async def async_open_cover(self, **kwargs):
"""Move the covers up."""
data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]}
await self.hass.services.async_call(
DOMAIN, SERVICE_OPEN_COVER, data, blocking=True)
async def async_close_cover(self, **kwargs):
"""Move the covers down."""
data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]}
await self.hass.services.async_call(
DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True)
async def async_stop_cover(self, **kwargs):
"""Fire the stop action."""
data = {ATTR_ENTITY_ID: self._covers[KEY_STOP]}
await self.hass.services.async_call(
DOMAIN, SERVICE_STOP_COVER, data, blocking=True)
async def async_set_cover_position(self, **kwargs):
"""Set covers position."""
data = {ATTR_ENTITY_ID: self._covers[KEY_POSITION],
ATTR_POSITION: kwargs[ATTR_POSITION]}
await self.hass.services.async_call(
DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True)
async def async_open_cover_tilt(self, **kwargs):
"""Tilt covers open."""
data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]}
await self.hass.services.async_call(
DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True)
async def async_close_cover_tilt(self, **kwargs):
"""Tilt covers closed."""
data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]}
await self.hass.services.async_call(
DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True)
async def async_stop_cover_tilt(self, **kwargs):
"""Stop cover tilt."""
data = {ATTR_ENTITY_ID: self._tilts[KEY_STOP]}
await self.hass.services.async_call(
DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True)
async def async_set_cover_tilt_position(self, **kwargs):
"""Set tilt position."""
data = {ATTR_ENTITY_ID: self._tilts[KEY_POSITION],
ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION]}
await self.hass.services.async_call(
DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True)
async def async_update(self):
"""Update state and attributes."""
self._assumed_state = False
self._is_closed = True
for entity_id in self._entities:
state = self.hass.states.get(entity_id)
if not state:
continue
if state.state != STATE_CLOSED:
self._is_closed = False
break
self._cover_position = None
if self._covers[KEY_POSITION]:
position = -1
self._cover_position = 0 if self.is_closed else 100
for entity_id in self._covers[KEY_POSITION]:
state = self.hass.states.get(entity_id)
pos = state.attributes.get(ATTR_CURRENT_POSITION)
if position == -1:
position = pos
elif position != pos:
self._assumed_state = True
break
else:
if position != -1:
self._cover_position = position
self._tilt_position = None
if self._tilts[KEY_POSITION]:
position = -1
self._tilt_position = 100
for entity_id in self._tilts[KEY_POSITION]:
state = self.hass.states.get(entity_id)
pos = state.attributes.get(ATTR_CURRENT_TILT_POSITION)
if position == -1:
position = pos
elif position != pos:
self._assumed_state = True
break
else:
if position != -1:
self._tilt_position = position
supported_features = 0
supported_features |= SUPPORT_OPEN | SUPPORT_CLOSE \
if self._covers[KEY_OPEN_CLOSE] else 0
supported_features |= SUPPORT_STOP \
if self._covers[KEY_STOP] else 0
supported_features |= SUPPORT_SET_POSITION \
if self._covers[KEY_POSITION] else 0
supported_features |= SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT \
if self._tilts[KEY_OPEN_CLOSE] else 0
supported_features |= SUPPORT_STOP_TILT \
if self._tilts[KEY_STOP] else 0
supported_features |= SUPPORT_SET_TILT_POSITION \
if self._tilts[KEY_POSITION] else 0
self._supported_features = supported_features
if not self._assumed_state:
for entity_id in self._entities:
state = self.hass.states.get(entity_id)
if state and state.attributes.get(ATTR_ASSUMED_STATE):
self._assumed_state = True
break

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,8 +49,8 @@ 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 discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices)
@@ -106,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
@@ -147,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
@@ -182,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

@@ -234,7 +234,9 @@ class CoverTemplate(CoverDevice):
None is unknown, 0 is closed, 100 is fully open.
"""
return self._position
if self._position_template or self._position_script:
return self._position
return None
@property
def current_cover_tilt_position(self):

View File

@@ -4,8 +4,6 @@ Support for deCONZ devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/deconz/
"""
import asyncio
import logging
import voluptuous as vol
@@ -19,7 +17,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['pydeconz==30']
REQUIREMENTS = ['pydeconz==32']
_LOGGER = logging.getLogger(__name__)
@@ -57,30 +55,28 @@ Unlock your deCONZ gateway to register with Home Assistant.
"""
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Set up services and configuration for deCONZ component."""
result = False
config_file = yield from hass.async_add_job(
config_file = await hass.async_add_job(
load_json, hass.config.path(CONFIG_FILE))
@asyncio.coroutine
def async_deconz_discovered(service, discovery_info):
async def async_deconz_discovered(service, discovery_info):
"""Call when deCONZ gateway has been found."""
deconz_config = {}
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
yield from async_request_configuration(hass, config, deconz_config)
await async_request_configuration(hass, config, deconz_config)
if config_file:
result = yield from async_setup_deconz(hass, config, config_file)
result = await async_setup_deconz(hass, config, config_file)
if not result and DOMAIN in config and CONF_HOST in config[DOMAIN]:
deconz_config = config[DOMAIN]
if CONF_API_KEY in deconz_config:
result = yield from async_setup_deconz(hass, config, deconz_config)
result = await async_setup_deconz(hass, config, deconz_config)
else:
yield from async_request_configuration(hass, config, deconz_config)
await async_request_configuration(hass, config, deconz_config)
return True
if not result:
@@ -89,8 +85,7 @@ def async_setup(hass, config):
return True
@asyncio.coroutine
def async_setup_deconz(hass, config, deconz_config):
async def async_setup_deconz(hass, config, deconz_config):
"""Set up a deCONZ session.
Load config, group, light and sensor data for server information.
@@ -100,7 +95,7 @@ def async_setup_deconz(hass, config, deconz_config):
from pydeconz import DeconzSession
websession = async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, websession, **deconz_config)
result = yield from deconz.async_load_parameters()
result = await deconz.async_load_parameters()
if result is False:
_LOGGER.error("Failed to communicate with deCONZ")
return False
@@ -113,8 +108,7 @@ def async_setup_deconz(hass, config, deconz_config):
hass, component, DOMAIN, {}, config))
deconz.start()
@asyncio.coroutine
def async_configure(call):
async def async_configure(call):
"""Set attribute of device in deCONZ.
Field is a string representing a specific device in deCONZ
@@ -140,7 +134,7 @@ def async_setup_deconz(hass, config, deconz_config):
if field is None:
_LOGGER.error('Could not find the entity %s', entity_id)
return
yield from deconz.async_put_state(field, data)
await deconz.async_put_state(field, data)
hass.services.async_register(
DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA)
@@ -159,21 +153,19 @@ def async_setup_deconz(hass, config, deconz_config):
return True
@asyncio.coroutine
def async_request_configuration(hass, config, deconz_config):
async def async_request_configuration(hass, config, deconz_config):
"""Request configuration steps from the user."""
configurator = hass.components.configurator
@asyncio.coroutine
def async_configuration_callback(data):
async def async_configuration_callback(data):
"""Set up actions to do when our configuration callback is called."""
from pydeconz.utils import async_get_api_key
api_key = yield from async_get_api_key(hass.loop, **deconz_config)
api_key = await async_get_api_key(hass.loop, **deconz_config)
if api_key:
deconz_config[CONF_API_KEY] = api_key
result = yield from async_setup_deconz(hass, config, deconz_config)
result = await async_setup_deconz(hass, config, deconz_config)
if result:
yield from hass.async_add_job(
await hass.async_add_job(
save_json, hass.config.path(CONFIG_FILE), deconz_config)
configurator.async_request_done(request_id)
return

View File

@@ -118,6 +118,17 @@ def async_setup(hass, config):
tasks2 = []
# Set up history graph
tasks2.append(bootstrap.async_setup_component(
hass, 'history_graph',
{'history_graph': {'switches': {
'name': 'Recent Switches',
'entities': switches,
'hours_to_show': 1,
'refresh': 60
}}}
))
# Set up scripts
tasks2.append(bootstrap.async_setup_component(
hass, 'script',

View File

@@ -28,7 +28,7 @@ from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import get_component
import homeassistant.util as util
from homeassistant.util.async import run_coroutine_threadsafe
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
from homeassistant.util.yaml import dump
@@ -77,11 +77,14 @@ ATTR_MAC = 'mac'
ATTR_NAME = 'name'
ATTR_SOURCE_TYPE = 'source_type'
ATTR_VENDOR = 'vendor'
ATTR_CONSIDER_HOME = 'consider_home'
SOURCE_TYPE_GPS = 'gps'
SOURCE_TYPE_ROUTER = 'router'
SOURCE_TYPE_BLUETOOTH = 'bluetooth'
SOURCE_TYPE_BLUETOOTH_LE = 'bluetooth_le'
SOURCE_TYPES = (SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER,
SOURCE_TYPE_BLUETOOTH, SOURCE_TYPE_BLUETOOTH_LE)
NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any(None, vol.Schema({
vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean,
@@ -96,6 +99,22 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NEW_DEVICE_DEFAULTS,
default={}): NEW_DEVICE_DEFAULTS_SCHEMA
})
SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(vol.All(
cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID), {
ATTR_MAC: cv.string,
ATTR_DEV_ID: cv.string,
ATTR_HOST_NAME: cv.string,
ATTR_LOCATION_NAME: cv.string,
ATTR_GPS: cv.gps,
ATTR_GPS_ACCURACY: cv.positive_int,
ATTR_BATTERY: cv.positive_int,
ATTR_ATTRIBUTES: dict,
ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES),
ATTR_CONSIDER_HOME: cv.time_period,
# Temp workaround for iOS app introduced in 0.65
vol.Optional('battery_status'): str,
vol.Optional('hostname'): str,
}))
@bind_hass
@@ -109,7 +128,7 @@ def is_on(hass: HomeAssistantType, entity_id: str = None):
def see(hass: HomeAssistantType, mac: str = None, dev_id: str = None,
host_name: str = None, location_name: str = None,
gps: GPSType = None, gps_accuracy=None,
battery=None, attributes: dict = None):
battery: int = None, attributes: dict = None):
"""Call service to notify you see device."""
data = {key: value for key, value in
((ATTR_MAC, mac),
@@ -203,12 +222,14 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
@asyncio.coroutine
def async_see_service(call):
"""Service to see a device."""
args = {key: value for key, value in call.data.items() if key in
(ATTR_MAC, ATTR_DEV_ID, ATTR_HOST_NAME, ATTR_LOCATION_NAME,
ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_BATTERY, ATTR_ATTRIBUTES)}
yield from tracker.async_see(**args)
# Temp workaround for iOS, introduced in 0.65
data = dict(call.data)
data.pop('hostname', None)
data.pop('battery_status', None)
yield from tracker.async_see(**data)
hass.services.async_register(DOMAIN, SERVICE_SEE, async_see_service)
hass.services.async_register(
DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA)
# restore
yield from tracker.async_setup_tracked_device()
@@ -240,23 +261,26 @@ class DeviceTracker(object):
dev.mac)
def see(self, mac: str = None, dev_id: str = None, host_name: str = None,
location_name: str = None, gps: GPSType = None, gps_accuracy=None,
battery: str = None, attributes: dict = None,
source_type: str = SOURCE_TYPE_GPS, picture: str = None,
icon: str = None):
location_name: str = None, gps: GPSType = None,
gps_accuracy: int = None, battery: int = None,
attributes: dict = None, source_type: str = SOURCE_TYPE_GPS,
picture: str = None, icon: str = None,
consider_home: timedelta = None):
"""Notify the device tracker that you see a device."""
self.hass.add_job(
self.async_see(mac, dev_id, host_name, location_name, gps,
gps_accuracy, battery, attributes, source_type,
picture, icon)
picture, icon, consider_home)
)
@asyncio.coroutine
def async_see(self, mac: str = None, dev_id: str = None,
host_name: str = None, location_name: str = None,
gps: GPSType = None, gps_accuracy=None, battery: str = None,
attributes: dict = None, source_type: str = SOURCE_TYPE_GPS,
picture: str = None, icon: str = None):
def async_see(
self, mac: str = None, dev_id: str = None, host_name: str = None,
location_name: str = None, gps: GPSType = None,
gps_accuracy: int = None, battery: int = None,
attributes: dict = None, source_type: str = SOURCE_TYPE_GPS,
picture: str = None, icon: str = None,
consider_home: timedelta = None):
"""Notify the device tracker that you see a device.
This method is a coroutine.
@@ -275,7 +299,7 @@ class DeviceTracker(object):
if device:
yield from device.async_seen(
host_name, location_name, gps, gps_accuracy, battery,
attributes, source_type)
attributes, source_type, consider_home)
if device.track:
yield from device.async_update_ha_state()
return
@@ -283,7 +307,7 @@ class DeviceTracker(object):
# If no device can be found, create it
dev_id = util.ensure_unique_string(dev_id, self.devices.keys())
device = Device(
self.hass, self.consider_home, self.track_new,
self.hass, consider_home or self.consider_home, self.track_new,
dev_id, mac, (host_name or dev_id).replace('_', ' '),
picture=picture, icon=icon,
hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE))
@@ -384,9 +408,10 @@ class Device(Entity):
host_name = None # type: str
location_name = None # type: str
gps = None # type: GPSType
gps_accuracy = 0
gps_accuracy = 0 # type: int
last_seen = None # type: dt_util.dt.datetime
battery = None # type: str
consider_home = None # type: dt_util.dt.timedelta
battery = None # type: int
attributes = None # type: dict
vendor = None # type: str
icon = None # type: str
@@ -476,14 +501,16 @@ class Device(Entity):
@asyncio.coroutine
def async_seen(self, host_name: str = None, location_name: str = None,
gps: GPSType = None, gps_accuracy=0, battery: str = None,
gps: GPSType = None, gps_accuracy=0, battery: int = None,
attributes: dict = None,
source_type: str = SOURCE_TYPE_GPS):
source_type: str = SOURCE_TYPE_GPS,
consider_home: timedelta = None):
"""Mark the device as seen."""
self.source_type = source_type
self.last_seen = dt_util.utcnow()
self.host_name = host_name
self.location_name = location_name
self.consider_home = consider_home or self.consider_home
if battery:
self.battery = battery

View File

@@ -283,15 +283,15 @@ class SshConnection(_Connection):
lines = self._ssh.before.split(b'\n')[1:-1]
return [line.decode('utf-8') for line in lines]
except exceptions.EOF as err:
_LOGGER.error("Connection refused. SSH enabled?")
_LOGGER.error("Connection refused. %s", self._ssh.before)
self.disconnect()
return None
except pxssh.ExceptionPxssh as err:
_LOGGER.error("Unexpected SSH error: %s", str(err))
_LOGGER.error("Unexpected SSH error: %s", err)
self.disconnect()
return None
except AssertionError as err:
_LOGGER.error("Connection to router unavailable: %s", str(err))
_LOGGER.error("Connection to router unavailable: %s", err)
self.disconnect()
return None
@@ -301,10 +301,10 @@ class SshConnection(_Connection):
self._ssh = pxssh.pxssh()
if self._ssh_key:
self._ssh.login(self._host, self._username,
self._ssh.login(self._host, self._username, quiet=False,
ssh_key=self._ssh_key, port=self._port)
else:
self._ssh.login(self._host, self._username,
self._ssh.login(self._host, self._username, quiet=False,
password=self._password, port=self._port)
super().connect()

View File

@@ -189,10 +189,12 @@ class Icloud(DeviceScanner):
for device in self.api.devices:
status = device.status(DEVICESTATUSSET)
devicename = slugify(status['name'].replace(' ', '', 99))
if devicename not in self.devices:
self.devices[devicename] = device
self._intervals[devicename] = 1
self._overridestates[devicename] = None
if devicename in self.devices:
_LOGGER.error('Multiple devices with name: %s', devicename)
continue
self.devices[devicename] = device
self._intervals[devicename] = 1
self._overridestates[devicename] = None
except PyiCloudNoDevicesException:
_LOGGER.error('No iCloud Devices found!')
@@ -319,14 +321,6 @@ class Icloud(DeviceScanner):
def determine_interval(self, devicename, latitude, longitude, battery):
"""Calculate new interval."""
distancefromhome = None
zone_state = self.hass.states.get('zone.home')
zone_state_lat = zone_state.attributes['latitude']
zone_state_long = zone_state.attributes['longitude']
distancefromhome = distance(
latitude, longitude, zone_state_lat, zone_state_long)
distancefromhome = round(distancefromhome / 1000, 1)
currentzone = active_zone(self.hass, latitude, longitude)
if ((currentzone is not None and
@@ -335,22 +329,48 @@ class Icloud(DeviceScanner):
self._overridestates.get(devicename) == 'away')):
return
zones = (self.hass.states.get(entity_id) for entity_id
in sorted(self.hass.states.entity_ids('zone')))
distances = []
for zone_state in zones:
zone_state_lat = zone_state.attributes['latitude']
zone_state_long = zone_state.attributes['longitude']
zone_distance = distance(
latitude, longitude, zone_state_lat, zone_state_long)
distances.append(round(zone_distance / 1000, 1))
if distances:
mindistance = min(distances)
else:
mindistance = None
self._overridestates[devicename] = None
if currentzone is not None:
self._intervals[devicename] = 30
return
if distancefromhome is None:
if mindistance is None:
return
if distancefromhome > 25:
self._intervals[devicename] = round(distancefromhome / 2, 0)
elif distancefromhome > 10:
self._intervals[devicename] = 5
else:
self._intervals[devicename] = 1
if battery is not None and battery <= 33 and distancefromhome > 3:
self._intervals[devicename] = self._intervals[devicename] * 2
# Calculate out how long it would take for the device to drive to the
# nearest zone at 120 km/h:
interval = round(mindistance / 2, 0)
# Never poll more than once per minute
interval = max(interval, 1)
if interval > 180:
# Three hour drive? This is far enough that they might be flying
# home - check every half hour
interval = 30
if battery is not None and battery <= 33 and mindistance > 3:
# Low battery - let's check half as often
interval = interval * 2
self._intervals[devicename] = interval
def update_device(self, devicename):
"""Update the device_tracker entity."""

View File

@@ -73,7 +73,8 @@ class MikrotikScanner(DeviceScanner):
self.host,
self.username,
self.password,
port=int(self.port)
port=int(self.port),
encoding='utf-8'
)
try:

View File

@@ -68,22 +68,18 @@ class TadoDeviceScanner(DeviceScanner):
self.websession = async_create_clientsession(
hass, cookie_jar=aiohttp.CookieJar(unsafe=True, loop=hass.loop))
self.success_init = self._update_info()
self.success_init = asyncio.run_coroutine_threadsafe(
self._async_update_info(), hass.loop
).result()
_LOGGER.info("Scanner initialized")
@asyncio.coroutine
def async_scan_devices(self):
async def async_scan_devices(self):
"""Scan for devices and return a list containing found device ids."""
info = self._update_info()
# Don't yield if we got None
if info is not None:
yield from info
await self._async_update_info()
return [device.mac for device in self.last_results]
@asyncio.coroutine
def async_get_device_name(self, device):
async def async_get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
filter_named = [result.name for result in self.last_results
if result.mac == device]
@@ -93,7 +89,7 @@ class TadoDeviceScanner(DeviceScanner):
return None
@Throttle(MIN_TIME_BETWEEN_SCANS)
def _update_info(self):
async def _async_update_info(self):
"""
Query Tado for device marked as at home.
@@ -104,21 +100,21 @@ class TadoDeviceScanner(DeviceScanner):
last_results = []
try:
with async_timeout.timeout(10, loop=self.hass.loop):
with async_timeout.timeout(10):
# Format the URL here, so we can log the template URL if
# anything goes wrong without exposing username and password.
url = self.tadoapiurl.format(
home_id=self.home_id, username=self.username,
password=self.password)
response = yield from self.websession.get(url)
response = await self.websession.get(url)
if response.status != 200:
_LOGGER.warning(
"Error %d on %s.", response.status, self.tadoapiurl)
return
return False
tado_json = yield from response.json()
tado_json = await response.json()
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Cannot load Tado data")
@@ -139,7 +135,7 @@ class TadoDeviceScanner(DeviceScanner):
self.last_results = last_results
_LOGGER.info(
_LOGGER.debug(
"Tado presence query successful, %d device(s) at home",
len(self.last_results)
)

View File

@@ -44,14 +44,15 @@ class TeslaDeviceTracker(object):
_LOGGER.debug("Updating device position: %s", name)
dev_id = slugify(device.uniq_name)
location = device.get_location()
lat = location['latitude']
lon = location['longitude']
attrs = {
'trackr_id': dev_id,
'id': dev_id,
'name': name
}
self.see(
dev_id=dev_id, host_name=name,
gps=(lat, lon), attributes=attrs
)
if location:
lat = location['latitude']
lon = location['longitude']
attrs = {
'trackr_id': dev_id,
'id': dev_id,
'name': name
}
self.see(
dev_id=dev_id, host_name=name,
gps=(lat, lon), attributes=attrs
)

View File

@@ -23,7 +23,8 @@ CONF_DHCP_SOFTWARE = 'dhcp_software'
DEFAULT_DHCP_SOFTWARE = 'dnsmasq'
DHCP_SOFTWARES = [
'dnsmasq',
'odhcpd'
'odhcpd',
'none'
]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -40,8 +41,10 @@ def get_scanner(hass, config):
dhcp_sw = config[DOMAIN][CONF_DHCP_SOFTWARE]
if dhcp_sw == 'dnsmasq':
scanner = DnsmasqUbusDeviceScanner(config[DOMAIN])
else:
elif dhcp_sw == 'odhcpd':
scanner = OdhcpdUbusDeviceScanner(config[DOMAIN])
else:
scanner = UbusDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
@@ -92,8 +95,8 @@ class UbusDeviceScanner(DeviceScanner):
return self.last_results
def _generate_mac2name(self):
"""Must be implemented depending on the software."""
raise NotImplementedError
"""Return empty MAC to name dict. Overriden if DHCP server is set."""
self.mac2name = dict()
@_refresh_on_access_denied
def get_device_name(self, device):

View File

@@ -98,7 +98,8 @@ class UnifiScanner(DeviceScanner):
# Filter clients to provided SSID list
if self._ssid_filter:
clients = [client for client in clients
if client['essid'] in self._ssid_filter]
if 'essid' in client and
client['essid'] in self._ssid_filter]
self._clients = {
client['mac']: client

View File

@@ -6,7 +6,6 @@ Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
Knows which components handle certain types, will make sure they are
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
"""
import asyncio
import json
from datetime import timedelta
import logging
@@ -21,7 +20,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==1.2.4']
REQUIREMENTS = ['netdisco==1.3.0']
DOMAIN = 'discovery'
@@ -39,6 +38,7 @@ SERVICE_TELLDUSLIVE = 'tellstick'
SERVICE_HUE = 'philips_hue'
SERVICE_DECONZ = 'deconz'
SERVICE_DAIKIN = 'daikin'
SERVICE_SAMSUNG_PRINTER = 'samsung_printer'
SERVICE_HANDLERS = {
SERVICE_HASS_IOS_APP: ('ios', None),
@@ -54,6 +54,7 @@ SERVICE_HANDLERS = {
SERVICE_HUE: ('hue', None),
SERVICE_DECONZ: ('deconz', None),
SERVICE_DAIKIN: ('daikin', None),
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
@@ -71,6 +72,7 @@ SERVICE_HANDLERS = {
'sabnzbd': ('sensor', 'sabnzbd'),
'bose_soundtouch': ('media_player', 'soundtouch'),
'bluesound': ('media_player', 'bluesound'),
'songpal': ('media_player', 'songpal'),
}
CONF_IGNORE = 'ignore'
@@ -83,8 +85,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Start a discovery service."""
from netdisco.discovery import NetworkDiscovery
@@ -98,8 +99,7 @@ def async_setup(hass, config):
# Platforms ignore by config
ignored_platforms = config[DOMAIN][CONF_IGNORE]
@asyncio.coroutine
def new_service_found(service, info):
async def new_service_found(service, info):
"""Handle a new service if one is found."""
if service in ignored_platforms:
logger.info("Ignoring service: %s %s", service, info)
@@ -123,15 +123,14 @@ def async_setup(hass, config):
component, platform = comp_plat
if platform is None:
yield from async_discover(hass, service, info, component, config)
await async_discover(hass, service, info, component, config)
else:
yield from async_load_platform(
await async_load_platform(
hass, component, platform, info, config)
@asyncio.coroutine
def scan_devices(now):
async def scan_devices(now):
"""Scan for devices."""
results = yield from hass.async_add_job(_discover, netdisco)
results = await hass.async_add_job(_discover, netdisco)
for result in results:
hass.async_add_job(new_service_found(*result))

View File

@@ -13,7 +13,7 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.components.http import HomeAssistantView
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['DoorBirdPy==0.1.2']
REQUIREMENTS = ['DoorBirdPy==0.1.3']
_LOGGER = logging.getLogger(__name__)

View File

@@ -25,6 +25,8 @@ ATTR_OVERWRITE = 'overwrite'
CONF_DOWNLOAD_DIR = 'download_dir'
DOMAIN = 'downloader'
DOWNLOAD_FAILED_EVENT = 'download_failed'
DOWNLOAD_COMPLETED_EVENT = 'download_completed'
SERVICE_DOWNLOAD_FILE = 'download_file'
@@ -133,9 +135,19 @@ def setup(hass, config):
fil.write(chunk)
_LOGGER.debug("Downloading of %s done", url)
hass.bus.fire(
"{}_{}".format(DOMAIN, DOWNLOAD_COMPLETED_EVENT), {
'url': url,
'filename': filename
})
except requests.exceptions.ConnectionError:
_LOGGER.exception("ConnectionError occurred for %s", url)
hass.bus.fire(
"{}_{}".format(DOMAIN, DOWNLOAD_FAILED_EVENT), {
'url': url,
'filename': filename
})
# Remove file if we started downloading but failed
if final_path and os.path.isfile(final_path):

View File

@@ -16,7 +16,7 @@ from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
from homeassistant.util.json import save_json
REQUIREMENTS = ['python-ecobee-api==0.0.15']
REQUIREMENTS = ['python-ecobee-api==0.0.17']
_CONFIGURING = {}
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,123 @@
"""
Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/egardia/
"""
import logging
import requests
import voluptuous as vol
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_PORT, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_NAME,
EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['pythonegardia==1.0.39']
_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'
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'
EGARDIA_SERVER = 'egardia_server'
EGARDIA_DEVICE = 'egardiadevice'
EGARDIA_NAME = 'egardianame'
EGARDIA_REPORT_SERVER_ENABLED = 'egardia_rs_enabled'
EGARDIA_REPORT_SERVER_CODES = 'egardia_rs_codes'
NOTIFICATION_ID = 'egardia_notification'
NOTIFICATION_TITLE = 'Egardia'
ATTR_DISCOVER_DEVICES = 'egardia_sensor'
SERVER_CODE_SCHEMA = vol.Schema({
vol.Optional('arm'): vol.All(cv.ensure_list_csv, [cv.string]),
vol.Optional('disarm'): vol.All(cv.ensure_list_csv, [cv.string]),
vol.Optional('armhome'): vol.All(cv.ensure_list_csv, [cv.string]),
vol.Optional('triggered'): vol.All(cv.ensure_list_csv, [cv.string]),
vol.Optional('ignore'): vol.All(cv.ensure_list_csv, [cv.string])
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
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, default={}): SERVER_CODE_SCHEMA,
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,
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Set up the Egardia platform."""
from pythonegardia import egardiadevice
from pythonegardia import egardiaserver
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
host = conf.get(CONF_HOST)
port = conf.get(CONF_PORT)
version = conf.get(CONF_VERSION)
rs_enabled = conf.get(CONF_REPORT_SERVER_ENABLED)
rs_port = conf.get(CONF_REPORT_SERVER_PORT)
try:
device = hass.data[EGARDIA_DEVICE] = egardiadevice.EgardiaDevice(
host, port, username, password, '', version)
except requests.exceptions.RequestException:
_LOGGER.error("An error occurred accessing your Egardia device. " +
"Please check config.")
return False
except egardiadevice.UnauthorizedError:
_LOGGER.error("Unable to authorize. Wrong password or username.")
return False
# Set up the egardia server if enabled
if rs_enabled:
_LOGGER.debug("Setting up EgardiaServer")
try:
if EGARDIA_SERVER 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[EGARDIA_SERVER] = server
server.start()
def handle_stop_event(event):
"""Callback function for HA stop event."""
server.stop()
# listen to home assistant stop event
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, handle_stop_event)
except IOError:
_LOGGER.error("Binding error occurred while starting " +
"EgardiaServer.")
return False
discovery.load_platform(hass, 'alarm_control_panel', DOMAIN,
discovered=conf, hass_config=config)
# get the sensors from the device and add those
sensors = device.getsensors()
discovery.load_platform(hass, 'binary_sensor', DOMAIN,
{ATTR_DISCOVER_DEVICES: sensors}, config)
return True

View File

@@ -4,7 +4,6 @@ Support for local control of entities by emulating the Phillips Hue bridge.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/emulated_hue/
"""
import asyncio
import logging
import voluptuous as vol
@@ -111,17 +110,15 @@ def setup(hass, yaml_config):
config.upnp_bind_multicast, config.advertise_ip,
config.advertise_port)
@asyncio.coroutine
def stop_emulated_hue_bridge(event):
async def stop_emulated_hue_bridge(event):
"""Stop the emulated hue bridge."""
upnp_listener.stop()
yield from server.stop()
await server.stop()
@asyncio.coroutine
def start_emulated_hue_bridge(event):
async def start_emulated_hue_bridge(event):
"""Start the emulated hue bridge."""
upnp_listener.start()
yield from server.start()
await server.start()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge)

View File

@@ -0,0 +1,96 @@
"""
Support for INSTEON fans via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan.insteon_plm/
"""
import asyncio
import logging
from homeassistant.components.fan import (SPEED_OFF,
SPEED_LOW,
SPEED_MEDIUM,
SPEED_HIGH,
FanEntity,
SUPPORT_SET_SPEED)
from homeassistant.const import STATE_OFF
from homeassistant.components.insteon_plm import InsteonPLMEntity
DEPENDENCIES = ['insteon_plm']
SPEED_TO_HEX = {SPEED_OFF: 0x00,
SPEED_LOW: 0x3f,
SPEED_MEDIUM: 0xbe,
SPEED_HIGH: 0xff}
FAN_SPEEDS = [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
_LOGGER = logging.getLogger(__name__)
@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']
address = discovery_info['address']
device = plm.devices[address]
state_key = discovery_info['state_key']
_LOGGER.debug('Adding device %s entity %s to Fan platform',
device.address.hex, device.states[state_key].name)
new_entity = InsteonPLMFan(device, state_key)
async_add_devices([new_entity])
class InsteonPLMFan(InsteonPLMEntity, FanEntity):
"""An INSTEON fan component."""
@property
def speed(self) -> str:
"""Return the current speed."""
return self._hex_to_speed(self._insteon_device_state.value)
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return FAN_SPEEDS
@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_SET_SPEED
@asyncio.coroutine
def async_turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the entity."""
if speed is None:
speed = SPEED_MEDIUM
yield from self.async_set_speed(speed)
@asyncio.coroutine
def async_turn_off(self, **kwargs) -> None:
"""Turn off the entity."""
yield from self.async_set_speed(SPEED_OFF)
@asyncio.coroutine
def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
fan_speed = SPEED_TO_HEX[speed]
if fan_speed == 0x00:
self._insteon_device_state.off()
else:
self._insteon_device_state.set_level(fan_speed)
@staticmethod
def _hex_to_speed(speed: int):
hex_speed = SPEED_OFF
if speed > 0xfe:
hex_speed = SPEED_HIGH
elif speed > 0x7f:
hex_speed = SPEED_MEDIUM
elif speed > 0:
hex_speed = SPEED_LOW
return hex_speed

View File

@@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
REQUIREMENTS = ['python-miio==0.3.7']
REQUIREMENTS = ['python-miio==0.3.8']
ATTR_TEMPERATURE = 'temperature'
ATTR_HUMIDITY = 'humidity'

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