Compare commits

..

298 Commits

Author SHA1 Message Date
Paulus Schoutsen
8ceb57752b Merge pull request #14876 from home-assistant/rc
0.71.0
2018-06-08 18:07:39 -04:00
Paulus Schoutsen
bd1af8c3d8 Version bump to 0.71.0 2018-06-08 16:57:59 -04:00
Paulus Schoutsen
19a30b0ce6 Version bump to 0.71.0b1 2018-06-08 15:04:58 -04:00
Robert Svensson
9a659a5d1d Zone - Hass configuration name is optional (#14449)
* Hass configuration name is optional

* Check explicitly if name is none

* Reverted back to old logic for zones configured in configuration.yaml, where many zones can have the same name

* New test to verify use case of allowing multiple zones having the same name

* Fix too long line
2018-06-08 15:04:42 -04:00
Paulus Schoutsen
1cfd770b95 Use hass iconset (#14185) 2018-06-08 15:04:41 -04:00
Paulus Schoutsen
3fda97eed7 Bump frontend to 20180608.0b0 2018-06-08 15:04:28 -04:00
Paulus Schoutsen
2dc40fe16e Version bump to 0.71.0b0 2018-06-03 16:53:48 -04:00
Paulus Schoutsen
bf8376ddcb Merge remote-tracking branch 'origin/master' into dev 2018-06-03 16:53:17 -04:00
Mattias Welponer
8f696193f0 Add homematicip_cloud illuminance sensor (#14720)
* Add iluminance sensor and device_class for sensors

* Fix lint
2018-06-03 18:48:51 +02:00
Paulus Schoutsen
70edb2492a Version bump to 20180603.0 2018-06-03 12:29:57 -04:00
quthla
e35d4beb95 Fix media_title empty when title is empty but label is set (#14791) 2018-06-03 09:27:17 -04:00
quthla
919b431a24 Add Kodi OnResume event (#14790) 2018-06-03 09:26:23 -04:00
Jason Woodford
7f59a8ea0c Update total-connect-client to 0.18 for Honeywell Lynx Touch-Wifi support (#14778) 2018-06-03 13:55:49 +02:00
Sebastian Muszynski
1ac3f0da63 Ignore the mistaken long_click event of the 86sw (Closes: #14694) (#14785) 2018-06-03 11:54:03 +02:00
Jason Hu
12e679c14d Assign device class to nest sensors (#14746)
* Assign device class to nest sensors

sensor/nest.NestSensor => /nest.NestSensorDevice,
so that BinarySensor platform do not reference Sensor platform anymore

* Resolve code review comment

* Follow code review comment
2018-06-03 03:54:48 +02:00
Fabian Affolter
28ef94c3fa Update syntax (#14772) 2018-06-02 15:08:10 +02:00
Fabian Affolter
27df4cca6c Upgrade shodan to 1.8.1 (#14760) 2018-06-02 08:34:47 -04:00
Fabian Affolter
a8413249c2 Upgrade sqlalchemy to 1.2.8 (#14765) 2018-06-02 08:34:30 -04:00
Fabian Affolter
f2dacb2570 Upgrade youtube_dl to 2018.06.02 (#14763) 2018-06-02 08:33:48 -04:00
Fabian Affolter
5aaf81f2c9 Upgrade Sphinx to 1.7.5 (#14764) 2018-06-02 08:31:43 -04:00
Fabian Affolter
b86cd325fe Update syntax (#14770) 2018-06-02 08:31:06 -04:00
Fabian Affolter
74b7dabf2d Update syntax (#14768) 2018-06-02 08:30:54 -04:00
Fabian Affolter
1ce4c2092a Update syntax (#14771) 2018-06-02 08:30:07 -04:00
Fabian Affolter
875e05ff38 Remove swagger file (#14762) 2018-06-02 08:29:38 -04:00
Mick Vleeshouwer
fe0e49db4b Update postnl api to 1.0.2 (#14769) 2018-06-02 13:45:48 +02:00
Fabian Affolter
ad86e68c1e Update syntax of platform random (#14767) 2018-06-02 12:00:01 +02:00
Tristan Caulfield
e7985c970b Upgrade directpy to 0.5 (#14750)
* Version Requirement bump

Bump required version to 0.5 to allow component to work with Genie Mini clients using the clientAddr variable.

* Ran script/gen_requirements_all.py as requested.
2018-06-02 09:30:15 +02:00
austinlg96
cfac537f51 Added option to block Osram Lightify individual lights in the same way that groups can be (#14470) 2018-06-02 09:23:51 +02:00
Fabian Affolter
d6e76969cc Tweak about the requirements 2018-06-01 23:33:04 +02:00
Fabian Affolter
77dca8272c Upgrade blockchain to 1.4.4 (#14738) 2018-06-01 19:41:35 +02:00
Fabian Affolter
3b8ee196be Update syntax (#14742) 2018-06-01 19:41:20 +02:00
michaeldavie
4935043f4a Add battery attribute to Sensibo (#14735)
* Added battery attribute

* Simplify current_battery
2018-06-01 19:41:04 +02:00
Matt Schmitt
f5d74e07d5 Add support for outlets in HomeKit (#14628) 2018-06-01 18:04:54 +02:00
Paulus Schoutsen
0a724a5473 Update frontend 2018-06-01 10:52:25 -04:00
Jason Hu
cba8333a13 Change nest to cloud push (#14656)
* Change nest component to Cloud Push

Change sensors.nest, binary_sensors.nest and climate.nest to push mode
nest camera still need poll to update snapshot image
Also change nest component to async

* Flake8 lint

* Fix async_notify_errors, it is not a coroutine

* Fix pylint

* Fix pylint, function name should shall shorter than 32

* Use dispatcher helper instead event bus

* Use async_update_ha_state(True)

* Refactoring load_platform

Move service registration into async_setup_nest(),
 resolve an issue that before the first time configuration done,
 set_mode service should not be registered

* Fix an issue that authorization failure may leave a blocked thread

* Pylinting

* async_nest_update_callback => async_update_state to avoid confusion

* Move signal handler register to async_added_to_hass

* Better handle nest api error

* Remove unnecessary register for binary_sensor

* Remove unused import

* Upgrade to python-nest 4.0.1

Fix a thread race condition issue

* Address my own comments

* Address my own comment
2018-06-01 10:44:58 -04:00
Anders Melchiorsen
fcbc399809 Disallow automation.trigger without entity_id (#14724) 2018-06-01 10:27:12 -04:00
Paulus Schoutsen
f6eb9e79d5 Custom panel (#14708)
* Add support for custom panels in JS

* Allow specifying JS custom panels

* Add trust external option

* Fix tests

* Do I/O outside event loop

* Change config to avoid breaking change
2018-06-01 10:06:17 -04:00
roiff
ab3717af76 Homekit Thermostat: Better support for temperature ranges (#14679)
* Support for obtaining temperature range
* Fallback to Defaults
* Fixed unit conversion
* Added test
2018-06-01 13:49:16 +02:00
Pierre Ståhl
6cd69b413c Bump pyatv to 0.3.10 (#14736)
* Bump pyatv to 0.3.10

* Update requirements_all.txt
2018-06-01 08:41:40 +02:00
Fabian Affolter
de56a0d021 Upgrade shodan to 1.8.0 (#14717) 2018-06-01 08:40:27 +02:00
glenn20
99fdd3e358 Add device_descriptor and device_name to keyboard event (#14642)
* Add device_descriptor and device_name to keyboard event

This allows automations to identify which device has generated the
keypress. This is especially useful for bluetooth remotes to control different
devices.

* Remove line breaks

* Fix
2018-06-01 00:32:09 +02:00
Paulus Schoutsen
9a3107aa66 Merge pull request #14727 from home-assistant/rc
0.70.1
2018-05-31 18:11:34 -04:00
Paulus Schoutsen
d31e01b877 Revert "Remove simplepush.io (#14358)"
This reverts commit 612a37b2dd.
2018-05-31 18:11:15 -04:00
Paulus Schoutsen
f8c8900297 Merge pull request #14728 from home-assistant/reinstate-simplepush
Revert "Remove simplepush.io (#14358)"
2018-05-31 18:09:50 -04:00
Paulus Schoutsen
ed9cf994c2 Revert "Remove simplepush.io (#14358)"
This reverts commit 612a37b2dd.
2018-05-31 17:58:03 -04:00
Paulus Schoutsen
d4a4938fce Version bump to 0.70.1 2018-05-31 17:27:55 -04:00
Paulus Schoutsen
f7f0138cff Bump frontend to 20180531.0 2018-05-31 17:27:44 -04:00
Marius
753ffdaffd Fix Eco mode display on Nest (#14706)
* Fix Eco mode display on Nest

* Fix Hound problems
2018-05-31 17:27:35 -04:00
Otto Winter
40aba3d785 MQTT Cover Fix Assumed State (#14672) 2018-05-31 17:27:35 -04:00
Anders Melchiorsen
64f157a036 Ignore unsupported Sonos favorite lists (#14665) 2018-05-31 17:27:35 -04:00
MizterB
0eddd287c5 Update Hue platform to aiohue 1.5.0, and re-implement logic for duplicate scene names. (#14653) 2018-05-31 17:27:34 -04:00
Marius
f32b50cb80 Fix Eco mode display on Nest (#14706)
* Fix Eco mode display on Nest

* Fix Hound problems
2018-05-31 17:26:59 -04:00
Paulus Schoutsen
a58a566ae8 Bump frontend to 20180531.0 2018-05-31 17:25:35 -04:00
Diogo Gomes
2f1d40e014 Merge pull request #14721 from PhilRW/climate-constants
Change climate default limits to constants
2018-05-31 22:19:25 +01:00
Fabian Affolter
14ee6178f9 Add Flock notification platform (#14533)
* Add Flock notification platform

* Use async syntax and move session and loop
2018-05-31 23:07:50 +02:00
Philip Rosenberg-Watt
753fe8279b Remove deprecated comments 2018-05-31 13:59:26 -06:00
Philip Rosenberg-Watt
cc264f415e Fix PEP-8 issues 2018-05-31 11:32:31 -06:00
Philip Rosenberg-Watt
dae90abb34 Change climate default limits to constants
Min and max temp and humidity are now defined in climate __init__.py
and are available for import in subclasses.
2018-05-31 11:23:04 -06:00
Aaron Bach
60f692c7bb Fixes (and stabilizes) some incorrect zone codes in RainMachine (#14719)
* Fixes (and stabilizes) some incorrect zone codes

* Fixed a misspelling
2018-05-31 18:55:50 +02:00
c727
7094d6d61e Change ACP code_format to None|"Number"|"Any" (#14686) 2018-05-31 14:31:40 +02:00
Paulus Schoutsen
08fc73aa20 Bump to 0.71.0.dev0 2018-05-30 11:19:27 -04:00
Michael Nosthoff
c14e41f431 Netatmo Sensor: Implement device_class (#14634)
added device_class and removed icon for temperature and humidity.
2018-05-30 10:53:35 -04:00
cdce8p
f1f4d80f24 Homekit Bugfixes (#14689)
* Fix async bug
* Fix debounce bug
2018-05-30 12:39:27 +02:00
Paulus Schoutsen
e746b92e0e Fix deprecated code (#14681) 2018-05-29 23:14:58 +02:00
cdce8p
7d2563eb1f Update HAP-python to 2.2.2 (#14674)
* Pass driver to accessory
* Added 'hk_driver' fixture for tests
2018-05-29 22:43:26 +02:00
Aaron Bach
084b3287ab Add sensors and services to RainMachine (#14326)
* Starting to add attributes

* All attributes added to programs

* Basic zone attributes in place

* Added advanced properties for zones

* We shouldn't calculate the MAC with every entity

* Small fixes

* Basic framework for push in play

* I THINK IT'S WORKING

* Some state cleanup

* Restart

* Restart part 2

* Added stub for service schema

* Update

* Added services

* Small service description update

* Lint

* Updated CODEOWNERS

* Moving to async methods

* Fixed coverage test

* Lint

* Removed unnecessary hass reference

* Lint

* Lint

* Round 1 of Owner-requested changes

* Round 2 of Owner-requested changes

* Round 3 of Owner-requested changes

* Round 4 (final for now) of Owner-requested changes

* Hound

* Updated package requirements

* Lint

* Collaborator-requested changes

* Collaborator-requested changes

* More small tweaks

* One more small tweak

* Bumping Travis and Coveralls
2018-05-29 21:02:16 +02:00
Thibault Cohen
4105429639 Add asyncio support for Ebox (#14183)
* Fix Ebox sensor

* Fix #14183 comments

* Update ebox.py

* Update ebox.py

* Continue fixing comments
2018-05-29 10:23:12 -04:00
Robert Svensson
8c93b484c4 deCONZ - Option to load or not to load clip sensors on start (#14480)
* Option to load or not to load clip sensors on start

* Full flow

* Fix config flow and add tests

* Fix attribute dark reporting properly

* Imported and properly configured deCONZ shouldn't need extra input to create config entry
2018-05-29 10:09:53 -04:00
Fabian Affolter
3b38de63ea Allow user-defined sensors (#14613)
* Allow user-defined sensors

* Require element for resources

* Don't use .get()
2018-05-29 10:03:00 -04:00
Alexei Chetroi
eff1d1f14e zha: fix temperature rounding for ZHA temperature sensors. (#14669) 2018-05-29 09:05:07 -04:00
Otto Winter
fcb60d472e MQTT Cover Fix Assumed State (#14672) 2018-05-29 09:03:45 -04:00
Anders Melchiorsen
f2a2f2cca5 Ignore unsupported Sonos favorite lists (#14665) 2018-05-29 10:15:30 +02:00
Paulus Schoutsen
8c7f0669c6 Allow hassio frontend development (#14675)
* Allow hassio frontend development

* Fix tests
2018-05-29 08:51:08 +02:00
Matthew Garrett
d36c7c3de7 Increase Eufy's requirement on lakeside (#14671)
python-lakeside was broken with at least some versions of the Python
protobuf code, so bump the requirement to a fixed version.
2018-05-29 08:42:27 +02:00
Bakkoda
79efb0e607 Update mfi.py (#14667)
Add ability to read door sensor states from the mPort.
2018-05-29 07:51:14 +02:00
Robert Accettura
9bc26e93a4 Add pin pad to alarm panel (#14178)
* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Update regex

* Update regex

* Update regex

* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Fix typos

* Fix typos

* Fix typos

* Add pin pad to alarm panel

* Fix errors
2018-05-29 07:50:27 +02:00
Andrey
6c3e2021df Give unknown zwave nodes a better name (#14353)
* Give unknown zwave nodes a better name

* Update util.py
2018-05-28 21:49:38 -04:00
Enrico Berndt
07255a29b4 Add tv channel and volume level for philips js API 5 (#14276)
* PhilipsTV API 5: Added tv channel change and setting of volume level.

* set_volume only sets volume via api now and nothing else.
2018-05-28 10:41:51 -04:00
Alexei Chetroi
144bb3492a zha/light: Properly parse currentX and currentY on async_update() (#14605) 2018-05-28 10:32:47 -04:00
cdce8p
6f4dd7b057 Improve Homekit media_player options (#14637)
* Optimize imports

* Optimize name

* Optimize config schema

* Rename mode to feature

* Replace mode with feature_list
2018-05-28 10:26:33 -04:00
David F. Mulcahey
27f3285d17 Force update ZHA electrical sensor (#14649)
* force state update because we have a real reading

* hound

* docstring
2018-05-28 10:22:29 -04:00
MizterB
9a87e62e0e Update Hue platform to aiohue 1.5.0, and re-implement logic for duplicate scene names. (#14653) 2018-05-28 10:21:00 -04:00
koreth
9044a9157f Reduce log churn from Envisalink binary sensors (#14659)
The Envisalink binary sensor was logging events with a relative
timestamp that updated every time it polled, so even when nothing
new was happening, the event log would be full of meaningless
state changes. Modify the sensor code to use an absolute time
which stays stable when there isn't new activity.
2018-05-28 10:19:03 -04:00
Michaël Arnauts
799ae894a8 Remove docker prereqs scripts that only install a package. Add informational message for this. (#14661) 2018-05-28 10:17:01 -04:00
Fabian Affolter
bff1e1ff6c Upgrade python_opendata_transport to 0.1.0 (#14652) 2018-05-28 08:17:10 +02:00
Fabian Affolter
cc2437614b Upgrade youtube_dl to 2018.05.26 (#14654) 2018-05-28 08:16:55 +02:00
Paulus Schoutsen
0700886d1a Merge pull request #14657 from home-assistant/rc
0.70.0
2018-05-27 20:22:19 -04:00
Paulus Schoutsen
cd0e321668 Version bump to 0.70.0 2018-05-27 17:18:53 -04:00
Paulus Schoutsen
94a82ab7dc Allow Hass.io panel dir (#14655) 2018-05-27 17:18:16 -04:00
Paulus Schoutsen
b6e4a7771a Allow Hass.io panel dir (#14655) 2018-05-27 17:17:19 -04:00
Fabian Affolter
13859388c1 Upgrade locationsharinglib to 2.0.7 (#14640) 2018-05-27 20:16:47 +02:00
Fabian Affolter
2f4c5f949b Use constants (#14647) 2018-05-27 20:16:30 +02:00
Fabian Affolter
36e8157268 Upgrade TwitterAPI to 2.5.4 (#14639) 2018-05-27 15:46:58 +02:00
Fabian Affolter
2d88f47795 Upgrade gitterpy to 0.1.7 (#14643) 2018-05-27 15:45:43 +02:00
Jason Hu
5acfe5da68 Upgrade python-nest to 4.0.0 (#14638)
* Upgrade python-nest to 4.0.0

Drop in replace to use nest stream API
Didn't change any logic from HA side

* Update requirements_all.txt
2018-05-27 11:31:05 +02:00
Fabian Affolter
5f9e4ae136 Upgrade luftdaten to 0.2.0 (#14620) 2018-05-27 09:53:53 +02:00
Paulus Schoutsen
a9b0f92afe Version bump to 0.70.0b7 2018-05-26 20:02:54 -04:00
Paulus Schoutsen
07b2728380 Bump frontend to 20180526.4 2018-05-26 20:02:36 -04:00
Paulus Schoutsen
a5e66ce6ba Bump frontend to 20180526.4 2018-05-26 20:02:24 -04:00
David F. Mulcahey
eae9726bec Add electrical measurement sensor to ZHA (#14561)
* Add electrical measurement sensor

* correct state update

* hound fix

* zha: Add metering sensor (#14562)

* Add IlluminanceMeasurementSensor to ZHA (#14563)

* add IlluminanceMeasurementSensor

* address review comment

* Fix whitespace error during merge

* Add electrical measurement sensor

* correct state update

* hound / flake8
2018-05-26 22:50:05 +02:00
guillaume1410
c425afe50e Adding ryobi garage door opener (#14618)
* Initial component for Ryobi cover

* Initial component for Ryobi cover

* Adding Ryobi cover

* Adding Ryobi cover

* Adding Ryobi cover

* Minor changes

* Remove import
2018-05-26 22:46:53 +02:00
Paulus Schoutsen
a5b9e59cee Version bump to 0.70.0b6 2018-05-26 14:30:03 -04:00
Paulus Schoutsen
fb447cab82 Bump frontend to 20180526.3 2018-05-26 14:29:53 -04:00
Paulus Schoutsen
bcde57bff8 Bump frontend to 20180526.3 2018-05-26 14:29:26 -04:00
David Ryan
dfd7ef1fce Add Hydrawise component (#14055)
* Added the Hydrawise component.

* Fixed lint errors.

* Multiple changes due to review comments addressed.

* Simplified boolean test. Passes pylint.

* Need hydrawiser package version 0.1.1.

* Added a docstring to the device_class method.

* Addressed all review comments from MartinHjelmare.

* Changed keys to single quote. Removed unnecessary duplicate method.

* Removed unused imports.

* Changed state to lowercase snakecase.

* Changes & fixes from review comments.
2018-05-26 18:42:52 +02:00
Paulus Schoutsen
6b9addfeea Update release script 2018-05-26 11:54:50 -04:00
Paulus Schoutsen
6c62f7231b Version bump to 0.70.0b5 2018-05-26 11:53:57 -04:00
Paulus Schoutsen
52c21a53b3 Bump frontend to 20180526.2 2018-05-26 11:53:44 -04:00
Paulus Schoutsen
fdb250d86c Bump frontend to 20180526.2 2018-05-26 11:53:36 -04:00
Fabian Affolter
8de56cfc10 Upgrade speedtest-cli to 2.0.2 (#14633) 2018-05-26 17:35:16 +02:00
Mattias Welponer
7ea25cd360 Add homematicip cloud climate platform (#14388)
* Add support for climatic devices

* Update requirements_all

* Change icon to mdi:thermostat

* Update of homematicip-rest-api lib version

* Remove all mode or state relevant things - will come later

* Add current_operation again to see proper status

* Remove STATE_PERFORMANCE import

* Remove trailing whitespace

* Update requirements file
2018-05-26 16:03:53 +02:00
Paulus Schoutsen
c9498d9f09 Version bump to 0.70.0b4 2018-05-26 08:35:18 -04:00
Paulus Schoutsen
2f0435ebd8 No longer use backports for ffmpeg (#14626) 2018-05-26 08:34:56 -04:00
Paulus Schoutsen
19351fc429 Use libsodium18 (#14624) 2018-05-26 08:34:55 -04:00
Paulus Schoutsen
bfc16428da Bump frontend to 20180526.1 2018-05-26 08:34:40 -04:00
Paulus Schoutsen
41fc44b27c Bump frontend to 20180526.1 2018-05-26 08:33:22 -04:00
Max Muth
a55fbd2be7 Add services for adding and removing items to shopping list (#14574) 2018-05-26 13:53:48 +02:00
Marcelo Moreira de Mello
28d6910e56 Added UDP and parallel streams support to Iperf3 (#14629) 2018-05-26 13:43:31 +02:00
Lorenz Schmid
edfc54b2eb Added option to connect via SSL for OpenWRT(luci) device tracker (#14627)
* Added option to connect via HTTPS for OpenWRT(luci) device tracker

* Use string formatting

* Update quotes

* Remove whitespace
2018-05-26 09:51:21 +02:00
cdce8p
6ceafabd78 Extend package support (#14611) 2018-05-25 16:41:50 -04:00
Paulus Schoutsen
48972c7570 No longer use backports for ffmpeg (#14626) 2018-05-25 13:49:45 -04:00
Paulus Schoutsen
bf3ead3359 Use libsodium18 (#14624) 2018-05-25 11:32:45 -04:00
Marius Kotlarz
b4f8d52fb1 Add configurable decimal rounding of display value for CoinMarketCap sensor and upgrade to 5.0.3 (#14437) (#14604) 2018-05-25 15:39:04 +02:00
Matt Schmitt
143be49c66 Add HomeKit support for automations (#14595) 2018-05-25 11:38:48 +02:00
Matt Schmitt
a9f19a16ee Add HomeKit support for media players (#14446) 2018-05-25 11:37:20 +02:00
Nik Klever
d53a8c0823 Adding illumination sensor (#14615)
* Adding illumination sensor

Adding Illumination sensor of 1wire device DS2438 (DEVICE_SENSOR type 26) according to [OWFS API](http://owfs.org/index.php?page=ds2438)

* Correcting typo illumination -> illuminance
2018-05-25 10:29:20 +02:00
bastshoes
6e5c541a00 Add support container status for Glances on RPi3 (#14529)
* Add support container status for Glances on RPi3

Glances on RPi3 return different container status. 
```
"containers": [
        {
            "Status": "Up 2 hours",
            "name": "HASS",
            "io": {
                "iow": 0,
                "time_since_update": 5.1789350509643555,
                "cumulative_ior": 94208,
                "ior": 0,
                "cumulative_iow": 4096
            },
```
This small PR adds support dealing with this differences.

* Making line shorter

* Fixing indentation

* Fix lint error

* Fix ident

* Fix intend
2018-05-25 09:58:53 +02:00
Gregory Benner
2cd127921a Update pyrainbird (#14617) 2018-05-25 06:39:41 +02:00
Paulus Schoutsen
43d2e436b9 Version bump to 0.70.0b3 2018-05-24 14:25:15 -04:00
Paulus Schoutsen
ef35b8d428 Fix hue discovery popping up (#14614)
* Fix hue discovery popping up

* Fix result

* Fix tests
2018-05-24 14:24:58 -04:00
Tom Harris
69e86c29a6 Bump insteonplm version to fix device hanging (#14582)
* Update inteonplm to 0.9.2

* Change to force Travis CI

* Change to force Travis CI
2018-05-24 14:24:58 -04:00
Greg Laabs
a4d45c46e8 Fix ISY moisure sensors showing unknown until a leak is detected (#14496)
* Fix ISY leak sensors always showing UNKNOWN until a leak is detected

Added some logic that handles both moisture sensors and door/window sensors

* Handle edge case of leak sensor status update after ISY reboot

If a leak sensor is unknown, due to a recent reboot of the ISY, the status will get updated to dry upon the first heartbeat. This status update is the only way that a leak sensor's status changes without an accompanying Control event, so we need to watch for it.

* Fixes from overnight testing

State was checking the incorrect parameter, and wasn't calling schedule update

* Remove leftover debug log line

* Remove unnecessary pylint instruction

* Remove access of protected property

We can't cast _.status directly to a bool for some unknown reason (possibly with the VarEvents library), but casting to an int then bool does work.
2018-05-24 14:24:57 -04:00
Paulus Schoutsen
45d1d30a8b Update frontend to 20180524.0 2018-05-24 14:24:37 -04:00
Paulus Schoutsen
fa9b9105a8 Fix hue discovery popping up (#14614)
* Fix hue discovery popping up

* Fix result

* Fix tests
2018-05-24 14:24:14 -04:00
Paulus Schoutsen
4fb4838bde Update frontend to 20180524.0 2018-05-24 13:08:12 -04:00
Robert Beal
3a487e54a2 Upgrade linode-api to 4.1.9b1 (#13863) (#14610) 2018-05-24 17:16:35 +02:00
Marcelo Moreira de Mello
36da82aa8d Add Iperf3 client sensor (#14213) 2018-05-24 09:25:27 +02:00
Aaron Bach
5205354cb7 Adds a device class of 'garage' to MyQ covers (#14602) 2018-05-24 07:58:35 +02:00
Jason Hu
3498234448 Add Nest away binary sensor and eta sensor (#14406) 2018-05-23 21:40:33 +02:00
Charles Garwood
c13ebacce1 Remove nma component (#14594)
* Remove nma component

* Update .coveragerc
2018-05-23 20:36:51 +02:00
Fabian Affolter
ad49942201 Upgrade TwitterAPI to 2.5.3 (#14596) 2018-05-23 16:47:58 +02:00
SchumyHao
82770faad7 Add Xiaomi Aqara Lock support (#14419) 2018-05-22 10:40:11 +02:00
Fabian Affolter
a2f9fdf339 Add new transmission sensor types (#14530) 2018-05-22 10:06:14 +02:00
Malte Franken
a2decdaaa3 NUT sensor enhancements (#14570) (Fixes #14324)
* removed default value from required parameter; raising PlatformNotReady when connection to nut unavailable; output human-readable state name by default

* removed superfluous sensor name part; showing human-readable form and raw value of current status in more info dialog

* introduced a new virtual sensor type based on the raw status value but used to display a human-readable form of the status

* renamed method

* format string instead of concatenation

* revert the change to the device state attributes - only output the human-readable status without the raw value
2018-05-22 09:34:02 +02:00
Julian Knauer
72a1b7ae3f Lagute LW-12 Wifi LED control (#13307)
* Added platform lw12wifi for Lagute LW-12 Wifi Lights

Supported features:
* RGB colors
* Variable brightness
* 29 effects
* Changing transitions speed for animated effects

* Added lw12wifi to the list of omitted files to test

* Added lw12 module as new requirement for lw12wifi platform

* Added configuration example docstring for platform lw12wifi

* Updating code according to review in PR:

* Removed unused imports: enum, socket.
* Unused and not imported feature SUPPORT_FLASH was removed.
* Unused import lw12 in setup_platform method removed.
* Fixed indention for valuptuous.
* Changed check if effect is None.
* Removed personal debug output.
* Blocking function are not async anymore.

* Further improvements to satisfy PR.

* Unused import asyncio removed.
* Fixed: Return value and docstring no match up for `assumed_state`.

* Check if the set effect is supported, otherwise revert to normal light.

* Added describing missing docstrings to all functions.

* Adopted code to work with HS color setting.

* Syntactical change in comment.

* Removed redefinition of DOMAIN.

* Refactored lw12 controller setup: removed requirement for host and port in LW12Wifi class.

* Rewritten supported feature setup to a more static  expression.

* Removed unused rgb_color property

* Fixed typo in comment for set_light_option

* Changed RGB option validation schema

* Removed instance properties as config options

* Removed optional settings to be more inline with code style.

* Removed unused option from config example

* Removal of unused import

* Added property to disable state polling for this entity.

* Raise an exception if an unknown effect was selected.

* Fixed an issue with the check for known effects.

* As we do not need to set a default, use simple accessing by key.

* Log if an unknown effect was selected.

* Added link to future documentation.
2018-05-22 09:25:10 +02:00
Marcelo Moreira de Mello
2753dd0c5e Removed attribute current_time from Raincloudy sensors to avoid being triggered by recorder component (#14584) 2018-05-22 08:19:45 +02:00
Daniel Perna
118c49ecaa Update pyhomematic to 0.1.43 (#14583)
* Update __init__.py

* Update requirements_all.txt
2018-05-22 01:50:08 +02:00
Tom Harris
0d9b3bea10 Bump insteonplm version to fix device hanging (#14582)
* Update inteonplm to 0.9.2

* Change to force Travis CI

* Change to force Travis CI
2018-05-22 01:46:20 +02:00
Greg Laabs
23afdec767 Fix ISY moisure sensors showing unknown until a leak is detected (#14496)
* Fix ISY leak sensors always showing UNKNOWN until a leak is detected

Added some logic that handles both moisture sensors and door/window sensors

* Handle edge case of leak sensor status update after ISY reboot

If a leak sensor is unknown, due to a recent reboot of the ISY, the status will get updated to dry upon the first heartbeat. This status update is the only way that a leak sensor's status changes without an accompanying Control event, so we need to watch for it.

* Fixes from overnight testing

State was checking the incorrect parameter, and wasn't calling schedule update

* Remove leftover debug log line

* Remove unnecessary pylint instruction

* Remove access of protected property

We can't cast _.status directly to a bool for some unknown reason (possibly with the VarEvents library), but casting to an int then bool does work.
2018-05-21 21:00:01 +02:00
Paulus Schoutsen
4671bd95c6 Version bump to 0.70.0b2 2018-05-21 11:10:53 -04:00
Marco Orovecchia
1bc916927c fix nanoleaf aurora lights min and max temperature (#14571)
* fixed nanoleaf aurora lights min and max temperature

* review changes
2018-05-21 11:10:36 -04:00
cdce8p
2f8865d6cb Homekit style cleanup (#14556)
* Style cleanup

* Sorted imports
* Harmonized service calls

* Test improvements

* Small update
2018-05-21 11:10:35 -04:00
Martin Hjelmare
cfdea8d20f Wait for future mysensors gateway ready (#14398)
* Wait for future mysensors gateway ready

* Add an asyncio future that is done when the gateway reports the
  gateway ready message, I_GATEWAY_READY.
* This will make sure that the gateway is ready before home assistant
  fires the home assistant start event. Automations can now send
  messages to the gateway when home assistant is started.
* Use async timeout to wait max 15 seconds for ready gateway.

* Address comments
2018-05-21 11:10:35 -04:00
Marco Orovecchia
6e941af9b2 fix nanoleaf aurora lights min and max temperature (#14571)
* fixed nanoleaf aurora lights min and max temperature

* review changes
2018-05-21 11:02:50 -04:00
Paulus Schoutsen
ba9bb90cf7 Update frontend to 20180521.0 2018-05-21 11:01:47 -04:00
Paulus Schoutsen
2ff61786bc Update frontend to 20180521.0 2018-05-21 11:01:35 -04:00
Russell Cloran
9791c6b21b zha: Bump to zigpy-xbee 0.1.1 (#14566) 2018-05-20 21:57:09 -07:00
David F. Mulcahey
a183043d5d Add IlluminanceMeasurementSensor to ZHA (#14563)
* add IlluminanceMeasurementSensor

* address review comment

* Fix whitespace error during merge
2018-05-20 21:56:41 -07:00
cdce8p
0589379de5 Homekit style cleanup (#14556)
* Style cleanup

* Sorted imports
* Harmonized service calls

* Test improvements

* Small update
2018-05-20 22:25:53 -04:00
damarco
ee7e59fe68 zha: Set default binary_sensor state to false (#14553) 2018-05-20 16:14:18 -07:00
David F. Mulcahey
b489519930 zha: Add metering sensor (#14562) 2018-05-20 16:01:56 -07:00
David F. Mulcahey
4395217031 zha: Don't poll switch devices (#14560) 2018-05-20 16:00:51 -07:00
Marco Orovecchia
c8ad9c4daa Add auto discovery for nanoleaf aurora lights (#14301)
* auto discovery added for nanoleaf aurora lights

* changes requested by review

* visual indentation

* line too long

* hide autocreated config
2018-05-20 20:53:57 +02:00
Oliver
c050eb4100 Pushed to version 0.7.2 of denonavr (#14551) 2018-05-20 09:50:12 +02:00
Martin Hjelmare
c8a53c564a Wait for future mysensors gateway ready (#14398)
* Wait for future mysensors gateway ready

* Add an asyncio future that is done when the gateway reports the
  gateway ready message, I_GATEWAY_READY.
* This will make sure that the gateway is ready before home assistant
  fires the home assistant start event. Automations can now send
  messages to the gateway when home assistant is started.
* Use async timeout to wait max 15 seconds for ready gateway.

* Address comments
2018-05-19 18:33:52 -04:00
Kerwin Bryant
c316d5b0b9 Add support to ignore a xiaomi aqara gateway (#14428) 2018-05-19 22:36:47 +02:00
Fabian Affolter
e88fc33eef Fix sensor name (fixes #14535) (#14541) 2018-05-19 17:14:53 +02:00
Paulus Schoutsen
8854efd685 Version bump to 0.70.0b1 2018-05-19 10:45:18 -04:00
Paulus Schoutsen
b0e850ba5d Bump frontend to 20180519.0 2018-05-19 10:45:03 -04:00
Paulus Schoutsen
74f1f08ab5 Bump frontend to 20180519.0 2018-05-19 10:44:54 -04:00
Greg Dowling
aa51bb6cb9 Bump pyvera version (improve stability of poll loop). (#14540) 2018-05-19 10:49:52 +02:00
Fabian Affolter
8deb462471 Upgrade restrictedpython to 4.0b4 (#14537) 2018-05-19 10:05:02 +02:00
Fabian Affolter
46dc9322a2 Upgrade keyring to 12.2.1 (#14521) 2018-05-19 10:04:42 +02:00
Fabian Affolter
daf8143d01 Upgrade youtube_dl to 2018.05.18 (#14519) 2018-05-19 10:04:20 +02:00
Fabian Affolter
54dfe045b2 Upgrade aiohttp to 3.2.1 (#14517)
* Upgrade aiohttp to 3.2.1

* Upgrade async_timeout to 3.0.0

* Update the order of the requirements
2018-05-19 10:04:00 +02:00
Paulus Schoutsen
f2dfc84d52 Version bump to 0.70.0b0 2018-05-18 19:31:16 -04:00
Paulus Schoutsen
e55f7ebf81 Merge branch 'master' into dev 2018-05-18 19:30:48 -04:00
Paulus Schoutsen
8d06469efe Bump frontend to 20180518.1 2018-05-18 18:15:49 -04:00
Fabian Affolter
c1127133ea Set certifi to >=2018.04.16 (#14536) 2018-05-18 18:14:40 -04:00
Sean Dague
25970027c6 Update mychevy to 0.4.0 (#14372)
After 2 months of being offline, the my.chevy website seems to be
working again. Some data structures changed in the mean time. The new
library will handle multiple cars. This involves a breaking change in
slug urls for devices where these now include the car make, model, and
year in them.

Discovery has to be delayed until after the initial site login to get
the car metadata.
2018-05-18 13:37:43 -04:00
Greg Laabs
d7640e6ec3 Fix some ISY sensors not getting detected as binary sensors (#14497)
Sensors that were defined via sensor_string were not getting properly identified as binary sensors when they had a uom defining them as binary (the other three methods of detecting binary sensors worked though.)
2018-05-18 08:42:09 -07:00
cdce8p
12e76ef7c1 Update HAP-python to 2.1.0 (#14528) 2018-05-18 16:32:57 +02:00
hanzoh
d36996c8f0 Add Homematic IP RotaryHandleSensor support (#14522)
* Add Homematic IP RotaryHandleSensor support

HmIP-SRH was in the RotaryHandleSensor class and threw errors that LOWBAT and ERROR could not be found (they are LOW_BAT and SABOTAGE).

* Revert REQUIREMENTS change
2018-05-18 16:20:30 +02:00
Fabian Affolter
e929f45ab8 Set pytz to >=2018.04 (#14520) 2018-05-18 09:42:41 -04:00
cdce8p
4c328baaa6 Add code to HomeKit lock (#14524) 2018-05-18 13:52:52 +02:00
Fabian Affolter
cc5edf69e3 Show warning if no locations are shared (fixes #14177) (#14511) 2018-05-18 09:04:47 +02:00
Matt Snyder
909f2448ca Flux bug fix (#14476)
* Simplify conditionals.

* Send white_value on service call.

* Remove extra blank line

* Further simplification of conditionals

* Requested changes

* Do not call getRgb if not needed

* Update log message
2018-05-18 08:50:57 +02:00
Diogo Gomes
97076aa3fd Fix probability_threshold in binary_sensor.bayesian (#14512) (Closes: #14362) 2018-05-18 07:48:16 +02:00
Malte Franken
a3777c4ea8 Feedreader configurable update interval and max entries (#14487) 2018-05-18 07:25:08 +02:00
Paulus Schoutsen
1c3293ac85 Update frontend to 20180518.0 (#14510)
* Update frontend to 20180517.0

* Update requirements

* Bump frontend to 20180518.0
2018-05-17 21:29:37 -04:00
thelittlefireman
f06a0ba373 Bump locationsharinglib to 2.0.2 (#14359)
* Bump locationsharinglib to 2.0.2

* Bump locationsharinglib to 2.0.2
2018-05-17 23:06:39 +02:00
Anders Melchiorsen
9afc2634c6 Adjust LimitlessLED properties for running effects (#14481) 2018-05-17 20:54:25 +02:00
Nate Clark
ed3efc8712 Konnected component follow up (#14491)
* make device_discovered synchronous

* small fixes from code review

* use dispatcher to update sensor state

* update switch state based on response from the device

* interpolate entity_id into dispatcher signal

* cleanup lint

* change coroutine to callback
2018-05-17 20:19:05 +02:00
Mike
144524fbbb Update hitron_coda.py (#14506)
missed a typo that wasn't caught with testing since I don't have a Rogers router.
2018-05-17 19:44:01 +02:00
ChristianKuehnel
298d31e42b New Sensor FinTS (#14334) 2018-05-17 02:45:47 +02:00
Paulus Schoutsen
3e7d4fc902 Bump frontend to 20180516.1 2018-05-16 09:39:14 -04:00
Paulus Schoutsen
64223cea72 Update frontend to 20180516.0 2018-05-16 09:01:30 -04:00
Nathan Henrie
1053473111 Add stdout and stderr to debug output for shell_command (#14465) 2018-05-16 13:47:41 +02:00
Matt Schmitt
25dcddfeef Add HomeKit support for fans (#14351) 2018-05-16 13:15:59 +02:00
Ville Skyttä
e20f88c143 Use "Returns" consistently to avoid being treated as section (#14448)
Otherwise, by side effect, results in error D413 by recent pydocstyle.
2018-05-16 10:01:48 +02:00
William Scanlon
1533a68c06 Added option to invert aREST pin switch logic for active low relays (#14467)
* Added option to invert aREST pin switch logic for active low relays

* Fixed line lengths

* Changed naming and set optional invert default value.

* Fixed line length

* Removed default from get
2018-05-16 09:58:49 +02:00
Greg Laabs
5ff5c73e2b "unavailable" Media players should be considered off in Universal player (#14466)
The Universal media player inherits the states of the first child player that is not in some sort of "Off" state (including idle.) It was not considering the "unavailable" state to be off. Now it does.
2018-05-16 08:00:57 +02:00
Anders Melchiorsen
6ba49e12a2 Improve handling of offline Sonos devices (#14479) 2018-05-16 07:35:43 +02:00
nordlead2005
852ce9f990 Added temperature (apparent) high/low, deprecated max/min (#12233) 2018-05-15 21:26:41 +02:00
Paulus Schoutsen
df69680d24 Don't add a url to built-in panels (#14456)
* Don't add a url to built-in panels

* Add url_path back

* Lint

* Frontend bump to 20180515.0

* Fix tests
2018-05-15 14:47:46 -04:00
Gerard
2e7b5dcd19 BMW code cleanup (#14424)
* Some cleanup for BMW sensors

* Changed dict sort

* Updates based on review and Travis
2018-05-15 20:47:32 +02:00
Malte Franken
e49e0b5a13 Make Feedreader component more extendable (#14342)
* moved regular updates definition to own method to be able to override behaviour in subclass

* moved filter by max entries to own method to be able to override behaviour in subclass

* event type used when firing events to the bus now based on variable to be able to override behaviour in subclass

* feed id introduced instead of url for storing meta-data about the feed to be able to fetch the same feed from different configs with different filtering rules applied

* keep the status of the last update; continue processing the entries retrieved even if a recoverable error was detected while fetching the feed

* added test cases for feedreader component

* better explanation around breaking change

* fixing lint issues and hound violations

* fixing lint issue

* using assert_called_once_with instead of assert_called_once to make it compatible with python 3.5
2018-05-15 20:43:26 +02:00
Nate Clark
de50d5d9c1 Add Konnected component with support for discovery, binary sensor and switch (#13670)
* Add Konnected component with support for discovery, binary sensor, and switch

Co-authored-by: Eitan Mosenkis <eitan@mosenkis.net>

* Use more built-in constants from const.py

* Fix switch actuation with low-level trigger

* Quiet logging; Improve schema validation.

* Execute sync request outside of event loop

* Whitespace cleanup

* Cleanup config validation; async device setup

* Update API endpoint for Konnected 2.2.0 changes

* Update async coroutines via @OttoWinter

* Make backwards compatible with Konnected < 2.2.0

* Add constants suggested by @syssi

* Add to CODEOWNERS

* Remove TODO comment
2018-05-15 19:58:00 +02:00
c727
612a37b2dd Remove simplepush.io (#14358) 2018-05-15 16:57:51 +02:00
Diogo Gomes
d47006c98f Optimistic MQTT light (#14401)
* Restores light state, case the light is optimistic

* lint

* hound

* hound

* Added mqtt_json

* hound

* added mqtt_template

* lint

* cleanup

* use ATTR
2018-05-15 12:25:50 +02:00
Sebastian Muszynski
16bf10b1a2 Don't poll the Samsung Family hub camera (#14473) 2018-05-15 08:50:07 +02:00
William Scanlon
710533ae8a Minor Wink fixes (#14468)
* Updated Wink light supported feature to reflect what features a given light support.

* Fix typo in wink climate
2018-05-15 07:53:12 +02:00
Matthew Garrett
11c57f9345 Bump lakeside version (#14471)
This should fix a couple of issues with T1013 bulbs, and also handle
accounts that contain unknown devices.
2018-05-15 07:51:32 +02:00
Martin Hjelmare
7562b4164b Fix key error upon missing node (#14460)
* This is needed after gateway ready message generates an update while
  persistence is off, or while the gateway node hasn't been presented
  yet.
2018-05-14 22:52:44 +02:00
Gregory Benner
cf44b77225 Samsung Family hub camera component (#14458)
* add familyhub.py camera

* fix import and REQUIREMENTS

* add to coveragerc

* fix formatting to make houndci-bot happy

* ran scripts/gen_requirements_all.py

* use CONF_IP_ADDRESS

* Revert "ran scripts/gen_requirements_all.py"

This reverts commit 3a38681d8a.

* fix library name

* add missing docstrings and enable polling

* Sort imports
2018-05-14 22:52:35 +02:00
Matt Schmitt
44e9783c7c Add support for direction to fan template (#14371)
* Initial commit

* Update and add tests
2018-05-14 22:37:49 +02:00
Fabian Affolter
1b5c02ff67 Upgrade pygatt to 3.2.0 (#14447) 2018-05-14 21:52:54 +02:00
Russell Cloran
2f74ffcf81 zha: Fix cluster class check in single-cluster device type (#14303)
zigpy now allows custom devices, which might mean that devices have cluster
objects which are not instances of the default, but may be instances of
sub-classes of the default. This fixes the check for finding single-cluster
device entities to handle sub-classes properly.
2018-05-14 16:50:09 +02:00
Fabian Affolter
954e4796b8 Use ATTR_NAME from const.py (#14450) 2018-05-14 13:05:52 +02:00
Fabian Affolter
fb501282cc Add SpaceAPI support (#14204)
* Add SpaceAPI support

* Changes according PR comments

* Add tests

* Remove print

* Minor changes
2018-05-14 09:13:59 +02:00
Robert Svensson
c06351f2a9 Bump requirement to pydeconz v38 (#14452) 2018-05-14 08:41:17 +02:00
Johann Kellerman
6b9c65c9ce Allow qwikswitch sensors as part of devices (#14454) 2018-05-14 08:40:25 +02:00
Philip Rosenberg-Watt
8ae3caa292 Add priority and cycles to LaMetric (#14414)
* Add priority and cycles to LaMetric

Priority can be "info", "warning" (default), or "critical" and
cycles is the number of times the message is displayed. If cycles
is set to 0 we get a persistent notification that has to be dismissed
manually.

* Fix for schema and style

* Fix for style
2018-05-13 18:04:21 +02:00
Fabian Affolter
391e3196ea Upgrade distro to 1.3.0 (#14436) 2018-05-13 18:01:10 +02:00
Fabian Affolter
cb709931e4 Upgrade youtube_dl to 2018.05.09 (#14438) 2018-05-13 18:00:37 +02:00
Fabian Affolter
a750f8444e Upgrade Sphinx to 1.7.4 (#14439) 2018-05-13 18:00:08 +02:00
Fabian Affolter
a5bff4cd8d Upgrade python-telegram-bot to 10.1.0 (#14441) 2018-05-13 17:59:25 +02:00
Fabian Affolter
e0bc894cbb Upgrade pyota to 2.0.5 (#14442)
* Use constants

* Upgrade pyota to 2.0.5
2018-05-13 17:58:57 +02:00
Fabian Affolter
3ec56d55c5 Upgrade requests_mock to 1.5 (#14444) 2018-05-13 17:58:18 +02:00
Martin Hjelmare
b904a4e770 Remove universal wheel setting (#14445)
* Home assistant should not build a universal wheel since we don't
  support Python 2.
2018-05-13 17:57:52 +02:00
Ville Skyttä
146a9492ec Clean up some Python 3.4 remnants (#14433) 2018-05-13 17:56:42 +02:00
cdce8p
e5d714ef52 Fix fan service description (#14423) 2018-05-13 14:41:42 +02:00
Ville Skyttä
4d63baf705 Invoke pytest instead of py.test per upstream recommendation, #dropthedot (#14434)
http://blog.pytest.org/2016/whats-new-in-pytest-30/
https://twitter.com/hashtag/dropthedot
2018-05-13 12:11:55 +02:00
Ville Skyttä
234bf1f0ea Spelling, grammar etc fixes (#14432)
* Spelling, grammar etc fixes

* s/an api data/data of an api/
2018-05-13 12:09:28 +02:00
Ville Skyttä
843789528e Remove extra quotes from docstrings (#14431) 2018-05-13 11:06:15 +02:00
Krasimir Chariyski
ea2c073612 Add Bulgarian to Google TTS (#14422) 2018-05-12 17:46:00 -04:00
Andrey
d1228d5cf4 Look at registry before pulling zwave config values (#14408)
* Look at registry before deciding on ID for zwave values

* Reuse the new function
2018-05-12 17:45:36 -04:00
Andrey
7aec098a05 Bring back typing check. Meanwhile just for homeassistant/*.py (#14410)
* Bring back typing check. Meanwhile just for homeassistant/.py

* Change follow-imports to silent. Add a few more checks.
2018-05-12 17:44:53 -04:00
Ville Skyttä
70af7e5fad Update pylint to 1.8.4 (#14421) 2018-05-12 22:22:20 +02:00
Daniel Høyer Iversen
99e272fc8d Upgrade PyXiaomiGatewa to 0.9.3 (#14420) (Closes: #14417) 2018-05-12 21:12:53 +02:00
cdce8p
990f476ac9 Homekit test cleanup (#14416) 2018-05-12 17:10:19 +02:00
Paulus Schoutsen
9abc13aaa6 Merge pull request #14413 from home-assistant/rc
0.69.1
2018-05-12 09:35:30 -04:00
Paulus Schoutsen
d17186a8b7 Version bump to 0.69.1 2018-05-12 09:34:28 -04:00
Lukas Barth
6fedad7890 Fix waiting for setup that never happens (#14346) 2018-05-12 09:34:13 -04:00
Sebastian Muszynski
b371bf700f Bump PyXiaomiGateway version (#14412) 2018-05-12 15:09:48 +02:00
damarco
01ce43ec7c Use None as initial state in zha component (#14389)
* Return None if state is unknown

* Use None as initial state
2018-05-12 14:41:44 +02:00
Lukas Barth
b903bbc042 Fix waiting for setup that never happens (#14346) 2018-05-12 10:30:21 +02:00
Martin Hjelmare
304137e7ff Fix name of tox pylint env (#14402) 2018-05-12 10:07:10 +02:00
Matthew Treinish
e80628d45b Bump pycmus version (#14395)
This commit bumps the pycmus version used by the cmus component. There
was a bug in the previous version used, 1.0.0, when running in local
mode. This was caused by a mtreinish/pycmus#1 and also was reported in
the home-assistant forums (but not as an issue):

https://community.home-assistant.io/t/cant-install-cmus-component/7961

Version 0.1.1 of pycmus fixes this issue so it should work properly for
users running cmus and home-assistant on the same machine.
2018-05-12 07:51:48 +02:00
Sebastian Muszynski
d6b81fb345 Xiaomi Aqara: Add new cube model (sensor_cube.aqgl01) (#14393) 2018-05-11 22:40:32 +02:00
Matt Schmitt
621c653fed Allow HomeKit name to be customized (#14159) 2018-05-11 14:22:45 +02:00
Malte Franken
48d70e520f more detailed error message (#14385) 2018-05-11 12:28:28 +02:00
Robin
528ad56530 Adds facebox (#14356)
* Adds facebox

* Update .coveragerc

* Remove facebox

* Add test of faces attribute

* Add event test

* Adds more tests

* Adds tests to increase coverage

* Rename MOCK_FACES to MOCK_FACE

* Adds STATE_UNKNOWN
2018-05-11 09:57:00 +02:00
Martin Hjelmare
be3b227a87 Make mysensors component async (#13641)
* Make mysensors component async

* Use async dispatcher and discovery.
* Run I/O in executor.
* Make mysensors actuator methods async.
* Upgrade pymysensors to 0.13.0.
* Use async serial gateway.
* Use async TCP gateway.
* Use async mqtt gateway.

* Start gateway before hass start event

* Make sure gateway is started after discovery of persistent devices
  and after corresponding platforms have been loaded.
* Don't wait to start gateway until after hass start.

* Bump pymysensors to 0.14.0
2018-05-11 09:39:18 +02:00
damarco
ef8fc1f201 Update sensor state before adding device (#14357) 2018-05-10 22:32:16 -07:00
cdce8p
8fcf085829 Rewritten HomeKit tests (#14377)
* Use pytest fixtures and parametrize
* Use async
2018-05-11 01:21:59 +02:00
Abílio Costa
6843893d9f Add "framerate" parameter to generic camera (#14079)
* add "framerate" parameter to generic camera

* fix lint
2018-05-11 00:22:02 +02:00
damarco
e963fc5acf Add support for pressure sensors (#14361) 2018-05-10 23:55:32 +02:00
Paulus Schoutsen
bc664c276c Bump frontend to 20180510.1 2018-05-10 17:38:41 -04:00
Paulus Schoutsen
f192ef8219 Remove domain expiry sensor (#14381) 2018-05-10 23:13:00 +02:00
damarco
db31cdf075 Fix binary_sensor device_state_attributes (#14375) 2018-05-10 21:28:57 +02:00
Andrey
f168226be9 Update to sensibo 1.0.3 with better error reporting (#14380) 2018-05-10 21:11:02 +02:00
Paulus Schoutsen
ea01b127c2 Add local auth provider (#14365)
* Add local auth provider

* Lint

* Docstring
2018-05-10 14:09:22 -04:00
damarco
6e831138b4 Fix binary_sensor async_update (#14376) 2018-05-10 10:59:23 -07:00
Pascal Vizeli
eb2671f4bb Update .coveragerc (#14368) 2018-05-10 13:18:13 +02:00
cdce8p
8d017b7678 script/lint: Ensure there are files to test with pylint (#14363) 2018-05-10 12:47:04 +02:00
Paulus Schoutsen
5ec7fc7ddb Backend tweaks to make authorization work (#14339)
* Backend tweaks to make authorization work

* Lint

* Add test

* Validate redirect uris

* Fix tests

* Fix tests

* Lint
2018-05-10 10:38:11 +02:00
Teemu R
0f3ec94fba debug++ for multiple volume controls (#14349)
Be less noisy for those who have more volume controls than one, mentioned in #13022.
2018-05-09 13:44:42 +02:00
Fabian Affolter
2c566072f5 Upgrade keyring to 12.2.0 and keyrings.alt to 3.1 (#14355) 2018-05-09 11:31:18 +02:00
Nash Kaminski
cf8562a030 Support control of away mode and hold mode in Venstar component. Correctly detect humidifiers. (#14256)
* Implement support for away mode and hold mode in Venstar component

* Fix Venstar humidifier capability detection

* Add option to configure humidifier control in Venstar component

* style fix: add missing space and resolve pylint issues

* Remove quotes
2018-05-09 11:26:29 +02:00
Mal Curtis
a91c1bc668 Add zone 3 for Onkyo media player (#14295)
* Add zone 3 for Onkyo media player

* CR Updates

* Fix travis lint errors
2018-05-08 22:33:38 -04:00
Anders Melchiorsen
d43e6a2888 Ignore NaN values for influxdb (#14347)
* Ignore NaN values for influxdb

* Catch TypeError
2018-05-08 20:54:38 -04:00
Paulus Schoutsen
50cea77887 Bump frontend to 20180509.0 2018-05-08 20:48:46 -04:00
Mario Di Raimondo
6231394614 Waze Travel Time: optional inclusive/exclusive filters (#14000)
* Waze Travel Time: optional inclusive/exclusive filters

Added optional `inc_filter` and `excl_filter' params that allow to refine the reported routes: the first is not always the best/desired. A simple case-insensitive filtering (no regular expression) is used.

* fix line lenght

* fix spaces

* Rename var

* Fix typo

* Fix missing var
2018-05-09 00:35:03 +02:00
Aaron Bach
f516cc7dc6 Adds useful attributes to RainMachine programs and zones (#14087)
* Starting to add attributes

* All attributes added to programs

* Basic zone attributes in place

* Added advanced properties for zones

* Working to move common logic into component + dispatcher

* We shouldn't calculate the MAC with every entity

* Small fixes

* Small adjustments

* Owner-requested changes

* Restart

* Restart part 2

* Added ID attribute to each switch

* Collaborator-requested changes
2018-05-08 18:10:03 -04:00
Evgeniy
9c7523d7b0 Improving icloud device tracker (#14078)
* Improving icloud device tracker

* Adding config validations for new values

* Adding config validations for new values

* Moving icloud specific setup to platform schema. Setting default in platform schema.
2018-05-08 23:42:57 +02:00
Andrey
10505d542a Make sure zwave nodes/entities enter the registry is proper state. (#14251)
* When zwave node's info is parsed remove it and re-add back.

* Delay value entity if not ready

* If node is ready consider it parsed even if manufacturer/product are missing.

* Add annotations
2018-05-08 15:30:28 -04:00
Nick Whyte
e12994a0cd Fix BOM weather '-' value (#14042) 2018-05-08 13:35:55 -04:00
stephanerosi
ff01aa40c9 Add help for conversation/process service (#14323)
* Add help for conversation/process service

* Add logging to debug text received when service is called

* Move conversation to specific folder
2018-05-08 13:24:27 -04:00
Paulus Schoutsen
6199e50e80 Fix Insteon PLM coverage 2018-05-08 11:55:04 -04:00
Tod Schmidt
c664c20165 Snips: Added slot values for siteId and probability (#14315)
* Added solt values for siteId and probability

* Update snips.py

* Update test_snips.py
2018-05-08 11:43:31 -04:00
David Broadfoot
eb551a6d5a Gogogate2 0.1.1 (#14294)
* Gogogate2 - bump version

Uses latest version of library which ensures commands to device are idempotent

* Update requirements_all

* Expose sensor temperature

* update version

* import attribute

* Set temperature

* Remove temperature attribute

Removed temperature attribute until it can be re-implemented as a separate sensor.

* Update ordering

* Fix copy-&-paste issue
2018-05-08 11:42:18 -04:00
m4dmin
4343659742 add 2 devices (#14321)
* add 2 devices

io:RollerShutterUnoIOComponent
io:ExteriorVenetianBlindIOComponent

* add 2 devices

* Update tahoma.py

* Fix hounci-bot violation

* Fixed Travis CI build failure

./homeassistant/components/cover/tahoma.py:83:13: E125 continuation line with same indent as next logical line

* Fixed Travis CI build failure

E125 continuation line with same indent as next logical line

* Fixed Travis CI build failure

E127 continuation line over-indented for visual indent

* Fix indent

* Change check
2018-05-08 13:43:07 +02:00
Mattias Welponer
230bd3929c Add more homematicip cloud components (#14084)
* Add support for shutter contact and motion detector device

* Add support for power switch devices

* Add support for light switch device

* Cleanup binary_switch and light platform

* Update comment
2018-05-08 09:57:51 +02:00
Gerard
ba7333e804 Add sensors for BMW electric cars (#14293)
* Add sensors for electric cars

* Updates based on review of @MartinHjelmare

* Fix Travis error

* Another fix for Travis
2018-05-08 09:52:21 +02:00
Mike
48b13cc865 Update hitron_coda.py to fix login for Shaw modems (#14306)
I have a Hitron modem provided by Shaw communications rather than from Rogers as the Docs specify for this device_tracker but it seems like the api/code is all the same except that the login failed due to the password being passed as "pws" instead of "pwd". Making that one character change allowed HASS to read the connected device details from my Hitron modem. If this difference is actually one that stands between the Rogers-provided Hitron modems and the Shaw-provided variant, I am happy to create another device-tracker file for the Shaw modem. I just figured I would go with the simplest solution first.
2018-05-08 09:26:46 +02:00
Aaron Bach
e7c7b9b2a9 Adds unique ID to Roku for entity registry inclusion (#14325)
* Adds unique ID to Roku for entity registry inclusion

* Owner-requested changes
2018-05-07 13:18:51 -04:00
Paulus Schoutsen
c7166241f7 Ignore more loading errors (#14331) 2018-05-07 13:12:12 -04:00
Paulus Schoutsen
6318178a8b Update netdisco to 1.4.1 2018-05-07 10:00:54 -04:00
Javier Gonel
a2b8ad50f2 fix(hbmqtt): partial packets breaking hbmqtt (#14329)
This issue was fixed in hbmqtt/issues#95 that was released in hbmqtt 0.9.2
2018-05-07 09:52:33 -04:00
Paulus Schoutsen
5c95c53c6c Revert custom component loading logic (#14327)
* Revert custom component loading logic

* Lint

* Fix tests

* Guard for infinite inserts into sys.path
2018-05-07 11:25:48 +02:00
Jerad Meisner
e60d066514 Converted SABnzbd to a component (#12915)
* Converted SABnzbd to a component

* fixed async issues

* Made sabnzbd scan interval static. More async fixes.

* Sabnzbd component code cleanup

* Skip sensor platform setup if discovery_info is None
2018-05-07 09:35:55 +02:00
cdce8p
91fe6e4e56 Add debounce to move_cover (#14314)
* Add debounce to move_cover

* Fix spelling mistake
2018-05-06 20:55:38 -04:00
Paulus Schoutsen
34727be5ac Fix module names for custom components (#14317)
* Fix module names for custom components

* Also set __package__ correctly

* bla

* Remove print
2018-05-06 20:54:56 -04:00
Justin Loutsenhizer
107769ab81 Add missing 'sensor' to ABODE_PLATFORMS (#14313)
This fixes missing light, humidity, temperature sensors from abode component.
2018-05-06 19:18:26 +02:00
Russell Cloran
63cc179ea2 zha: Bump to zigpy 0.1.0 (#14305) 2018-05-06 11:17:05 +02:00
thepotoo
2bb1a95098 Add unique_id to MQTT switch (#13719) 2018-05-06 08:21:02 +02:00
Paulus Schoutsen
f3411f8db2 Version bump to 0.70.0.dev0 2018-05-05 11:42:32 -04:00
372 changed files with 11260 additions and 4980 deletions

View File

@@ -4,6 +4,8 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
homeassistant/util/async.py
homeassistant/monkey_patch.py
homeassistant/helpers/typing.py
homeassistant/helpers/signal.py
@@ -121,13 +123,16 @@ omit =
homeassistant/components/homematicip_cloud.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/hydrawise.py
homeassistant/components/*/hydrawise.py
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
homeassistant/components/insteon_plm.py
homeassistant/components/insteon_plm/*
homeassistant/components/*/insteon_plm.py
homeassistant/components/ios.py
@@ -151,6 +156,9 @@ omit =
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/konnected.py
homeassistant/components/*/konnected.py
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
@@ -211,7 +219,7 @@ omit =
homeassistant/components/raincloud.py
homeassistant/components/*/raincloud.py
homeassistant/components/rainmachine.py
homeassistant/components/rainmachine/*
homeassistant/components/*/rainmachine.py
homeassistant/components/raspihats.py
@@ -226,6 +234,9 @@ omit =
homeassistant/components/rpi_pfio.py
homeassistant/components/*/rpi_pfio.py
homeassistant/components/sabnzbd.py
homeassistant/components/*/sabnzbd.py
homeassistant/components/satel_integra.py
homeassistant/components/*/satel_integra.py
@@ -342,6 +353,7 @@ omit =
homeassistant/components/calendar/todoist.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/canary.py
homeassistant/components/camera/familyhub.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
@@ -373,6 +385,7 @@ omit =
homeassistant/components/cover/myq.py
homeassistant/components/cover/opengarage.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/ryobi_gdo.py
homeassistant/components/cover/scsgate.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
@@ -412,7 +425,6 @@ omit =
homeassistant/components/emoncms_history.py
homeassistant/components/emulated_hue/upnp.py
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/folder_watcher.py
homeassistant/components/foursquare.py
homeassistant/components/goalfeed.py
@@ -435,6 +447,7 @@ omit =
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/lw12wifi.py
homeassistant/components/light/mystrom.py
homeassistant/components/light/nanoleaf_aurora.py
homeassistant/components/light/osramlightify.py
@@ -510,9 +523,10 @@ omit =
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clickatell.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/flock.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
@@ -525,7 +539,6 @@ omit =
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/mycroft.py
homeassistant/components/notify/nfandroidtv.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/prowl.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
@@ -592,6 +605,7 @@ omit =
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fedex.py
homeassistant/components/sensor/filesize.py
homeassistant/components/sensor/fints.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/folder.py
@@ -611,6 +625,7 @@ omit =
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/iperf3.py
homeassistant/components/sensor/irish_rail_transport.py
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lacrosse.py
@@ -650,7 +665,6 @@ omit =
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/rainbird.py
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

View File

@@ -20,7 +20,7 @@ If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
- [ ] 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 or updated 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:

View File

@@ -10,8 +10,8 @@ matrix:
env: TOXENV=lint
- python: "3.5.3"
env: TOXENV=pylint
# - python: "3.5"
# env: TOXENV=typing
- python: "3.5.3"
env: TOXENV=typing
- python: "3.5.3"
env: TOXENV=py35
- python: "3.6"

View File

@@ -78,7 +78,6 @@ homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/upnp.py @dgomes
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/rainmachine.py @bachya
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
@@ -94,10 +93,14 @@ homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/homekit/* @cdce8p
homeassistant/components/knx.py @Julius2342
homeassistant/components/*/knx.py @Julius2342
homeassistant/components/konnected.py @heythisisnate
homeassistant/components/*/konnected.py @heythisisnate
homeassistant/components/matrix.py @tinloaf
homeassistant/components/*/matrix.py @tinloaf
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
homeassistant/components/rainmachine/* @bachya
homeassistant/components/*/rainmachine.py @bachya
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei

View File

@@ -12,6 +12,7 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_SSOCR no
#ENV INSTALL_IPERF3 no
VOLUME /config

View File

@@ -1,606 +0,0 @@
swagger: '2.0'
info:
title: Home Assistant
description: Home Assistant REST API
version: "1.0.1"
# the domain of the service
host: localhost:8123
# array of all schemes that your API supports
schemes:
- http
- https
securityDefinitions:
#api_key:
# type: apiKey
# description: API password
# name: api_password
# in: query
api_key:
type: apiKey
description: API password
name: x-ha-access
in: header
# will be prefixed to all paths
basePath: /api
consumes:
- application/json
produces:
- application/json
paths:
/:
get:
summary: API alive message
description: Returns message if API is up and running.
tags:
- Core
security:
- api_key: []
responses:
200:
description: API is up and running
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/config:
get:
summary: API alive message
description: Returns the current configuration as JSON.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Current configuration
schema:
$ref: '#/definitions/ApiConfig'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/discovery_info:
get:
summary: Basic information about Home Assistant instance
tags:
- Core
responses:
200:
description: Basic information
schema:
$ref: '#/definitions/DiscoveryInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/bootstrap:
get:
summary: Returns all data needed to bootstrap Home Assistant.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Bootstrap information
schema:
$ref: '#/definitions/BootstrapInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events:
get:
summary: Array of event objects.
description: Returns an array of event objects. Each event object contain event name and listener count.
tags:
- Events
security:
- api_key: []
responses:
200:
description: Events
schema:
type: array
items:
$ref: '#/definitions/Event'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services:
get:
summary: Array of service objects.
description: Returns an array of service objects. Each object contains the domain and which services it contains.
tags:
- Services
security:
- api_key: []
responses:
200:
description: Services
schema:
type: array
items:
$ref: '#/definitions/Service'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/history:
get:
summary: Array of state changes in the past.
description: Returns an array of state changes in the past. Each object contains further detail for the entities.
tags:
- State
security:
- api_key: []
responses:
200:
description: State changes
schema:
type: array
items:
$ref: '#/definitions/History'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states:
get:
summary: Array of state objects.
description: |
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
tags:
- State
security:
- api_key: []
responses:
200:
description: States
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states/{entity_id}:
get:
summary: Specific state object.
description: |
Returns a state object for specified entity_id.
tags:
- State
security:
- api_key: []
parameters:
- name: entity_id
in: path
description: entity_id of the entity to query
required: true
type: string
responses:
200:
description: State
schema:
$ref: '#/definitions/State'
404:
description: Not found
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
post:
description: |
Updates or creates the current state of an entity.
tags:
- State
consumes:
- application/json
parameters:
- name: entity_id
in: path
description: entity_id to set the state of
required: true
type: string
- $ref: '#/parameters/State'
responses:
200:
description: State of existing entity was set
schema:
$ref: '#/definitions/State'
201:
description: State of new entity was set
schema:
$ref: '#/definitions/State'
headers:
location:
type: string
description: location of the new entity
default:
description: Error
schema:
$ref: '#/definitions/Message'
/error_log:
get:
summary: Error log
description: |
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
tags:
- Core
security:
- api_key: []
produces:
- text/plain
responses:
200:
description: Plain text error log
default:
description: Error
schema:
$ref: '#/definitions/Message'
/camera_proxy/camera.{entity_id}:
get:
summary: Camera image.
description: |
Returns the data (image) from the specified camera entity_id.
tags:
- Camera
security:
- api_key: []
produces:
- image/jpeg
parameters:
- name: entity_id
in: path
description: entity_id of the camera to query
required: true
type: string
responses:
200:
description: Camera image
schema:
type: file
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events/{event_type}:
post:
description: |
Fires an event with event_type
tags:
- Events
security:
- api_key: []
consumes:
- application/json
parameters:
- name: event_type
in: path
description: event_type to fire event with
required: true
type: string
- $ref: '#/parameters/EventData'
responses:
200:
description: Response message
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services/{domain}/{service}:
post:
description: |
Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first.
tags:
- Services
security:
- api_key: []
consumes:
- application/json
parameters:
- name: domain
in: path
description: domain of the service
required: true
type: string
- name: service
in: path
description: service to call
required: true
type: string
- $ref: '#/parameters/ServiceData'
responses:
200:
description: List of states that have changed while the service was being executed. The result will include any changed states that changed while the service was being executed, even if their change was the result of something else happening in the system.
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/template:
post:
description: |
Render a Home Assistant template.
tags:
- Template
security:
- api_key: []
consumes:
- application/json
produces:
- text/plain
parameters:
- $ref: '#/parameters/Template'
responses:
200:
description: Returns the rendered template in plain text.
schema:
type: string
default:
description: Error
schema:
$ref: '#/definitions/Message'
/event_forwarding:
post:
description: |
Setup event forwarding to another Home Assistant instance.
tags:
- Core
security:
- api_key: []
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was setup successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
delete:
description: |
Cancel event forwarding to another Home Assistant instance.
tags:
- Core
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was cancelled successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/stream:
get:
summary: Server-sent events
description: The server-sent events feature is a one-way channel from your Home Assistant server to a client which is acting as a consumer.
tags:
- Core
- Events
security:
- api_key: []
produces:
- text/event-stream
parameters:
- name: restrict
in: query
description: comma-separated list of event_types to filter
required: false
type: string
responses:
default:
description: Stream of events
schema:
type: object
x-events:
state_changed:
type: object
properties:
entity_id:
type: string
old_state:
$ref: '#/definitions/State'
new_state:
$ref: '#/definitions/State'
definitions:
ApiConfig:
type: object
properties:
components:
type: array
description: List of component types
items:
type: string
description: Component type
latitude:
type: number
format: float
description: Latitude of Home Assistant server
longitude:
type: number
format: float
description: Longitude of Home Assistant server
location_name:
type: string
unit_system:
type: object
properties:
length:
type: string
mass:
type: string
temperature:
type: string
volume:
type: string
time_zone:
type: string
version:
type: string
DiscoveryInfo:
type: object
properties:
base_url:
type: string
location_name:
type: string
requires_api_password:
type: boolean
version:
type: string
BootstrapInfo:
type: object
properties:
config:
$ref: '#/definitions/ApiConfig'
events:
type: array
items:
$ref: '#/definitions/Event'
services:
type: array
items:
$ref: '#/definitions/Service'
states:
type: array
items:
$ref: '#/definitions/State'
Event:
type: object
properties:
event:
type: string
listener_count:
type: integer
Service:
type: object
properties:
domain:
type: string
services:
type: object
additionalProperties:
$ref: '#/definitions/DomainService'
DomainService:
type: object
properties:
description:
type: string
fields:
type: object
description: Object with service fields that can be called
State:
type: object
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
entity_id:
type: string
last_changed:
type: string
format: date-time
StateAttributes:
type: object
additionalProperties:
type: string
History:
allOf:
- $ref: '#/definitions/State'
- type: object
properties:
last_updated:
type: string
format: date-time
Message:
type: object
properties:
message:
type: string
parameters:
State:
name: body
in: body
description: State parameter
required: false
schema:
type: object
required:
- state
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
EventData:
name: body
in: body
description: event_data
required: false
schema:
type: object
ServiceData:
name: body
in: body
description: service_data
required: false
schema:
type: object
Template:
name: body
in: body
description: Template to render
required: true
schema:
type: object
required:
- template
properties:
template:
description: Jinja2 template string
type: string
EventForwarding:
name: body
in: body
description: Event Forwarding parameter
required: true
schema:
type: object
required:
- host
- api_password
properties:
host:
type: string
api_password:
type: string
port:
type: integer

View File

@@ -8,7 +8,8 @@ import subprocess
import sys
import threading
from typing import Optional, List
from typing import Optional, List, Dict, Any # noqa #pylint: disable=unused-import
from homeassistant import monkey_patch
from homeassistant.const import (
@@ -259,7 +260,7 @@ def setup_and_run_hass(config_dir: str,
config = {
'frontend': {},
'demo': {}
}
} # type: Dict[str, Any]
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days,

View File

@@ -15,7 +15,6 @@ from voluptuous.humanize import humanize_error
from homeassistant import data_entry_flow, requirements
from homeassistant.core import callback
from homeassistant.const import CONF_TYPE, CONF_NAME, CONF_ID
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.decorator import Registry
from homeassistant.util import dt as dt_util
@@ -36,23 +35,7 @@ ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
DATA_REQS = 'auth_reqs_processed'
class AuthError(HomeAssistantError):
"""Generic authentication error."""
class InvalidUser(AuthError):
"""Raised when an invalid user has been specified."""
class InvalidPassword(AuthError):
"""Raised when an invalid password has been supplied."""
class UnknownError(AuthError):
"""When an unknown error occurs."""
def generate_secret(entropy=32):
def generate_secret(entropy: int = 32) -> str:
"""Generate a secret.
Backport of secrets.token_hex from Python 3.6
@@ -69,8 +52,9 @@ class AuthProvider:
initialized = False
def __init__(self, store, config):
def __init__(self, hass, store, config):
"""Initialize an auth provider."""
self.hass = hass
self.store = store
self.config = config
@@ -210,6 +194,7 @@ class Client:
name = attr.ib(type=str)
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
secret = attr.ib(type=str, default=attr.Factory(generate_secret))
redirect_uris = attr.ib(type=list, default=attr.Factory(list))
async def load_auth_provider_module(hass, provider):
@@ -283,7 +268,7 @@ async def _auth_provider_from_config(hass, store, config):
provider_name, humanize_error(config, err))
return None
return AUTH_PROVIDERS[provider_name](store, config)
return AUTH_PROVIDERS[provider_name](hass, store, config)
class AuthManager:
@@ -340,9 +325,11 @@ class AuthManager:
"""Get an access token."""
return self.access_tokens.get(token)
async def async_create_client(self, name):
async def async_create_client(self, name, *, redirect_uris=None,
no_secret=False):
"""Create a new client."""
return await self._store.async_create_client(name)
return await self._store.async_create_client(
name, redirect_uris, no_secret)
async def async_get_client(self, client_id):
"""Get a client."""
@@ -360,6 +347,9 @@ class AuthManager:
async def _async_finish_login_flow(self, result):
"""Result of a credential login flow."""
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
return None
auth_provider = self._providers[result['handler']]
return await auth_provider.async_get_or_create_credentials(
result['data'])
@@ -477,12 +467,20 @@ class AuthStore:
return None
async def async_create_client(self, name):
async def async_create_client(self, name, redirect_uris, no_secret):
"""Create a new client."""
if self.clients is None:
await self.async_load()
client = Client(name)
kwargs = {
'name': name,
'redirect_uris': redirect_uris
}
if no_secret:
kwargs['secret'] = None
client = Client(**kwargs)
self.clients[client.id] = client
await self.async_save()
return client

View File

@@ -0,0 +1,181 @@
"""Home Assistant auth provider."""
import base64
from collections import OrderedDict
import hashlib
import hmac
import voluptuous as vol
from homeassistant import auth, data_entry_flow
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import json
PATH_DATA = '.users.json'
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
class InvalidAuth(HomeAssistantError):
"""Raised when we encounter invalid authentication."""
class InvalidUser(HomeAssistantError):
"""Raised when invalid user is specified.
Will not be raised when validating authentication.
"""
class Data:
"""Hold the user data."""
def __init__(self, path, data):
"""Initialize the user data store."""
self.path = path
if data is None:
data = {
'salt': auth.generate_secret(),
'users': []
}
self._data = data
@property
def users(self):
"""Return users."""
return self._data['users']
def validate_login(self, username, password):
"""Validate a username and password.
Raises InvalidAuth if auth invalid.
"""
password = self.hash_password(password)
found = None
# Compare all users to avoid timing attacks.
for user in self._data['users']:
if username == user['username']:
found = user
if found is None:
# Do one more compare to make timing the same as if user was found.
hmac.compare_digest(password, password)
raise InvalidAuth
if not hmac.compare_digest(password,
base64.b64decode(found['password'])):
raise InvalidAuth
def hash_password(self, password, for_storage=False):
"""Encode a password."""
hashed = hashlib.pbkdf2_hmac(
'sha512', password.encode(), self._data['salt'].encode(), 100000)
if for_storage:
hashed = base64.b64encode(hashed).decode()
return hashed
def add_user(self, username, password):
"""Add a user."""
if any(user['username'] == username for user in self.users):
raise InvalidUser
self.users.append({
'username': username,
'password': self.hash_password(password, True),
})
def change_password(self, username, new_password):
"""Update the password of a user.
Raises InvalidUser if user cannot be found.
"""
for user in self.users:
if user['username'] == username:
user['password'] = self.hash_password(new_password, True)
break
else:
raise InvalidUser
def save(self):
"""Save data."""
json.save_json(self.path, self._data)
def load_data(path):
"""Load auth data."""
return Data(path, json.load_json(path, None))
@auth.AUTH_PROVIDERS.register('homeassistant')
class HassAuthProvider(auth.AuthProvider):
"""Auth provider based on a local storage of users in HASS config dir."""
DEFAULT_TITLE = 'Home Assistant Local'
async def async_credential_flow(self):
"""Return a flow to login."""
return LoginFlow(self)
async def async_validate_login(self, username, password):
"""Helper to validate a username and password."""
def validate():
"""Validate creds."""
data = self._auth_data()
data.validate_login(username, password)
await self.hass.async_add_job(validate)
async def async_get_or_create_credentials(self, flow_result):
"""Get credentials based on the flow result."""
username = flow_result['username']
for credential in await self.async_credentials():
if credential.data['username'] == username:
return credential
# Create new credentials.
return self.async_create_credentials({
'username': username
})
def _auth_data(self):
"""Return the auth provider data."""
return load_data(self.hass.config.path(PATH_DATA))
class LoginFlow(data_entry_flow.FlowHandler):
"""Handler for the login flow."""
def __init__(self, auth_provider):
"""Initialize the login flow."""
self._auth_provider = auth_provider
async def async_step_init(self, user_input=None):
"""Handle the step of the form."""
errors = {}
if user_input is not None:
try:
await self._auth_provider.async_validate_login(
user_input['username'], user_input['password'])
except InvalidAuth:
errors['base'] = 'invalid_auth'
if not errors:
return self.async_create_entry(
title=self._auth_provider.name,
data=user_input
)
schema = OrderedDict()
schema['username'] = str
schema['password'] = str
return self.async_show_form(
step_id='init',
data_schema=vol.Schema(schema),
errors=errors,
)

View File

@@ -4,6 +4,7 @@ import hmac
import voluptuous as vol
from homeassistant.exceptions import HomeAssistantError
from homeassistant import auth, data_entry_flow
from homeassistant.core import callback
@@ -20,6 +21,10 @@ CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
class InvalidAuthError(HomeAssistantError):
"""Raised when submitting invalid authentication."""
@auth.AUTH_PROVIDERS.register('insecure_example')
class ExampleAuthProvider(auth.AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
@@ -43,18 +48,15 @@ class ExampleAuthProvider(auth.AuthProvider):
# Do one more compare to make timing the same as if user was found.
hmac.compare_digest(password.encode('utf-8'),
password.encode('utf-8'))
raise auth.InvalidUser
raise InvalidAuthError
if not hmac.compare_digest(user['password'].encode('utf-8'),
password.encode('utf-8')):
raise auth.InvalidPassword
raise InvalidAuthError
async def async_get_or_create_credentials(self, flow_result):
"""Get credentials based on the flow result."""
username = flow_result['username']
password = flow_result['password']
self.async_validate_login(username, password)
for credential in await self.async_credentials():
if credential.data['username'] == username:
@@ -96,7 +98,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
try:
self._auth_provider.async_validate_login(
user_input['username'], user_input['password'])
except (auth.InvalidUser, auth.InvalidPassword):
except InvalidAuthError:
errors['base'] = 'invalid_auth'
if not errors:

View File

@@ -278,7 +278,8 @@ def async_enable_logging(hass: core.HomeAssistant,
if log_rotate_days:
err_handler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when='midnight', backupCount=log_rotate_days)
err_log_path, when='midnight',
backupCount=log_rotate_days) # type: logging.FileHandler
else:
err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True)
@@ -297,7 +298,7 @@ def async_enable_logging(hass: core.HomeAssistant,
EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler)
logger = logging.getLogger('')
logger.addHandler(async_handler)
logger.addHandler(async_handler) # type: ignore
logger.setLevel(logging.INFO)
# Save the log file location for access by other components.

View File

@@ -100,8 +100,8 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the regex for code format or None if no code is required."""
return '^\\d{4,6}$'
"""Return one or more digits/characters."""
return 'Number'
@property
def state(self):

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import asyncio
import logging
import re
import voluptuous as vol
@@ -79,8 +80,12 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters if code is defined."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):

View File

@@ -80,7 +80,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
return 'Number'
@property
def state(self):

View File

@@ -106,7 +106,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Regex for code format or None if no code is required."""
if self._code:
return None
return '^\\d{4,6}$'
return 'Number'
@property
def state(self):

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ifttt/
"""
import logging
import re
import voluptuous as vol
@@ -124,8 +125,12 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -7,6 +7,7 @@ https://home-assistant.io/components/alarm_control_panel.manual/
import copy
import datetime
import logging
import re
import voluptuous as vol
@@ -201,8 +202,12 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -8,6 +8,7 @@ import asyncio
import copy
import datetime
import logging
import re
import voluptuous as vol
@@ -237,8 +238,12 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/alarm_control_panel.mqtt/
"""
import asyncio
import logging
import re
import voluptuous as vol
@@ -117,8 +118,12 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
@property
def code_format(self):
"""One or more characters if code is defined."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@asyncio.coroutine
def async_alarm_disarm(self, code=None):

View File

@@ -69,8 +69,8 @@ class NX584Alarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return che characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
"""Return one or more digits/characters."""
return 'Number'
@property
def state(self):

View File

@@ -66,7 +66,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the regex for code format or None if no code is required."""
return '^\\d{4,6}$'
return 'Number'
@property
def state(self):

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.simplisafe/
"""
import logging
import re
import voluptuous as vol
@@ -83,8 +84,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters if code is defined."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_CUSTOM_BYPASS)
REQUIREMENTS = ['total_connect_client==0.17']
REQUIREMENTS = ['total_connect_client==0.18']
_LOGGER = logging.getLogger(__name__)

View File

@@ -60,8 +60,8 @@ class VerisureAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the code format as regex."""
return '^\\d{%s}$' % self._digits
"""Return one or more digits/characters."""
return 'Number'
@property
def changed_by(self):

View File

@@ -2,7 +2,7 @@
Rest API for Home Assistant.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/
https://developers.home-assistant.io/docs/en/external_api_rest.html
"""
import asyncio
import json
@@ -11,31 +11,34 @@ import logging
from aiohttp import web
import async_timeout
import homeassistant.core as ha
import homeassistant.remote as rem
from homeassistant.bootstrap import DATA_LOGGING
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED,
HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND,
MATCH_ALL, URL_API, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG,
URL_API_EVENTS, URL_API_SERVICES,
URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, URL_API_TEMPLATE,
__version__)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.state import AsyncTrackStates
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers import template
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, HTTP_BAD_REQUEST,
HTTP_CREATED, HTTP_NOT_FOUND, MATCH_ALL, URL_API, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG, URL_API_EVENTS,
URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM,
URL_API_TEMPLATE, __version__)
import homeassistant.core as ha
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates
import homeassistant.remote as rem
_LOGGER = logging.getLogger(__name__)
ATTR_BASE_URL = 'base_url'
ATTR_LOCATION_NAME = 'location_name'
ATTR_REQUIRES_API_PASSWORD = 'requires_api_password'
ATTR_VERSION = 'version'
DOMAIN = 'api'
DEPENDENCIES = ['http']
STREAM_PING_PAYLOAD = "ping"
STREAM_PING_PAYLOAD = 'ping'
STREAM_PING_INTERVAL = 50 # seconds
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Register the API with the HTTP interface."""
@@ -62,19 +65,19 @@ class APIStatusView(HomeAssistantView):
"""View to handle Status requests."""
url = URL_API
name = "api:status"
name = 'api:status'
@ha.callback
def get(self, request):
"""Retrieve if API is running."""
return self.json_message('API running.')
return self.json_message("API running.")
class APIEventStream(HomeAssistantView):
"""View to handle EventStream requests."""
url = URL_API_STREAM
name = "api:stream"
name = 'api:stream'
async def get(self, request):
"""Provide a streaming interface for the event bus."""
@@ -95,7 +98,7 @@ class APIEventStream(HomeAssistantView):
if restrict and event.event_type not in restrict:
return
_LOGGER.debug('STREAM %s FORWARDING %s', id(stop_obj), event)
_LOGGER.debug("STREAM %s FORWARDING %s", id(stop_obj), event)
if event.event_type == EVENT_HOMEASSISTANT_STOP:
data = stop_obj
@@ -111,7 +114,7 @@ class APIEventStream(HomeAssistantView):
unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events)
try:
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
_LOGGER.debug("STREAM %s ATTACHED", id(stop_obj))
# Fire off one message so browsers fire open event right away
await to_write.put(STREAM_PING_PAYLOAD)
@@ -126,25 +129,25 @@ class APIEventStream(HomeAssistantView):
break
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
await response.write(msg.encode("UTF-8"))
_LOGGER.debug(
"STREAM %s WRITING %s", id(stop_obj), msg.strip())
await response.write(msg.encode('UTF-8'))
except asyncio.TimeoutError:
await to_write.put(STREAM_PING_PAYLOAD)
except asyncio.CancelledError:
_LOGGER.debug('STREAM %s ABORT', id(stop_obj))
_LOGGER.debug("STREAM %s ABORT", id(stop_obj))
finally:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
_LOGGER.debug("STREAM %s RESPONSE CLOSED", id(stop_obj))
unsub_stream()
class APIConfigView(HomeAssistantView):
"""View to handle Config requests."""
"""View to handle Configuration requests."""
url = URL_API_CONFIG
name = "api:config"
name = 'api:config'
@ha.callback
def get(self, request):
@@ -153,22 +156,22 @@ class APIConfigView(HomeAssistantView):
class APIDiscoveryView(HomeAssistantView):
"""View to provide discovery info."""
"""View to provide Discovery information."""
requires_auth = False
url = URL_API_DISCOVERY_INFO
name = "api:discovery"
name = 'api:discovery'
@ha.callback
def get(self, request):
"""Get discovery info."""
"""Get discovery information."""
hass = request.app['hass']
needs_auth = hass.config.api.api_password is not None
return self.json({
'base_url': hass.config.api.base_url,
'location_name': hass.config.location_name,
'requires_api_password': needs_auth,
'version': __version__
ATTR_BASE_URL: hass.config.api.base_url,
ATTR_LOCATION_NAME: hass.config.location_name,
ATTR_REQUIRES_API_PASSWORD: needs_auth,
ATTR_VERSION: __version__,
})
@@ -187,8 +190,8 @@ class APIStatesView(HomeAssistantView):
class APIEntityStateView(HomeAssistantView):
"""View to handle EntityState requests."""
url = "/api/states/{entity_id}"
name = "api:entity-state"
url = '/api/states/{entity_id}'
name = 'api:entity-state'
@ha.callback
def get(self, request, entity_id):
@@ -196,7 +199,7 @@ class APIEntityStateView(HomeAssistantView):
state = request.app['hass'].states.get(entity_id)
if state:
return self.json(state)
return self.json_message('Entity not found', HTTP_NOT_FOUND)
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
async def post(self, request, entity_id):
"""Update state of entity."""
@@ -204,13 +207,13 @@ class APIEntityStateView(HomeAssistantView):
try:
data = await request.json()
except ValueError:
return self.json_message('Invalid JSON specified',
HTTP_BAD_REQUEST)
return self.json_message(
"Invalid JSON specified.", HTTP_BAD_REQUEST)
new_state = data.get('state')
if new_state is None:
return self.json_message('No state specified', HTTP_BAD_REQUEST)
return self.json_message("No state specified.", HTTP_BAD_REQUEST)
attributes = data.get('attributes')
force_update = data.get('force_update', False)
@@ -232,15 +235,15 @@ class APIEntityStateView(HomeAssistantView):
def delete(self, request, entity_id):
"""Remove entity."""
if request.app['hass'].states.async_remove(entity_id):
return self.json_message('Entity removed')
return self.json_message('Entity not found', HTTP_NOT_FOUND)
return self.json_message("Entity removed.")
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
class APIEventListenersView(HomeAssistantView):
"""View to handle EventListeners requests."""
url = URL_API_EVENTS
name = "api:event-listeners"
name = 'api:event-listeners'
@ha.callback
def get(self, request):
@@ -252,7 +255,7 @@ class APIEventView(HomeAssistantView):
"""View to handle Event requests."""
url = '/api/events/{event_type}'
name = "api:event"
name = 'api:event'
async def post(self, request, event_type):
"""Fire events."""
@@ -260,12 +263,12 @@ class APIEventView(HomeAssistantView):
try:
event_data = json.loads(body) if body else None
except ValueError:
return self.json_message('Event data should be valid JSON',
HTTP_BAD_REQUEST)
return self.json_message(
"Event data should be valid JSON.", HTTP_BAD_REQUEST)
if event_data is not None and not isinstance(event_data, dict):
return self.json_message('Event data should be a JSON object',
HTTP_BAD_REQUEST)
return self.json_message(
"Event data should be a JSON object", HTTP_BAD_REQUEST)
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
@@ -276,8 +279,8 @@ class APIEventView(HomeAssistantView):
if state:
event_data[key] = state
request.app['hass'].bus.async_fire(event_type, event_data,
ha.EventOrigin.remote)
request.app['hass'].bus.async_fire(
event_type, event_data, ha.EventOrigin.remote)
return self.json_message("Event {} fired.".format(event_type))
@@ -286,7 +289,7 @@ class APIServicesView(HomeAssistantView):
"""View to handle Services requests."""
url = URL_API_SERVICES
name = "api:services"
name = 'api:services'
async def get(self, request):
"""Get registered services."""
@@ -297,8 +300,8 @@ class APIServicesView(HomeAssistantView):
class APIDomainServicesView(HomeAssistantView):
"""View to handle DomainServices requests."""
url = "/api/services/{domain}/{service}"
name = "api:domain-services"
url = '/api/services/{domain}/{service}'
name = 'api:domain-services'
async def post(self, request, domain, service):
"""Call a service.
@@ -310,8 +313,8 @@ class APIDomainServicesView(HomeAssistantView):
try:
data = json.loads(body) if body else None
except ValueError:
return self.json_message('Data should be valid JSON',
HTTP_BAD_REQUEST)
return self.json_message(
"Data should be valid JSON.", HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states:
await hass.services.async_call(domain, service, data, True)
@@ -323,7 +326,7 @@ class APIComponentsView(HomeAssistantView):
"""View to handle Components requests."""
url = URL_API_COMPONENTS
name = "api:components"
name = 'api:components'
@ha.callback
def get(self, request):
@@ -332,10 +335,10 @@ class APIComponentsView(HomeAssistantView):
class APITemplateView(HomeAssistantView):
"""View to handle requests."""
"""View to handle Template requests."""
url = URL_API_TEMPLATE
name = "api:template"
name = 'api:template'
async def post(self, request):
"""Render a template."""
@@ -344,29 +347,29 @@ class APITemplateView(HomeAssistantView):
tpl = template.Template(data['template'], request.app['hass'])
return tpl.async_render(data.get('variables'))
except (ValueError, TemplateError) as ex:
return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST)
return self.json_message(
"Error rendering template: {}".format(ex), HTTP_BAD_REQUEST)
class APIErrorLog(HomeAssistantView):
"""View to fetch the error log."""
"""View to fetch the API error log."""
url = URL_API_ERROR_LOG
name = "api:error_log"
name = 'api:error_log'
async def get(self, request):
"""Retrieve API error log."""
return await self.file(request, request.app['hass'].data[DATA_LOGGING])
return web.FileResponse(request.app['hass'].data[DATA_LOGGING])
async def async_services_json(hass):
"""Generate services data to JSONify."""
descriptions = await async_get_all_descriptions(hass)
return [{"domain": key, "services": value}
return [{'domain': key, 'services': value}
for key, value in descriptions.items()]
def async_events_json(hass):
"""Generate event data to JSONify."""
return [{"event": key, "listener_count": value}
return [{'event': key, 'listener_count': value}
for key, value in hass.bus.async_listeners().items()]

View File

@@ -17,7 +17,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyatv==0.3.9']
REQUIREMENTS = ['pyatv==0.3.10']
_LOGGER = logging.getLogger(__name__)

View File

@@ -144,7 +144,7 @@ class AuthProvidersView(HomeAssistantView):
requires_auth = False
@verify_client
async def get(self, request, client_id):
async def get(self, request, client):
"""Get available auth providers."""
return self.json([{
'name': provider.name,
@@ -166,8 +166,15 @@ class LoginFlowIndexView(FlowManagerIndexView):
# pylint: disable=arguments-differ
@verify_client
async def post(self, request, client_id):
@RequestDataValidator(vol.Schema({
vol.Required('handler'): vol.Any(str, list),
vol.Required('redirect_uri'): str,
}))
async def post(self, request, client, data):
"""Create a new login flow."""
if data['redirect_uri'] not in client.redirect_uris:
return self.json_message('invalid redirect uri', )
# pylint: disable=no-value-for-parameter
return await super().post(request)
@@ -192,7 +199,7 @@ class LoginFlowResourceView(FlowManagerResourceView):
# pylint: disable=arguments-differ
@verify_client
@RequestDataValidator(vol.Schema(dict), allow_empty=True)
async def post(self, request, client_id, flow_id, data):
async def post(self, request, client, flow_id, data):
"""Handle progressing a login flow request."""
try:
result = await self._flow_mgr.async_configure(flow_id, data)
@@ -205,7 +212,7 @@ class LoginFlowResourceView(FlowManagerResourceView):
return self.json(self._prepare_result_json(result))
result.pop('data')
result['result'] = self._store_credentials(client_id, result['result'])
result['result'] = self._store_credentials(client.id, result['result'])
return self.json(result)
@@ -222,7 +229,7 @@ class GrantTokenView(HomeAssistantView):
self._retrieve_credentials = retrieve_credentials
@verify_client
async def post(self, request, client_id):
async def post(self, request, client):
"""Grant a token."""
hass = request.app['hass']
data = await request.post()
@@ -230,11 +237,11 @@ class GrantTokenView(HomeAssistantView):
if grant_type == 'authorization_code':
return await self._async_handle_auth_code(
hass, client_id, data)
hass, client.id, data)
elif grant_type == 'refresh_token':
return await self._async_handle_refresh_token(
hass, client_id, data)
hass, client.id, data)
return self.json({
'error': 'unsupported_grant_type',

View File

@@ -11,15 +11,15 @@ def verify_client(method):
@wraps(method)
async def wrapper(view, request, *args, **kwargs):
"""Verify client id/secret before doing request."""
client_id = await _verify_client(request)
client = await _verify_client(request)
if client_id is None:
if client is None:
return view.json({
'error': 'invalid_client',
}, status_code=401)
return await method(
view, request, *args, client_id=client_id, **kwargs)
view, request, *args, **kwargs, client=client)
return wrapper
@@ -46,18 +46,34 @@ async def _verify_client(request):
client_id, client_secret = decoded.split(':', 1)
except ValueError:
# If no ':' in decoded
return None
client_id, client_secret = decoded, None
client = await request.app['hass'].auth.async_get_client(client_id)
return await async_secure_get_client(
request.app['hass'], client_id, client_secret)
async def async_secure_get_client(hass, client_id, client_secret):
"""Get a client id/secret in consistent time."""
client = await hass.auth.async_get_client(client_id)
if client is None:
# Still do a compare so we run same time as if a client was found.
hmac.compare_digest(client_secret.encode('utf-8'),
client_secret.encode('utf-8'))
if client_secret is not None:
# Still do a compare so we run same time as if a client was found.
hmac.compare_digest(client_secret.encode('utf-8'),
client_secret.encode('utf-8'))
return None
if hmac.compare_digest(client_secret.encode('utf-8'),
client.secret.encode('utf-8')):
return client_id
if client.secret is None:
return client
elif client_secret is None:
# Still do a compare so we run same time as if a secret was passed.
hmac.compare_digest(client.secret.encode('utf-8'),
client.secret.encode('utf-8'))
return None
elif hmac.compare_digest(client_secret.encode('utf-8'),
client.secret.encode('utf-8')):
return client
return None

View File

@@ -98,7 +98,7 @@ SERVICE_SCHEMA = vol.Schema({
})
TRIGGER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_VARIABLES, default={}): dict,
})

View File

@@ -217,4 +217,4 @@ class BayesianBinarySensor(BinarySensorDevice):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states."""
self._deviation = bool(self.probability > self._probability_threshold)
self._deviation = bool(self.probability >= self._probability_threshold)

View File

@@ -17,9 +17,19 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'lids': ['Doors', 'opening'],
'windows': ['Windows', 'opening'],
'door_lock_state': ['Door lock state', 'safety']
'door_lock_state': ['Door lock state', 'safety'],
'lights_parking': ['Parking lights', 'light'],
'condition_based_services': ['Condition based services', 'problem'],
'check_control_messages': ['Control messages', 'problem']
}
SENSOR_TYPES_ELEC = {
'charging_status': ['Charging status', 'power'],
'connection_status': ['Connection status', 'plug']
}
SENSOR_TYPES_ELEC.update(SENSOR_TYPES)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the BMW sensors."""
@@ -29,10 +39,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
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)
if vehicle.has_hv_battery:
_LOGGER.debug('BMW with a high voltage battery')
for key, value in sorted(SENSOR_TYPES_ELEC.items()):
device = BMWConnectedDriveSensor(account, vehicle, key,
value[0], value[1])
devices.append(device)
elif vehicle.has_internal_combustion_engine:
_LOGGER.debug('BMW with an internal combustion engine')
for key, value in sorted(SENSOR_TYPES.items()):
device = BMWConnectedDriveSensor(account, vehicle, key,
value[0], value[1])
devices.append(device)
add_devices(devices, True)
@@ -92,12 +110,34 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
result[window.name] = window.state.value
elif self._attribute == 'door_lock_state':
result['door_lock_state'] = vehicle_state.door_lock_state.value
result['last_update_reason'] = vehicle_state.last_update_reason
elif self._attribute == 'lights_parking':
result['lights_parking'] = vehicle_state.parking_lights.value
elif self._attribute == 'condition_based_services':
for report in vehicle_state.condition_based_services:
result.update(self._format_cbs_report(report))
elif self._attribute == 'check_control_messages':
check_control_messages = vehicle_state.check_control_messages
if not check_control_messages:
result['check_control_messages'] = 'OK'
else:
result['check_control_messages'] = check_control_messages
elif self._attribute == 'charging_status':
result['charging_status'] = vehicle_state.charging_status.value
# pylint: disable=W0212
result['last_charging_end_result'] = \
vehicle_state._attributes['lastChargingEndResult']
if self._attribute == 'connection_status':
# pylint: disable=W0212
result['connection_status'] = \
vehicle_state._attributes['connectionStatus']
return result
return sorted(result.items())
def update(self):
"""Read new state data from the library."""
from bimmer_connected.state import LockState
from bimmer_connected.state import ChargingState
vehicle_state = self._vehicle.state
# device class opening: On means open, Off means closed
@@ -111,6 +151,37 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
# Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED
self._state = vehicle_state.door_lock_state not in \
[LockState.LOCKED, LockState.SECURED]
# device class light: On means light detected, Off means no light
if self._attribute == 'lights_parking':
self._state = vehicle_state.are_parking_lights_on
# device class problem: On means problem detected, Off means no problem
if self._attribute == 'condition_based_services':
self._state = not vehicle_state.are_all_cbs_ok
if self._attribute == 'check_control_messages':
self._state = vehicle_state.has_check_control_messages
# device class power: On means power detected, Off means no power
if self._attribute == 'charging_status':
self._state = vehicle_state.charging_status in \
[ChargingState.CHARGING]
# device class plug: On means device is plugged in,
# Off means device is unplugged
if self._attribute == 'connection_status':
# pylint: disable=W0212
self._state = (vehicle_state._attributes['connectionStatus'] ==
'CONNECTED')
@staticmethod
def _format_cbs_report(report):
result = {}
service_type = report.service_type.lower().replace('_', ' ')
result['{} status'.format(service_type)] = report.state.value
if report.due_date is not None:
result['{} date'.format(service_type)] = \
report.due_date.strftime('%Y-%m-%d')
if report.due_distance is not None:
result['{} distance'.format(service_type)] = \
'{} km'.format(report.due_distance)
return result
def update_callback(self):
"""Schedule a state update."""

View File

@@ -6,7 +6,8 @@ https://home-assistant.io/components/binary_sensor.deconz/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -27,10 +28,13 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
"""Add binary sensor from deCONZ."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_BINARY_SENSOR:
if sensor.type in DECONZ_BINARY_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
@@ -103,6 +107,6 @@ class DeconzBinarySensor(BinarySensorDevice):
attr = {}
if self._sensor.battery:
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
if self._sensor.type in PRESENCE and self._sensor.dark:
if self._sensor.type in PRESENCE and self._sensor.dark is not None:
attr['dark'] = self._sensor.dark
return attr

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/binary_sensor.envisalink/
"""
import asyncio
import logging
import datetime
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -14,6 +15,7 @@ from homeassistant.components.envisalink import (
DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
@@ -63,7 +65,25 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr[ATTR_LAST_TRIP_TIME] = self._info['last_fault']
# The Envisalink library returns a "last_fault" value that's the
# number of seconds since the last fault, up to a maximum of 327680
# seconds (65536 5-second ticks).
#
# We don't want the HA event log to fill up with a bunch of no-op
# "state changes" that are just that number ticking up once per poll
# interval, so we subtract it from the current second-accurate time
# unless it is already at the maximum value, in which case we set it
# to None since we can't determine the actual value.
seconds_ago = self._info['last_fault']
if seconds_ago < 65536 * 5:
now = dt_util.now().replace(microsecond=0)
delta = datetime.timedelta(seconds=seconds_ago)
last_trip_time = (now - delta).isoformat()
else:
last_trip_time = None
attr[ATTR_LAST_TRIP_TIME] = last_trip_time
return attr
@property

View File

@@ -0,0 +1,85 @@
"""
Support for HomematicIP binary sensor.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematicip_cloud/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.homematicip_cloud import (
HomematicipGenericDevice, DOMAIN as HOMEMATICIP_CLOUD_DOMAIN,
ATTR_HOME_ID)
DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
ATTR_WINDOW_STATE = 'window_state'
ATTR_EVENT_DELAY = 'event_delay'
ATTR_MOTION_DETECTED = 'motion_detected'
ATTR_ILLUMINATION = 'illumination'
HMIP_OPEN = 'open'
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the HomematicIP binary sensor devices."""
from homematicip.device import (ShutterContact, MotionDetectorIndoor)
if discovery_info is None:
return
home = hass.data[HOMEMATICIP_CLOUD_DOMAIN][discovery_info[ATTR_HOME_ID]]
devices = []
for device in home.devices:
if isinstance(device, ShutterContact):
devices.append(HomematicipShutterContact(home, device))
elif isinstance(device, MotionDetectorIndoor):
devices.append(HomematicipMotionDetector(home, device))
if devices:
async_add_devices(devices)
class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
"""HomematicIP shutter contact."""
def __init__(self, home, device):
"""Initialize the shutter contact."""
super().__init__(home, device)
@property
def device_class(self):
"""Return the class of this sensor."""
return 'door'
@property
def is_on(self):
"""Return true if the shutter contact is on/open."""
if self._device.sabotage:
return True
if self._device.windowState is None:
return None
return self._device.windowState.lower() == HMIP_OPEN
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
"""MomematicIP motion detector."""
def __init__(self, home, device):
"""Initialize the shutter contact."""
super().__init__(home, device)
@property
def device_class(self):
"""Return the class of this sensor."""
return 'motion'
@property
def is_on(self):
"""Return true if motion is detected."""
if self._device.sabotage:
return True
return self._device.motionDetected

View File

@@ -0,0 +1,81 @@
"""
Support for Hydrawise sprinkler.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hydrawise/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.hydrawise import (
BINARY_SENSORS, DATA_HYDRAWISE, HydrawiseEntity, DEVICE_MAP,
DEVICE_MAP_INDEX)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['hydrawise']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=BINARY_SENSORS):
vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a Hydrawise device."""
hydrawise = hass.data[DATA_HYDRAWISE].data
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type in ['status', 'rain_sensor']:
sensors.append(
HydrawiseBinarySensor(
hydrawise.controller_status, sensor_type))
else:
# create a sensor for each zone
for zone in hydrawise.relays:
zone_data = zone
zone_data['running'] = \
hydrawise.controller_status.get('running', False)
sensors.append(HydrawiseBinarySensor(zone_data, sensor_type))
add_devices(sensors, True)
class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorDevice):
"""A sensor implementation for Hydrawise device."""
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
def update(self):
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating Hydrawise binary sensor: %s", self._name)
mydata = self.hass.data[DATA_HYDRAWISE].data
if self._sensor_type == 'status':
self._state = mydata.status == 'All good!'
elif self._sensor_type == 'rain_sensor':
for sensor in mydata.sensors:
if sensor['name'] == 'Rain':
self._state = sensor['active'] == 1
elif self._sensor_type == 'is_watering':
if not mydata.running:
self._state = False
elif int(mydata.running[0]['relay']) == self.data['relay']:
self._state = True
else:
self._state = False
@property
def device_class(self):
"""Return the device class of the sensor type."""
return DEVICE_MAP[self._sensor_type][
DEVICE_MAP_INDEX.index('DEVICE_CLASS_INDEX')]

View File

@@ -117,8 +117,10 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
# pylint: disable=protected-access
if _is_val_unknown(self._node.status._val):
self._computed_state = None
self._status_was_unknown = True
else:
self._computed_state = bool(self._node.status._val)
self._status_was_unknown = False
@asyncio.coroutine
def async_added_to_hass(self) -> None:
@@ -156,9 +158,13 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
# pylint: disable=protected-access
if not _is_val_unknown(self._negative_node.status._val):
# If the negative node has a value, it means the negative node is
# in use for this device. Therefore, we cannot determine the state
# of the sensor until we receive our first ON event.
self._computed_state = None
# in use for this device. Next we need to check to see if the
# negative and positive nodes disagree on the state (both ON or
# both OFF).
if self._negative_node.status._val == self._node.status._val:
# The states disagree, therefore we cannot determine the state
# of the sensor until we receive our first ON event.
self._computed_state = None
def _negative_node_control_handler(self, event: object) -> None:
"""Handle an "On" control event from the "negative" node."""
@@ -189,14 +195,21 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
self.schedule_update_ha_state()
self._heartbeat()
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Ignore primary node status updates.
"""Primary node status updates.
We listen directly to the Control events on all nodes for this
device.
We MOSTLY ignore these updates, as we listen directly to the Control
events on all nodes for this device. However, there is one edge case:
If a leak sensor is unknown, due to a recent reboot of the ISY, the
status will get updated to dry upon the first heartbeat. This status
update is the only way that a leak sensor's status changes without
an accompanying Control event, so we need to watch for it.
"""
pass
if self._status_was_unknown and self._computed_state is None:
self._computed_state = bool(int(self._node.status))
self._status_was_unknown = False
self.schedule_update_ha_state()
self._heartbeat()
@property
def value(self) -> object:

View File

@@ -0,0 +1,82 @@
"""
Support for wired binary sensors attached to a Konnected device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.konnected/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.konnected import (
DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, SIGNAL_SENSOR_UPDATE)
from homeassistant.const import (
CONF_DEVICES, CONF_TYPE, CONF_NAME, CONF_BINARY_SENSORS, ATTR_ENTITY_ID,
ATTR_STATE)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['konnected']
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up binary sensors attached to a Konnected device."""
if discovery_info is None:
return
data = hass.data[KONNECTED_DOMAIN]
device_id = discovery_info['device_id']
sensors = [KonnectedBinarySensor(device_id, pin_num, pin_data)
for pin_num, pin_data in
data[CONF_DEVICES][device_id][CONF_BINARY_SENSORS].items()]
async_add_devices(sensors)
class KonnectedBinarySensor(BinarySensorDevice):
"""Representation of a Konnected binary sensor."""
def __init__(self, device_id, pin_num, data):
"""Initialize the binary sensor."""
self._data = data
self._device_id = device_id
self._pin_num = pin_num
self._state = self._data.get(ATTR_STATE)
self._device_class = self._data.get(CONF_TYPE)
self._name = self._data.get(CONF_NAME, 'Konnected {} Zone {}'.format(
device_id, PIN_TO_ZONE[pin_num]))
_LOGGER.debug('Created new Konnected sensor: %s', self._name)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the sensor."""
return self._state
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def device_class(self):
"""Return the device class."""
return self._device_class
async def async_added_to_hass(self):
"""Store entity_id and register state change callback."""
self._data[ATTR_ENTITY_ID] = self.entity_id
async_dispatcher_connect(
self.hass, SIGNAL_SENSOR_UPDATE.format(self.entity_id),
self.async_set_state)
@callback
def async_set_state(self, state):
"""Update the sensor's state."""
self._state = state
self.async_schedule_update_ha_state()

View File

@@ -31,7 +31,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
sensors = []
hub = hass.data[MYCHEVY_DOMAIN]
for sconfig in SENSORS:
sensors.append(EVBinarySensor(hub, sconfig))
for car in hub.cars:
sensors.append(EVBinarySensor(hub, sconfig, car.vid))
async_add_devices(sensors)
@@ -45,16 +46,18 @@ class EVBinarySensor(BinarySensorDevice):
"""
def __init__(self, connection, config):
def __init__(self, connection, config, car_vid):
"""Initialize sensor with car connection."""
self._conn = connection
self._name = config.name
self._attr = config.attr
self._type = config.device_class
self._is_on = None
self._car_vid = car_vid
self.entity_id = ENTITY_ID_FORMAT.format(
'{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name)))
'{}_{}_{}'.format(MYCHEVY_DOMAIN,
slugify(self._car.name),
slugify(self._name)))
@property
def name(self):
@@ -66,6 +69,11 @@ class EVBinarySensor(BinarySensorDevice):
"""Return if on."""
return self._is_on
@property
def _car(self):
"""Return the car."""
return self._conn.get_car(self._car_vid)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
@@ -75,8 +83,8 @@ class EVBinarySensor(BinarySensorDevice):
@callback
def async_update_callback(self):
"""Update state."""
if self._conn.car is not None:
self._is_on = getattr(self._conn.car, self._attr, None)
if self._car is not None:
self._is_on = getattr(self._car, self._attr, None)
self.async_schedule_update_ha_state()
@property

View File

@@ -7,27 +7,36 @@ https://home-assistant.io/components/binary_sensor.nest/
from itertools import chain
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.components.sensor.nest import NestSensor
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.nest import DATA_NEST, NestSensorDevice
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.components.nest import DATA_NEST
DEPENDENCIES = ['nest']
BINARY_TYPES = ['online']
BINARY_TYPES = {'online': 'connectivity'}
CLIMATE_BINARY_TYPES = [
'fan',
'is_using_emergency_heat',
'is_locked',
'has_leaf',
]
CLIMATE_BINARY_TYPES = {
'fan': None,
'is_using_emergency_heat': 'heat',
'is_locked': None,
'has_leaf': None,
}
CAMERA_BINARY_TYPES = [
'motion_detected',
'sound_detected',
'person_detected',
]
CAMERA_BINARY_TYPES = {
'motion_detected': 'motion',
'sound_detected': 'sound',
'person_detected': 'occupancy',
}
STRUCTURE_BINARY_TYPES = {
'away': None,
# 'security_state', # pending python-nest update
}
STRUCTURE_BINARY_STATE_MAP = {
'away': {'away': True, 'home': False},
'security_state': {'deter': True, 'ok': False},
}
_BINARY_TYPES_DEPRECATED = [
'hvac_ac_state',
@@ -40,8 +49,8 @@ _BINARY_TYPES_DEPRECATED = [
'hvac_emer_heat_state',
]
_VALID_BINARY_SENSOR_TYPES = BINARY_TYPES + CLIMATE_BINARY_TYPES \
+ CAMERA_BINARY_TYPES
_VALID_BINARY_SENSOR_TYPES = {**BINARY_TYPES, **CLIMATE_BINARY_TYPES,
**CAMERA_BINARY_TYPES, **STRUCTURE_BINARY_TYPES}
_LOGGER = logging.getLogger(__name__)
@@ -68,6 +77,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error(wstr)
sensors = []
for structure in nest.structures():
sensors += [NestBinarySensor(structure, None, variable)
for variable in conditions
if variable in STRUCTURE_BINARY_TYPES]
device_chain = chain(nest.thermostats(),
nest.smoke_co_alarms(),
nest.cameras())
@@ -88,11 +101,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensors += [NestActivityZoneSensor(structure,
device,
activity_zone)]
add_devices(sensors, True)
class NestBinarySensor(NestSensor, BinarySensorDevice):
class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
"""Represents a Nest binary sensor."""
@property
@@ -100,9 +112,19 @@ class NestBinarySensor(NestSensor, BinarySensorDevice):
"""Return true if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the device class of the binary sensor."""
return _VALID_BINARY_SENSOR_TYPES.get(self.variable)
def update(self):
"""Retrieve latest state."""
self._state = bool(getattr(self.device, self.variable))
value = getattr(self.device, self.variable)
if self.variable in STRUCTURE_BINARY_TYPES:
self._state = bool(STRUCTURE_BINARY_STATE_MAP
[self.variable][value])
else:
self._state = bool(value)
class NestActivityZoneSensor(NestBinarySensor):
@@ -115,9 +137,9 @@ class NestActivityZoneSensor(NestBinarySensor):
self._name = "{} {} activity".format(self._name, self.zone.name)
@property
def name(self):
"""Return the name of the nest, if any."""
return self._name
def device_class(self):
"""Return the device class of the binary sensor."""
return 'motion'
def update(self):
"""Retrieve latest state."""

View File

@@ -0,0 +1,102 @@
"""
This platform provides binary sensors for key RainMachine data.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rainmachine/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rainmachine import (
BINARY_SENSORS, DATA_RAINMACHINE, DATA_UPDATE_TOPIC, TYPE_FREEZE,
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['rainmachine']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the RainMachine Switch platform."""
if discovery_info is None:
return
rainmachine = hass.data[DATA_RAINMACHINE]
binary_sensors = []
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
name, icon = BINARY_SENSORS[sensor_type]
binary_sensors.append(
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
add_devices(binary_sensors, True)
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
"""A sensor implementation for raincloud device."""
def __init__(self, rainmachine, sensor_type, name, icon):
"""Initialize the sensor."""
super().__init__(rainmachine)
self._icon = icon
self._name = name
self._sensor_type = sensor_type
self._state = None
@property
def icon(self) -> str:
"""Return the icon."""
return self._icon
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def unique_id(self) -> str:
"""Return a unique, HASS-friendly identifier for this entity."""
return '{0}_{1}'.format(
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
@callback
def update_data(self):
"""Update the state."""
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(self.hass, DATA_UPDATE_TOPIC,
self.update_data)
def update(self):
"""Update the state."""
if self._sensor_type == TYPE_FREEZE:
self._state = self.rainmachine.restrictions['current']['freeze']
elif self._sensor_type == TYPE_FREEZE_PROTECTION:
self._state = self.rainmachine.restrictions['global'][
'freezeProtectEnabled']
elif self._sensor_type == TYPE_HOT_DAYS:
self._state = self.rainmachine.restrictions['global'][
'hotDaysExtraWatering']
elif self._sensor_type == TYPE_HOURLY:
self._state = self.rainmachine.restrictions['current']['hourly']
elif self._sensor_type == TYPE_MONTH:
self._state = self.rainmachine.restrictions['current']['month']
elif self._sensor_type == TYPE_RAINDELAY:
self._state = self.rainmachine.restrictions['current']['rainDelay']
elif self._sensor_type == TYPE_RAINSENSOR:
self._state = self.rainmachine.restrictions['current'][
'rainSensor']
elif self._sensor_type == TYPE_WEEKDAY:
self._state = self.rainmachine.restrictions['current']['weekDay']

View File

@@ -4,7 +4,6 @@ Support for showing random states.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.random/
"""
import asyncio
import logging
import voluptuous as vol
@@ -24,8 +23,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 the Random binary sensor."""
name = config.get(CONF_NAME)
device_class = config.get(CONF_DEVICE_CLASS)
@@ -57,8 +56,7 @@ class RandomSensor(BinarySensorDevice):
"""Return the sensor class of the sensor."""
return self._device_class
@asyncio.coroutine
def async_update(self):
async def async_update(self):
"""Get new state and update the sensor's state."""
from random import getrandbits
self._state = bool(getrandbits(1))

View File

@@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.components import rfxtrx
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, BinarySensorDevice)
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.rfxtrx import (
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES,
CONF_FIRE_EVENT, CONF_OFF_DELAY)
@@ -29,8 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS):
DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_OFF_DELAY):
vol.Any(cv.time_period, cv.positive_timedelta),

View File

@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
'channel_1', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
elif model in ['cube', 'sensor_cube']:
elif model in ['cube', 'sensor_cube', 'sensor_cube.aqgl01']:
devices.append(XiaomiCube(device, hass, gateway))
add_devices(devices)
@@ -330,6 +330,8 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'both'
elif value == 'shake':
click_type = 'shake'
elif value == 'long_click':
return False
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)
return False

View File

@@ -108,7 +108,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
if self._state == 'unknown':
if self._state is None:
return False
return bool(self._state)
@@ -133,7 +133,8 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
from bellows.types.basic import uint16_t
result = await zha.safe_read(self._endpoint.ias_zone,
['zone_status'])
['zone_status'],
allow_cache=False)
state = result.get('zone_status', self._state)
if isinstance(state, (int, uint16_t)):
self._state = result.get('zone_status', self._state) & 3
@@ -202,14 +203,19 @@ class Switch(zha.Entity, BinarySensorDevice):
def __init__(self, **kwargs):
"""Initialize Switch."""
super().__init__(**kwargs)
self._state = True
self._level = 255
self._state = False
self._level = 0
from zigpy.zcl.clusters import general
self._out_listeners = {
general.OnOff.cluster_id: self.OnOffListener(self),
general.LevelControl.cluster_id: self.LevelListener(self),
}
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
@@ -218,7 +224,10 @@ class Switch(zha.Entity, BinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the device state attributes."""
return {'level': self._state and self._level or 0}
self._device_state_attributes.update({
'level': self._state and self._level or 0
})
return self._device_state_attributes
def move_level(self, change):
"""Increment the level, setting state if appropriate."""

View File

@@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bimmer_connected==0.5.0']
REQUIREMENTS = ['bimmer_connected==0.5.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -27,7 +27,7 @@ activate_air_conditioning:
description: >
Start the air conditioning of the vehicle. What exactly is started here
depends on the type of vehicle. It might range from just ventilation over
auxilary heating to real air conditioning. The vehicle is identified via
auxiliary heating to real air conditioning. The vehicle is identified via
the vin (see below).
fields:
vin:
@@ -39,4 +39,4 @@ update_state:
description: >
Fetch the last state of the vehicles of all your accounts from the BMW
server. This does *not* trigger an update from the vehicle, it just gets
the data from the BMW servers. This service does not require any attributes.
the data from the BMW servers. This service does not require any attributes.

View File

@@ -256,6 +256,11 @@ class Camera(Entity):
"""Return the camera model."""
return None
@property
def frame_interval(self):
"""Return the interval between frames of the mjpeg stream."""
return 0.5
def camera_image(self):
"""Return bytes of camera image."""
raise NotImplementedError()
@@ -272,10 +277,6 @@ class Camera(Entity):
This method must be run in the event loop.
"""
if interval < MIN_STREAM_INTERVAL:
raise ValueError("Stream interval must be be > {}"
.format(MIN_STREAM_INTERVAL))
response = web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--frameboundary')
@@ -325,8 +326,7 @@ class Camera(Entity):
a direct stream from the camera.
This method must be run in the event loop.
"""
await self.handle_async_still_stream(request,
FALLBACK_STREAM_INTERVAL)
await self.handle_async_still_stream(request, self.frame_interval)
@property
def state(self):
@@ -448,6 +448,9 @@ class CameraMjpegStream(CameraView):
try:
# Compose camera stream from stills
interval = float(request.query.get('interval'))
if interval < MIN_STREAM_INTERVAL:
raise ValueError("Stream interval must be be > {}"
.format(MIN_STREAM_INTERVAL))
await camera.handle_async_still_stream(request, interval)
return
except ValueError:

View File

@@ -0,0 +1,58 @@
"""
Family Hub camera for Samsung Refrigerators.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/camera.familyhub/
"""
import logging
import voluptuous as vol
from homeassistant.components.camera import Camera
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_IP_ADDRESS, CONF_NAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-family-hub-local==0.0.2']
DEFAULT_NAME = 'FamilyHub Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up the Family Hub Camera."""
from pyfamilyhublocal import FamilyHubCam
address = config.get(CONF_IP_ADDRESS)
name = config.get(CONF_NAME)
session = async_get_clientsession(hass)
family_hub_cam = FamilyHubCam(address, hass.loop, session)
async_add_devices([FamilyHubCamera(name, family_hub_cam)], True)
class FamilyHubCamera(Camera):
"""The representation of a Family Hub camera."""
def __init__(self, name, family_hub_cam):
"""Initialize camera component."""
super().__init__()
self._name = name
self.family_hub_cam = family_hub_cam
async def async_camera_image(self):
"""Return a still image response."""
return await self.family_hub_cam.async_get_cam_image()
@property
def name(self):
"""Return the name of this camera."""
return self._name

View File

@@ -28,6 +28,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_CONTENT_TYPE = 'content_type'
CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change'
CONF_STILL_IMAGE_URL = 'still_image_url'
CONF_FRAMERATE = 'framerate'
DEFAULT_NAME = 'Generic Camera'
@@ -40,6 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string,
vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int,
})
@@ -62,6 +64,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
self._frame_interval = 1 / device_info[CONF_FRAMERATE]
self.content_type = device_info[CONF_CONTENT_TYPE]
username = device_info.get(CONF_USERNAME)
@@ -78,6 +81,11 @@ class GenericCamera(Camera):
self._last_url = None
self._last_image = None
@property
def frame_interval(self):
"""Return the interval between frames of the mjpeg stream."""
return self._frame_interval
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(

View File

@@ -22,6 +22,12 @@ from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
PRECISION_TENTHS, )
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
DEFAULT_MIN_HUMITIDY = 30
DEFAULT_MAX_HUMIDITY = 99
DOMAIN = 'climate'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -778,19 +784,21 @@ class ClimateDevice(Entity):
@property
def min_temp(self):
"""Return the minimum temperature."""
return convert_temperature(7, TEMP_CELSIUS, self.temperature_unit)
return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS,
self.temperature_unit)
@property
def max_temp(self):
"""Return the maximum temperature."""
return convert_temperature(35, TEMP_CELSIUS, self.temperature_unit)
return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS,
self.temperature_unit)
@property
def min_humidity(self):
"""Return the minimum humidity."""
return 30
return DEFAULT_MIN_HUMITIDY
@property
def max_humidity(self):
"""Return the maximum humidity."""
return 99
return DEFAULT_MAX_HUMIDITY

View File

@@ -14,7 +14,8 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA,
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
@@ -267,8 +268,7 @@ class GenericThermostat(ClimateDevice):
if self._min_temp:
return self._min_temp
# get default temp from super class
return ClimateDevice.min_temp.fget(self)
return DEFAULT_MIN_TEMP
@property
def max_temp(self):
@@ -277,8 +277,7 @@ class GenericThermostat(ClimateDevice):
if self._max_temp:
return self._max_temp
# Get default temp from super class
return ClimateDevice.max_temp.fget(self)
return DEFAULT_MAX_TEMP
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):

View File

@@ -0,0 +1,101 @@
"""
Support for HomematicIP climate.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/climate.homematicip_cloud/
"""
import logging
from homeassistant.components.climate import (
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, ATTR_TEMPERATURE,
STATE_AUTO, STATE_MANUAL)
from homeassistant.const import TEMP_CELSIUS
from homeassistant.components.homematicip_cloud import (
HomematicipGenericDevice, DOMAIN as HOMEMATICIP_CLOUD_DOMAIN,
ATTR_HOME_ID)
_LOGGER = logging.getLogger(__name__)
STATE_BOOST = 'Boost'
HA_STATE_TO_HMIP = {
STATE_AUTO: 'AUTOMATIC',
STATE_MANUAL: 'MANUAL',
}
HMIP_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_HMIP.items()}
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the HomematicIP climate devices."""
from homematicip.group import HeatingGroup
if discovery_info is None:
return
home = hass.data[HOMEMATICIP_CLOUD_DOMAIN][discovery_info[ATTR_HOME_ID]]
devices = []
for device in home.groups:
if isinstance(device, HeatingGroup):
devices.append(HomematicipHeatingGroup(home, device))
if devices:
async_add_devices(devices)
class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice):
"""Representation of a MomematicIP heating group."""
def __init__(self, home, device):
"""Initialize heating group."""
device.modelType = 'Group-Heating'
super().__init__(home, device)
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._device.setPointTemperature
@property
def current_temperature(self):
"""Return the current temperature."""
return self._device.actualTemperature
@property
def current_humidity(self):
"""Return the current humidity."""
return self._device.humidity
@property
def current_operation(self):
"""Return current operation ie. automatic or manual."""
return HMIP_STATE_TO_HA.get(self._device.controlMode)
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._device.minTemperature
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._device.maxTemperature
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._device.set_point_temperature(temperature)

View File

@@ -115,7 +115,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
"""List of available fan modes."""
return ['Auto', 'Min', 'Normal', 'Max']
def set_temperature(self, **kwargs):
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
temp = kwargs.get(ATTR_TEMPERATURE)
@@ -143,9 +143,9 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
if self.gateway.optimistic:
# Optimistically assume that device has changed state
self._values[value_type] = value
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
def set_fan_mode(self, fan_mode):
async def async_set_fan_mode(self, fan_mode):
"""Set new target temperature."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
@@ -153,9 +153,9 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
if self.gateway.optimistic:
# Optimistically assume that device has changed state
self._values[set_req.V_HVAC_SPEED] = fan_mode
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
def set_operation_mode(self, operation_mode):
async def async_set_operation_mode(self, operation_mode):
"""Set new target temperature."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type,
@@ -163,7 +163,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
if self.gateway.optimistic:
# Optimistically assume that device has changed state
self._values[self.value_type] = operation_mode
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
async def async_update(self):
"""Update the controller with the latest value from a sensor."""

View File

@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
from homeassistant.components.nest import DATA_NEST
from homeassistant.components.nest import DATA_NEST, SIGNAL_NEST_UPDATE
from homeassistant.components.climate import (
STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_ECO, ClimateDevice,
PLATFORM_SCHEMA, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
@@ -18,6 +18,7 @@ from homeassistant.components.climate import (
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT,
CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['nest']
_LOGGER = logging.getLogger(__name__)
@@ -37,11 +38,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
temp_unit = hass.config.units.temperature_unit
add_devices(
[NestThermostat(structure, device, temp_unit)
for structure, device in hass.data[DATA_NEST].thermostats()],
True
)
all_devices = [NestThermostat(structure, device, temp_unit)
for structure, device in hass.data[DATA_NEST].thermostats()]
add_devices(all_devices, True)
class NestThermostat(ClimateDevice):
@@ -97,6 +97,20 @@ class NestThermostat(ClimateDevice):
self._min_temperature = None
self._max_temperature = None
@property
def should_poll(self):
"""Do not need poll thanks using Nest streaming API."""
return False
async def async_added_to_hass(self):
"""Register update signal handler."""
async def async_update_state():
"""Update device state."""
await self.async_update_ha_state(True)
async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE,
async_update_state)
@property
def supported_features(self):
"""Return the list of supported features."""
@@ -134,7 +148,9 @@ class NestThermostat(ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._mode != NEST_MODE_HEAT_COOL and not self.is_away_mode_on:
if self._mode != NEST_MODE_HEAT_COOL and \
self._mode != STATE_ECO and \
not self.is_away_mode_on:
return self._target_temperature
return None
@@ -168,18 +184,24 @@ class NestThermostat(ClimateDevice):
def set_temperature(self, **kwargs):
"""Set new target temperature."""
import nest
temp = None
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:
if target_temp_low is not None and target_temp_high is not None:
temp = (target_temp_low, target_temp_high)
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
else:
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
_LOGGER.debug("Nest set_temperature-output-value=%s", temp)
try:
self.device.target = temp
except nest.nest.APIError:
_LOGGER.error("An error occurred while setting the temperature")
if temp is not None:
self.device.target = temp
except nest.nest.APIError as api_error:
_LOGGER.error("An error occurred while setting temperature: %s",
api_error)
# restore target temperature
self.schedule_update_ha_state(True)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""

View File

@@ -19,13 +19,13 @@ from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
SUPPORT_ON_OFF)
SUPPORT_ON_OFF, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.temperature import convert as convert_temperature
REQUIREMENTS = ['pysensibo==1.0.2']
REQUIREMENTS = ['pysensibo==1.0.3']
_LOGGER = logging.getLogger(__name__)
@@ -154,7 +154,8 @@ class SensiboClimate(ClimateDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {ATTR_CURRENT_HUMIDITY: self.current_humidity}
return {ATTR_CURRENT_HUMIDITY: self.current_humidity,
'battery': self.current_battery}
@property
def temperature_unit(self):
@@ -191,6 +192,11 @@ class SensiboClimate(ClimateDevice):
"""Return the current humidity."""
return self._measurements['humidity']
@property
def current_battery(self):
"""Return the current battery voltage."""
return self._measurements.get('batteryVoltage')
@property
def current_temperature(self):
"""Return the current temperature."""
@@ -240,13 +246,13 @@ 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 DEFAULT_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 DEFAULT_MAX_TEMP
@property
def unique_id(self):

View File

@@ -8,7 +8,8 @@ import logging
from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS)
from homeassistant.components.climate import (
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.components.tado import DATA_TADO
@@ -232,16 +233,16 @@ class TadoClimate(ClimateDevice):
"""Return the minimum temperature."""
if self._min_temp:
return self._min_temp
# get default temp from super class
return super().min_temp
return DEFAULT_MIN_TEMP
@property
def max_temp(self):
"""Return the maximum temperature."""
if self._max_temp:
return self._max_temp
# Get default temp from super class
return super().max_temp
return DEFAULT_MAX_TEMP
def update(self):
"""Update the state of this climate device."""

View File

@@ -11,9 +11,11 @@ import voluptuous as vol
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW, ClimateDevice)
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_AWAY_MODE,
SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW,
SUPPORT_HOLD_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
ClimateDevice)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT,
CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS,
@@ -27,14 +29,20 @@ _LOGGER = logging.getLogger(__name__)
ATTR_FAN_STATE = 'fan_state'
ATTR_HVAC_STATE = 'hvac_state'
CONF_HUMIDIFIER = 'humidifier'
DEFAULT_SSL = False
VALID_FAN_STATES = [STATE_ON, STATE_AUTO]
VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO]
HOLD_MODE_OFF = 'off'
HOLD_MODE_TEMPERATURE = 'temperature'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HUMIDIFIER, default=True): cv.boolean,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_TIMEOUT, default=5):
vol.All(vol.Coerce(int), vol.Range(min=1)),
@@ -50,6 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
timeout = config.get(CONF_TIMEOUT)
humidifier = config.get(CONF_HUMIDIFIER)
if config.get(CONF_SSL):
proto = 'https'
@@ -60,15 +69,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
addr=host, timeout=timeout, user=username, password=password,
proto=proto)
add_devices([VenstarThermostat(client)], True)
add_devices([VenstarThermostat(client, humidifier)], True)
class VenstarThermostat(ClimateDevice):
"""Representation of a Venstar thermostat."""
def __init__(self, client):
def __init__(self, client, humidifier):
"""Initialize the thermostat."""
self._client = client
self._humidifier = humidifier
def update(self):
"""Update the data from the thermostat."""
@@ -81,14 +91,18 @@ class VenstarThermostat(ClimateDevice):
def supported_features(self):
"""Return the list of supported features."""
features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE)
SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE)
if self._client.mode == self._client.MODE_AUTO:
features |= (SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
if self._client.hum_active == 1:
features |= SUPPORT_TARGET_HUMIDITY
if (self._humidifier and
hasattr(self._client, 'hum_active')):
features |= (SUPPORT_TARGET_HUMIDITY |
SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_TARGET_HUMIDITY_LOW)
return features
@@ -197,6 +211,18 @@ class VenstarThermostat(ClimateDevice):
"""Return the maximum humidity. Hardcoded to 60 in API."""
return 60
@property
def is_away_mode_on(self):
"""Return the status of away mode."""
return self._client.away == self._client.AWAY_AWAY
@property
def current_hold_mode(self):
"""Return the status of hold mode."""
if self._client.schedule == 0:
return HOLD_MODE_TEMPERATURE
return HOLD_MODE_OFF
def _set_operation_mode(self, operation_mode):
"""Change the operation mode (internal)."""
if operation_mode == STATE_HEAT:
@@ -259,3 +285,30 @@ class VenstarThermostat(ClimateDevice):
if not success:
_LOGGER.error("Failed to change the target humidity level")
def set_hold_mode(self, hold_mode):
"""Set the hold mode."""
if hold_mode == HOLD_MODE_TEMPERATURE:
success = self._client.set_schedule(0)
elif hold_mode == HOLD_MODE_OFF:
success = self._client.set_schedule(1)
else:
_LOGGER.error("Unknown hold mode: %s", hold_mode)
success = False
if not success:
_LOGGER.error("Failed to change the schedule/hold state")
def turn_away_mode_on(self):
"""Activate away mode."""
success = self._client.set_away(self._client.AWAY_AWAY)
if not success:
_LOGGER.error("Failed to activate away mode")
def turn_away_mode_off(self):
"""Deactivate away mode."""
success = self._client.set_away(self._client.AWAY_HOME)
if not success:
_LOGGER.error("Failed to deactivate away mode")

View File

@@ -190,7 +190,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
@property
def cool_on(self):
"""Return whether or not the heat is actually heating."""
return self.wink.heat_on()
return self.wink.cool_on()
@property
def current_operation(self):

View File

@@ -185,7 +185,7 @@ class CloudIoT:
yield from client.send_json(response)
except client_exceptions.WSServerHandshakeError as err:
if err.code == 401:
if err.status == 401:
disconnect_warn = 'Invalid auth.'
self.close_requested = True
# Should we notify user?

View File

@@ -21,7 +21,7 @@ ON_DEMAND = ('zwave',)
async def async_setup(hass, config):
"""Set up the config component."""
await hass.components.frontend.async_register_built_in_panel(
'config', 'config', 'mdi:settings')
'config', 'config', 'hass:settings')
async def setup_panel(panel_name):
"""Set up a panel."""

View File

@@ -96,6 +96,7 @@ async def async_setup(hass, config):
async def process(service):
"""Parse text into commands."""
text = service.data[ATTR_TEXT]
_LOGGER.debug('Processing: <%s>', text)
try:
await _process(hass, text)
except intent.IntentHandleError as err:

View File

@@ -0,0 +1,10 @@
# Describes the format for available component services
process:
description: Launch a conversation from a transcribed text.
fields:
text:
description: Transcribed text
example: Turn all lights on

View File

@@ -9,9 +9,9 @@ import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (ATTR_ENTITY_ID, CONF_ICON, CONF_NAME)
from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
@@ -94,9 +94,8 @@ def async_reset(hass, entity_id):
DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id}))
@asyncio.coroutine
def async_setup(hass, config):
"""Set up a counter."""
async def async_setup(hass, config):
"""Set up the counters."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
entities = []
@@ -115,8 +114,7 @@ def async_setup(hass, config):
if not entities:
return False
@asyncio.coroutine
def async_handler_service(service):
async def async_handler_service(service):
"""Handle a call to the counter services."""
target_counters = component.async_extract_from_service(service)
@@ -129,7 +127,7 @@ def async_setup(hass, config):
tasks = [getattr(counter, attr)() for counter in target_counters]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
await asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_INCREMENT, async_handler_service)
@@ -138,7 +136,7 @@ def async_setup(hass, config):
hass.services.async_register(
DOMAIN, SERVICE_RESET, async_handler_service)
yield from component.async_add_entities(entities)
await component.async_add_entities(entities)
return True
@@ -181,30 +179,26 @@ class Counter(Entity):
ATTR_STEP: self._step,
}
@asyncio.coroutine
def async_added_to_hass(self):
async def async_added_to_hass(self):
"""Call when entity about to be added to Home Assistant."""
# If not None, we got an initial value.
if self._state is not None:
return
state = yield from async_get_last_state(self.hass, self.entity_id)
state = await async_get_last_state(self.hass, self.entity_id)
self._state = state and state.state == state
@asyncio.coroutine
def async_decrement(self):
async def async_decrement(self):
"""Decrement the counter."""
self._state -= self._step
yield from self.async_update_ha_state()
await self.async_update_ha_state()
@asyncio.coroutine
def async_increment(self):
async def async_increment(self):
"""Increment a counter."""
self._state += self._step
yield from self.async_update_ha_state()
await self.async_update_ha_state()
@asyncio.coroutine
def async_reset(self):
async def async_reset(self):
"""Reset a counter."""
self._state = self._initial
yield from self.async_update_ha_state()
await self.async_update_ha_state()

View File

@@ -1,5 +1,5 @@
"""
Support for Gogogate2 Garage Doors.
Support for Gogogate2 garage Doors.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover.gogogate2/
@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_IP_ADDRESS, CONF_NAME)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pygogogate2==0.0.7']
REQUIREMENTS = ['pygogogate2==0.1.1']
_LOGGER = logging.getLogger(__name__)
@@ -25,9 +25,9 @@ NOTIFICATION_ID = 'gogogate2_notification'
NOTIFICATION_TITLE = 'Gogogate2 Cover Setup'
COVER_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@@ -36,10 +36,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Gogogate2 component."""
from pygogogate2 import Gogogate2API as pygogogate2
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
ip_address = config.get(CONF_IP_ADDRESS)
name = config.get(CONF_NAME)
password = config.get(CONF_PASSWORD)
username = config.get(CONF_USERNAME)
mygogogate2 = pygogogate2(username, password, ip_address)
try:

View File

@@ -235,6 +235,11 @@ class MqttCover(MqttAvailability, CoverDevice):
"""No polling needed."""
return False
@property
def assumed_state(self):
"""Return true if we do optimistic updates."""
return self._optimistic
@property
def name(self):
"""Return the name of the cover."""

View File

@@ -69,6 +69,11 @@ class MyQDevice(CoverDevice):
self._name = device['name']
self._status = STATE_CLOSED
@property
def device_class(self):
"""Define this cover as a garage door."""
return 'garage'
@property
def should_poll(self):
"""Poll for state."""

View File

@@ -42,7 +42,7 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
set_req = self.gateway.const.SetReq
return self._values.get(set_req.V_DIMMER)
def open_cover(self, **kwargs):
async def async_open_cover(self, **kwargs):
"""Move the cover up."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
@@ -53,9 +53,9 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
self._values[set_req.V_DIMMER] = 100
else:
self._values[set_req.V_LIGHT] = STATE_ON
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
def close_cover(self, **kwargs):
async def async_close_cover(self, **kwargs):
"""Move the cover down."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(
@@ -66,9 +66,9 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
self._values[set_req.V_DIMMER] = 0
else:
self._values[set_req.V_LIGHT] = STATE_OFF
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
def set_cover_position(self, **kwargs):
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
set_req = self.gateway.const.SetReq
@@ -77,9 +77,9 @@ class MySensorsCover(mysensors.MySensorsEntity, CoverDevice):
if self.gateway.optimistic:
# Optimistically assume that cover has changed state.
self._values[set_req.V_DIMMER] = position
self.schedule_update_ha_state()
self.async_schedule_update_ha_state()
def stop_cover(self, **kwargs):
async def async_stop_cover(self, **kwargs):
"""Stop the device."""
set_req = self.gateway.const.SetReq
self.gateway.set_child_value(

View File

@@ -0,0 +1,103 @@
"""
Ryobi platform for the cover component.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover.ryobi_gdo/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE)
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, STATE_UNKNOWN, STATE_CLOSED)
REQUIREMENTS = ['py_ryobi_gdo==0.0.10']
_LOGGER = logging.getLogger(__name__)
CONF_DEVICE_ID = 'device_id'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]),
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
})
SUPPORTED_FEATURES = (SUPPORT_OPEN | SUPPORT_CLOSE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Ryobi covers."""
from py_ryobi_gdo import RyobiGDO as ryobi_door
covers = []
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
devices = config.get(CONF_DEVICE_ID)
for device_id in devices:
my_door = ryobi_door(username, password, device_id)
_LOGGER.debug("Getting the API key")
if my_door.get_api_key() is False:
_LOGGER.error("Wrong credentials, no API key retrieved")
return
_LOGGER.debug("Checking if the device ID is present")
if my_door.check_device_id() is False:
_LOGGER.error("%s not in your device list", device_id)
return
_LOGGER.debug("Adding device %s to covers", device_id)
covers.append(RyobiCover(hass, my_door))
if covers:
_LOGGER.debug("Adding covers")
add_devices(covers, True)
class RyobiCover(CoverDevice):
"""Representation of a ryobi cover."""
def __init__(self, hass, ryobi_door):
"""Initialize the cover."""
self.ryobi_door = ryobi_door
self._name = 'ryobi_gdo_{}'.format(ryobi_door.get_device_id())
self._door_state = None
@property
def name(self):
"""Return the name of the cover."""
return self._name
@property
def is_closed(self):
"""Return if the cover is closed."""
if self._door_state == STATE_UNKNOWN:
return False
return self._door_state == STATE_CLOSED
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'garage'
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORTED_FEATURES
def close_cover(self, **kwargs):
"""Close the cover."""
_LOGGER.debug("Closing garage door")
self.ryobi_door.close_device()
def open_cover(self, **kwargs):
"""Open the cover."""
_LOGGER.debug("Opening garage door")
self.ryobi_door.open_device()
def update(self):
"""Update status from the door."""
_LOGGER.debug("Updating RyobiGDO status")
self.ryobi_door.update()
self._door_state = self.ryobi_door.get_door_status()

View File

@@ -79,7 +79,9 @@ class TahomaCover(TahomaDevice, CoverDevice):
if self.tahoma_device.type == \
'io:RollerShutterWithLowSpeedManagementIOComponent':
self.apply_action('setPosition', 'secured')
elif self.tahoma_device.type == 'rts:BlindRTSComponent':
elif self.tahoma_device.type in \
('rts:BlindRTSComponent',
'io:ExteriorVenetianBlindIOComponent'):
self.apply_action('my')
else:
self.apply_action('stopIdentify')

View File

@@ -19,8 +19,14 @@
"link": {
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button",
"title": "Link with deCONZ"
},
"options": {
"title": "Extra configuration options for deCONZ",
"data": {
"allow_clip_sensor": "Allow importing virtual sensors"
}
}
},
"title": "deCONZ"
"title": "deCONZ Zigbee gateway"
}
}

View File

@@ -19,10 +19,10 @@ from homeassistant.util.json import load_json
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
from .const import (
CONFIG_FILE, DATA_DECONZ_EVENT, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
REQUIREMENTS = ['pydeconz==37']
REQUIREMENTS = ['pydeconz==38']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -104,8 +104,10 @@ async def async_setup_entry(hass, config_entry):
def async_add_remote(sensors):
"""Setup remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_REMOTE:
if sensor.type in DECONZ_REMOTE and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
hass.data[DATA_DECONZ_EVENT].append(DeconzEvent(hass, sensor))
hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_remote))

View File

@@ -8,13 +8,15 @@ from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.helpers import aiohttp_client
from homeassistant.util.json import load_json
from .const import CONFIG_FILE, DOMAIN
from .const import CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DOMAIN
CONF_BRIDGEID = 'bridgeid'
@callback
def configured_hosts(hass):
"""Return a set of the configured hosts."""
return set(entry.data['host'] for entry
return set(entry.data[CONF_HOST] for entry
in hass.config_entries.async_entries(DOMAIN))
@@ -30,7 +32,12 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
self.deconz_config = {}
async def async_step_init(self, user_input=None):
"""Handle a deCONZ config flow start."""
"""Handle a deCONZ config flow start.
Only allows one instance to be set up.
If only one bridge is found go to link step.
If more than one bridge is found let user choose bridge to link.
"""
from pydeconz.utils import async_discovery
if configured_hosts(self.hass):
@@ -65,7 +72,7 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
async def async_step_link(self, user_input=None):
"""Attempt to link with the deCONZ bridge."""
from pydeconz.utils import async_get_api_key, async_get_bridgeid
from pydeconz.utils import async_get_api_key
errors = {}
if user_input is not None:
@@ -75,13 +82,7 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
api_key = await async_get_api_key(session, **self.deconz_config)
if api_key:
self.deconz_config[CONF_API_KEY] = api_key
if 'bridgeid' not in self.deconz_config:
self.deconz_config['bridgeid'] = await async_get_bridgeid(
session, **self.deconz_config)
return self.async_create_entry(
title='deCONZ-' + self.deconz_config['bridgeid'],
data=self.deconz_config
)
return await self.async_step_options()
errors['base'] = 'no_key'
return self.async_show_form(
@@ -89,6 +90,34 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
errors=errors,
)
async def async_step_options(self, user_input=None):
"""Extra options for deCONZ.
CONF_CLIP_SENSOR -- Allow user to choose if they want clip sensors.
"""
from pydeconz.utils import async_get_bridgeid
if user_input is not None:
self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = \
user_input[CONF_ALLOW_CLIP_SENSOR]
if CONF_BRIDGEID not in self.deconz_config:
session = aiohttp_client.async_get_clientsession(self.hass)
self.deconz_config[CONF_BRIDGEID] = await async_get_bridgeid(
session, **self.deconz_config)
return self.async_create_entry(
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
data=self.deconz_config
)
return self.async_show_form(
step_id='options',
data_schema=vol.Schema({
vol.Optional(CONF_ALLOW_CLIP_SENSOR): bool,
}),
)
async def async_step_discovery(self, discovery_info):
"""Prepare configuration for a discovered deCONZ bridge.
@@ -97,7 +126,7 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
deconz_config = {}
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
deconz_config['bridgeid'] = discovery_info.get('serial')
deconz_config[CONF_BRIDGEID] = discovery_info.get('serial')
config_file = await self.hass.async_add_job(
load_json, self.hass.config.path(CONFIG_FILE))
@@ -121,19 +150,15 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler):
Otherwise we will delegate to `link` step which
will ask user to link the bridge.
"""
from pydeconz.utils import async_get_bridgeid
if configured_hosts(self.hass):
return self.async_abort(reason='one_instance_only')
elif CONF_API_KEY not in import_config:
self.deconz_config = import_config
self.deconz_config = import_config
if CONF_API_KEY not in import_config:
return await self.async_step_link()
if 'bridgeid' not in import_config:
session = aiohttp_client.async_get_clientsession(self.hass)
import_config['bridgeid'] = await async_get_bridgeid(
session, **import_config)
self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = True
return self.async_create_entry(
title='deCONZ-' + import_config['bridgeid'],
data=import_config
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
data=self.deconz_config
)

View File

@@ -8,3 +8,5 @@ CONFIG_FILE = 'deconz.conf'
DATA_DECONZ_EVENT = 'deconz_events'
DATA_DECONZ_ID = 'deconz_entities'
DATA_DECONZ_UNSUB = 'deconz_dispatchers'
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'

View File

@@ -1,6 +1,6 @@
{
"config": {
"title": "deCONZ",
"title": "deCONZ Zigbee gateway",
"step": {
"init": {
"title": "Define deCONZ gateway",
@@ -12,6 +12,12 @@
"link": {
"title": "Link with deCONZ",
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button"
},
"options": {
"title": "Extra configuration options for deCONZ",
"data":{
"allow_clip_sensor": "Allow importing virtual sensors"
}
}
},
"error": {

View File

@@ -33,7 +33,7 @@ from homeassistant.helpers.event import async_track_utc_time_change
from homeassistant.const import (
ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME, CONF_MAC,
DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_ID,
CONF_ICON, ATTR_ICON)
CONF_ICON, ATTR_ICON, ATTR_NAME)
_LOGGER = logging.getLogger(__name__)
@@ -71,7 +71,6 @@ ATTR_GPS = 'gps'
ATTR_HOST_NAME = 'host_name'
ATTR_LOCATION_NAME = 'location_name'
ATTR_MAC = 'mac'
ATTR_NAME = 'name'
ATTR_SOURCE_TYPE = 'source_type'
ATTR_CONSIDER_HOME = 'consider_home'

View File

@@ -4,22 +4,27 @@ Support for Google Maps location sharing.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.google_maps/
"""
import logging
from datetime import timedelta
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, SOURCE_TYPE_GPS)
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import ATTR_ID, CONF_PASSWORD, CONF_USERNAME
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
REQUIREMENTS = ['locationsharinglib==2.0.7']
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['locationsharinglib==1.2.2']
ATTR_ADDRESS = 'address'
ATTR_FULL_NAME = 'full_name'
ATTR_LAST_SEEN = 'last_seen'
ATTR_NICKNAME = 'nickname'
CREDENTIALS_FILE = '.google_maps_location_sharing.cookies'
@@ -60,19 +65,23 @@ class GoogleMapsScanner(object):
self.success_init = True
except InvalidUser:
_LOGGER.error('You have specified invalid login credentials')
_LOGGER.error("You have specified invalid login credentials")
self.success_init = False
def _update_info(self, now=None):
for person in self.service.get_all_people():
dev_id = 'google_maps_{0}'.format(slugify(person.id))
try:
dev_id = 'google_maps_{0}'.format(slugify(person.id))
except TypeError:
_LOGGER.warning("No location(s) shared with this account")
return
attrs = {
'id': person.id,
'nickname': person.nickname,
'full_name': person.full_name,
'last_seen': person.datetime,
'address': person.address
ATTR_ADDRESS: person.address,
ATTR_FULL_NAME: person.full_name,
ATTR_ID: person.id,
ATTR_LAST_SEEN: person.datetime,
ATTR_NICKNAME: person.nickname,
}
self.see(
dev_id=dev_id,
@@ -80,5 +89,5 @@ class GoogleMapsScanner(object):
picture=person.picture_url,
source_type=SOURCE_TYPE_GPS,
gps_accuracy=person.accuracy,
attributes=attrs
attributes=attrs,
)

View File

@@ -14,15 +14,18 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_TYPE
)
_LOGGER = logging.getLogger(__name__)
DEFAULT_TYPE = "rogers"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): cv.string,
})
@@ -49,6 +52,11 @@ class HitronCODADeviceScanner(DeviceScanner):
self._username = config.get(CONF_USERNAME)
self._password = config.get(CONF_PASSWORD)
if config.get(CONF_TYPE) == "shaw":
self._type = 'pwd'
else:
self._type = 'pws'
self._userid = None
self.success_init = self._update_info()
@@ -74,7 +82,7 @@ class HitronCODADeviceScanner(DeviceScanner):
try:
data = [
('user', self._username),
('pws', self._password),
(self._type, self._password),
]
res = requests.post(self._loginurl, data=data, timeout=10)
except requests.exceptions.Timeout:

View File

@@ -24,8 +24,9 @@ _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pyicloud==0.9.1']
CONF_IGNORED_DEVICES = 'ignored_devices'
CONF_ACCOUNTNAME = 'account_name'
CONF_MAX_INTERVAL = 'max_interval'
CONF_GPS_ACCURACY_THRESHOLD = 'gps_accuracy_threshold'
# entity attributes
ATTR_ACCOUNTNAME = 'account_name'
@@ -64,13 +65,15 @@ DEVICESTATUSCODES = {
SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ACCOUNTNAME): vol.All(cv.ensure_list, [cv.slugify]),
vol.Optional(ATTR_DEVICENAME): cv.slugify,
vol.Optional(ATTR_INTERVAL): cv.positive_int,
vol.Optional(ATTR_INTERVAL): cv.positive_int
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(ATTR_ACCOUNTNAME): cv.slugify,
vol.Optional(CONF_MAX_INTERVAL, default=30): cv.positive_int,
vol.Optional(CONF_GPS_ACCURACY_THRESHOLD, default=1000): cv.positive_int
})
@@ -79,8 +82,11 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
account = config.get(CONF_ACCOUNTNAME, slugify(username.partition('@')[0]))
max_interval = config.get(CONF_MAX_INTERVAL)
gps_accuracy_threshold = config.get(CONF_GPS_ACCURACY_THRESHOLD)
icloudaccount = Icloud(hass, username, password, account, see)
icloudaccount = Icloud(hass, username, password, account, max_interval,
gps_accuracy_threshold, see)
if icloudaccount.api is not None:
ICLOUDTRACKERS[account] = icloudaccount
@@ -96,6 +102,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
for account in accounts:
if account in ICLOUDTRACKERS:
ICLOUDTRACKERS[account].lost_iphone(devicename)
hass.services.register(DOMAIN, 'icloud_lost_iphone', lost_iphone,
schema=SERVICE_SCHEMA)
@@ -106,6 +113,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
for account in accounts:
if account in ICLOUDTRACKERS:
ICLOUDTRACKERS[account].update_icloud(devicename)
hass.services.register(DOMAIN, 'icloud_update', update_icloud,
schema=SERVICE_SCHEMA)
@@ -115,6 +123,7 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
for account in accounts:
if account in ICLOUDTRACKERS:
ICLOUDTRACKERS[account].reset_account_icloud()
hass.services.register(DOMAIN, 'icloud_reset_account',
reset_account_icloud, schema=SERVICE_SCHEMA)
@@ -137,7 +146,8 @@ def setup_scanner(hass, config: dict, see, discovery_info=None):
class Icloud(DeviceScanner):
"""Representation of an iCloud account."""
def __init__(self, hass, username, password, name, see):
def __init__(self, hass, username, password, name, max_interval,
gps_accuracy_threshold, see):
"""Initialize an iCloud account."""
self.hass = hass
self.username = username
@@ -148,6 +158,8 @@ class Icloud(DeviceScanner):
self.seen_devices = {}
self._overridestates = {}
self._intervals = {}
self._max_interval = max_interval
self._gps_accuracy_threshold = gps_accuracy_threshold
self.see = see
self._trusted_device = None
@@ -348,7 +360,7 @@ class Icloud(DeviceScanner):
self._overridestates[devicename] = None
if currentzone is not None:
self._intervals[devicename] = 30
self._intervals[devicename] = self._max_interval
return
if mindistance is None:
@@ -363,7 +375,6 @@ class Icloud(DeviceScanner):
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:
@@ -403,22 +414,24 @@ class Icloud(DeviceScanner):
status = device.status(DEVICESTATUSSET)
battery = status.get('batteryLevel', 0) * 100
location = status['location']
if location:
self.determine_interval(
devicename, location['latitude'],
location['longitude'], battery)
interval = self._intervals.get(devicename, 1)
attrs[ATTR_INTERVAL] = interval
accuracy = location['horizontalAccuracy']
kwargs['dev_id'] = dev_id
kwargs['host_name'] = status['name']
kwargs['gps'] = (location['latitude'],
location['longitude'])
kwargs['battery'] = battery
kwargs['gps_accuracy'] = accuracy
kwargs[ATTR_ATTRIBUTES] = attrs
self.see(**kwargs)
self.seen_devices[devicename] = True
if location and location['horizontalAccuracy']:
horizontal_accuracy = int(location['horizontalAccuracy'])
if horizontal_accuracy < self._gps_accuracy_threshold:
self.determine_interval(
devicename, location['latitude'],
location['longitude'], battery)
interval = self._intervals.get(devicename, 1)
attrs[ATTR_INTERVAL] = interval
accuracy = location['horizontalAccuracy']
kwargs['dev_id'] = dev_id
kwargs['host_name'] = status['name']
kwargs['gps'] = (location['latitude'],
location['longitude'])
kwargs['battery'] = battery
kwargs['gps_accuracy'] = accuracy
kwargs[ATTR_ATTRIBUTES] = attrs
self.see(**kwargs)
self.seen_devices[devicename] = True
except PyiCloudNoDevicesException:
_LOGGER.error("No iCloud Devices found")
@@ -434,7 +447,7 @@ class Icloud(DeviceScanner):
device.play_sound()
def update_icloud(self, devicename=None):
"""Authenticate against iCloud and scan for devices."""
"""Request device information from iCloud and update device_tracker."""
from pyicloud.exceptions import PyiCloudNoDevicesException
if self.api is None:
@@ -443,13 +456,13 @@ class Icloud(DeviceScanner):
try:
if devicename is not None:
if devicename in self.devices:
self.devices[devicename].location()
self.update_device(devicename)
else:
_LOGGER.error("devicename %s unknown for account %s",
devicename, self._attrs[ATTR_ACCOUNTNAME])
else:
for device in self.devices:
self.devices[device].location()
self.update_device(device)
except PyiCloudNoDevicesException:
_LOGGER.error("No iCloud Devices found")

View File

@@ -15,14 +15,18 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import HomeAssistantError
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import (
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SSL)
_LOGGER = logging.getLogger(__name__)
DEFAULT_SSL = False
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean
})
@@ -44,7 +48,9 @@ class LuciDeviceScanner(DeviceScanner):
def __init__(self, config):
"""Initialize the scanner."""
self.host = config[CONF_HOST]
host = config[CONF_HOST]
protocol = 'http' if not config[CONF_SSL] else 'https'
self.origin = '{}://{}'.format(protocol, host)
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -57,7 +63,7 @@ class LuciDeviceScanner(DeviceScanner):
def refresh_token(self):
"""Get a new token."""
self.token = _get_token(self.host, self.username, self.password)
self.token = _get_token(self.origin, self.username, self.password)
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
@@ -67,9 +73,9 @@ class LuciDeviceScanner(DeviceScanner):
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
if self.mac2name is None:
url = 'http://{}/cgi-bin/luci/rpc/uci'.format(self.host)
result = _req_json_rpc(url, 'get_all', 'dhcp',
params={'auth': self.token})
url = '{}/cgi-bin/luci/rpc/uci'.format(self.origin)
result = _req_json_rpc(
url, 'get_all', 'dhcp', params={'auth': self.token})
if result:
hosts = [x for x in result.values()
if x['.type'] == 'host' and
@@ -92,11 +98,11 @@ class LuciDeviceScanner(DeviceScanner):
_LOGGER.info("Checking ARP")
url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host)
url = '{}/cgi-bin/luci/rpc/sys'.format(self.origin)
try:
result = _req_json_rpc(url, 'net.arptable',
params={'auth': self.token})
result = _req_json_rpc(
url, 'net.arptable', params={'auth': self.token})
except InvalidLuciTokenError:
_LOGGER.info("Refreshing token")
self.refresh_token()
@@ -146,10 +152,10 @@ def _req_json_rpc(url, method, *args, **kwargs):
raise InvalidLuciTokenError
else:
_LOGGER.error('Invalid response from luci: %s', res)
_LOGGER.error("Invalid response from luci: %s", res)
def _get_token(host, username, password):
"""Get authentication token for the given host+username+password."""
url = 'http://{}/cgi-bin/luci/rpc/auth'.format(host)
def _get_token(origin, username, password):
"""Get authentication token for the given configuration."""
url = '{}/cgi-bin/luci/rpc/auth'.format(origin)
return _req_json_rpc(url, 'login', username, password)

View File

@@ -37,8 +37,10 @@ SERVICE_WINK = 'wink'
SERVICE_XIAOMI_GW = 'xiaomi_gw'
SERVICE_TELLDUSLIVE = 'tellstick'
SERVICE_HUE = 'philips_hue'
SERVICE_KONNECTED = 'konnected'
SERVICE_DECONZ = 'deconz'
SERVICE_DAIKIN = 'daikin'
SERVICE_SABNZBD = 'sabnzbd'
SERVICE_SAMSUNG_PRINTER = 'samsung_printer'
SERVICE_HOMEKIT = 'homekit'
@@ -59,7 +61,9 @@ SERVICE_HANDLERS = {
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
SERVICE_TELLDUSLIVE: ('tellduslive', None),
SERVICE_DAIKIN: ('daikin', None),
SERVICE_SABNZBD: ('sabnzbd', None),
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
SERVICE_KONNECTED: ('konnected', None),
'google_cast': ('media_player', 'cast'),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
@@ -74,12 +78,12 @@ SERVICE_HANDLERS = {
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
'harmony': ('remote', 'harmony'),
'sabnzbd': ('sensor', 'sabnzbd'),
'bose_soundtouch': ('media_player', 'soundtouch'),
'bluesound': ('media_player', 'bluesound'),
'songpal': ('media_player', 'songpal'),
'kodi': ('media_player', 'kodi'),
'volumio': ('media_player', 'volumio'),
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
}
OPTIONAL_SERVICE_HANDLERS = {
@@ -190,6 +194,7 @@ def _discover(netdisco):
for disc in netdisco.discover():
for service in netdisco.get_info(disc):
results.append((disc, service))
finally:
netdisco.stop()

View File

@@ -15,7 +15,7 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['lakeside==0.5']
REQUIREMENTS = ['lakeside==0.7']
_LOGGER = logging.getLogger(__name__)

View File

@@ -51,8 +51,8 @@ set_direction:
description: Name(s) of the entities to toggle
example: 'fan.living_room'
direction:
description: The direction to rotate
example: 'left'
description: The direction to rotate. Either 'forward' or 'reverse'
example: 'forward'
dyson_set_night_mode:
description: Set the fan in night mode.

View File

@@ -18,11 +18,10 @@ from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH, SUPPORT_SET_SPEED,
SUPPORT_OSCILLATE, FanEntity,
ATTR_SPEED, ATTR_OSCILLATING,
ENTITY_ID_FORMAT)
from homeassistant.components.fan import (
SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
FanEntity, ATTR_SPEED, ATTR_OSCILLATING, ENTITY_ID_FORMAT,
SUPPORT_DIRECTION, DIRECTION_FORWARD, DIRECTION_REVERSE, ATTR_DIRECTION)
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.script import Script
@@ -33,25 +32,30 @@ CONF_FANS = 'fans'
CONF_SPEED_LIST = 'speeds'
CONF_SPEED_TEMPLATE = 'speed_template'
CONF_OSCILLATING_TEMPLATE = 'oscillating_template'
CONF_DIRECTION_TEMPLATE = 'direction_template'
CONF_ON_ACTION = 'turn_on'
CONF_OFF_ACTION = 'turn_off'
CONF_SET_SPEED_ACTION = 'set_speed'
CONF_SET_OSCILLATING_ACTION = 'set_oscillating'
CONF_SET_DIRECTION_ACTION = 'set_direction'
_VALID_STATES = [STATE_ON, STATE_OFF]
_VALID_OSC = [True, False]
_VALID_DIRECTIONS = [DIRECTION_FORWARD, DIRECTION_REVERSE]
FAN_SCHEMA = vol.Schema({
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_SPEED_TEMPLATE): cv.template,
vol.Optional(CONF_OSCILLATING_TEMPLATE): cv.template,
vol.Optional(CONF_DIRECTION_TEMPLATE): cv.template,
vol.Required(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
vol.Required(CONF_OFF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_SET_SPEED_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_SET_OSCILLATING_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_SET_DIRECTION_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(
CONF_SPEED_LIST,
@@ -80,18 +84,21 @@ async def async_setup_platform(
oscillating_template = device_config.get(
CONF_OSCILLATING_TEMPLATE
)
direction_template = device_config.get(CONF_DIRECTION_TEMPLATE)
on_action = device_config[CONF_ON_ACTION]
off_action = device_config[CONF_OFF_ACTION]
set_speed_action = device_config.get(CONF_SET_SPEED_ACTION)
set_oscillating_action = device_config.get(CONF_SET_OSCILLATING_ACTION)
set_direction_action = device_config.get(CONF_SET_DIRECTION_ACTION)
speed_list = device_config[CONF_SPEED_LIST]
entity_ids = set()
manual_entity_ids = device_config.get(CONF_ENTITY_ID)
for template in (state_template, speed_template, oscillating_template):
for template in (state_template, speed_template, oscillating_template,
direction_template):
if template is None:
continue
template.hass = hass
@@ -114,8 +121,9 @@ async def async_setup_platform(
TemplateFan(
hass, device, friendly_name,
state_template, speed_template, oscillating_template,
on_action, off_action, set_speed_action,
set_oscillating_action, speed_list, entity_ids
direction_template, on_action, off_action, set_speed_action,
set_oscillating_action, set_direction_action, speed_list,
entity_ids
)
)
@@ -127,8 +135,9 @@ class TemplateFan(FanEntity):
def __init__(self, hass, device_id, friendly_name,
state_template, speed_template, oscillating_template,
on_action, off_action, set_speed_action,
set_oscillating_action, speed_list, entity_ids):
direction_template, on_action, off_action, set_speed_action,
set_oscillating_action, set_direction_action, speed_list,
entity_ids):
"""Initialize the fan."""
self.hass = hass
self.entity_id = async_generate_entity_id(
@@ -138,6 +147,7 @@ class TemplateFan(FanEntity):
self._template = state_template
self._speed_template = speed_template
self._oscillating_template = oscillating_template
self._direction_template = direction_template
self._supported_features = 0
self._on_script = Script(hass, on_action)
@@ -151,9 +161,14 @@ class TemplateFan(FanEntity):
if set_oscillating_action:
self._set_oscillating_script = Script(hass, set_oscillating_action)
self._set_direction_script = None
if set_direction_action:
self._set_direction_script = Script(hass, set_direction_action)
self._state = STATE_OFF
self._speed = None
self._oscillating = None
self._direction = None
self._template.hass = self.hass
if self._speed_template:
@@ -162,6 +177,9 @@ class TemplateFan(FanEntity):
if self._oscillating_template:
self._oscillating_template.hass = self.hass
self._supported_features |= SUPPORT_OSCILLATE
if self._direction_template:
self._direction_template.hass = self.hass
self._supported_features |= SUPPORT_DIRECTION
self._entities = entity_ids
# List of valid speeds
@@ -197,6 +215,11 @@ class TemplateFan(FanEntity):
"""Return the oscillation state."""
return self._oscillating
@property
def direction(self):
"""Return the oscillation state."""
return self._direction
@property
def should_poll(self):
"""Return the polling state."""
@@ -236,10 +259,30 @@ class TemplateFan(FanEntity):
if self._set_oscillating_script is None:
return
await self._set_oscillating_script.async_run(
{ATTR_OSCILLATING: oscillating}
)
self._oscillating = oscillating
if oscillating in _VALID_OSC:
self._oscillating = oscillating
await self._set_oscillating_script.async_run(
{ATTR_OSCILLATING: oscillating})
else:
_LOGGER.error(
'Received invalid oscillating value: %s. ' +
'Expected: %s.',
oscillating, ', '.join(_VALID_OSC))
async def async_set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
if self._set_direction_script is None:
return
if direction in _VALID_DIRECTIONS:
self._direction = direction
await self._set_direction_script.async_run(
{ATTR_DIRECTION: direction})
else:
_LOGGER.error(
'Received invalid direction: %s. ' +
'Expected: %s.',
direction, ', '.join(_VALID_DIRECTIONS))
async def async_added_to_hass(self):
"""Register callbacks."""
@@ -308,6 +351,7 @@ class TemplateFan(FanEntity):
oscillating = self._oscillating_template.async_render()
except TemplateError as ex:
_LOGGER.error(ex)
oscillating = None
self._state = None
# Validate osc
@@ -322,3 +366,24 @@ class TemplateFan(FanEntity):
'Received invalid oscillating: %s. ' +
'Expected: True/False.', oscillating)
self._oscillating = None
# Update direction if 'direction_template' is configured
if self._direction_template is not None:
try:
direction = self._direction_template.async_render()
except TemplateError as ex:
_LOGGER.error(ex)
direction = None
self._state = None
# Validate speed
if direction in _VALID_DIRECTIONS:
self._direction = direction
elif direction == STATE_UNKNOWN:
self._direction = None
else:
_LOGGER.error(
'Received invalid direction: %s. ' +
'Expected: %s.',
direction, ', '.join(_VALID_DIRECTIONS))
self._direction = None

View File

@@ -10,7 +10,6 @@ from homeassistant.components import zha
from homeassistant.components.fan import (
DOMAIN, FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED)
from homeassistant.const import STATE_UNKNOWN
DEPENDENCIES = ['zha']
@@ -72,7 +71,7 @@ class ZhaFan(zha.Entity, FanEntity):
@property
def is_on(self) -> bool:
"""Return true if entity is on."""
if self._state == STATE_UNKNOWN:
if self._state is None:
return False
return self._state != SPEED_OFF
@@ -103,7 +102,7 @@ class ZhaFan(zha.Entity, FanEntity):
"""Retrieve latest state."""
result = yield from zha.safe_read(self._endpoint.fan, ['fan_mode'])
new_value = result.get('fan_mode', None)
self._state = VALUE_TO_SPEED.get(new_value, STATE_UNKNOWN)
self._state = VALUE_TO_SPEED.get(new_value, None)
@property
def should_poll(self) -> bool:

View File

@@ -4,7 +4,7 @@ Support for RSS/Atom feeds.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/feedreader/
"""
from datetime import datetime
from datetime import datetime, timedelta
from logging import getLogger
from os.path import exists
from threading import Lock
@@ -12,8 +12,8 @@ import pickle
import voluptuous as vol
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.event import track_utc_time_change
from homeassistant.const import EVENT_HOMEASSISTANT_START, CONF_SCAN_INTERVAL
from homeassistant.helpers.event import track_time_interval
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['feedparser==5.2.1']
@@ -21,16 +21,22 @@ REQUIREMENTS = ['feedparser==5.2.1']
_LOGGER = getLogger(__name__)
CONF_URLS = 'urls'
CONF_MAX_ENTRIES = 'max_entries'
DEFAULT_MAX_ENTRIES = 20
DEFAULT_SCAN_INTERVAL = timedelta(hours=1)
DOMAIN = 'feedreader'
EVENT_FEEDREADER = 'feedreader'
MAX_ENTRIES = 20
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
vol.Required(CONF_URLS): vol.All(cv.ensure_list, [cv.url]),
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
cv.time_period,
vol.Optional(CONF_MAX_ENTRIES, default=DEFAULT_MAX_ENTRIES):
cv.positive_int
}
}, extra=vol.ALLOW_EXTRA)
@@ -38,33 +44,50 @@ CONFIG_SCHEMA = vol.Schema({
def setup(hass, config):
"""Set up the Feedreader component."""
urls = config.get(DOMAIN)[CONF_URLS]
scan_interval = config.get(DOMAIN).get(CONF_SCAN_INTERVAL)
max_entries = config.get(DOMAIN).get(CONF_MAX_ENTRIES)
data_file = hass.config.path("{}.pickle".format(DOMAIN))
storage = StoredData(data_file)
feeds = [FeedManager(url, hass, storage) for url in urls]
feeds = [FeedManager(url, scan_interval, max_entries, hass, storage) for
url in urls]
return len(feeds) > 0
class FeedManager(object):
"""Abstraction over Feedparser module."""
def __init__(self, url, hass, storage):
"""Initialize the FeedManager object, poll every hour."""
def __init__(self, url, scan_interval, max_entries, hass, storage):
"""Initialize the FeedManager object, poll as per scan interval."""
self._url = url
self._scan_interval = scan_interval
self._max_entries = max_entries
self._feed = None
self._hass = hass
self._firstrun = True
self._storage = storage
self._last_entry_timestamp = None
self._last_update_successful = False
self._has_published_parsed = False
self._event_type = EVENT_FEEDREADER
self._feed_id = url
hass.bus.listen_once(
EVENT_HOMEASSISTANT_START, lambda _: self._update())
track_utc_time_change(
hass, lambda now: self._update(), minute=0, second=0)
self._init_regular_updates(hass)
def _log_no_entries(self):
"""Send no entries log at debug level."""
_LOGGER.debug("No new entries to be published in feed %s", self._url)
def _init_regular_updates(self, hass):
"""Schedule regular updates at the top of the clock."""
track_time_interval(hass, lambda now: self._update(),
self._scan_interval)
@property
def last_update_successful(self):
"""Return True if the last feed update was successful."""
return self._last_update_successful
def _update(self):
"""Update the feed and publish new entries to the event bus."""
import feedparser
@@ -76,26 +99,39 @@ class FeedManager(object):
else self._feed.get('modified'))
if not self._feed:
_LOGGER.error("Error fetching feed data from %s", self._url)
self._last_update_successful = False
else:
# The 'bozo' flag really only indicates that there was an issue
# during the initial parsing of the XML, but it doesn't indicate
# whether this is an unrecoverable error. In this case the
# feedparser lib is trying a less strict parsing approach.
# If an error is detected here, log error message but continue
# processing the feed entries if present.
if self._feed.bozo != 0:
_LOGGER.error("Error parsing feed %s", self._url)
_LOGGER.error("Error parsing feed %s: %s", self._url,
self._feed.bozo_exception)
# Using etag and modified, if there's no new data available,
# the entries list will be empty
elif self._feed.entries:
if self._feed.entries:
_LOGGER.debug("%s entri(es) available in feed %s",
len(self._feed.entries), self._url)
if len(self._feed.entries) > MAX_ENTRIES:
_LOGGER.debug("Processing only the first %s entries "
"in feed %s", MAX_ENTRIES, self._url)
self._feed.entries = self._feed.entries[0:MAX_ENTRIES]
self._filter_entries()
self._publish_new_entries()
if self._has_published_parsed:
self._storage.put_timestamp(
self._url, self._last_entry_timestamp)
self._feed_id, self._last_entry_timestamp)
else:
self._log_no_entries()
self._last_update_successful = True
_LOGGER.info("Fetch from feed %s completed", self._url)
def _filter_entries(self):
"""Filter the entries provided and return the ones to keep."""
if len(self._feed.entries) > self._max_entries:
_LOGGER.debug("Processing only the first %s entries "
"in feed %s", self._max_entries, self._url)
self._feed.entries = self._feed.entries[0:self._max_entries]
def _update_and_fire_entry(self, entry):
"""Update last_entry_timestamp and fire entry."""
# We are lucky, `published_parsed` data available, let's make use of
@@ -109,12 +145,12 @@ class FeedManager(object):
_LOGGER.debug("No published_parsed info available for entry %s",
entry.title)
entry.update({'feed_url': self._url})
self._hass.bus.fire(EVENT_FEEDREADER, entry)
self._hass.bus.fire(self._event_type, entry)
def _publish_new_entries(self):
"""Publish new entries to the event bus."""
new_entries = False
self._last_entry_timestamp = self._storage.get_timestamp(self._url)
self._last_entry_timestamp = self._storage.get_timestamp(self._feed_id)
if self._last_entry_timestamp:
self._firstrun = False
else:
@@ -157,18 +193,18 @@ class StoredData(object):
_LOGGER.error("Error loading data from pickled file %s",
self._data_file)
def get_timestamp(self, url):
"""Return stored timestamp for given url."""
def get_timestamp(self, feed_id):
"""Return stored timestamp for given feed id (usually the url)."""
self._fetch_data()
return self._data.get(url)
return self._data.get(feed_id)
def put_timestamp(self, url, timestamp):
"""Update timestamp for given URL."""
def put_timestamp(self, feed_id, timestamp):
"""Update timestamp for given feed id (usually the url)."""
self._fetch_data()
with self._lock, open(self._data_file, 'wb') as myfile:
self._data.update({url: timestamp})
self._data.update({feed_id: timestamp})
_LOGGER.debug("Overwriting feed %s timestamp in storage file %s",
url, self._data_file)
feed_id, self._data_file)
try:
pickle.dump(self._data, myfile)
except: # noqa: E722 # pylint: disable=bare-except

View File

@@ -43,7 +43,7 @@ def setup(hass, config):
def create_event_handler(patterns, hass):
""""Return the Watchdog EventHandler object."""
"""Return the Watchdog EventHandler object."""
from watchdog.events import PatternMatchingEventHandler
class EventHandler(PatternMatchingEventHandler):

View File

@@ -25,7 +25,7 @@ from homeassistant.core import callback
from homeassistant.helpers.translation import async_get_translations
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20180509.0']
REQUIREMENTS = ['home-assistant-frontend==20180608.0b0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
@@ -147,21 +147,6 @@ class AbstractPanel:
'get', '/{}/{{extra:.+}}'.format(self.frontend_url_path),
index_view.get)
def to_response(self, hass, request):
"""Panel as dictionary."""
result = {
'component_name': self.component_name,
'icon': self.sidebar_icon,
'title': self.sidebar_title,
'url_path': self.frontend_url_path,
'config': self.config,
}
if _is_latest(hass.data[DATA_JS_VERSION], request):
result['url'] = self.webcomponent_url_latest
else:
result['url'] = self.webcomponent_url_es5
return result
class BuiltInPanel(AbstractPanel):
"""Panel that is part of hass_frontend."""
@@ -175,30 +160,15 @@ class BuiltInPanel(AbstractPanel):
self.frontend_url_path = frontend_url_path or component_name
self.config = config
@asyncio.coroutine
def async_finalize(self, hass, frontend_repository_path):
"""Finalize this panel for usage.
If frontend_repository_path is set, will be prepended to path of
built-in components.
"""
if frontend_repository_path is None:
import hass_frontend
import hass_frontend_es5
self.webcomponent_url_latest = \
'/frontend_latest/panels/ha-panel-{}-{}.html'.format(
self.component_name,
hass_frontend.FINGERPRINTS[self.component_name])
self.webcomponent_url_es5 = \
'/frontend_es5/panels/ha-panel-{}-{}.html'.format(
self.component_name,
hass_frontend_es5.FINGERPRINTS[self.component_name])
else:
# Dev mode
self.webcomponent_url_es5 = self.webcomponent_url_latest = \
'/home-assistant-polymer/panels/{}/ha-panel-{}.html'.format(
self.component_name, self.component_name)
def to_response(self, hass, request):
"""Panel as dictionary."""
return {
'component_name': self.component_name,
'icon': self.sidebar_icon,
'title': self.sidebar_title,
'config': self.config,
'url_path': self.frontend_url_path,
}
class ExternalPanel(AbstractPanel):
@@ -244,6 +214,21 @@ class ExternalPanel(AbstractPanel):
frontend_repository_path is None)
self.REGISTERED_COMPONENTS.add(self.component_name)
def to_response(self, hass, request):
"""Panel as dictionary."""
result = {
'component_name': self.component_name,
'icon': self.sidebar_icon,
'title': self.sidebar_title,
'url_path': self.frontend_url_path,
'config': self.config,
}
if _is_latest(hass.data[DATA_JS_VERSION], request):
result['url'] = self.webcomponent_url_latest
else:
result['url'] = self.webcomponent_url_es5
return result
@bind_hass
@asyncio.coroutine
@@ -296,6 +281,15 @@ def add_manifest_json_key(key, val):
@asyncio.coroutine
def async_setup(hass, config):
"""Set up the serving of the frontend."""
if list(hass.auth.async_auth_providers):
client = yield from hass.auth.async_create_client(
'Home Assistant Frontend',
redirect_uris=['/'],
no_secret=True,
)
else:
client = None
hass.components.websocket_api.async_register_command(
WS_TYPE_GET_PANELS, websocket_handle_get_panels, SCHEMA_GET_PANELS)
hass.http.register_view(ManifestJSONView)
@@ -307,59 +301,40 @@ def async_setup(hass, config):
hass.data[DATA_JS_VERSION] = js_version = conf.get(CONF_JS_VERSION)
if is_dev:
for subpath in ["src", "build-translations", "build-temp", "build",
"hass_frontend", "bower_components", "panels",
"hassio"]:
hass.http.register_static_path(
"/home-assistant-polymer/{}".format(subpath),
os.path.join(repo_path, subpath),
False)
hass.http.register_static_path(
"/static/translations",
os.path.join(repo_path, "build-translations/output"), False)
sw_path_es5 = os.path.join(repo_path, "build-es5/service_worker.js")
sw_path_latest = os.path.join(repo_path, "build/service_worker.js")
static_path = os.path.join(repo_path, 'hass_frontend')
frontend_es5_path = os.path.join(repo_path, 'build-es5')
frontend_latest_path = os.path.join(repo_path, 'build')
hass_frontend_path = os.path.join(repo_path, 'hass_frontend')
hass_frontend_es5_path = os.path.join(repo_path, 'hass_frontend_es5')
else:
import hass_frontend
import hass_frontend_es5
sw_path_es5 = os.path.join(hass_frontend_es5.where(),
"service_worker.js")
sw_path_latest = os.path.join(hass_frontend.where(),
"service_worker.js")
# /static points to dir with files that are JS-type agnostic.
# ES5 files are served from /frontend_es5.
# ES6 files are served from /frontend_latest.
static_path = hass_frontend.where()
frontend_es5_path = hass_frontend_es5.where()
frontend_latest_path = static_path
hass_frontend_path = hass_frontend.where()
hass_frontend_es5_path = hass_frontend_es5.where()
hass.http.register_static_path(
"/service_worker_es5.js", sw_path_es5, False)
"/service_worker_es5.js",
os.path.join(hass_frontend_es5_path, "service_worker.js"), False)
hass.http.register_static_path(
"/service_worker.js", sw_path_latest, False)
"/service_worker.js",
os.path.join(hass_frontend_path, "service_worker.js"), False)
hass.http.register_static_path(
"/robots.txt", os.path.join(static_path, "robots.txt"), not is_dev)
hass.http.register_static_path("/static", static_path, not is_dev)
"/robots.txt",
os.path.join(hass_frontend_path, "robots.txt"), False)
hass.http.register_static_path("/static", hass_frontend_path, not is_dev)
hass.http.register_static_path(
"/frontend_latest", frontend_latest_path, not is_dev)
"/frontend_latest", hass_frontend_path, not is_dev)
hass.http.register_static_path(
"/frontend_es5", frontend_es5_path, not is_dev)
"/frontend_es5", hass_frontend_es5_path, not is_dev)
local = hass.config.path('www')
if os.path.isdir(local):
hass.http.register_static_path("/local", local, not is_dev)
index_view = IndexView(repo_path, js_version)
index_view = IndexView(repo_path, js_version, client)
hass.http.register_view(index_view)
@asyncio.coroutine
def finalize_panel(panel):
async def finalize_panel(panel):
"""Finalize setup of a panel."""
yield from panel.async_finalize(hass, repo_path)
if hasattr(panel, 'async_finalize'):
await panel.async_finalize(hass, repo_path)
panel.async_register_index_routes(hass.http.app.router, index_view)
yield from asyncio.wait([
@@ -451,10 +426,11 @@ class IndexView(HomeAssistantView):
requires_auth = False
extra_urls = ['/states', '/states/{extra}']
def __init__(self, repo_path, js_option):
def __init__(self, repo_path, js_option, client):
"""Initialize the frontend view."""
self.repo_path = repo_path
self.js_option = js_option
self.client = client
self._template_cache = {}
def get_template(self, latest):
@@ -508,7 +484,7 @@ class IndexView(HomeAssistantView):
extra_key = DATA_EXTRA_HTML_URL if latest else DATA_EXTRA_HTML_URL_ES5
resp = template.render(
template_params = dict(
no_auth=no_auth,
panel_url=panel_url,
panels=hass.data[DATA_PANELS],
@@ -516,7 +492,11 @@ class IndexView(HomeAssistantView):
extra_urls=hass.data[extra_key],
)
return web.Response(text=resp, content_type='text/html')
if self.client is not None:
template_params['client_id'] = self.client.id
return web.Response(text=template.render(**template_params),
content_type='text/html')
class ManifestJSONView(HomeAssistantView):

View File

@@ -14,7 +14,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_LOCKED,
STATE_UNLOCKED, STATE_OK, STATE_PROBLEM, STATE_UNKNOWN,
ATTR_ASSUMED_STATE, SERVICE_RELOAD)
ATTR_ASSUMED_STATE, SERVICE_RELOAD, ATTR_NAME, ATTR_ICON)
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import Entity, async_generate_entity_id
@@ -35,8 +35,6 @@ ATTR_ADD_ENTITIES = 'add_entities'
ATTR_AUTO = 'auto'
ATTR_CONTROL = 'control'
ATTR_ENTITIES = 'entities'
ATTR_ICON = 'icon'
ATTR_NAME = 'name'
ATTR_OBJECT_ID = 'object_id'
ATTR_ORDER = 'order'
ATTR_VIEW = 'view'

View File

@@ -13,12 +13,13 @@ import voluptuous as vol
from homeassistant.components import SERVICE_CHECK_CONFIG
from homeassistant.const import (
SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP)
ATTR_NAME, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP)
from homeassistant.core import DOMAIN as HASS_DOMAIN
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
from homeassistant.util.dt import utcnow
from .handler import HassIO
from .http import HassIOView
@@ -27,6 +28,15 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'hassio'
DEPENDENCIES = ['http']
CONF_FRONTEND_REPO = 'development_repo'
CONFIG_SCHEMA = vol.Schema({
vol.Optional(DOMAIN): vol.Schema({
vol.Optional(CONF_FRONTEND_REPO): cv.isdir,
}),
}, extra=vol.ALLOW_EXTRA)
DATA_HOMEASSISTANT_VERSION = 'hassio_hass_version'
HASSIO_UPDATE_INTERVAL = timedelta(minutes=55)
@@ -47,7 +57,6 @@ ATTR_SNAPSHOT = 'snapshot'
ATTR_ADDONS = 'addons'
ATTR_FOLDERS = 'folders'
ATTR_HOMEASSISTANT = 'homeassistant'
ATTR_NAME = 'name'
ATTR_PASSWORD = 'password'
SCHEMA_NO_DATA = vol.Schema({})
@@ -142,7 +151,13 @@ def async_setup(hass, config):
try:
host = os.environ['HASSIO']
except KeyError:
_LOGGER.error("No Hass.io supervisor detect")
_LOGGER.error("Missing HASSIO environment variable.")
return False
try:
os.environ['HASSIO_TOKEN']
except KeyError:
_LOGGER.error("Missing HASSIO_TOKEN environment variable.")
return False
websession = hass.helpers.aiohttp_client.async_get_clientsession()
@@ -152,11 +167,18 @@ def async_setup(hass, config):
_LOGGER.error("Not connected with Hass.io")
return False
# This overrides the normal API call that would be forwarded
development_repo = config.get(DOMAIN, {}).get(CONF_FRONTEND_REPO)
if development_repo is not None:
hass.http.register_static_path(
'/api/hassio/app-es5',
os.path.join(development_repo, 'hassio/build-es5'), False)
hass.http.register_view(HassIOView(host, websession))
if 'frontend' in hass.config.components:
yield from hass.components.frontend.async_register_built_in_panel(
'hassio', 'Hass.io', 'mdi:home-assistant')
'hassio', 'Hass.io', 'hass:home-assistant')
if 'http' in config:
yield from hassio.update_hass_api(config['http'])

View File

@@ -33,7 +33,7 @@ def _api_bool(funct):
def _api_data(funct):
"""Return a api data."""
"""Return data of an api."""
@asyncio.coroutine
def _wrapper(*argv, **kwargs):
"""Wrap function."""

View File

@@ -36,7 +36,7 @@ NO_TIMEOUT = {
}
NO_AUTH = {
re.compile(r'^app-(es5|latest)/(index|hassio-app).html$'),
re.compile(r'^app-(es5|latest)/.+$'),
re.compile(r'^addons/[^/]*/logo$')
}

View File

@@ -274,7 +274,7 @@ async def async_setup(hass, config):
hass.http.register_view(HistoryPeriodView(filters, use_include_order))
await hass.components.frontend.async_register_built_in_panel(
'history', 'history', 'mdi:poll-box')
'history', 'history', 'hass:poll-box')
return True

View File

@@ -9,28 +9,28 @@ from zlib import adler32
import voluptuous as vol
from homeassistant.components.cover import (
SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION)
import homeassistant.components.cover as cover
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
ATTR_DEVICE_CLASS, CONF_IP_ADDRESS, CONF_PORT, TEMP_CELSIUS,
TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE)
ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, CONF_TYPE, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.util import get_local_ip
from homeassistant.util.decorator import Registry
from .const import (
DOMAIN, HOMEKIT_FILE, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FILTER,
DEFAULT_PORT, DEFAULT_AUTO_START, SERVICE_HOMEKIT_START,
DEVICE_CLASS_CO2, DEVICE_CLASS_PM25)
CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, CONF_FILTER,
DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25,
DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH)
from .util import (
validate_entity_config, show_setup_message)
show_setup_message, validate_entity_config, validate_media_player_features)
TYPES = Registry()
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['HAP-python==2.0.0']
REQUIREMENTS = ['HAP-python==2.2.2']
# #### Driver Status ####
STATUS_READY = 0
@@ -38,6 +38,8 @@ STATUS_RUNNING = 1
STATUS_STOPPED = 2
STATUS_WAIT = 3
SWITCH_TYPES = {TYPE_OUTLET: 'Outlet',
TYPE_SWITCH: 'Switch'}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All({
@@ -84,7 +86,7 @@ async def async_setup(hass, config):
return True
def get_accessory(hass, state, aid, config):
def get_accessory(hass, driver, state, aid, config):
"""Take state and return an accessory object if supported."""
if not aid:
_LOGGER.warning('The entitiy "%s" is not supported, since it '
@@ -93,7 +95,7 @@ def get_accessory(hass, state, aid, config):
return None
a_type = None
config = config or {}
name = config.get(CONF_NAME, state.name)
if state.domain == 'alarm_control_panel':
a_type = 'SecuritySystem'
@@ -109,19 +111,28 @@ def get_accessory(hass, state, aid, config):
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
if device_class == 'garage' and \
features & (SUPPORT_OPEN | SUPPORT_CLOSE):
features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE):
a_type = 'GarageDoorOpener'
elif features & SUPPORT_SET_POSITION:
elif features & cover.SUPPORT_SET_POSITION:
a_type = 'WindowCovering'
elif features & (SUPPORT_OPEN | SUPPORT_CLOSE):
elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE):
a_type = 'WindowCoveringBasic'
elif state.domain == 'fan':
a_type = 'Fan'
elif state.domain == 'light':
a_type = 'Light'
elif state.domain == 'lock':
a_type = 'Lock'
elif state.domain == 'media_player':
feature_list = config.get(CONF_FEATURE_LIST)
if feature_list and \
validate_media_player_features(state, feature_list):
a_type = 'MediaPlayer'
elif state.domain == 'sensor':
unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
@@ -140,14 +151,18 @@ def get_accessory(hass, state, aid, config):
elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ('lm', 'lx'):
a_type = 'LightSensor'
elif state.domain in ('switch', 'remote', 'input_boolean', 'script'):
elif state.domain == 'switch':
switch_type = config.get(CONF_TYPE, TYPE_SWITCH)
a_type = SWITCH_TYPES[switch_type]
elif state.domain in ('automation', 'input_boolean', 'remote', 'script'):
a_type = 'Switch'
if a_type is None:
return None
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, a_type)
return TYPES[a_type](hass, state.name, state.entity_id, aid, config=config)
return TYPES[a_type](hass, driver, name, state.entity_id, aid, config)
def generate_aid(entity_id):
@@ -182,8 +197,9 @@ class HomeKit():
ip_addr = self._ip_address or get_local_ip()
path = self.hass.config.path(HOMEKIT_FILE)
self.bridge = HomeBridge(self.hass)
self.driver = HomeDriver(self.bridge, self._port, ip_addr, path)
self.driver = HomeDriver(self.hass, address=ip_addr,
port=self._port, persist_file=path)
self.bridge = HomeBridge(self.hass, self.driver)
def add_bridge_accessory(self, state):
"""Try adding accessory to bridge if configured beforehand."""
@@ -191,7 +207,7 @@ class HomeKit():
return
aid = generate_aid(state.entity_id)
conf = self._config.pop(state.entity_id, {})
acc = get_accessory(self.hass, state, aid, conf)
acc = get_accessory(self.hass, self.driver, state, aid, conf)
if acc is not None:
self.bridge.add_accessory(acc)
@@ -203,15 +219,16 @@ class HomeKit():
# pylint: disable=unused-variable
from . import ( # noqa F401
type_covers, type_lights, type_locks, type_security_systems,
type_sensors, type_switches, type_thermostats)
type_covers, type_fans, type_lights, type_locks,
type_media_players, type_security_systems, type_sensors,
type_switches, type_thermostats)
for state in self.hass.states.all():
self.add_bridge_accessory(state)
self.bridge.set_driver(self.driver)
self.driver.add_accessory(self.bridge)
if not self.bridge.paired:
show_setup_message(self.hass, self.bridge)
if not self.driver.state.paired:
show_setup_message(self.hass, self.driver.state.pincode)
_LOGGER.debug('Driver start')
self.hass.add_job(self.driver.start)

View File

@@ -1,6 +1,6 @@
"""Extend the basic Accessory and Bridge functions."""
from datetime import timedelta
from functools import wraps
from functools import partial, wraps
from inspect import getmodule
import logging
@@ -16,8 +16,8 @@ from homeassistant.helpers.event import (
from homeassistant.util import dt as dt_util
from .const import (
DEBOUNCE_TIMEOUT, BRIDGE_MODEL, BRIDGE_NAME,
BRIDGE_SERIAL_NUMBER, MANUFACTURER)
BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER,
DEBOUNCE_TIMEOUT, MANUFACTURER)
from .util import (
show_setup_message, dismiss_setup_message)
@@ -27,35 +27,25 @@ _LOGGER = logging.getLogger(__name__)
def debounce(func):
"""Decorator function. Debounce callbacks form HomeKit."""
@ha_callback
def call_later_listener(*args):
def call_later_listener(self, *args):
"""Callback listener called from call_later."""
# pylint: disable=unsubscriptable-object
nonlocal lastargs, remove_listener
hass = lastargs['hass']
hass.async_add_job(func, *lastargs['args'])
lastargs = remove_listener = None
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
self.hass.async_add_job(func, self, *debounce_params[1:])
@wraps(func)
def wrapper(*args):
"""Wrapper starts async timer.
The accessory must have 'self.hass' and 'self.entity_id' as attributes.
"""
# pylint: disable=not-callable
hass = args[0].hass
nonlocal lastargs, remove_listener
if remove_listener:
remove_listener()
lastargs = remove_listener = None
lastargs = {'hass': hass, 'args': [*args]}
def wrapper(self, *args):
"""Wrapper starts async timer."""
debounce_params = self.debounce.pop(func.__name__, None)
if debounce_params:
debounce_params[0]() # remove listener
remove_listener = track_point_in_utc_time(
hass, call_later_listener,
self.hass, partial(call_later_listener, self),
dt_util.utcnow() + timedelta(seconds=DEBOUNCE_TIMEOUT))
logger.debug('%s: Start %s timeout', args[0].entity_id,
self.debounce[func.__name__] = (remove_listener, *args)
logger.debug('%s: Start %s timeout', self.entity_id,
func.__name__.replace('set_', ''))
remove_listener = None
lastargs = None
name = getmodule(func).__name__
logger = logging.getLogger(name)
return wrapper
@@ -64,46 +54,53 @@ def debounce(func):
class HomeAccessory(Accessory):
"""Adapter class for Accessory."""
def __init__(self, hass, name, entity_id, aid, category=CATEGORY_OTHER):
def __init__(self, hass, driver, name, entity_id, aid, config,
category=CATEGORY_OTHER):
"""Initialize a Accessory object."""
super().__init__(name, aid=aid)
domain = split_entity_id(entity_id)[0].replace("_", " ").title()
super().__init__(driver, name, aid=aid)
model = split_entity_id(entity_id)[0].replace("_", " ").title()
self.set_info_service(
firmware_revision=__version__, manufacturer=MANUFACTURER,
model=domain, serial_number=entity_id)
model=model, serial_number=entity_id)
self.category = category
self.config = config
self.entity_id = entity_id
self.hass = hass
self.debounce = {}
def run(self):
"""Method called by accessory after driver is started."""
async def run(self):
"""Method called by accessory after driver is started.
Run inside the HAP-python event loop.
"""
state = self.hass.states.get(self.entity_id)
self.update_state_callback(new_state=state)
self.hass.add_job(self.update_state_callback, None, None, state)
async_track_state_change(
self.hass, self.entity_id, self.update_state_callback)
@ha_callback
def update_state_callback(self, entity_id=None, old_state=None,
new_state=None):
"""Callback from state change listener."""
_LOGGER.debug('New_state: %s', new_state)
if new_state is None:
return
self.update_state(new_state)
self.hass.async_add_job(self.update_state, new_state)
def update_state(self, new_state):
"""Method called on state change to update HomeKit value.
Overridden by accessory types.
"""
pass
raise NotImplementedError()
class HomeBridge(Bridge):
"""Adapter class for Bridge."""
def __init__(self, hass, name=BRIDGE_NAME):
def __init__(self, hass, driver, name=BRIDGE_NAME):
"""Initialize a Bridge object."""
super().__init__(name)
super().__init__(driver, name)
self.set_info_service(
firmware_revision=__version__, manufacturer=MANUFACTURER,
model=BRIDGE_MODEL, serial_number=BRIDGE_SERIAL_NUMBER)
@@ -113,20 +110,23 @@ class HomeBridge(Bridge):
"""Prevent print of pyhap setup message to terminal."""
pass
def add_paired_client(self, client_uuid, client_public):
"""Override super function to dismiss setup message if paired."""
super().add_paired_client(client_uuid, client_public)
dismiss_setup_message(self.hass)
def remove_paired_client(self, client_uuid):
"""Override super function to show setup message if unpaired."""
super().remove_paired_client(client_uuid)
show_setup_message(self.hass, self)
class HomeDriver(AccessoryDriver):
"""Adapter class for AccessoryDriver."""
def __init__(self, *args, **kwargs):
def __init__(self, hass, **kwargs):
"""Initialize a AccessoryDriver object."""
super().__init__(*args, **kwargs)
super().__init__(**kwargs)
self.hass = hass
def pair(self, client_uuid, client_public):
"""Override super function to dismiss setup message if paired."""
success = super().pair(client_uuid, client_public)
if success:
dismiss_setup_message(self.hass)
return success
def unpair(self, client_uuid):
"""Override super function to show setup message if unpaired."""
super().unpair(client_uuid)
show_setup_message(self.hass, self.state.pincode)

View File

@@ -1,54 +1,68 @@
"""Constants used be the HomeKit component."""
# #### MISC ####
# #### Misc ####
DEBOUNCE_TIMEOUT = 0.5
DOMAIN = 'homekit'
HOMEKIT_FILE = '.homekit.state'
HOMEKIT_NOTIFY_ID = 4663548
# #### CONFIG ####
# #### Config ####
CONF_AUTO_START = 'auto_start'
CONF_ENTITY_CONFIG = 'entity_config'
CONF_FEATURE = 'feature'
CONF_FEATURE_LIST = 'feature_list'
CONF_FILTER = 'filter'
# #### CONFIG DEFAULTS ####
# #### Config Defaults ####
DEFAULT_AUTO_START = True
DEFAULT_PORT = 51827
# #### HOMEKIT COMPONENT SERVICES ####
# #### Features ####
FEATURE_ON_OFF = 'on_off'
FEATURE_PLAY_PAUSE = 'play_pause'
FEATURE_PLAY_STOP = 'play_stop'
FEATURE_TOGGLE_MUTE = 'toggle_mute'
# #### HomeKit Component Services ####
SERVICE_HOMEKIT_START = 'start'
# #### STRING CONSTANTS ####
# #### String Constants ####
BRIDGE_MODEL = 'Bridge'
BRIDGE_NAME = 'Home Assistant Bridge'
BRIDGE_SERIAL_NUMBER = 'homekit.bridge'
MANUFACTURER = 'Home Assistant'
# #### Switch Types ####
TYPE_OUTLET = 'outlet'
TYPE_SWITCH = 'switch'
# #### Services ####
SERV_ACCESSORY_INFO = 'AccessoryInformation'
SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
SERV_CARBON_DIOXIDE_SENSOR = 'CarbonDioxideSensor'
SERV_CARBON_MONOXIDE_SENSOR = 'CarbonMonoxideSensor'
SERV_CONTACT_SENSOR = 'ContactSensor'
SERV_FANV2 = 'Fanv2'
SERV_GARAGE_DOOR_OPENER = 'GarageDoorOpener'
SERV_HUMIDITY_SENSOR = 'HumiditySensor' # CurrentRelativeHumidity
SERV_HUMIDITY_SENSOR = 'HumiditySensor'
SERV_LEAK_SENSOR = 'LeakSensor'
SERV_LIGHT_SENSOR = 'LightSensor'
SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name
SERV_LIGHTBULB = 'Lightbulb'
SERV_LOCK = 'LockMechanism'
SERV_MOTION_SENSOR = 'MotionSensor'
SERV_OCCUPANCY_SENSOR = 'OccupancySensor'
SERV_OUTLET = 'Outlet'
SERV_SECURITY_SYSTEM = 'SecuritySystem'
SERV_SMOKE_SENSOR = 'SmokeSensor'
SERV_SWITCH = 'Switch'
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
SERV_THERMOSTAT = 'Thermostat'
SERV_WINDOW_COVERING = 'WindowCovering'
# CurrentPosition, TargetPosition, PositionState
# #### Characteristics ####
CHAR_ACTIVE = 'Active'
CHAR_AIR_PARTICULATE_DENSITY = 'AirParticulateDensity'
CHAR_AIR_QUALITY = 'AirQuality'
CHAR_BRIGHTNESS = 'Brightness' # Int | [0, 100]
CHAR_BRIGHTNESS = 'Brightness'
CHAR_CARBON_DIOXIDE_DETECTED = 'CarbonDioxideDetected'
CHAR_CARBON_DIOXIDE_LEVEL = 'CarbonDioxideLevel'
CHAR_CARBON_DIOXIDE_PEAK_LEVEL = 'CarbonDioxidePeakLevel'
@@ -59,13 +73,13 @@ CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL = 'CurrentAmbientLightLevel'
CHAR_CURRENT_DOOR_STATE = 'CurrentDoorState'
CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState'
CHAR_CURRENT_POSITION = 'CurrentPosition' # Int | [0, 100]
CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' # percent
CHAR_CURRENT_POSITION = 'CurrentPosition'
CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity'
CHAR_CURRENT_SECURITY_STATE = 'SecuritySystemCurrentState'
CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
CHAR_FIRMWARE_REVISION = 'FirmwareRevision'
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
CHAR_HUE = 'Hue' # arcdegress | [0, 360]
CHAR_HUE = 'Hue'
CHAR_LEAK_DETECTED = 'LeakDetected'
CHAR_LOCK_CURRENT_STATE = 'LockCurrentState'
CHAR_LOCK_TARGET_STATE = 'LockTargetState'
@@ -75,33 +89,35 @@ CHAR_MODEL = 'Model'
CHAR_MOTION_DETECTED = 'MotionDetected'
CHAR_NAME = 'Name'
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
CHAR_ON = 'On' # boolean
CHAR_OUTLET_IN_USE = 'OutletInUse'
CHAR_ON = 'On'
CHAR_POSITION_STATE = 'PositionState'
CHAR_SATURATION = 'Saturation' # percent
CHAR_ROTATION_DIRECTION = 'RotationDirection'
CHAR_SATURATION = 'Saturation'
CHAR_SERIAL_NUMBER = 'SerialNumber'
CHAR_SMOKE_DETECTED = 'SmokeDetected'
CHAR_SWING_MODE = 'SwingMode'
CHAR_TARGET_DOOR_STATE = 'TargetDoorState'
CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState'
CHAR_TARGET_POSITION = 'TargetPosition' # Int | [0, 100]
CHAR_TARGET_POSITION = 'TargetPosition'
CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
CHAR_TARGET_TEMPERATURE = 'TargetTemperature'
CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits'
# #### Properties ####
PROP_MAX_VALUE = 'maxValue'
PROP_MIN_VALUE = 'minValue'
PROP_CELSIUS = {'minValue': -273, 'maxValue': 999}
# #### Device Class ####
# #### Device Classes ####
DEVICE_CLASS_CO2 = 'co2'
DEVICE_CLASS_DOOR = 'door'
DEVICE_CLASS_GARAGE_DOOR = 'garage_door'
DEVICE_CLASS_GAS = 'gas'
DEVICE_CLASS_HUMIDITY = 'humidity'
DEVICE_CLASS_LIGHT = 'light'
DEVICE_CLASS_MOISTURE = 'moisture'
DEVICE_CLASS_MOTION = 'motion'
DEVICE_CLASS_OCCUPANCY = 'occupancy'
DEVICE_CLASS_OPENING = 'opening'
DEVICE_CLASS_PM25 = 'pm25'
DEVICE_CLASS_SMOKE = 'smoke'
DEVICE_CLASS_TEMPERATURE = 'temperature'
DEVICE_CLASS_WINDOW = 'window'

View File

@@ -1,21 +1,21 @@
"""Class to hold all cover accessories."""
import logging
from pyhap.const import CATEGORY_WINDOW_COVERING, CATEGORY_GARAGE_DOOR_OPENER
from pyhap.const import CATEGORY_GARAGE_DOOR_OPENER, CATEGORY_WINDOW_COVERING
from homeassistant.components.cover import (
ATTR_CURRENT_POSITION, ATTR_POSITION, DOMAIN, SUPPORT_STOP)
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_SET_COVER_POSITION, STATE_OPEN, STATE_CLOSED,
SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_STOP_COVER,
ATTR_SUPPORTED_FEATURES)
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER, SERVICE_SET_COVER_POSITION, SERVICE_STOP_COVER,
STATE_CLOSED, STATE_OPEN)
from . import TYPES
from .accessories import HomeAccessory, debounce
from .accessories import debounce, HomeAccessory
from .const import (
SERV_WINDOW_COVERING, CHAR_CURRENT_POSITION,
CHAR_TARGET_POSITION, CHAR_POSITION_STATE,
SERV_GARAGE_DOOR_OPENER, CHAR_CURRENT_DOOR_STATE, CHAR_TARGET_DOOR_STATE)
CHAR_CURRENT_DOOR_STATE, CHAR_CURRENT_POSITION, CHAR_POSITION_STATE,
CHAR_TARGET_DOOR_STATE, CHAR_TARGET_POSITION,
SERV_GARAGE_DOOR_OPENER, SERV_WINDOW_COVERING)
_LOGGER = logging.getLogger(__name__)
@@ -28,7 +28,7 @@ class GarageDoorOpener(HomeAccessory):
and support no more than open, close, and stop.
"""
def __init__(self, *args, config):
def __init__(self, *args):
"""Initialize a GarageDoorOpener accessory object."""
super().__init__(*args, category=CATEGORY_GARAGE_DOOR_OPENER)
self.flag_target_state = False
@@ -44,12 +44,13 @@ class GarageDoorOpener(HomeAccessory):
_LOGGER.debug('%s: Set state to %d', self.entity_id, value)
self.flag_target_state = True
params = {ATTR_ENTITY_ID: self.entity_id}
if value == 0:
self.char_current_state.set_value(3)
self.hass.components.cover.open_cover(self.entity_id)
self.hass.services.call(DOMAIN, SERVICE_OPEN_COVER, params)
elif value == 1:
self.char_current_state.set_value(2)
self.hass.components.cover.close_cover(self.entity_id)
self.hass.services.call(DOMAIN, SERVICE_CLOSE_COVER, params)
def update_state(self, new_state):
"""Update cover state after state changed."""
@@ -69,7 +70,7 @@ class WindowCovering(HomeAccessory):
The cover entity must support: set_cover_position.
"""
def __init__(self, *args, config):
def __init__(self, *args):
"""Initialize a WindowCovering accessory object."""
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
self.homekit_target = None
@@ -108,7 +109,7 @@ class WindowCoveringBasic(HomeAccessory):
stop_cover (optional).
"""
def __init__(self, *args, config):
def __init__(self, *args):
"""Initialize a WindowCovering accessory object."""
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
features = self.hass.states.get(self.entity_id) \
@@ -141,8 +142,8 @@ class WindowCoveringBasic(HomeAccessory):
else:
service, position = (SERVICE_CLOSE_COVER, 0)
self.hass.services.call(DOMAIN, service,
{ATTR_ENTITY_ID: self.entity_id})
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call(DOMAIN, service, params)
# Snap the current/target position to the expected final position.
self.char_current_position.set_value(position)

View File

@@ -0,0 +1,115 @@
"""Class to hold all light accessories."""
import logging
from pyhap.const import CATEGORY_FAN
from homeassistant.components.fan import (
ATTR_DIRECTION, ATTR_OSCILLATING, DIRECTION_FORWARD, DIRECTION_REVERSE,
DOMAIN, SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, SUPPORT_DIRECTION,
SUPPORT_OSCILLATE)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_OFF,
SERVICE_TURN_ON, STATE_OFF, STATE_ON)
from . import TYPES
from .accessories import HomeAccessory
from .const import (
CHAR_ACTIVE, CHAR_ROTATION_DIRECTION, CHAR_SWING_MODE, SERV_FANV2)
_LOGGER = logging.getLogger(__name__)
@TYPES.register('Fan')
class Fan(HomeAccessory):
"""Generate a Fan accessory for a fan entity.
Currently supports: state, speed, oscillate, direction.
"""
def __init__(self, *args):
"""Initialize a new Light accessory object."""
super().__init__(*args, category=CATEGORY_FAN)
self._flag = {CHAR_ACTIVE: False,
CHAR_ROTATION_DIRECTION: False,
CHAR_SWING_MODE: False}
self._state = 0
self.chars = []
features = self.hass.states.get(self.entity_id) \
.attributes.get(ATTR_SUPPORTED_FEATURES)
if features & SUPPORT_DIRECTION:
self.chars.append(CHAR_ROTATION_DIRECTION)
if features & SUPPORT_OSCILLATE:
self.chars.append(CHAR_SWING_MODE)
serv_fan = self.add_preload_service(SERV_FANV2, self.chars)
self.char_active = serv_fan.configure_char(
CHAR_ACTIVE, value=0, setter_callback=self.set_state)
if CHAR_ROTATION_DIRECTION in self.chars:
self.char_direction = serv_fan.configure_char(
CHAR_ROTATION_DIRECTION, value=0,
setter_callback=self.set_direction)
if CHAR_SWING_MODE in self.chars:
self.char_swing = serv_fan.configure_char(
CHAR_SWING_MODE, value=0, setter_callback=self.set_oscillating)
def set_state(self, value):
"""Set state if call came from HomeKit."""
if self._state == value:
return
_LOGGER.debug('%s: Set state to %d', self.entity_id, value)
self._flag[CHAR_ACTIVE] = True
service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call(DOMAIN, service, params)
def set_direction(self, value):
"""Set state if call came from HomeKit."""
_LOGGER.debug('%s: Set direction to %d', self.entity_id, value)
self._flag[CHAR_ROTATION_DIRECTION] = True
direction = DIRECTION_REVERSE if value == 1 else DIRECTION_FORWARD
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_DIRECTION: direction}
self.hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, params)
def set_oscillating(self, value):
"""Set state if call came from HomeKit."""
_LOGGER.debug('%s: Set oscillating to %d', self.entity_id, value)
self._flag[CHAR_SWING_MODE] = True
oscillating = True if value == 1 else False
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_OSCILLATING: oscillating}
self.hass.services.call(DOMAIN, SERVICE_OSCILLATE, params)
def update_state(self, new_state):
"""Update fan after state change."""
# Handle State
state = new_state.state
if state in (STATE_ON, STATE_OFF):
self._state = 1 if state == STATE_ON else 0
if not self._flag[CHAR_ACTIVE] and \
self.char_active.value != self._state:
self.char_active.set_value(self._state)
self._flag[CHAR_ACTIVE] = False
# Handle Direction
if CHAR_ROTATION_DIRECTION in self.chars:
direction = new_state.attributes.get(ATTR_DIRECTION)
if not self._flag[CHAR_ROTATION_DIRECTION] and \
direction in (DIRECTION_FORWARD, DIRECTION_REVERSE):
hk_direction = 1 if direction == DIRECTION_REVERSE else 0
if self.char_direction.value != hk_direction:
self.char_direction.set_value(hk_direction)
self._flag[CHAR_ROTATION_DIRECTION] = False
# Handle Oscillating
if CHAR_SWING_MODE in self.chars:
oscillating = new_state.attributes.get(ATTR_OSCILLATING)
if not self._flag[CHAR_SWING_MODE] and \
oscillating in (True, False):
hk_oscillating = 1 if oscillating else 0
if self.char_swing.value != hk_oscillating:
self.char_swing.set_value(hk_oscillating)
self._flag[CHAR_SWING_MODE] = False

View File

@@ -4,15 +4,18 @@ import logging
from pyhap.const import CATEGORY_LIGHTBULB
from homeassistant.components.light import (
ATTR_HS_COLOR, ATTR_COLOR_TEMP, ATTR_BRIGHTNESS, ATTR_MIN_MIREDS,
ATTR_MAX_MIREDS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_BRIGHTNESS)
from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_ON, STATE_OFF
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_TEMP, ATTR_HS_COLOR,
ATTR_MAX_MIREDS, ATTR_MIN_MIREDS, DOMAIN,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP)
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, SERVICE_TURN_ON,
SERVICE_TURN_OFF, STATE_OFF, STATE_ON)
from . import TYPES
from .accessories import HomeAccessory, debounce
from .accessories import debounce, HomeAccessory
from .const import (
SERV_LIGHTBULB, CHAR_COLOR_TEMPERATURE,
CHAR_BRIGHTNESS, CHAR_HUE, CHAR_ON, CHAR_SATURATION)
CHAR_BRIGHTNESS, CHAR_COLOR_TEMPERATURE, CHAR_HUE, CHAR_ON,
CHAR_SATURATION, SERV_LIGHTBULB, PROP_MAX_VALUE, PROP_MIN_VALUE)
_LOGGER = logging.getLogger(__name__)
@@ -26,7 +29,7 @@ class Light(HomeAccessory):
Currently supports: state, brightness, color temperature, rgb_color.
"""
def __init__(self, *args, config):
def __init__(self, *args):
"""Initialize a new Light accessory object."""
super().__init__(*args, category=CATEGORY_LIGHTBULB)
self._flag = {CHAR_ON: False, CHAR_BRIGHTNESS: False,
@@ -61,7 +64,8 @@ class Light(HomeAccessory):
.attributes.get(ATTR_MAX_MIREDS, 500)
self.char_color_temperature = serv_light.configure_char(
CHAR_COLOR_TEMPERATURE, value=min_mireds,
properties={'minValue': min_mireds, 'maxValue': max_mireds},
properties={PROP_MIN_VALUE: min_mireds,
PROP_MAX_VALUE: max_mireds},
setter_callback=self.set_color_temperature)
if CHAR_HUE in self.chars:
self.char_hue = serv_light.configure_char(
@@ -77,28 +81,27 @@ class Light(HomeAccessory):
_LOGGER.debug('%s: Set state to %d', self.entity_id, value)
self._flag[CHAR_ON] = True
if value == 1:
self.hass.components.light.turn_on(self.entity_id)
elif value == 0:
self.hass.components.light.turn_off(self.entity_id)
params = {ATTR_ENTITY_ID: self.entity_id}
service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF
self.hass.services.call(DOMAIN, service, params)
@debounce
def set_brightness(self, value):
"""Set brightness if call came from HomeKit."""
_LOGGER.debug('%s: Set brightness to %d', self.entity_id, value)
self._flag[CHAR_BRIGHTNESS] = True
if value != 0:
self.hass.components.light.turn_on(
self.entity_id, brightness_pct=value)
else:
self.hass.components.light.turn_off(self.entity_id)
if value == 0:
self.set_state(0) # Turn off light
return
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_BRIGHTNESS_PCT: value}
self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params)
def set_color_temperature(self, value):
"""Set color temperature if call came from HomeKit."""
_LOGGER.debug('%s: Set color temp to %s', self.entity_id, value)
self._flag[CHAR_COLOR_TEMPERATURE] = True
self.hass.components.light.turn_on(self.entity_id, color_temp=value)
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_COLOR_TEMP: value}
self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params)
def set_saturation(self, value):
"""Set saturation if call came from HomeKit."""
@@ -116,15 +119,14 @@ class Light(HomeAccessory):
def set_color(self):
"""Set color if call came from HomeKit."""
# Handle Color
if self._features & SUPPORT_COLOR and self._flag[CHAR_HUE] and \
self._flag[CHAR_SATURATION]:
color = (self._hue, self._saturation)
_LOGGER.debug('%s: Set hs_color to %s', self.entity_id, color)
self._flag.update({
CHAR_HUE: False, CHAR_SATURATION: False, RGB_COLOR: True})
self.hass.components.light.turn_on(
self.entity_id, hs_color=color)
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HS_COLOR: color}
self.hass.services.call(DOMAIN, SERVICE_TURN_ON, params)
def update_state(self, new_state):
"""Update light after state change."""

View File

@@ -4,12 +4,12 @@ import logging
from pyhap.const import CATEGORY_DOOR_LOCK
from homeassistant.components.lock import (
ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN)
ATTR_ENTITY_ID, DOMAIN, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN)
from homeassistant.const import ATTR_CODE
from . import TYPES
from .accessories import HomeAccessory
from .const import (
SERV_LOCK, CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE)
from .const import CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE, SERV_LOCK
_LOGGER = logging.getLogger(__name__)
@@ -29,9 +29,10 @@ class Lock(HomeAccessory):
The lock entity must support: unlock and lock.
"""
def __init__(self, *args, config):
def __init__(self, *args):
"""Initialize a Lock accessory object."""
super().__init__(*args, category=CATEGORY_DOOR_LOCK)
self._code = self.config.get(ATTR_CODE)
self.flag_target_state = False
serv_lock_mechanism = self.add_preload_service(SERV_LOCK)
@@ -51,7 +52,9 @@ class Lock(HomeAccessory):
service = STATE_TO_SERVICE[hass_value]
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call('lock', service, params)
if self._code:
params[ATTR_CODE] = self._code
self.hass.services.call(DOMAIN, service, params)
def update_state(self, new_state):
"""Update lock after state changed."""

View File

@@ -0,0 +1,142 @@
"""Class to hold all media player accessories."""
import logging
from pyhap.const import CATEGORY_SWITCH
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_MUTE,
STATE_OFF, STATE_PLAYING, STATE_UNKNOWN)
from homeassistant.components.media_player import (
ATTR_MEDIA_VOLUME_MUTED, DOMAIN)
from . import TYPES
from .accessories import HomeAccessory
from .const import (
CHAR_NAME, CHAR_ON, CONF_FEATURE_LIST, FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, SERV_SWITCH)
_LOGGER = logging.getLogger(__name__)
MODE_FRIENDLY_NAME = {FEATURE_ON_OFF: 'Power',
FEATURE_PLAY_PAUSE: 'Play/Pause',
FEATURE_PLAY_STOP: 'Play/Stop',
FEATURE_TOGGLE_MUTE: 'Mute'}
@TYPES.register('MediaPlayer')
class MediaPlayer(HomeAccessory):
"""Generate a Media Player accessory."""
def __init__(self, *args):
"""Initialize a Switch accessory object."""
super().__init__(*args, category=CATEGORY_SWITCH)
self._flag = {FEATURE_ON_OFF: False, FEATURE_PLAY_PAUSE: False,
FEATURE_PLAY_STOP: False, FEATURE_TOGGLE_MUTE: False}
self.chars = {FEATURE_ON_OFF: None, FEATURE_PLAY_PAUSE: None,
FEATURE_PLAY_STOP: None, FEATURE_TOGGLE_MUTE: None}
feature_list = self.config[CONF_FEATURE_LIST]
if FEATURE_ON_OFF in feature_list:
name = self.generate_service_name(FEATURE_ON_OFF)
serv_on_off = self.add_preload_service(SERV_SWITCH, CHAR_NAME)
serv_on_off.configure_char(CHAR_NAME, value=name)
self.chars[FEATURE_ON_OFF] = serv_on_off.configure_char(
CHAR_ON, value=False, setter_callback=self.set_on_off)
if FEATURE_PLAY_PAUSE in feature_list:
name = self.generate_service_name(FEATURE_PLAY_PAUSE)
serv_play_pause = self.add_preload_service(SERV_SWITCH, CHAR_NAME)
serv_play_pause.configure_char(CHAR_NAME, value=name)
self.chars[FEATURE_PLAY_PAUSE] = serv_play_pause.configure_char(
CHAR_ON, value=False, setter_callback=self.set_play_pause)
if FEATURE_PLAY_STOP in feature_list:
name = self.generate_service_name(FEATURE_PLAY_STOP)
serv_play_stop = self.add_preload_service(SERV_SWITCH, CHAR_NAME)
serv_play_stop.configure_char(CHAR_NAME, value=name)
self.chars[FEATURE_PLAY_STOP] = serv_play_stop.configure_char(
CHAR_ON, value=False, setter_callback=self.set_play_stop)
if FEATURE_TOGGLE_MUTE in feature_list:
name = self.generate_service_name(FEATURE_TOGGLE_MUTE)
serv_toggle_mute = self.add_preload_service(SERV_SWITCH, CHAR_NAME)
serv_toggle_mute.configure_char(CHAR_NAME, value=name)
self.chars[FEATURE_TOGGLE_MUTE] = serv_toggle_mute.configure_char(
CHAR_ON, value=False, setter_callback=self.set_toggle_mute)
def generate_service_name(self, mode):
"""Generate name for individual service."""
return '{} {}'.format(self.display_name, MODE_FRIENDLY_NAME[mode])
def set_on_off(self, value):
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug('%s: Set switch state for "on_off" to %s',
self.entity_id, value)
self._flag[FEATURE_ON_OFF] = True
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call(DOMAIN, service, params)
def set_play_pause(self, value):
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug('%s: Set switch state for "play_pause" to %s',
self.entity_id, value)
self._flag[FEATURE_PLAY_PAUSE] = True
service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_PAUSE
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call(DOMAIN, service, params)
def set_play_stop(self, value):
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug('%s: Set switch state for "play_stop" to %s',
self.entity_id, value)
self._flag[FEATURE_PLAY_STOP] = True
service = SERVICE_MEDIA_PLAY if value else SERVICE_MEDIA_STOP
params = {ATTR_ENTITY_ID: self.entity_id}
self.hass.services.call(DOMAIN, service, params)
def set_toggle_mute(self, value):
"""Move switch state to value if call came from HomeKit."""
_LOGGER.debug('%s: Set switch state for "toggle_mute" to %s',
self.entity_id, value)
self._flag[FEATURE_TOGGLE_MUTE] = True
params = {ATTR_ENTITY_ID: self.entity_id,
ATTR_MEDIA_VOLUME_MUTED: value}
self.hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, params)
def update_state(self, new_state):
"""Update switch state after state changed."""
current_state = new_state.state
if self.chars[FEATURE_ON_OFF]:
hk_state = current_state not in (STATE_OFF, STATE_UNKNOWN, 'None')
if not self._flag[FEATURE_ON_OFF]:
_LOGGER.debug('%s: Set current state for "on_off" to %s',
self.entity_id, hk_state)
self.chars[FEATURE_ON_OFF].set_value(hk_state)
self._flag[FEATURE_ON_OFF] = False
if self.chars[FEATURE_PLAY_PAUSE]:
hk_state = current_state == STATE_PLAYING
if not self._flag[FEATURE_PLAY_PAUSE]:
_LOGGER.debug('%s: Set current state for "play_pause" to %s',
self.entity_id, hk_state)
self.chars[FEATURE_PLAY_PAUSE].set_value(hk_state)
self._flag[FEATURE_PLAY_PAUSE] = False
if self.chars[FEATURE_PLAY_STOP]:
hk_state = current_state == STATE_PLAYING
if not self._flag[FEATURE_PLAY_STOP]:
_LOGGER.debug('%s: Set current state for "play_stop" to %s',
self.entity_id, hk_state)
self.chars[FEATURE_PLAY_STOP].set_value(hk_state)
self._flag[FEATURE_PLAY_STOP] = False
if self.chars[FEATURE_TOGGLE_MUTE]:
current_state = new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED)
if not self._flag[FEATURE_TOGGLE_MUTE]:
_LOGGER.debug('%s: Set current state for "toggle_mute" to %s',
self.entity_id, current_state)
self.chars[FEATURE_TOGGLE_MUTE].set_value(current_state)
self._flag[FEATURE_TOGGLE_MUTE] = False

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