Compare commits

..

310 Commits

Author SHA1 Message Date
Paulus Schoutsen
2aabdf29bb Merge pull request #17970 from home-assistant/rc
0.81.2
2018-10-30 08:13:59 +01:00
Paulus Schoutsen
1e03f945b5 Don't use keyset (#17984) 2018-10-29 21:32:37 +01:00
Paulus Schoutsen
a91d894132 Update requests to 2.20.0 (#17978) 2018-10-29 21:32:36 +01:00
Paulus Schoutsen
66d0fb7dbf Bumped version to 0.81.2 2018-10-29 19:22:59 +01:00
Paulus Schoutsen
3a8891d9ac Pass hass_config to load_platform (#17952)
* Pass hass_config to load_platform

* Fix tests

* Lint
2018-10-29 19:22:40 +01:00
Robert Svensson
04e0fd1d46 Fix controller not being stored when setup fails and sequentially fails the retry functionality (#17927) 2018-10-29 19:21:38 +01:00
Paulus Schoutsen
b6a799499a Merge pull request #17895 from home-assistant/rc
0.81.1
2018-10-28 08:37:44 +01:00
Paulus Schoutsen
29c9081ca1 Bumped version to 0.81.1 2018-10-27 23:54:15 +02:00
Paulus Schoutsen
1f07909a14 Move migrate to separate WS command (#17890) 2018-10-27 23:54:15 +02:00
Paulus Schoutsen
21686c9263 Allow a list ofr update entity (#17860)
* Allow a list ofr update entity

* Update services.yaml

* Update services.yaml
2018-10-27 23:54:15 +02:00
Aaron Bach
edf2974979 Fixes an issue with OpenUV config import failing (#17831)
* Fixes an issue with OpenUV config import failing

* Update

* Update __init__.py

* Update config_flow.py
2018-10-27 23:54:15 +02:00
Paulus Schoutsen
ea75e3bfa8 bump frontend to 20181026.1 2018-10-27 23:54:15 +02:00
Rohan Kapoor
a0d4595f78 Switch to using Client from twilio.rest rather than the deleted TwilioRestClient (#17885) 2018-10-27 21:36:47 +02:00
Paulus Schoutsen
e28170a0a6 Merge pull request #17809 from home-assistant/rc
0.81
2018-10-26 19:45:29 +02:00
Paulus Schoutsen
dcf8aba150 frontend bump 2018-10-26 12:55:57 +02:00
Paulus Schoutsen
121a59abe0 Bumped version to 0.81.0 2018-10-26 10:22:26 +02:00
Paulus Schoutsen
0c7b0bdb44 Fix unloading an entry can leave states around (#17786)
* Add test that tests unloading on remove

* Add more test things

* Untangle entity remove code from entity platform

* Don't add default implementation of async_will_remove

* Keep entity weakref alive
2018-10-26 10:19:59 +02:00
Tomas Hellström
c4b2c2bfcf SMHI weather component not showing correct values in current forecast (#17783)
* fixes not showing current forecast correctly

* Update config_flow.py

* Update smhi.py

* Update requirements_all.txt

* Update requirements_test_all.txt
2018-10-26 10:19:58 +02:00
cdce8p
bc67115df3 Update HAP-python to 2.3.0 (#17778)
* Update HAP-python to 2.3.0

* Fix tests
2018-10-26 10:19:58 +02:00
Aaron Bach
af03390c4f Fixed an incorrect reference in the entity registry (#17775) 2018-10-26 10:19:57 +02:00
Paulus Schoutsen
a4773dc3e4 Update translations 2018-10-26 10:19:28 +02:00
Paulus Schoutsen
ad903f9917 Bump frontend to 20181026.0 2018-10-26 10:19:23 +02:00
Paulus Schoutsen
a3ec37834b Bumped version to 0.81.0b2 2018-10-24 22:15:57 +02:00
Fabian Affolter
3d841681d7 Remove day (fixes #17741) (#17743) 2018-10-24 22:15:51 +02:00
Bram Kragten
295a004326 Lovelace ws: add card (#17730)
* Change set to update

* Add 'add card'

* Woof.
2018-10-24 22:15:50 +02:00
Charles Garwood
8de0824688 Add cover to supported platforms (#17725) 2018-10-24 22:15:50 +02:00
Glenn Waters
edc1cbdc32 Elk-M1 climate (#17679)
* Initial climate for Elk-M1.

* Tidy

* fix hound error

* fix hound error
2018-10-24 22:15:49 +02:00
Paulus Schoutsen
1788eaf037 Update frontend to 20181024.0 2018-10-24 22:15:35 +02:00
Paulus Schoutsen
4750656f1a Bumped version to 0.81.0b1 2018-10-23 14:09:47 +02:00
Jaxom Nutt
011cc624b6 Bug fix for clicksend (#17713)
* Bug fix

Current version causes 500 error since it is sending an array of from numbers to ClickSend. Changing the from number to 'hass' identifies all messages as coming from Home Assistant making them more recognisable and removes the bug.

* Amendment

Changed it to use 'hass' as the default instead of defaulting to the recipient which is the array. Would have worked if users set their own name but users who were using the default were experiencing the issue.

* Added DEFAULT_SENDER variable
2018-10-23 14:09:41 +02:00
Anders Melchiorsen
2d9a964953 Update limitlessled to 1.1.3 (#17703) 2018-10-23 14:09:40 +02:00
Matt Snyder
87133a0e77 Update flux library version (#17677) 2018-10-23 14:09:40 +02:00
Nicko van Someren
fe8dec27a3 Fixed issue #16903 re exception with multiple simultanious writes (#17636)
Reworked tests/components/emulated_hue/test_init.py to not be
dependent on the specific internal implementation of util/jsonn.py
2018-10-23 14:09:39 +02:00
Bram Kragten
b5323cd894 Add lovelace websocket get and set card (#17600)
* Add ws get, set card

* lint+fix test

* Add test for set

* Added more tests, catch unsupported yaml constructors

Like !include will now give an error in the frontend.

* lint
2018-10-23 14:09:39 +02:00
Malte Franken
23316a8344 Geo location trigger added (#16967)
* zone trigger supports entity id pattern

* fixed lint error

* fixed test code

* initial version of new geo_location trigger

* revert to original

* simplified code and added tests

* refactored geo_location trigger to be based on a source defined by the entity

* amended test cases

* small refactorings
2018-10-23 14:09:38 +02:00
Paulus Schoutsen
86ecd7555a Update translations 2018-10-23 14:05:03 +02:00
Paulus Schoutsen
02f55b039c Update frontend to 20181023.0 2018-10-23 14:04:37 +02:00
Paulus Schoutsen
ebaf7f8c00 Bumped version to 0.81.0b0 2018-10-21 20:34:50 +02:00
Paulus Schoutsen
355005114b Update translations 2018-10-21 20:34:28 +02:00
Paulus Schoutsen
8f529b20d7 Bump frontend to 20181021.0 2018-10-21 20:34:12 +02:00
Richard Patel
b2faa67ab7 Add new rtorrent sensor (#17421)
* New rtorrent sensor

* Fix lint issue

* Fix another lint issue

* Fix pylint issue

* how many python linters do you guys use

* Cleanup code

* python linting

* newline
2018-10-21 20:12:51 +02:00
Luke Fritz
95371fe4a6 Bump pyarlo==0.2.2 (#17673)
* Bump pyarlo to 0.2.2, fixes #17427

* Increase log level for refresh message to clear up logs
2018-10-21 19:54:51 +02:00
Mathieu Velten
2d980f2a92 Update pynetgear to 0.5.0 (#17652) 2018-10-21 19:54:01 +02:00
Oscar Tin Lai
b6d3a199ce Add support for Dyson Hot+Cool Fan as a climate device (#14598)
* Added support for dyson hot+cool fan as climate device

* Removed decimal place in kelvin units conversion

Minor edits to be consistent with Dyson's internal conversion of temperature from kelvin to celsius. It does not include decimal place to convert between kelvin and celsius.

* made changes according to comments

* Refactored target temp logics, fixed enum issues

* changed name of component to entity

* removed temperature conversion for min/max property

* changed back to 644 permission

* added extra tests for almost-all coverage

* changed assert method to avoid lack of certain method in py35

* added test_setup_component

* shorten line length

* fixed mock spec and added checking of message listener is called

* added doc string and debug msg

* shorten line length

* removed pending target temp
2018-10-21 17:35:07 +02:00
Fabian Affolter
731753b604 Upgrade holidays to 0.9.8 (#17656) 2018-10-21 15:07:44 +02:00
Fabian Affolter
cf24687024 Upgrade async_timeout to 3.0.1 (#17655) 2018-10-21 14:13:30 +02:00
Daniel Høyer Iversen
ef93d48d50 available to switchmate (#17640) 2018-10-21 13:41:27 +02:00
emontnemery
9982867d66 Very minor cleanup of RFLink components (#17649) 2018-10-21 13:05:02 +02:00
Paulus Schoutsen
bdfd473aaa Reconnect if sub info comes in that is valid again (#17651) 2018-10-21 12:16:24 +02:00
Daniel Høyer Iversen
7e3d0f0700 Remove ryobi from .coveragerc (#17647) 2018-10-21 11:21:09 +02:00
guillaume1410
c03b137130 Removing ryobi gdo (#17637) 2018-10-21 08:08:35 +02:00
Raymon de Looff
85dbf1eed3 Upgrade dsmr_parser to 0.12 (#17634) 2018-10-20 22:07:47 +02:00
mvn23
237ac08076 Add opentherm_gw binary sensor support (#17625)
* Add OpenTherm Gateway binary sensor support.

* opentherm_gw binary_sensor platform does not need polling.
2018-10-20 18:51:01 +02:00
Bob Clough
2e973c7572 Fix mqtt light brightness slider (#17075)
* Enable brightness slider for RGB

If we are using RGB with no brightness topic, the brighness slider
should still be visible, as we can scale the RGB amount to give us the
brightness.

* Output RGB scaled by brightness

If we are outputting to an RGB device, but do not have a dedicated
brightness topic set, when the brightness slider is changed, we should
output the current colour's HS, with the V coming from the brightness
slider.

* Brightness from RGB when we're not using a brightness topic

When we aren't using a brightness topic, set the brightness slider based
on the received value from an RGB -> HSV conversion.

* Test for new brightness state scaled by RGB

This adds a test to make sure the brightness stored in the state is
being computed correctly from the RGB value when a dedicated brightness
topic is not set.

* Changes from review

Fixes formatting of supported features flags, and checks HS colour
hasn't been set when operating in RGB-only mode

* Set optimistic brightness correctly in rgb mode

When we're using rgb mode to set the brightness, we want to set
optimistic brightness if:

we are running in optimistic mode
OR
the brightness state topic isn't set and we have a brightness command topic
OR
the rgb state topic isn't set and we don't have a brightness command topic

* Add test for turn_on in RGB brightness mode
2018-10-20 12:37:25 -04:00
Julien Debaru
e980d1b9fe Fix linky sensor login error (#17110)
* Fix linky sensor login error

* Make platform fail-safe

Adding following enhancements:
* Make sure the platform loads correctly by making the first API request in setup_platform.
* Close the session after each API call.
* Use timeout parameter everywhere.

* Fix Hound CI error: line too long.

* Update pylinky library

* Remove LinkyClient from update()
2018-10-20 15:30:10 +02:00
Robert Svensson
26b7c2de7e deCONZ - Add support for Xiaomi window covers (#17337)
Add support for Xiaomi window covers
2018-10-20 15:13:23 +02:00
Daniel Shokouhi
ab826b8fe2 Use cached robot serial for Neato update (#17633)
* Use cached robot serial for neato update

* Hound
2018-10-20 14:28:30 +02:00
cdce8p
a9a8cbbd10 Homekit component cleanup (#17627)
* hass.async_add_executor_job
* Fix accessories.run -> async_track_state_change
* Fixed media_player test
* Flags are now local vars
* consistent use of " and '
2018-10-20 00:14:05 +02:00
Glenn Waters
3655fefec2 Add Elk-M1 sensor platform (#17342)
* Initial sensor version.

* Add Elk-M1 sensor services. Initial version of services.yaml.

* Fix lint errors.

* Fix PR comments.

* fix PR comment

* Fix PR comment

* Fix PR comments

* fix
2018-10-19 23:41:04 +02:00
cdce8p
ff33cbd22f Add water_heater support to HomeKit (#17614)
* Homekit add support for water_heater
* Added tests
2018-10-19 21:04:05 +02:00
Nick Touran
e343f5521c Upgrade gstreamer-player to 1.1.2 (#17568)
* Upgrade gstreamer-player to 1.1.2

* Updated requirements for gstreamer-player properly.
2018-10-19 18:11:47 +02:00
Steven Looman
f7bc44955c Upgrade async_upnp_client to 0.12.7 (#17601) 2018-10-19 18:10:04 +02:00
Nick Horvath
df2c3cdded Bump thermoworks version to fix conflict from upstream pyrebase sseclient (#17620) 2018-10-19 17:48:46 +02:00
ehendrix23
f504e5ef61 Add doorsense sensor for August 3rd Gen Smart Lock Pro (#17299)
* Add doorsense sensor for August 3rd Gen Smart Lock Pro

Add a binary sensor to August for the August 3rd Gen Smart Lock Pro doorsense sensor.

This is a re-do from PR 17116 https://github.com/home-assistant/home-assistant/pull/17116 that I closed due to rebase issue on my end.

* Changed to use snjoetw provided code

Going through the py-august I found that snjoetw had provided updated versions for the august component (august.py and binary_sensor/august.py) to include DoorSense sensor.
Changed what I did to to what snjoetw provided instead as he split it into 2 classes; much cleaner I think.

I modified his coding with:
   Fixes that were done to the August component and not part of the coding snjoetw provided.
   Added the debug logging improvement I had done in the code.

Note, fix I committed earlier for lock atribute (lock/august.py) is thus still the same.

* Reverted change from add_device to add_entities

Missed an item when merging snjoetw's code with current. Fixed.

* Updated call from add_devices to add_entities as well

Updated the call from add_devices to add_entities.

* Fixed permissions on files

Fixed permissions on components/august.py and binary_snesor/august.py

* Changed if/else to if/continue

Changed logic so that if the door sensor state is unknown during initalization the debug log is written and then continue the loop instead of using if/else logic.

* Added available property for Door Sensor

Added the available property for the Door Sensor and setting it to False if a status unknown is received.

* Updated setting self._available

Changed line for setting self._available to what Martin provided. Much  more efficient to read. :-)
2018-10-19 09:37:02 +02:00
Thomas Lovén
8bf58e1df5 Revert "De-syncing binary_sensor.ping (#17056)" (#17606)
This reverts commit 11d5671ee0.
2018-10-19 09:29:48 +02:00
Thomas Lovén
90183bd682 Tuya light icon fix (#17605)
* Fix for tuya light icons going too bright

* Make sure other values aren't strings either
2018-10-19 09:17:19 +02:00
jjlawren
88ec73ed8f Add missing await for coroutine (#17609) 2018-10-19 09:10:41 +02:00
mvn23
7baffed7b7 Add sensor support to opentherm_gw (#17314)
* Add OpenTherm Gateway sensor platform.

* Add OTGW_ variables to list of supported sensors.

* Order imports.

* Add OpenTherm Gateway binary sensor support.

* Revert "Add OpenTherm Gateway binary sensor support."

This reverts commit 115acaa912.

* Import COMP_SENSOR from sensor component rather than defining it.

* Update opentherm_gw sensor platform docs url.

* Update dependency to v0.2b1
Old version had incorrect variable names for some of the sensors

* Update requirements_all.txt

* Address review findings.

* Update .coveragerc
2018-10-19 07:31:19 +02:00
Anders Melchiorsen
cc4d29d42a Fix flux switch update interval (#17458) 2018-10-18 23:04:22 +02:00
emontnemery
10c1378195 Add binary_sensor support to RFlink (#17146)
* Add binary_sensor support to RFlink

* Add support for aliases

* Fix review comments

* Refactor, add tests

* Review comments

* Review comments

* Review comments

* Review comments
2018-10-18 22:28:40 +02:00
Ville Skyttä
cf3a97ff3c Upgrade pytest to 3.9.1 (#17598) 2018-10-18 21:31:52 +02:00
Kevin Fronczak
222ba96b3e Bump blinkpy version to 0.10.1 (#17595) 2018-10-18 21:30:46 +02:00
Nikolay Vasilchuk
6239a523cc Fix: Xiaomi Plug state is set twice (#17482)
* Xiaomi Plug UI fix #17422

* Review
2018-10-18 20:28:43 +02:00
Malte Franken
7eb6e49df7 Fixing race condition in geo location platforms (#17581)
* fixed race condition where obsolete entities kept listening for dispatcher signals

* making tests python 3.5 compatible
2018-10-18 15:25:48 +02:00
Paulus Schoutsen
7e91c0dc83 Merge branch 'master' into dev 2018-10-18 14:59:14 +02:00
Paulus Schoutsen
b1a05e7605 Merge pull request #17578 from home-assistant/rc
0.80.3
2018-10-18 14:55:46 +02:00
Paulus Schoutsen
cd90bb4161 Bumped version to 0.80.3 2018-10-18 13:58:58 +02:00
Paulus Schoutsen
17d0fe02c7 Update snapcast to 2.0.9 (#17573) 2018-10-18 13:58:48 +02:00
Steven Looman
43b140be5e Upgrade async_upnp_client to 0.12.6 (#17560) 2018-10-18 13:58:48 +02:00
Steven Looman
9ded16ccc3 Update to async-upnp-client==0.12.5 (#17401) 2018-10-18 13:58:47 +02:00
Marcel Hoppe
91dc0c3731 update hangups to 0.4.6 and fix Issue #16593 hangouts reconnects. (#17518) 2018-10-18 13:57:04 +02:00
Paulus Schoutsen
6e4a99cec0 Bump frontend to 20181018.0 2018-10-18 13:52:52 +02:00
Paulus Schoutsen
20bd14defb Bump frontend to 20181018.0 2018-10-18 13:52:38 +02:00
Paulus Schoutsen
20fa6c5383 Update snapcast to 2.0.9 (#17573) 2018-10-18 11:06:32 +02:00
Matthew Garrett
e2a1e21c8d Add support for LG soundbars (#17570)
* Add LG soundbar support

We can autodiscover these, so for now let's skip any local configuration.
Currently we expose volume, source and equaliser preset - we can expose the
other volume controls and options as well if necessary, but I don't know
whether it's worth it.

* Add discovery of LG devices

This is a generic discovery type that doesn't obviously contain enough
information to identify whether we're talking to a speaker system or any
other kind of device - on the other hand I haven't been able to find any
other LG devices that respond like this, so we can cross that bridge when
we get to it.

* Lint
2018-10-18 10:39:33 +02:00
Brian Gianforcaro
45878c6df0 Upgrade twilio package to version 6.19.1 (#17395) (#17424)
- Bump twilio requirement to latest 6.19.1 version

- The generic response type is gone in the latest
  versions of the twilio package. It appears we were
  generating an empty response just to get the empty
  xml body. TwilML is the new base class all responses
  inherit from. So I've switched the code over to using
  and empty TwilML object instead.

- The exception type was moved to a different location.
2018-10-18 09:17:55 +02:00
Daniel Høyer Iversen
3e5233d115 danielhiversen as mill codeowner (#17571) 2018-10-18 08:12:09 +02:00
Jorim Tielemans
b50c93ccb7 Validate ports as a port (#17549)
* Validate ports as port

Better than just a positive integer since it limits the range from 1 to 65535.

* Validate port for Axis

* Validate port for Xiaomi Home Camera

* Validate port for Modbus

* Validate port for Yamaha MusicCast Receivers

* Update zhong_hong.py

Validate port as a port, the gateway address as positive_int
Also moved the default values to their variable

* Validate port for the Asterisk Voicemail interface

* Fix lint

* Validate port for Xiaomi Cameras
2018-10-17 23:09:05 +02:00
Eduard van Valkenburg
50a66abd80 Updated package to fix #16960 (#17555) 2018-10-17 22:56:54 +02:00
Steven Looman
7106d9e9d4 Upgrade async_upnp_client to 0.12.6 (#17560) 2018-10-17 22:56:21 +02:00
ehendrix23
daf9d28565 Fix mold_indicator errors at startup (#17346)
* Initial changes to resolve issue 16733

Added logic to ensure that if the state is unknown during startup that the error about being unable to parse the value is not logged.
Further,  also ensured that if an attribute is set to None it does not try to convert the None value to Fahrenheit as that will cause an error.

* Cleaned up and added few comments

Cleaned up some lines based on flake8 and pylint.
Added some comment lines on the items added.

* Changed to async and using async_added_to_hass

Changed sensor to use async.
Registering state tracking for sensors and initial setup is now done upon the home assistant start event.

* Updated test and small fix

Updated test to handle unavailable state of sensor and return of None for attributes when data is unavailable.
Ensured that atributes are set to None when state is unavailable due to incorrect data.

* Fixed some flake8 issues in test

Fixed some flake8 issues in test_moldindicator.py.

* Updates based on review

Updates based on review from MartinHjelmare

* Added sensor entity_id to logger errors

Added sensor entity_id to logger error messages
Update test to use constant STATE_UNKNOWN instead of fixed string.
2018-10-17 18:42:39 +02:00
Bram Kragten
33860bf23c Adding id to lovelace cards in ui-lovelace.yaml (#17498)
* ID is added to cards without ID in ui-lovelace.yaml when loaded

* Hound

* Remove ui-lovelace.yaml

* Nicer get

* Update tests

* If YAML dump fails, config not gone

* Add tests

* Woof!

* Remove nosetests

* Address comments

* Woof...

* Delete test.yaml

* update rights to saved file

* fix

* line break
2018-10-17 16:31:06 +02:00
Paulus Schoutsen
fa196e5889 Merge pull request #17546 from home-assistant/rc
0.80.2
2018-10-17 14:56:58 +02:00
Paulus Schoutsen
9500bb1ac8 Bumped version to 0.80.2 2018-10-17 14:17:56 +02:00
Paulus Schoutsen
b3548f1ead Add another 3 days leeway to give time for payment processing times (#17542) 2018-10-17 14:17:47 +02:00
Kevin Fronczak
f74e976be1 Blink update - fixes #17316 (#17538)
* Updgrae blinkpy to 0.10.0

- Remove status sensor (API endpoint unreliable for this)
- Wifi strength reports in wifi bars rather than dBm (result of new API
endpoint)
- Added unique ids based on serial number

* Update requirements
2018-10-17 14:17:47 +02:00
Nikolay Vasilchuk
dc55718bc3 Fix: Connection pool of Request object is smaller than optimal value (8) (#17483) 2018-10-17 14:17:46 +02:00
Paulus Schoutsen
6551c53e32 Bump frontend to 20181017.0 2018-10-17 14:17:35 +02:00
Paulus Schoutsen
a27d49d022 Update translations 2018-10-17 14:17:33 +02:00
Paulus Schoutsen
95c43d634b Bump frontend to 20181017.0 2018-10-17 14:16:49 +02:00
Paulus Schoutsen
a5b9f5040f Update translations 2018-10-17 14:12:41 +02:00
cgtobi
e83a9aace4 Remove unnecessary call (#17514) 2018-10-17 10:53:05 +02:00
Anders Melchiorsen
1e4463957d Scan all network interfaces for LIFX bulbs (#17530) 2018-10-17 10:50:13 +02:00
Paulus Schoutsen
326787ef1a Add another 3 days leeway to give time for payment processing times (#17542) 2018-10-17 10:45:01 +02:00
Ville Skyttä
15f4ed74ac Tweak sensors comments in default config (#17526)
Makes it more clear that there should be only one sensors section, and
that the weather prediction comment applies only to the yr platform.
2018-10-17 09:58:41 +02:00
Ville Skyttä
b7b4224429 Huawei LTE sensor improvements (#17533)
* Sensor value formatting improvements

* Make default names more consistent with other sensors

* Improve unique id formatting
2018-10-17 09:00:15 +02:00
Kevin Fronczak
5088e7ee49 Blink update - fixes #17316 (#17538)
* Updgrae blinkpy to 0.10.0

- Remove status sensor (API endpoint unreliable for this)
- Wifi strength reports in wifi bars rather than dBm (result of new API
endpoint)
- Added unique ids based on serial number

* Update requirements
2018-10-17 08:38:03 +02:00
Nate Clark
11004bcf34 Manual IP & port configuration for Konnected devices (#17120)
* add capability for manually specifying IP and port of Konnected device(s)

* add config options for blink and discovery settings

* import konnected only in functions where needed

* updates from code review feedback

* convert manual_discovery to async

* code review updates; use correct sync vs async
2018-10-17 00:39:32 +02:00
Brant Knudson
83db673bd0 Add unique_id to Vera entities (#17450)
I believe this adds registry support. The UI allows me to change
the entity ID now.

For example, a light bulb called "BasementHallLight" in the Vera
has an initial Entity ID like light.basementhalllight_108, where
108 is the unique ID that the Vera assigned the device when I
added it to the z-wave network.

Now I can use the UI to change the Entity ID to
light.basementhalllight and I can still turn it on and off.
2018-10-16 23:27:57 +02:00
Marcel Hoppe
6b3e4ca7bd update hangups to 0.4.6 and fix Issue #16593 hangouts reconnects. (#17518) 2018-10-16 20:09:34 +02:00
Daniel Høyer Iversen
aa176312a5 Update switchmate library (#17519) 2018-10-16 20:08:34 +02:00
Daniel Høyer Iversen
6235aae196 Update mill library (#17520) 2018-10-16 20:08:04 +02:00
Ben Lebherz
764ea06795 Fix unhandled exception which creates many useless logs (#17508)
* Fix unhandled exception which creates many useless logs

* recover old component logic, sorry

* remove inline conditional
2018-10-16 16:41:38 +02:00
Charles Garwood
9c52a3ce22 Z-Wave Device Registry Support (#17291)
* Add device_registry support for sensor and switch domains

* Add device_registry support for light

* Add device registry to binary_sensor, climate, cover

* Add device registry to zwave fan

* Fix test for config entry loading

* lint

* revert erroneous modification

* Revert device_registry.py change
2018-10-16 14:58:25 +02:00
Tsvi Mostovicz
c6d9ceca63 Bump hdate version to 0.6.5 (#17510)
* Bump hdate version to 0.6.4

* Bump to 0.6.5

* Change test so we check when passing tzinfo object
2018-10-16 13:45:39 +02:00
ehendrix23
fee87cd6ed Add LogBook support to HomeKit (#17180) 2018-10-16 13:32:53 +02:00
William Scanlon
71ab8a9b1a Moved Wink water heater from climate to water heater. (#17504)
* Moved Wink water heater from climate to water heater.

* Remove deprecated states from Tuya

* Update toon.py

* Update toon.py

* Lint
2018-10-16 11:27:01 +02:00
Robert Svensson
a795093705 UniFi POE control (#17011)
* First commit

* Feature complete?

* Add dependency

* Move setting poe mode logic to library

* Use guard clauses

* Bump requirement to 2

* Simplify saving switches with poe off

* Store and use poe mode

* Fix indentation

* Fix flake8

* Configuration future proofing

* Bump dependency to v3

* Add first test

* Proper use of defaults with config flow (thanks helto)

* Appease hound

* Make sure there can't be duplicate entries of combination host+site

* More tests

* More tests

* 98% coverage of controller

* Fix hound comments

* Config flow step init not necessary

* Use async_current_entries to check if host and site for controller is used

* Remove storing/restoring poe off devices to slim PR

* First batch of switch tests

* More switch tests.

* Small improvements and clean up

* Make tests pass
Don't name device in device registry

* Dont process clients that belong to non-UniFi POE switches

* Allow selection of site from a list in config flow

* Fix double blank lines in method

* Update codeowners
2018-10-16 10:35:35 +02:00
Nikolay Vasilchuk
0c0c471447 Fix: Connection pool of Request object is smaller than optimal value (8) (#17483) 2018-10-16 10:22:57 +02:00
Daniel Shokouhi
dc7e5e3af4 Add unique_id for Ring (#17497) 2018-10-16 10:06:00 +02:00
Aaron Bach
a9389d2d43 Bumps simplisafe-python to 3.1.12 (#17509)
* Bumps simplisafe-python to 3.1.12

* Updated requirements
2018-10-15 22:54:32 -06:00
Alexander Lyon
993a02b8c4 Fix the sabnzbd component api error (#17014)
* Bump pysabnzbd version number

* Pass hass aiohttp session to pysabnzbd
2018-10-16 00:18:59 +02:00
Aaron Bach
29c2b2fe79 Clean up OpenUV config flow (#17349)
* Cleaned up OpenUV config flow

* Added proper listener removal

* Added proper exception

* Unnecessary exception message

* Moved API key error to correct place

* Member-requested changes (part 1)

* Hound

* Member-requested changes (part 2)

* Cleanup

* Fixed tests
2018-10-15 13:21:21 -06:00
Daniel Perna
73197c9a6c Update pyhomematic to 0.1.51 (#17491) 2018-10-15 21:15:26 +02:00
Anders Melchiorsen
1a5048baaf Add device info for LIFX (#17330)
* Add device info for LIFX

* Address review comments
2018-10-15 20:46:33 +02:00
Daniel Perna
9718a17351 Fix HomeMatic availability detection (#17341)
* Fix availability detection
2018-10-15 20:34:03 +02:00
Dougal Matthews
6feacbbfe1 Include the name of the Volumio media player in errors (#17481)
When you have multiple Volumio media players it can be hard to determine
which one has a problem without this information.
2018-10-15 19:11:12 +02:00
Paulus Schoutsen
4ea71b0602 Merge pull request #17480 from home-assistant/rc
0.80.1
2018-10-15 16:10:50 +02:00
emontnemery
2ceb4d2d1e Refactor RFLink component (#17402)
* Start refactor of RFLink component

* alias _id not added correctly

Aliases for sensor not added correctly
And some debug traces.

* Update rflink.py

* Cleanup, fix review comments

* Call event handlers directly when processing initial event

* Use hass.async_create_task when adding discovered device

* Review comments

* Review comments
2018-10-15 15:35:33 +02:00
Paulus Schoutsen
708334c0c2 Bumped version to 0.80.1 2018-10-15 15:31:12 +02:00
Paulus Schoutsen
b5272f2bc7 Fix websocket API (#17471) 2018-10-15 15:30:21 +02:00
Pascal Vizeli
80867cc9b7 Bugfix eventstream with EOF on end (#17465) 2018-10-15 15:30:20 +02:00
Tommy Jonsson
f92b392a24 Fix hangout.send_message requiring data key (#17393) 2018-10-15 15:30:20 +02:00
Martin Berg
220054a6c3 Fix arm/disarm calls. (#17381) 2018-10-15 15:30:19 +02:00
Pascal Vizeli
0904ff45fe Cleanup HM Notify platform (#17355)
* Cleanup HM Notify platform

* Fix python 3.5.4

* Update homematic.py

* Update homematic.py
2018-10-15 13:26:09 +02:00
Niels Mündler
d6752d2270 Fix rangefilter (#17473)
* Fix rangefilter

RangeFilter would break for lower or upper bounds of 0, evaluating to False and thus not being handled correctly as bounds

* Add test for zero bounds
2018-10-15 12:24:21 +02:00
Julius Mittenzwei
373e3b12d8 Switched to async_fire (#17472) 2018-10-15 12:16:40 +02:00
Paulus Schoutsen
e985f30247 Fix websocket API (#17471) 2018-10-15 11:48:36 +02:00
Adam
22bf4d0783 Re-assign conditions (#17364) 2018-10-15 11:43:27 +02:00
kennedyshead
1cbb5b8e51 State is set to UNKNOWN rather than ON in order to make UI have an play/pause button (#17357) 2018-10-15 11:42:27 +02:00
Paulus Schoutsen
ac79ff9e24 Add context to scripts run by template entities (#17329) 2018-10-15 11:38:49 +02:00
Anders Melchiorsen
0bf10b0b09 Fire an event when timer gets out of sync (#17398) 2018-10-15 11:34:36 +02:00
Ville Skyttä
31981fde7e Make dicttoxml logging less verbose (#17446) 2018-10-15 11:25:38 +02:00
Ville Skyttä
4fce051838 Add RSRQ, RSRP, and SINR to huawei_lte default sensors (#17425)
These are important LTE signal monitoring values.
2018-10-15 11:22:49 +02:00
Rohan Kapoor
bd450ee9ff Migrate CONF_WEBHOOK_ID to homeassistant.const (#17460)
* Migrate CONF_WEBHOOK_ID to homeassistant.const

* Switch over all instances of webhook_id to the const

* Switch last instance of webhook_id to the const

* automation: conf constants for conf

* webhook: conf constants for conf
2018-10-15 11:18:21 +02:00
Malte Franken
879924fea4 refactored to make its code structure similar to nsw_rural_fire_service_feed platform (#17461) 2018-10-15 11:17:46 +02:00
Pascal Vizeli
3b0db291dd Bugfix eventstream with EOF on end (#17465) 2018-10-15 10:31:49 +02:00
Julius Mittenzwei
a71cc67efb Fix NoEntitySpecifiedError during knx startup (#17366)
* Potential fix for #13699

* removed uneccessary initialization of hass

* removed hass from signature
2018-10-15 03:29:36 +02:00
Tsvi Mostovicz
c5905ee5ca Show torah reading during weekdays (#17447)
* Add support for showing torah reading on weekdays as well

* Update docstrings for test functions
2018-10-14 23:55:11 +02:00
Craig J. Midwinter
3edcc9420a Update pysher version (#17455) 2018-10-14 23:51:15 +02:00
Aaron Bach
b022dde622 Bumped simplisafe-python to 3.1.11 (#17454)
* Bumped simplisafe-python to 3.1.10

* Updated requirements

* Updated requirements
2018-10-14 14:26:44 -06:00
kennedyshead
1187e0aea5 Remove day as a conf option (#17452) 2018-10-14 21:33:18 +02:00
Daniel Høyer Iversen
a401be9b1b New climate device (#17313)
* initial version of millheater

* Remove unused imports

* Add some comments

* separate lib

* fix review comments
2018-10-14 20:22:20 +02:00
Paulus Schoutsen
49a9b61f6f Update frontend to 20181014.0 2018-10-14 19:17:41 +02:00
Paulus Schoutsen
ed683d8c2c Update frontend to 20181014.0 2018-10-14 19:17:30 +02:00
Fabian Affolter
d4061b73b0 Upgrade youtube_dl to 2018.10.05 (#17429) 2018-10-14 17:14:06 +02:00
Fabian Affolter
9305ea9a6b Upgrade numpy to 1.15.2 (#17431) 2018-10-14 17:13:25 +02:00
Fabian Affolter
7b0c88c54b Update docstrings (#17435) 2018-10-14 17:12:34 +02:00
Fabian Affolter
253e154a79 Minor updates (#17436) 2018-10-14 17:11:39 +02:00
Fabian Affolter
daf48a3b1f Minor updates (#17437) 2018-10-14 17:10:46 +02:00
Fabian Affolter
5ac0469ef9 Upgrade python-telegram-bot to 11.1.0 (#17441) 2018-10-14 17:08:17 +02:00
Malte Franken
fccaf7f919 NSW Rural Fire Service platform (#16802)
* initial integration with nsw rural fire service feed

* improved test coverage

* updated requirements

* grouped imports

* removed debug print statement

* moved manager's startup code into separate call

* simplified feed update code

* simplified feed update code

* simplified device state attribute code

* added source to conform with pr #17339

* fixed lint

* refactored how entities are managed

* fixed pylint

* simplified signalling
2018-10-14 14:11:25 +02:00
Brant Knudson
f198859441 Upgrade pyvera to 0.2.45 (#17419)
This release adds support for garage door openers. A garage door
opener will show up as a switch.
2018-10-14 09:26:34 +02:00
333ryan18
a302112879 Bump Totalconnect (#17418)
* Update totalconnect.py

* Update requirements_all.txt
2018-10-14 09:25:24 +02:00
William Scanlon
6a8eb8d0a1 Moved econet from climate to water heater (#17322)
* Moved econet from climate to water heater

* Updated .coveragerc

* Fixed requirements_all.txt
2018-10-13 23:16:44 +02:00
Fredrik Baberg
f23708ce6f Update Vagrant Windows support (#17205)
* Update Vagrantfile with Hyper-V support

* Correction to name.

* Exclude files in Vagrant provision tests
2018-10-13 23:02:00 +02:00
noxhirsch
0dd3640c78 Adding support for HmIP-SLO (outdoor brightness sensor) (#17413)
* Add IPBrightnessSensor

* Add ILLUMINATION unit & icon

* Update homematic.py

* Added missing entry
2018-10-13 22:58:41 +02:00
J4nsen
d0da265166 Fix netio component (#17411)
* bump pynetio to 0.1.9.1 to actually use provided credentials.

* cast to int to fix TypeError

* update requirements_all.txt
2018-10-13 21:09:10 +02:00
Dan Klaffenbach
3ca3fe7015 homematic: Support additional property for sabotage detection (#17407)
At least HM-Sec-Sir-WM uses ERROR_SABOTAGE, see:

    pyhomematic.devicetypes.actors.RFSiren
2018-10-13 21:08:06 +02:00
Miloš Bunčić
ef8253c549 Added ssl and verify_ssl parameters in ddwrt device tracker component (#17406)
* Added ssl and verify_ssl parameters in ddwrt device tracker component

* Set defaults in validation
2018-10-13 21:04:51 +02:00
Keiran S
78e29cd3fa Add AWS Route53 Dynamic DNS support (#17072)
* Add AWS Route53 dynamic DNS support to Home Assistant

* Remove line breaks
2018-10-13 21:03:30 +02:00
Ville Skyttä
9c178cf488 Add unique id to syncthru sensors (#17399) 2018-10-13 14:31:53 +02:00
Steven Looman
e6d002c377 Update to async-upnp-client==0.12.5 (#17401) 2018-10-13 14:29:12 +02:00
Tommy Jonsson
70281a148b Fix hangout.send_message requiring data key (#17393) 2018-10-13 10:54:35 +02:00
Florian Klien
d4b092706a XMPP async (#17283)
* new lib dependencies, working old xmpp

* non working aioxmpp

* reverting to sync xmpp

will try slixmpp instead of aioxmpp

reasons:
echo bot example of aioxmpp had blocking behavior (slixmpp echo bot works fine)
closer API to sleekxmpp
less dependencies than aioxmpp

* first working slixmpp version

* DEBUG messages, changed MUC call

the joinMUC method changed from sleekxmpp to slixmpp
added debug messages
better name for cleanup callback

* flake8

* little cleanup, tested MUC

* requirements_all

* dependencies managed by slixmpp, removed debug messages

* resource configurable by user, requirements updated

* changed __init__ parameter code format

* removed trailing dots from LOG messages

* changed super call to python3 format
2018-10-13 10:37:42 +02:00
Tom Harris
db536797be Bump insteonplm version to 0.15.0 (#17384) 2018-10-13 10:33:34 +02:00
Martin Berg
d9d27733d1 Fix arm/disarm calls. (#17381) 2018-10-13 10:30:49 +02:00
Daniel Shokouhi
5f16f3c3a6 Add unique_id for Bloomsky (#17383)
* Add unique_id for Bloomsky

* Add bloomsky camera unique ID
2018-10-13 10:23:00 +02:00
Daniel Shokouhi
d3672f36fb Add unique_id for Neato (#17369)
* Add unique_id for Neato

* Only send the serial per review comments
2018-10-13 00:33:13 +02:00
Daniel Shokouhi
3cf6c76f8b Add unique_id for Lightify (#17377) 2018-10-13 00:32:35 +02:00
Tom French
434d1d7d63 Added option to use a location other than home (#17340) 2018-10-12 20:04:52 +02:00
Aaron Bach
401e22fc0c Add config entry for SimpliSafe (#17148)
* Initial move into component

* Base functionality in place

* Starting tests in place

* All config entry tests in place

* Made default scan interval more obvious and removed extra logging

* Inherit default scan interval from alarm_control_panel

* Updated coveragerc and CODEOWNERS

* Member-requested changes

* Hound

* Updated requirements

* Updated tests

* Member-requested changes

* Owner-requested changes

* Constant cleanup

* Fixed config flow test

* Owner-requested updates

* Updated requirements

* Using async_will_remove_from_hass

* Corrected scan interval logic

* Fixed tests

* Owner-requested changes

* Additional logging

* Owner-requested changes
2018-10-12 19:07:47 +02:00
Paulus Schoutsen
8eb4e77365 Merge pull request #17361 from home-assistant/rc
0.80.0
2018-10-12 16:57:52 +02:00
Malte Franken
1f863830e1 Adding source attribute to geo location platforms (#17339)
* added source attribute to all geo_location platforms

* amended test cases

* constant moved and source method now forces subclasses to override
2018-10-12 16:48:02 +02:00
Paulus Schoutsen
cd29b47924 Bumped version to 0.80.0 2018-10-12 14:59:36 +02:00
Pascal Vizeli
edb3722abd Hass.io auth/sso part2 (#17324)
* Update discovery.py

* Create const.py

* Update auth.py

* Update const.py

* Update const.py

* Update http.py

* Update handler.py

* Update auth.py

* Update auth.py

* Update test_auth.py
2018-10-12 14:59:17 +02:00
Pascal Vizeli
bc036cc2fe Fix auth for hass.io (#17318)
* Update test_auth.py

* Update auth.py

* Update test_auth.py
2018-10-12 14:59:17 +02:00
Martin Hjelmare
8778b707f1 Allow tradfri groups for new imported entries (#17310)
* Clean up leftover config schema option

* Allow import groups via new config yaml setup

* Fix and add test

* Add a test without groups for legacy import

* Change default import groups to False

* Fix I/O in test
2018-10-12 14:59:16 +02:00
Alok Saboo
05ae8f9dd4 Fix samsung bug (#17285) 2018-10-12 14:59:16 +02:00
Nikolay Vasilchuk
d199f57aa8 Logbook: filter by entity and period (#17095)
* Filter logbook by entity_id

* Filter logbook by period

* Simple test

* houndci-bot review

* Tests

* Test fix

* Test Fix
2018-10-12 14:59:15 +02:00
Paulus Schoutsen
f47e080f37 Update translations 2018-10-12 14:58:48 +02:00
Paulus Schoutsen
cfc5ebbfb0 Update frontend 2018-10-12 14:58:23 +02:00
Paulus Schoutsen
6971e84ddf Update translations 2018-10-12 14:58:09 +02:00
Paulus Schoutsen
397c4336bc Update frontend 2018-10-12 14:57:35 +02:00
kennedyshead
e00ed84d84 The ping command will not detect device in standby as off (#17358) 2018-10-12 14:51:03 +02:00
Jedmeng
7b28963a88 Fix setting opple light color temperature (#17359) 2018-10-12 14:50:20 +02:00
Sebastian Muszynski
5a00cc5afc Provide an individual color temperature range per Yeelight model (#17305)
* Provide an individual color temperature range per Yeelight model

* Fix lint

* Bump yeelight version

* Remove unused const

* Enable SUPPORT_COLOR_TEMP for BulbType.WhiteTemp
2018-10-12 11:35:33 +02:00
Martin Mois
cb3d62eeef notify.homematic (#16973)
* Add notify.homematic_signalgen

* Update homematic_signalgen.py, test_homematic_signalgen.py

* Added new files to .coveragerc

* Fixed review comments from houndci-bot

* Fixed pylint errors

* Regenerate requirements_test_all.txt by script/gen_requirements_all.py

* Fix flake8 warnings

* Renamed notify.homematic_signalgen to notify.homematic and made it generic

* Update .coveragerc and requirements_test_all.txt

* Removed the terms signal generator from the sources.
2018-10-12 09:36:52 +02:00
Daniel Perna
241d87e9d3 Add exception handling to dnsip sensor (#17332)
* Add exception handling to dnsip sensor

* Refactor import

* Fix exception
2018-10-12 09:30:35 +02:00
emontnemery
b2789d9883 Support abbreviations in discovery topic (#16635)
* Support abbreviations in discovery topic

* Add abbreviations for all words. Add testcase.

Add missing docstring.

* Add missing abbreviations

* Support topic prefix

* Update test case

* Restrict topic prefix

* Fix merge

* Simplify abbreviations expanding, assume TOPIC_PREFIX is one character long

* Support abbreviated keys instead of words

* Remove redundant abbreviations

* Remove extra spaces in abbreviation list

* Make topic prefix less restrictive

* Make topix prefix a bit more restrictive again
2018-10-12 08:51:16 +02:00
shred86
7bb60068d7 Color control for Abode RGB lights (#17347)
* Color control support for Abode lights

* Updated add_devices to add_entities

* Update line length

* Changed elif to if for pylint warning
2018-10-12 08:47:14 +02:00
Daniel Shokouhi
1c23a36f46 Set botvac availability (#17350)
* Set botvac availability

* Lint

* Reduce availability calls per review comments
2018-10-12 08:40:45 +02:00
Zhong Jianxin
0ea5a73e8d Fix motion sensor in Aqara LAN protocol V2 (#17240) 2018-10-12 08:34:32 +02:00
Mister Wil
6df3c480b3 Bump version of abodepy to 0.14.0 (#17336) 2018-10-11 22:00:51 +02:00
Paulus Schoutsen
61f7a39748 Add permissions foundation (#16890)
* Add permission foundation

* Address comments

* typing

* False > True

* Convert more lambdas

* Use constants

* Remove support for False

* Fix only allow True
2018-10-11 19:24:25 +02:00
emontnemery
5961f2f577 Add support for off_delay to MQTT binary_sensor (#16993)
* Add support for off_delay to MQTT binary_sensor

* Fix debounce, add testcase

* Make off_delay number of seconds instead of timedelta

* Update mqtt.py

* Fix testcase, remove CONF_OFF_DELAY from const.py
2018-10-11 19:14:23 +02:00
Paulus Schoutsen
61bf4d8a29 Add user events (#17328) 2018-10-11 17:06:51 +02:00
Nikolay Vasilchuk
44477f3d32 Logbook: filter by entity and period (#17095)
* Filter logbook by entity_id

* Filter logbook by period

* Simple test

* houndci-bot review

* Tests

* Test fix

* Test Fix
2018-10-11 14:15:04 +02:00
Karim Geiger
ed45dff6e8 Implement turn_off and turn_on actions for eq3btsmart (#17168)
* Implement turn_off and turn_on actions for eq3btsmart

This commit implements the turn_off and turn_on methods for eq3btsmart. Turning the device off will set the thermostat to "OFF". Turning it on will set it to "AUTO".

* Add missing support flags for on/off feature

* Fix line length
2018-10-11 13:25:48 +02:00
Nikolay Vasilchuk
2a35a3901e Template Lock (#17288)
* Template Lock component

* Tests

* CI Fix

* Don't track templates if they have result in MATCH_ALL

* async/await

* houndci-bot review fix
2018-10-11 12:53:54 +02:00
Paulus Schoutsen
ebff253cc9 still update sensor on startup (#17319) 2018-10-11 11:38:35 +02:00
Pascal Vizeli
f5d3aa1826 Hass.io auth/sso part2 (#17324)
* Update discovery.py

* Create const.py

* Update auth.py

* Update const.py

* Update const.py

* Update http.py

* Update handler.py

* Update auth.py

* Update auth.py

* Update test_auth.py
2018-10-11 10:55:38 +02:00
uchagani
cffb704311 Enable BMW component to be unit system aware (#17197)
* Enable BMW component to be unit system aware

* lint fixes

* use constants for config entries

* remove configuration from component and rely only on HA config of unit_system

* remove unused import

* update code to reflect feedback

* lint fixes

* remove unnecessary comments

* rework return statement to satisfy pylint

* more lint fixes

* add tests for volume utils

* lint fixes

* more lint fixes

* remove unnecessary comments
2018-10-11 10:55:22 +02:00
Martin Hjelmare
58af332d21 Allow tradfri groups for new imported entries (#17310)
* Clean up leftover config schema option

* Allow import groups via new config yaml setup

* Fix and add test

* Add a test without groups for legacy import

* Change default import groups to False

* Fix I/O in test
2018-10-11 10:37:34 +02:00
George Marshall
ef2c8b2e5b Update python_openzwave==0.4.10 (#17323) 2018-10-11 10:59:16 +03:00
Fabien Piuzzi
9fa7906aef Made it possible to define multiple Octoprint printers (#16519)
* Made it possible to define multiple octoprint printers

* style fix

* Added configuration option for octoprint port

* SSL support in octoprint platform configuration

* Octoprint component now auto loads sensor and binary_sensor platforms

* preliminary support for auto discovery of octoprint servers

* Moved sensors and binary sensors configuration into main octoprint configuration

* Using base_url as the key for storing api in the octoprint component

* made sure to not supersede the platforms' domains

* bugfix: continue setting up other printers if one fails

* flake8 style correction

* Added icons to sensors

* Fail platform setup if no printers were successfully added

* Simplified custom validator
2018-10-11 09:52:13 +02:00
Dav0815
c6c5d40056 Transport NSW (#17242)
* Resubmission of development

* Clean up

* Finishing touch and clean up

* Remove not needed error check
2018-10-11 09:44:17 +02:00
Rohan Kapoor
d7cd1a2b4b Implement ZoneMinder run states (#17198) 2018-10-11 09:38:31 +02:00
Fabian Affolter
7e8973a315 Update file header (#17317) 2018-10-11 07:43:15 +02:00
Pascal Vizeli
3f87d41381 Fix auth for hass.io (#17318)
* Update test_auth.py

* Update auth.py

* Update test_auth.py
2018-10-11 01:02:00 +02:00
Fabian Affolter
8d9da4e7b9 Upgrade construct to 2.9.45 (#16362) 2018-10-10 23:52:45 +02:00
Glenn Waters
93e3596e5a Add Elk-M1 switch and scene platforms (#17256)
* Add Elk-M1 switch platform.

* Fix travis error.

* Fix very annoying lint error.

* Fix PR comments.

* Fix comment.

* Fix lint errors.

* Fix PR comments.

* Fix PR

Apologize. Going too fast. You should not have to find those.
2018-10-10 19:05:19 +02:00
zhumuht
c434ad6af5 fix_broadlink_sp2_show_energy (#17271)
* fix_broadlink_sp2_show_energy

Signed-off-by: 朱海涛 <zhumu.zht@alibaba-inc.com>

* fix_broadlink_sp2_show_energy

Signed-off-by: 朱海涛 <zhumu.zht@alibaba-inc.com>

* fix_broadlink_sp2_show_energy

Signed-off-by: 朱海涛 <zhumu.zht@alibaba-inc.com>

* fix_broadlink_sp2_show_energy

Signed-off-by: 朱海涛 <zhumu.zht@alibaba-inc.com>

* fix_broadlink_sp2_show_energy

Signed-off-by: 朱海涛 <zhumu.zht@alibaba-inc.com>
2018-10-10 18:56:00 +02:00
cgtobi
99c6622ee2 Add direction configuration (#17308) 2018-10-10 17:59:55 +02:00
Paulus Schoutsen
9d4dbd7d97 ABC config entries (#17309) 2018-10-10 16:02:03 +02:00
Paulus Schoutsen
d16e6c8524 Update translations 2018-10-10 14:25:21 +02:00
Paul Annekov
052c094425 fixed 'on_startup() takes 0 positional arguments but 1 was given' (#17295) 2018-10-10 14:24:30 +02:00
Pascal Vizeli
40e0966d7f Hassio auth (#17274)
* Create auth.py

* Update auth.py

* Update auth.py

* Update __init__.py

* Update auth.py

* Update auth.py

* Update auth.py

* Update auth.py

* Update auth.py

* Update auth.py

* Update auth.py

* Update auth.py

* Update auth.py

* Add tests

* Update test_auth.py

* Update auth.py

* Update test_auth.py

* Update auth.py
2018-10-10 14:07:51 +02:00
Joshi
ad4d5666fe Yamaha AVR update and change Sound Mode only on main_zone (#17241)
* Add support for sound_mode for Yamaha rxv media_player

* Catch ParseError Exeption on surround_program for unsupported models

* Catch all Exeptions from rxv

* only get sound mode list / current sound mode on main_zone
2018-10-10 14:07:33 +02:00
definitio
7f896bfb40 WIP: Don't set initial values for MQTT HVAC in non-optimistic mode (#17268)
* Don't set initial temperature in non-optimistic mode

* Fix tests

* Don't set initial values in non-optimistic mode

For fan mode, current operation and swing mode

* Fix tests again
2018-10-10 14:06:53 +02:00
Pascal Vizeli
83dd961fde Fix hassio discovery (#17275)
* Update discovery.py

* Update test_discovery.py

* Update test_discovery.py

* Update test_discovery.py

* Update test_discovery.py

* Update test_discovery.py

* Update test_discovery.py

* Fix tests

* fix lint
2018-10-10 13:50:11 +02:00
Paulus Schoutsen
a1dac28e4b Template sensors to not track all state changes (#17276)
* Disable template sensor match all

* Only manual update template sensors that match all
2018-10-10 13:49:15 +02:00
Martin Hjelmare
e5c3a4be80 Fix and clean haveibeenpwned (#17306)
* Move first forced data fetching and update to async_added_to_hass.
* Clean up code.
2018-10-10 13:46:03 +02:00
Ville Skyttä
707b7c202d Narrow scope of various pylint inline disables (#15364)
* Narrow scope of various pylint inline disables

* Whitespace tweaks
2018-10-10 12:17:11 +02:00
Teemu R
78c38749ab Xiaomi Vacuum: keep error state active after erroring (#16562)
* Check for got_error to keep consistent error reporting

* reword a comment
2018-10-10 12:16:32 +02:00
Markus Nigbur
670c75e844 Added resolve_state to template distance function (#17290)
_resolve_state was already used in the "closest" function, to allow for states and entity ids
2018-10-10 11:49:24 +02:00
Colby Rome
419725e1a9 Add Verizon Fios Quantum Gateway device_tracker platform (#17023)
* wrote quantum_gateway.py

* ran gen_requirements script

* fixed linting errors, added docstrings

* update .coveragerc

* fixed typo

* add myself to contributors

* single quotes for single words

* added error handling to prevent stacktrace

* updated my pypi library

* houndci fixes - added RequestException

* added password to config schema
2018-10-10 08:23:31 +02:00
cgtobi
cfc175d71d Make rmvtransport async (#17225)
* Make rmvtransport async

* Make rmv transport async

* Make async tests

* Update rmvtransport module version

* Remove unnecessary import

* Make rmvtransport async

* Make rmv transport async

* Make async tests

* Update rmvtransport module version

* Remove unnecessary import

* Update requirements

* Remove async loop

* Fix wrong import

* Fix stupidness

* Remove unnecessary import

* Bump upstream version

* Don't store the session

* Refactor tests

* Add test for no data

* Fix linter issues

* Fix stale docstring

* Fix stale docstring

* Remove unnecessary test code

* Remove unnecessary import

* Add configurable timeout

* Remove global variable
2018-10-10 08:10:42 +02:00
hanzoh
8310f4a1cf Add valve level to HmIP thermostat attributes (#17297) 2018-10-09 18:53:49 -06:00
Fabian Affolter
5022cf8a6c Upgrade locationsharinglib to 3.0.6 (#17294) 2018-10-10 00:02:07 +02:00
Fabian Affolter
2cd99e5a97 Upgrade shodan to 1.10.4 (#17292) 2018-10-09 22:37:50 +02:00
Alok Saboo
26f2e3dd8b Fix samsung bug (#17285) 2018-10-09 21:43:59 +02:00
mvn23
fc67f5eef3 Rewrite opentherm_gw to a component (#17133)
* Rewrite opentherm_gw to a component which loads the opentherm_gw climate platform.

* Add OpenTherm Gateway sensor platform.

* Remove library imports from platforms (use hass.data instead)
* Update .coveragerc
* Update docstrings to use new component documentation url

* Add OpenTherm Gateway binary sensor support.
Fix houndci findings.

* Revert "Add OpenTherm Gateway binary sensor support."

This reverts commit 5711dc4c25.

* Revert "Add OpenTherm Gateway sensor platform."

This reverts commit b3505ed561.

* Remove import from platform, use hass.data instead.
Update .coveragerc
Update docstrings
Update requirements_all.txt
General code cleanup

* Fix review findings.
Avoid using hass.data within connect_and_subscribe.
2018-10-09 21:06:24 +02:00
dickesW
2aeb0efc7c Fixed Temperature for HMIP-WeatherStation Plus/Basic (#17216) 2018-10-09 11:19:21 -06:00
Markus Nigbur
a99ba0a1d4 Bumped fints component to version 1.0.1 (#17280) 2018-10-09 11:18:46 -06:00
Paulus Schoutsen
e903f7ffda Manual updates (#17278) 2018-10-09 16:54:38 +02:00
Charles Garwood
cf249e3e5e Z-Wave Config Entry Support (#17119)
* Initial Z-Wave Config Entry Support

* Use conf.get() for config import

* Uncomment test

* Re-add line breaks

* tabs -> space

* Unused import cleanup & lint fixes

* Remove unused config flow link step

* Address comments

* Remove unused import

* Fix tests

* Check for valid usb_path

* Test for Z-Stick in config flow

* Pass config dir to ZWaveOption

* Auto-generate Network Key if none provided

* Test fixes

* Address comments & more start network service registration

* add_executor_job for options.lock()
2018-10-09 16:30:55 +02:00
damarco
5167658a1d Add support for zha custom cluster mappings (#16714)
* Add support for custom cluster mappings

* Refactor sub_component mapping
2018-10-09 12:53:02 +02:00
definitio
6bf3f9e748 Fix mpd timeout error (#17254)
* Increase mpd client timeout

* Update mpd.py
2018-10-09 12:24:39 +02:00
Daniel Perna
9d56730b8d Add optional "all" parameter for groups (#17179)
* Added optional mode parameter

* Cleanup

* Using boolean configuration

* Fix invalid syntax

* Added tests for all-parameter

* Grammar

* Lint

* Docstrings

* Better description
2018-10-09 10:14:55 +02:00
Otto Winter
26cf5acd5b Make async_track_time_change smarter (#17199)
* Make async_track_time_change smarter

* Move to util/dt

* Remove unnecessary check

* Lint

* Remove tzinfo check

* Remove check

* Add comment about async_track_point_in_utc_time

* Fix typing check

* Lint
2018-10-09 10:14:18 +02:00
Otto Winter
9190fe1c21 Add device registry to MQTT fan (#17250) 2018-10-09 10:13:42 +02:00
marcolertora
0c34c50d2f Added lumitek/ankuoo recswitch component (#15764)
* Added lumitek/ankuoo recswitch component

* cosmetics

* remove callback

* cosmetics

* update requirements pyrecswitch==1.0.2

* add in .coveragerc
2018-10-09 10:13:03 +02:00
Sebastian Muszynski
757ba3b60e Add basic support of the Philips Zhirui desk lamp (philips.light.mono1) (#17258) 2018-10-09 10:11:34 +02:00
Sebastian Muszynski
882c4b73ae Fix ambient light state of the Philips Eyecare Lamp (Closes: #16269) (#17259) 2018-10-09 10:11:14 +02:00
Steven Looman
4455a287fc Add defaults, fixing #17229 (#17261) 2018-10-09 10:07:30 +02:00
Anders Melchiorsen
5db7d702c8 Remove warning on script delay (#17264)
* Remove warning on script delay

* Use suppress
2018-10-09 10:06:42 +02:00
Tomas Hellström
540d22d603 Swedish weather institute weather component (#16717)
* SMHI Component

* Clean up typos

* Fixed default values first config to home location (tests will follow)

* Fixed tests and removed unused function

* Minor fixup after comments from @kane610

* add support for precipitation in forecast

* Removed old async_step_init not needed.
2018-10-08 23:54:55 +02:00
Teemu R
56a43436d7 Bump python-miio requirement (#17260) 2018-10-08 22:33:06 +02:00
Nick Horvath
1393766659 Thermoworks Smoke Sensor (#16139)
* Add support for monitoring the temperature of a thermoworks smoke thermometer.

* Use string formatting.

* Add line break.

* Add error handling for authentication.

* Fix linting errors.

* Fix quotes.

* Bump thermoworks_smoke library version.

* Review changes for @MartinHjelmare

* Add unique id attribute and change battery attribute to the standard "battery_level".

* requested changes to snake case and monitored conditions

* fix lint error

* exclude firmware from state attrs. rename original_unit to unit_of_min_max so it's more clear what it is for.

* add device_info

* add regex validator for exclude

* undo device info stuff

* remove serial number from attributes even though other components are allowed to have it...

* exclude firmware
2018-10-08 22:15:38 +02:00
Paulus Schoutsen
68d72931c4 block external IP (#17248)
* block external IP

* Update __init__.py
2018-10-08 20:50:24 +02:00
Jari Ylimäinen
0c0184973b Add configurable temperature step for MQTT climate component (#16201)
* Add configurable temperature step

* Remove temp step from climate component
2018-10-08 20:24:25 +02:00
damarco
59ec469722 Use only_cache parameter in binary_sensor.zha.Remote (#16711) 2018-10-08 20:23:26 +02:00
Paulus Schoutsen
68472b8241 Add a webhook automation trigger (#17246) 2018-10-08 20:16:37 +02:00
Glenn Waters
9380fca97e Add Elk light platform (#17222)
* Add Elk light platform.

* Add ElkM1 light code. Doh.

* Fix PR comments.

* Fix hound errors.

* Fix PR comment.

* Move state from base to class(s) where used
2018-10-08 17:30:27 +02:00
Paulus Schoutsen
c3b1121d77 Add group foundation (#16935)
Add group foundation
2018-10-08 16:35:38 +02:00
Otto Winter
dd55ff87c8 Add device registry to MQTT cover (#17245)
* Add device registry to MQTT cover

* Fix tests
2018-10-08 16:13:44 +02:00
Malte Franken
b637b48bd8 emptying device state attributes if the update from the feed fails (#17249) 2018-10-08 16:13:08 +02:00
emontnemery
42fb886d71 Add support for HS color to mqtt light (#16958)
* Add support for HS color to mqtt light

* Warn if hs state update is invalid
2018-10-08 15:36:57 +02:00
Otto Winter
9290f245bf Convert MQTT fan to config entry (#17247) 2018-10-08 14:59:04 +02:00
Otto Winter
c41ef65da6 Add device registry to MQTT switches (#17244) 2018-10-08 14:57:07 +02:00
Otto Winter
744dd42ad3 Add device registry to MQTT binary sensor (#17243) 2018-10-08 14:44:00 +02:00
Matt Schmitt
27f0331f0f MyQ cover return unknown state if not available (#17207)
* Add additional supported states

* Use get method for lookup

* Return None if unable to get status
2018-10-08 14:19:23 +02:00
Malte Franken
394ffc40b6 updated georss-client library to 0.3 (#17239) 2018-10-08 13:32:16 +02:00
Paulus Schoutsen
d5f5273c31 Guard for bad device info (#17238) 2018-10-08 12:53:51 +02:00
Otto Winter
af2402ea59 Implement base for MQTT device registry integration (#16943)
* Implement base for MQTT device registry integration

* Lint

* Lint

* Address comments

* Lint

* Lint

* Address comments

* Only add keys if specified

See https://github.com/home-assistant/home-assistant/pull/17136#discussion_r223267185
2018-10-08 12:53:30 +02:00
Daniel Lashua
71a0274b12 Add Support for Xiaomi Vibration Sensor (#16422) 2018-10-08 12:08:46 +02:00
Otto Winter
3f498bd042 Fix potential MQTT discovery race condition (#17208)
* Fix potential MQTT discovery race condition

* Rename data key
2018-10-08 10:59:43 +02:00
MatteGary
ee5e1fa355 Daikin Climate - Better integration with Climate base component (#16913)
* Daikin Climate - Better integration with Climate base component

Made some modification in order to better integrate the Daikin AC Component with the base Climate Component.
Benefits are:
Support localization for Operation Mode
Support for Homekit Integration (if the AC is turned On, now the status is updated in Homekit)

* Build bug fixing

* Bug fixing in Set Operation_Mode functionality

Fixed to .title() functionality in matching the Operation_Mode

* Fix useless code

* Fix hound bug

* Bug fixing

Change in list of Operation Mode

* Trailing white space fix

* Compile fixing

* Whitespace fix
2018-10-08 10:49:54 +02:00
William Scanlon
f2d8f3bcb8 Water heater support (#17058)
* Moved econet to water_heater

* Wink and Econet are now in water heater!

* Removed away mode from econet and added demo water heater

* Added demo tests

* Updated coveragerc

* Fix lint issues.

* updated requirements all

* Requirements all actually updated.

* Reset wink and econet and fixed service.

* Reset wink and econet to the correct dev state

* Reset requirements_all and .coveragerc and removed the new econet and wink water_heater files

* Removed @bind_hass service methods

* Actually reset the .coverage file

* Fixed the tests

* Addressed @MartinHjelmare's comments

* Removed unused import

* Switched to async_add_executor_job

* Fixed lint

* Removed is_on

* Added celsius demo water heater and tests.

* Removed metric import
2018-10-08 10:38:07 +02:00
Sebastian Muszynski
ff4204244b Fix data_key and power_consumed attribute of the Aqara Wall Switch (Closes: #16457) (#17235) 2018-10-08 10:37:27 +02:00
Paulus Schoutsen
a54e242245 Fix SPC (#17236) 2018-10-08 10:20:18 +02:00
Oliver
849665b9ca Pushed to version 0.7.6 of denonavr library to add more sound modes (#17227) 2018-10-08 09:32:14 +02:00
Sebastian Muszynski
315f83e1ea Add some new model names of Xiaomi Aqara devices (#17234)
* Add additional model name of the Xiaomi Aqara Button (WXKG11LM)

* Add additional model name of the Xiaomi Aqara Wireless Switch (WXKG02LM, WXKG03LM)

* Bump PyXiaomiGateway version
2018-10-08 09:32:01 +02:00
Paulus Schoutsen
4b7f85518f Prevent accidental device reg override (#17136) 2018-10-08 09:30:40 +02:00
Daniel Høyer Iversen
59d78b060f danielhiversen as codeowner for met.no (#17232) 2018-10-08 08:36:32 +02:00
Martin Berg
e922dd10ba Init sub-components using global var. (#17220) 2018-10-07 23:30:09 +02:00
Paulus Schoutsen
2de1193fd9 Update translations 2018-10-07 23:26:46 +02:00
Paulus Schoutsen
c12bbddc0b Update frontend to 20181007.0 2018-10-07 23:26:23 +02:00
Anders Melchiorsen
086c71525e Add config entry for LIFX (#17201)
* Add config entry for LIFX

* Use list for dependencies

* Obsolete the platform config

* Use DOMAIN

* Use async_create_task
2018-10-07 23:14:53 +02:00
Glenn Waters
06a64c0167 Add support for ElkM1 alarm/automation panel (#16952)
* Add support for ElkM1 alarm/automation panel.

* fix lines too long

* Address PR comments

* Fix hound ci errors

* Changes for PR comments

* Use vol.Range for checking range value

* Address PR comments

* Fix lint errors

* Added elkm1-lib requirement

* Update coverage to exclude elk

* Fix flake8 errors

* Fix flake8 error

* Cleanup config parsing

* Add housecode converter

* fix PR comments

* fix syntax error

* Fix PR comment
2018-10-07 21:45:36 +02:00
Daniel Høyer Iversen
c1ed9edd26 Add forecast for Met.no (#17109)
test met

met no 0.3.0

fix line length

fix line length
2018-10-07 21:00:12 +02:00
emontnemery
1d7d82fde5 Fix aliases support for RFLink sensors (#17190) 2018-10-07 13:14:37 +02:00
Brian Towles
592e1dc96a Enable new registry rename for Insteon (#17171)
* Enable new registry rename for Insteon

* Segment unique_id from name
2018-10-07 13:12:33 +02:00
Anders Melchiorsen
6e81ae096e Disallow list/dict for string configuration (#17202) 2018-10-07 12:35:44 +02:00
Per Sandström
c8266c6692 vsure version 1.5.0 (#17209) 2018-10-07 12:33:16 +02:00
Philip Rosenberg-Watt
8fda705377 Fix Todoist custom project update (#17115)
* Fix Todoist custom project update

Custom projects were not refreshing the API state and were using
local/stored state. This resulted in invalid tasks being retained upon
update. This change resets the local Todoist API state, syncs it, and
then continues normal update operation(s) on the Todoist project data
object.

* Remove blank line after docstring

* Update logging call

* Simplify logging
2018-10-07 01:38:25 +02:00
cdce8p
5d6562a73f Bugfix switch flux - light service call (#17187)
* Bugfix switch flux - light service call

* Change x_val and y_val test
2018-10-06 23:30:07 +02:00
Guy Khmelnitsky
9285831fa1 Upgrade boto3 to 1.9.16 (#17140) 2018-10-06 20:46:20 +02:00
Per Sandström
760047f964 Verisure standard config for scan interval (#17192)
* verisure configurable polling

* fix indentation
2018-10-06 20:03:22 +02:00
Anders Melchiorsen
8683eeb908 Upgrade aiolifx_effects to 0.2.1 (#17188) 2018-10-06 14:32:54 +02:00
Fabian Affolter
75e236de73 Add myself to more sensors (#17185) 2018-10-06 09:54:03 +02:00
Fabian Affolter
169abe637c Update core, add myself and introduce grouping (#17175) 2018-10-06 07:16:06 +02:00
cdce8p
07d90c6c55 Fix device_tracker service call & cleanup (#17173)
* Bugfix group service - device_tracker

* Cleanup
2018-10-05 23:09:55 +02:00
Paulus Schoutsen
a66db59359 Fix data used for logbook (#17172)
* Fix data used for logbook

* Lint
2018-10-05 23:07:27 +02:00
Fabian Affolter
bed1b96f5a Revert "Update core, add myself and introduce grouping"
This reverts commit 13106a9b55.
2018-10-05 21:46:26 +02:00
Fabian Affolter
13106a9b55 Update core, add myself and introduce grouping 2018-10-05 21:45:14 +02:00
kennedyshead
7598067b55 Adding myself as melissa owner (#17157) 2018-10-05 20:48:20 +02:00
Paulus Schoutsen
5e7d4a57a3 Fix incorrect yaml in hangouts (#17169) 2018-10-05 20:21:09 +02:00
Paulus Schoutsen
1061c369f1 Bumped version to 0.81.0.dev0 2018-10-05 19:54:15 +02:00
580 changed files with 20943 additions and 3002 deletions

View File

@@ -102,6 +102,9 @@ omit =
homeassistant/components/egardia.py
homeassistant/components/*/egardia.py
homeassistant/components/elkm1/*
homeassistant/components/*/elkm1.py
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
@@ -245,6 +248,9 @@ omit =
homeassistant/components/opencv.py
homeassistant/components/*/opencv.py
homeassistant/components/opentherm_gw.py
homeassistant/components/*/opentherm_gw.py
homeassistant/components/openuv/__init__.py
homeassistant/components/*/openuv.py
@@ -284,6 +290,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/simplisafe/__init__.py
homeassistant/components/*/simplisafe.py
homeassistant/components/sisyphus.py
homeassistant/components/*/sisyphus.py
@@ -379,7 +388,7 @@ omit =
homeassistant/components/zigbee.py
homeassistant/components/*/zigbee.py
homeassistant/components/zoneminder.py
homeassistant/components/zoneminder/*
homeassistant/components/*/zoneminder.py
homeassistant/components/tuya.py
@@ -395,7 +404,6 @@ omit =
homeassistant/components/alarm_control_panel/ifttt.py
homeassistant/components/alarm_control_panel/manual_mqtt.py
homeassistant/components/alarm_control_panel/nx584.py
homeassistant/components/alarm_control_panel/simplisafe.py
homeassistant/components/alarm_control_panel/totalconnect.py
homeassistant/components/alarm_control_panel/yale_smart_alarm.py
homeassistant/components/apiai.py
@@ -426,7 +434,6 @@ omit =
homeassistant/components/camera/xeoma.py
homeassistant/components/camera/xiaomi.py
homeassistant/components/camera/yi.py
homeassistant/components/climate/econet.py
homeassistant/components/climate/ephember.py
homeassistant/components/climate/eq3btsmart.py
homeassistant/components/climate/flexit.py
@@ -434,8 +441,8 @@ omit =
homeassistant/components/climate/homematic.py
homeassistant/components/climate/honeywell.py
homeassistant/components/climate/knx.py
homeassistant/components/climate/mill.py
homeassistant/components/climate/oem.py
homeassistant/components/climate/opentherm_gw.py
homeassistant/components/climate/proliphix.py
homeassistant/components/climate/radiotherm.py
homeassistant/components/climate/sensibo.py
@@ -451,7 +458,6 @@ 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
@@ -478,6 +484,7 @@ omit =
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/ping.py
homeassistant/components/device_tracker/quantum_gateway.py
homeassistant/components/device_tracker/ritassist.py
homeassistant/components/device_tracker/sky_hub.py
homeassistant/components/device_tracker/snmp.py
@@ -561,6 +568,7 @@ omit =
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/lg_soundbar.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/mediaroom.py
homeassistant/components/media_player/mpchc.py
@@ -604,6 +612,7 @@ omit =
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
homeassistant/components/notify/hipchat.py
homeassistant/components/notify/homematic.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/kodi.py
homeassistant/components/notify/lannouncer.py
@@ -636,6 +645,7 @@ omit =
homeassistant/components/remember_the_milk/__init__.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/route53.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/airvisual.py
@@ -747,6 +757,7 @@ omit =
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/rainbird.py
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/rtorrent.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sense.py
homeassistant/components/sensor/sensehat.py
@@ -776,6 +787,7 @@ omit =
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/thermoworks_smoke.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/trafikverket_weatherstation.py
@@ -816,6 +828,7 @@ omit =
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainbird.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/recswitch.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/snmp.py
homeassistant/components/switch/switchbot.py
@@ -832,6 +845,7 @@ omit =
homeassistant/components/tts/picotts.py
homeassistant/components/vacuum/mqtt.py
homeassistant/components/vacuum/roomba.py
homeassistant/components/water_heater/econet.py
homeassistant/components/watson_iot.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py

View File

@@ -2,59 +2,68 @@
# when the code that they own is touched.
# https://github.com/blog/2392-introducing-code-owners
# Home Assistant Core
setup.py @home-assistant/core
homeassistant/*.py @home-assistant/core
homeassistant/helpers/* @home-assistant/core
homeassistant/util/* @home-assistant/core
homeassistant/components/api.py @home-assistant/core
homeassistant/components/auth/* @home-assistant/core
homeassistant/components/automation/* @home-assistant/core
homeassistant/components/cloud/* @home-assistant/core
homeassistant/components/config/* @home-assistant/core
homeassistant/components/configurator.py @home-assistant/core
homeassistant/components/group.py @home-assistant/core
homeassistant/components/conversation/* @home-assistant/core
homeassistant/components/frontend/* @home-assistant/core
homeassistant/components/group/* @home-assistant/core
homeassistant/components/history.py @home-assistant/core
homeassistant/components/http/* @home-assistant/core
homeassistant/components/input_*.py @home-assistant/core
homeassistant/components/introduction.py @home-assistant/core
homeassistant/components/logger.py @home-assistant/core
homeassistant/components/lovelace/* @home-assistant/core
homeassistant/components/mqtt/* @home-assistant/core
homeassistant/components/panel_custom.py @home-assistant/core
homeassistant/components/panel_iframe.py @home-assistant/core
homeassistant/components/persistent_notification.py @home-assistant/core
homeassistant/components/onboarding/* @home-assistant/core
homeassistant/components/persistent_notification/* @home-assistant/core
homeassistant/components/scene/__init__.py @home-assistant/core
homeassistant/components/scene/hass.py @home-assistant/core
homeassistant/components/script.py @home-assistant/core
homeassistant/components/shell_command.py @home-assistant/core
homeassistant/components/sun.py @home-assistant/core
homeassistant/components/updater.py @home-assistant/core
homeassistant/components/weblink.py @home-assistant/core
homeassistant/components/weblink/* @home-assistant/core
homeassistant/components/websocket_api.py @home-assistant/core
homeassistant/components/zone.py @home-assistant/core
homeassistant/components/zone/* @home-assistant/core
# HomeAssistant developer Teams
# Home Assistant Developer Teams
Dockerfile @home-assistant/docker
virtualization/Docker/* @home-assistant/docker
homeassistant/components/zwave/* @home-assistant/z-wave
homeassistant/components/*/zwave.py @home-assistant/z-wave
homeassistant/components/hassio.py @home-assistant/hassio
homeassistant/components/hassio/* @home-assistant/hassio
# Individual components
# Individual platforms
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
homeassistant/components/alarm_control_panel/simplisafe.py @bachya
homeassistant/components/binary_sensor/hikvision.py @mezz64
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/binary_sensor/threshold.py @fabaff
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
homeassistant/components/climate/eq3btsmart.py @rytilahti
homeassistant/components/climate/mill.py @danielhiversen
homeassistant/components/climate/sensibo.py @andrey-git
homeassistant/components/cover/group.py @cdce8p
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/device_tracker/huawei_router.py @abmantis
homeassistant/components/device_tracker/quantum_gateway.py @cisasteelersfan
homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/lifx.py @amelchio
homeassistant/components/influx.py @fabaff
homeassistant/components/light/lifx_legacy.py @amelchio
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
@@ -65,74 +74,173 @@ homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/liveboxplaytv.py @pschmitt
homeassistant/components/media_player/mediaroom.py @dgomes
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/mpd.py @fabaff
homeassistant/components/media_player/sonos.py @amelchio
homeassistant/components/media_player/xiaomi_tv.py @fattdev
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/no_ip.py @fabaff
homeassistant/components/notify/file.py @fabaff
homeassistant/components/notify/flock.py @fabaff
homeassistant/components/notify/instapush.py @fabaff
homeassistant/components/notify/mastodon.py @fabaff
homeassistant/components/notify/smtp.py @fabaff
homeassistant/components/notify/syslog.py @fabaff
homeassistant/components/notify/xmpp.py @fabaff
homeassistant/components/plant.py @ChristianKuehnel
homeassistant/components/scene/lifx_cloud.py @amelchio
homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/alpha_vantage.py @fabaff
homeassistant/components/sensor/bitcoin.py @fabaff
homeassistant/components/sensor/cpuspeed.py @fabaff
homeassistant/components/sensor/cups.py @fabaff
homeassistant/components/sensor/darksky.py @fabaff
homeassistant/components/sensor/file.py @fabaff
homeassistant/components/sensor/filter.py @dgomes
homeassistant/components/sensor/fixer.py @fabaff
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/gitter.py @fabaff
homeassistant/components/sensor/glances.py @fabaff
homeassistant/components/sensor/gpsd.py @fabaff
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/jewish_calendar.py @tsvi
homeassistant/components/sensor/linux_battery.py @fabaff
homeassistant/components/sensor/luftdaten.py @fabaff
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/min_max.py @fabaff
homeassistant/components/sensor/moon.py @fabaff
homeassistant/components/sensor/netdata.py @fabaff
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
homeassistant/components/sensor/pi_hole.py @fabaff
homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/pvoutput.py @fabaff
homeassistant/components/sensor/qnap.py @colinodell
homeassistant/components/sensor/scrape.py @fabaff
homeassistant/components/sensor/serial.py @fabaff
homeassistant/components/sensor/shodan.py @fabaff
homeassistant/components/sensor/sma.py @kellerza
homeassistant/components/sensor/sql.py @dgomes
homeassistant/components/sensor/statistics.py @fabaff
homeassistant/components/sensor/swiss*.py @fabaff
homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/time_data.py @fabaff
homeassistant/components/sensor/version.py @fabaff
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/sensor/worldclock.py @fabaff
homeassistant/components/shiftr.py @fabaff
homeassistant/components/spaceapi.py @fabaff
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/weather/__init__.py @fabaff
homeassistant/components/weather/darksky.py @fabaff
homeassistant/components/weather/demo.py @fabaff
homeassistant/components/weather/met.py @danielhiversen
homeassistant/components/weather/openweathermap.py @fabaff
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
# A
homeassistant/components/arduino.py @fabaff
homeassistant/components/*/arduino.py @fabaff
homeassistant/components/*/arest.py @fabaff
homeassistant/components/*/axis.py @kane610
# B
homeassistant/components/blink/* @fronzbot
homeassistant/components/*/blink.py @fronzbot
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen
# C
homeassistant/components/counter/* @fabaff
# D
homeassistant/components/*/deconz.py @kane610
homeassistant/components/digital_ocean.py @fabaff
homeassistant/components/*/digital_ocean.py @fabaff
homeassistant/components/dweet.py @fabaff
homeassistant/components/*/dweet.py @fabaff
# E
homeassistant/components/ecovacs.py @OverloadUT
homeassistant/components/*/ecovacs.py @OverloadUT
homeassistant/components/edp_redy.py @abmantis
homeassistant/components/*/edp_redy.py @abmantis
homeassistant/components/edp_redy.py @abmantis
homeassistant/components/eight_sleep.py @mezz64
homeassistant/components/*/eight_sleep.py @mezz64
# H
homeassistant/components/hive.py @Rendili @KJonline
homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/homekit/* @cdce8p
homeassistant/components/huawei_lte.py @scop
homeassistant/components/*/huawei_lte.py @scop
# K
homeassistant/components/knx.py @Julius2342
homeassistant/components/*/knx.py @Julius2342
homeassistant/components/konnected.py @heythisisnate
homeassistant/components/*/konnected.py @heythisisnate
# L
homeassistant/components/lifx.py @amelchio
homeassistant/components/*/lifx.py @amelchio
# M
homeassistant/components/matrix.py @tinloaf
homeassistant/components/*/matrix.py @tinloaf
homeassistant/components/melissa.py @kennedyshead
homeassistant/components/*/melissa.py @kennedyshead
homeassistant/components/*/mystrom.py @fabaff
# O
homeassistant/components/openuv/* @bachya
homeassistant/components/*/openuv.py @bachya
# Q
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
# R
homeassistant/components/rainmachine/* @bachya
homeassistant/components/*/rainmachine.py @bachya
homeassistant/components/*/random.py @fabaff
homeassistant/components/*/rfxtrx.py @danielhiversen
# S
homeassistant/components/simplisafe/* @bachya
homeassistant/components/*/simplisafe.py @bachya
# T
homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/thethingsnetwork.py @fabaff
homeassistant/components/*/thethingsnetwork.py @fabaff
homeassistant/components/tibber/* @danielhiversen
homeassistant/components/*/tibber.py @danielhiversen
homeassistant/components/tradfri/* @ggravlingen
homeassistant/components/*/tradfri.py @ggravlingen
# U
homeassistant/components/unifi.py @kane610
homeassistant/components/switch/unifi.py @kane610
homeassistant/components/upcloud.py @scop
homeassistant/components/*/upcloud.py @scop
# V
homeassistant/components/velux.py @Julius2342
homeassistant/components/*/velux.py @Julius2342
# X
homeassistant/components/*/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/xiaomi_miio.py @rytilahti @syssi
homeassistant/components/zoneminder.py @rohankapoorcom
# Z
homeassistant/components/zoneminder/ @rohankapoorcom
homeassistant/components/*/zoneminder.py @rohankapoorcom
# Other code
homeassistant/scripts/check_config.py @kellerza

View File

@@ -16,6 +16,9 @@ from . import auth_store, models
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
from .providers import auth_provider_from_config, AuthProvider, LoginFlow
EVENT_USER_ADDED = 'user_added'
EVENT_USER_REMOVED = 'user_removed'
_LOGGER = logging.getLogger(__name__)
_MfaModuleDict = Dict[str, MultiFactorAuthModule]
_ProviderKey = Tuple[str, Optional[str]]
@@ -126,23 +129,38 @@ class AuthManager:
async def async_create_system_user(self, name: str) -> models.User:
"""Create a system user."""
return await self._store.async_create_user(
user = await self._store.async_create_user(
name=name,
system_generated=True,
is_active=True,
groups=[],
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {
'user_id': user.id
})
return user
async def async_create_user(self, name: str) -> models.User:
"""Create a user."""
group = (await self._store.async_get_groups())[0]
kwargs = {
'name': name,
'is_active': True,
'groups': [group]
} # type: Dict[str, Any]
if await self._user_should_be_owner():
kwargs['is_owner'] = True
return await self._store.async_create_user(**kwargs)
user = await self._store.async_create_user(**kwargs)
self.hass.bus.async_fire(EVENT_USER_ADDED, {
'user_id': user.id
})
return user
async def async_get_or_create_user(self, credentials: models.Credentials) \
-> models.User:
@@ -162,12 +180,18 @@ class AuthManager:
info = await auth_provider.async_user_meta_for_credentials(
credentials)
return await self._store.async_create_user(
user = await self._store.async_create_user(
credentials=credentials,
name=info.name,
is_active=info.is_active,
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {
'user_id': user.id
})
return user
async def async_link_user(self, user: models.User,
credentials: models.Credentials) -> None:
"""Link credentials to an existing user."""
@@ -185,6 +209,10 @@ class AuthManager:
await self._store.async_remove_user(user)
self.hass.bus.async_fire(EVENT_USER_REMOVED, {
'user_id': user.id
})
async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
await self._store.async_activate_user(user)

View File

@@ -10,9 +10,11 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util
from . import models
from .permissions import DEFAULT_POLICY
STORAGE_VERSION = 1
STORAGE_KEY = 'auth'
INITIAL_GROUP_NAME = 'All Access'
class AuthStore:
@@ -28,9 +30,18 @@ class AuthStore:
"""Initialize the auth store."""
self.hass = hass
self._users = None # type: Optional[Dict[str, models.User]]
self._groups = None # type: Optional[Dict[str, models.Group]]
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
private=True)
async def async_get_groups(self) -> List[models.Group]:
"""Retrieve all users."""
if self._groups is None:
await self._async_load()
assert self._groups is not None
return list(self._groups.values())
async def async_get_users(self) -> List[models.User]:
"""Retrieve all users."""
if self._users is None:
@@ -51,14 +62,20 @@ class AuthStore:
self, name: Optional[str], is_owner: Optional[bool] = None,
is_active: Optional[bool] = None,
system_generated: Optional[bool] = None,
credentials: Optional[models.Credentials] = None) -> models.User:
credentials: Optional[models.Credentials] = None,
groups: Optional[List[models.Group]] = None) -> models.User:
"""Create a new user."""
if self._users is None:
await self._async_load()
assert self._users is not None
assert self._users is not None
assert self._groups is not None
kwargs = {
'name': name
'name': name,
# Until we get group management, we just put everyone in the
# same group.
'groups': groups or [],
} # type: Dict[str, Any]
if is_owner is not None:
@@ -219,19 +236,40 @@ class AuthStore:
return
users = OrderedDict() # type: Dict[str, models.User]
groups = OrderedDict() # type: Dict[str, models.Group]
# When creating objects we mention each attribute explicetely. This
# prevents crashing if user rolls back HA version after a new property
# was added.
for group_dict in data.get('groups', []):
groups[group_dict['id']] = models.Group(
name=group_dict['name'],
id=group_dict['id'],
policy=group_dict.get('policy', DEFAULT_POLICY),
)
migrate_group = None
if not groups:
migrate_group = models.Group(
name=INITIAL_GROUP_NAME,
policy=DEFAULT_POLICY
)
groups[migrate_group.id] = migrate_group
for user_dict in data['users']:
users[user_dict['id']] = models.User(
name=user_dict['name'],
groups=[groups[group_id] for group_id
in user_dict.get('group_ids', [])],
id=user_dict['id'],
is_owner=user_dict['is_owner'],
is_active=user_dict['is_active'],
system_generated=user_dict['system_generated'],
)
if migrate_group is not None and not user_dict['system_generated']:
users[user_dict['id']].groups = [migrate_group]
for cred_dict in data['credentials']:
users[cred_dict['user_id']].credentials.append(models.Credentials(
@@ -286,6 +324,7 @@ class AuthStore:
)
users[rt_dict['user_id']].refresh_tokens[token.id] = token
self._groups = groups
self._users = users
@callback
@@ -300,10 +339,12 @@ class AuthStore:
def _data_to_save(self) -> Dict:
"""Return the data to store."""
assert self._users is not None
assert self._groups is not None
users = [
{
'id': user.id,
'group_ids': [group.id for group in user.groups],
'is_owner': user.is_owner,
'is_active': user.is_active,
'name': user.name,
@@ -312,6 +353,18 @@ class AuthStore:
for user in self._users.values()
]
groups = []
for group in self._groups.values():
g_dict = {
'name': group.name,
'id': group.id,
} # type: Dict[str, Any]
if group.policy is not DEFAULT_POLICY:
g_dict['policy'] = group.policy
groups.append(g_dict)
credentials = [
{
'id': credential.id,
@@ -348,6 +401,7 @@ class AuthStore:
return {
'users': users,
'groups': groups,
'credentials': credentials,
'refresh_tokens': refresh_tokens,
}
@@ -355,3 +409,14 @@ class AuthStore:
def _set_defaults(self) -> None:
"""Set default values for auth store."""
self._users = OrderedDict() # type: Dict[str, models.User]
# Add default group
all_access_group = models.Group(
name=INITIAL_GROUP_NAME,
policy=DEFAULT_POLICY,
)
groups = OrderedDict() # type: Dict[str, models.Group]
groups[all_access_group.id] = all_access_group
self._groups = groups

View File

@@ -7,6 +7,7 @@ import attr
from homeassistant.util import dt as dt_util
from . import permissions as perm_mdl
from .util import generate_secret
TOKEN_TYPE_NORMAL = 'normal'
@@ -14,6 +15,15 @@ TOKEN_TYPE_SYSTEM = 'system'
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = 'long_lived_access_token'
@attr.s(slots=True)
class Group:
"""A group."""
name = attr.ib(type=str) # type: Optional[str]
policy = attr.ib(type=perm_mdl.PolicyType)
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
@attr.s(slots=True)
class User:
"""A user."""
@@ -24,6 +34,8 @@ class User:
is_active = attr.ib(type=bool, default=False)
system_generated = attr.ib(type=bool, default=False)
groups = attr.ib(type=List, factory=list, cmp=False) # type: List[Group]
# List of credentials of a user.
credentials = attr.ib(
type=list, factory=list, cmp=False
@@ -34,6 +46,28 @@ class User:
type=dict, factory=dict, cmp=False
) # type: Dict[str, RefreshToken]
_permissions = attr.ib(
type=perm_mdl.PolicyPermissions,
init=False,
cmp=False,
default=None,
)
@property
def permissions(self) -> perm_mdl.AbstractPermissions:
"""Return permissions object for user."""
if self.is_owner:
return perm_mdl.OwnerPermissions
if self._permissions is not None:
return self._permissions
self._permissions = perm_mdl.PolicyPermissions(
perm_mdl.merge_policies([
group.policy for group in self.groups]))
return self._permissions
@attr.s(slots=True)
class RefreshToken:

View File

@@ -0,0 +1,252 @@
"""Permissions for Home Assistant."""
from typing import ( # noqa: F401
cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union)
import voluptuous as vol
from homeassistant.core import State
CategoryType = Union[Mapping[str, 'CategoryType'], bool, None]
PolicyType = Mapping[str, CategoryType]
# Default policy if group has no policy applied.
DEFAULT_POLICY = {
"entities": True
} # type: PolicyType
CAT_ENTITIES = 'entities'
ENTITY_DOMAINS = 'domains'
ENTITY_ENTITY_IDS = 'entity_ids'
VALUES_SCHEMA = vol.Any(True, vol.Schema({
str: True
}))
ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({
vol.Optional(ENTITY_DOMAINS): VALUES_SCHEMA,
vol.Optional(ENTITY_ENTITY_IDS): VALUES_SCHEMA,
}))
POLICY_SCHEMA = vol.Schema({
vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA
})
class AbstractPermissions:
"""Default permissions class."""
def check_entity(self, entity_id: str, *keys: str) -> bool:
"""Test if we can access entity."""
raise NotImplementedError
def filter_states(self, states: List[State]) -> List[State]:
"""Filter a list of states for what the user is allowed to see."""
raise NotImplementedError
class PolicyPermissions(AbstractPermissions):
"""Handle permissions."""
def __init__(self, policy: PolicyType) -> None:
"""Initialize the permission class."""
self._policy = policy
self._compiled = {} # type: Dict[str, Callable[..., bool]]
def check_entity(self, entity_id: str, *keys: str) -> bool:
"""Test if we can access entity."""
func = self._policy_func(CAT_ENTITIES, _compile_entities)
return func(entity_id, keys)
def filter_states(self, states: List[State]) -> List[State]:
"""Filter a list of states for what the user is allowed to see."""
func = self._policy_func(CAT_ENTITIES, _compile_entities)
keys = ('read',)
return [entity for entity in states if func(entity.entity_id, keys)]
def _policy_func(self, category: str,
compile_func: Callable[[CategoryType], Callable]) \
-> Callable[..., bool]:
"""Get a policy function."""
func = self._compiled.get(category)
if func:
return func
func = self._compiled[category] = compile_func(
self._policy.get(category))
return func
def __eq__(self, other: Any) -> bool:
"""Equals check."""
# pylint: disable=protected-access
return (isinstance(other, PolicyPermissions) and
other._policy == self._policy)
class _OwnerPermissions(AbstractPermissions):
"""Owner permissions."""
# pylint: disable=no-self-use
def check_entity(self, entity_id: str, *keys: str) -> bool:
"""Test if we can access entity."""
return True
def filter_states(self, states: List[State]) -> List[State]:
"""Filter a list of states for what the user is allowed to see."""
return states
OwnerPermissions = _OwnerPermissions() # pylint: disable=invalid-name
def _compile_entities(policy: CategoryType) \
-> Callable[[str, Tuple[str]], bool]:
"""Compile policy into a function that tests policy."""
# None, Empty Dict, False
if not policy:
def apply_policy_deny_all(entity_id: str, keys: Tuple[str]) -> bool:
"""Decline all."""
return False
return apply_policy_deny_all
if policy is True:
def apply_policy_allow_all(entity_id: str, keys: Tuple[str]) -> bool:
"""Approve all."""
return True
return apply_policy_allow_all
assert isinstance(policy, dict)
domains = policy.get(ENTITY_DOMAINS)
entity_ids = policy.get(ENTITY_ENTITY_IDS)
funcs = [] # type: List[Callable[[str, Tuple[str]], Union[None, bool]]]
# The order of these functions matter. The more precise are at the top.
# If a function returns None, they cannot handle it.
# If a function returns a boolean, that's the result to return.
# Setting entity_ids to a boolean is final decision for permissions
# So return right away.
if isinstance(entity_ids, bool):
def apply_entity_id_policy(entity_id: str, keys: Tuple[str]) -> bool:
"""Test if allowed entity_id."""
return entity_ids # type: ignore
return apply_entity_id_policy
if entity_ids is not None:
def allowed_entity_id(entity_id: str, keys: Tuple[str]) \
-> Union[None, bool]:
"""Test if allowed entity_id."""
return entity_ids.get(entity_id) # type: ignore
funcs.append(allowed_entity_id)
if isinstance(domains, bool):
def allowed_domain(entity_id: str, keys: Tuple[str]) \
-> Union[None, bool]:
"""Test if allowed domain."""
return domains
funcs.append(allowed_domain)
elif domains is not None:
def allowed_domain(entity_id: str, keys: Tuple[str]) \
-> Union[None, bool]:
"""Test if allowed domain."""
domain = entity_id.split(".", 1)[0]
return domains.get(domain) # type: ignore
funcs.append(allowed_domain)
# Can happen if no valid subcategories specified
if not funcs:
def apply_policy_deny_all_2(entity_id: str, keys: Tuple[str]) -> bool:
"""Decline all."""
return False
return apply_policy_deny_all_2
if len(funcs) == 1:
func = funcs[0]
def apply_policy_func(entity_id: str, keys: Tuple[str]) -> bool:
"""Apply a single policy function."""
return func(entity_id, keys) is True
return apply_policy_func
def apply_policy_funcs(entity_id: str, keys: Tuple[str]) -> bool:
"""Apply several policy functions."""
for func in funcs:
result = func(entity_id, keys)
if result is not None:
return result
return False
return apply_policy_funcs
def merge_policies(policies: List[PolicyType]) -> PolicyType:
"""Merge policies."""
new_policy = {} # type: Dict[str, CategoryType]
seen = set() # type: Set[str]
for policy in policies:
for category in policy:
if category in seen:
continue
seen.add(category)
new_policy[category] = _merge_policies([
policy.get(category) for policy in policies])
cast(PolicyType, new_policy)
return new_policy
def _merge_policies(sources: List[CategoryType]) -> CategoryType:
"""Merge a policy."""
# When merging policies, the most permissive wins.
# This means we order it like this:
# True > Dict > None
#
# True: allow everything
# Dict: specify more granular permissions
# None: no opinion
#
# If there are multiple sources with a dict as policy, we recursively
# merge each key in the source.
policy = None # type: CategoryType
seen = set() # type: Set[str]
for source in sources:
if source is None:
continue
# A source that's True will always win. Shortcut return.
if source is True:
return True
assert isinstance(source, dict)
if policy is None:
policy = {}
assert isinstance(policy, dict)
for key in source:
if key in seen:
continue
seen.add(key)
key_sources = []
for src in sources:
if isinstance(src, dict):
key_sources.append(src.get(key))
policy[key] = _merge_policies(key_sources)
return policy

View File

@@ -12,6 +12,8 @@ import itertools as it
import logging
from typing import Awaitable
import voluptuous as vol
import homeassistant.core as ha
import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError
@@ -21,11 +23,16 @@ from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
RESTART_EXIT_CODE)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config'
SERVICE_CHECK_CONFIG = 'check_config'
SERVICE_UPDATE_ENTITY = 'update_entity'
SCHEMA_UPDATE_ENTITY = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids
})
def is_on(hass, entity_id=None):
@@ -133,12 +140,23 @@ async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
if call.service == SERVICE_HOMEASSISTANT_RESTART:
hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE))
async def async_handle_update_service(call):
"""Service handler for updating an entity."""
tasks = [hass.helpers.entity_component.async_update_entity(entity)
for entity in call.data[ATTR_ENTITY_ID]]
if tasks:
await asyncio.wait(tasks)
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
hass.services.async_register(
ha.DOMAIN, SERVICE_UPDATE_ENTITY, async_handle_update_service,
schema=SCHEMA_UPDATE_ENTITY)
async def async_handle_reload_config(call):
"""Service handler for reloading core config."""

View File

@@ -18,7 +18,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['abodepy==0.13.1']
REQUIREMENTS = ['abodepy==0.14.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -43,6 +43,11 @@ class BlinkSyncModule(AlarmControlPanel):
self._name = name
self._state = None
@property
def unique_id(self):
"""Return the unique id for the sync module."""
return self.sync.serial
@property
def icon(self):
"""Return icon."""
@@ -61,9 +66,10 @@ class BlinkSyncModule(AlarmControlPanel):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION,
}
attr = self.sync.attributes
attr['network_info'] = self.data.networks
attr[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION
return attr
def update(self):
"""Update the state of the device."""

View File

@@ -0,0 +1,204 @@
"""
Each ElkM1 area will be created as a separate alarm_control_panel in HASS.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.elkm1/
"""
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.const import (
ATTR_CODE, ATTR_ENTITY_ID, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMING, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED)
from homeassistant.components.elkm1 import (
DOMAIN as ELK_DOMAIN, create_elk_entities, ElkEntity)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
DEPENDENCIES = [ELK_DOMAIN]
SIGNAL_ARM_ENTITY = 'elkm1_arm'
SIGNAL_DISPLAY_MESSAGE = 'elkm1_display_message'
ELK_ALARM_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID, default=[]): cv.entity_ids,
vol.Required(ATTR_CODE): vol.All(vol.Coerce(int), vol.Range(0, 999999)),
})
DISPLAY_MESSAGE_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID, default=[]): cv.entity_ids,
vol.Optional('clear', default=2): vol.In([0, 1, 2]),
vol.Optional('beep', default=False): cv.boolean,
vol.Optional('timeout', default=0): vol.Range(min=0, max=65535),
vol.Optional('line1', default=''): cv.string,
vol.Optional('line2', default=''): cv.string,
})
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the ElkM1 alarm platform."""
if discovery_info is None:
return
elk = hass.data[ELK_DOMAIN]['elk']
entities = create_elk_entities(hass, elk.areas, 'area', ElkArea, [])
async_add_entities(entities, True)
def _dispatch(signal, entity_ids, *args):
for entity_id in entity_ids:
async_dispatcher_send(
hass, '{}_{}'.format(signal, entity_id), *args)
def _arm_service(service):
entity_ids = service.data.get(ATTR_ENTITY_ID, [])
arm_level = _arm_services().get(service.service)
args = (arm_level, service.data.get(ATTR_CODE))
_dispatch(SIGNAL_ARM_ENTITY, entity_ids, *args)
for service in _arm_services():
hass.services.async_register(
alarm.DOMAIN, service, _arm_service, ELK_ALARM_SERVICE_SCHEMA)
def _display_message_service(service):
entity_ids = service.data.get(ATTR_ENTITY_ID, [])
data = service.data
args = (data['clear'], data['beep'], data['timeout'],
data['line1'], data['line2'])
_dispatch(SIGNAL_DISPLAY_MESSAGE, entity_ids, *args)
hass.services.async_register(
alarm.DOMAIN, 'elkm1_alarm_display_message',
_display_message_service, DISPLAY_MESSAGE_SERVICE_SCHEMA)
def _arm_services():
from elkm1_lib.const import ArmLevel
return {
'elkm1_alarm_arm_vacation': ArmLevel.ARMED_VACATION.value,
'elkm1_alarm_arm_home_instant': ArmLevel.ARMED_STAY_INSTANT.value,
'elkm1_alarm_arm_night_instant': ArmLevel.ARMED_NIGHT_INSTANT.value,
}
class ElkArea(ElkEntity, alarm.AlarmControlPanel):
"""Representation of an Area / Partition within the ElkM1 alarm panel."""
def __init__(self, element, elk, elk_data):
"""Initialize Area as Alarm Control Panel."""
super().__init__(element, elk, elk_data)
self._changed_by_entity_id = ''
self._state = None
async def async_added_to_hass(self):
"""Register callback for ElkM1 changes."""
await super().async_added_to_hass()
for keypad in self._elk.keypads:
keypad.add_callback(self._watch_keypad)
async_dispatcher_connect(
self.hass, '{}_{}'.format(SIGNAL_ARM_ENTITY, self.entity_id),
self._arm_service)
async_dispatcher_connect(
self.hass, '{}_{}'.format(SIGNAL_DISPLAY_MESSAGE, self.entity_id),
self._display_message)
def _watch_keypad(self, keypad, changeset):
if keypad.area != self._element.index:
return
if changeset.get('last_user') is not None:
self._changed_by_entity_id = self.hass.data[
ELK_DOMAIN]['keypads'].get(keypad.index, '')
self.async_schedule_update_ha_state(True)
@property
def code_format(self):
"""Return the alarm code format."""
return '^[0-9]{4}([0-9]{2})?$'
@property
def state(self):
"""Return the state of the element."""
return self._state
@property
def device_state_attributes(self):
"""Attributes of the area."""
from elkm1_lib.const import AlarmState, ArmedStatus, ArmUpState
attrs = self.initial_attrs()
elmt = self._element
attrs['is_exit'] = elmt.is_exit
attrs['timer1'] = elmt.timer1
attrs['timer2'] = elmt.timer2
if elmt.armed_status is not None:
attrs['armed_status'] = \
ArmedStatus(elmt.armed_status).name.lower()
if elmt.arm_up_state is not None:
attrs['arm_up_state'] = ArmUpState(elmt.arm_up_state).name.lower()
if elmt.alarm_state is not None:
attrs['alarm_state'] = AlarmState(elmt.alarm_state).name.lower()
attrs['changed_by_entity_id'] = self._changed_by_entity_id
return attrs
def _element_changed(self, element, changeset):
from elkm1_lib.const import ArmedStatus
elk_state_to_hass_state = {
ArmedStatus.DISARMED.value: STATE_ALARM_DISARMED,
ArmedStatus.ARMED_AWAY.value: STATE_ALARM_ARMED_AWAY,
ArmedStatus.ARMED_STAY.value: STATE_ALARM_ARMED_HOME,
ArmedStatus.ARMED_STAY_INSTANT.value: STATE_ALARM_ARMED_HOME,
ArmedStatus.ARMED_TO_NIGHT.value: STATE_ALARM_ARMED_NIGHT,
ArmedStatus.ARMED_TO_NIGHT_INSTANT.value: STATE_ALARM_ARMED_NIGHT,
ArmedStatus.ARMED_TO_VACATION.value: STATE_ALARM_ARMED_AWAY,
}
if self._element.alarm_state is None:
self._state = None
elif self._area_is_in_alarm_state():
self._state = STATE_ALARM_TRIGGERED
elif self._entry_exit_timer_is_running():
self._state = STATE_ALARM_ARMING \
if self._element.is_exit else STATE_ALARM_PENDING
else:
self._state = elk_state_to_hass_state[self._element.armed_status]
def _entry_exit_timer_is_running(self):
return self._element.timer1 > 0 or self._element.timer2 > 0
def _area_is_in_alarm_state(self):
from elkm1_lib.const import AlarmState
return self._element.alarm_state >= AlarmState.FIRE_ALARM.value
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
self._element.disarm(int(code))
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
from elkm1_lib.const import ArmLevel
self._element.arm(ArmLevel.ARMED_STAY.value, int(code))
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
from elkm1_lib.const import ArmLevel
self._element.arm(ArmLevel.ARMED_AWAY.value, int(code))
async def async_alarm_arm_night(self, code=None):
"""Send arm night command."""
from elkm1_lib.const import ArmLevel
self._element.arm(ArmLevel.ARMED_NIGHT.value, int(code))
async def _arm_service(self, arm_level, code):
self._element.arm(arm_level, code)
async def _display_message(self, clear, beep, timeout, line1, line2):
"""Display a message on all keypads for the area."""
self._element.display_message(clear, beep, timeout, line1, line2)

View File

@@ -79,3 +79,55 @@ ifttt_push_alarm_state:
state:
description: The state to which the alarm control panel has to be set.
example: 'armed_night'
elkm1_alarm_arm_vacation:
description: Arm the ElkM1 in vacation mode.
fields:
entity_id:
description: Name of alarm control panel to arm.
example: 'alarm_control_panel.main'
code:
description: An code to arm the alarm control panel.
example: 1234
elkm1_alarm_arm_home_instant:
description: Arm the ElkM1 in home instant mode.
fields:
entity_id:
description: Name of alarm control panel to arm.
example: 'alarm_control_panel.main'
code:
description: An code to arm the alarm control panel.
example: 1234
elkm1_alarm_arm_night_instant:
description: Arm the ElkM1 in night instant mode.
fields:
entity_id:
description: Name of alarm control panel to arm.
example: 'alarm_control_panel.main'
code:
description: An code to arm the alarm control panel.
example: 1234
elkm1_alarm_display_message:
description: Display a message on all of the ElkM1 keypads for an area.
fields:
entity_id:
description: Name of alarm control panel to display messages on.
example: 'alarm_control_panel.main'
clear:
description: 0=clear message, 1=clear message with * key, 2=Display until timeout; default 2
example: 1
beep:
description: 0=no beep, 1=beep; default 0
example: 1
timeout:
description: Time to display message, 0=forever, max 65535, default 0
example: 4242
line1:
description: Up to 16 characters of text (truncated if too long). Default blank.
example: The answer to life,
line2:
description: Up to 16 characters of text (truncated if too long). Default blank.
example: the universe, and everything.

View File

@@ -1,5 +1,5 @@
"""
Interfaces with SimpliSafe alarm control panel.
This platform provides alarm control functionality for SimpliSafe.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.simplisafe/
@@ -7,86 +7,44 @@ https://home-assistant.io/components/alarm_control_panel.simplisafe/
import logging
import re
import voluptuous as vol
from homeassistant.components.alarm_control_panel import (
PLATFORM_SCHEMA, AlarmControlPanel)
from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.components.simplisafe.const import (
DATA_CLIENT, DOMAIN, TOPIC_UPDATE)
from homeassistant.const import (
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['simplisafe-python==3.1.2']
CONF_CODE, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
ATTR_ALARM_ACTIVE = "alarm_active"
ATTR_TEMPERATURE = "temperature"
DATA_FILE = '.simplisafe'
DEFAULT_NAME = 'SimpliSafe'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_CODE): cv.string,
})
ATTR_ALARM_ACTIVE = 'alarm_active'
ATTR_TEMPERATURE = 'temperature'
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the SimpliSafe platform."""
from simplipy import API
from simplipy.errors import SimplipyError
"""Set up a SimpliSafe alarm control panel based on existing config."""
pass
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
websession = aiohttp_client.async_get_clientsession(hass)
config_data = await hass.async_add_executor_job(
load_json, hass.config.path(DATA_FILE))
try:
if config_data:
try:
simplisafe = await API.login_via_token(
config_data['refresh_token'], websession)
_LOGGER.debug('Logging in with refresh token')
except SimplipyError:
_LOGGER.info('Refresh token expired; attempting credentials')
simplisafe = await API.login_via_credentials(
username, password, websession)
else:
simplisafe = await API.login_via_credentials(
username, password, websession)
_LOGGER.debug('Logging in with credentials')
except SimplipyError as err:
_LOGGER.error("There was an error during setup: %s", err)
return
config_data = {'refresh_token': simplisafe.refresh_token}
await hass.async_add_executor_job(
save_json, hass.config.path(DATA_FILE), config_data)
systems = await simplisafe.get_systems()
async_add_entities(
[SimpliSafeAlarm(system, name, code) for system in systems], True)
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up a SimpliSafe alarm control panel based on a config entry."""
systems = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id]
async_add_entities([
SimpliSafeAlarm(system, entry.data.get(CONF_CODE))
for system in systems
], True)
class SimpliSafeAlarm(AlarmControlPanel):
"""Representation of a SimpliSafe alarm."""
def __init__(self, system, name, code):
def __init__(self, system, code):
"""Initialize the SimpliSafe alarm."""
self._async_unsub_dispatcher_connect = None
self._attrs = {}
self._code = str(code) if code else None
self._name = name
self._code = code
self._system = system
self._state = None
@@ -98,9 +56,7 @@ class SimpliSafeAlarm(AlarmControlPanel):
@property
def name(self):
"""Return the name of the device."""
if self._name:
return self._name
return 'Alarm {}'.format(self._system.system_id)
return self._system.address
@property
def code_format(self):
@@ -128,6 +84,21 @@ class SimpliSafeAlarm(AlarmControlPanel):
_LOGGER.warning("Wrong code entered for %s", state)
return check
async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, update)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect dispatcher listener when removed."""
if self._async_unsub_dispatcher_connect:
self._async_unsub_dispatcher_connect()
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if not self._validate_code(code, 'disarming'):
@@ -151,22 +122,24 @@ class SimpliSafeAlarm(AlarmControlPanel):
async def async_update(self):
"""Update alarm status."""
await self._system.update()
from simplipy.system import SystemStates
if self._system.state == self._system.SystemStates.off:
self._state = STATE_ALARM_DISARMED
elif self._system.state in (
self._system.SystemStates.home,
self._system.SystemStates.home_count):
self._state = STATE_ALARM_ARMED_HOME
elif self._system.state in (
self._system.SystemStates.away,
self._system.SystemStates.away_count,
self._system.SystemStates.exit_delay):
self._state = STATE_ALARM_ARMED_AWAY
else:
self._state = None
await self._system.update()
self._attrs[ATTR_ALARM_ACTIVE] = self._system.alarm_going_off
if self._system.temperature:
self._attrs[ATTR_TEMPERATURE] = self._system.temperature
if self._system.state == SystemStates.error:
return
if self._system.state == SystemStates.off:
self._state = STATE_ALARM_DISARMED
elif self._system.state in (SystemStates.home,
SystemStates.home_count):
self._state = STATE_ALARM_ARMED_HOME
elif self._system.state in (SystemStates.away, SystemStates.away_count,
SystemStates.exit_delay):
self._state = STATE_ALARM_ARMED_AWAY
else:
self._state = None

View File

@@ -85,19 +85,23 @@ class SpcAlarm(alarm.AlarmControlPanel):
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.UNSET)
await self._api.change_mode(area=self._area,
new_mode=AreaMode.UNSET)
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.PART_SET_A)
await self._api.change_mode(area=self._area,
new_mode=AreaMode.PART_SET_A)
async def async_alarm_arm_night(self, code=None):
"""Send arm home command."""
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.PART_SET_B)
await self._api.change_mode(area=self._area,
new_mode=AreaMode.PART_SET_B)
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
from pyspcwebgw.const import AreaMode
self._api.change_mode(area=self._area, new_mode=AreaMode.FULL_SET)
await self._api.change_mode(area=self._area,
new_mode=AreaMode.FULL_SET)

View File

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

View File

@@ -49,10 +49,9 @@ def setup(hass, config):
# It doesn't really matter why we're not able to get the status, just that
# we can't.
# pylint: disable=broad-except
try:
DATA.update(no_throttle=True)
except Exception:
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Failure while testing APCUPSd status retrieval.")
return False
return True

View File

@@ -141,6 +141,8 @@ class APIEventStream(HomeAssistantView):
_LOGGER.debug("STREAM %s RESPONSE CLOSED", id(stop_obj))
unsub_stream()
return response
class APIConfigView(HomeAssistantView):
"""View to handle Configuration requests."""

View File

@@ -163,7 +163,7 @@ async def async_setup(hass, config):
async def atv_discovered(service, info):
"""Set up an Apple TV that was auto discovered."""
await _setup_atv(hass, {
await _setup_atv(hass, config, {
CONF_NAME: info['name'],
CONF_HOST: info['host'],
CONF_LOGIN_ID: info['properties']['hG'],
@@ -172,7 +172,7 @@ async def async_setup(hass, config):
discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered)
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
tasks = [_setup_atv(hass, config, conf) for conf in config.get(DOMAIN, [])]
if tasks:
await asyncio.wait(tasks, loop=hass.loop)
@@ -187,7 +187,7 @@ async def async_setup(hass, config):
return True
async def _setup_atv(hass, atv_config):
async def _setup_atv(hass, hass_config, atv_config):
"""Set up an Apple TV."""
import pyatv
name = atv_config.get(CONF_NAME)
@@ -212,10 +212,10 @@ async def _setup_atv(hass, atv_config):
}
hass.async_create_task(discovery.async_load_platform(
hass, 'media_player', DOMAIN, atv_config))
hass, 'media_player', DOMAIN, atv_config, hass_config))
hass.async_create_task(discovery.async_load_platform(
hass, 'remote', DOMAIN, atv_config))
hass, 'remote', DOMAIN, atv_config, hass_config))
class AppleTVPowerManager:

View File

@@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
REQUIREMENTS = ['pyarlo==0.2.0']
REQUIREMENTS = ['pyarlo==0.2.2']
_LOGGER = logging.getLogger(__name__)
@@ -81,7 +81,7 @@ def setup(hass, config):
def hub_refresh(event_time):
"""Call ArloHub to refresh information."""
_LOGGER.info("Updating Arlo Hub component")
_LOGGER.debug("Updating Arlo Hub component")
hass.data[DATA_ARLO].update(update_cameras=True,
update_base_station=True)
dispatcher_send(hass, SIGNAL_UPDATE_ARLO)

View File

@@ -31,7 +31,7 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_PORT): int,
vol.Required(CONF_PORT): cv.port,
}),
}, extra=vol.ALLOW_EXTRA)

View File

@@ -4,7 +4,6 @@ Support for August devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/august/
"""
import logging
from datetime import timedelta
@@ -124,6 +123,7 @@ def setup_august(hass, config, api, authenticator):
return True
if state == AuthenticationState.BAD_PASSWORD:
_LOGGER.error("Invalid password provided")
return False
if state == AuthenticationState.REQUIRES_VALIDATION:
request_configuration(hass, config, api, authenticator)
@@ -165,6 +165,7 @@ class AugustData:
self._doorbell_detail_by_id = {}
self._lock_status_by_id = {}
self._lock_detail_by_id = {}
self._door_state_by_id = {}
self._activities_by_id = {}
@property
@@ -184,6 +185,7 @@ class AugustData:
def get_device_activities(self, device_id, *activity_types):
"""Return a list of activities."""
_LOGGER.debug("Getting device activities")
self._update_device_activities()
activities = self._activities_by_id.get(device_id, [])
@@ -199,6 +201,7 @@ class AugustData:
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
"""Update data object with latest from August API."""
_LOGGER.debug("Updating device activities")
for house_id in self.house_ids:
activities = self._api.get_house_activities(self._access_token,
house_id,
@@ -218,14 +221,21 @@ class AugustData:
def _update_doorbells(self):
detail_by_id = {}
_LOGGER.debug("Start retrieving doorbell details")
for doorbell in self._doorbells:
_LOGGER.debug("Updating status for %s",
doorbell.device_name)
detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail(
self._access_token, doorbell.device_id)
_LOGGER.debug("Completed retrieving doorbell details")
self._doorbell_detail_by_id = detail_by_id
def get_lock_status(self, lock_id):
"""Return lock status."""
"""Return status if the door is locked or unlocked.
This is status for the lock itself.
"""
self._update_locks()
return self._lock_status_by_id.get(lock_id)
@@ -234,17 +244,43 @@ class AugustData:
self._update_locks()
return self._lock_detail_by_id.get(lock_id)
def get_door_state(self, lock_id):
"""Return status if the door is open or closed.
This is the status from the door sensor.
"""
self._update_doors()
return self._door_state_by_id.get(lock_id)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_doors(self):
state_by_id = {}
_LOGGER.debug("Start retrieving door status")
for lock in self._locks:
_LOGGER.debug("Updating status for %s",
lock.device_name)
state_by_id[lock.device_id] = self._api.get_lock_door_status(
self._access_token, lock.device_id)
_LOGGER.debug("Completed retrieving door status")
self._door_state_by_id = state_by_id
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_locks(self):
status_by_id = {}
detail_by_id = {}
_LOGGER.debug("Start retrieving locks status")
for lock in self._locks:
_LOGGER.debug("Updating status for %s",
lock.device_name)
status_by_id[lock.device_id] = self._api.get_lock_status(
self._access_token, lock.device_id)
detail_by_id[lock.device_id] = self._api.get_lock_detail(
self._access_token, lock.device_id)
_LOGGER.debug("Completed retrieving locks status")
self._lock_status_by_id = status_by_id
self._lock_detail_by_id = detail_by_id

View File

@@ -0,0 +1,34 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Nu sunt disponibile servicii de notificare."
},
"error": {
"invalid_code": "Cod invalid, va rugam incercati din nou."
},
"step": {
"init": {
"description": "Selecta\u021bi unul dintre serviciile de notificare:",
"title": "Configura\u021bi o parol\u0103 unic\u0103 livrat\u0103 de o component\u0103 de notificare"
},
"setup": {
"description": "O parol\u0103 unic\u0103 a fost trimis\u0103 prin **notify.{notify_service}**. Introduce\u021bi parola mai jos:",
"title": "Verifica\u021bi configurarea"
}
},
"title": "Notifica\u021bi o parol\u0103 unic\u0103"
},
"totp": {
"error": {
"invalid_code": "Cod invalid, va rugam incercati din nou. Dac\u0103 primi\u021bi aceast\u0103 eroare \u00een mod consecvent, asigura\u021bi-v\u0103 c\u0103 ceasul sistemului dvs. Home Assistant este corect."
},
"step": {
"init": {
"title": "Configura\u021bi autentificarea cu doi factori utiliz\u00e2nd TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -8,11 +8,16 @@
"invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen."
},
"step": {
"init": {
"description": "Var god v\u00e4lj en av notifieringstj\u00e4nsterna:",
"title": "Konfigurera ett eng\u00e5ngsl\u00f6senord levererat genom notifieringskomponenten"
},
"setup": {
"description": "Ett eng\u00e5ngsl\u00f6senord har skickats av **notify.{notify_service}**. V\u00e4nligen ange det nedan:",
"title": "Verifiera installationen"
"title": "Verifiera inst\u00e4llningen"
}
}
},
"title": "Meddela eng\u00e5ngsl\u00f6senord"
},
"totp": {
"error": {

View File

@@ -0,0 +1,9 @@
{
"mfa_setup": {
"notify": {
"error": {
"invalid_code": "\u041d\u0435\u0432\u0456\u0440\u043d\u0438\u0439 \u043a\u043e\u0434, \u0441\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0449\u0435 \u0440\u0430\u0437."
}
}
}
}

View File

@@ -0,0 +1,74 @@
"""
Offer geo location automation rules.
For more details about this automation trigger, please refer to the
documentation at
https://home-assistant.io/docs/automation/trigger/#geo-location-trigger
"""
import voluptuous as vol
from homeassistant.components.geo_location import DOMAIN
from homeassistant.core import callback
from homeassistant.const import (
CONF_EVENT, CONF_PLATFORM, CONF_SOURCE, CONF_ZONE, EVENT_STATE_CHANGED)
from homeassistant.helpers import (
condition, config_validation as cv)
from homeassistant.helpers.config_validation import entity_domain
EVENT_ENTER = 'enter'
EVENT_LEAVE = 'leave'
DEFAULT_EVENT = EVENT_ENTER
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'geo_location',
vol.Required(CONF_SOURCE): cv.string,
vol.Required(CONF_ZONE): entity_domain('zone'),
vol.Required(CONF_EVENT, default=DEFAULT_EVENT):
vol.Any(EVENT_ENTER, EVENT_LEAVE),
})
def source_match(state, source):
"""Check if the state matches the provided source."""
return state and state.attributes.get('source') == source
async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
source = config.get(CONF_SOURCE).lower()
zone_entity_id = config.get(CONF_ZONE)
trigger_event = config.get(CONF_EVENT)
@callback
def state_change_listener(event):
"""Handle specific state changes."""
# Skip if the event is not a geo_location entity.
if not event.data.get('entity_id').startswith(DOMAIN):
return
# Skip if the event's source does not match the trigger's source.
from_state = event.data.get('old_state')
to_state = event.data.get('new_state')
if not source_match(from_state, source) \
and not source_match(to_state, source):
return
zone_state = hass.states.get(zone_entity_id)
from_match = condition.zone(hass, zone_state, from_state)
to_match = condition.zone(hass, zone_state, to_state)
# pylint: disable=too-many-boolean-expressions
if trigger_event == EVENT_ENTER and not from_match and to_match or \
trigger_event == EVENT_LEAVE and from_match and not to_match:
hass.async_run_job(action({
'trigger': {
'platform': 'geo_location',
'source': source,
'entity_id': event.data.get('entity_id'),
'from_state': from_state,
'to_state': to_state,
'zone': zone_state,
'event': trigger_event,
},
}, context=event.context))
return hass.bus.async_listen(EVENT_STATE_CHANGED, state_change_listener)

View File

@@ -11,13 +11,12 @@ from aiohttp import hdrs
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM
from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ('webhook',)
_LOGGER = logging.getLogger(__name__)
CONF_WEBHOOK_ID = 'webhook_id'
TRIGGER_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'webhook',

View File

@@ -38,6 +38,7 @@ AXIS_INCLUDE = EVENT_TYPES + PLATFORMS
AXIS_DEFAULT_HOST = '192.168.0.90'
AXIS_DEFAULT_USERNAME = 'root'
AXIS_DEFAULT_PASSWORD = 'pass'
DEFAULT_PORT = 80
DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_INCLUDE):
@@ -47,7 +48,7 @@ DEVICE_SCHEMA = vol.Schema({
vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_TRIGGER_TIME, default=0): cv.positive_int,
vol.Optional(CONF_PORT, default=80): cv.positive_int,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(ATTR_LOCATION, default=''): cv.string,
})

View File

@@ -4,16 +4,26 @@ Support for August binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.august/
"""
import logging
from datetime import timedelta, datetime
from homeassistant.components.august import DATA_AUGUST
from homeassistant.components.binary_sensor import (BinarySensorDevice)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['august']
SCAN_INTERVAL = timedelta(seconds=5)
def _retrieve_door_state(data, lock):
"""Get the latest state of the DoorSense sensor."""
from august.lock import LockDoorStatus
doorstate = data.get_door_state(lock.device_id)
return doorstate == LockDoorStatus.OPEN
def _retrieve_online_state(data, doorbell):
"""Get the latest state of the sensor."""
detail = data.get_doorbell_detail(doorbell.device_id)
@@ -46,7 +56,11 @@ def _activity_time_based_state(data, doorbell, activity_types):
# Sensor types: Name, device_class, state_provider
SENSOR_TYPES = {
SENSOR_TYPES_DOOR = {
'door_open': ['Open', 'door', _retrieve_door_state],
}
SENSOR_TYPES_DOORBELL = {
'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state],
'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state],
'doorbell_online': ['Online', 'connectivity', _retrieve_online_state],
@@ -58,14 +72,78 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data = hass.data[DATA_AUGUST]
devices = []
from august.lock import LockDoorStatus
for door in data.locks:
for sensor_type in SENSOR_TYPES_DOOR:
state_provider = SENSOR_TYPES_DOOR[sensor_type][2]
if state_provider(data, door) is LockDoorStatus.UNKNOWN:
_LOGGER.debug(
"Not adding sensor class %s for lock %s ",
SENSOR_TYPES_DOOR[sensor_type][1], door.device_name
)
continue
_LOGGER.debug(
"Adding sensor class %s for %s",
SENSOR_TYPES_DOOR[sensor_type][1], door.device_name
)
devices.append(AugustDoorBinarySensor(data, sensor_type, door))
for doorbell in data.doorbells:
for sensor_type in SENSOR_TYPES:
devices.append(AugustBinarySensor(data, sensor_type, doorbell))
for sensor_type in SENSOR_TYPES_DOORBELL:
_LOGGER.debug("Adding doorbell sensor class %s for %s",
SENSOR_TYPES_DOORBELL[sensor_type][1],
doorbell.device_name)
devices.append(
AugustDoorbellBinarySensor(data, sensor_type,
doorbell)
)
add_entities(devices, True)
class AugustBinarySensor(BinarySensorDevice):
class AugustDoorBinarySensor(BinarySensorDevice):
"""Representation of an August Door binary sensor."""
def __init__(self, data, sensor_type, door):
"""Initialize the sensor."""
self._data = data
self._sensor_type = sensor_type
self._door = door
self._state = None
self._available = False
@property
def available(self):
"""Return the availability of this sensor."""
return self._available
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return SENSOR_TYPES_DOOR[self._sensor_type][1]
@property
def name(self):
"""Return the name of the binary sensor."""
return "{} {}".format(self._door.device_name,
SENSOR_TYPES_DOOR[self._sensor_type][0])
def update(self):
"""Get the latest state of the sensor."""
state_provider = SENSOR_TYPES_DOOR[self._sensor_type][2]
self._state = state_provider(self._data, self._door)
from august.lock import LockDoorStatus
self._available = self._state != LockDoorStatus.UNKNOWN
class AugustDoorbellBinarySensor(BinarySensorDevice):
"""Representation of an August binary sensor."""
def __init__(self, data, sensor_type, doorbell):
@@ -83,15 +161,15 @@ class AugustBinarySensor(BinarySensorDevice):
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return SENSOR_TYPES[self._sensor_type][1]
return SENSOR_TYPES_DOORBELL[self._sensor_type][1]
@property
def name(self):
"""Return the name of the binary sensor."""
return "{} {}".format(self._doorbell.device_name,
SENSOR_TYPES[self._sensor_type][0])
SENSOR_TYPES_DOORBELL[self._sensor_type][0])
def update(self):
"""Get the latest state of the sensor."""
state_provider = SENSOR_TYPES[self._sensor_type][2]
state_provider = SENSOR_TYPES_DOORBELL[self._sensor_type][2]
self._state = state_provider(self._data, self._doorbell)

View File

@@ -36,6 +36,7 @@ class BlinkBinarySensor(BinarySensorDevice):
self._icon = icon
self._camera = data.sync.cameras[camera]
self._state = None
self._unique_id = "{}-{}".format(self._camera.serial, self._type)
@property
def name(self):

View File

@@ -50,6 +50,12 @@ class BloomSkySensor(BinarySensorDevice):
self._sensor_name = sensor_name
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
self._state = None
self._unique_id = '{}-{}'.format(self._device_id, self._sensor_name)
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def name(self):

View File

@@ -8,6 +8,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
from homeassistant.const import LENGTH_KILOMETERS
DEPENDENCIES = ['bmw_connected_drive']
@@ -117,7 +118,8 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
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))
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:
@@ -175,8 +177,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
self._state = (vehicle_state._attributes['connectionStatus'] ==
'CONNECTED')
@staticmethod
def _format_cbs_report(report):
def _format_cbs_report(self, report):
result = {}
service_type = report.service_type.lower().replace('_', ' ')
result['{} status'.format(service_type)] = report.state.value
@@ -184,8 +185,10 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
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)
distance = round(self.hass.config.units.length(
report.due_distance, LENGTH_KILOMETERS))
result['{} distance'.format(service_type)] = '{} {}'.format(
distance, self.hass.config.units.length_unit)
return result
def update_callback(self):

View File

@@ -69,7 +69,7 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
entities.append(KNXBinarySensor(hass, device))
entities.append(KNXBinarySensor(device))
async_add_entities(entities)
@@ -87,7 +87,7 @@ def async_add_entities_config(hass, config, async_add_entities):
reset_after=config.get(CONF_RESET_AFTER))
hass.data[DATA_KNX].xknx.devices.add(binary_sensor)
entity = KNXBinarySensor(hass, binary_sensor)
entity = KNXBinarySensor(binary_sensor)
automations = config.get(CONF_AUTOMATION)
if automations is not None:
for automation in automations:
@@ -103,11 +103,9 @@ def async_add_entities_config(hass, config, async_add_entities):
class KNXBinarySensor(BinarySensorDevice):
"""Representation of a KNX binary sensor."""
def __init__(self, hass, device):
def __init__(self, device):
"""Initialize of KNX binary sensor."""
self.device = device
self.hass = hass
self.async_register_callbacks()
self.automations = []
@callback
@@ -118,6 +116,10 @@ class KNXBinarySensor(BinarySensorDevice):
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
@property
def name(self):
"""Return the name of the KNX device."""

View File

@@ -15,19 +15,21 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS, CONF_DEVICE)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability, MqttDiscoveryUpdate)
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.helpers.event as evt
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor'
CONF_OFF_DELAY = 'off_delay'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
@@ -41,9 +43,12 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
vol.Optional(CONF_OFF_DELAY):
vol.All(vol.Coerce(int), vol.Range(min=0)),
# Integrations shouldn't never expose unique_id through configuration
# this here is an exception because MQTT is a msg transport, not a protocol
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -80,28 +85,32 @@ async def _async_setup_entity(hass, config, async_add_entities,
config.get(CONF_DEVICE_CLASS),
config.get(CONF_QOS),
config.get(CONF_FORCE_UPDATE),
config.get(CONF_OFF_DELAY),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template,
config.get(CONF_UNIQUE_ID),
config.get(CONF_DEVICE),
discovery_hash,
)])
class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
BinarySensorDevice):
MqttEntityDeviceInfo, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class,
qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template,
unique_id: Optional[str], discovery_hash):
qos, force_update, off_delay, payload_on, payload_off,
payload_available, payload_not_available, value_template,
unique_id: Optional[str], device_config: Optional[ConfigType],
discovery_hash):
"""Initialize the MQTT binary sensor."""
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
MqttEntityDeviceInfo.__init__(self, device_config)
self._name = name
self._state = None
self._state_topic = state_topic
@@ -110,9 +119,11 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
self._payload_off = payload_off
self._qos = qos
self._force_update = force_update
self._off_delay = off_delay
self._template = value_template
self._unique_id = unique_id
self._discovery_hash = discovery_hash
self._delay_listener = None
async def async_added_to_hass(self):
"""Subscribe mqtt events."""
@@ -135,6 +146,20 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
self._name, self._state_topic)
return
if (self._state and self._off_delay is not None):
@callback
def off_delay_listener(now):
"""Switch device off after a delay."""
self._delay_listener = None
self._state = False
self.async_schedule_update_ha_state()
if self._delay_listener is not None:
self._delay_listener()
self._delay_listener = evt.async_call_later(
self.hass, self._off_delay, off_delay_listener)
self.async_schedule_update_ha_state()
await mqtt.async_subscribe(

View File

@@ -7,45 +7,33 @@ https://home-assistant.io/components/binary_sensor.octoprint/
import logging
import requests
import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.octoprint import (BINARY_SENSOR_TYPES,
DOMAIN as COMPONENT_DOMAIN)
from homeassistant.components.binary_sensor import BinarySensorDevice
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['octoprint']
DOMAIN = "octoprint"
DEFAULT_NAME = 'OctoPrint'
SENSOR_TYPES = {
# API Endpoint, Group, Key, unit
'Printing': ['printer', 'state', 'printing', None],
'Printing Error': ['printer', 'state', 'error', None]
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the available OctoPrint binary sensors."""
octoprint_api = hass.data[DOMAIN]["api"]
name = config.get(CONF_NAME)
monitored_conditions = config.get(
CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys())
if discovery_info is None:
return
name = discovery_info['name']
base_url = discovery_info['base_url']
monitored_conditions = discovery_info['sensors']
octoprint_api = hass.data[COMPONENT_DOMAIN][base_url]
devices = []
for octo_type in monitored_conditions:
new_sensor = OctoPrintBinarySensor(
octoprint_api, octo_type, SENSOR_TYPES[octo_type][2],
name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0],
SENSOR_TYPES[octo_type][1], 'flags')
octoprint_api, octo_type, BINARY_SENSOR_TYPES[octo_type][2],
name, BINARY_SENSOR_TYPES[octo_type][3],
BINARY_SENSOR_TYPES[octo_type][0],
BINARY_SENSOR_TYPES[octo_type][1], 'flags')
devices.append(new_sensor)
add_entities(devices, True)

View File

@@ -0,0 +1,145 @@
"""
Support for OpenTherm Gateway binary sensors.
For more details about this platform, please refer to the documentation at
http://home-assistant.io/components/binary_sensor.opentherm_gw/
"""
import logging
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT)
from homeassistant.components.opentherm_gw import (
DATA_GW_VARS, DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import async_generate_entity_id
DEVICE_CLASS_COLD = 'cold'
DEVICE_CLASS_HEAT = 'heat'
DEVICE_CLASS_PROBLEM = 'problem'
DEPENDENCIES = ['opentherm_gw']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the OpenTherm Gateway binary sensors."""
if discovery_info is None:
return
gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS]
sensor_info = {
# [device_class, friendly_name]
gw_vars.DATA_MASTER_CH_ENABLED: [
None, "Thermostat Central Heating Enabled"],
gw_vars.DATA_MASTER_DHW_ENABLED: [
None, "Thermostat Hot Water Enabled"],
gw_vars.DATA_MASTER_COOLING_ENABLED: [
None, "Thermostat Cooling Enabled"],
gw_vars.DATA_MASTER_OTC_ENABLED: [
None, "Thermostat Outside Temperature Correction Enabled"],
gw_vars.DATA_MASTER_CH2_ENABLED: [
None, "Thermostat Central Heating 2 Enabled"],
gw_vars.DATA_SLAVE_FAULT_IND: [
DEVICE_CLASS_PROBLEM, "Boiler Fault Indication"],
gw_vars.DATA_SLAVE_CH_ACTIVE: [
DEVICE_CLASS_HEAT, "Boiler Central Heating Status"],
gw_vars.DATA_SLAVE_DHW_ACTIVE: [
DEVICE_CLASS_HEAT, "Boiler Hot Water Status"],
gw_vars.DATA_SLAVE_FLAME_ON: [
DEVICE_CLASS_HEAT, "Boiler Flame Status"],
gw_vars.DATA_SLAVE_COOLING_ACTIVE: [
DEVICE_CLASS_COLD, "Boiler Cooling Status"],
gw_vars.DATA_SLAVE_CH2_ACTIVE: [
DEVICE_CLASS_HEAT, "Boiler Central Heating 2 Status"],
gw_vars.DATA_SLAVE_DIAG_IND: [
DEVICE_CLASS_PROBLEM, "Boiler Diagnostics Indication"],
gw_vars.DATA_SLAVE_DHW_PRESENT: [None, "Boiler Hot Water Present"],
gw_vars.DATA_SLAVE_CONTROL_TYPE: [None, "Boiler Control Type"],
gw_vars.DATA_SLAVE_COOLING_SUPPORTED: [None, "Boiler Cooling Support"],
gw_vars.DATA_SLAVE_DHW_CONFIG: [
None, "Boiler Hot Water Configuration"],
gw_vars.DATA_SLAVE_MASTER_LOW_OFF_PUMP: [
None, "Boiler Pump Commands Support"],
gw_vars.DATA_SLAVE_CH2_PRESENT: [
None, "Boiler Central Heating 2 Present"],
gw_vars.DATA_SLAVE_SERVICE_REQ: [
DEVICE_CLASS_PROBLEM, "Boiler Service Required"],
gw_vars.DATA_SLAVE_REMOTE_RESET: [None, "Boiler Remote Reset Support"],
gw_vars.DATA_SLAVE_LOW_WATER_PRESS: [
DEVICE_CLASS_PROBLEM, "Boiler Low Water Pressure"],
gw_vars.DATA_SLAVE_GAS_FAULT: [
DEVICE_CLASS_PROBLEM, "Boiler Gas Fault"],
gw_vars.DATA_SLAVE_AIR_PRESS_FAULT: [
DEVICE_CLASS_PROBLEM, "Boiler Air Pressure Fault"],
gw_vars.DATA_SLAVE_WATER_OVERTEMP: [
DEVICE_CLASS_PROBLEM, "Boiler Water Overtemperature"],
gw_vars.DATA_REMOTE_TRANSFER_DHW: [
None, "Remote Hot Water Setpoint Transfer Support"],
gw_vars.DATA_REMOTE_TRANSFER_MAX_CH: [
None, "Remote Maximum Central Heating Setpoint Write Support"],
gw_vars.DATA_REMOTE_RW_DHW: [
None, "Remote Hot Water Setpoint Write Support"],
gw_vars.DATA_REMOTE_RW_MAX_CH: [
None, "Remote Central Heating Setpoint Write Support"],
gw_vars.DATA_ROVRD_MAN_PRIO: [
None, "Remote Override Manual Change Priority"],
gw_vars.DATA_ROVRD_AUTO_PRIO: [
None, "Remote Override Program Change Priority"],
gw_vars.OTGW_GPIO_A_STATE: [None, "Gateway GPIO A State"],
gw_vars.OTGW_GPIO_B_STATE: [None, "Gateway GPIO B State"],
gw_vars.OTGW_IGNORE_TRANSITIONS: [None, "Gateway Ignore Transitions"],
gw_vars.OTGW_OVRD_HB: [None, "Gateway Override High Byte"],
}
sensors = []
for var in discovery_info:
device_class = sensor_info[var][0]
friendly_name = sensor_info[var][1]
entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, var, hass=hass)
sensors.append(OpenThermBinarySensor(entity_id, var, device_class,
friendly_name))
async_add_entities(sensors)
class OpenThermBinarySensor(BinarySensorDevice):
"""Represent an OpenTherm Gateway binary sensor."""
def __init__(self, entity_id, var, device_class, friendly_name):
"""Initialize the binary sensor."""
self.entity_id = entity_id
self._var = var
self._state = None
self._device_class = device_class
self._friendly_name = friendly_name
async def async_added_to_hass(self):
"""Subscribe to updates from the component."""
_LOGGER.debug(
"Added OpenTherm Gateway binary sensor %s", self._friendly_name)
async_dispatcher_connect(self.hass, SIGNAL_OPENTHERM_GW_UPDATE,
self.receive_report)
async def receive_report(self, status):
"""Handle status updates from the component."""
self._state = bool(status.get(self._var))
self.async_schedule_update_ha_state()
@property
def name(self):
"""Return the friendly name."""
return self._friendly_name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of this device."""
return self._device_class
@property
def should_poll(self):
"""Return False because entity pushes its state."""
return False

View File

@@ -50,12 +50,12 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
"""Initialize the sensor."""
super().__init__(openuv)
self._async_unsub_dispatcher_connect = None
self._entry_id = entry_id
self._icon = icon
self._latitude = openuv.client.latitude
self._longitude = openuv.client.longitude
self._name = name
self._dispatch_remove = None
self._sensor_type = sensor_type
self._state = None
@@ -80,16 +80,20 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
return '{0}_{1}_{2}'.format(
self._latitude, self._longitude, 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."""
self._dispatch_remove = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, self._update_data)
self.async_on_remove(self._dispatch_remove)
@callback
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)
self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, TOPIC_UPDATE, update)
async def async_will_remove_from_hass(self):
"""Disconnect dispatcher listener when removed."""
if self._async_unsub_dispatcher_connect:
self._async_unsub_dispatcher_connect()
async def async_update(self):
"""Update the state."""

View File

@@ -4,19 +4,18 @@ Tracks the latency of a host by sending ICMP echo requests (ping).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ping/
"""
import asyncio
from datetime import timedelta
import logging
import re
import subprocess
import re
import sys
from datetime import timedelta
import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_HOST, CONF_NAME
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_NAME, CONF_HOST
_LOGGER = logging.getLogger(__name__)
@@ -49,14 +48,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Ping Binary sensor."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
count = config.get(CONF_PING_COUNT)
async_add_entities([PingBinarySensor(name, PingData(host, count))], True)
add_entities([PingBinarySensor(name, PingData(host, count))], True)
class PingBinarySensor(BinarySensorDevice):
@@ -93,9 +91,9 @@ class PingBinarySensor(BinarySensorDevice):
ATTR_ROUND_TRIP_TIME_MIN: self.ping.data['min'],
}
async def async_update(self):
def update(self):
"""Get the latest data."""
await self.ping.update()
self.ping.update()
class PingData:
@@ -116,13 +114,12 @@ class PingData:
'ping', '-n', '-q', '-c', str(self._count), '-W1',
self._ip_address]
async def ping(self):
def ping(self):
"""Send ICMP echo request and return details if success."""
pinger = await asyncio.create_subprocess_shell(
' '.join(self._ping_cmd), stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
pinger = subprocess.Popen(
self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
out = await pinger.communicate()
out = pinger.communicate()
_LOGGER.debug("Output is %s", str(out))
if sys.platform == 'win32':
match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1])
@@ -131,8 +128,7 @@ class PingData:
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': '',
}
'mdev': ''}
if 'max/' not in str(out):
match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max = match.groups()
@@ -140,20 +136,18 @@ class PingData:
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': '',
}
'mdev': ''}
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
'mdev': rtt_mdev,
}
'mdev': rtt_mdev}
except (subprocess.CalledProcessError, AttributeError):
return False
async def update(self):
def update(self):
"""Retrieve the latest details from the host."""
self.data = await self.ping()
self.data = self.ping()
self.available = bool(self.data)

View File

@@ -0,0 +1,105 @@
"""
Support for Rflink binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rflink/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.rflink import (
CONF_ALIASES, CONF_DEVICES, RflinkDevice)
from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_DEVICE_CLASS)
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.event as evt
CONF_OFF_DELAY = 'off_delay'
DEFAULT_FORCE_UPDATE = False
DEPENDENCIES = ['rflink']
_LOGGER = logging.getLogger(__name__)
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_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE):
cv.boolean,
vol.Optional(CONF_OFF_DELAY): cv.positive_int,
vol.Optional(CONF_ALIASES, default=[]):
vol.All(cv.ensure_list, [cv.string]),
})
},
}, extra=vol.ALLOW_EXTRA)
def devices_from_config(domain_config):
"""Parse configuration and add Rflink sensor devices."""
devices = []
for device_id, config in domain_config[CONF_DEVICES].items():
device = RflinkBinarySensor(device_id, **config)
devices.append(device)
return devices
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Rflink platform."""
async_add_entities(devices_from_config(config))
class RflinkBinarySensor(RflinkDevice, BinarySensorDevice):
"""Representation of an Rflink binary sensor."""
def __init__(self, device_id, device_class=None,
force_update=None, off_delay=None,
**kwargs):
"""Handle sensor specific args and super init."""
self._state = None
self._device_class = device_class
self._force_update = force_update
self._off_delay = off_delay
self._delay_listener = None
super().__init__(device_id, **kwargs)
def _handle_event(self, event):
"""Domain specific event handler."""
command = event['command']
if command == 'on':
self._state = True
elif command == 'off':
self._state = False
if (self._state and self._off_delay is not None):
def off_delay_listener(now):
"""Switch device off after a delay."""
self._delay_listener = None
self._state = False
self.async_schedule_update_ha_state()
if self._delay_listener is not None:
self._delay_listener()
self._delay_listener = evt.async_call_later(
self.hass, self._off_delay, off_delay_listener)
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of this sensor."""
return self._device_class
@property
def force_update(self):
"""Force update."""
return self._force_update

View File

@@ -73,6 +73,7 @@ class RingBinarySensor(BinarySensorDevice):
SENSOR_TYPES.get(self._sensor_type)[0])
self._device_class = SENSOR_TYPES.get(self._sensor_type)[2]
self._state = None
self._unique_id = '{}-{}'.format(self._data.id, self._sensor_type)
@property
def name(self):
@@ -89,6 +90,11 @@ class RingBinarySensor(BinarySensorDevice):
"""Return the class of the binary sensor."""
return self._device_class
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def device_state_attributes(self):
"""Return the state attributes."""

View File

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

View File

@@ -14,15 +14,16 @@ from homeassistant.const import CONF_NAME, WEEKDAYS
from homeassistant.components.binary_sensor import BinarySensorDevice
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['holidays==0.9.7']
REQUIREMENTS = ['holidays==0.9.8']
_LOGGER = logging.getLogger(__name__)
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime
ALL_COUNTRIES = [
'Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT', 'Belarus', 'BY'
'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech', 'CZ',
'Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT',
'Brazil', 'BR', 'Belarus', 'BY', 'Belgium', 'BE',
'Canada', 'CA', 'Colombia', 'CO', 'Croatia', 'HR', 'Czech', 'CZ',
'Denmark', 'DK', 'England', 'EuropeanCentralBank', 'ECB', 'TAR',
'Finland', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Hungary', 'HU',
'India', 'IND', 'Ireland', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP',
@@ -30,7 +31,7 @@ ALL_COUNTRIES = [
'Northern Ireland', 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI', 'Slovakia', 'SK',
'South Africa', 'ZA', 'Spain', 'ES', 'Sweden', 'SE', 'Switzerland', 'CH',
'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales',
'Ukraine', 'UA', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales',
]
ALLOWED_DAYS = WEEKDAYS + ['holiday']

View File

@@ -4,6 +4,8 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY,
XiaomiDevice)
from homeassistant.core import callback
from homeassistant.helpers.event import async_call_later
_LOGGER = logging.getLogger(__name__)
@@ -36,21 +38,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
elif model in ['natgas', 'sensor_natgas']:
devices.append(XiaomiNatgasSensor(device, gateway))
elif model in ['switch', 'sensor_switch',
'sensor_switch.aq2', 'sensor_switch.aq3']:
'sensor_switch.aq2', 'sensor_switch.aq3',
'remote.b1acn01']:
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
data_key = 'button_0'
devices.append(XiaomiButton(device, 'Switch', data_key,
hass, gateway))
elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']:
elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1',
'remote.b186acn01']:
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'channel_0'
else:
data_key = 'button_0'
devices.append(XiaomiButton(device, 'Wall Switch', data_key,
hass, gateway))
elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1']:
elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1',
'remote.b286acn01']:
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key_left = 'channel_0'
data_key_right = 'channel_1'
@@ -65,6 +70,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
'dual_channel', hass, gateway))
elif model in ['cube', 'sensor_cube', 'sensor_cube.aqgl01']:
devices.append(XiaomiCube(device, hass, gateway))
elif model in ['vibration', 'vibration.aq1']:
devices.append(XiaomiVibration(device, 'Vibration',
'status', gateway))
else:
_LOGGER.warning('Unmapped Device Model %s', model)
add_entities(devices)
@@ -144,6 +155,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
"""Initialize the XiaomiMotionSensor."""
self._hass = hass
self._no_motion_since = 0
self._unsub_set_no_motion = None
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
@@ -158,6 +170,13 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
attrs.update(super().device_state_attributes)
return attrs
@callback
def _async_set_no_motion(self, now):
"""Set state to False."""
self._unsub_set_no_motion = None
self._state = False
self.async_schedule_update_ha_state()
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if raw_data['cmd'] == 'heartbeat':
@@ -179,11 +198,20 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
return False
if value == MOTION:
self._should_poll = True
if self.entity_id is not None:
self._hass.bus.fire('motion', {
'entity_id': self.entity_id
})
if self._data_key == 'motion_status':
if self._unsub_set_no_motion:
self._unsub_set_no_motion()
self._unsub_set_no_motion = async_call_later(
self._hass,
180,
self._async_set_no_motion
)
else:
self._should_poll = True
if self.entity_id is not None:
self._hass.bus.fire('motion', {
'entity_id': self.entity_id
})
self._no_motion_since = 0
if self._state:
@@ -311,6 +339,38 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
return False
class XiaomiVibration(XiaomiBinarySensor):
"""Representation of a Xiaomi Vibration Sensor."""
def __init__(self, device, name, data_key, xiaomi_hub):
"""Initialize the XiaomiVibration."""
self._last_action = None
super().__init__(device, name, xiaomi_hub, data_key, None)
@property
def device_state_attributes(self):
"""Return the state attributes."""
attrs = {ATTR_LAST_ACTION: self._last_action}
attrs.update(super().device_state_attributes)
return attrs
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value not in ('vibrate', 'tilt', 'free_fall'):
_LOGGER.warning("Unsupported movement_type detected: %s",
value)
return False
self.hass.bus.fire('xiaomi_aqara.movement', {
'entity_id': self.entity_id,
'movement_type': value
})
self._last_action = value
return True
class XiaomiButton(XiaomiBinarySensor):
"""Representation of a Xiaomi Button."""

View File

@@ -253,5 +253,9 @@ class Remote(zha.Entity, BinarySensorDevice):
"""Retrieve latest state."""
from zigpy.zcl.clusters.general import OnOff
result = await zha.safe_read(
self._endpoint.out_clusters[OnOff.cluster_id], ['on_off'])
self._endpoint.out_clusters[OnOff.cluster_id],
['on_off'],
allow_cache=False,
only_cache=(not self._initialized)
)
self._state = result.get('on_off', self._state)

View File

@@ -7,10 +7,11 @@ https://home-assistant.io/components/binary_sensor.zwave/
import logging
import datetime
import homeassistant.util.dt as dt_util
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import
async_setup_platform, workaround)
from homeassistant.components.zwave import workaround
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
@@ -19,6 +20,23 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave binary sensors."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave binary sensors from Config Entry."""
@callback
def async_add_binary_sensor(binary_sensor):
"""Add Z-Wave binary sensor."""
async_add_entities([binary_sensor])
async_dispatcher_connect(hass, 'zwave_new_binary_sensor',
async_add_binary_sensor)
def get_device(values, **kwargs):
"""Create Z-Wave entity device."""
device_mapping = workaround.get_device_mapping(values.primary)

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
REQUIREMENTS = ['blinkpy==0.9.0']
REQUIREMENTS = ['blinkpy==0.10.1']
_LOGGER = logging.getLogger(__name__)
@@ -36,7 +36,6 @@ TYPE_MOTION_DETECTED = 'motion_detected'
TYPE_TEMPERATURE = 'temperature'
TYPE_BATTERY = 'battery'
TYPE_WIFI_STRENGTH = 'wifi_strength'
TYPE_STATUS = 'status'
SERVICE_REFRESH = 'blink_update'
SERVICE_TRIGGER = 'trigger_camera'
@@ -50,8 +49,7 @@ BINARY_SENSORS = {
SENSORS = {
TYPE_TEMPERATURE: ['Temperature', TEMP_FAHRENHEIT, 'mdi:thermometer'],
TYPE_BATTERY: ['Battery', '%', 'mdi:battery-80'],
TYPE_WIFI_STRENGTH: ['Wifi Signal', 'dBm', 'mdi:wifi-strength-2'],
TYPE_STATUS: ['Status', '', 'mdi:bell']
TYPE_WIFI_STRENGTH: ['Wifi Signal', 'bars', 'mdi:wifi-strength-2'],
}
BINARY_SENSOR_SCHEMA = vol.Schema({

View File

@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD)
from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
@@ -85,6 +85,7 @@ def setup_account(account_config: dict, hass, name: str) \
password = account_config[CONF_PASSWORD]
region = account_config[CONF_REGION]
read_only = account_config[CONF_READ_ONLY]
_LOGGER.debug('Adding new account %s', name)
cd_account = BMWConnectedDriveAccount(username, password, region, name,
read_only)

View File

@@ -68,6 +68,7 @@ class GoogleCalendarData:
self.event = None
def _prepare_query(self):
# pylint: disable=import-error
from httplib2 import ServerNotFoundError
try:

View File

@@ -518,6 +518,8 @@ class TodoistProjectData:
def update(self):
"""Get the latest data."""
if self._id is None:
self._api.reset_state()
self._api.sync()
project_task_data = [
task for task in self._api.state[TASKS]
if not self._project_id_whitelist or
@@ -527,6 +529,7 @@ class TodoistProjectData:
# If we have no data, we can just return right away.
if not project_task_data:
_LOGGER.debug("No data for %s", self._name)
self.event = None
return True
@@ -541,6 +544,8 @@ class TodoistProjectData:
if not project_tasks:
# We had no valid tasks
_LOGGER.debug("No valid tasks for %s", self._name)
self.event = None
return True
# Make sure the task collection is reset to prevent an

View File

@@ -38,6 +38,7 @@ class BlinkCamera(Camera):
self.data = data
self._name = "{} {}".format(BLINK_DATA, name)
self._camera = camera
self._unique_id = "{}-camera".format(camera.serial)
self.response = None
self.current_image = None
self.last_image = None
@@ -48,6 +49,11 @@ class BlinkCamera(Camera):
"""Return the camera name."""
return self._name
@property
def unique_id(self):
"""Return the unique camera id."""
return self._unique_id
@property
def device_state_attributes(self):
"""Return the camera attributes."""
@@ -64,7 +70,7 @@ class BlinkCamera(Camera):
@property
def motion_detection_enabled(self):
"""Return the state of the camera."""
return self._camera.armed
return self._camera.motion_enabled
@property
def brand(self):

View File

@@ -53,6 +53,11 @@ class BloomSkyCamera(Camera):
return self._last_image
@property
def unique_id(self):
"""Return a unique ID."""
return self._id
@property
def name(self):
"""Return the name of this BloomSky device."""

View File

@@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up a FFmpeg camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
if not await hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
async_add_entities([FFmpegCamera(hass, config)])

View File

@@ -63,3 +63,8 @@ class NeatoCleaningMap(Camera):
def name(self):
"""Return the name of this camera."""
return self._robot_name
@property
def unique_id(self):
"""Return unique ID."""
return self._robot_serial

View File

@@ -97,6 +97,11 @@ class RingCam(Camera):
"""Return the name of this camera."""
return self._name
@property
def unique_id(self):
"""Return a unique ID."""
return self._camera.id
@property
def device_state_attributes(self):
"""Return the state attributes."""

View File

@@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_MODEL): vol.Any(MODEL_YI,
MODEL_XIAOFANG),
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,

View File

@@ -32,7 +32,7 @@ CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,

View File

@@ -7,9 +7,9 @@
"step": {
"confirm": {
"description": "Deseja configurar o Google Cast?",
"title": ""
"title": "Google Cast"
}
},
"title": ""
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Nu s-au g\u0103sit dispozitive Google Cast \u00een re\u021bea.",
"single_instance_allowed": "Este necesar\u0103 o singur\u0103 configura\u021bie a serviciului Google Cast."
},
"step": {
"confirm": {
"description": "Dori\u021bi s\u0103 configura\u021bi Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -48,11 +48,6 @@ STATE_MANUAL = 'manual'
STATE_DRY = 'dry'
STATE_FAN_ONLY = 'fan_only'
STATE_ECO = 'eco'
STATE_ELECTRIC = 'electric'
STATE_PERFORMANCE = 'performance'
STATE_HIGH_DEMAND = 'high_demand'
STATE_HEAT_PUMP = 'heat_pump'
STATE_GAS = 'gas'
SUPPORT_TARGET_TEMPERATURE = 1
SUPPORT_TARGET_TEMPERATURE_HIGH = 2

View File

@@ -40,6 +40,15 @@ HA_STATE_TO_DAIKIN = {
STATE_OFF: 'off',
}
DAIKIN_TO_HA_STATE = {
'fan': STATE_FAN_ONLY,
'dry': STATE_DRY,
'cool': STATE_COOL,
'hot': STATE_HEAT,
'auto': STATE_AUTO,
'off': STATE_OFF,
}
HA_ATTR_TO_DAIKIN = {
ATTR_OPERATION_MODE: 'mode',
ATTR_FAN_MODE: 'f_rate',
@@ -75,9 +84,7 @@ class DaikinClimate(ClimateDevice):
self._api = api
self._force_refresh = False
self._list = {
ATTR_OPERATION_MODE: list(
map(str.title, set(HA_STATE_TO_DAIKIN.values()))
),
ATTR_OPERATION_MODE: list(HA_STATE_TO_DAIKIN),
ATTR_FAN_MODE: list(
map(
str.title,
@@ -136,11 +143,11 @@ class DaikinClimate(ClimateDevice):
elif key == ATTR_OPERATION_MODE:
# Daikin can return also internal states auto-1 or auto-7
# and we need to translate them as AUTO
value = re.sub(
'[^a-z]',
'',
self._api.device.represent(daikin_attr)[1]
).title()
daikin_mode = re.sub(
'[^a-z]', '',
self._api.device.represent(daikin_attr)[1])
ha_mode = DAIKIN_TO_HA_STATE.get(daikin_mode)
value = ha_mode
if value is None:
_LOGGER.error("Invalid value requested for key %s", key)
@@ -167,8 +174,8 @@ class DaikinClimate(ClimateDevice):
daikin_attr = HA_ATTR_TO_DAIKIN.get(attr)
if daikin_attr is not None:
if value.title() in self._list[attr]:
values[daikin_attr] = value.lower()
if value in self._list[attr]:
values[daikin_attr] = HA_STATE_TO_DAIKIN[value]
else:
_LOGGER.error("Invalid value %s for %s", attr, value)

View File

@@ -0,0 +1,176 @@
"""
Support for Dyson Pure Hot+Cool link fan.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.dyson/
"""
import logging
from homeassistant.components.dyson import DYSON_DEVICES
from homeassistant.components.climate import (
ClimateDevice, STATE_HEAT, STATE_COOL, STATE_IDLE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE)
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
_LOGGER = logging.getLogger(__name__)
STATE_DIFFUSE = "Diffuse Mode"
STATE_FOCUS = "Focus Mode"
FAN_LIST = [STATE_FOCUS, STATE_DIFFUSE]
OPERATION_LIST = [STATE_HEAT, STATE_COOL]
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
| SUPPORT_OPERATION_MODE)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Dyson fan components."""
if discovery_info is None:
return
from libpurecoollink.dyson_pure_hotcool_link import DysonPureHotCoolLink
# Get Dyson Devices from parent component.
add_devices(
[DysonPureHotCoolLinkDevice(device)
for device in hass.data[DYSON_DEVICES]
if isinstance(device, DysonPureHotCoolLink)]
)
class DysonPureHotCoolLinkDevice(ClimateDevice):
"""Representation of a Dyson climate fan."""
def __init__(self, device):
"""Initialize the fan."""
self._device = device
self._current_temp = None
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(self._device.add_message_listener,
self.on_message)
def on_message(self, message):
"""Call when new messages received from the climate."""
from libpurecoollink.dyson_pure_state import DysonPureHotCoolState
if isinstance(message, DysonPureHotCoolState):
_LOGGER.debug("Message received for climate device %s : %s",
self.name, message)
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def name(self):
"""Return the display name of this climate."""
return self._device.name
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
@property
def current_temperature(self):
"""Return the current temperature."""
if self._device.environmental_state:
temperature_kelvin = self._device.environmental_state.temperature
if temperature_kelvin != 0:
self._current_temp = float("{0:.1f}".format(
temperature_kelvin - 273))
return self._current_temp
@property
def target_temperature(self):
"""Return the target temperature."""
heat_target = int(self._device.state.heat_target) / 10
return int(heat_target - 273)
@property
def current_humidity(self):
"""Return the current humidity."""
if self._device.environmental_state:
if self._device.environmental_state.humidity == 0:
return None
return self._device.environmental_state.humidity
return None
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
from libpurecoollink.const import HeatMode, HeatState
if self._device.state.heat_mode == HeatMode.HEAT_ON.value:
if self._device.state.heat_state == HeatState.HEAT_STATE_ON.value:
return STATE_HEAT
return STATE_IDLE
return STATE_COOL
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
@property
def current_fan_mode(self):
"""Return the fan setting."""
from libpurecoollink.const import FocusMode
if self._device.state.focus_mode == FocusMode.FOCUS_ON.value:
return STATE_FOCUS
return STATE_DIFFUSE
@property
def fan_list(self):
"""Return the list of available fan modes."""
return FAN_LIST
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
if target_temp is None:
return
target_temp = int(target_temp)
_LOGGER.debug("Set %s temperature %s", self.name, target_temp)
# Limit the target temperature into acceptable range.
target_temp = min(self.max_temp, target_temp)
target_temp = max(self.min_temp, target_temp)
from libpurecoollink.const import HeatTarget, HeatMode
self._device.set_configuration(
heat_target=HeatTarget.celsius(target_temp),
heat_mode=HeatMode.HEAT_ON)
def set_fan_mode(self, fan_mode):
"""Set new fan mode."""
_LOGGER.debug("Set %s focus mode %s", self.name, fan_mode)
from libpurecoollink.const import FocusMode
if fan_mode == STATE_FOCUS:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_ON)
elif fan_mode == STATE_DIFFUSE:
self._device.set_configuration(focus_mode=FocusMode.FOCUS_OFF)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
_LOGGER.debug("Set %s heat mode %s", self.name, operation_mode)
from libpurecoollink.const import HeatMode
if operation_mode == STATE_HEAT:
self._device.set_configuration(heat_mode=HeatMode.HEAT_ON)
elif operation_mode == STATE_COOL:
self._device.set_configuration(heat_mode=HeatMode.HEAT_OFF)
@property
def min_temp(self):
"""Return the minimum temperature."""
return 1
@property
def max_temp(self):
"""Return the maximum temperature."""
return 37

View File

@@ -0,0 +1,193 @@
"""
Support for control of Elk-M1 connected thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.elkm1/
"""
from homeassistant.components.climate import (
ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, PRECISION_WHOLE, STATE_AUTO,
STATE_COOL, STATE_FAN_ONLY, STATE_HEAT, STATE_IDLE, SUPPORT_AUX_HEAT,
SUPPORT_FAN_MODE, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW, ClimateDevice)
from homeassistant.components.elkm1 import (
DOMAIN as ELK_DOMAIN, ElkEntity, create_elk_entities)
from homeassistant.const import STATE_ON
DEPENDENCIES = [ELK_DOMAIN]
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Create the Elk-M1 thermostat platform."""
if discovery_info is None:
return
elk = hass.data[ELK_DOMAIN]['elk']
async_add_entities(create_elk_entities(
hass, elk.thermostats, 'thermostat', ElkThermostat, []), True)
class ElkThermostat(ElkEntity, ClimateDevice):
"""Representation of an Elk-M1 Thermostat."""
def __init__(self, element, elk, elk_data):
"""Initialize climate entity."""
super().__init__(element, elk, elk_data)
self._state = None
@property
def supported_features(self):
"""Return the list of supported features."""
return (SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT
| SUPPORT_TARGET_TEMPERATURE_HIGH
| SUPPORT_TARGET_TEMPERATURE_LOW)
@property
def temperature_unit(self):
"""Return the temperature unit."""
return self._temperature_unit
@property
def current_temperature(self):
"""Return the current temperature."""
return self._element.current_temp
@property
def target_temperature(self):
"""Return the temperature we are trying to reach."""
from elkm1_lib.const import ThermostatMode
if (self._element.mode == ThermostatMode.HEAT.value) or (
self._element.mode == ThermostatMode.EMERGENCY_HEAT.value):
return self._element.heat_setpoint
if self._element.mode == ThermostatMode.COOL.value:
return self._element.cool_setpoint
return None
@property
def target_temperature_high(self):
"""Return the high target temperature."""
return self._element.cool_setpoint
@property
def target_temperature_low(self):
"""Return the low target temperature."""
return self._element.heat_setpoint
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return 1
@property
def current_humidity(self):
"""Return the current humidity."""
return self._element.humidity
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._state
@property
def operation_list(self):
"""Return the list of available operation modes."""
return [STATE_IDLE, STATE_HEAT, STATE_COOL, STATE_AUTO, STATE_FAN_ONLY]
@property
def precision(self):
"""Return the precision of the system."""
return PRECISION_WHOLE
@property
def is_aux_heat_on(self):
"""Return if aux heater is on."""
from elkm1_lib.const import ThermostatMode
return self._element.mode == ThermostatMode.EMERGENCY_HEAT.value
@property
def min_temp(self):
"""Return the minimum temperature supported."""
return 1
@property
def max_temp(self):
"""Return the maximum temperature supported."""
return 99
@property
def current_fan_mode(self):
"""Return the fan setting."""
from elkm1_lib.const import ThermostatFan
if self._element.fan == ThermostatFan.AUTO.value:
return STATE_AUTO
if self._element.fan == ThermostatFan.ON.value:
return STATE_ON
return None
def _elk_set(self, mode, fan):
from elkm1_lib.const import ThermostatSetting
if mode is not None:
self._element.set(ThermostatSetting.MODE.value, mode)
if fan is not None:
self._element.set(ThermostatSetting.FAN.value, fan)
async def async_set_operation_mode(self, operation_mode):
"""Set thermostat operation mode."""
from elkm1_lib.const import ThermostatFan, ThermostatMode
settings = {
STATE_IDLE: (ThermostatMode.OFF.value, ThermostatFan.AUTO.value),
STATE_HEAT: (ThermostatMode.HEAT.value, None),
STATE_COOL: (ThermostatMode.COOL.value, None),
STATE_AUTO: (ThermostatMode.AUTO.value, None),
STATE_FAN_ONLY: (ThermostatMode.OFF.value, ThermostatFan.ON.value)
}
self._elk_set(settings[operation_mode][0], settings[operation_mode][1])
async def async_turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
from elkm1_lib.const import ThermostatMode
self._elk_set(ThermostatMode.EMERGENCY_HEAT.value, None)
async def async_turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
from elkm1_lib.const import ThermostatMode
self._elk_set(ThermostatMode.HEAT.value, None)
@property
def fan_list(self):
"""Return the list of available fan modes."""
return [STATE_AUTO, STATE_ON]
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
from elkm1_lib.const import ThermostatFan
if fan_mode == STATE_AUTO:
self._elk_set(None, ThermostatFan.AUTO.value)
elif fan_mode == STATE_ON:
self._elk_set(None, ThermostatFan.ON.value)
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
from elkm1_lib.const import ThermostatSetting
low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW)
high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH)
if low_temp is not None:
self._element.set(
ThermostatSetting.HEAT_SETPOINT.value, round(low_temp))
if high_temp is not None:
self._element.set(
ThermostatSetting.COOL_SETPOINT.value, round(high_temp))
def _element_changed(self, element, changeset):
from elkm1_lib.const import ThermostatFan, ThermostatMode
mode_to_state = {
ThermostatMode.OFF.value: STATE_IDLE,
ThermostatMode.COOL.value: STATE_COOL,
ThermostatMode.HEAT.value: STATE_HEAT,
ThermostatMode.EMERGENCY_HEAT.value: STATE_HEAT,
ThermostatMode.AUTO.value: STATE_AUTO,
}
self._state = mode_to_state.get(self._element.mode)
if self._state == STATE_IDLE and \
self._element.fan == ThermostatFan.ON.value:
self._state = STATE_FAN_ONLY

View File

@@ -10,12 +10,13 @@ import voluptuous as vol
from homeassistant.components.climate import (
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE)
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
SUPPORT_ON_OFF)
from homeassistant.const import (
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.41']
REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.45']
_LOGGER = logging.getLogger(__name__)
@@ -39,7 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
SUPPORT_AWAY_MODE | SUPPORT_ON_OFF)
def setup_platform(hass, config, add_entities, discovery_info=None):
@@ -53,14 +54,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(devices)
# pylint: disable=import-error
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of an eQ-3 Bluetooth Smart thermostat."""
def __init__(self, _mac, _name):
"""Initialize the thermostat."""
# We want to avoid name clash with this module.
import eq3bt as eq3
import eq3bt as eq3 # pylint: disable=import-error
self.modes = {
eq3.Mode.Open: STATE_ON,
@@ -151,6 +151,14 @@ class EQ3BTSmartThermostat(ClimateDevice):
"""Return if we are away."""
return self.current_operation == STATE_AWAY
def turn_on(self):
"""Turn device on."""
self.set_operation_mode(STATE_AUTO)
def turn_off(self):
"""Turn device off."""
self.set_operation_mode(STATE_OFF)
@property
def min_temp(self):
"""Return the minimum temperature."""
@@ -176,7 +184,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
def update(self):
"""Update the data from the thermostat."""
from bluepy.btle import BTLEException
from bluepy.btle import BTLEException # pylint: disable=import-error
try:
self._thermostat.update()
except BTLEException as ex:

View File

@@ -75,7 +75,7 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
entities.append(KNXClimate(hass, device))
entities.append(KNXClimate(device))
async_add_entities(entities)
@@ -110,17 +110,15 @@ def async_add_entities_config(hass, config, async_add_entities):
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS))
hass.data[DATA_KNX].xknx.devices.add(climate)
async_add_entities([KNXClimate(hass, climate)])
async_add_entities([KNXClimate(climate)])
class KNXClimate(ClimateDevice):
"""Representation of a KNX climate device."""
def __init__(self, hass, device):
def __init__(self, device):
"""Initialize of a KNX climate device."""
self.device = device
self.hass = hass
self.async_register_callbacks()
@property
def supported_features(self):
@@ -137,6 +135,10 @@ class KNXClimate(ClimateDevice):
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
@property
def name(self):
"""Return the name of the KNX device."""

View File

@@ -0,0 +1,153 @@
"""
Support for mill wifi-enabled home heaters.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.mill/
"""
import logging
import voluptuous as vol
from homeassistant.components.climate import (
ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE, SUPPORT_ON_OFF)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME,
STATE_ON, STATE_OFF, TEMP_CELSIUS)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['millheater==0.1.2']
_LOGGER = logging.getLogger(__name__)
MAX_TEMP = 35
MIN_TEMP = 5
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_FAN_MODE | SUPPORT_ON_OFF)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Mill heater."""
from mill import Mill
mill_data_connection = Mill(config[CONF_USERNAME],
config[CONF_PASSWORD],
websession=async_get_clientsession(hass))
if not await mill_data_connection.connect():
_LOGGER.error("Failed to connect to Mill")
return
await mill_data_connection.update_heaters()
dev = []
for heater in mill_data_connection.heaters.values():
dev.append(MillHeater(heater, mill_data_connection))
async_add_entities(dev)
class MillHeater(ClimateDevice):
"""Representation of a Mill Thermostat device."""
def __init__(self, heater, mill_data_connection):
"""Initialize the thermostat."""
self._heater = heater
self._conn = mill_data_connection
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def available(self):
"""Return True if entity is available."""
return self._heater.device_status == 0 # weird api choice
@property
def unique_id(self):
"""Return a unique ID."""
return self._heater.device_id
@property
def name(self):
"""Return the name of the entity."""
return self._heater.name
@property
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._heater.set_temp
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return 1
@property
def current_temperature(self):
"""Return the current temperature."""
return self._heater.current_temp
@property
def current_fan_mode(self):
"""Return the fan setting."""
return STATE_ON if self._heater.fan_status == 1 else STATE_OFF
@property
def fan_list(self):
"""List of available fan modes."""
return [STATE_ON, STATE_OFF]
@property
def is_on(self):
"""Return true if heater is on."""
return self._heater.power_status == 1
@property
def min_temp(self):
"""Return the minimum temperature."""
return MIN_TEMP
@property
def max_temp(self):
"""Return the maximum temperature."""
return MAX_TEMP
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return
await self._conn.set_heater_temp(self._heater.device_id,
int(temperature))
async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
fan_status = 1 if fan_mode == STATE_ON else 0
await self._conn.heater_control(self._heater.device_id,
fan_status=fan_status)
async def async_turn_on(self):
"""Turn Mill unit on."""
await self._conn.heater_control(self._heater.device_id,
power_status=1)
async def async_turn_off(self):
"""Turn Mill unit off."""
await self._conn.heater_control(self._heater.device_id,
power_status=0)
async def async_update(self):
"""Retrieve latest state."""
self._heater = await self._conn.update_device(self._heater.device_id)

View File

@@ -75,6 +75,7 @@ CONF_SEND_IF_OFF = 'send_if_off'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
CONF_TEMP_STEP = 'temp_step'
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
@@ -124,7 +125,8 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float)
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float),
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float)
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -213,6 +215,7 @@ async def _async_setup_entity(hass, config, async_add_entities,
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_MIN_TEMP),
config.get(CONF_MAX_TEMP),
config.get(CONF_TEMP_STEP),
discovery_hash,
)])
@@ -226,7 +229,7 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
current_swing_mode, current_operation, aux, send_if_off,
payload_on, payload_off, availability_topic,
payload_available, payload_not_available,
min_temp, max_temp, discovery_hash):
min_temp, max_temp, temp_step, discovery_hash):
"""Initialize the climate device."""
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
@@ -237,19 +240,26 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
self._value_templates = value_templates
self._qos = qos
self._retain = retain
self._target_temperature = target_temperature
# set to None in non-optimistic mode
self._target_temperature = self._current_fan_mode = \
self._current_operation = self._current_swing_mode = None
if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
self._target_temperature = target_temperature
self._unit_of_measurement = hass.config.units.temperature_unit
self._away = away
self._hold = hold
self._current_temperature = None
self._current_fan_mode = current_fan_mode
self._current_operation = current_operation
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = current_fan_mode
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = current_operation
self._aux = aux
self._current_swing_mode = current_swing_mode
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
self._current_swing_mode = current_swing_mode
self._fan_list = fan_mode_list
self._operation_list = mode_list
self._swing_list = swing_mode_list
self._target_temperature_step = 1
self._target_temperature_step = temp_step
self._send_if_off = send_if_off
self._payload_on = payload_on
self._payload_off = payload_off

View File

@@ -1,34 +1,23 @@
"""
Support for OpenTherm Gateway devices.
Support for OpenTherm Gateway climate devices.
For more details about this component, please refer to the documentation at
For more details about this platform, please refer to the documentation at
http://home-assistant.io/components/climate.opentherm_gw/
"""
import logging
import voluptuous as vol
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA,
STATE_IDLE, STATE_HEAT,
STATE_COOL,
from homeassistant.components.climate import (ClimateDevice, STATE_IDLE,
STATE_HEAT, STATE_COOL,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME,
PRECISION_HALVES, PRECISION_TENTHS,
TEMP_CELSIUS, PRECISION_WHOLE)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.opentherm_gw import (
CONF_FLOOR_TEMP, CONF_PRECISION, DATA_DEVICE, DATA_GW_VARS,
DATA_OPENTHERM_GW, SIGNAL_OPENTHERM_GW_UPDATE)
from homeassistant.const import (ATTR_TEMPERATURE, CONF_NAME, PRECISION_HALVES,
PRECISION_TENTHS, PRECISION_WHOLE,
TEMP_CELSIUS)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
REQUIREMENTS = ['pyotgw==0.1b0']
CONF_FLOOR_TEMP = "floor_temperature"
CONF_PRECISION = 'precision'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICE): cv.string,
vol.Optional(CONF_NAME, default="OpenTherm Gateway"): cv.string,
vol.Optional(CONF_PRECISION): vol.In([PRECISION_TENTHS, PRECISION_HALVES,
PRECISION_WHOLE]),
vol.Optional(CONF_FLOOR_TEMP, default=False): cv.boolean,
})
DEPENDENCIES = ['opentherm_gw']
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
_LOGGER = logging.getLogger(__name__)
@@ -37,19 +26,17 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the opentherm_gw device."""
gateway = OpenThermGateway(config)
gateway = OpenThermGateway(hass, discovery_info)
async_add_entities([gateway])
class OpenThermGateway(ClimateDevice):
"""Representation of a climate device."""
def __init__(self, config):
"""Initialize the sensor."""
import pyotgw
self.pyotgw = pyotgw
self.gateway = self.pyotgw.pyotgw()
self._device = config[CONF_DEVICE]
def __init__(self, hass, config):
"""Initialize the device."""
self._gateway = hass.data[DATA_OPENTHERM_GW][DATA_DEVICE]
self._gw_vars = hass.data[DATA_OPENTHERM_GW][DATA_GW_VARS]
self.friendly_name = config.get(CONF_NAME)
self.floor_temp = config.get(CONF_FLOOR_TEMP)
self.temp_precision = config.get(CONF_PRECISION)
@@ -63,40 +50,38 @@ class OpenThermGateway(ClimateDevice):
async def async_added_to_hass(self):
"""Connect to the OpenTherm Gateway device."""
await self.gateway.connect(self.hass.loop, self._device)
self.gateway.subscribe(self.receive_report)
_LOGGER.debug("Connected to %s on %s", self.friendly_name,
self._device)
_LOGGER.debug("Added device %s", self.friendly_name)
async_dispatcher_connect(self.hass, SIGNAL_OPENTHERM_GW_UPDATE,
self.receive_report)
async def receive_report(self, status):
"""Receive and handle a new report from the Gateway."""
_LOGGER.debug("Received report: %s", status)
ch_active = status.get(self.pyotgw.DATA_SLAVE_CH_ACTIVE)
flame_on = status.get(self.pyotgw.DATA_SLAVE_FLAME_ON)
cooling_active = status.get(self.pyotgw.DATA_SLAVE_COOLING_ACTIVE)
ch_active = status.get(self._gw_vars.DATA_SLAVE_CH_ACTIVE)
flame_on = status.get(self._gw_vars.DATA_SLAVE_FLAME_ON)
cooling_active = status.get(self._gw_vars.DATA_SLAVE_COOLING_ACTIVE)
if ch_active and flame_on:
self._current_operation = STATE_HEAT
elif cooling_active:
self._current_operation = STATE_COOL
else:
self._current_operation = STATE_IDLE
self._current_temperature = status.get(self.pyotgw.DATA_ROOM_TEMP)
self._current_temperature = status.get(self._gw_vars.DATA_ROOM_TEMP)
temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT_OVRD)
temp = status.get(self._gw_vars.DATA_ROOM_SETPOINT_OVRD)
if temp is None:
temp = status.get(self.pyotgw.DATA_ROOM_SETPOINT)
temp = status.get(self._gw_vars.DATA_ROOM_SETPOINT)
self._target_temperature = temp
# GPIO mode 5: 0 == Away
# GPIO mode 6: 1 == Away
gpio_a_state = status.get(self.pyotgw.OTGW_GPIO_A)
gpio_a_state = status.get(self._gw_vars.OTGW_GPIO_A)
if gpio_a_state == 5:
self._away_mode_a = 0
elif gpio_a_state == 6:
self._away_mode_a = 1
else:
self._away_mode_a = None
gpio_b_state = status.get(self.pyotgw.OTGW_GPIO_B)
gpio_b_state = status.get(self._gw_vars.OTGW_GPIO_B)
if gpio_b_state == 5:
self._away_mode_b = 0
elif gpio_b_state == 6:
@@ -104,11 +89,11 @@ class OpenThermGateway(ClimateDevice):
else:
self._away_mode_b = None
if self._away_mode_a is not None:
self._away_state_a = (status.get(self.pyotgw.OTGW_GPIO_A_STATE) ==
self._away_mode_a)
self._away_state_a = (status.get(
self._gw_vars.OTGW_GPIO_A_STATE) == self._away_mode_a)
if self._away_mode_b is not None:
self._away_state_b = (status.get(self.pyotgw.OTGW_GPIO_B_STATE) ==
self._away_mode_b)
self._away_state_b = (status.get(
self._gw_vars.OTGW_GPIO_B_STATE) == self._away_mode_b)
self.async_schedule_update_ha_state()
@property
@@ -170,7 +155,7 @@ class OpenThermGateway(ClimateDevice):
"""Set new target temperature."""
if ATTR_TEMPERATURE in kwargs:
temp = float(kwargs[ATTR_TEMPERATURE])
self._target_temperature = await self.gateway.set_target_temp(
self._target_temperature = await self._gateway.set_target_temp(
temp)
self.async_schedule_update_ha_state()

View File

@@ -123,26 +123,6 @@ nuheat_resume_program:
description: Name(s) of entities to change.
example: 'climate.kitchen'
econet_add_vacation:
description: Add a vacation to your water heater.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.water_heater'
start_date:
description: The timestamp of when the vacation should start. (Optional, defaults to now)
example: 1513186320
end_date:
description: The timestamp of when the vacation should end.
example: 1513445520
econet_delete_vacation:
description: Delete your existing vacation from your water heater.
fields:
entity_id:
description: Name(s) of entities to change.
example: 'climate.water_heater'
sensibo_assume_state:
description: Set Sensibo device to external state.
fields:

View File

@@ -8,7 +8,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.toon/
"""
from homeassistant.components.climate import (
ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_PERFORMANCE,
ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_AUTO,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
import homeassistant.components.toon as toon_main
from homeassistant.const import TEMP_CELSIUS
@@ -34,7 +34,7 @@ class ThermostatDevice(ClimateDevice):
self._temperature = None
self._setpoint = None
self._operation_list = [
STATE_PERFORMANCE,
STATE_AUTO,
STATE_HEAT,
STATE_ECO,
STATE_COOL,
@@ -84,7 +84,7 @@ class ThermostatDevice(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set new operation mode."""
toonlib_values = {
STATE_PERFORMANCE: 'Comfort',
STATE_AUTO: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep',

View File

@@ -7,8 +7,7 @@ https://home-assistant.io/components/climate.tuya/
from homeassistant.components.climate import (
ATTR_TEMPERATURE, ENTITY_ID_FORMAT, STATE_AUTO, STATE_COOL, STATE_ECO,
STATE_ELECTRIC, STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP,
STATE_HIGH_DEMAND, STATE_PERFORMANCE, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH
from homeassistant.components.tuya import DATA_TUYA, TuyaDevice
@@ -23,13 +22,8 @@ HA_STATE_TO_TUYA = {
STATE_AUTO: 'auto',
STATE_COOL: 'cold',
STATE_ECO: 'eco',
STATE_ELECTRIC: 'electric',
STATE_FAN_ONLY: 'wind',
STATE_GAS: 'gas',
STATE_HEAT: 'hot',
STATE_HEAT_PUMP: 'heat_pump',
STATE_HIGH_DEMAND: 'high_demand',
STATE_PERFORMANCE: 'performance',
}
TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()}

View File

@@ -1,5 +1,5 @@
"""
Support for Wink thermostats, Air Conditioners, and Water Heaters.
Support for Wink thermostats and Air Conditioners.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
@@ -8,9 +8,9 @@ import logging
from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW,
ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_ELECTRIC,
STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, STATE_HIGH_DEMAND,
STATE_PERFORMANCE, SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO,
STATE_FAN_ONLY, STATE_HEAT, SUPPORT_AUX_HEAT,
SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW,
ClimateDevice)
@@ -24,11 +24,9 @@ _LOGGER = logging.getLogger(__name__)
ATTR_ECO_TARGET = 'eco_target'
ATTR_EXTERNAL_TEMPERATURE = 'external_temperature'
ATTR_OCCUPIED = 'occupied'
ATTR_RHEEM_TYPE = 'rheem_type'
ATTR_SCHEDULE_ENABLED = 'schedule_enabled'
ATTR_SMART_TEMPERATURE = 'smart_temperature'
ATTR_TOTAL_CONSUMPTION = 'total_consumption'
ATTR_VACATION_MODE = 'vacation_mode'
ATTR_HEAT_ON = 'heat_on'
ATTR_COOL_ON = 'cool_on'
@@ -42,14 +40,9 @@ HA_STATE_TO_WINK = {
STATE_AUTO: 'auto',
STATE_COOL: 'cool_only',
STATE_ECO: 'eco',
STATE_ELECTRIC: 'electric_only',
STATE_FAN_ONLY: 'fan_only',
STATE_GAS: 'gas',
STATE_HEAT: 'heat_only',
STATE_HEAT_PUMP: 'heat_pump',
STATE_HIGH_DEMAND: 'high_demand',
STATE_OFF: 'off',
STATE_PERFORMANCE: 'performance',
}
WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()}
@@ -62,9 +55,6 @@ SUPPORT_FLAGS_THERMOSTAT = (
SUPPORT_FLAGS_AC = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_FAN_MODE)
SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE |
SUPPORT_AWAY_MODE)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Wink climate devices."""
@@ -77,10 +67,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_id = climate.object_id() + climate.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_entities([WinkAC(climate, hass)])
for water_heater in pywink.get_water_heaters():
_id = water_heater.object_id() + water_heater.name()
if _id not in hass.data[DOMAIN]['unique_ids']:
add_entities([WinkWaterHeater(water_heater, hass)])
class WinkThermostat(WinkDevice, ClimateDevice):
@@ -504,93 +490,3 @@ class WinkAC(WinkDevice, ClimateDevice):
elif fan_mode == SPEED_HIGH:
speed = 1.0
self.wink.set_ac_fan_speed(speed)
class WinkWaterHeater(WinkDevice, ClimateDevice):
"""Representation of a Wink water heater."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS_HEATER
@property
def temperature_unit(self):
"""Return the unit of measurement."""
# The Wink API always returns temp in Celsius
return TEMP_CELSIUS
@property
def device_state_attributes(self):
"""Return the optional device state attributes."""
data = {}
data[ATTR_VACATION_MODE] = self.wink.vacation_mode_enabled()
data[ATTR_RHEEM_TYPE] = self.wink.rheem_type()
return data
@property
def current_operation(self):
"""
Return current operation one of the following.
["eco", "performance", "heat_pump",
"high_demand", "electric_only", "gas]
"""
if not self.wink.is_on():
current_op = STATE_OFF
else:
current_op = WINK_STATE_TO_HA.get(self.wink.current_mode())
if current_op is None:
current_op = STATE_UNKNOWN
return current_op
@property
def operation_list(self):
"""List of available operation modes."""
op_list = ['off']
modes = self.wink.modes()
for mode in modes:
if mode == 'aux':
continue
ha_mode = WINK_STATE_TO_HA.get(mode)
if ha_mode is not None:
op_list.append(ha_mode)
else:
error = "Invalid operation mode mapping. " + mode + \
" doesn't map. Please report this."
_LOGGER.error(error)
return op_list
def set_temperature(self, **kwargs):
"""Set new target temperature."""
target_temp = kwargs.get(ATTR_TEMPERATURE)
self.wink.set_temperature(target_temp)
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode)
self.wink.set_operation_mode(op_mode_to_set)
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self.wink.current_set_point()
def turn_away_mode_on(self):
"""Turn away on."""
self.wink.set_vacation_mode(True)
def turn_away_mode_off(self):
"""Turn away off."""
self.wink.set_vacation_mode(False)
@property
def min_temp(self):
"""Return the minimum temperature."""
return self.wink.min_set_point()
@property
def max_temp(self):
"""Return the maximum temperature."""
return self.wink.max_set_point()

View File

@@ -18,21 +18,23 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (async_dispatcher_connect,
async_dispatcher_send)
REQUIREMENTS = ['zhong_hong_hvac==1.0.9']
_LOGGER = logging.getLogger(__name__)
CONF_GATEWAY_ADDRRESS = 'gateway_address'
REQUIREMENTS = ['zhong_hong_hvac==1.0.9']
DEFAULT_PORT = 9999
DEFAULT_GATEWAY_ADDRRESS = 1
SIGNAL_DEVICE_ADDED = 'zhong_hong_device_added'
SIGNAL_ZHONG_HONG_HUB_START = 'zhong_hong_hub_start'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST):
cv.string,
vol.Optional(CONF_PORT, default=9999):
vol.Coerce(int),
vol.Optional(CONF_GATEWAY_ADDRRESS, default=1):
vol.Coerce(int),
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_GATEWAY_ADDRRESS, default=DEFAULT_GATEWAY_ADDRRESS):
cv.positive_int,
})

View File

@@ -6,14 +6,15 @@ https://home-assistant.io/components/climate.zwave/
"""
# Because we do not compile openzwave on CI
import logging
from homeassistant.core import callback
from homeassistant.components.climate import (
DOMAIN, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import
ZWaveDeviceEntity, async_setup_platform)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
@@ -42,6 +43,22 @@ STATE_MAPPINGS = {
}
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave climate devices."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Climate device from Config Entry."""
@callback
def async_add_climate(climate):
"""Add Z-Wave Climate Device."""
async_add_entities([climate])
async_dispatcher_connect(hass, 'zwave_new_climate', async_add_climate)
def get_device(hass, values, **kwargs):
"""Create Z-Wave entity device."""
temp_unit = hass.config.units.temperature_unit

View File

@@ -4,20 +4,16 @@ Component to integrate the Home Assistant cloud.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/cloud/
"""
import asyncio
from datetime import datetime
from datetime import datetime, timedelta
import json
import logging
import os
import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME)
from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import helpers as ga_h
@@ -129,7 +125,6 @@ class Cloud:
self._google_actions = google_actions
self._gactions_config = None
self._prefs = None
self.jwt_keyset = None
self.id_token = None
self.access_token = None
self.refresh_token = None
@@ -162,7 +157,7 @@ class Cloud:
@property
def subscription_expired(self):
"""Return a boolean if the subscription has expired."""
return dt_util.utcnow() > self.expiration_date
return dt_util.utcnow() > self.expiration_date + timedelta(days=7)
@property
def expiration_date(self):
@@ -262,13 +257,6 @@ class Cloud:
}
self._prefs = prefs
success = await self._fetch_jwt_keyset()
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.event.async_call_later(5, self.async_start)
return
def load_config():
"""Load config."""
# Ensure config dir exists
@@ -288,14 +276,6 @@ class Cloud:
if info is None:
return
# Validate tokens
try:
for token in 'id_token', 'access_token':
self._decode_claims(info[token])
except ValueError as err: # Raised when token is invalid
_LOGGER.warning("Found invalid token %s: %s", token, err)
return
self.id_token = info['id_token']
self.access_token = info['access_token']
self.refresh_token = info['refresh_token']
@@ -311,49 +291,7 @@ class Cloud:
self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled
await self._store.async_save(self._prefs)
async def _fetch_jwt_keyset(self):
"""Fetch the JWT keyset for the Cognito instance."""
session = async_get_clientsession(self.hass)
url = ("https://cognito-idp.us-east-1.amazonaws.com/"
"{}/.well-known/jwks.json".format(self.user_pool_id))
try:
with async_timeout.timeout(10, loop=self.hass.loop):
req = await session.get(url)
self.jwt_keyset = await req.json()
return True
except (asyncio.TimeoutError, aiohttp.ClientError) as err:
_LOGGER.error("Error fetching Cognito keyset: %s", err)
return False
def _decode_claims(self, token):
def _decode_claims(self, token): # pylint: disable=no-self-use
"""Decode the claims in a token."""
from jose import jwt, exceptions as jose_exceptions
try:
header = jwt.get_unverified_header(token)
except jose_exceptions.JWTError as err:
raise ValueError(str(err)) from None
kid = header.get('kid')
if kid is None:
raise ValueError("No kid in header")
# Locate the key for this kid
key = None
for key_dict in self.jwt_keyset['keys']:
if key_dict['kid'] == kid:
key = key_dict
break
if not key:
raise ValueError(
"Unable to locate kid ({}) in keyset".format(kid))
try:
return jwt.decode(
token, key, audience=self.cognito_client_id, options={
'verify_exp': False,
})
except jose_exceptions.JWTError as err:
raise ValueError(str(err)) from None
from jose import jwt
return jwt.get_unverified_claims(token)

View File

@@ -113,6 +113,24 @@ def check_token(cloud):
raise _map_aws_exception(err)
def renew_access_token(cloud):
"""Renew access token."""
from botocore.exceptions import ClientError
cognito = _cognito(
cloud,
access_token=cloud.access_token,
refresh_token=cloud.refresh_token)
try:
cognito.renew_access_token()
cloud.id_token = cognito.id_token
cloud.access_token = cognito.access_token
cloud.write_user_info()
except ClientError as err:
raise _map_aws_exception(err)
def _authenticate(cloud, email, password):
"""Log in and return an authenticated Cognito instance."""
from botocore.exceptions import ClientError

View File

@@ -14,7 +14,7 @@ from homeassistant.components import websocket_api
from . import auth_api
from .const import DOMAIN, REQUEST_TIMEOUT
from .iot import STATE_DISCONNECTED
from .iot import STATE_DISCONNECTED, STATE_CONNECTED
_LOGGER = logging.getLogger(__name__)
@@ -249,13 +249,28 @@ async def websocket_subscription(hass, connection, msg):
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
response = await cloud.fetch_subscription_info()
if response.status == 200:
connection.send_message(websocket_api.result_message(
msg['id'], await response.json()))
else:
if response.status != 200:
connection.send_message(websocket_api.error_message(
msg['id'], 'request_failed', 'Failed to request subscription'))
data = await response.json()
# Check if a user is subscribed but local info is outdated
# In that case, let's refresh and reconnect
if data.get('provider') and cloud.iot.state != STATE_CONNECTED:
_LOGGER.debug(
"Found disconnected account with valid subscriotion, connecting")
await hass.async_add_executor_job(
auth_api.renew_access_token, cloud)
# Cancel reconnect in progress
if cloud.iot.state != STATE_DISCONNECTED:
await cloud.iot.disconnect()
hass.async_create_task(cloud.iot.connect())
connection.send_message(websocket_api.result_message(msg['id'], data))
@websocket_api.async_response
async def websocket_update_prefs(hass, connection, msg):

View File

@@ -92,6 +92,7 @@ def _user_info(user):
'is_owner': user.is_owner,
'is_active': user.is_active,
'system_generated': user.system_generated,
'group_ids': [group.id for group in user.groups],
'credentials': [
{
'type': c.auth_provider_type,

View File

@@ -5,10 +5,10 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.deconz/
"""
from homeassistant.components.deconz.const import (
COVER_TYPES, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB,
DECONZ_DOMAIN)
COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB, DECONZ_DOMAIN, WINDOW_COVERS)
from homeassistant.components.cover import (
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN,
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP,
SUPPORT_SET_POSITION)
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
@@ -16,6 +16,8 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['deconz']
ZIGBEE_SPEC = ['lumi.curtain']
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
@@ -34,7 +36,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = []
for light in lights:
if light.type in COVER_TYPES:
entities.append(DeconzCover(light))
if light.modelid in ZIGBEE_SPEC:
entities.append(DeconzCoverZigbeeSpec(light))
else:
entities.append(DeconzCover(light))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
@@ -49,7 +54,10 @@ class DeconzCover(CoverDevice):
def __init__(self, cover):
"""Set up cover and add update callback to get data from websocket."""
self._cover = cover
self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
self._features = SUPPORT_OPEN
self._features |= SUPPORT_CLOSE
self._features |= SUPPORT_STOP
self._features |= SUPPORT_SET_POSITION
async def async_added_to_hass(self):
"""Subscribe to covers events."""
@@ -91,7 +99,11 @@ class DeconzCover(CoverDevice):
@property
def device_class(self):
"""Return the class of the cover."""
return 'damper'
if self._cover.type in DAMPERS:
return 'damper'
if self._cover.type in WINDOW_COVERS:
return 'window'
return None
@property
def supported_features(self):
@@ -127,6 +139,11 @@ class DeconzCover(CoverDevice):
data = {ATTR_POSITION: 0}
await self.async_set_cover_position(**data)
async def async_stop_cover(self, **kwargs):
"""Stop cover."""
data = {'bri_inc': 0}
await self._cover.async_set_state(data)
@property
def device_info(self):
"""Return a device description for device registry."""
@@ -144,3 +161,26 @@ class DeconzCover(CoverDevice):
'sw_version': self._cover.swversion,
'via_hub': (DECONZ_DOMAIN, bridgeid),
}
class DeconzCoverZigbeeSpec(DeconzCover):
"""Zigbee spec is the inverse of how deCONZ normally reports attributes."""
@property
def current_cover_position(self):
"""Return the current position of the cover."""
return 100 - int(self._cover.brightness / 255 * 100)
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._cover.state
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
position = kwargs[ATTR_POSITION]
data = {'on': False}
if position < 100:
data['on'] = True
data['bri'] = 255 - int(position / 100 * 255)
await self._cover.async_set_state(data)

View File

@@ -64,7 +64,7 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
entities = []
for device_name in discovery_info[ATTR_DISCOVER_DEVICES]:
device = hass.data[DATA_KNX].xknx.devices[device_name]
entities.append(KNXCover(hass, device))
entities.append(KNXCover(device))
async_add_entities(entities)
@@ -88,18 +88,15 @@ def async_add_entities_config(hass, config, async_add_entities):
invert_angle=config.get(CONF_INVERT_ANGLE))
hass.data[DATA_KNX].xknx.devices.add(cover)
async_add_entities([KNXCover(hass, cover)])
async_add_entities([KNXCover(cover)])
class KNXCover(CoverDevice):
"""Representation of a KNX cover."""
def __init__(self, hass, device):
def __init__(self, device):
"""Initialize the cover."""
self.device = device
self.hass = hass
self.async_register_callbacks()
self._unsubscribe_auto_updater = None
@callback
@@ -110,6 +107,10 @@ class KNXCover(CoverDevice):
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)
async def async_added_to_hass(self):
"""Store register state change callback."""
self.async_register_callbacks()
@property
def name(self):
"""Return the name of the KNX device."""

View File

@@ -19,12 +19,12 @@ from homeassistant.components.cover import (
from homeassistant.exceptions import TemplateError
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN)
STATE_CLOSED, STATE_UNKNOWN, CONF_DEVICE)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
CONF_QOS, CONF_RETAIN, valid_publish_topic, valid_subscribe_topic,
MqttAvailability, MqttDiscoveryUpdate)
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -96,6 +96,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TILT_INVERT_STATE,
default=DEFAULT_TILT_INVERT_STATE): cv.boolean,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -155,11 +156,13 @@ async def _async_setup_entity(hass, config, async_add_entities,
config.get(CONF_POSITION_TOPIC),
set_position_template,
config.get(CONF_UNIQUE_ID),
config.get(CONF_DEVICE),
discovery_hash
)])
class MqttCover(MqttAvailability, MqttDiscoveryUpdate, CoverDevice):
class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
def __init__(self, name, state_topic, command_topic, availability_topic,
@@ -169,11 +172,13 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, CoverDevice):
optimistic, value_template, tilt_open_position,
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
tilt_invert, position_topic, set_position_template,
unique_id: Optional[str], discovery_hash):
unique_id: Optional[str], device_config: Optional[ConfigType],
discovery_hash):
"""Initialize the cover."""
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
MqttEntityDeviceInfo.__init__(self, device_config)
self._position = None
self._state = None
self._name = name

View File

@@ -9,8 +9,9 @@ import logging
import voluptuous as vol
from homeassistant.components.rflink import (
DATA_ENTITY_GROUP_LOOKUP, DATA_ENTITY_LOOKUP,
DEVICE_DEFAULTS_SCHEMA, EVENT_KEY_COMMAND, RflinkCommand)
CONF_ALIASES, CONF_DEVICE_DEFAULTS, CONF_DEVICES, CONF_FIRE_EVENT,
CONF_GROUP, CONF_GROUP_ALIASES, CONF_NOGROUP_ALIASES,
CONF_SIGNAL_REPETITIONS, DEVICE_DEFAULTS_SCHEMA, RflinkCommand)
from homeassistant.components.cover import (
CoverDevice, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
@@ -22,19 +23,6 @@ DEPENDENCIES = ['rflink']
_LOGGER = logging.getLogger(__name__)
CONF_ALIASES = 'aliases'
CONF_GROUP_ALIASES = 'group_aliases'
CONF_GROUP = 'group'
CONF_NOGROUP_ALIASES = 'nogroup_aliases'
CONF_DEVICE_DEFAULTS = 'device_defaults'
CONF_DEVICES = 'devices'
CONF_AUTOMATIC_ADD = 'automatic_add'
CONF_FIRE_EVENT = 'fire_event'
CONF_IGNORE_DEVICES = 'ignore_devices'
CONF_RECONNECT_INTERVAL = 'reconnect_interval'
CONF_SIGNAL_REPETITIONS = 'signal_repetitions'
CONF_WAIT_FOR_ACK = 'wait_for_ack'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})):
DEVICE_DEFAULTS_SCHEMA,
@@ -55,33 +43,21 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def devices_from_config(domain_config, hass=None):
def devices_from_config(domain_config):
"""Parse configuration and add Rflink cover devices."""
devices = []
for device_id, config in domain_config[CONF_DEVICES].items():
device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config)
device = RflinkCover(device_id, hass, **device_config)
device = RflinkCover(device_id, **device_config)
devices.append(device)
# Register entity (and aliases) to listen to incoming rflink events
# Device id and normal aliases respond to normal and group command
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
if config[CONF_GROUP]:
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][device_id].append(device)
for _id in config[CONF_ALIASES]:
hass.data[DATA_ENTITY_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
hass.data[DATA_ENTITY_GROUP_LOOKUP][
EVENT_KEY_COMMAND][_id].append(device)
return devices
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Rflink cover platform."""
async_add_entities(devices_from_config(config, hass))
async_add_entities(devices_from_config(config))
class RflinkCover(RflinkCommand, CoverDevice):

View File

@@ -1,103 +0,0 @@
"""
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_entities, 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_entities(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

@@ -278,9 +278,10 @@ class CoverTemplate(CoverDevice):
async def async_open_cover(self, **kwargs):
"""Move the cover up."""
if self._open_script:
await self._open_script.async_run()
await self._open_script.async_run(context=self._context)
elif self._position_script:
await self._position_script.async_run({"position": 100})
await self._position_script.async_run(
{"position": 100}, context=self._context)
if self._optimistic:
self._position = 100
self.async_schedule_update_ha_state()
@@ -288,9 +289,10 @@ class CoverTemplate(CoverDevice):
async def async_close_cover(self, **kwargs):
"""Move the cover down."""
if self._close_script:
await self._close_script.async_run()
await self._close_script.async_run(context=self._context)
elif self._position_script:
await self._position_script.async_run({"position": 0})
await self._position_script.async_run(
{"position": 0}, context=self._context)
if self._optimistic:
self._position = 0
self.async_schedule_update_ha_state()
@@ -298,20 +300,21 @@ class CoverTemplate(CoverDevice):
async def async_stop_cover(self, **kwargs):
"""Fire the stop action."""
if self._stop_script:
await self._stop_script.async_run()
await self._stop_script.async_run(context=self._context)
async def async_set_cover_position(self, **kwargs):
"""Set cover position."""
self._position = kwargs[ATTR_POSITION]
await self._position_script.async_run(
{"position": self._position})
{"position": self._position}, context=self._context)
if self._optimistic:
self.async_schedule_update_ha_state()
async def async_open_cover_tilt(self, **kwargs):
"""Tilt the cover open."""
self._tilt_value = 100
await self._tilt_script.async_run({"tilt": self._tilt_value})
await self._tilt_script.async_run(
{"tilt": self._tilt_value}, context=self._context)
if self._tilt_optimistic:
self.async_schedule_update_ha_state()
@@ -319,14 +322,15 @@ class CoverTemplate(CoverDevice):
"""Tilt the cover closed."""
self._tilt_value = 0
await self._tilt_script.async_run(
{"tilt": self._tilt_value})
{"tilt": self._tilt_value}, context=self._context)
if self._tilt_optimistic:
self.async_schedule_update_ha_state()
async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
self._tilt_value = kwargs[ATTR_TILT_POSITION]
await self._tilt_script.async_run({"tilt": self._tilt_value})
await self._tilt_script.async_run(
{"tilt": self._tilt_value}, context=self._context)
if self._tilt_optimistic:
self.async_schedule_update_ha_state()

View File

@@ -4,21 +4,37 @@ Support for Z-Wave cover components.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover.zwave/
"""
# Because we do not compile openzwave on CI
# pylint: disable=import-error
import logging
from homeassistant.core import callback
from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION)
from homeassistant.components import zwave
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import
ZWaveDeviceEntity, async_setup_platform, workaround)
from homeassistant.components.zwave import (
ZWaveDeviceEntity, workaround)
from homeassistant.components.cover import CoverDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave covers."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Cover from Config Entry."""
@callback
def async_add_cover(cover):
"""Add Z-Wave Cover."""
async_add_entities([cover])
async_dispatcher_connect(hass, 'zwave_new_cover', async_add_cover)
def get_device(hass, values, node_config, **kwargs):
"""Create Z-Wave entity device."""
invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS)

View File

@@ -28,6 +28,6 @@
"title": "Op\u00e7\u00f5es extra de configura\u00e7\u00e3o para deCONZ"
}
},
"title": "deCONZ"
"title": "Gateway Zigbee deCONZ"
}
}

View File

@@ -28,6 +28,6 @@
"title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f deCONZ"
}
},
"title": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0448\u043b\u044e\u0437 deCONZ"
"title": "deCONZ"
}
}

View File

@@ -3,7 +3,7 @@
"abort": {
"already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"no_bridges": "\u672a\u641c\u5c0b\u5230 deCONZ Bridfe",
"one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u5be6\u4f8b"
"one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u7269\u4ef6"
},
"error": {
"no_key": "\u7121\u6cd5\u53d6\u5f97 API key"

View File

@@ -16,7 +16,9 @@ CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
ATTR_DARK = 'dark'
ATTR_ON = 'on'
COVER_TYPES = ["Level controllable output"]
DAMPERS = ["Level controllable output"]
WINDOW_COVERS = ["Window covering device"]
COVER_TYPES = DAMPERS + WINDOW_COVERS
POWER_PLUGS = ["On/Off plug-in unit", "Smart plug"]
SIRENS = ["Warning device"]

View File

@@ -10,20 +10,26 @@ import re
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}')
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
DEFAULT_SSL = False
DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
@@ -39,7 +45,9 @@ class DdWrtDeviceScanner(DeviceScanner):
"""This class queries a wireless router running DD-WRT firmware."""
def __init__(self, config):
"""Initialize the scanner."""
"""Initialize the DD-WRT scanner."""
self.protocol = 'https' if config[CONF_SSL] else 'http'
self.verify_ssl = config[CONF_VERIFY_SSL]
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -48,7 +56,8 @@ class DdWrtDeviceScanner(DeviceScanner):
self.mac2name = {}
# Test the router is accessible
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
url = '{}://{}/Status_Wireless.live.asp'.format(
self.protocol, self.host)
data = self.get_ddwrt_data(url)
if not data:
raise ConnectionError('Cannot connect to DD-Wrt router')
@@ -63,7 +72,8 @@ class DdWrtDeviceScanner(DeviceScanner):
"""Return the name of the given device or None if we don't know."""
# If not initialised and not already scanned and not found.
if device not in self.mac2name:
url = 'http://{}/Status_Lan.live.asp'.format(self.host)
url = '{}://{}/Status_Lan.live.asp'.format(
self.protocol, self.host)
data = self.get_ddwrt_data(url)
if not data:
@@ -98,7 +108,8 @@ class DdWrtDeviceScanner(DeviceScanner):
"""
_LOGGER.info("Checking ARP")
url = 'http://{}/Status_Wireless.live.asp'.format(self.host)
url = '{}://{}/Status_Wireless.live.asp'.format(
self.protocol, self.host)
data = self.get_ddwrt_data(url)
if not data:
@@ -125,7 +136,8 @@ class DdWrtDeviceScanner(DeviceScanner):
"""Retrieve data from DD-WRT and return parsed result."""
try:
response = requests.get(
url, auth=(self.username, self.password), timeout=4)
url, auth=(self.username, self.password),
timeout=4, verify=self.verify_ssl)
except requests.exceptions.Timeout:
_LOGGER.exception("Connection to the router timed out")
return
@@ -142,5 +154,4 @@ class DdWrtDeviceScanner(DeviceScanner):
def _parse_ddwrt_response(data_str):
"""Parse the DD-WRT data format."""
return {
key: val for key, val in _DDWRT_DATA_REGEX
.findall(data_str)}
key: val for key, val in _DDWRT_DATA_REGEX.findall(data_str)}

View File

@@ -43,8 +43,7 @@ class FritzBoxScanner(DeviceScanner):
self.password = config[CONF_PASSWORD]
self.success_init = True
# pylint: disable=import-error
import fritzconnection as fc
import fritzconnection as fc # pylint: disable=import-error
# Establish a connection to the FRITZ!Box.
try:

View File

@@ -19,7 +19,7 @@ from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify, dt as dt_util
REQUIREMENTS = ['locationsharinglib==3.0.3']
REQUIREMENTS = ['locationsharinglib==3.0.6']
_LOGGER = logging.getLogger(__name__)

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL,
CONF_DEVICES, CONF_EXCLUDE)
REQUIREMENTS = ['pynetgear==0.4.2']
REQUIREMENTS = ['pynetgear==0.5.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -0,0 +1,69 @@
"""
Support for Verizon FiOS Quantum Gateways.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.quantum_gateway/
"""
import logging
from requests.exceptions import RequestException
import voluptuous as vol
from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA,
DeviceScanner)
from homeassistant.const import (CONF_HOST, CONF_PASSWORD)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['quantum-gateway==0.0.3']
_LOGGER = logging.getLogger(__name__)
DEFAULT_HOST = 'myfiosgateway.com'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def get_scanner(hass, config):
"""Validate the configuration and return a Quantum Gateway scanner."""
scanner = QuantumGatewayDeviceScanner(config[DOMAIN])
return scanner if scanner.success_init else None
class QuantumGatewayDeviceScanner(DeviceScanner):
"""This class queries a Quantum Gateway."""
def __init__(self, config):
"""Initialize the scanner."""
from quantum_gateway import QuantumGatewayScanner
self.host = config[CONF_HOST]
self.password = config[CONF_PASSWORD]
_LOGGER.debug('Initializing')
try:
self.quantum = QuantumGatewayScanner(self.host, self.password)
self.success_init = self.quantum.success_init
except RequestException:
self.success_init = False
_LOGGER.error("Unable to connect to gateway. Check host.")
if not self.success_init:
_LOGGER.error("Unable to login to gateway. Check password and "
"host.")
def scan_devices(self):
"""Scan for new devices and return a list of found MACs."""
connected_devices = []
try:
connected_devices = self.quantum.scan_devices()
except RequestException:
_LOGGER.error("Unable to scan devices. Check connection to router")
return connected_devices
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
return self.quantum.get_device_name(device)

View File

@@ -86,10 +86,9 @@ class UnifiDeviceScanner(DeviceScanner):
def _disconnect(self):
"""Disconnect the current SSH connection."""
# pylint: disable=broad-except
try:
self.ssh.logout()
except Exception:
except Exception: # pylint: disable=broad-except
pass
finally:
self.ssh = None

View File

@@ -8,10 +8,12 @@ import logging
import voluptuous as vol
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_TOKEN
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA,
DeviceScanner)
from homeassistant.const import (CONF_HOST, CONF_TOKEN)
REQUIREMENTS = ['python-miio==0.4.2', 'construct==2.9.45']
_LOGGER = logging.getLogger(__name__)
@@ -20,8 +22,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TOKEN): vol.All(cv.string, vol.Length(min=32, max=32)),
})
REQUIREMENTS = ['python-miio==0.4.1', 'construct==2.9.41']
def get_scanner(hass, config):
"""Return a Xiaomi MiIO device scanner."""
@@ -56,7 +56,7 @@ class XiaomiMiioDeviceScanner(DeviceScanner):
self.device = device
async def async_scan_devices(self):
"""Scan for devices and return a list containing found device ids."""
"""Scan for devices and return a list containing found device IDs."""
from miio import DeviceException
devices = []
@@ -68,7 +68,7 @@ class XiaomiMiioDeviceScanner(DeviceScanner):
devices.append(device['mac'])
except DeviceException as ex:
_LOGGER.error("Got exception while fetching the state: %s", ex)
_LOGGER.error("Unable to fetch the state: %s", ex)
return devices

View File

@@ -21,7 +21,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==2.1.0']
REQUIREMENTS = ['netdisco==2.2.0']
DOMAIN = 'discovery'
@@ -43,6 +43,7 @@ SERVICE_DAIKIN = 'daikin'
SERVICE_SABNZBD = 'sabnzbd'
SERVICE_SAMSUNG_PRINTER = 'samsung_printer'
SERVICE_HOMEKIT = 'homekit'
SERVICE_OCTOPRINT = 'octoprint'
CONFIG_ENTRY_HANDLERS = {
SERVICE_DECONZ: 'deconz',
@@ -67,6 +68,7 @@ SERVICE_HANDLERS = {
SERVICE_SABNZBD: ('sabnzbd', None),
SERVICE_SAMSUNG_PRINTER: ('sensor', 'syncthru'),
SERVICE_KONNECTED: ('konnected', None),
SERVICE_OCTOPRINT: ('octoprint', None),
'panasonic_viera': ('media_player', 'panasonic_viera'),
'plex_mediaserver': ('media_player', 'plex'),
'roku': ('media_player', 'roku'),
@@ -84,6 +86,7 @@ SERVICE_HANDLERS = {
'songpal': ('media_player', 'songpal'),
'kodi': ('media_player', 'kodi'),
'volumio': ('media_player', 'volumio'),
'lg_smart_device': ('media_player', 'lg_soundbar'),
'nanoleaf_aurora': ('light', 'nanoleaf_aurora'),
'freebox': ('device_tracker', 'freebox'),
}

View File

@@ -102,5 +102,6 @@ def setup(hass, config):
discovery.load_platform(hass, "sensor", DOMAIN, {}, config)
discovery.load_platform(hass, "fan", DOMAIN, {}, config)
discovery.load_platform(hass, "vacuum", DOMAIN, {}, config)
discovery.load_platform(hass, "climate", DOMAIN, {}, config)
return True

View File

@@ -0,0 +1,233 @@
"""
Support the ElkM1 Gold and ElkM1 EZ8 alarm / integration panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/elkm1/
"""
import logging
import re
import voluptuous as vol
from homeassistant.const import (
CONF_EXCLUDE, CONF_HOST, CONF_INCLUDE, CONF_PASSWORD,
CONF_TEMPERATURE_UNIT, CONF_USERNAME, TEMP_FAHRENHEIT)
from homeassistant.core import HomeAssistant, callback # noqa
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType # noqa
DOMAIN = "elkm1"
REQUIREMENTS = ['elkm1-lib==0.7.10']
CONF_AREA = 'area'
CONF_COUNTER = 'counter'
CONF_KEYPAD = 'keypad'
CONF_OUTPUT = 'output'
CONF_SETTING = 'setting'
CONF_TASK = 'task'
CONF_THERMOSTAT = 'thermostat'
CONF_PLC = 'plc'
CONF_ZONE = 'zone'
CONF_ENABLED = 'enabled'
_LOGGER = logging.getLogger(__name__)
SUPPORTED_DOMAINS = ['alarm_control_panel', 'climate', 'light', 'scene',
'sensor', 'switch']
SPEAK_SERVICE_SCHEMA = vol.Schema({
vol.Required('number'):
vol.All(vol.Coerce(int), vol.Range(min=0, max=999))
})
def _host_validator(config):
"""Validate that a host is properly configured."""
if config[CONF_HOST].startswith('elks://'):
if CONF_USERNAME not in config or CONF_PASSWORD not in config:
raise vol.Invalid("Specify username and password for elks://")
elif not config[CONF_HOST].startswith('elk://') and not config[
CONF_HOST].startswith('serial://'):
raise vol.Invalid("Invalid host URL")
return config
def _elk_range_validator(rng):
def _housecode_to_int(val):
match = re.search(r'^([a-p])(0[1-9]|1[0-6]|[1-9])$', val.lower())
if match:
return (ord(match.group(1)) - ord('a')) * 16 + int(match.group(2))
raise vol.Invalid("Invalid range")
def _elk_value(val):
return int(val) if val.isdigit() else _housecode_to_int(val)
vals = [s.strip() for s in str(rng).split('-')]
start = _elk_value(vals[0])
end = start if len(vals) == 1 else _elk_value(vals[1])
return (start, end)
CONFIG_SCHEMA_SUBDOMAIN = vol.Schema({
vol.Optional(CONF_ENABLED, default=True): cv.boolean,
vol.Optional(CONF_INCLUDE, default=[]): [_elk_range_validator],
vol.Optional(CONF_EXCLUDE, default=[]): [_elk_range_validator],
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_USERNAME, default=''): cv.string,
vol.Optional(CONF_PASSWORD, default=''): cv.string,
vol.Optional(CONF_TEMPERATURE_UNIT, default=TEMP_FAHRENHEIT):
cv.temperature_unit,
vol.Optional(CONF_AREA): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_COUNTER): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_KEYPAD): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_OUTPUT): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_PLC): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_SETTING): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_TASK): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_THERMOSTAT): CONFIG_SCHEMA_SUBDOMAIN,
vol.Optional(CONF_ZONE): CONFIG_SCHEMA_SUBDOMAIN,
},
_host_validator,
)
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass: HomeAssistant, hass_config: ConfigType) -> bool:
"""Set up the Elk M1 platform."""
from elkm1_lib.const import Max
import elkm1_lib as elkm1
configs = {
CONF_AREA: Max.AREAS.value,
CONF_COUNTER: Max.COUNTERS.value,
CONF_KEYPAD: Max.KEYPADS.value,
CONF_OUTPUT: Max.OUTPUTS.value,
CONF_PLC: Max.LIGHTS.value,
CONF_SETTING: Max.SETTINGS.value,
CONF_TASK: Max.TASKS.value,
CONF_THERMOSTAT: Max.THERMOSTATS.value,
CONF_ZONE: Max.ZONES.value,
}
def _included(ranges, set_to, values):
for rng in ranges:
if not rng[0] <= rng[1] <= len(values):
raise vol.Invalid("Invalid range {}".format(rng))
values[rng[0]-1:rng[1]] = [set_to] * (rng[1] - rng[0] + 1)
conf = hass_config[DOMAIN]
config = {'temperature_unit': conf[CONF_TEMPERATURE_UNIT]}
config['panel'] = {'enabled': True, 'included': [True]}
for item, max_ in configs.items():
config[item] = {'enabled': conf[item][CONF_ENABLED],
'included': [not conf[item]['include']] * max_}
try:
_included(conf[item]['include'], True, config[item]['included'])
_included(conf[item]['exclude'], False, config[item]['included'])
except (ValueError, vol.Invalid) as err:
_LOGGER.error("Config item: %s; %s", item, err)
return False
elk = elkm1.Elk({'url': conf[CONF_HOST], 'userid': conf[CONF_USERNAME],
'password': conf[CONF_PASSWORD]})
elk.connect()
_create_elk_services(hass, elk)
hass.data[DOMAIN] = {'elk': elk, 'config': config, 'keypads': {}}
for component in SUPPORTED_DOMAINS:
hass.async_create_task(
discovery.async_load_platform(hass, component, DOMAIN, {},
hass_config))
return True
def _create_elk_services(hass, elk):
def _speak_word_service(service):
elk.panel.speak_word(service.data.get('number'))
def _speak_phrase_service(service):
elk.panel.speak_phrase(service.data.get('number'))
hass.services.async_register(
DOMAIN, 'speak_word', _speak_word_service, SPEAK_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, 'speak_phrase', _speak_phrase_service, SPEAK_SERVICE_SCHEMA)
def create_elk_entities(hass, elk_elements, element_type, class_, entities):
"""Create the ElkM1 devices of a particular class."""
elk_data = hass.data[DOMAIN]
if elk_data['config'][element_type]['enabled']:
elk = elk_data['elk']
for element in elk_elements:
if elk_data['config'][element_type]['included'][element.index]:
entities.append(class_(element, elk, elk_data))
return entities
class ElkEntity(Entity):
"""Base class for all Elk entities."""
def __init__(self, element, elk, elk_data):
"""Initialize the base of all Elk devices."""
self._elk = elk
self._element = element
self._temperature_unit = elk_data['config']['temperature_unit']
self._unique_id = 'elkm1_{}'.format(
self._element.default_name('_').lower())
@property
def name(self):
"""Name of the element."""
return self._element.name
@property
def unique_id(self):
"""Return unique id of the element."""
return self._unique_id
@property
def should_poll(self) -> bool:
"""Don't poll this device."""
return False
@property
def device_state_attributes(self):
"""Return the default attributes of the element."""
return {**self._element.as_dict(), **self.initial_attrs()}
@property
def available(self):
"""Is the entity available to be updated."""
return self._elk.is_connected()
def initial_attrs(self):
"""Return the underlying element's attributes as a dict."""
attrs = {}
attrs['index'] = self._element.index + 1
return attrs
def _element_changed(self, element, changeset):
pass
@callback
def _element_callback(self, element, changeset):
"""Handle callback from an Elk element that has changed."""
self._element_changed(element, changeset)
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register callback for ElkM1 changes and update entity state."""
self._element.add_callback(self._element_callback)
self._element_callback(self._element, {})

View File

@@ -0,0 +1,12 @@
speak_word:
description: Speak a word. See list of words in ElkM1 ASCII Protocol documentation.
fields:
number:
description: Word number to speak.
example: 142
speak_phrase:
description: Speak a phrase. See list of phrases in ElkM1 ASCII Protocol documentation.
fields:
number:
description: Phrase number to speak.
example: 42

View File

@@ -140,6 +140,6 @@ def setup(hass, config):
_LOGGER.debug("setup(), location = %s", tmp_loc)
load_platform(hass, 'climate', DOMAIN)
load_platform(hass, 'climate', DOMAIN, {}, config)
return True

View File

@@ -10,20 +10,23 @@ from typing import Optional
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components import mqtt
from homeassistant.components import fan, mqtt
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON)
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_DEVICE)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH, FanEntity,
SUPPORT_SET_SPEED, SUPPORT_OSCILLATE,
SPEED_OFF, ATTR_SPEED)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
_LOGGER = logging.getLogger(__name__)
@@ -77,19 +80,32 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
async_add_entities, discovery_info=None):
"""Set up the MQTT fan platform."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
"""Set up MQTT fan through configuration.yaml."""
await _async_setup_entity(hass, config, async_add_entities)
discovery_hash = None
if discovery_info is not None and ATTR_DISCOVERY_HASH in discovery_info:
discovery_hash = discovery_info[ATTR_DISCOVERY_HASH]
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up MQTT fan dynamically through MQTT discovery."""
async def async_discover(discovery_payload):
"""Discover and add a MQTT fan."""
config = PLATFORM_SCHEMA(discovery_payload)
await _async_setup_entity(hass, config, async_add_entities,
discovery_payload[ATTR_DISCOVERY_HASH])
async_dispatcher_connect(
hass, MQTT_DISCOVERY_NEW.format(fan.DOMAIN, 'mqtt'),
async_discover)
async def _async_setup_entity(hass, config, async_add_entities,
discovery_hash=None):
"""Set up the MQTT fan."""
async_add_entities([MqttFan(
config.get(CONF_NAME),
{
@@ -124,21 +140,24 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_UNIQUE_ID),
config.get(CONF_DEVICE),
discovery_hash,
)])
class MqttFan(MqttAvailability, MqttDiscoveryUpdate, FanEntity):
class MqttFan(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
FanEntity):
"""A MQTT fan component."""
def __init__(self, name, topic, templates, qos, retain, payload,
speed_list, optimistic, availability_topic, payload_available,
payload_not_available, unique_id: Optional[str],
discovery_hash):
device_config: Optional[ConfigType], discovery_hash):
"""Initialize the MQTT fan."""
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
MqttEntityDeviceInfo.__init__(self, device_config)
self._name = name
self._topic = topic
self._qos = qos

View File

@@ -224,7 +224,7 @@ class TemplateFan(FanEntity):
# pylint: disable=arguments-differ
async def async_turn_on(self, speed: str = None) -> None:
"""Turn on the fan."""
await self._on_script.async_run()
await self._on_script.async_run(context=self._context)
self._state = STATE_ON
if speed is not None:
@@ -233,7 +233,7 @@ class TemplateFan(FanEntity):
# pylint: disable=arguments-differ
async def async_turn_off(self) -> None:
"""Turn off the fan."""
await self._off_script.async_run()
await self._off_script.async_run(context=self._context)
self._state = STATE_OFF
async def async_set_speed(self, speed: str) -> None:
@@ -243,7 +243,8 @@ class TemplateFan(FanEntity):
if speed in self._speed_list:
self._speed = speed
await self._set_speed_script.async_run({ATTR_SPEED: speed})
await self._set_speed_script.async_run(
{ATTR_SPEED: speed}, context=self._context)
else:
_LOGGER.error(
'Received invalid speed: %s. Expected: %s.',
@@ -257,7 +258,7 @@ class TemplateFan(FanEntity):
if oscillating in _VALID_OSC:
self._oscillating = oscillating
await self._set_oscillating_script.async_run(
{ATTR_OSCILLATING: oscillating})
{ATTR_OSCILLATING: oscillating}, context=self._context)
else:
_LOGGER.error(
'Received invalid oscillating value: %s. Expected: %s.',
@@ -271,7 +272,7 @@ class TemplateFan(FanEntity):
if direction in _VALID_DIRECTIONS:
self._direction = direction
await self._set_direction_script.async_run(
{ATTR_DIRECTION: direction})
{ATTR_DIRECTION: direction}, context=self._context)
else:
_LOGGER.error(
'Received invalid direction: %s. Expected: %s.',

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