Compare commits

...

660 Commits

Author SHA1 Message Date
Paulus Schoutsen
8a3d43745d Bumped version to 0.85.0b1 2019-01-08 20:23:59 -08:00
Pascal Vizeli
6b56985e01 Update OZW to 0.1.2 (#19878)
* Update ozw 0.1.2

* Update requirements_all.txt
2019-01-08 20:23:49 -08:00
Steven Looman
ed41421a3d Fix error when trying to log used UPnP device, if multiple found (#19875) 2019-01-08 20:23:49 -08:00
Alistair Galbraith
f019131352 Resolves #17196, Resolves #18739 - Hue Beyond light fixture errors (#19874)
* Resolves #17196, Resolves #18739 - Hue Beyond light fixtures being incorrectly recognized

* Removed long code lines that were failing code review

* Removed trailing whitespace
2019-01-08 20:23:48 -08:00
Otto Winter
4ec313cb3b Bump aioesphomeapi (#19838) 2019-01-08 20:23:47 -08:00
Fredrik Erlandsson
68e33fdbf5 fixes #19814, Daikin config setting (#19823) 2019-01-08 20:23:47 -08:00
cdheiser
312ad7057d Fix a bug in Lutron RadioRA2 Scene support (#19819) 2019-01-08 20:23:46 -08:00
cdce8p
0d49b19624 Update HAP-python to 2.4.2 (#19776)
* Bugfixes for connection issues
2019-01-08 20:23:46 -08:00
Otto Winter
6d9c37d636 Fix some ESPHome race conditions (#19772)
* Fix some ESPHome race conditions

* Remove debug

* Update requirements_all.txt

* 🚑 Fix IDE line length settings
2019-01-08 20:23:45 -08:00
Sebastian Muszynski
ed881f399f Don't slugify unique id (#19770) 2019-01-08 20:23:45 -08:00
Sebastian Muszynski
3a466195b9 Simplify data_key for a stable unique_id because the order of the dict will not be preserved (Closes: #13522) (#19766) 2019-01-08 20:23:44 -08:00
Alexei Chetroi
3453d31f01 Use manufacturer id only for configure_reporting only when specified. (#19729) 2019-01-08 20:23:44 -08:00
Eliseo Martelli
afa0d37ff0 Rename air pollutants to air quality (#19448)
* mv component folder

* moved in airquality

* changed names in files

* renamed test init

* renamed test air quality

* renamed in tests

* renamed coverage

* fixed naming

* corrected attr names

* changed attr names
2019-01-08 20:23:43 -08:00
Otto Winter
57c96a5489 Add ESPHome native API discovery (#19399)
* ESPHome discovery

* Add note about netdisco

* 🔡

* Address comments

* Bump netdisco to 2.3.0

* Update requirements_all.txt
2019-01-08 20:21:31 -08:00
Paulus Schoutsen
6fb8378b45 Bumped version to 0.85.0b0 2019-01-04 13:47:02 -05:00
Maciej Bieniek
27a9f5a05c Round illumination and lux value to one (#19747) 2019-01-04 18:25:37 +01:00
Daniel Høyer Iversen
16ab799798 Upgrade tibber library (#19768) 2019-01-04 17:59:46 +01:00
Sean Dague
03488af3fb Add mychevy optional country parameter (#19727)
* Add optional country parameter

mychevy 1.2.0 provides the ability to work in canada as well as the us
(there are different service urls for each region). This creates a new
config option to enable it.

* Update mychevy.py
2019-01-04 10:01:47 -05:00
Dan Cinnamon
dbb3802b4e Move envisalink component to package and add services.yaml (#19731)
* Moved component to a package and added a services.yaml file.

* Fixing coverage issue and grammar issue on the services.yaml file.

* Fixed typo in the services.yaml file.
2019-01-04 09:57:32 -05:00
Abílio Costa
ead38f6005 Proactive Alexa ChangeReport messages (#18114)
* Alexa: implement auth and proactive ChangeReport messages

* refactor after rebase from dev to use the new AlexaDirective and Response classes

* move to aiohttp; cleanup

* better function name

* move endpoint to config

* allow passing token function

* remove uneeded state get

* use iterable directly

Co-Authored-By: abmantis <abmantis@users.noreply.github.com>

* missing delete from previous commit

* checks for when user has no auth config

* update cloud component

* PR suggestions

* string lint

* Revert "string lint"

This reverts commit a05a1f134c9ebc7a6e67c093009744f142256365.

* linters are now happier

* more happy linters

* use internal date parser; improve json response handling

* remove unused import

* use await instead of async_add_job

* protect access token update method

* add test_report_state

* line too long

* add docstring

* Update test_smart_home.py

* test accept grant api

* init prefs if None

* add tests for auth and token requests

* replace global with hass.data

* doc lint
2019-01-03 22:28:43 +01:00
Rohan Kapoor
c2525bede2 Filter urllib3.connectionpool warnings in camera.axis and camera.zoneminder (#19641)
* Filter urllib3.connectionpool warnings in camera.axis and camera.zoneminder

* Lint
2019-01-03 11:56:36 -07:00
carstenschroeder
b79057348d Add exception handling to ADS shutdown (#19682)
* Added exception handling to ADS shutdown

* corrected whitespaces

* deleted blank line
2019-01-03 11:47:16 -07:00
ctborg
6b18b92bdd Drop bme680 os_lookup for temp_offset (#19733)
Drops os_lookup, as it isn't needed to set the temperature offset.
2019-01-03 11:35:13 -07:00
Adam Belebczuk
ada0f7cf65 Fix WeMo incorrect mapping of device type during discovery (#19691) 2019-01-03 11:28:40 -07:00
Andrew Hayworth
87a0118082 Do not choke on no awair data (#19708)
* awair: do not choke on no data

The awair API returns an empty response for various air data queries
when a device is offline. The underlying library (python_awair) does
not directly inform us that a device is offline, since we really can
only infer it from an empty response - there is no online/offline
indicator in the graphql API.

So - we should just ensure that we do not attempt to update device state
from an empty response. This ensures that the platform does not crash
when starting up with offline devices, and also ensures that the
platform is marked unavailable once devices go offline.

* Fix typo

Further proof that coding after 10pm is rolling the dice.
2019-01-03 14:41:18 +01:00
ctborg
688bdc6532 Adds ability to calibrate temperature for BME680 (#19684)
* Adds temperature calibration

* Add deps.  Lint fix
2019-01-02 13:02:29 -05:00
kennedyshead
bba9ef7d7d Bumping aioasuswrt version to 1.1.17 (#19714) 2019-01-02 07:55:09 -05:00
mvn23
635252ec8e Bump pyotgw to 0.4b1 (#19715) 2019-01-02 07:54:23 -05:00
Dan Cinnamon
a10ca95c01 Envisalink pgm (#19499)
* Added a new service for calling custom PGM functions.

* Fixed lint issues

* Fixed lint issues reported by travis-CI

* Fixed style issue.

* Complete rename of attribute.
2019-01-02 05:46:33 -07:00
Daniel Perna
4244ea78d0 Update pyhomematic 0.1.54 + small fixes (#19667)
* Update pyhomematic + small fix

* Add casting for ILLUMINATION

* Revert suggested fix
2019-01-01 16:25:57 +01:00
Nick Whyte
5aa2bd81cf Add ness alarm control panel using nessclient (#18463)
* Add ness alarm control panel using nessclient

* indenting

* .

* Remove availability functionality, will improve and add back in another PR

* Use call_count

* lint

* lint

* Review changes

* Lint

* Bump nessclient to 0.9.8

* Bump nessclient to 0.9.9

* Remove from .coveragerc
2019-01-01 08:08:13 -07:00
Daniel Chesterton
61d5b3028d Add support for color_temp_command_template in MQTT light component (#19675)
* Add support for color_temp_command_template in MQTT light component
2019-01-01 15:42:41 +01:00
javicalle
b9f4a7220e Improve rflink coverage (#19596)
* some minor tests refactor
* async/await refactor
* toggle have not brightness
* test for race condition in unknown device
* test for 'no_command' and 'not_connected'
* test for race condition in unknown device
* sensor events are handled in sensor devices, RflinkDevice handle
command events
* test race conditions & bogus entity remove
* two more tests
* Test race condition for unknown components
* Test cleanup for `commands events` and `sensor events`
2019-01-01 15:35:31 +01:00
Fabian Affolter
2ea53e0787 Suppress traceback if network is not available (#19651) 2019-01-01 14:21:46 +01:00
Fabian Affolter
7c302bfd7e Luftdaten traceback (#19666)
* Suppress traceback if there is not connection available

* Remove line break
2019-01-01 14:21:02 +01:00
Robin
ff80fc347b Fix london_underground issue (#19642)
* Update london_underground.py

* Update test

* Update london_underground.py

* Update london_underground.py

* Update london_underground.py

* Fix lint

* Use london-tube-status==0.2
2018-12-31 06:24:52 -08:00
Michael Dubno
4b541f4058 Add IDTECK proximity card component (#18309)
* Added IDTECK proximity card sensor component.

* Moved from sensor to platform

* Made requested standards changes
2018-12-30 20:15:45 -08:00
Jc2k
855274e354 Fix homekit_controller pairing regression (#19654)
* Fix homekit_controller pairing regression

* Use constant for pairing file name
2018-12-30 14:44:26 -05:00
ehendrix23
43eaa960e8 Fix error in got_connected for remote.harmony (#19662)
* Fix config call in connected

* Change aioharmony version for fixes
2018-12-30 13:35:08 -05:00
Thom Troy
81a0ce621e Fix exception checking for next dublin bus (#19663) 2018-12-30 18:35:12 +01:00
Pär Svanström
18d36e011a Added regexp validation allowing Twilio notifications to use Sender ID instead of phone number (#19644)
* Added regexp validation allowing Twilio notifications to use Sender ID instead of phone number

* Fix line length
2018-12-30 18:34:29 +01:00
Joakim Sørensen
6d44245456 pytraccar version bump (#19659) 2018-12-30 14:59:43 +01:00
carstenschroeder
4b90ed6b22 Fix ADS light when parameter adsvar_brightness is not set (#19636)
* ADS light breaks if optional parameter adsvar_brightness is not set

Just a small change to prevent exception if optional parameter adsvar_brightness is not set

* corrected blank lines
2018-12-30 02:39:00 -08:00
John Mihalic
cc8b811572 Bump pyHik library to 0.1.9 to improve device support. (#19656) 2018-12-30 10:13:49 +01:00
ehendrix23
faeee4f7ad Use aioharmony for remote.harmony platform (#19595)
* Use aioharmony for async

Use aioharmony to interact with Harmony hub. Due to this following improvements:
-) Setting of available state for entity
-) Automatic config update if configuration changes (including updating file containing config)
-) Allow using of device name instead of number
-) When sending command with repeat, nothing else will be able to put a IR command in between

* Requirements updated

* Version update for fix

* Mainly cleanup

* Update requirements

Updated requirements

* Fixed lint issue

* Small bump for aioharmony

Small version bump increase for aioharmony

* Updated based on review
2018-12-29 17:22:27 -08:00
Markus Ressel
9aa6037219 Add RaspyRFM switch platform (#19130)
* added components and requirement

* change config to allow the definition of multiple switches without redefining the gateway

* dont assume false state
fix default value

* added exclude to coveragerc

* sorted imports

* review fixes

* review fixes

* bugfix
review fixes

* review fix
2018-12-29 16:40:03 -08:00
David F. Mulcahey
d0742cb332 Only bind clusters in ZHA remote entity (#19577)
* split bind and configure reporting helpers

* only bind remote clusters

* update comments - review comment
2018-12-29 16:17:17 -08:00
Alexei Chetroi
e096532cf1 Use async_configure for ZHA IAS binary sensor (#19629)
* Update Zha IAS binary sensor to use async_configure().

* Make less debug logging noise.
2018-12-29 16:13:52 -08:00
Adam Belebczuk
25e5864a22 Improve Wemo setup speed (#19563)
* Wemo - Improve setup speed

Move WeMo device discovery to an async context so it won't block initial component setup from completing quickly.

* WeMo - Fix too long lines

* WeMo - Update subscription shutdown log message

* WeMo - Fix flake8 issues

* WeMo - Code review fixes

* WeMo - Fix long lines

* WeMo - More code review fixes

* WeMo - Code review fixes
2018-12-29 16:05:21 -08:00
Marvin Wichmann
338077f557 Support knx operation types (#19546)
* Updated version to 0.9.3

Adjusted climate component due to changes in the underlying library.

* Climate.KNX: fix updating view when operation mode is changed due to refactoring

* Addressed review comments

* Added validation for config.
2018-12-29 15:18:55 -08:00
Ville Skyttä
f925d9ca6b Use xml.etree through defusedxml (#19640) 2018-12-30 00:07:48 +01:00
Fabian Affolter
b1c9f8d55d Suppress traceback if network is not available 2018-12-29 23:55:43 +01:00
Marius Retegan
32eb4e518b Fix cpu_temp issue on Vero 4K (#19638) 2018-12-29 21:01:47 +01:00
Robbert Müller
9928b977fd Added events STARTED, RESTARTED AND PAUSED (#19516)
Rewrote the tests a bit
the 'wait for the timer to finish' part of the test is now it's own test.
The rest is a sequence of fire/assert. Which i rewrote to a loop to
reduce the amount of duplicate code
2018-12-29 16:40:17 +01:00
mvn23
dc9da79a1c Revert "Bump pyotgw to 0.4b0 (#19618)" (#19635)
This reverts commit dae4543e54.
There's a bug in the new version of the library that may cause 100% CPU usage, rendering Home Assistant unresponsive.
2018-12-29 16:38:55 +01:00
Steven Looman
2ba86310f0 Upgrade to async_upnp_client==0.13.8 (#19634) 2018-12-29 14:09:29 +00:00
Marcelo Moreira de Mello
457708cbda Upgraded pyarlo to 0.2.3 (#19626) 2018-12-28 16:51:59 -05:00
Andrei
82d6fe5bd5 Fix cpu_temp issue on Odroid (#19620) 2018-12-28 21:36:00 +01:00
mvn23
dae4543e54 Bump pyotgw to 0.4b0 (#19618) 2018-12-28 10:12:10 -05:00
Daniel Shokouhi
33c5e09ac2 Add additional neato alerts and errors (#19608) 2018-12-28 09:53:54 -05:00
Andre Lengwenus
f09cea1499 LCN component and light platform (#18621)
* Initial commit of LCN component and light platform

* Corrected pre-review comments

* Fixed dimming behaviour in combination with transitions for lcn lights

* Removed unused logger

* Combined __init__.py and core.py into lcn.py component. Bumped to pypck==0.5.6

* Fixed .coveragerc

* Bumped to pypck==0.5.7

* Bump to pypck==0.5.8

* Fixed requirements_all.txt

* Moved unique generation of connection names to config schema's validator

* Minor changes due to review comments.
Bump to pypck==0.5.9.

* Address_connection is passed into LcnDevice

* Set should_poll property on LcnDevice to return False

* Moved platform config validation to component. Load platform using discovery helper

* Furtehr changes due to the review

* Light configuration is set required as there are no other platforms up to now
2018-12-28 03:39:06 -08:00
Rene Nulsch
14c39f7c24 Systemmonitor - add device_class property (#19614) 2018-12-28 10:28:40 +01:00
Ville Skyttä
b83a405b14 Upgrade huawei-lte-api to 1.1.1 (#19615) 2018-12-28 11:10:34 +02:00
SNoof85
699a38de52 Add Freebox component with sensors and device tracker (#18472)
* Add freebox component with sensor and device tracker

* script/gen_requirements_all passed and pylint fixes

* Fix docstring in wrong place

* Fix indentation

* Lint fixes

* More lint fixes

* Lint fixes again

* Pylint fixes

* Bump aiopyfreebox version

* Close freebox connection on HA Stop

* Fixed docstring

* Fixed ident

* Lint fixes

* Fix cloing session when HA stop

* Fix URL

* Fix URL

* Fix double look up in discovery datas

* Fix logging level

* Fix get_device_name

Thx for the hint Martin

* Fix async_update_info

* Update requirements_all.txt
2018-12-27 15:26:09 -08:00
Fabian Affolter
fe14be53e3 Upgrade aiohttp to 3.5.1 (#19584) 2018-12-27 21:56:08 +01:00
Max Rydahl Andersen
b32e6fe0d5 Add AfterShip sensor for packages (#18034)
* Add AfterShip sensor for packages

Why:

 * I receive a lot of packages from many different shipping companies.
 * I would like to see in haas how many packages are being delivered.

This change addreses the need by:

 * Adding a sensor for AfterShip (aftership.com)
 * AfterShip supports ~490 couriers world wide thus should cover
   almost any sensible tracking.

Notes:
  - For now this sensor assumes you somehow have added trackings to
    aftership manually.
  - Future idea is to expose service that allows adding a tracking
    based on incoming mails.
  - Other improvments would be to add map markers for package locations.

Related:
- https://community.home-assistant.io/t/package-tracking/858
- https://community.home-assistant.io/t/aftership-package-tracking/24068
- https://community.home-assistant.io/t/aftership-shipment-tracking-platform/14074
- https://community.home-assistant.io/t/aftership-state-card/57912

* Fix typo and update ordering
2018-12-27 10:01:57 -08:00
Daniel Shokouhi
d05450487c Improve how neato displays alerts and add alerts for persistent maps (#19593)
* Improve how neato displays alerts and add alerts for persistent maps

* Review comments
2018-12-27 09:57:38 -08:00
emontnemery
f9aa364b6d Don't truncate brightness and white_value of MQTT light (#19502)
* MQTT light: Don't truncate brightness

* Clamp after rounding
2018-12-27 18:18:12 +01:00
Ioan Loosley
5eab4f1dcc Version Bump for aioftp (#19510)
* Version Bump for aioftp

* Version Bump
2018-12-27 18:17:12 +01:00
Andrei
4c59a6522a Updated set of available voices for Yandex TTS (#19603) 2018-12-27 17:54:12 +01:00
Simon Nørager Sørensen
40bb4266c9 Update pymitv dependency (#19601)
* Security update, fixed fatal error when TV could become unresponsive

* Dependency update
2018-12-27 17:38:07 +01:00
FieldofClay
bf8b201bb3 Add verify_ssl option to Splunk component (#19112)
* added verify_ssl option to Splunk component

* update Splunk tests

* fix typo in Splunk tests

* Update test
2018-12-27 14:23:04 +01:00
apetrycki
cd0da4ed0e Fix mpd shuffle/random status (#19308)
* Fix shuffle/random status

MPD always shows true for shuffle.  For some reason casting the 0 or 1 as a boolean does not work.  Now returns 'true' or 'false' based on the value of 'random'.

* Update homeassistant/components/media_player/mpd.py

Change to correct way of returning shuffle boolean. 'random' needs to be cast as an integer before being cast as a boolean.

Co-Authored-By: apetrycki <34962392+apetrycki@users.noreply.github.com>

* Remove incorrect string code

Original fix method returns a string instead of a boolean.  Removed in favor of MartinHjelmare's method.
2018-12-27 00:04:40 -08:00
Fabian Affolter
22acc03fb8 Upgrade Sphinx to 1.8.3 (#19580) 2018-12-27 08:26:37 +01:00
Fabian Affolter
10831a0889 Upgrade rpi-rf to 0.9.7 (#19394) 2018-12-26 14:50:45 -05:00
Fabian Affolter
5de4f546f9 Upgrade keyring to 17.1.0 (#19583) 2018-12-26 14:47:38 -05:00
Fabian Affolter
2efa297df1 Upgrade pyowm to 2.10.0 (#19582) 2018-12-26 14:46:59 -05:00
Fabian Affolter
98229899dc Upgrade TwitterAPI to 2.5.8 (#19581) 2018-12-26 14:46:18 -05:00
Fabian Affolter
0a792620f8 Upgrade sphinx-autodoc-typehints to 1.6.0 (#19579) 2018-12-26 14:44:44 -05:00
Ted Sluis
54f6cfd569 Add a new click_type double_both to improve the support of the new Xiaomi aqara remote.b286acn01 dual switch. (#19578) 2018-12-26 17:48:58 +01:00
javicalle
70fff26383 Clean up remaining rflink tests (#19551)
* some minor tests refactor

* unused import

* async/await refactor

* Correct tests failures
2018-12-26 15:25:16 +01:00
Sebastian Muszynski
dc11e41cf0 Add a new click_type "long_both" to improve the support of the new Xiaomi Wireless Wall Switch (remote.b286acn01) (#19573) 2018-12-26 14:16:53 +01:00
Ville Skyttä
a6e091f60f Link to dicttoxml excessive INFO logging issue (#19575) 2018-12-26 14:16:08 +01:00
Daniel Høyer Iversen
1428919f98 Tibber, improve server reconnection (#19574) 2018-12-26 13:03:06 +01:00
Michael Dubno
6f9943787a Pencom (#19369)
* Added Pencom relay switch.

* Added Pencom relay switch.

* Cleaned up for submission.

* Fixed attribute keys. Switched to add_entities.
2018-12-26 08:49:34 +01:00
DoloresHA
796b195c73 Update pylaunches dependency to 0.2.0 (#19570)
* Update pylaunches dependency to 0.2.0

Update launch library to use pylaunches 0.2.0 as a dependency. launch_time sensor attribute will now be passed in ISO format, allowing for templating/easier automating with this attribute.

* Update pylaunches to 0.2.0
2018-12-25 23:47:19 +01:00
Heine Furubotten
47f8d248f7 Whitelisting of lines on entur sensor (#19539) 2018-12-25 23:43:46 +01:00
Igor Motov
b9a0f40827 Add device_id configuration option to Bluetooth tracker (#18539)
* Bluetooth tracker: add device_id configuration option

Makes the device id that bluetooth tracker is using configurable instead 
of using the first available device.

* Remove unncessary default in config.get

It is already handled in the configuration validation.
2018-12-25 18:29:44 +01:00
sander76
6b204941cf Add homematicip cloud full flush measuring switch (#19247) 2018-12-25 16:43:28 +01:00
mreiling
4d62e77049 Added support for triggered state on NX584 alarm. (#19524)
* Added support for triggered state on NX584 alarm.

* Minor update
2018-12-25 16:29:53 +01:00
Mattias Welponer
b80bed64f5 Add HomematicIP SMI55 device (#19400) 2018-12-25 10:40:21 +01:00
sander76
18b7f74ad7 Clean up homematicip cloud (#19481)
* Better logging, remove unused method, re-try handling fix. Other minor fixes.

* fix test

* typo fix
2018-12-25 09:40:36 +01:00
cdheiser
b2081c579b Improve Lutron RadioRA2 support, adding switches and scenes (#18330)
* Improve Lutron RadioRA2 support, adding switch and scene support.

- Update the version of pylutron to 0.2 which has various bug fixes.
- Switch to pylutron's per-device subscribe
- Add switch support, and configure any non-dimmable output as a switch.
- Add scene support, using any configured keypad button with a corresponding LED as a scene.

* Fixes for comments in pull request home-assistant/home-assistant#18330

* More fixes for comments in pull request #18330

* Remove unused imports

* Cleanup in docstrings for Lutron scene support.
2018-12-25 09:33:03 +01:00
David F. Mulcahey
bef85ecd2e Remove global from ZHA application controller (#19557)
* remove global from application controller per request

* remove unneeded line

* don't store controller application in hass.data - review comment
2018-12-25 09:20:09 +01:00
Jc2k
e0f50a9e54 Update homekit controller to homekit==0.12.0 (#19549) 2018-12-24 22:13:17 +01:00
emontnemery
a8797a08c6 Improve handling of MQTT light discovery (#19436) 2018-12-24 14:28:26 +01:00
emontnemery
edb7ec78f9 Fix support for base topic for empty values in MQTT discovery msg (#19501)
* Fix topic expansion for short strings

* Lint
2018-12-24 14:21:58 +01:00
Alexei Chetroi
4a1da0b041 Configure ZHA entity on new ZHA device join (#19470)
* Address PR#19177 comments.

* Make 'new_join' part of ZhaEntity.

Call async_configure() automatically when new device ZHA device joins.
2018-12-23 20:47:06 +01:00
Fredrik Erlandsson
0b84eefa2e Add hub- and device-info for tellduslive (#19180)
* add hub- and device-info

* attempt to make I/O outside event loop

* add_executer_job

* coroutines

* async_get_hubs

Co-Authored-By: fredrike <fredrik.e@gmail.com>

* asyncio fixes

* do device_info IO when device is discovered

* it's called async_add_executor_job

* nicer unique_id

* add comment

* it's called `async_add_executor_job`

* hub always contains 'name'

* await each new device

* add comment to why gather is bad
2018-12-23 13:13:49 -05:00
David F. Mulcahey
50888ae339 Fix issues in ZHA light (#19368)
* make light report on/off and level

* refactoring and review comments

* refactor

* use boolean for set_state - review comment

* async_schedule_update_ha_state() on level change - review comment

* fix docstring - review comment
2018-12-23 16:16:21 +01:00
kdvlr
a9f796a97c Updated to support per device find iphone sound. (#19535)
Added additional log statements to help debug
2018-12-23 13:35:39 +01:00
Steve9F
10ff169c76 Change ISY binary_sensor subnode to hex (#19471)
The subnode id for the motion enable node of Insteon 2844-222 motion sensor II on the ISY is 'D'. The binary_sensory/isy994.py assumed this value will be an integer rather than hex and fails. Changing line 55 to subnode_id = int(node.nid[-1], 16) fixes the issue.
2018-12-23 13:31:16 +01:00
Diogo Gomes
0b22880f22 increase robustness, when something upstream fails (#19493) 2018-12-23 13:29:02 +01:00
Alexei Chetroi
5a4e6bbb07 Support ZHA light turn_off transition (#19531) 2018-12-23 12:15:54 +01:00
Adam Belebczuk
0776456b59 Pywemo version bump (#19538)
* Bump pywemo version

* Bump pywemo version
2018-12-23 11:40:34 +01:00
Alexei Chetroi
01fc322488 Make ZHA entities non-polled by default (#19536) 2018-12-23 11:11:24 +01:00
David F. Mulcahey
2a2af80309 Add ZHA occupancy sensor (#19365)
* occupancy sensor

* lint

* map occupancy cluster to binary_sensor

* update to use reporting configuration and async_configure

* refactor

* fix typo - review comment

* handle restore entity functionality
2018-12-22 20:53:15 +01:00
William Comartin
2765440aa5 Implement path in the config to fix issues for some users (#19491)
* Update Tautulli sensor dependency pytautulli

Implement path in the config to fix issues for some users

* Add requirement
2018-12-22 14:36:06 -05:00
Andrew Loe
43e174899d Add additional Z-Wave Bulbs to ZW098 Workaround (#19480) 2018-12-22 14:33:28 -05:00
Panagiotis Panagiotopoulos
07b6aaec63 Add long click at new Aqara Wireless Remote Switch (#19518)
New Aqara Wireless Remote Switch Single supports long click.
2018-12-22 19:35:25 +01:00
Daniel Høyer Iversen
ef53a2d118 Fix Mill connection problem (#19519)
* Update mill library, improve connection

* Mill connection

* revert print

* Mill connection
2018-12-22 19:01:52 +01:00
Antoine GRÉA
1099018a5e Fix fail2ban by removal of internal timer logic (#19456)
* Remove timer logic from sensor class

Proposed fix for issue #10500

* Updating the tests to remove timer logic

* Removing unecessary dependancy

* Fixing requested changes

* Commit to try to fix the CLA ?
2018-12-22 18:25:02 +01:00
David F. Mulcahey
4bdb21a871 Update ZHA entity state on ZigBee zdo device announce (#19208)
* call async_update if defined on device_announce

* lint

* change update method

* remove unneeded listener
2018-12-22 18:18:48 +01:00
Fabian Affolter
6880be5aeb Add sunrise and sunset to Darksky weather sensor (#19492)
* Add sunrise and sunset to Darksky weather sensor

* Fix lint issue
2018-12-22 08:52:36 -05:00
Dom
f0e187e306 Update yale smart alarm client to v0.1.6 (#19495) 2018-12-22 08:50:03 -05:00
Joakim Sørensen
7c5ac88aae Add deprecation warning (#19515)
* Update ruter.py

* formating
2018-12-22 14:01:31 +01:00
Alexei Chetroi
54c57fe5db Restore state for zha binary_sensors on restart. (#19314)
Poll zha devices for current status, if not available restore state.
2018-12-22 09:34:47 +01:00
David F. Mulcahey
b444dfe8a6 Add ZHA battery sensor (#19363)
* add batery sensor

* add additional battery sizes

* remove blank line

* lint

* fix attribute report configuration

* return None - review comments
2018-12-22 09:11:33 +01:00
javicalle
fb226e3e3b Clean up RFLink tests and add two tests (#19511)
* some minor tests refactor

* unused import
2018-12-22 08:53:02 +01:00
uchagani
3a3d488de3 Allow scrape sensor to retry setting up platform if initial setup fails (#19498) 2018-12-22 08:40:30 +01:00
Michael Dubno
30841ef4da Add Lutron Homeworks component (#18311)
* Added Lutron Homeworks components.

* Made all requested changes other than config.

* Removed commented out code.

* Removed binary_sensor.
Implemented new signal/events for button presses.
Cleaned up some data passing.

* Fixed minor formatting.

* Got rid of unused config stuff.
Reordered imports.

* Missed removing vol, and forgot an extra line.

* More requested changes
Removed HomeworksController, it wasn't needed.

* Removed stale code.

* Imperative doc change.
2018-12-21 18:11:00 -05:00
Steven Looman
501b3f9927 Disable creating port mappings from UI, add discovery from component (#18565)
* Disable creating port mappings from UI, add discovery from component

* Remove unused constant

* Upgrade to async_upnp_client==0.13.6 and use manufacturer from device

* Upgrade to async_upnp_client==0.13.7
2018-12-21 17:25:23 +00:00
Paulus Schoutsen
5efc61feaf Merge branch 'master' into dev 2018-12-21 15:34:40 +01:00
Paulus Schoutsen
28abc30b4d Merge pull request #19505 from home-assistant/rc
0.84.6
2018-12-21 15:33:17 +01:00
Paulus Schoutsen
0471e15c28 Bumped version to 0.84.6 2018-12-21 14:04:54 +01:00
Paulus Schoutsen
ec28ee3c42 Remove check if base url is local (#19494) 2018-12-21 14:04:46 +01:00
Paulus Schoutsen
1281da024c Remove check if base url is local (#19494) 2018-12-21 11:23:05 +01:00
Diogo Gomes
c789f11ef8 Fixed the range filter unknown argument precision (#19428)
In HomeAssistant 0.84.3, the range filter would not work due to the unexpected precision filter parameter. 
The default range scheme has been edited to remove the unexpected precision parameter.

Verified and tested.
2018-12-20 22:30:06 +00:00
Otto Winter
dbd5396dc7 Add native ESPHome Home Assistant state feature (#19429)
* Add native ESPHome Home Assistant state feature

* Update aioesphomeapi
2018-12-20 23:29:57 +01:00
uchagani
71900ca719 Add new sensor platform to expose Islamic prayer times (#19444)
* added new sensor platform to expose Islamic prayer times

* added new sensor platform to expose Islamic prayer times

* updated tests according to feedback

* make prayer_times_info a public attribute

* remove stale comments
2018-12-20 22:52:43 +01:00
Aaron Bach
c15445159d Add timeout to RainMachine login (#19476)
* Add timeout to RainMachine login

* Moved timeout logic to regenmaschine

* Moving logic back into try/except

* Bumped requirements
2018-12-20 22:51:10 +01:00
Tom French
dd885a456e Reorder FLOW entries in config_entries.py (#19475) 2018-12-20 21:54:42 +01:00
Mathieu Velten
b5c9eca654 Update pynetgear to 0.5.2 (#19490) 2018-12-20 21:16:50 +01:00
jumpkick
dcf925a67f Rename ocr.png to ssocr-(entity_name).png to allow multiple instances (#18634)
* * Rename ocr.png to ssocr-(entity_name).png to allow multiple seven_segments instances to run without overwrting each other's data.

* Update seven_segments.py

* Update seven_segments.py

* Use string formatting
2018-12-20 11:31:58 -05:00
Bob Clough
d42d8543c8 Add Mythic Beasts DNSAPI Component (#18333)
* Add Mythic Beasts DNSAPI Component

* Added timeout, and tests for exceptions while updating

* Move API to external module

* Move mbddns import into function

* Updated tests to mock out mbddns library
2018-12-20 11:33:47 +01:00
Jens
fa0185a481 Adds battery_percent which had been introduced with pyatmo 1.4 and resolves unknown var warning. (#19309) 2018-12-19 23:42:16 +01:00
Daniel Shokouhi
28d2f9bd87 Bump Pybotvac To Support D7 On Latest Firmware (#19463)
* Bump pybotvac to support D7 on latest firmware

* Update requirements
2018-12-19 22:37:20 +01:00
emontnemery
27ea59f6c3 Add device registry to MQTT climate (#19332)
* Add device registry to MQTT climate

* Add testcase test_entity_id_update
2018-12-19 19:27:44 +01:00
emontnemery
ae776e2d28 Add device registry to MQTT alarm control panel (#19331)
* Add device registry to MQTT alarm control panel

* Add testcase test_entity_id_update
2018-12-19 19:27:23 +01:00
emontnemery
fed5d0f5be Add device registry to MQTT lock (#19333) 2018-12-19 19:26:07 +01:00
Paulus Schoutsen
4f134f339c Merge pull request #19461 from home-assistant/rc
0.84.5
2018-12-19 16:49:21 +01:00
Paulus Schoutsen
264d18bc83 Bumped version to 0.84.5 2018-12-19 15:42:02 +01:00
Paulus Schoutsen
4c1d978aa4 Bump pyharmony (#19460) 2018-12-19 15:41:25 +01:00
Paulus Schoutsen
196fe4b927 Bump pyharmony (#19460) 2018-12-19 15:41:14 +01:00
Erik
a9de9aa58d Add testcase test_entity_id_update 2018-12-19 15:28:25 +01:00
Erik
e874093818 Add device registry to MQTT climate 2018-12-19 15:28:25 +01:00
Paulus Schoutsen
b10149c2a0 Merge pull request #19459 from home-assistant/rc
0.84.4
2018-12-19 15:13:42 +01:00
Paulus Schoutsen
c71a6ee562 Updated frontend to 20181219.0 2018-12-19 15:01:02 +01:00
Paulus Schoutsen
57ee514d70 Update translations 2018-12-19 15:01:02 +01:00
Alexei Chetroi
4692605974 ZHA entity ZCL reporting configuration (#19177)
* Implement async_configure() method for ZHA entities.

Allow attribute reporting configuration to be stored as dict of zha
entity.

* Update ZHA platform to use new attribute reporting configuration.

* Use const declaration instead of magic numbers.

* Add support for manufacturer_id in ZCL attribute reporting configuration.

* Refactor async_configure() method.

Rename attribute reporting dict to zcl_reporting_config.
2018-12-19 14:52:20 +01:00
Paulus Schoutsen
2b82830eb1 Bumped version to 0.84.4 2018-12-19 14:23:07 +01:00
ehendrix23
ff1dba3529 Use web sockets for Harmony HUB (#19440)
* Updates to Harmony for web sockets

Updates to harmony to use web sockets with async

* Lint

* Small fixes

* Fix send_command

Continued improvements:
-) Fixed send_command
-) Get HUB configuration during update in case it was not retrieved earlier (i.e. HUB unavailable)

* Further improvements

Completely removed dependency on __main__ for pyharmony, instead everything is now done from the HarmonyClient class.
Writing out Harmony configuration file as a JSON file.
Using same functionality to determine if activity provided is an ID or name for device, allowing send_command to receive a device ID or device name.

* Point requirements to updated pyharmony repo

Updated REQUIREMENTS to point to repository containing the updates for pyharmony.

* lint

lint

* Small fix for device and activity ID

Small fix in checking if provided device or activity ID is valid.

* Pin package version

* No I/O in event loop

* Point at HA fork with correct version bump

* Fix req
2018-12-19 14:23:00 +01:00
Morten Lüneborg
257a91d929 Fix IHC config schema (#19415)
* Update __init__.py

Update "unit" -> "unit_of_measurement" and configuration (from plural to singular)

* Update __init__.py

* Removing vol.ALLOW_EXTRA arguments

* Update __init__.py
2018-12-19 14:22:59 +01:00
ehendrix23
23a579421d Use web sockets for Harmony HUB (#19440)
* Updates to Harmony for web sockets

Updates to harmony to use web sockets with async

* Lint

* Small fixes

* Fix send_command

Continued improvements:
-) Fixed send_command
-) Get HUB configuration during update in case it was not retrieved earlier (i.e. HUB unavailable)

* Further improvements

Completely removed dependency on __main__ for pyharmony, instead everything is now done from the HarmonyClient class.
Writing out Harmony configuration file as a JSON file.
Using same functionality to determine if activity provided is an ID or name for device, allowing send_command to receive a device ID or device name.

* Point requirements to updated pyharmony repo

Updated REQUIREMENTS to point to repository containing the updates for pyharmony.

* lint

lint

* Small fix for device and activity ID

Small fix in checking if provided device or activity ID is valid.

* Pin package version

* No I/O in event loop

* Point at HA fork with correct version bump

* Fix req
2018-12-19 14:21:40 +01:00
emontnemery
1568de62df Correct calls to subscription.async_unsubscribe_topics (#19414)
* Correct calls to subscription.async_unsubscribe_topics

* Review comments

* Add testcases
2018-12-19 14:05:24 +01:00
Paulus Schoutsen
a7e98f12f4 Updated frontend to 20181211.2 2018-12-19 14:04:08 +01:00
Fabian Affolter
8cec559103 Various updates (#19449)
* Various updates

* Fix lint issues
2018-12-19 12:39:16 +01:00
Gido
258fe1f09b Add sensor platform for SolarEdge Monitoring API (#18846)
* Adding sensor for SolarEdge Monitoring API support

* Adding support for Rova garbage calendar

* Update solaredge to pass lint and flake8

* Added solaredge.py to .coveragerc

* Added extend for Voluptuous schema

* Fixed styling issues

* Removed rova.py for later feature

* Replaced API requests with python pip package

* Fixed styling issues

* Updated to new async syntax
Added async_update to sensor class
Added Throttle to SolarEdge data update
Added CONF_NAME to platform settings
Added credentials check for api
Minor code style changes

* Remove unnecessary debug logging

* Updated dict keys

* Added SCAN_INTERVAL
Updated platform setup

* Remove DOMAIN variable
Correct import for PLATFORM_SCHEMA

* Change some debug to error messages
Correct return statements
Remove initial update call

* Fix pylint and flake8 errors
2018-12-19 09:56:45 +01:00
Fredrik Erlandsson
e5487722a8 Add device_info to Daikin (#19372)
* add device_info to Daikin

* Use the constant CONNECTION_NETWORK_MAC from the device registry helper.
2018-12-19 08:18:40 +01:00
Adam Belebczuk
7f0dd442fd Various enhancements for WeMo component/platforms (#19419)
* WeMo - Various fixes and improvements

Various fixes & improvements to the WeMo components, including:
-- Fixes to rediscovery
-- New reset filter service for the WeMo Humidifier
-- Switched the remainder of the WeMo components to async IO
-- Removed any remaining IO in entity properties and moved them to the polling/subscription update process

* WeMo - Fix pywemo version and remove test code from WeMo fan component

* WeMo Humidifier - Add services.yaml entry for reset filter life service

* WeMo - Update binary_sensor component to use asyncio

* WeMo - Add available property to binary_sensor component

* WeMo - Fixed line length issue

* WeMo - Fix issue with discovering the same device multiple times

* WeMo - Fix for the fix for discovering devices multiple times

* WeMo - Fix long lines

* WeMo - Fixes from code review

* WeMo - Breaking Change - entity_ids is now required on wemo_set_humidity

* WeMo - Code review fixes

* WeMo - Code review fixes

* WeMo - Code review fixes
2018-12-19 08:12:32 +01:00
Fabian Affolter
ef6c39f911 Fix typo (#19433) 2018-12-18 19:32:42 +01:00
Otto Winter
7317b1bb8b Miscellaneous ESPHome cleanups (#19425) 2018-12-18 19:04:50 +01:00
Fabian Affolter
c0ae7b1a49 Upgrade requests to 2.21.0 (#19385) 2018-12-18 17:29:38 +01:00
Fabian Affolter
686a856a17 Upgrade sqlalchemy to 1.2.15 (#19383) 2018-12-18 15:48:06 +01:00
Eliseo Martelli
51e6371991 Add Prezzibenzina (Italian Fuel Price) Sensor (#19297)
* complete(?)

* fixed linting

* update requirements

* added to coveragerc

* fixed linting

* added ability to set custom name

* fixed linting

* added filter

* spacing

* Added list of possible fuels

* Minor updates
2018-12-18 15:47:38 +01:00
Fabian Affolter
96c233d4b9 Use string foratting (#19427) 2018-12-18 15:23:53 +01:00
timkoers
17fbeb6245 Fixed the range filter unknown argument precision
In HomeAssistant 0.84.3, the range filter would not work due to the unexpected precision filter parameter. 
The default range scheme has been edited to remove the unexpected precision parameter.

Verified and tested.
2018-12-18 13:21:08 +01:00
Morten Lüneborg
c59e049050 Fix IHC config schema (#19415)
* Update __init__.py

Update "unit" -> "unit_of_measurement" and configuration (from plural to singular)

* Update __init__.py

* Removing vol.ALLOW_EXTRA arguments

* Update __init__.py
2018-12-18 12:40:03 +01:00
Rohan Kapoor
6c64b315db Optionally disable ssl verification for mjpeg (#19277) 2018-12-18 11:22:47 +01:00
Paulus Schoutsen
2f6ef08959 Remove reviewed by hound. That's not worth a badge. 2018-12-18 11:06:30 +01:00
Fabian Affolter
9c8e10936b Add openSenseMap air pollutants platform (#19357)
* Add openSenseMap air pollutants platform

* Fix issues (Docstring, log entries and check)

* Use SCAN_INTERVAL and name handling
2018-12-18 09:15:07 +01:00
John Mihalic
f2c7e3fed4 Bump pyEmby to 1.6, add channel media type mapping (#19318) 2018-12-17 19:16:32 -05:00
Nick Horvath
6adbf3ba84 Add camera selection config to skybell camera (#19310)
* add camera selection config to skybell camera

* code review changes.
2018-12-17 18:31:10 -05:00
Marius Retegan
da10598fa1 Fix cpu_temp issue on Raspberry Pi (#19404) 2018-12-17 18:30:17 -05:00
c-soft
6c8ed86f3e Satel integra monitor outputs (#19149)
* Adjusted api for new library version.

* Added support for output monitoring. Added initial status check for the alarm.

* Added default values to the configuration as per review notes.
2018-12-17 18:28:41 -05:00
jumpkick
f1005d37a7 Cast lametric cycles parameter to int (#19370) 2018-12-18 00:14:55 +01:00
Erik Eriksson
d270d52cb5 Upgrade volvooncall to 0.8.7 (#19398) 2018-12-18 00:08:57 +01:00
Eliseo Martelli
6e26713184 Add GTT Sensor (#18449)
* added gtt sensor

* removed trailing space

* updated requirements_all

* fixed two errors in the code style

* fixed imperative in docstring

* disabled pylint false positive

* fixed description on top of the file

* added files to .coveragerc

* fixes

* linting

* fixed linting 👍

* left a trailing space, now it's gone.

* fix
2018-12-17 18:03:34 -05:00
Sean Dague
e9c19462d6 Provide charging indicator for mychevy (#19348)
* Provide charging indicator for mychevy

This expands the mychevy sensor for the battery level to also know if
the system is currently charging to get the correct icon.

* address review feedback
2018-12-17 17:56:49 -05:00
Fabian Affolter
57ccd8283d Upgade colorlog to 4.0.2 (#19390) 2018-12-17 17:54:07 -05:00
Otto Winter
4ffacec4be Add native ESPHome API service call feature (#19401)
* Add native ESPHome API service call feature

* 🚑 Fix
2018-12-17 21:18:54 +01:00
Otto Winter
44bf5ba001 Add native ESPHome API device registry feature (#19381)
* Add native ESPHome API device registry feature

* 😅 Actually call method

* Run script/gen_requirements_all

* Don't prefix sw_version
2018-12-17 20:54:39 +01:00
Otto Winter
77e4f69af0 ESPHome Native API Restore Entities on startup (#19379)
* Update __init__.py

* Use attr.fields_dict
2018-12-17 20:53:51 +01:00
Otto Winter
8861909ea4 Add native ESPHome API text sensor (#19377)
* Update

* 🚑 Lint
2018-12-17 20:51:59 +01:00
Otto Winter
4b124e4c25 Add native ESPHome API switch (#19376)
* Add esphomelib native API switch

* Update

* 🚑 Lint
2018-12-17 20:50:56 +01:00
Otto Winter
c45beeef6d Add native ESPHome API light (#19375)
* Add esphomelib native API light

* Update

* 🚑 Lint
2018-12-17 20:49:03 +01:00
Otto Winter
a158397b6d Add native ESPHome API fan (#19374)
* Add esphomelib native API fan

* Update

* 🚑 Lint
2018-12-17 20:46:57 +01:00
Otto Winter
a1fb6ae38f Add native ESPHome API cover (#19373)
* Add esphomelib native API cover

* Update

* 🚑 Lint
2018-12-17 20:44:47 +01:00
Otto Winter
8c67ebc143 Add native ESPHome API binary sensor (#19371)
* Add esphomelib native API binary sensor

* Fixes

* 🚑 Lint
2018-12-17 20:40:57 +01:00
Fredrik Erlandsson
40d8bd43a1 fix unique_id for Tellduslive sensors (#19389) 2018-12-17 20:33:01 +01:00
Fabian Affolter
c7ea1d07be Add air pollutants PROP_TO_ATTR (#19336)
* Add PROP_TO_ATTR

* Change condition
2018-12-17 19:38:36 +01:00
Fabian Affolter
e60de53404 Upgrade RPi.GPIO to 0.6.5 (#19392) 2018-12-17 12:31:03 -05:00
Fabian Affolter
3a1dc16c0d Set pytz to >=2018.07 (#19387) 2018-12-17 11:35:13 -05:00
Fabian Affolter
a6568fba7a Upgrade keyrings.alt==3.1.1 (#19386) 2018-12-17 11:34:19 -05:00
Fredrik Erlandsson
0ab9e33110 Version bump pydaikin (#19388)
* version bump pydaikin

* remove requirements from platform
2018-12-17 11:32:10 -05:00
Fabian Affolter
8483850729 Upgrade ruamel.yaml to 0.15.81 (#19384) 2018-12-17 08:28:35 -05:00
Fabian Affolter
90608da5c2 Upgrade TwitterAPI to 2.5.7 (#19382) 2018-12-17 13:52:09 +01:00
Fabian Affolter
6b8835b196 Upgrade youtube_dl to 2018.12.17 (#19378) 2018-12-17 13:28:20 +01:00
Paulus Schoutsen
f55ab9d4ea Merge pull request #19391 from home-assistant/rc
0.84.3
2018-12-17 12:44:50 +01:00
Paulus Schoutsen
23cc4d1453 Bumped version to 0.84.3 2018-12-17 10:46:39 +01:00
Glen Takahashi
f613cd38fc Fix not being able to update entities (#19344)
When editing an entity in the frontend dialog, pressing save causes a "save failed: Entity is already registered" error. This is because the frontend always sets `name` and `new_entity_id` in the websocket command even if they haven't been changed. This adds a check that the `new_entity_id` is actually different from `entity_id` before erroring that the `new_entity_id` is already registered.
2018-12-17 10:46:31 +01:00
liaanvdm
45238295df Fix restore state for manual alarm control panel (#19284)
* Fixed manual alarm control panel restore state

* Revert "Fixed manual alarm control panel restore state"

This reverts commit 61c9faf434a8bb276133578a0811100a796784ca.

* Fixed manual alarm control panel's state restore
2018-12-17 10:46:31 +01:00
Andrew Hayworth
65bd308491 Set lock status correctly for Schlage BE469 Z-Wave locks (#18737)
* Set lock status correctly for Schlage BE469 Z-Wave locks

PR #17386 attempted to improve the state of z-wave lock tracking for
some problematic models. However, it operated under a flawed
assumptions. Namely, that we can always trust `self.values` to have
fresh data, and that the Schlage BE469 sends alarm reports after every
lock event. We can't trust `self.values`, and the Schlage is very
broken. :)

When we receive a notification from the driver about a state change,
we call `update_properties` - but we can (and do!) have _stale_
properties left over from previous updates. #17386 really works best
if you start from a clean slate each time. However, `update_properties`
is called on every value update, and we don't get a reason why.
Moreover, values that weren't just refreshed are not removed. So blindly
looking at something like `self.values.access_control` when deciding to
apply a workaround is not going to always be correct - it may or may not
be, depending on what happened in the past.

For the sad case of the BE469, here are the Z-Wave events that happen
under various circumstances:

RF Lock / Unlock:
- Send: door lock command set
- Receive: door lock report
- Send: door lock command get
- Receive: door lock report

Manual lock / Unlock:
- Receive: alarm
- Send: door lock command get
- Receive: door lock report

Keypad lock / Unlock:
- Receive: alarm
- Send: door lock command get
- Receive: door lock report

Thus, this PR introduces yet another work around - we track the current
and last z-wave command that the driver saw, and make assumptions based
on the sequence of events. This seems to be the most reliable way to go
- simply asking the driver to refresh various states doesn't clear out
alarms the way you would expect; this model doesn't support the access
control logging commands; and trying to manually clear out alarm state
when calling RF lock/unlock was tricky.

The lock state, when the z-wave network restarts, may look out of sync
for a few minutes. However, after the full network restart is complete,
everything looks good in my testing.

* Fix linter
2018-12-17 10:46:30 +01:00
Paulus Schoutsen
30345489e6 Updated frontend to 20181211.1 2018-12-17 10:21:24 +01:00
Pascal Vizeli
2bf36bb1db Use unicode slugify (#19192)
* Update __init__.py

* Update setup.py

* Update requirements_all.txt

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* remove `-`

* fix packages

* Update package_constraints.txt

* Update __init__.py

* Update package_constraints.txt

* Update requirements_all.txt

* Update setup.py

* Fix tests

* Fix line issue

* fix all test

* fix type

* Fix lint
2018-12-17 07:51:13 +01:00
Aaron Bach
cc90cba78a Add support for statewide data for Flu Near You (#19341)
* Initial changes

* Add support for state-wide data for Flu Near You

* Bumped requirements

* Member comments
2018-12-16 18:51:56 -07:00
Otto Winter
a08bab7b18 Add native ESPHome API component (#19334)
* Create esphomelib component

* Update requirements

* Remove python 2 string literals

* MVP

* Remove config flow

* Remove config flow translations

* Use dispatcher more

* Use data classes

* Type Hints

* Cleanup on remove

* Use helper and cleanup

* Fix HA stop listener cleanup

* Add config flow

* Fix SyntaxError for Py3.5

* Update

* Lint

* Re-do tests

*  Rename to ESPHome

* Better error message for resolve errors

* Fix tests when aioesphomeapi not installed

* Refactor mock

* Update requirements

* Add strings.json

* 🍵 100% config flow  test coverage
2018-12-17 01:29:32 +01:00
Anders Melchiorsen
f9c02889b2 Remove recorder purge protection (#19358) 2018-12-16 23:31:50 +01:00
PeteBa
9d4de2a722 Initialise plant attributes at startup (#19315)
* Initialise plant attributes at startup

* Pvizeli review comments

* Martin review change
2018-12-16 21:54:33 +01:00
Joakim Sørensen
92c5249746 Add traccar motion, speed and battery_level attributes (#19090)
* Added motion, speed and battery attributes.

* Use dict[key] when we know there is a value there.

Co-Authored-By: ludeeus <joasoe@gmail.com>

* Use dict[key] when we know there is a value there.

Co-Authored-By: ludeeus <joasoe@gmail.com>

* Use dict[key] when we know there is a value there.

Co-Authored-By: ludeeus <joasoe@gmail.com>

* Use dict[key] when we know there is a value there.

Co-Authored-By: ludeeus <joasoe@gmail.com>

* Use dict[key] when we know there is a value there.

Co-Authored-By: ludeeus <joasoe@gmail.com>

* Use dict[key] when we know there is a value there.

Co-Authored-By: ludeeus <joasoe@gmail.com>
2018-12-16 20:09:45 +01:00
Glen Takahashi
b031ded671 Fix not being able to update entities (#19344)
When editing an entity in the frontend dialog, pressing save causes a "save failed: Entity is already registered" error. This is because the frontend always sets `name` and `new_entity_id` in the websocket command even if they haven't been changed. This adds a check that the `new_entity_id` is actually different from `entity_id` before erroring that the `new_entity_id` is already registered.
2018-12-16 13:06:27 -05:00
Fredrik Erlandsson
5a295ad42b Add config flow for Daikin (#19182)
* config flow for daikin

* tox test

* return fixes

* tox test fixes

* tox formatting
2018-12-16 16:19:18 +01:00
Jens
266477a4f5 Adds io:OnOffIOComponent as switch to tahoma.py (#19338) 2018-12-16 16:05:21 +01:00
emontnemery
07e3843918 Fix broken sensor.mqtt json_attributes deprecation message (#19349) 2018-12-16 15:44:25 +01:00
Christian Biamont
c26c8affc7 Add Brottsplatskartan sensor (#19018)
* Added brottsplatskartan sensor

* Update brottsplatskartan sensor

* Remove redundant configuration checks.
* Set attributes during initialization.
* Setup brottsplatskartan module in setup_platform().
* Just do a simple return, no need for returning false.

* Remove file that was used during testing

* Better variable name and remove excessive newline.

* More updates to brottsplatskartan

* Import uuid at the top of the module.
* Use sensor as domain.
* Don't fire custom events.
* Remove period from logging message.

* Adding validation for area parameter

* Validate empty area configuration

* Fixing indentation.

* Fixing the config schema
2018-12-16 12:21:07 +01:00
Malte Franken
629dd24ff3 bump georss_client to 0.5 (#19337) 2018-12-16 11:14:36 +01:00
Ville Skyttä
c545d2e22e Upgrade pydocstyle to 3.0.0 (#19328) 2018-12-15 21:11:28 +02:00
Ville Skyttä
06383a4383 Upgrade pytest to 4.0.2 (#19327) 2018-12-15 21:10:55 +02:00
emontnemery
3a7083900c Merge pull request #19223 from emontnemery/mqtt_sensor_json_attributes_topic
Add JSON attribute topic to MQTT sensor
2018-12-15 16:28:34 +01:00
John Mihalic
e2d3beca22 Bump pyEight to fix Single Sleeper (#19316) 2018-12-14 23:25:12 -05:00
Charles Garwood
70dbbbd974 Add note to issue template regarding frontend issues (#19295) 2018-12-14 19:06:20 -05:00
tmd224
d60b7d46b7 Add Ambient Weather PWS Sensor component (#18551)
* Adding ambient_station.py sensor and updating requirements file

* Cleaning up code and fixing flake8 warnings

* Fixing flake8 and pylint warnings

* Adding ambient_station.py sensor and updating requirements file

* Cleaning up code and fixing flake8 warnings

* Fixing flake8 and pylint warnings

* Fixing bug, things are working well now

* removing nosetests file

* Adding changes as requested in pull request #18551

* Updating with more change requests from PR code review

* Removing SCAN_INTERVAL from PLATFORM_SCHEMA

* Removing unused import

* Adding platform schema validation for monitored_conditions and conf_units

* Updating link to documentation in doc-string.  File already named in doc repo

* Only setup platform if component can successfully communicate with API

* Inverting check for platform setup success
2018-12-14 23:53:49 +01:00
Paulus Schoutsen
d83f20f743 Fix tests 2018-12-14 17:58:45 +01:00
Ville Skyttä
1c147b5c5f huawei_lte: Fetch only required data (#17618)
When debug logging is enabled, still fetch everything in order to
provide indication of available/supported data to users, as instructed
in docs.
2018-12-14 14:51:13 +01:00
Colin Harrington
9c62149b00 Adding support for Plum Lightpad (#16576)
* Adding basic Plum Lightpad support - https://plumlife.com/

* Used Const values
is_on is a bool

* Added LightpadPowerMeter Sensor to the plum_lightpad platform

* Moved to async setup, Introduced a PlumManager, events, subscription, Light and Power meter working

* Added PlumMotionSensor

* Added Glow Ring support

* Updated plum library and re-normalized

* set the glow-ring's icon

* Naming the glow ring

* Formatting and linting

* Cleaned up a number of linting issues.  Left a number of documentation warnings

* setup_platform migrated to async_setup_platform Plum discovery run as a job

* bumped plumlightpad version

* On shutdown disconnect the telnet session from each plum lightpad

* Cleanup & formatting. Worked on parallell cloud update

* Moved setup from async to non-async

* Utilize async_call_later from the helpers

* Cleanedup and linted, down to documentation & one #TODO

* Remove commented out debug lines

* Fixed Linting issues

* Remove TODO

* Updated comments & fixed Linting issues

* Added plumlightpad to requirements_all.txt

* Fixing imports with isort

* Added components to .coveragerc

* Added PLUM_DATA constant for accessing hass.data[PLUM_DATA]

* used dictionary syntax vs get(...) for config

* Linting needed an additonal line

* Fully async_setup now. removed @callback utilize bus events for detecting new devices found.

* Upgraded to plumlightpad 0.0.10

* Removed extra unused PLATFORM_SCHEMA declarations

* Moved listener attachment to `async_added_to_hass` and removed unused properties & device_state_attributes

* Utilized Discovery when devices were located

* Linting and cleanup

* used `hass.async_create_task` instead of `hass.async_add_job` per Martin

* Removed redundant criteria in if block

* Without discovery info, there is no need to setup

* Better state management and async on/off for Glow Ring

* renamed async_set_config back to set_config, fixed cleanup callback and Plum Initialization

* Fixed flake8 linting issues

* plumlightpad package update

* Add 'motion' device_class to Motion Sensor

* Fixed last known Linting issue

* let homeassistant handle setting the brightness state

* String formatting vs concatenation

* use shared aiohttp session from homeassistant

* Updating to use new formatting style

* looks like @cleanup isn't neccesary

* ditch the serial awaits

* Ensure async_add_entities is only called once per async_setup_platform

* Creating tasks to wait for vs coroutines

* Remove unused white component in the GlowRing

* Used local variables for GlowRing colors & added a setter for the hs_color property to keep the values in sync

* Linted and added docstring

* Update the documentation path to point to the component page

* Removed the extra sensor and binary_sensor platforms as requested. (To be added in later PRs)

* Update plum_lightpad.py

* Update plum_lightpad.py
2018-12-14 14:42:04 +01:00
liaanvdm
027920ff52 Fix restore state for manual alarm control panel (#19284)
* Fixed manual alarm control panel restore state

* Revert "Fixed manual alarm control panel restore state"

This reverts commit 61c9faf434a8bb276133578a0811100a796784ca.

* Fixed manual alarm control panel's state restore
2018-12-14 14:04:04 +01:00
Brian Towles
a30921e67d Set InsteonEntity name to be combo of description and address. (#17262)
* Set InsteonEntity name to be combo of description and address.

ie "LampLinc Dimmer 26453a" or "Keypad Dimmer 291abb_2"

Using a centralized network name

* Updated the name to have a more contextual references for device
functions then just the group id.

* Cleanup for hound

* Removed the _generate_network_address function.  Not used anymore

* Cleanup for lint

* clean for hound

* Moved descriptor mapper to be a class variable of the InsteonEntity in order to fix lib loading issue for lint

* Well, moved DescriptorMapper instance to a function variable now...

* fix hound

* Lint Cleanup

* Clean up docstrings

* Simplify Label lookup based on state name
2018-12-14 14:02:06 +01:00
pbalogh77
7f9cc10447 Device config for Fibaro hub integration (#19171) 2018-12-14 13:35:12 +01:00
Fabian Affolter
b88cf64850 Add air pollutants component (#18707)
* Add air pollutants component

* Fix lint issue

* Update docstrings

* Revert change

* Remove entries

* Remove imports

* Fix variable and other fixes

* Change tests

* Set SCAN_INTERVAL
2018-12-14 13:32:58 +01:00
Erik Eriksson
004179775c Updated ELIQ Online sensor to async API (#19248)
* Updated ELIQ Online sensor to async API

* Remove use of STATE_UNKNOWN
2018-12-14 13:25:28 +01:00
MaxG88
f95bd9c78f Set unavailable when unreachable (#19012)
* Turn GPMDP Off When Unavailable

* Update requirements_all.txt

* Specified Exception Type

* Update gpmdp.py
2018-12-14 13:14:32 +01:00
emontnemery
b97f0c0261 Make variable entity_id available to value_template for MQTT binary sensor (#19195)
* MQTT binary_sensor: Make variable `entity_id` available to value_template

* Review comments

* Add testcase
2018-12-14 13:00:37 +01:00
Eric Nagley
e886576a64 home-assistant/home-assistant#17333: update to use DOMAIN constants and standards. (#19242) 2018-12-14 12:41:09 +01:00
Paulus Schoutsen
bb11b0f067 Merge branch 'master' into dev 2018-12-14 11:22:05 +01:00
Paulus Schoutsen
1135446de4 Merge pull request #19281 from home-assistant/rc
0.84.2
2018-12-14 11:19:41 +01:00
emontnemery
a262d0f9e4 Fix race in entity_platform.async_add_entities (#19222) 2018-12-14 10:34:02 +01:00
emontnemery
7a7c2ad416 Fix race in entity_platform.async_add_entities (#19222) 2018-12-14 10:33:37 +01:00
Paulus Schoutsen
d425aabae3 Bumped version to 0.84.2 2018-12-14 10:27:45 +01:00
Eric Nagley
8d44b721c6 Fix call to super() (#19279)
* home-assistant/home-assistant#19273: fix call to super()

* home-assistant/home-assistant#19273: adjust to python3 standards.

* home-assistant/home-assistant#19273: remove bad test.
2018-12-14 10:27:37 +01:00
Paulus Schoutsen
baa1801e13 Fix OwnTracks deadlocking (#19260)
* Fix OwnTracks deadlocking

* Fix deadlock
2018-12-14 10:27:36 +01:00
Fabian Affolter
965e47eb6a Fix list (fixes #19235) (#19258) 2018-12-14 10:27:35 +01:00
Luca Angemi
16c0301227 Add automation and script events to logbook filter events (#19253)
* Add automation and script events to logbook filter events

* Update logbook.py

* Update logbook.py

* Update logbook tests

* Update test_logbook.py

* Update test_logbook.py

* Update test_logbook.py

* Update test_logbook.py
2018-12-14 10:27:35 +01:00
kennedyshead
37096a2b65 Bump aioasuswrt (#19229)
* bump aioasuswrt version

* run gen_requirements
2018-12-14 10:27:34 +01:00
Erik
bead08840e Review comments 2018-12-14 10:27:13 +01:00
Erik
b7b55f941c Move check to websocket 2018-12-14 10:27:13 +01:00
Erik
4231775e04 Fail if new entity_id is in hass.states 2018-12-14 10:27:13 +01:00
damarco
14d90b5484 Always add friendly name attribute to ZHA entities (#19141)
* Always add friendly name attribute

* Only change device_info name
2018-12-14 10:25:41 +01:00
Sebastian Muszynski
afa48c54e9 Don't avoid async_schedule_update_ha_state by returning false (#19102) 2018-12-14 10:25:40 +01:00
Luca Angemi
fb680bc1e4 Add automation and script events to logbook filter events (#19253)
* Add automation and script events to logbook filter events

* Update logbook.py

* Update logbook.py

* Update logbook tests

* Update test_logbook.py

* Update test_logbook.py

* Update test_logbook.py

* Update test_logbook.py
2018-12-14 10:25:02 +01:00
Paulus Schoutsen
4f98818258 Rename is_owner decorator to is_admin (#19266)
* Rename is_owner decorator to is_admin

* Update test_auth.py
2018-12-14 10:19:27 +01:00
Paulus Schoutsen
a5a896b519 Check admin permission before able to manage config entries (#19265)
* Check admin permission before able to manage config entries

* Lint
2018-12-14 10:19:04 +01:00
Paulus Schoutsen
0eb0faff03 Add permission check to light service (#19259) 2018-12-14 10:18:36 +01:00
Thibault Maekelbergh
4a23d4c7d3 Add NMBS (Belgian railway) sensor platform (#18610)
* Add custom component to core

* Add pyrail to reqs

* Format & lint

* Sort nmbs.py into place on coveragerc

* Only set up station live if provided

* Only set up sensor if config is provided

* Fix line too long linting error

* PR Remarks

* Add docstrings

* Fix hound line to long error

* Fix quotes

* Rebase coveragerc

* PR Review

* Init empty attrs

* Dont include delay if there is none

* PR review

* Safer check

* Rebase reqs

* Generate req

* Update homeassistant/components/sensor/nmbs.py

Co-Authored-By: thibmaek <thibault.maekelbergh@iCloud.com>

* PR remarks
2018-12-14 09:52:34 +01:00
Eric Nagley
377c61203d Fix call to super() (#19279)
* home-assistant/home-assistant#19273: fix call to super()

* home-assistant/home-assistant#19273: adjust to python3 standards.

* home-assistant/home-assistant#19273: remove bad test.
2018-12-14 08:52:29 +01:00
bremor
74a93fe764 Synology chat add verify ssl (#19276)
* Update synology_chat.py

* Added verify_ssl option to notify.synology_chat

Python requests will verify ssl by default, this configuration options allows the user to specify if they want to verify ssl or not. Non breaking change, default is True - do verify ssl.
2018-12-14 08:42:01 +01:00
Heine Furubotten
ddbfdf14e9 Upgraded enturclient to 0.1.2 (#19267) 2018-12-14 08:33:46 +01:00
Rohan Kapoor
e8ec74b944 Expose ZoneMinder availability to Home Assistant (#18946)
* Expose ZoneMinder availability to Home Assistant

* Bump zm-py to 0.2.0 with the availability changes published
2018-12-14 08:10:54 +01:00
damarco
f60f9bae00 Always add friendly name attribute to ZHA entities (#19141)
* Always add friendly name attribute

* Only change device_info name
2018-12-13 23:08:35 +01:00
Paulus Schoutsen
eada1a184c Improve check for duplicated entity_id (#19194)
* Fail if new entity_id is in hass.states

* Move check to websocket

* Review comments
2018-12-13 20:57:33 +01:00
Paulus Schoutsen
34cfdb4e35 Fix OwnTracks deadlocking (#19260)
* Fix OwnTracks deadlocking

* Fix deadlock
2018-12-13 20:56:48 +01:00
Paulus Schoutsen
85e6f92c5a Lint 2018-12-13 20:08:31 +01:00
Tom Harris
9efb90d23c Resolve IOLinc sensor name (#19050) 2018-12-13 16:52:12 +01:00
Fabian Affolter
66aa7d0e68 Fix list (fixes #19235) (#19258) 2018-12-13 16:43:59 +01:00
Fredrik Erlandsson
9f790325bb Fix point sensor discovery (#19245) 2018-12-13 16:40:56 +01:00
Sander Geerts
0fa7186296 Support for the Harman Kardon AVR (#18471)
* Feature: support for the HK AVR

* Remove testcode

* Feature: support for the HK AVR

* Remove testcode

* Added checklist

* Review fixes whitespaces

* Lint fixes

* Review fixes, add current source

* Remove unused imports

* Review fixes; State constants, dict[key]

* More review fixes, Unknown state and Sources

* Review fix; rename devices to entities
2018-12-13 16:31:14 +01:00
Paulus Schoutsen
90df932fe1 Check admin permission before able to manage config entries 2018-12-13 16:13:43 +01:00
emontnemery
7436c0fe42 Add device registry to MQTT light (#19013) 2018-12-13 15:51:50 +01:00
Erik Eriksson
6766d25e62 Re-use connection-pool (#19249)
Re-use connection-pool of VOC
2018-12-13 12:25:39 +01:00
Paulus Schoutsen
9d9e11372b Make automations log errors (#18965) 2018-12-13 12:21:16 +01:00
Paulus Schoutsen
8ea0a8d40b RFC: Deprecate auto target all for services and introduce entity_id: * (#19006)
* Deprecate auto target all

* Match on word 'all'
2018-12-13 10:07:59 +01:00
Andrey Kupreychik
56c7e78cf2 Bumped NDMS2 client to 0.0.6 (#19244) 2018-12-13 10:01:41 +01:00
Teemu R
2fc0dfecb1 Convert songpal to use asynchronous websocket for state updates (#19129)
* Add websocket-based non-polling variant for songpal

* linting fixes

* changes based on Martin's feedback

* Fix linting

* add backoff timer for reconnects, fix variable naming (I thought that this wouldn't matter for internals..)

* Remove poll configuration variable

* bump the version just to be sure, the previous release lacked a version file (required for setup.py)
2018-12-12 23:05:55 +01:00
kennedyshead
8c6b9b57cd Bump aioasuswrt (#19229)
* bump aioasuswrt version

* run gen_requirements
2018-12-12 20:24:44 +01:00
Aaron Bach
f8438e96d1 Add package data attribute to 17track.net summary sensors (#19213)
* 17track.net: Add package data attribute to summary sensors

* Member comments
2018-12-12 19:07:06 +01:00
Paulus Schoutsen
2926989ec3 Merge remote-tracking branch 'origin/master' into dev 2018-12-12 18:47:41 +01:00
Paulus Schoutsen
6603b3eccd Merge pull request #19228 from home-assistant/rc
0.84.1
2018-12-12 17:54:37 +01:00
Paulus Schoutsen
e2bf3ac095 Bumped version to 0.84.1 2018-12-12 17:18:47 +01:00
Paulus Schoutsen
ced96775fe Fix owntracks topic in encrypted ios (#19220)
* Fix owntracks topic

* Warn if per-topic secret and using HTTP
2018-12-12 17:18:38 +01:00
Jason Hunter
f65e57bf7b Add automation and script events to logbook event types (#19219) 2018-12-12 17:18:37 +01:00
Paulus Schoutsen
7d9e257713 Fix owntracks topic in encrypted ios (#19220)
* Fix owntracks topic

* Warn if per-topic secret and using HTTP
2018-12-12 17:17:27 +01:00
David F. Mulcahey
031ee71adf Add ZHA device handler library (#19099)
* event foundation

* implement quirks

* lock zha-quirks version

* allow quirks handling to be toggled on and off

* revert event commit

* disable warning

* update requirements_all

* Remove fix in favor of #19141

#19141 should be what ultimately corrects this issue.

* review comment
2018-12-12 17:06:22 +01:00
Erik
d03dfd985b Review comments 2018-12-12 16:30:42 +01:00
Erik
0e868deedd Add JSON attribute topic to MQTT sensor 2018-12-12 16:26:44 +01:00
Lukas Barth
4984030871 Fix geizhals crash if no price found (#19197)
* Fix geizhals crash if no price found

* Return None on unknown price.

* Linting

* Linting the linting
2018-12-12 16:11:18 +01:00
Jason Hunter
7de509dc76 Add automation and script events to logbook event types (#19219) 2018-12-12 15:54:25 +01:00
Paulus Schoutsen
88cda043ac Merge pull request #19215 from home-assistant/rc
0.84
2018-12-12 14:17:53 +01:00
Paulus Schoutsen
404fbe388c Bumped version to 0.84.0 2018-12-12 11:45:42 +01:00
Paulus Schoutsen
a0bc96c20d Revert PR #18602 (#19188) 2018-12-12 11:45:20 +01:00
Paulus Schoutsen
6f4657fe02 Revert PR #18602 (#19188) 2018-12-12 11:44:50 +01:00
Fredrik Erlandsson
c0cd2d48ec add unique_id to SMHI (#19183) 2018-12-12 09:29:45 +01:00
Daniel Høyer Iversen
d1eb5da5f4 Update switchbot library (#19202) 2018-12-12 09:16:20 +01:00
Erik
c7492b0feb Move check to websocket 2018-12-11 20:20:57 +01:00
Fredrik Erlandsson
c20322232a Move daikin to package (#19187) 2018-12-11 18:17:45 +01:00
javicalle
61ca9bb8e4 Restore states for RFLink devices (#18816)
* Merge branch 'master' of https://github.com/home-assistant/home-assistant into dev

# Conflicts:
#	homeassistant/components/binary_sensor/point.py
#	homeassistant/components/cloud/__init__.py
#	homeassistant/components/cloud/prefs.py
#	homeassistant/components/frontend/__init__.py
#	homeassistant/components/light/fibaro.py
#	homeassistant/components/logbook.py
#	homeassistant/components/point/__init__.py
#	homeassistant/config_entries.py
#	homeassistant/const.py
#	homeassistant/helpers/service.py
#	requirements_all.txt
#	requirements_test_all.txt

* one 'async_get_last_state' refactor left behind

* Remove RestoreEntity inheritance (already in parent class)

* # pylint: disable=too-many-ancestors

* code predictor can be a bitch

* lint corrections

* # pylint: disable=too-many-ancestors

* recover from dict[key]

* Remove all 'coroutine' decorator, replace for 'async def'
Replace all 'yield from' for 'await'
Replace 'hass.async_add_job' for 'hass.async_create_task'
2018-12-11 17:20:30 +01:00
Erik
1f8156e26c Fail if new entity_id is in hass.states 2018-12-11 17:17:42 +01:00
Fabian Affolter
3e7b908a61 Add SCAN_INTERVAL (#19186)
* Add SCAN_INTERVAL

* More clean-up
2018-12-11 14:12:27 +01:00
Jonathan Keljo
eb4a44535c Enable alarmdecoder to see open/close state of bypassed RF zones when armed (#18477)
* Enable alarmdecoder to see open/close state of bypassed zones when armed

The alarmdecoder component already reported RF state bits as attributes. If the user knows which loop is set up for the zone in the alarm panel, they can use that information to tell whether the zone is open or closed even when the system is armed by monitoring the appropriate attribute. That’s awkward, so this commit enables the user to simply configure which loop is used and the component will update the state itself.

* Simplify, also it's more correct to treat it as a state change rather than a
permanent state, since it's possible the decoder might miss some events.

* Remove relative import
2018-12-11 11:34:03 +01:00
Paulus Schoutsen
e98476e026 Bumped version to 0.84.0b4 2018-12-11 10:33:58 +01:00
Paulus Schoutsen
aa45ff83bd Fix cloud defaults (#19172) 2018-12-11 10:33:21 +01:00
Paulus Schoutsen
029d006beb Updated frontend to 20181211.0 2018-12-11 10:30:35 +01:00
Paulus Schoutsen
ab8cf4f1e4 Updated frontend to 20181211.0 2018-12-11 10:30:24 +01:00
Paulus Schoutsen
557720b094 Fix cloud defaults (#19172) 2018-12-11 06:50:54 +01:00
Fredrik Erlandsson
92e19f6001 TelldusLive config flow (#18758)
* update TelldusLive to use config flow

* fixes from Martin

* Update homeassistant/components/tellduslive/config_flow.py

Co-Authored-By: fredrike <fredrik.e@gmail.com>

* revert changes in entry.py

* tox tests

* tox fixes

* woof woof (fix for hound)

* lint ignore

* unload entry

* coverall toxtests

* fix some toxtests
2018-12-10 18:44:45 +01:00
David F. Mulcahey
f4f42176bd ZHA - Event foundation (#19095)
* event foundation

* add missing periods to comments

* reworked so that entities don't fire events

* lint

* review comments
2018-12-10 10:59:50 -05:00
Paulus Schoutsen
e94eb686a6 Bumped version to 0.84.0b3 2018-12-10 13:00:41 +01:00
Paulus Schoutsen
2da5a02285 Add raw service data to event (#19163) 2018-12-10 13:00:35 +01:00
Paulus Schoutsen
e3b1008511 Fix lovelace save (#19162) 2018-12-10 13:00:34 +01:00
Paulus Schoutsen
cb874fefbb Drop OwnTracks bad packets (#19161) 2018-12-10 13:00:34 +01:00
phnx
0454a5fa3f home-assistant/home-assistant#18645: revert heat-cool -> auto change 2018-12-10 13:00:09 +01:00
phnx
d8f6331318 home-assistant/home-assistant#18645: Remove un-used constants. 2018-12-10 13:00:09 +01:00
phnx
d7459c73e0 home-assistant/home-assistant#18645: Fix climate mode mapping. 2018-12-10 13:00:09 +01:00
Eric Nagley
fa9fe4067a Google assistant fix target temp for *F values. (#19083)
* home-assistant/home-assistant#18524 : Add rounding to *F temps

* home-assistant/home-assistant#18524 : Linting

* simplify round behavior

* fix trailing whitespace

(thanks github editor)
2018-12-10 12:59:16 +01:00
Paulus Schoutsen
59581786d3 Add raw service data to event (#19163) 2018-12-10 12:58:51 +01:00
Paulus Schoutsen
55aaa894c3 Updated frontend to 20181210.1 2018-12-10 12:50:42 +01:00
Paulus Schoutsen
3cf8610cb3 Updated frontend to 20181210.1 2018-12-10 12:50:30 +01:00
Paulus Schoutsen
802497b05c Google_assistant: fix climate modes (#19084)
* home-assistant/home-assistant#18645: Fix climate mode mapping.

* home-assistant/home-assistant#18645: Remove un-used constants.

* home-assistant/home-assistant#18645: revert heat-cool -> auto change
2018-12-10 12:32:24 +01:00
Eric Nagley
df2f476c67 Google assistant fix target temp for *F values. (#19083)
* home-assistant/home-assistant#18524 : Add rounding to *F temps

* home-assistant/home-assistant#18524 : Linting

* simplify round behavior

* fix trailing whitespace

(thanks github editor)
2018-12-10 12:31:52 +01:00
Paulus Schoutsen
da338f2c1a Fix lovelace save (#19162) 2018-12-10 12:25:08 +01:00
Paulus Schoutsen
fdea9cb426 Drop OwnTracks bad packets (#19161) 2018-12-10 12:24:56 +01:00
Paulus Schoutsen
18bc772cbb Bumped version to 0.84.0b2 2018-12-10 09:55:19 +01:00
arigilder
a5072f0fe4 Remove marking device tracker stale if state is stale (#19133) 2018-12-10 09:54:37 +01:00
Paulus Schoutsen
76c26da4cb Lovelace using storage (#19101)
* Add MVP

* Remove unused code

* Fix

* Add force back

* Fix tests

* Storage keyed

* Error out when storage doesnt find config

* Use old load_yaml

* Set config for panel correct

* Use instance cache var

* Make config option
2018-12-10 09:54:36 +01:00
Nick Horvath
3528d865b7 Bump skybellpy version to fix api issue (#19100) 2018-12-10 09:54:36 +01:00
Daniel Høyer Iversen
e6c224fa40 Upgrade Tibber lib (#19098) 2018-12-10 09:54:35 +01:00
Pierre Ståhl
048f219a7f Upgrade pyatv to 0.3.12 (#19085) 2018-12-10 09:54:35 +01:00
Paulus Schoutsen
945b84a7df Updated frontend to 20181210.0 2018-12-10 09:54:20 +01:00
Paulus Schoutsen
fe2d24c240 Update translations 2018-12-10 09:54:12 +01:00
Paulus Schoutsen
faab0aa9df Updated frontend to 20181210.0 2018-12-10 09:53:53 +01:00
Paulus Schoutsen
a521b885bf Lovelace using storage (#19101)
* Add MVP

* Remove unused code

* Fix

* Add force back

* Fix tests

* Storage keyed

* Error out when storage doesnt find config

* Use old load_yaml

* Set config for panel correct

* Use instance cache var

* Make config option
2018-12-10 08:57:17 +01:00
Yaron de Leeuw
a744dc270b Update pygtfs to upstream's 0.1.5 (#19151)
This works by adding `?check_same_thread=False` to the sqlite
connection string, as suggested by robbiet480@.
It upgrades pygtfs from homeassitant's forked 0.1.3 version to 0.1.5.

Fixes #15725
2018-12-09 23:32:48 +01:00
clayton craft
866c2ca994 Update radiotherm to 2.0.0 and handle change in tstat error detection (#19107)
* radiotherm: bump version to 2.0.0

* radiotherm: change handling of transient errors from tstat

Radiotherm 2.0.0 now throws an exception when a transient error is
detected, instead of returning -1 for the field where the error was
detected. This change supports handling the exception.
2018-12-09 23:27:31 +01:00
Fabian Affolter
bc8414a6f8 Merge pull request #19148 from scop/upgrade-upcloud
Upgrade upcloud-api to 0.4.3
2018-12-09 23:23:50 +01:00
Fabian Affolter
527f9cdfb2 Upgrade slacker to 0.12.0 (#19142) 2018-12-09 23:23:07 +01:00
Fabian Affolter
4c04fe652c Upgrade sphinx-autodoc-typehints to 1.5.2 (#19140) 2018-12-09 23:22:56 +01:00
Lukas Barth
5e65e27bda Update geizhals dependency (#19152) 2018-12-09 23:22:33 +01:00
Ville Skyttä
7d3a962f73 Upgrade mypy to 0.650 (#19150)
* Upgrade to 0.650

* Remove no longer needed type: ignore
2018-12-09 21:22:08 +02:00
Ville Skyttä
ce998cdc87 Upgrade upcloud-api to 0.4.3 2018-12-09 19:19:13 +02:00
Fabian Affolter
dbbbfaa869 Upgrade youtube_dl to 2018.12.03 (#19139) 2018-12-09 12:38:42 +01:00
Fabian Affolter
863edfd660 Upgrade slacker to 0.12.0 2018-12-09 11:34:53 +01:00
Ludovico de Nittis
4d4967d0dd Add code support for iAlarm (#19124) 2018-12-09 11:08:39 +01:00
Bas Schipper
4b4f51fb6f Fixed doorbird config without events (empty list) (#19121) 2018-12-09 10:33:39 +01:00
arigilder
30064655c2 Remove marking device tracker stale if state is stale (#19133) 2018-12-08 21:27:01 -07:00
edif30
fd5b92b2fb Update Google Assistant services description and request sync timeout (#19113)
* Fix google assistant request sync service call

* More descriptive services.yaml

* Update services.yaml

* Update __init__.py

* Update request sync service call timeout

Change from 5s to 15s to allow Google to respond.  5s was too short.  The service would sync but the service call would time out and throw the error.
2018-12-08 20:39:51 -05:00
Abílio Costa
6e55c2a345 update edp_redy version (#19078)
* update edp_redy version

* update requirements_all.txt
2018-12-08 21:16:16 +01:00
Sebastian Muszynski
2134331e2b Add Philips Moonlight Bedside Lamp support (#18496)
* Add Philips Moonlight Bedside Lamp support

* Update comment

* Make hound happy

* Wrap the call that could raise the exception only

* Remote blank line

* Use updated python-miio API
2018-12-08 14:49:14 +01:00
Daniel Høyer Iversen
ffe83d9ab1 Upgrade Mill library (#19117) 2018-12-08 11:38:42 +01:00
Sebastian Muszynski
f2f649680f Don't avoid async_schedule_update_ha_state by returning false (#19102) 2018-12-08 08:45:03 +01:00
Sebastian Muszynski
ece7b498ed Fix the Xiaomi Aqara Cube rotate event of the LAN protocol 2.0 (Closes: #18199) (#19104) 2018-12-08 08:28:39 +01:00
Sebastian Muszynski
65c2a25736 Support next generation of the Xiaomi Mi Smart Plug (chuangmi.plug.hmi205) (#19071)
* Add next generation of the Xiaomi Mi Smart Plug (chuangmi.plug.hmi205)

* Fix linting
2018-12-07 23:40:48 +01:00
Andrew Hayworth
05586de51f Set lock status correctly for Schlage BE469 Z-Wave locks (#18737)
* Set lock status correctly for Schlage BE469 Z-Wave locks

PR #17386 attempted to improve the state of z-wave lock tracking for
some problematic models. However, it operated under a flawed
assumptions. Namely, that we can always trust `self.values` to have
fresh data, and that the Schlage BE469 sends alarm reports after every
lock event. We can't trust `self.values`, and the Schlage is very
broken. :)

When we receive a notification from the driver about a state change,
we call `update_properties` - but we can (and do!) have _stale_
properties left over from previous updates. #17386 really works best
if you start from a clean slate each time. However, `update_properties`
is called on every value update, and we don't get a reason why.
Moreover, values that weren't just refreshed are not removed. So blindly
looking at something like `self.values.access_control` when deciding to
apply a workaround is not going to always be correct - it may or may not
be, depending on what happened in the past.

For the sad case of the BE469, here are the Z-Wave events that happen
under various circumstances:

RF Lock / Unlock:
- Send: door lock command set
- Receive: door lock report
- Send: door lock command get
- Receive: door lock report

Manual lock / Unlock:
- Receive: alarm
- Send: door lock command get
- Receive: door lock report

Keypad lock / Unlock:
- Receive: alarm
- Send: door lock command get
- Receive: door lock report

Thus, this PR introduces yet another work around - we track the current
and last z-wave command that the driver saw, and make assumptions based
on the sequence of events. This seems to be the most reliable way to go
- simply asking the driver to refresh various states doesn't clear out
alarms the way you would expect; this model doesn't support the access
control logging commands; and trying to manually clear out alarm state
when calling RF lock/unlock was tricky.

The lock state, when the z-wave network restarts, may look out of sync
for a few minutes. However, after the full network restart is complete,
everything looks good in my testing.

* Fix linter
2018-12-07 21:17:34 +01:00
Daniel Høyer Iversen
a58b3aad59 Upgrade Tibber lib (#19098) 2018-12-07 19:33:06 +01:00
Nick Horvath
e567e3d4e7 Bump skybellpy version to fix api issue (#19100) 2018-12-07 19:20:05 +01:00
phnx
385f0298bd home-assistant/home-assistant#18645: revert heat-cool -> auto change 2018-12-07 10:00:56 -05:00
speedmann
7edd241059 Automatically detect if ipv4/ipv6 is used for cert_expiry (#18916)
* Automatically detect if ipv4/ipv6 is used for cert_expiry

Fixes #18818
Python sockets use ipv4 per default. If the domain which should be checked
only has an ipv6 record, socket creation errors out with
`[Errno -2] Name or service not known`
This fix tries to guess the protocol family and creates the socket
with the correct family type

* Fix line length violation
2018-12-07 11:08:41 +01:00
Pierre Ståhl
5bf6951311 Upgrade pyatv to 0.3.12 (#19085) 2018-12-07 11:06:38 +01:00
phnx
24d0aa3f55 home-assistant/home-assistant#18645: Remove un-used constants. 2018-12-07 01:50:48 -05:00
emontnemery
5ff7563070 Merge pull request #18923 from emontnemery/mqtt_binary_sensor_json_attributes_topic
Add JSON attribute topic to MQTT binary sensor
2018-12-07 07:48:55 +01:00
Matthew Garrett
def4e89372 Bump lakeside requirement to support more Eufy devices (#19080)
The T1203 works fine with the existing protocol.
2018-12-07 07:32:21 +01:00
ehendrix23
8a62bc9237 Set directv unavailable state when errors returned for longer then a minute (#19014)
* Fix unavailable

Change setting to unavailable when getting request exceptions only after 1 minute has past since 1st occurrence.

* Put common code in _check_state_available

Put common code to determine if available should be set to False in method _check_state_available
2018-12-07 07:26:49 +01:00
phnx
471d94b6cd home-assistant/home-assistant#18645: Fix climate mode mapping. 2018-12-07 01:15:04 -05:00
Paulus Schoutsen
393ada0312 Bumped version to 0.84.0b1 2018-12-07 07:14:19 +01:00
Anders Melchiorsen
da160066c3 Upgrade aiolifx to 0.6.7 (#19077) 2018-12-07 07:13:35 +01:00
Bram Kragten
ff9427d463 Force refresh Lovelace (#19073)
* Force refresh Lovelace

* Check config on load

* Update __init__.py

* Update __init__.py
2018-12-07 07:13:35 +01:00
Mike Miller
3eb646eb0d Fix missing colorTemperatureInKelvin from Alexa responses (#19069)
* Fix missing colorTemperatureInKelvin from Alexa responses

* Update smart_home.py

* Add test
2018-12-07 07:13:34 +01:00
Paulus Schoutsen
578fe371c6 Revert #17745 (#19064) 2018-12-07 07:13:34 +01:00
Paulus Schoutsen
1b03a35fa1 Updated frontend to 20181207.0 2018-12-07 07:13:23 +01:00
Paulus Schoutsen
ce736e7ba1 Updated frontend to 20181207.0 2018-12-07 07:12:59 +01:00
Bram Kragten
455508deac Force refresh Lovelace (#19073)
* Force refresh Lovelace

* Check config on load

* Update __init__.py

* Update __init__.py
2018-12-07 07:09:05 +01:00
Anders Melchiorsen
0a3af545fe Upgrade aiolifx to 0.6.7 (#19077) 2018-12-07 07:06:35 +01:00
Mike Miller
dd92318762 Fix missing colorTemperatureInKelvin from Alexa responses (#19069)
* Fix missing colorTemperatureInKelvin from Alexa responses

* Update smart_home.py

* Add test
2018-12-06 17:05:14 +01:00
Sean Wilson
c931619269 Add CM17A support (#19041)
* Add CM17A support.

* Update log entry
2018-12-06 16:25:58 +01:00
Ville Skyttä
1be440a72b Upgrade pylint to 2.2.2 (#18750)
* Upgrade to 2.2.0

* simplifiable-if-expression fixes

* duplicate-string-formatting-argument fixes

* unused-import fixes

* Upgrade to 2.2.1

* Remove no longer needed disable

* Upgrade to 2.2.2
2018-12-06 11:54:44 +01:00
Paulus Schoutsen
04c7d5c128 Revert #17745 (#19064) 2018-12-06 10:38:26 +01:00
Paulus Schoutsen
30c77b9e64 Bumped version to 0.85.0.dev0 2018-12-06 09:36:44 +01:00
Paulus Schoutsen
4fd4e84b72 Bumped version to 0.84.0b0 2018-12-06 09:34:21 +01:00
Daniel Høyer Iversen
d4c8024522 Add support for more Tibber Pulse data (#19033) 2018-12-06 09:30:11 +01:00
Martin Gross
72379c166e Update locationsharinglib to 3.0.9 (#19045) 2018-12-06 09:29:30 +01:00
Paulus Schoutsen
f198706767 Remove Instapush notify platform (#19051) 2018-12-06 09:28:31 +01:00
pbalogh77
f0d534cebc Implemented unique ID support for Fibaro hub integration (#19055)
* Unique ID support

New unique ID support, based on hub's serial number and device's permanent ID

* Fixes, showing attributes

Minor fixes
Showing room, hub, fibaro_id for easier mapping and finding of devices

* Update fibaro.py
2018-12-06 09:28:06 +01:00
Daniel Perna
47320adcc6 Update pyhomematic to 0.1.53 (#19056) 2018-12-06 09:25:39 +01:00
Bram Kragten
b9ed4b7a76 Fix saving YAML as JSON with empty array (#19057)
* Fix saving YAML as JSON with empty array

* Lint
2018-12-06 09:24:49 +01:00
Erik Eriksson
b71d65015a VOC: Update external dependency to fix engine start issue (#19062) 2018-12-06 09:22:49 +01:00
Paulus Schoutsen
962358bf87 Fix cloud const (#19052)
* Fix cloud const

* Fix tests
2018-12-06 09:20:53 +01:00
Paulus Schoutsen
26a38f1fae Updated frontend to 20181205.0 2018-12-06 00:30:45 +01:00
Paulus Schoutsen
83311df933 Add translations 2018-12-06 00:30:33 +01:00
emontnemery
06285d1bf3 Merge pull request #19019 from emontnemery/fix_mqtt_availability
Fix entity unavailable after reconfiguring MQTT availability
2018-12-05 22:14:37 +01:00
photinus
0aee355b14 Bump pyvizio version (#19048)
* Update vizio.py

Bump pyvizio version to reoslve get volume call and component setup failures

* Update of requirement_all
2018-12-05 16:00:49 -05:00
Fabian Affolter
b2b4712bb7 Remove Instapush notify platform 2018-12-05 21:17:02 +01:00
Sebastian Muszynski
af96694430 Remove unsupported strong mode of the Xiaomi Air Humidifier CA1 (#18926)
* Remove unsupported strong mode of the Xiaomi Air Humidifier CA1

* Clean up filter of unsupported modes
2018-12-05 20:56:43 +01:00
Erik
df346feb65 Review comments 2018-12-05 19:48:44 +01:00
Aaron Bach
08702548f3 Add support for multiple RainMachine controllers (#18989)
* Add support for multiple RainMachine controllers

* Member comments

* Member comments

* Member comments

* Cleanup

* More config flow cleanup

* Member comments
2018-12-05 10:31:32 -07:00
Teemu R
bc69309b46 Add last clean times to xiaomi vacuum (#19043) 2018-12-05 18:20:26 +01:00
Teemu R
da0542e961 Bump python-miio to 0.4.4 (#19042) 2018-12-05 18:19:30 +01:00
Jeff Irion
16e25f2039 Catch 'BrokenPipeError' exceptions for ADB commands (#19011) 2018-12-05 17:04:08 +01:00
Sébastien RAMAGE
3627de3e8a Change error to warning (#19035) 2018-12-05 15:58:46 +01:00
Glenn Waters
b31c52419d Bump version of elkm1_lib (#19030) 2018-12-05 09:31:07 -05:00
Paulus Schoutsen
578a2cf357 Small refactoring of MQTT switch (#19010) 2018-12-05 14:47:02 +01:00
emontnemery
69fd3aa856 Small refactoring of MQTT light (#19009) 2018-12-05 14:46:37 +01:00
Paulus Schoutsen
12f222b5e3 Don't wait for answer for webhook register (#19025) 2018-12-05 14:45:30 +01:00
Paulus Schoutsen
eb317bd302 Merge pull request #19036 from sdague/mychevy_1.0.1
update mychevy to 1.0.1
2018-12-05 14:44:43 +01:00
Paulus Schoutsen
ab9d1a83af Bump waterfurnace to 1.0 (#19040)
This bumps to the new version of the waterfurnace API. In the new
version the unit id is no longer manually set by the user, instead it
is retrieved from the service after login. This is less error prone as
it turns out discovering the correct unit id is hard from an end user
perspective.

Breaking change on the config, as the unit parameter is removed from
config. However I believe the number of users is very low (possibly
only 2), so adaptation should be easy.
2018-12-05 14:44:25 +01:00
Paulus Schoutsen
0e9e253b7b Fix CI by pinning IDNA (#19038)
* Fix CI

* Actual fix by @sdague
2018-12-05 14:43:29 +01:00
Bram Kragten
850caef5c1 Add states to panels (#19026)
* Add states to panels

* Line too long

* remove extra urls for states

* Update __init__.py
2018-12-05 14:27:35 +01:00
Sean Dague
8c0b50b5df Bump waterfurnace to 1.0
This bumps to the new version of the waterfurnace API. In the new
version the unit id is no longer manually set by the user, instead it
is retrieved from the service after login. This is less error prone as
it turns out discovering the correct unit id is hard from an end user
perspective.

Breaking change on the config, as the unit parameter is removed from
config. However I believe the number of users is very low (possibly
only 2), so adaptation should be easy.
2018-12-05 07:03:27 -05:00
Sean Dague
a785a1ab5d update mychevy to 1.0.1
After six months the chevy website finally has been reimplemented to
something that seems to work and is stable. The backend library has
been updated thanks to upstream help, and now is working again.
2018-12-05 05:42:27 -05:00
Paulus Schoutsen
3928d034a3 Allow checking entity permissions based on devices (#19007)
* Allow checking entity permissions based on devices

* Fix tests
2018-12-05 11:41:00 +01:00
jxwolstenholme
2680bf8a61 Update requirement btsmarthub_devicelist==0.1.3 (#18961)
* Added requirement 'btsmarthub_devicelist==0.1.2'

* Update requirements_all.txt

* Update bt_smarthub.py

* Update requirements_all.txt

* Update bt_smarthub.py
2018-12-04 23:26:20 +01:00
majuss
a8b5cc833d Lupupy version push to 0.0.17 - will now transmitted state_alarm_triggered (#19008)
* added state_alarm_triggered transmission; pushed lupupy version

* added state_alarm_triggered transmission; pushed lupupy version

* added state_alarm_triggered transmission; pushed lupupy version

* added state_alarm_triggered transmission; pushed lupupy version
2018-12-04 22:04:39 +01:00
Erik
f54710c454 Fix bug when reconfiguring MQTT availability 2018-12-04 21:25:18 +01:00
Erik
21197fb968 Review comments 2018-12-04 19:56:34 +01:00
Erik
47d48c5990 Small refactoring of MQTT switch 2018-12-04 16:39:49 +01:00
Matt Hamilton
38b09b1613 Remove stale user salts code (#19004)
user['salt'] was originally used as a part of the pbkdf2 implementation.
I failed to remove this as a part of the cleanup in #18736.
2018-12-04 14:39:43 +01:00
Paulus Schoutsen
26dd490e8e Fix toon operation mode (#18966)
* Fix toon

* Update toon.py
2018-12-04 13:24:18 +01:00
Emil Stjerneman
1c99960357 Fix VOC configuration resource list (#18992) 2018-12-04 11:39:42 +01:00
Alexei Chetroi
3e1ab1b23a Sort import order of zha component. (#18993) 2018-12-04 11:38:57 +01:00
pbalogh77
2a0c2d5247 Fibaro Light fixes (#18972)
* minor fixes to color scaling

* capped input to fibaro on setcolor
2018-12-04 11:38:21 +01:00
Paulus Schoutsen
b65bffd849 Mock out device tracker configuration loading funcs in Geofency + OwnTracks (#18968)
* Mock out device tracker configuration loading funcs

* Update test_init.py

* Update test_init.py
2018-12-04 10:45:41 +01:00
cdce8p
ab7c52a9c4 Add unnecessary-pass for pylint-update (#18985) 2018-12-04 10:45:16 +01:00
Fredrik Erlandsson
d6a4e106a9 Tellduslive refactoring (#18780)
* move component to a package

* move TelldusLiveEntry to separate file

* refactor

* move entities from a shared container
* using the dispatch helper instead for communication between component and platforms

* updated covereagerc and codeowners

* suggestions from MartinHjelmare

* don't make update async

* "Strip is good!"
2018-12-04 10:08:40 +01:00
Pierre Gronlier
a6511fc0b9 remove the need to have query feature support (#18942)
* remove the need to have query feature support

Some InfluxDB servers don't have /query support feature but are still valid servers for storing data.
Usually those servers are proxies to others timeseries databases.
The change proposes to still validate the configuration but with less requirements on the server side.

* `.query` call is replaced by `.write_points`

* no more query call in the influxdb component. remove test

* reset mock after the setup and before the test

* remove unused import

* reset mock stats after component setup
2018-12-04 09:59:03 +01:00
Bram Kragten
75b855ef93 Lovelace fix: badges are removed from view after update (#18983)
* badges are removed from view after update

* Only add badges and cards when not provided in new config
2018-12-04 09:56:30 +01:00
Dom
d8a7e9ded8 Updated Yale Smart Alarm platform to new Yale API (#18990)
* Updated Yale Smart Alarm platform to use Yale's new API which replaces the deprecated version. Bumped yalesmartalarmclient to v0.1.5.

* Update requirements
2018-12-04 09:56:14 +01:00
Craig J. Midwinter
f3d7cc66e5 downgrade version of client (#18995)
* downgrade version of client

* update requirements
2018-12-04 09:52:30 +01:00
Jason Hunter
b900005d1e New Events and Context Fixes (#18765)
* Add new events for automation trigger and script run, fix context for image processing, add tests to ensure same context

* remove custom logbook entry for automation and add new automation event to logbook

* code review updates
2018-12-04 09:45:17 +01:00
Daniel Høyer Iversen
8e9c73eb18 Upgrade switchbot lib (#18980) 2018-12-04 06:48:27 +01:00
Daniel Høyer Iversen
b024c3a833 Add @danielhiversen as codeowner (#18979) 2018-12-04 06:48:16 +01:00
Daniel Høyer Iversen
31078b2b3e Merge pull request #18928 from home-assistant/tibber_err_handle
Tibber, Improve logging and error handling
2018-12-04 06:47:50 +01:00
Joakim Sørensen
ad0e3cea8a Update CODEOWNERS (#18976) 2018-12-03 21:49:15 -05:00
Bram Kragten
b5e7e45f6c no ordered dict (#18982) 2018-12-03 21:54:34 +01:00
Kevin Fronczak
4486de743d Support for mulitple Blink sync modules (#18663) 2018-12-03 20:45:12 +00:00
Daniel Høyer Iversen
df3c683023 Improve err handling 2018-12-03 20:53:18 +01:00
Erik Eriksson
d7a10136df VOC: Update library version. Moved method one step out. Instruments can be a set as well (#18967) 2018-12-03 19:52:50 +01:00
Otto Winter
c8d92ce907 Fix MQTT re-subscription logic (#18953)
* Fix MQTT re-subscription logic

* Cleanup

* Lint

* Fix
2018-12-03 19:16:58 +01:00
Fredrik Erlandsson
111a3254fb Point fix for multiple devices (#18959)
* fix for multiple devices closes, #18956

* Point API finally supports "all" events
2018-12-03 16:50:05 +01:00
Paulus Schoutsen
d028236bf2 Refactor script helper actions into their own methods (#18962)
* Refactor script helper actions into their own methods

* Lint

* Lint
2018-12-03 15:46:25 +01:00
Bram Kragten
d0751ffd91 Add id when not exist and fix dup id check (#18960)
* Add id when not exist and fix dup id check

* config possibly not be a yaml dict
2018-12-03 15:44:04 +01:00
Paulus Schoutsen
2fff0324f8 Merge remote-tracking branch 'origin/master' into dev 2018-12-03 15:26:55 +01:00
pbalogh77
149eddaf46 Initial scene support for Fibaro hubs (#18779)
* Initial scene support

Added initial support for fibaro scenes

* removed comments

* cleanup based on code review

* Removed unused functions

* grrr, my mistake.

My local pylint and flake8 are playing tricks with me

* Update homeassistant/components/scene/fibaro.py

* fixes based on code review

ABC ordered the list of platforms
changed setup platform to async
removed overloaded name property as the FibaroDevice parent class already provides this
Changed to new style string formatting

* Update homeassistant/components/scene/fibaro.py

Co-Authored-By: pbalogh77 <peter.balogh2@gmail.com>
2018-12-03 14:57:55 +01:00
Paulus Schoutsen
acd2f55d4f Merge pull request #18958 from home-assistant/rc
0.83.3
2018-12-03 13:56:35 +01:00
Paulus Schoutsen
4ef1bf2157 Bumped version to 0.83.3 2018-12-03 11:42:49 +01:00
kennedyshead
106cb63922 bump aioasuswrt version (#18955) 2018-12-03 11:42:39 +01:00
kennedyshead
f6a79059e5 fix aioasuswrt sometimes return empty lists (#18742)
* aioasuswrt sometimes return empty lists

* Bumping aioasuswrt to 1.1.12
2018-12-03 11:42:38 +01:00
Paulus Schoutsen
35690d5b29 Add users added via credentials to admin group too (#18922)
* Add users added via credentials to admin group too

* Update test_init.py
2018-12-03 11:41:04 +01:00
William Scanlon
3575c34f77 Use capability of sensor if present to fix multisensor Wink devices (#18907) 2018-12-03 11:41:03 +01:00
ludeeus
ee1c29b392 corrects , -> . typo 2018-12-03 11:40:50 +01:00
ludeeus
82d89edb4f Use dict.get('key') instead of dict['key'] 2018-12-03 11:40:50 +01:00
ludeeus
475be636d6 Fix requirements_all 2018-12-03 11:40:50 +01:00
ludeeus
f8218b5e01 Fix IndexError for home stats 2018-12-03 11:40:50 +01:00
ludeeus
79a9c1af9e bump ghlocalapi to use clear_scan_result 2018-12-03 11:40:03 +01:00
ludeeus
6de0ed3f0a Fix stability issues with multiple units 2018-12-03 11:40:03 +01:00
Andrew Hayworth
1d717b768d bugfix: ensure the google_assistant component respects allow_unlock (#18874)
The `Config` object specific to the `google_assistant` component
had a default value for `allow_unlock`. We were not overriding this
default when constructing the Config object during `google_assistant`
component setup, whereas we do when setting up the `cloud` component.

To fix, we thread the `allow_unlock` parameter down through http setup,
and ensure that it's set correctly. Moreover, we also change the
ordering of the `Config` parameters, and remove the default. Future
refactoring should not miss it, as it is now a required parameter.
2018-12-03 11:38:43 +01:00
William Scanlon
85c0de550c Use capability of sensor if present to fix multisensor Wink devices (#18907) 2018-12-03 11:34:22 +01:00
Paulus Schoutsen
d2b62840f2 Add users added via credentials to admin group too (#18922)
* Add users added via credentials to admin group too

* Update test_init.py
2018-12-03 11:34:01 +01:00
kennedyshead
17c6ef5d54 bump aioasuswrt version (#18955) 2018-12-03 11:13:06 +01:00
cdce8p
3904d83c32 Extend partial reload to include packages (#18884)
* Merge packages after partial reload

* Remove merge from core reload & test

* Integrate merge in 'async_hass_config_yaml'

* Merge executors
2018-12-03 10:56:26 +01:00
Oliver
f3946cb54f Push to version 0.7.7 of denonavr (#18917) 2018-12-03 10:07:43 +01:00
James Hilliard
832fa61477 Initial hlk-sw16 relay switch support (#17855)
* Initial hlk-sw16 relay switch support

* remove entity_id and validate relay id's

* Bump hlk-sw16 library version and cleanup component

* refactor hlk-sw16 switch platform loading

* Use voluptuous to coerce relay id to string

* remove force_update for SW16Switch

* Move to callback based hlk-sw16 relay state changes

* fix hlk-sw16 default port and cleanup some unused variables

* Refactor to allow registration of multiple HLK-SW16 device

* Store protocol in instance variable instead of class variable

* remove is_connected

* flake8 style fix

* Move reconnect logic into HLK-SW16 client library

* Cleanup and improve logging

* Load hlk-sw16 platform entities at same time per device

* scope SIGNAL_AVAILABILITY to device_id

* Fixes for connection resume

* move device_client out of switches loop

* Add timeout for commands and keep alive

* remove unused variables
2018-12-03 09:31:53 +01:00
Andrew Hayworth
5ae65142b8 Allow verisure locks to be configured with a default code (#18873)
* Allow verisure locks to be configured with a default code

* linting fix

* PR feedback

* PR feedback - try harder to prevent future typos

A python mock is a magical thing, and will respond to basicaly
any method you call on it. It's somewhat better to assert against
an explicit variable named 'mock', rather than to assert on the
method name you wanted to mock... could prevent a typo from messing up
tests.

* PR feedback: convert tests to integration-style tests

Set up a fake verisure hub, stub out a _lot_ of calls, then test
after platform discovery and service calls.

It should be noted that we're overriding the `update()` calls in
these tests. This was done to prevent even further mocking of
the verisure hub's responses.

Hopefully, this'll be a foundation for people to write more tests.

* more pr feedback
2018-12-03 07:25:54 +01:00
GeoffAtHome
eb584a26e2 Add lightwave components for switches and lights (#18026)
* Added lightwave components for switches and lights.

* Address warnings raised by Hound

* Correcting lint messages and major typo. This time tested before commit.

* Trying to fix author

* Minor lint changes

* Attempt to correct other lint error.

* Another lint attempt.

* More lint issues.

* Last two lint errors! Hurrah.

* Changes after review from fabaff.

* Moved device dependent code to PyPi.

* Replaced DEPENDENCIES with REQUIREMENTS

* Updated following code review from Martin Hjelmare.

* Added lightwave to requirements_all.txt

* Omit lightwave from tests.

* Updated requirements_all.txt

* Refactored how lightwave lights and switches load.

* Removed imports that were no longer required.

* Add guard for no discovery_info.

* Make it a guard clause and save indentation. Rename LRFxxx to LWRFxxx.

* Sorted imports to match style guidelines.

* Correct return value.

* Update requirements_all.txt

* Catch case where we have no lights or switches configured.

* Improve configuration validation.
2018-12-02 20:58:31 +01:00
emontnemery
87fb492b14 Remove commented out code (#18925) 2018-12-02 19:12:03 +01:00
Erik
b9ad19acbf Add JSON attribute topic to MQTT binary sensor
Add MqttAttributes mixin
2018-12-02 17:00:31 +01:00
Paulus Schoutsen
d1a621601d No more opt-out auth (#18854)
* No more opt-out auth

* Fix var
2018-12-02 16:32:53 +01:00
emontnemery
ae9e3d83d7 Reconfigure MQTT switch component if discovery info is changed (#18179) 2018-12-02 16:16:46 +01:00
emontnemery
afa99915e3 Reconfigure MQTT light component if discovery info is changed (#18176) 2018-12-02 16:16:36 +01:00
Martin Fuchs
bb13829e13 Set sensor to unavailable if battery is dead. (#18802) 2018-12-02 16:01:18 +01:00
Daniel Høyer Iversen
fb12294bb7 remove unused import 2018-12-02 15:54:52 +01:00
Vladimir Eremin
debae6ad2e Fix hdmi_cec entity race (#18753)
* Update shouldn't be called before adding the entity.
* Transitional states from
  8adc786bac/include/cectypes.h (L458-L459)

Addressing https://github.com/home-assistant/home-assistant/issues/12846
2018-12-02 15:51:04 +01:00
Otto Winter
eec4564c71 Show ANSI color codes in logs in Hass.io (#18834)
* Hass.io: Show ANSI color codes in logs

* Lint

* Fix test

* Lint
2018-12-02 15:46:14 +01:00
Daniel Høyer Iversen
08dbd792cd Improve logging and error handling 2018-12-02 15:35:59 +01:00
Andrew Hayworth
b7e2522083 bugfix: ensure the google_assistant component respects allow_unlock (#18874)
The `Config` object specific to the `google_assistant` component
had a default value for `allow_unlock`. We were not overriding this
default when constructing the Config object during `google_assistant`
component setup, whereas we do when setting up the `cloud` component.

To fix, we thread the `allow_unlock` parameter down through http setup,
and ensure that it's set correctly. Moreover, we also change the
ordering of the `Config` parameters, and remove the default. Future
refactoring should not miss it, as it is now a required parameter.
2018-12-02 11:14:46 +01:00
Paulus Schoutsen
a62fc7ca04 Use string formatting (#18886)
* Use string formatting

* Fix lint issue

* Fix change
2018-12-02 10:53:09 +01:00
pbalogh77
0a68cae507 Fibaro ubs (#18889)
* Fibaro HC connection, initial commit

Very first steps working, connects, fetches devices, represents sensors, binary_sensors and lights towards HA.

* Cover, switch, bugfixes

Initial support for covers
Initial support for switches
Bugfixes

* Some cleanup and improved lights

pylint based cleanup
light switches handled properly
light features reported correctly

* Added status updates and actions

Lights, Blinds, Switches are mostly working now

* Code cleanup, fiblary3 req

Fiblary3 is now in pypi, set it as req
Cleanup based on pylint

* Included in .coveragerc and added how to use guide

Included the fibaro component in coveragerc
Added usage instructions to file header

* PyLint inspired fixes

Fixed pylint warnings

* PyLint inspired fixes

PyLint inspired fixes

* updated to fiblary3 0.1.5

* Minor fixes to finally pass pull req

Fixed fiblary3 to work with python 3.5
Updated fiblary3 to 0.1.6
(added energy and batteryLevel dummies)

* module import and flake8 fixes

Finally (hopefully) figured out what lint is complaining about

* Fixed color support for lights, simplified callback

Fixed color support for lights
Simplified callback for updates
Uses updated fiblary3 for color light handling

* Lean and mean refactor

While waiting for a brave reviewer, I've been making the code smaller and easier to understand.

* Minor fixes to please HoundCI

* Removed unused component

Scenes are not implemented yet

* Nicer comments.

* DEVICE_CLASS, ignore plugins, improved mapping

Added support for device class and icons in sensors and binary_sensors
Improved mapping of sensors and added heuristic matching
Added support for hidden devices
Fixed conversion to float in sensors

* Fixed dimming

Fibaro apparently does not need, nor like the extra turnOn commands for dimmers

* flake8

* Cleanup, Light fixes, switch power

Cleanup of the component to separate init from connect, handle connection error better
Improved light handling, especially for RGBW strips and working around Fibaro quirks
Added energy and power reporting to switches

* Missing comment added

Missing comment added to please flake8

* Removed everything but bin.sensors

Stripdown, hoping for a review

* better aligned comments

OMG

* Fixes based on code review

Fixes based on code review

* Implemented stopping

Implemented stopping of StateHandler thread
Cleanup for clarity

* Minor fix

Removed unnecessary list copying

* Nicer wording on shutdown

* Minor changes based on code review

* minor fixes based on code review

* removed extra line break

* Added Fibaro omcponents

Added cover, light, sensor and switch components

* Improved support for Fibaro UBS

Improved support for Fibaro Universal Binary Sensor, when configured to flood sensor or motion sensor.
2018-12-02 10:52:37 +01:00
Adam Mills
a10cbadb57 Restore states when removing/adding entities (#18890) 2018-12-02 10:51:15 +01:00
emontnemery
bbb40fde84 Optionally do not log template rendering errors (#18724) 2018-12-02 10:31:46 +01:00
emontnemery
ce218b172a Small refactoring of MQTT climate (#18814) 2018-12-02 10:30:07 +01:00
emontnemery
2e4e673bbe Small refactoring of MQTT alarm (#18813) 2018-12-02 10:29:31 +01:00
emontnemery
db4a0e3244 Small refactoring of MQTT cover (#18850) 2018-12-02 10:27:50 +01:00
Daniel Høyer Iversen
de82df3c6b Merge pull request #18892 from home-assistant/upgrade-sphinx
Upgrade Sphinx to 1.8.2
2018-12-02 09:57:52 +01:00
Daniel Høyer Iversen
3bc83920b4 Merge branch 'dev' into upgrade-sphinx 2018-12-02 08:46:34 +01:00
Daniel Høyer Iversen
253dc66129 Merge pull request #18895 from home-assistant/upgrade-slacker
Upgrade slacker to 0.11.0
2018-12-02 08:45:02 +01:00
Daniel Høyer Iversen
b063547138 Merge pull request #18851 from emontnemery/mqtt_fan_refactor
Small refactoring of MQTT fan
2018-12-02 08:43:56 +01:00
Daniel Høyer Iversen
d8c6cb1112 Merge pull request #18852 from emontnemery/mqtt_sensor_refactor
Small refactoring of MQTT sensor
2018-12-02 08:40:57 +01:00
Daniel Høyer Iversen
5b0c12b12b Merge pull request #18864 from meatheadmike/dev
Bump pywemo to 0.4.33 (expanded port range fixes dimmers on latest firmware)
2018-12-02 08:38:37 +01:00
Daniel Høyer Iversen
ba372c085c Merge pull request #18880 from ludeeus/tautulli-fix
Fixes error with getting attributes from Tautulli
2018-12-02 08:35:30 +01:00
Daniel Høyer Iversen
2c36f4411e Merge pull request #18879 from ludeeus/multiple-googlehome
Fix stability issues with multiple googlehome units
2018-12-02 08:33:33 +01:00
Daniel Høyer Iversen
8eb9445bea Merge pull request #18904 from home-assistant/upgrade-Pillow
Upgrade pillow to 5.3.0
2018-12-02 08:28:27 +01:00
Daniel Høyer Iversen
456cec2931 Merge pull request #18903 from home-assistant/upgrade-ruamel.yaml
Upgrade ruamel.yaml to 0.15.80
2018-12-02 08:28:04 +01:00
Daniel Høyer Iversen
af7fe8c4fd Merge pull request #18902 from home-assistant/upgrade-restrictedpython
Upgrade restrictedpython to 4.0b7
2018-12-02 08:27:40 +01:00
Daniel Høyer Iversen
41ad04276b Upgrade sphinx-autodoc-typehints to 1.5.1 (#18893) 2018-12-02 08:26:46 +01:00
Fabian Affolter
e591234b59 Upgrade keyring to 17.0.0 (#18901) 2018-12-02 08:26:23 +01:00
Fabian Affolter
9f3c9cdb11 Upgrade pillow to 5.3.0 2018-12-02 00:30:02 +01:00
Fabian Affolter
48b8fc9e01 Upgrade ruamel.yaml to 0.15.80 2018-12-02 00:17:41 +01:00
Fabian Affolter
4807ad7875 Upgrade restrictedpython to 4.0b7 2018-12-02 00:11:47 +01:00
Fabian Affolter
7b6893c9d3 Fix change 2018-12-01 22:08:15 +01:00
Fabian Affolter
4b85ffae4f Upgrade slacker to 0.11.0 2018-12-01 22:01:22 +01:00
Fabian Affolter
2ca4893948 Upgrade sphinx-autodoc-typehints to 1.5.1 2018-12-01 21:48:56 +01:00
Fabian Affolter
9156a827ce Upgrade Sphinx to 1.8.2 2018-12-01 21:45:16 +01:00
ludeeus
1dac84e9dd corrects , -> . typo 2018-12-01 21:34:31 +01:00
ludeeus
da715c2a03 Use dict.get('key') instead of dict['key'] 2018-12-01 21:32:31 +01:00
Fabian Affolter
fc1a4543d3 Fix lint issue 2018-12-01 20:57:39 +01:00
Fabian Affolter
54904fb6c0 Use string formatting 2018-12-01 19:27:21 +01:00
Michael Nosthoff
bd09e96681 Reintroduce unique_id for Netatmo sensor (#18774)
* netatmo: make module type identification more consistent

For the interpretation of voltage values the different types of netatmo
modules need to be distinguished. This is currently done by selecting
the second character of the modules '_id'. The _id-field actually
contains a mac address. This is an undocumented way of identifying the
module_type.

The netatmo API also delivers a field called 'type' which provides a
more consistent way to differentiate the fields. This commit introduces
a differentiation which uses this provided type. This should improve
readability.

Also the field module_id is renamed to module_type which should better
resemble what it actually represents.

* netatmo: reintroduce unique_id using actual module mac address

Each netatmo module features a unique MAC-Address. The base station uses
an actual assigned MAC Address it also uses on the Wifi it connects to.
All other modules have unique MAC Addresses which are only assigned and
used by Netatmo on the internal Wireless-Network. All theses Addresses
are exposed via the API. So we could use the combination
MAC-Address-Sensor_type as unique_id.

In a previous commit this had already been tried but there was a
misunderstanding in what the 'module_id' represented. It was actually
only a module_type representation so it clashed when two modules of the
same type where used.

* Netatmo: fixed line length
2018-12-01 18:00:49 +01:00
ludeeus
8e84401b68 bump ghlocalapi to use clear_scan_result 2018-12-01 16:28:22 +01:00
ludeeus
934eccfeee Fix stability issues with multiple units 2018-12-01 14:55:50 +01:00
ludeeus
89bd6fa494 Fix requirements_all 2018-12-01 14:51:32 +01:00
ludeeus
d8b9bee7fb Fix IndexError for home stats 2018-12-01 14:51:32 +01:00
Joakim Sørensen
558504c686 Fix ordinal filter in template (#18878) 2018-12-01 14:49:34 +01:00
Eliseo Martelli
c69fe43e75 fixed state case for rtorrent (#18778) 2018-12-01 12:00:35 +01:00
Carlos Gustavo Sarmiento
29f15393b1 Updated UVC camera component to support SSL connections (#18829) 2018-12-01 11:58:59 +01:00
Mahasri Kalavala
c23792d1fb Added new filters for templates (#18125)
* added additional filters

Added base64_encode, base64_decode and ordinal filters.

* added test cases

added test cases for base64_encode, base64_decode and ordinal filters.

* forgot to add filters :)
2018-12-01 10:38:10 +01:00
damarco
1ae58ce48b Add support for zha device registry (#18755) 2018-12-01 10:31:49 +01:00
ehendrix23
ecca51b16b Add tests for directv platform (#18590)
* Create test for platform

Created test for platform.
Added media_stop to common.py test

* Multiple improvements

Fixed lint issue in common.py
Fixed lint issues in test_directv.py
Improved patching import using modile_patcher.start() and stop()
Added asserts for service calls.

* Updates based on Martin's review

Updates based on Martin's review.

* Updated test based on PR#18474

Updated test to use service play_media instead of select_source based on change from PR18474

* Lint issues

Lint issues

* Further updates based on feedback

Updates based on feedback provided.

* Using async_load_platform for discovery test

Using async_load_platform for discovery tests.
Added asserts to ensure entities are created with correct names.

* Used HASS event_loop to setup component

Use HASS event_loop to setup the component async.

* Updated to use state machine for # entities

Updated to use state machine to count # entities instead of entities.

* Use hass.loop instead of getting current loop

Small update to use hass.loop instead, thanks Martin!

* Forgot to remove asyncio

Removed asyncio import.

* Added fixtures

Added fixtures.

* Remove not needed updates and assertions

* Return mocked dtv instance from side_effect

* Fix return correct fixture instance

* Clean up assertions

* Fix remaining patches

* Mock time when setting up component in fixture

* Patch time correctly

* Attribute _last_update should return utcnow
2018-12-01 10:28:27 +01:00
Aaron Bach
3a854f4c05 Fix issues with 17track.net sensor names (#18860) 2018-11-30 21:54:40 -07:00
Aaron Bach
c24ddfb1be Bump py17track to 2.1.1 (#18861) 2018-11-30 21:12:55 -07:00
meatheadmike
0754a63969 Bumped pywemo to 0.4.33 2018-11-30 14:03:32 -07:00
meatheadmike
8a75bee82f bump pywemo to 0.4.33
Bump pywemo to 0.4.33 - includes expended port range fix for dimmers
2018-11-30 14:00:26 -07:00
Paulus Schoutsen
df21dd21f2 RFC: Call services directly (#18720)
* Call services directly

* Simplify

* Type

* Lint

* Update name

* Fix tests

* Catch exceptions in HTTP view

* Lint

* Handle ServiceNotFound in API endpoints that call services

* Type

* Don't crash recorder on non-JSON serializable objects
2018-11-30 21:28:35 +01:00
Paulus Schoutsen
bac48aa9d2 Merge pull request #18857 from home-assistant/rc
0.83.2
2018-11-30 20:09:29 +01:00
Paulus Schoutsen
53cbb28926 Fix flaky geofency test (#18855) 2018-11-30 20:06:10 +01:00
Erik Eriksson
d7809c5398 Update of volvooncall component (#18702) 2018-11-30 19:07:42 +01:00
Paulus Schoutsen
9b3373a15b Bumped version to 0.83.2 2018-11-30 17:53:14 +01:00
pbalogh77
474909b515 Hotfix for Fibaro wall plug (#18845)
Fibaro wall plug with a lamp plugged in was misrecognized as a color light, generating crashes in the update function.
2018-11-30 17:53:04 +01:00
Paulus Schoutsen
80f2c2b124 Always set hass_user (#18844) 2018-11-30 17:53:04 +01:00
Darren Foo
ada148eeae bump gtts-token to 1.1.3 (#18824) 2018-11-30 17:53:03 +01:00
emontnemery
449cde5396 Revert change to MQTT discovery_hash introduced in #18169 (#18763) 2018-11-30 17:53:03 +01:00
Paulus Schoutsen
d014517ce2 Always set hass_user (#18844) 2018-11-30 17:32:47 +01:00
pbalogh77
8f50180598 Hotfix for Fibaro wall plug (#18845)
Fibaro wall plug with a lamp plugged in was misrecognized as a color light, generating crashes in the update function.
2018-11-30 17:23:25 +01:00
Erik
1686f73749 Small refactoring of MQTT sensor 2018-11-30 16:53:56 +01:00
Erik
deb9a1133c Small refactoring of MQTT fan 2018-11-30 16:53:14 +01:00
Matt Schmitt
e0f0487ce2 Add services description (#18839) 2018-11-30 16:31:35 +01:00
Daniel Høyer Iversen
44e35ec9a1 update netatmo library (#18823) 2018-11-30 08:45:40 -05:00
emontnemery
a9990c130d Revert change to MQTT discovery_hash introduced in #18169 (#18763) 2018-11-30 13:57:17 +01:00
Darren Foo
fcdb25eb3c bump gtts-token to 1.1.3 (#18824) 2018-11-30 11:18:24 +01:00
Heine Furubotten
4bee3f760f Add Entur departure information sensor (#17286)
* Added Entur departure information sensor.

* Fixed houndci-bot comments.

* Removed tailing whitespace.

* Fixed some comments from tox lint.

* Improved docstring, i think.

* Fix for C1801

* Unit test for entur platform setup

* Rewritten entur component to have pypi dependecy.

* Propper client id for api usage.

* Minor cleanup of usage of constants.

* Made location output configurable.

* Cleaned up usage of constants.

* Moved logic to be contained within setup or update methods.

* Moved icon consts to root in module.

* Using config directly in test

* Minor changes
2018-11-30 09:06:59 +01:00
Andrew Hayworth
5f53627c0a Bump python_awair to 0.0.3 (#18819) 2018-11-30 08:47:05 +01:00
Adam Mills
22f27b8621 Store state last seen time separately (#18806)
* Store state last seen time separately

This ensures that infrequently updated entities aren't accidentally
dropped from the restore states store

* Fix mock restore cache
2018-11-30 08:26:19 +01:00
ehendrix23
a9dc4ba297 Increase pyatv to 0.3.11 (#18801) 2018-11-29 23:44:29 +01:00
Paulus Schoutsen
3701c0f219 Merge pull request #18811 from home-assistant/rc
0.83.1
2018-11-29 23:18:13 +01:00
ehendrix23
a035725c67 Service already discovered log entry (#18800)
Add debug log entry if service is already discovered.
2018-11-29 23:15:48 +01:00
Paulus Schoutsen
440614dd9d Use proper signals (#18613)
* Emulated Hue not use deprecated handler

* Remove no longer needed workaround

* Add middleware directly

* Dont always load the ban config file

* Update homeassistant/components/http/ban.py

Co-Authored-By: balloob <paulus@home-assistant.io>

* Update __init__.py
2018-11-29 23:05:23 +01:00
Paulus Schoutsen
163c881ced Bumped version to 0.83.1 2018-11-29 22:58:06 +01:00
pbalogh77
0467d0563a Hotfix for crash with virtual devices (#18808)
* Quickfix for crash with virtual devices

Added try/except to critical loops of processing
Reinforced read_devices, map_device_to_type and update processing

* oops
2018-11-29 22:57:45 +01:00
pbalogh77
2b52f27eb9 Hotfix for crash with virtual devices (#18808)
* Quickfix for crash with virtual devices

Added try/except to critical loops of processing
Reinforced read_devices, map_device_to_type and update processing

* oops
2018-11-29 22:57:05 +01:00
Aaron Bach
31d7221c90 Remove additional self from update function in RainMachine (#18810) 2018-11-29 22:51:16 +01:00
Daniel Høyer Iversen
d9124b182a Remove self from update function in rainmachine (#18807) 2018-11-29 22:51:15 +01:00
Aaron Bach
f2b818658f Bumped py17track to 2.1.0 (#18804) 2018-11-29 22:51:15 +01:00
Eric Nagley
5a6ac9ee72 BUGFIX: handle extra fan speeds. (#18799)
* BUGFIX: add support for extra fan speeds.

* Drop extra fan speeds.

Remove catch all, drop missing fan speeds.

* fix self.speed_synonyms call. Remove un-needed keys() call
2018-11-29 22:51:14 +01:00
Paulus Schoutsen
7fa5f07218 Fix race condition in group.set (#18796) 2018-11-29 22:51:13 +01:00
Paulus Schoutsen
fa9a200e3c Render the secret (#18793) 2018-11-29 22:51:13 +01:00
Paulus Schoutsen
0ca67bf6f7 Make auth backwards compat again (#18792)
* Made auth not backwards compat

* Fix tests
2018-11-29 22:51:12 +01:00
cdce8p
f1c5e756ff Fix logbook domain filter - alexa, homekit (#18790) 2018-11-29 22:51:12 +01:00
Paulus Schoutsen
ff33d34b81 Legacy api fix (#18733)
* Set user for API password requests

* Fix tests

* Fix typing
2018-11-29 22:51:11 +01:00
Ian Richardson
601389302a Convert shopping-list update to WebSockets (#18713)
* Convert shopping-list update to WebSockets

* Update shopping_list.py

* Update test_shopping_list.py
2018-11-29 22:51:10 +01:00
Ian Richardson
2ba521caf8 Add websocket call for adding item to shopping-list (#18623) 2018-11-29 22:51:10 +01:00
Aaron Bach
6f7ff9a18a Remove additional self from update function in RainMachine (#18810) 2018-11-29 14:47:41 -07:00
Daniel Høyer Iversen
4bc9e6dfe0 Remove self from update function in rainmachine (#18807) 2018-11-29 22:28:27 +01:00
Paulus Schoutsen
28215d7edd Make auth backwards compat again (#18792)
* Made auth not backwards compat

* Fix tests
2018-11-29 22:26:19 +01:00
Paulus Schoutsen
38ecf71307 Fix race condition in group.set (#18796) 2018-11-29 22:26:06 +01:00
Eric Nagley
4e272624eb BUGFIX: handle extra fan speeds. (#18799)
* BUGFIX: add support for extra fan speeds.

* Drop extra fan speeds.

Remove catch all, drop missing fan speeds.

* fix self.speed_synonyms call. Remove un-needed keys() call
2018-11-29 22:24:53 +01:00
Aaron Bach
ab4d0a7fc3 Bumped py17track to 2.1.0 (#18804) 2018-11-29 22:24:32 +01:00
Paulus Schoutsen
ca74f5efde Render the secret (#18793) 2018-11-29 22:17:01 +01:00
Eric Nagley
e50a6ef8af Add support for Mode trait in Google Assistant. (#18772)
* Add support for Mode trait in Google Assistant.

* Simplify supported logic.

* Fix SUPPORTED_MODE_SETTINGS to correct rip failures.

* more stray commas

* update tests.
2018-11-29 21:14:17 +01:00
Eliseo Martelli
5c026b1fa2 Added qbittorrent sensor platform (#18618)
* added qbittorrent sensor platform

* Added requirements

* linting

* disabled broad-except

* added noqa

* removed pass statement (left that from development session)

* Added to coveragerc & moved to async

* fixed linting

* fixed indentation

* removed white space

* added await

* Removed generic exception

* removed pylint disable

* added auth checks

* linting

* fixed linting

* fixed error

* should be fixed now

* linting

* ordered imports

* added requested changes

* Update homeassistant/components/sensor/qbittorrent.py

Co-Authored-By: eliseomartelli <martely98@gmail.com>

* Update qbittorrent.py

* Minor changes
2018-11-29 20:40:26 +01:00
cdce8p
474567e762 Fix logbook domain filter - alexa, homekit (#18790) 2018-11-29 20:16:39 +01:00
Fabian Affolter
16911a5cb4 Update lang list (fixes #18768) (#18773)
* Update lang list (fixes #18768)

* Fix lint issues
2018-11-29 19:14:14 +01:00
Daniel Høyer Iversen
46389fb6ca Update switchmate lib (#18785) 2018-11-29 19:13:08 +01:00
Paulus Schoutsen
9aeb489282 Raise NotImplementedError (#18777) 2018-11-29 16:40:49 +01:00
Daniel Høyer Iversen
8c9a39845c Round average price for Tibber (#18784) 2018-11-29 16:39:39 +01:00
Fabian Affolter
c976ac3b39 Fix lint issues 2018-11-29 12:28:50 +01:00
mdallaire
07a7ee0ac7 Add more waterfurnace sensors (#18451)
Add the following sensors that provide interesting data when using a variable speed geothermal system:

* Compressor Power
* Fan Power
* Aux Power
* Loop Pump Power
* Compressor Speed
* Fan Speed
2018-11-29 06:04:12 -05:00
Ian Richardson
a306475065 Convert shopping-list clear to WebSockets (#18769) 2018-11-29 10:06:18 +01:00
Fabian Affolter
faeaa43393 Update lang list (fixes #18768) 2018-11-29 09:26:48 +01:00
ehendrix23
aadf72d445 Fix statistics for binary sensor (#18764)
* Fix statistics for binary sensor

-) Binary sensors have 'on' and 'off' for state resulting in issue as numbers were expected. Fixed so that it works with non-numeric states as well.
-) Added check to skip unknown states.
-) Updates test so that binary sensor test will use non-numeric values for states.

* Using guard clause and changed debug to error

Changed to use a guard clause for state unknown.
Writing error on value error instead of debug.

* Add docstring
2018-11-29 09:01:56 +01:00
Paulus Schoutsen
48e28843e6 OwnTracks Config Entry (#18759)
* OwnTracks Config Entry

* Fix test

* Fix headers

* Lint

* Username for android only

* Update translations

* Tweak translation

* Create config entry if not there

* Update reqs

* Types

* Lint
2018-11-28 22:20:13 +01:00
Paulus Schoutsen
e06fa0d2d0 Default to on if logged in (#18766) 2018-11-28 22:17:37 +01:00
ehendrix23
0bdf96d94c Add block after setting up component (#18756)
Added a block_till_done after setting up component and before starting HASS.
2018-11-28 16:14:37 +01:00
Fabian Affolter
623cec206b Upgrade Adafruit-DHT to 1.4.0 (fixes #15847) (#18614) 2018-11-28 13:38:26 +01:00
Paulus Schoutsen
a2386f871d Forbid float NaN in JSON (#18757) 2018-11-28 13:25:23 +01:00
Adam Mills
5c3a4e3d10 Restore states through a JSON store instead of recorder (#17270)
* Restore states through a JSON store

* Accept entity_id directly in restore state helper

* Keep states stored between runs for a limited time

* Remove warning
2018-11-28 13:16:43 +01:00
Diogo Gomes
a039c3209b Replace token in camera.push with webhook (#18380)
* replace token with webhook

* missing PR 18206 aditions

* remove unused property

* increase robustness

* lint

* address review comments

* id -> name
2018-11-28 10:36:29 +01:00
majuss
fc8b1f4968 Update lupupy version to 0.0.13 (#18754)
* lupupy version push
2018-11-27 21:21:27 -05:00
damarco
052d305243 Add config entry for ZHA (#18352)
* Add support for zha config entries

* Add support for zha config entries

* Fix node_config retrieval

* Dynamically load discovered entities

* Restore device config support

* Refactor loading of entities

* Remove device registry support

* Send discovery_info directly

* Clean up discovery_info in hass.data

* Update tests

* Clean up rebase

* Simplify config flow

* Address comments

* Fix config path and zigpy check timeout

* Remove device entities when unloading config entry
2018-11-27 21:21:25 +01:00
ehendrix23
43676fcaf4 Moved stop method and registering STOP_EVENT outside of init (#18582)
* Moved stop method and registering outside of init

Moved the cleanup to a seperate method and perform registering for the event in setup.

* Removed use of global variable

Removed use of global variable.

* Removed API_SESSIONS

Removed unused declaration API_SESSIONS.
2018-11-27 20:41:25 +01:00
Aaron Bach
093fa6f5e9 Bumped simplisafe-python to 3.1.14 (#18752) 2018-11-27 11:40:49 -07:00
Anton Johansson
dd8544fdf8 Fix typo in log (#18751) 2018-11-27 13:09:25 -05:00
Bryan York
02309cc318 Enable Google Assistant OnOffTrait for climate devices that support them (#18544)
* Enable Google Assistant OnOffTrait for climate devices that support them

This commit enables the OnOffTrait for climate devices that have the SUPPORT_ON_OFF feature. I have tested this locally with a Sensibo device which supports ON_OFF and a nest device that does not.

* Update trait.py

* Add tests for onoff_climate

* Add OnOff trait to climate.heatpump

* Add on status to heatpump in google_assistant tests
2018-11-27 17:11:55 +01:00
Austin
2f07e92cc2 Fix decora_wifi residences (#17228)
* Fix decora multiple residences

* Fix typo

* Update decora_wifi.py
2018-11-27 16:53:28 +01:00
Luis Martinez de Bartolome Izquierdo
7b3b7d2eec Wunderlist component (#18339)
* Wunderlist component

* Check credentials

* Dont print credentials

* Update __init__.py
2018-11-27 15:44:09 +01:00
Fredrik Erlandsson
5d5c78b374 Add unique_id for Daikin entities (#18747) 2018-11-27 15:36:55 +01:00
Fredrik Erlandsson
eb2e2a116e Add unique_id for tellduslive (#18744) 2018-11-27 15:35:51 +01:00
Fredrik Erlandsson
392898e694 Updated codeowners (#18746) 2018-11-27 14:59:25 +01:00
Jason Hu
4d5338a1b0 Fix google assistant request sync service call (#17415)
* Update __init__.py

* Add optional agent_user_id field to request_sync service

* Update services.yaml
2018-11-27 14:57:42 +01:00
kennedyshead
87507c4b6f fix aioasuswrt sometimes return empty lists (#18742)
* aioasuswrt sometimes return empty lists

* Bumping aioasuswrt to 1.1.12
2018-11-27 08:20:25 -05:00
Luis Martinez de Bartolome Izquierdo
9d1b94c24a Supports the new Netatmo Home Coach (#18308)
* Supports the new Netatmo Home Coach

* unused import

* Missing docstring

* Fixed pylint

* pydocs

* doc style
2018-11-27 14:01:34 +01:00
emontnemery
16e3ff2fec Mqtt light refactor (#18227)
* Rename mqtt light files

* Refactor mqtt light

* Remove outdated testcase

* Add backwards compatibility for MQTT discovered MQTT lights.
Refactor according to review comments.
2018-11-27 14:00:05 +01:00
Robert Dunmire III
c1ed2f17ac Update librouteros and re-connect to api if connection is lost (#18421)
* Reconnect when connection is lost

* Fix tabs

* add librouteros.exceptions

* add logger

* fix line too long

* added import librouteros

* Update librouteros version

* Update mikrotik.py

* Update mikrotik.py

* Fix trailing whitespace

* Update mikrotik.py

* Update mikrotik.py
2018-11-27 13:26:52 +01:00
Fabian Affolter
1cbe080df9 Fix remaining issues (#18416) 2018-11-27 13:21:42 +01:00
Malte Franken
61e0e11156 Geo Location platform code clean up (#18717)
* code cleanup to make use of new externalised feed manager

* fixed lint

* revert change, keep asynctest

* using asynctest

* changed unit test from mocking to inspecting dispatcher signals

* code clean-up
2018-11-27 13:12:29 +01:00
Malte Franken
013e181497 U.S. Geological Survey Earthquake Hazards Program Feed platform (#18207)
* new platform for usgs earthquake hazards program feed

* lint and pylint issues

* fixed config access

* shortened names of platform, classes, etc.

* refactored tests

* fixed hound

* regenerated requirements

* refactored tests

* fixed hound
2018-11-27 12:55:15 +01:00
David Bonnes
9a25054a0d Add zones to evohome component (#18428)
* Added Zones, and removed available() logic

flesh out Zones

tidy up init

some more tidying up

Nearly there - full functionality

passed txo - ready to send PR

Ready to PR, except to remove logging

Add Zones and associated functionality to evohome component

Add Zones to evohome (some more tidying up)

Add Zones to evohome (Nearly there - full functionality)

Add Zones to evohome (passed tox)

Add Zones to evohome (except to remove logging)

Add Zones and associated functionality to evohome component

Revert _LOGGER.warn to .debug, as it should be

Cleanup stupid REBASE

* removed a duplicate/unwanted code block

* tidy up comment

* use async_added_to_hass instead of bus.listen

* Pass evo_data instead of hass when instntiating

* switch to async version of setup_platform/add_entities

* Remove workaround for bug in client library
 - using github version for now, as awaiting new PyPi package

* Avoid invalid-name lint - use 'zone_idx' instead of 'z'

* Fix line too long error

* remove commented-out line of code

* fix a logic error, improve REDACTION of potentially-sensitive infomation

* restore use of EVENT_HOMEASSISTANT_START to improve HA startup time

* added a docstring to _flatten_json

* Switch instantiation from component to platform

* Use v0.2.8 of client api (resolves logging bug)

* import rather than duplicate, and de-lint

* We use evohomeclient v0.2.8 now

* remove all the api logging

* Changed scan_interal to Throttle

* added a configurable scan_interval

* small code tidy-up, removed sub-function

* tidy up update() code

* minimize use of self.hass.data[]

* remove lint

* remove unwanted logging

* remove debug code

* correct a small coding error

* small tidyup of code

* remove flatten_json

* add @callback to _first_update()

* switch back to load_platform

* adhere to standards fro logging

* use new format string formatting

* minor change to comments

* convert scan_interval to timedelta from int

* restore rounding up of scan_interval

* code tidy up

* sync when in sync context

* fix typo

* remove raises not needed

* tidy up typos, etc.

* remove invalid-name lint

* tidy up exception handling

* de-lint/pretty-fy

* move 'status' to a JSON node, so theirs room for 'config', 'schedule' in the future
2018-11-27 12:17:22 +01:00
emontnemery
a03cb12c61 Reconfigure MQTT sensor component if discovery info is changed (#18178)
* Reconfigure MQTT sensor component if discovery info is changed

* Do not pass hass to MqttSensor constructor

* Remove duplicated line
2018-11-27 11:23:47 +01:00
emontnemery
4a4ed128db Reconfigure MQTT fan component if discovery info is changed (#18177) 2018-11-27 11:22:55 +01:00
emontnemery
6170065a2c Reconfigure MQTT cover component if discovery info is changed (#18175)
* Reconfigure MQTT cover component if discovery info is changed

* Do not pass hass to MqttCover constructor
2018-11-27 11:22:26 +01:00
Matt Hamilton
4f2e7fc912 remove pbkdf2 upgrade path (#18736) 2018-11-27 10:42:56 +01:00
Paulus Schoutsen
c2f8dfcb9f Legacy api fix (#18733)
* Set user for API password requests

* Fix tests

* Fix typing
2018-11-27 10:41:44 +01:00
Paulus Schoutsen
9d7b1fc3a7 Enforce permissions for Websocket API (#18719)
* Handle unauth exceptions in websocket

* Enforce permissions in websocket API
2018-11-27 10:12:31 +01:00
Ville Skyttä
7248c9cb0e Remove some unused imports (#18732) 2018-11-27 09:35:35 +01:00
Ville Skyttä
b4e2f2a6ef Upgrade pytest and -timeout (#18722)
* Upgrade pytest to 4.0.1

* Upgrade pytest-timeout to 1.3.3
2018-11-26 22:43:14 +01:00
Paulus Schoutsen
9894eff732 Fix logbook filtering entities (#18721)
* Fix logbook filtering entities

* Fix flaky test
2018-11-26 19:53:24 +01:00
Paulus Schoutsen
1f123ebcc1 Updated frontend to 20181126.0 2018-11-26 14:40:43 +01:00
Paulus Schoutsen
3c92aa9ecb Update translations 2018-11-26 14:30:21 +01:00
Paulus Schoutsen
7848381f43 Allow managing cloud webhook (#18672)
* Add cloud webhook support

* Simplify payload

* Add cloud http api tests

* Fix tests

* Lint

* Handle cloud webhooks

* Fix things

* Fix name

* Rename it to cloudhook

* Final rename

* Final final rename?

* Fix docstring

* More tests

* Lint

* Add types

* Fix things
2018-11-26 14:10:18 +01:00
pbalogh77
4a661e351f Use asyncio Lock for fibaro light (#18622)
* Use asyncio Lock for fibaro light

* line length and empty line at end

* async turn_off

Turned the turn_off into async as well

* bless you, blank lines...

My local flake8 lies to me. Not cool.
2018-11-26 13:17:56 +01:00
Ian Richardson
b5b5bc2de8 Convert shopping-list update to WebSockets (#18713)
* Convert shopping-list update to WebSockets

* Update shopping_list.py

* Update test_shopping_list.py
2018-11-26 09:59:53 +01:00
emontnemery
d290ce3c9e Small refactoring of MQTT binary_sensor (#18674) 2018-11-25 20:53:03 +01:00
Franck Nijhof
2cbe083460 ⬆️ Upgrades InfluxDB dependency to 5.2.0 (#18668) 2018-11-25 20:52:09 +01:00
Paulus Schoutsen
8b8629a5f4 Add permission checks to Rest API (#18639)
* Add permission checks to Rest API

* Clean up unnecessary method

* Remove all the tuple stuff from entity check

* Simplify perms

* Correct param name for owner permission

* Hass.io make/update user to be admin

* Types
2018-11-25 18:04:48 +01:00
Fabian Affolter
f387cdec59 Upgrade pysnmp to 4.4.6 (#18695) 2018-11-25 17:59:14 +01:00
Adam Mills
78b90be116 Async cover template tests (#18690) 2018-11-25 11:39:35 -05:00
Adam Mills
91c526d9fe Async device sun light trigger tests (#18689) 2018-11-25 11:39:18 -05:00
Jens
f3ce463862 Adds SomfyContactIOSystemSensor to TaHoma (#18560)
* Sorts all TAHOME_TYPES and adds SomfyContactIOSystemSensor as it wasn't added with 558b659f7c

* Fixes syntax errors related to sorting of entries.
2018-11-25 13:47:16 +01:00
Joakim Sørensen
23f5d785c4 Set correct default offset (#18678) 2018-11-25 12:30:38 +01:00
Soós Péter
cd773455f0 Fix false log message on CAPsMAN only devices (#18687)
* Fix false log message on CAPsMAN only devices 

False debug log message appeared on CAPsMAN only devices without physichal wireless interfaces. This fix eliminates them.

* Fixed indentation to pass flake8 test
2018-11-25 12:21:26 +01:00
Fabian Affolter
5a5cbe4e72 Upgrade youtube_dl to 2018.11.23 (#18694) 2018-11-25 11:41:49 +01:00
Daniel Høyer Iversen
ad2e8b3174 update mill lib, handle bad data from mill server (#18693) 2018-11-25 09:39:06 +01:00
Andrew Hayworth
eb6b6ed87d Add Awair sensor platform (#18570)
* Awair Sensor Platform

This commit adds a sensor platform for Awair devices, by accessing
their beta API. Awair heavily rate-limits this API, so we throttle
updates based on the number of devices found. We also allow for the
user to bypass API device listing entirely, because the device list
endpoint is limited to only 6 calls per day. A crashing or restarting
server would quickly hit that limit.

This sensor platform uses the python_awair library (also written
as part of this PR), which is available for async usage.

* Disable pylint warning for broad try/catch

It's true that this is generally not a great idea, but we really don't
want to crash here. If we can't set up the platform, logging it and
continuing is the right answer.

* Add space to satisfy the linter

* Awair platform PR feedback

- Bump python_awair to 0.0.2, which has support for more granular exceptions
- Ensure we have python_awair available in test
- Raise PlatformNotReady if we can't set up Awair
- Make the 'Awair score' its own sensor, rather than exposing it other ways
- Set the platform up as polling, and set a sensible default
- Pass in throttling parameters to the underlying data class, rather
than use hacky global variable access to dynamically set the interval
- Switch to dict access for required variables
- Use pytest coroutines, set up components via async_setup_component,
  and test/modify/assert in generally better ways
- Commit test data as fixtures

* Awair PR feedback, volume 2

- Don't force updates in test, instead modify time itself and let
  homeassistant update things "normally".
- Remove unneeded polling attribute
- Rename timestamp attribute to 'last_api_update', to better reflect
  that it is the timestamp of the last time the Awair API servers
  received data from this device.
- Use that attribute to flag the component as unavailable when data
  is stale. My own Awair device periodically goes offline and it really
  hardly indicates that at all.
- Dynamically set fixture timestamps to the test run utcnow() value,
  so that we don't have to worry about ancient timestamps in tests
  blowing up down the line.
- Don't assert on entities directly, for the most part. Find desired
  attributes in ... the attributes dict.

* Patch an instance of utcnow I overlooked

* Switch to using a context manager for timestream modification

Honestly, it's just a lot easier to keep track of patches. Moreover,
the ones I seem to have missed are now caught, and tests seem to
consistently pass.

Also, switch test_throttle_async_update to manipulating time more
explicitly.

* Missing blank line, thank you hound

* Fix pydocstyle error

I very much need to set up a script to do this quickly w/o tox, because
running flake8 is not enough!

* PR feedback

* PR feedback
2018-11-25 09:01:19 +01:00
Adam Mills
00c9ca64c8 Async tests for mqtt switch (#18685) 2018-11-24 17:08:28 -05:00
Adam Mills
6f0a3b4b22 Async tests for counter (#18684) 2018-11-24 16:12:29 -05:00
Adam Mills
66f1643de5 Async timer tests (#18683) 2018-11-24 16:12:19 -05:00
Adam Mills
50a30d4dc9 Async tests for remaining device trackers (#18682) 2018-11-24 15:10:57 -05:00
Adam Mills
6ebdc7dabc Async tests for owntracks device tracker (#18681) 2018-11-24 14:34:36 -05:00
Adam Mills
d24ea7da90 Async tests for device tracker mqtt (#18680) 2018-11-24 13:24:06 -05:00
emontnemery
5e18d52302 Reconfigure MQTT alarm component if discovery info is changed (#18173) 2018-11-24 10:48:01 +01:00
emontnemery
e41af133fc Reconfigure MQTT climate component if discovery info is changed (#18174) 2018-11-24 10:40:07 +01:00
Bram Kragten
986ca23934 Dict -> dict (#18665) 2018-11-24 10:02:06 +01:00
Kacper Krupa
8771f9f7dd converted majority of effects from ifs to dict map, which makes it easier to extend in the future. Also, added LSD effect! (#18656) 2018-11-23 23:53:33 +01:00
Bram Kragten
37327f6cbd Add save command to lovelace (#18655)
* Add save command to lovelace

* Default for save should by json

* typing
2018-11-23 22:56:58 +01:00
emontnemery
4c04abfccc Merge pull request #18654 from emontnemery/fix_mqtt_availability_qos
Support updated MQTT QoS when reconfiguring MQTT availability
2018-11-23 17:13:29 +01:00
Erik
b198bb441a Support updated MQTT QoS when reconfiguring MQTT availability 2018-11-23 15:32:24 +01:00
Eliseo Martelli
1c17b885db Added deviceclass timestamp constant (#18652)
* Added deviceclass timestamp

* added device class timestamp to sensor

* fixed comment
2018-11-23 14:51:26 +01:00
Paulus Schoutsen
c0cf29aba9 Remove since last boot from systemmonitor sensor (#18644)
* Remove since last boot

* Make systemmonitor last_boot be a timestamp
2018-11-23 11:55:45 +01:00
Ian Richardson
92978b2f26 Add websocket call for adding item to shopping-list (#18623) 2018-11-23 08:56:18 +01:00
Adam Mills
c99204149c Convert device tracker init tests to async (#18640) 2018-11-23 08:55:25 +01:00
cdheiser
98f159a039 [Breaking Change] Cleanup Lutron light component (#18650)
Remove the return value from setup_platform
Convert LutronLight.__init__ to use super() when referencing the parent class.
Change device_state_attributes() to use lowercase snakecase (Rename 'Lutron Integration ID' to 'lutron_integration_id')
2018-11-23 08:54:28 +01:00
Eliseo Martelli
bb37151987 fixed wording that may confuse user (#18628) 2018-11-23 01:46:22 +01:00
Diogo Gomes
af0f3fcbdb IPMA Weather Service - version bump (#18626)
* version bump

* gen

* gen
2018-11-22 19:09:45 -05:00
Joakim Sørensen
c7bfdbf3cf Merge pull request #18632 from dshokouhi/neato_additional_error_messages
Add additional neato error messages to status attribute
2018-11-22 22:20:58 +01:00
David F. Mulcahey
67aa76d295 Refactor ZHA (#18629)
* refactor ZHA

* lint

* review request

* Exclude more zha modules from coverage
2018-11-22 19:00:46 +01:00
Fredrik Erlandsson
cccc41c23e Updated webhook_register, version bump pypoint (#18635)
* Updated webhook_register, version bump pypoint

* A binary_sensor should be a BinarySensorDevice
2018-11-22 16:43:10 +01:00
Pascal Vizeli
13144af65e Fix raising objects on proxy camera component 2018-11-22 15:06:31 +01:00
Giuseppe
b246fc977e Add support for cropping pictures in proxy camera (#18431)
* Added support for cropping pictures in proxy camera

This includes extending the configuration to introduce a mode
(either 'resize', default, or 'crop') and further coordinates
for the crop operation.

* Also fixed async job type, following code review
2018-11-22 13:14:28 +01:00
Bram Kragten
e5d2900151 Fix vol Dict -> dict (#18637) 2018-11-22 12:48:50 +01:00
mopolus
01ee03a9a1 Add support for multiple IHC controllers (#18058)
* Added support for secondary IHC controller

Most IHC systems only have one controller but the system can be setup with a linked secondary controller.
I have updated the code to have it support both primary and secondary controller. Existing configuration is not impacted and secondary controller can be setup the same way, with similar settings nested under 'secondary' in the configuration

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update __init__.py

* Update const.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update const.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update ihc.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

* Update __init__.py

indentation was incorrect for "load_platform" in "get_manual_configuration". Load_platform was not called with the correct component name
2018-11-22 09:45:40 +01:00
Daniel Shokouhi
7daf2caef2 Correct error message 2018-11-21 23:31:08 -08:00
Daniel Shokouhi
9f36cebe59 Add additional neato error messages to status attribute 2018-11-21 22:47:30 -08:00
Nick Whyte
22ab83acae Cleanup BOM dependencies + add basic test + IDEA autoformat (#18462)
* Cleanup BOM dependencies + add basic test
2018-11-21 20:41:53 -05:00
nragon
1ad3c3b1e2 Minor change to still image on mjpeg (#18602)
* Update mjpeg.py

* Lint
2018-11-21 23:12:16 +01:00
Josh Anderson
3d178708fc Add /sbin to launchd PATH (#18601)
* Add /sbin to launchd PATH

* Put /sbin at the end to allow overrides

Co-Authored-By: andersonshatch <andersonshatch@gmail.com>
2018-11-21 20:56:38 +01:00
Paulus Schoutsen
1341ecd2eb Use proper signals (#18613)
* Emulated Hue not use deprecated handler

* Remove no longer needed workaround

* Add middleware directly

* Dont always load the ban config file

* Update homeassistant/components/http/ban.py

Co-Authored-By: balloob <paulus@home-assistant.io>

* Update __init__.py
2018-11-21 20:55:21 +01:00
Paulus Schoutsen
5b3e9399a9 Bump to 0.84.0.dev0 2018-11-21 20:53:44 +01:00
801 changed files with 29474 additions and 12282 deletions

View File

@@ -73,7 +73,8 @@ omit =
homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py
homeassistant/components/daikin.py
homeassistant/components/daikin/__init__.py
homeassistant/components/daikin/const.py
homeassistant/components/*/daikin.py
homeassistant/components/digital_ocean.py
@@ -105,18 +106,24 @@ omit =
homeassistant/components/enocean.py
homeassistant/components/*/enocean.py
homeassistant/components/envisalink.py
homeassistant/components/envisalink/__init__.py
homeassistant/components/*/envisalink.py
homeassistant/components/evohome.py
homeassistant/components/*/evohome.py
homeassistant/components/freebox.py
homeassistant/components/*/freebox.py
homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py
homeassistant/components/ecovacs.py
homeassistant/components/*/ecovacs.py
homeassistant/components/esphome/__init__.py
homeassistant/components/*/esphome.py
homeassistant/components/eufy.py
homeassistant/components/*/eufy.py
@@ -148,6 +155,9 @@ omit =
homeassistant/components/hive.py
homeassistant/components/*/hive.py
homeassistant/components/hlk_sw16.py
homeassistant/components/*/hlk_sw16.py
homeassistant/components/homekit_controller/__init__.py
homeassistant/components/*/homekit_controller.py
@@ -157,6 +167,9 @@ omit =
homeassistant/components/homematicip_cloud.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/homeworks.py
homeassistant/components/*/homeworks.py
homeassistant/components/huawei_lte.py
homeassistant/components/*/huawei_lte.py
@@ -200,9 +213,15 @@ omit =
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
homeassistant/components/lcn.py
homeassistant/components/*/lcn.py
homeassistant/components/linode.py
homeassistant/components/*/linode.py
homeassistant/components/lightwave.py
homeassistant/components/*/lightwave.py
homeassistant/components/logi_circle.py
homeassistant/components/*/logi_circle.py
@@ -259,6 +278,9 @@ omit =
homeassistant/components/openuv/__init__.py
homeassistant/components/*/openuv.py
homeassistant/components/plum_lightpad.py
homeassistant/components/*/plum_lightpad.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
@@ -281,6 +303,8 @@ omit =
homeassistant/components/raspihats.py
homeassistant/components/*/raspihats.py
homeassistant/components/*/raspyrfm.py
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
@@ -323,7 +347,8 @@ omit =
homeassistant/components/tahoma.py
homeassistant/components/*/tahoma.py
homeassistant/components/tellduslive.py
homeassistant/components/tellduslive/__init__.py
homeassistant/components/tellduslive/entry.py
homeassistant/components/*/tellduslive.py
homeassistant/components/tellstick.py
@@ -400,6 +425,9 @@ omit =
homeassistant/components/zha/__init__.py
homeassistant/components/zha/const.py
homeassistant/components/zha/event.py
homeassistant/components/zha/entities/*
homeassistant/components/zha/helpers.py
homeassistant/components/*/zha.py
homeassistant/components/zigbee.py
@@ -414,6 +442,7 @@ omit =
homeassistant/components/spider.py
homeassistant/components/*/spider.py
homeassistant/components/air_quality/opensensemap.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
@@ -487,7 +516,6 @@ omit =
homeassistant/components/device_tracker/bt_smarthub.py
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/freebox.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/google_maps.py
homeassistant/components/device_tracker/googlehome.py
@@ -524,6 +552,7 @@ omit =
homeassistant/components/folder_watcher.py
homeassistant/components/foursquare.py
homeassistant/components/goalfeed.py
homeassistant/components/idteck_prox.py
homeassistant/components/ifttt.py
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
@@ -587,6 +616,7 @@ omit =
homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/harman_kardon_avr.py
homeassistant/components/media_player/horizon.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
@@ -637,7 +667,6 @@ omit =
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
homeassistant/components/notify/llamalab_automate.py
@@ -672,8 +701,10 @@ omit =
homeassistant/components/route53.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/aftership.py
homeassistant/components/sensor/airvisual.py
homeassistant/components/sensor/alpha_vantage.py
homeassistant/components/sensor/ambient_station.py
homeassistant/components/sensor/arest.py
homeassistant/components/sensor/arwn.py
homeassistant/components/sensor/bbox.py
@@ -684,6 +715,7 @@ omit =
homeassistant/components/sensor/bme680.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/brottsplatskartan.py
homeassistant/components/sensor/buienradar.py
homeassistant/components/sensor/cert_expiry.py
homeassistant/components/sensor/citybikes.py
@@ -729,6 +761,7 @@ omit =
homeassistant/components/sensor/google_travel_time.py
homeassistant/components/sensor/gpsd.py
homeassistant/components/sensor/gtfs.py
homeassistant/components/sensor/gtt.py
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
@@ -744,6 +777,7 @@ omit =
homeassistant/components/sensor/launch_library.py
homeassistant/components/sensor/linky.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/london_underground.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
homeassistant/components/sensor/lyft.py
@@ -761,6 +795,7 @@ omit =
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/netdata_public.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nmbs.py
homeassistant/components/sensor/noaa_tides.py
homeassistant/components/sensor/nsw_fuel_station.py
homeassistant/components/sensor/nut.py
@@ -777,9 +812,11 @@ omit =
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pollen.py
homeassistant/components/sensor/postnl.py
homeassistant/components/sensor/prezzibenzina.py
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/pyload.py
homeassistant/components/sensor/qbittorrent.py
homeassistant/components/sensor/qnap.py
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/rainbird.py
@@ -800,6 +837,7 @@ omit =
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/sochain.py
homeassistant/components/sensor/socialblade.py
homeassistant/components/sensor/solaredge.py
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/spotcrime.py
@@ -855,6 +893,7 @@ omit =
homeassistant/components/switch/mystrom.py
homeassistant/components/switch/netio.py
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pencom.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainbird.py
homeassistant/components/switch/rest.py

View File

@@ -1,6 +1,7 @@
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!

View File

@@ -7,6 +7,7 @@ about: Create a report to help us improve
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Frontend issues should be submitted to the home-assistant-polymer repository: https://github.com/home-assistant/home-assistant-polymer/issues
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!

View File

@@ -51,6 +51,7 @@ homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
homeassistant/components/binary_sensor/hikvision.py @mezz64
homeassistant/components/binary_sensor/threshold.py @fabaff
homeassistant/components/binary_sensor/uptimerobot.py @ludeeus
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
homeassistant/components/climate/eq3btsmart.py @rytilahti
@@ -61,9 +62,11 @@ homeassistant/components/cover/group.py @cdce8p
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/asuswrt.py @kennedyshead
homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/device_tracker/googlehome.py @ludeeus
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/device_tracker/traccar.py @ludeeus
homeassistant/components/device_tracker/bt_smarthub.py @jxwolstenholme
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/influx.py @fabaff
@@ -109,6 +112,7 @@ 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/launch_library.py @ludeeus
homeassistant/components/sensor/linux_battery.py @fabaff
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/min_max.py @fabaff
@@ -119,6 +123,7 @@ 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/ruter.py @ludeeus
homeassistant/components/sensor/scrape.py @fabaff
homeassistant/components/sensor/serial.py @fabaff
homeassistant/components/sensor/seventeentrack.py @bachya
@@ -128,12 +133,15 @@ 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/tautulli.py @ludeeus
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/switchbot.py @danielhiversen
homeassistant/components/switch/switchmate.py @danielhiversen
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/weather/__init__.py @fabaff
@@ -157,9 +165,12 @@ homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen
# C
homeassistant/components/cloudflare.py @ludeeus
homeassistant/components/counter/* @fabaff
# D
homeassistant/components/daikin.py @fredrike @rofrantz
homeassistant/components/*/daikin.py @fredrike @rofrantz
homeassistant/components/*/deconz.py @kane610
homeassistant/components/digital_ocean.py @fabaff
homeassistant/components/*/digital_ocean.py @fabaff
@@ -173,6 +184,8 @@ homeassistant/components/*/edp_redy.py @abmantis
homeassistant/components/edp_redy.py @abmantis
homeassistant/components/eight_sleep.py @mezz64
homeassistant/components/*/eight_sleep.py @mezz64
homeassistant/components/esphome/*.py @OttoWinter
homeassistant/components/*/esphome.py @OttoWinter
# H
homeassistant/components/hive.py @Rendili @KJonline
@@ -200,10 +213,18 @@ homeassistant/components/melissa.py @kennedyshead
homeassistant/components/*/melissa.py @kennedyshead
homeassistant/components/*/mystrom.py @fabaff
# N
homeassistant/components/ness_alarm.py @nickw444
homeassistant/components/*/ness_alarm.py @nickw444
# O
homeassistant/components/openuv/* @bachya
homeassistant/components/*/openuv.py @bachya
# P
homeassistant/components/point/* @fredrike
homeassistant/components/*/point.py @fredrike
# Q
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
@@ -221,8 +242,8 @@ homeassistant/components/*/simplisafe.py @bachya
# T
homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei
homeassistant/components/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
homeassistant/components/tellduslive/*.py @fredrike
homeassistant/components/*/tellduslive.py @fredrike
homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/thethingsnetwork.py @fabaff

View File

@@ -1,4 +1,4 @@
Home Assistant |Build Status| |Coverage Status| |Chat Status| |Reviewed by Hound|
Home Assistant |Build Status| |Coverage Status| |Chat Status|
=================================================================================
Home Assistant is a home automation platform running on Python 3. It is able to track and control all devices at home and offer a platform for automating control.
@@ -33,8 +33,6 @@ of a component, check the `Home Assistant help section <https://home-assistant.i
:target: https://coveralls.io/r/home-assistant/home-assistant?branch=master
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
:target: https://discord.gg/c5DvZ4e
.. |Reviewed by Hound| image:: https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg
:target: https://houndci.com
.. |screenshot-states| image:: https://raw.github.com/home-assistant/home-assistant/master/docs/screenshots.png
:target: https://home-assistant.io/demo/
.. |screenshot-components| image:: https://raw.github.com/home-assistant/home-assistant/dev/docs/screenshot-components.png

View File

@@ -78,11 +78,6 @@ class AuthManager:
hass, self._async_create_login_flow,
self._async_finish_login_flow)
@property
def active(self) -> bool:
"""Return if any auth providers are registered."""
return bool(self._providers)
@property
def support_legacy(self) -> bool:
"""
@@ -190,6 +185,7 @@ class AuthManager:
credentials=credentials,
name=info.name,
is_active=info.is_active,
group_ids=[GROUP_ID_ADMIN],
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {

View File

@@ -1,4 +1,5 @@
"""Storage for auth models."""
import asyncio
from collections import OrderedDict
from datetime import timedelta
import hmac
@@ -11,7 +12,7 @@ from homeassistant.util import dt as dt_util
from . import models
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
from .permissions import system_policies
from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType # noqa: F401
STORAGE_VERSION = 1
@@ -34,6 +35,7 @@ class AuthStore:
self.hass = hass
self._users = None # type: Optional[Dict[str, models.User]]
self._groups = None # type: Optional[Dict[str, models.Group]]
self._perm_lookup = None # type: Optional[PermissionLookup]
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
private=True)
@@ -94,6 +96,7 @@ class AuthStore:
# Until we get group management, we just put everyone in the
# same group.
'groups': groups,
'perm_lookup': self._perm_lookup,
} # type: Dict[str, Any]
if is_owner is not None:
@@ -269,13 +272,18 @@ class AuthStore:
async def _async_load(self) -> None:
"""Load the users."""
data = await self._store.async_load()
[ent_reg, data] = await asyncio.gather(
self.hass.helpers.entity_registry.async_get_registry(),
self._store.async_load(),
)
# Make sure that we're not overriding data if 2 loads happened at the
# same time
if self._users is not None:
return
self._perm_lookup = perm_lookup = PermissionLookup(ent_reg)
if data is None:
self._set_defaults()
return
@@ -374,6 +382,7 @@ class AuthStore:
is_owner=user_dict['is_owner'],
is_active=user_dict['is_active'],
system_generated=user_dict['system_generated'],
perm_lookup=perm_lookup,
)
for cred_dict in data['credentials']:
@@ -462,10 +471,11 @@ class AuthStore:
for group in self._groups.values():
g_dict = {
'id': group.id,
# Name not read for sys groups. Kept here for backwards compat
'name': group.name
} # type: Dict[str, Any]
if group.id not in (GROUP_ID_READ_ONLY, GROUP_ID_ADMIN):
g_dict['name'] = group.name
g_dict['policy'] = group.policy
groups.append(g_dict)

View File

@@ -4,13 +4,14 @@ Sending HOTP through notify service
"""
import logging
from collections import OrderedDict
from typing import Any, Dict, Optional, Tuple, List # noqa: F401
from typing import Any, Dict, Optional, List
import attr
import voluptuous as vol
from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceNotFound
from homeassistant.helpers import config_validation as cv
from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \
@@ -314,8 +315,11 @@ class NotifySetupFlow(SetupFlow):
_generate_otp, self._secret, self._count)
assert self._notify_service
await self._auth_module.async_notify(
code, self._notify_service, self._target)
try:
await self._auth_module.async_notify(
code, self._notify_service, self._target)
except ServiceNotFound:
return self.async_abort(reason='notify_service_not_exist')
return self.async_show_form(
step_id='setup',

View File

@@ -31,6 +31,9 @@ class User:
"""A user."""
name = attr.ib(type=str) # type: Optional[str]
perm_lookup = attr.ib(
type=perm_mdl.PermissionLookup, cmp=False,
) # type: perm_mdl.PermissionLookup
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
is_owner = attr.ib(type=bool, default=False)
is_active = attr.ib(type=bool, default=False)
@@ -66,7 +69,8 @@ class User:
self._permissions = perm_mdl.PolicyPermissions(
perm_mdl.merge_policies([
group.policy for group in self.groups]))
group.policy for group in self.groups]),
self.perm_lookup)
return self._permissions

View File

@@ -1,15 +1,18 @@
"""Permissions for Home Assistant."""
import logging
from typing import ( # noqa: F401
cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union)
cast, Any, Callable, Dict, List, Mapping, Set, Tuple, Union,
TYPE_CHECKING)
import voluptuous as vol
from .const import CAT_ENTITIES
from .models import PermissionLookup
from .types import PolicyType
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
from .merge import merge_policies # noqa
POLICY_SCHEMA = vol.Schema({
vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA
})
@@ -39,13 +42,16 @@ class AbstractPermissions:
class PolicyPermissions(AbstractPermissions):
"""Handle permissions."""
def __init__(self, policy: PolicyType) -> None:
def __init__(self, policy: PolicyType,
perm_lookup: PermissionLookup) -> None:
"""Initialize the permission class."""
self._policy = policy
self._perm_lookup = perm_lookup
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
return compile_entities(self._policy.get(CAT_ENTITIES))
return compile_entities(self._policy.get(CAT_ENTITIES),
self._perm_lookup)
def __eq__(self, other: Any) -> bool:
"""Equals check."""

View File

@@ -1,5 +1,6 @@
"""Permission constants."""
CAT_ENTITIES = 'entities'
CAT_CONFIG_ENTRIES = 'config_entries'
SUBCAT_ALL = 'all'
POLICY_READ = 'read'

View File

@@ -1,11 +1,11 @@
"""Entity permissions."""
from functools import wraps
from typing import ( # noqa: F401
Callable, Dict, List, Tuple, Union)
from typing import Callable, List, Union # noqa: F401
import voluptuous as vol
from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT
from .models import PermissionLookup
from .types import CategoryType, ValueType
SINGLE_ENTITY_SCHEMA = vol.Any(True, vol.Schema({
@@ -15,6 +15,7 @@ SINGLE_ENTITY_SCHEMA = vol.Any(True, vol.Schema({
}))
ENTITY_DOMAINS = 'domains'
ENTITY_DEVICE_IDS = 'device_ids'
ENTITY_ENTITY_IDS = 'entity_ids'
ENTITY_VALUES_SCHEMA = vol.Any(True, vol.Schema({
@@ -23,6 +24,7 @@ ENTITY_VALUES_SCHEMA = vol.Any(True, vol.Schema({
ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({
vol.Optional(SUBCAT_ALL): SINGLE_ENTITY_SCHEMA,
vol.Optional(ENTITY_DEVICE_IDS): ENTITY_VALUES_SCHEMA,
vol.Optional(ENTITY_DOMAINS): ENTITY_VALUES_SCHEMA,
vol.Optional(ENTITY_ENTITY_IDS): ENTITY_VALUES_SCHEMA,
}))
@@ -37,7 +39,7 @@ def _entity_allowed(schema: ValueType, key: str) \
return schema.get(key)
def compile_entities(policy: CategoryType) \
def compile_entities(policy: CategoryType, perm_lookup: PermissionLookup) \
-> Callable[[str, str], bool]:
"""Compile policy into a function that tests policy."""
# None, Empty Dict, False
@@ -58,6 +60,7 @@ def compile_entities(policy: CategoryType) \
assert isinstance(policy, dict)
domains = policy.get(ENTITY_DOMAINS)
device_ids = policy.get(ENTITY_DEVICE_IDS)
entity_ids = policy.get(ENTITY_ENTITY_IDS)
all_entities = policy.get(SUBCAT_ALL)
@@ -85,6 +88,29 @@ def compile_entities(policy: CategoryType) \
funcs.append(allowed_entity_id_dict)
if isinstance(device_ids, bool):
def allowed_device_id_bool(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed device_id."""
return device_ids
funcs.append(allowed_device_id_bool)
elif device_ids is not None:
def allowed_device_id_dict(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed device_id."""
entity_entry = perm_lookup.entity_registry.async_get(entity_id)
if entity_entry is None or entity_entry.device_id is None:
return None
return _entity_allowed(
device_ids.get(entity_entry.device_id), key # type: ignore
)
funcs.append(allowed_device_id_dict)
if isinstance(domains, bool):
def allowed_domain_bool(entity_id: str, key: str) \
-> Union[None, bool]:

View File

@@ -0,0 +1,17 @@
"""Models for permissions."""
from typing import TYPE_CHECKING
import attr
if TYPE_CHECKING:
# pylint: disable=unused-import
from homeassistant.helpers import ( # noqa
entity_registry as ent_reg,
)
@attr.s(slots=True)
class PermissionLookup:
"""Class to hold data for permission lookups."""
entity_registry = attr.ib(type='ent_reg.EntityRegistry')

View File

@@ -1,6 +1,5 @@
"""Common code for permissions."""
from typing import ( # noqa: F401
Mapping, Union, Any)
from typing import Mapping, Union
# MyPy doesn't support recursion yet. So writing it out as far as we need.

View File

@@ -226,7 +226,11 @@ class LoginFlow(data_entry_flow.FlowHandler):
if user_input is None and hasattr(auth_module,
'async_initialize_login_mfa_step'):
await auth_module.async_initialize_login_mfa_step(self.user.id)
try:
await auth_module.async_initialize_login_mfa_step(self.user.id)
except HomeAssistantError:
_LOGGER.exception('Error initializing MFA step')
return self.async_abort(reason='unknown_error')
if user_input is not None:
expires = self.created_at + MFA_SESSION_EXPIRATION

View File

@@ -1,8 +1,6 @@
"""Home Assistant auth provider."""
import base64
from collections import OrderedDict
import hashlib
import hmac
from typing import Any, Dict, List, Optional, cast
import bcrypt
@@ -11,12 +9,10 @@ import voluptuous as vol
from homeassistant.const import CONF_ID
from homeassistant.core import callback, HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.async_ import run_coroutine_threadsafe
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
from ..models import Credentials, UserMeta
from ..util import generate_secret
STORAGE_VERSION = 1
@@ -62,7 +58,6 @@ class Data:
if data is None:
data = {
'salt': generate_secret(),
'users': []
}
@@ -94,39 +89,11 @@ class Data:
user_hash = base64.b64decode(found['password'])
# if the hash is not a bcrypt hash...
# provide a transparant upgrade for old pbkdf2 hash format
if not (user_hash.startswith(b'$2a$')
or user_hash.startswith(b'$2b$')
or user_hash.startswith(b'$2x$')
or user_hash.startswith(b'$2y$')):
# IMPORTANT! validate the login, bail if invalid
hashed = self.legacy_hash_password(password)
if not hmac.compare_digest(hashed, user_hash):
raise InvalidAuth
# then re-hash the valid password with bcrypt
self.change_password(found['username'], password)
run_coroutine_threadsafe(
self.async_save(), self.hass.loop
).result()
user_hash = base64.b64decode(found['password'])
# bcrypt.checkpw is timing-safe
if not bcrypt.checkpw(password.encode(),
user_hash):
raise InvalidAuth
def legacy_hash_password(self, password: str,
for_storage: bool = False) -> bytes:
"""LEGACY password encoding."""
# We're no longer storing salts in data, but if one exists we
# should be able to retrieve it.
salt = self._data['salt'].encode() # type: ignore
hashed = hashlib.pbkdf2_hmac('sha512', password.encode(), salt, 100000)
if for_storage:
hashed = base64.b64encode(hashed)
return hashed
# pylint: disable=no-self-use
def hash_password(self, password: str, for_storage: bool = False) -> bytes:
"""Encode a password."""

View File

@@ -4,16 +4,19 @@ Support Legacy API password auth provider.
It will be removed when auth system production ready
"""
import hmac
from typing import Any, Dict, Optional, cast
from typing import Any, Dict, Optional, cast, TYPE_CHECKING
import voluptuous as vol
from homeassistant.components.http import HomeAssistantHTTP # noqa: F401
from homeassistant.core import callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow
from ..models import Credentials, UserMeta
from .. import AuthManager
from ..models import Credentials, UserMeta, User
if TYPE_CHECKING:
from homeassistant.components.http import HomeAssistantHTTP # noqa: F401
USER_SCHEMA = vol.Schema({
@@ -31,6 +34,24 @@ class InvalidAuthError(HomeAssistantError):
"""Raised when submitting invalid authentication."""
async def async_get_user(hass: HomeAssistant) -> User:
"""Return the legacy API password user."""
auth = cast(AuthManager, hass.auth) # type: ignore
found = None
for prv in auth.auth_providers:
if prv.type == 'legacy_api_password':
found = prv
break
if found is None:
raise ValueError('Legacy API password provider not found')
return await auth.async_get_or_create_user(
await found.async_get_or_create_credentials({})
)
@AUTH_PROVIDERS.register('legacy_api_password')
class LegacyApiPasswordAuthProvider(AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""

View File

@@ -115,11 +115,6 @@ async def async_from_config_dict(config: Dict[str, Any],
conf_util.merge_packages_config(
hass, config, core_config.get(conf_util.CONF_PACKAGES, {}))
# Ensure we have no None values after merge
for key, value in config.items():
if not value:
config[key] = {}
hass.config_entries = config_entries.ConfigEntries(hass, config)
await hass.config_entries.async_load()

View File

@@ -125,16 +125,23 @@ class AdsHub:
def shutdown(self, *args, **kwargs):
"""Shutdown ADS connection."""
import pyads
_LOGGER.debug("Shutting down ADS")
for notification_item in self._notification_items.values():
self._client.del_device_notification(
notification_item.hnotify,
notification_item.huser
)
_LOGGER.debug(
"Deleting device notification %d, %d",
notification_item.hnotify, notification_item.huser)
self._client.close()
try:
self._client.del_device_notification(
notification_item.hnotify,
notification_item.huser
)
except pyads.ADSError as err:
_LOGGER.error(err)
try:
self._client.close()
except pyads.ADSError as err:
_LOGGER.error(err)
def register_device(self, device):
"""Register a new device."""

View File

@@ -0,0 +1,147 @@
"""
Component for handling Air Quality data for your location.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/air_quality/
"""
from datetime import timedelta
import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import Entity
_LOGGER = logging.getLogger(__name__)
ATTR_AQI = 'air_quality_index'
ATTR_ATTRIBUTION = 'attribution'
ATTR_C02 = 'carbon_dioxide'
ATTR_CO = 'carbon_monoxide'
ATTR_N2O = 'nitrogen_oxide'
ATTR_NO = 'nitrogen_monoxide'
ATTR_NO2 = 'nitrogen_dioxide'
ATTR_OZONE = 'ozone'
ATTR_PM_0_1 = 'particulate_matter_0_1'
ATTR_PM_10 = 'particulate_matter_10'
ATTR_PM_2_5 = 'particulate_matter_2_5'
ATTR_SO2 = 'sulphur_dioxide'
DOMAIN = 'air_quality'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=30)
PROP_TO_ATTR = {
'air_quality_index': ATTR_AQI,
'attribution': ATTR_ATTRIBUTION,
'carbon_dioxide': ATTR_C02,
'carbon_monoxide': ATTR_CO,
'nitrogen_oxide': ATTR_N2O,
'nitrogen_monoxide': ATTR_NO,
'nitrogen_dioxide': ATTR_NO2,
'ozone': ATTR_OZONE,
'particulate_matter_0_1': ATTR_PM_0_1,
'particulate_matter_10': ATTR_PM_10,
'particulate_matter_2_5': ATTR_PM_2_5,
'sulphur_dioxide': ATTR_SO2,
}
async def async_setup(hass, config):
"""Set up the air quality component."""
component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
return True
async def async_setup_entry(hass, entry):
"""Set up a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
class AirQualityEntity(Entity):
"""ABC for air quality data."""
@property
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
raise NotImplementedError()
@property
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return None
@property
def particulate_matter_0_1(self):
"""Return the particulate matter 0.1 level."""
return None
@property
def air_quality_index(self):
"""Return the Air Quality Index (AQI)."""
return None
@property
def ozone(self):
"""Return the O3 (ozone) level."""
return None
@property
def carbon_monoxide(self):
"""Return the CO (carbon monoxide) level."""
return None
@property
def carbon_dioxide(self):
"""Return the CO2 (carbon dioxide) level."""
return None
@property
def attribution(self):
"""Return the attribution."""
return None
@property
def sulphur_dioxide(self):
"""Return the SO2 (sulphur dioxide) level."""
return None
@property
def nitrogen_oxide(self):
"""Return the N2O (nitrogen oxide) level."""
return None
@property
def nitrogen_monoxide(self):
"""Return the NO (nitrogen monoxide) level."""
return None
@property
def nitrogen_dioxide(self):
"""Return the NO2 (nitrogen dioxide) level."""
return None
@property
def state_attributes(self):
"""Return the state attributes."""
data = {}
for prop, attr in PROP_TO_ATTR.items():
value = getattr(self, prop)
if value is not None:
data[attr] = value
return data
@property
def state(self):
"""Return the current state."""
return self.particulate_matter_2_5

View File

@@ -0,0 +1,56 @@
"""
Demo platform that offers fake air quality data.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
from homeassistant.components.air_quality import AirQualityEntity
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Air Quality."""
add_entities([
DemoAirQuality('Home', 14, 23, 100),
DemoAirQuality('Office', 4, 16, None)
])
class DemoAirQuality(AirQualityEntity):
"""Representation of Air Quality data."""
def __init__(self, name, pm_2_5, pm_10, n2o):
"""Initialize the Demo Air Quality."""
self._name = name
self._pm_2_5 = pm_2_5
self._pm_10 = pm_10
self._n2o = n2o
@property
def name(self):
"""Return the name of the sensor."""
return '{} {}'.format('Demo Air Quality', self._name)
@property
def should_poll(self):
"""No polling needed for Demo Air Quality."""
return False
@property
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
return self._pm_2_5
@property
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return self._pm_10
@property
def nitrogen_oxide(self):
"""Return the nitrogen oxide (N2O) level."""
return self._n2o
@property
def attribution(self):
"""Return the attribution."""
return 'Powered by Home Assistant'

View File

@@ -0,0 +1,105 @@
"""
Support for openSenseMap Air Quality data.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/air_quality/opensensemap/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.air_quality import (
PLATFORM_SCHEMA, AirQualityEntity)
from homeassistant.const import CONF_NAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
REQUIREMENTS = ['opensensemap-api==0.1.3']
_LOGGER = logging.getLogger(__name__)
ATTRIBUTION = 'Data provided by openSenseMap'
CONF_STATION_ID = 'station_id'
SCAN_INTERVAL = timedelta(minutes=10)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STATION_ID): cv.string,
vol.Optional(CONF_NAME): cv.string,
})
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the openSenseMap air quality platform."""
from opensensemap_api import OpenSenseMap
name = config.get(CONF_NAME)
station_id = config[CONF_STATION_ID]
session = async_get_clientsession(hass)
osm_api = OpenSenseMapData(OpenSenseMap(station_id, hass.loop, session))
await osm_api.async_update()
if 'name' not in osm_api.api.data:
_LOGGER.error("Station %s is not available", station_id)
return
station_name = osm_api.api.data['name'] if name is None else name
async_add_entities([OpenSenseMapQuality(station_name, osm_api)], True)
class OpenSenseMapQuality(AirQualityEntity):
"""Implementation of an openSenseMap air quality entity."""
def __init__(self, name, osm):
"""Initialize the air quality entity."""
self._name = name
self._osm = osm
@property
def name(self):
"""Return the name of the air quality entity."""
return self._name
@property
def particulate_matter_2_5(self):
"""Return the particulate matter 2.5 level."""
return self._osm.api.pm2_5
@property
def particulate_matter_10(self):
"""Return the particulate matter 10 level."""
return self._osm.api.pm10
@property
def attribution(self):
"""Return the attribution."""
return ATTRIBUTION
async def async_update(self):
"""Get the latest data from the openSenseMap API."""
await self._osm.async_update()
class OpenSenseMapData:
"""Get the latest data and update the states."""
def __init__(self, api):
"""Initialize the data object."""
self.api = api
@Throttle(SCAN_INTERVAL)
async def async_update(self):
"""Get the latest data from the Pi-hole."""
from opensensemap_api.exceptions import OpenSenseMapError
try:
await self.api.get_data()
except OpenSenseMapError as err:
_LOGGER.error("Unable to fetch data: %s", err)

View File

@@ -25,7 +25,7 @@ ATTR_CHANGED_BY = 'changed_by'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
ALARM_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_CODE): cv.string,
})

View File

@@ -25,21 +25,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
return
data = hass.data[BLINK_DATA]
# Current version of blinkpy API only supports one sync module. When
# support for additional models is added, the sync module name should
# come from the API.
sync_modules = []
sync_modules.append(BlinkSyncModule(data, 'sync'))
for sync_name, sync_module in data.sync.items():
sync_modules.append(BlinkSyncModule(data, sync_name, sync_module))
add_entities(sync_modules, True)
class BlinkSyncModule(AlarmControlPanel):
"""Representation of a Blink Alarm Control Panel."""
def __init__(self, data, name):
def __init__(self, data, name, sync):
"""Initialize the alarm control panel."""
self.data = data
self.sync = data.sync
self.sync = sync
self._name = name
self._state = None
@@ -68,6 +66,7 @@ class BlinkSyncModule(AlarmControlPanel):
"""Return the state attributes."""
attr = self.sync.attributes
attr['network_info'] = self.data.networks
attr['associated_cameras'] = list(self.sync.cameras.keys())
attr[ATTR_ATTRIBUTION] = DEFAULT_ATTRIBUTION
return attr

View File

@@ -5,14 +5,16 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ialarm/
"""
import logging
import re
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
CONF_CODE, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyialarm==0.3']
@@ -36,6 +38,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): vol.All(cv.string, no_application_protocol),
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_CODE): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@@ -43,23 +46,25 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up an iAlarm control panel."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
url = 'http://{}'.format(host)
ialarm = IAlarmPanel(name, username, password, url)
ialarm = IAlarmPanel(name, code, username, password, url)
add_entities([ialarm], True)
class IAlarmPanel(alarm.AlarmControlPanel):
"""Representation of an iAlarm status."""
def __init__(self, name, username, password, url):
def __init__(self, name, code, username, password, url):
"""Initialize the iAlarm status."""
from pyialarm import IAlarm
self._name = name
self._code = str(code) if code else None
self._username = username
self._password = password
self._url = url
@@ -71,6 +76,15 @@ class IAlarmPanel(alarm.AlarmControlPanel):
"""Return the name of the device."""
return self._name
@property
def code_format(self):
"""Return one or more digits/characters."""
if self._code is None:
return None
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):
"""Return the state of the device."""
@@ -98,12 +112,22 @@ class IAlarmPanel(alarm.AlarmControlPanel):
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._client.disarm()
if self._validate_code(code):
self._client.disarm()
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._client.arm_away()
if self._validate_code(code):
self._client.arm_away()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._client.arm_stay()
if self._validate_code(code):
self._client.arm_stay()
def _validate_code(self, code):
"""Validate given code."""
check = self._code is None or code == self._code
if not check:
_LOGGER.warning("Wrong code entered")
return check

View File

@@ -12,7 +12,8 @@ from homeassistant.components.lupusec import DOMAIN as LUPUSEC_DOMAIN
from homeassistant.components.lupusec import LupusecDevice
from homeassistant.const import (STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED)
DEPENDENCIES = ['lupusec']
@@ -50,6 +51,8 @@ class LupusecAlarm(LupusecDevice, AlarmControlPanel):
state = STATE_ALARM_ARMED_AWAY
elif self._device.is_home:
state = STATE_ALARM_ARMED_HOME
elif self._device.is_alarm_triggered:
state = STATE_ALARM_TRIGGERED
else:
state = None
return state

View File

@@ -21,7 +21,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_point_in_time
import homeassistant.util.dt as dt_util
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.restore_state import RestoreEntity
_LOGGER = logging.getLogger(__name__)
@@ -116,7 +116,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
)])
class ManualAlarm(alarm.AlarmControlPanel):
class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity):
"""
Representation of an alarm status.
@@ -310,7 +310,15 @@ class ManualAlarm(alarm.AlarmControlPanel):
async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
state = await async_get_last_state(self.hass, self.entity_id)
await super().async_added_to_hass()
state = await self.async_get_last_state()
if state:
self._state = state.state
self._state_ts = state.last_updated
if state.state == STATE_ALARM_PENDING and \
hasattr(state, 'attributes') and \
state.attributes['pre_pending_state']:
# If in pending state, we return to the pre_pending_state
self._state = state.attributes['pre_pending_state']
self._state_ts = dt_util.utcnow()
else:
self._state = state.state
self._state_ts = state.last_updated

View File

@@ -13,13 +13,14 @@ from homeassistant.core import callback
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components import mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME, CONF_CODE)
CONF_CODE, CONF_DEVICE, CONF_NAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_PENDING,
STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
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)
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -30,6 +31,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_PAYLOAD_DISARM = 'payload_disarm'
CONF_PAYLOAD_ARM_HOME = 'payload_arm_home'
CONF_PAYLOAD_ARM_AWAY = 'payload_arm_away'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_ARM_AWAY = 'ARM_AWAY'
DEFAULT_ARM_HOME = 'ARM_HOME'
@@ -45,13 +47,15 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
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 MQTT alarm control panel through configuration.yaml."""
await _async_setup_entity(hass, config, async_add_entities)
await _async_setup_entity(config, async_add_entities)
async def async_setup_entry(hass, config_entry, async_add_entities):
@@ -59,7 +63,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_discover(discovery_payload):
"""Discover and add an MQTT alarm control panel."""
config = PLATFORM_SCHEMA(discovery_payload)
await _async_setup_entity(hass, config, async_add_entities,
await _async_setup_entity(config, async_add_entities,
discovery_payload[ATTR_DISCOVERY_HASH])
async_dispatcher_connect(
@@ -67,54 +71,50 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async_discover)
async def _async_setup_entity(hass, config, async_add_entities,
async def _async_setup_entity(config, async_add_entities,
discovery_hash=None):
"""Set up the MQTT Alarm Control Panel platform."""
async_add_entities([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
config.get(CONF_QOS),
config.get(CONF_RETAIN),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY),
config.get(CONF_CODE),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
discovery_hash,)])
async_add_entities([MqttAlarm(config, discovery_hash)])
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
def __init__(self, name, state_topic, command_topic, qos, retain,
payload_disarm, payload_arm_home, payload_arm_away, code,
availability_topic, payload_available, payload_not_available,
discovery_hash):
def __init__(self, config, discovery_hash):
"""Init the MQTT Alarm Control Panel."""
self._state = STATE_UNKNOWN
self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._sub_state = None
availability_topic = config.get(CONF_AVAILABILITY_TOPIC)
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._state = STATE_UNKNOWN
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._retain = retain
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
self._code = code
self._discovery_hash = discovery_hash
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)
async def async_added_to_hass(self):
"""Subscribe mqtt events."""
await MqttAvailability.async_added_to_hass(self)
await MqttDiscoveryUpdate.async_added_to_hass(self)
await super().async_added_to_hass()
await self._subscribe_topics()
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
config = PLATFORM_SCHEMA(discovery_payload)
self._config = config
await self.availability_discovery_update(config)
await self._subscribe_topics()
self.async_schedule_update_ha_state()
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
@callback
def message_received(topic, payload, qos):
"""Run when new MQTT message has been received."""
@@ -126,8 +126,17 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
self._state = payload
self.async_schedule_update_ha_state()
await mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
{'state_topic': {'topic': self._config.get(CONF_STATE_TOPIC),
'msg_callback': message_received,
'qos': self._config.get(CONF_QOS)}})
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAvailability.async_will_remove_from_hass(self)
@property
def should_poll(self):
@@ -137,7 +146,12 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the device."""
return self._name
return self._config.get(CONF_NAME)
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def state(self):
@@ -147,9 +161,10 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
@property
def code_format(self):
"""Return one or more digits/characters."""
if self._code is None:
code = self._config.get(CONF_CODE)
if code is None:
return None
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(code, str) and re.search('^\\d+$', code):
return 'Number'
return 'Any'
@@ -161,8 +176,10 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
if not self._validate_code(code, 'disarming'):
return
mqtt.async_publish(
self.hass, self._command_topic, self._payload_disarm, self._qos,
self._retain)
self.hass, self._config.get(CONF_COMMAND_TOPIC),
self._config.get(CONF_PAYLOAD_DISARM),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
async def async_alarm_arm_home(self, code=None):
"""Send arm home command.
@@ -172,8 +189,10 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
if not self._validate_code(code, 'arming home'):
return
mqtt.async_publish(
self.hass, self._command_topic, self._payload_arm_home, self._qos,
self._retain)
self.hass, self._config.get(CONF_COMMAND_TOPIC),
self._config.get(CONF_PAYLOAD_ARM_HOME),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
async def async_alarm_arm_away(self, code=None):
"""Send arm away command.
@@ -183,12 +202,15 @@ class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
if not self._validate_code(code, 'arming away'):
return
mqtt.async_publish(
self.hass, self._command_topic, self._payload_arm_away, self._qos,
self._retain)
self.hass, self._config.get(CONF_COMMAND_TOPIC),
self._config.get(CONF_PAYLOAD_ARM_AWAY),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
def _validate_code(self, code, state):
"""Validate given code."""
check = self._code is None or code == self._code
conf_code = self._config.get(CONF_CODE)
check = conf_code is None or code == conf_code
if not check:
_LOGGER.warning('Wrong code entered for %s', state)
return check

View File

@@ -0,0 +1,107 @@
"""
Support for Ness D8X/D16X alarm panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ness_alarm/
"""
import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.ness_alarm import (
DATA_NESS, SIGNAL_ARMING_STATE_CHANGED)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMING,
STATE_ALARM_TRIGGERED, STATE_ALARM_PENDING, STATE_ALARM_DISARMED)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['ness_alarm']
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Ness Alarm alarm control panel devices."""
if discovery_info is None:
return
device = NessAlarmPanel(hass.data[DATA_NESS], 'Alarm Panel')
async_add_entities([device])
class NessAlarmPanel(alarm.AlarmControlPanel):
"""Representation of a Ness alarm panel."""
def __init__(self, client, name):
"""Initialize the alarm panel."""
self._client = client
self._name = name
self._state = None
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ARMING_STATE_CHANGED,
self._handle_arming_state_change)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def should_poll(self):
"""Return the polling state."""
return False
@property
def code_format(self):
"""Return the regex for code format or None if no code is required."""
return 'Number'
@property
def state(self):
"""Return the state of the device."""
return self._state
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
await self._client.disarm(code)
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
await self._client.arm_away(code)
async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
await self._client.arm_home(code)
async def async_alarm_trigger(self, code=None):
"""Send trigger/panic command."""
await self._client.panic(code)
@callback
def _handle_arming_state_change(self, arming_state):
"""Handle arming state update."""
from nessclient import ArmingState
if arming_state == ArmingState.UNKNOWN:
self._state = None
elif arming_state == ArmingState.DISARMED:
self._state = STATE_ALARM_DISARMED
elif arming_state == ArmingState.ARMING:
self._state = STATE_ALARM_ARMING
elif arming_state == ArmingState.EXIT_DELAY:
self._state = STATE_ALARM_ARMING
elif arming_state == ArmingState.ARMED:
self._state = STATE_ALARM_ARMED_AWAY
elif arming_state == ArmingState.ENTRY_DELAY:
self._state = STATE_ALARM_PENDING
elif arming_state == ArmingState.TRIGGERED:
self._state = STATE_ALARM_TRIGGERED
else:
_LOGGER.warning("Unhandled arming state: %s", arming_state)
self.async_schedule_update_ha_state()

View File

@@ -13,7 +13,7 @@ import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PORT, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN)
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pynx584==0.4']
@@ -43,7 +43,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities([NX584Alarm(hass, url, name)])
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to NX584: %s", str(ex))
return False
return
class NX584Alarm(alarm.AlarmControlPanel):
@@ -60,7 +60,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
# talk to the API and trigger a requests exception for setup_platform()
# to catch
self._alarm.list_zones()
self._state = STATE_UNKNOWN
self._state = None
@property
def name(self):
@@ -85,11 +85,11 @@ class NX584Alarm(alarm.AlarmControlPanel):
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to %(host)s: %(reason)s",
dict(host=self._url, reason=ex))
self._state = STATE_UNKNOWN
self._state = None
zones = []
except IndexError:
_LOGGER.error("NX584 reports no partitions")
self._state = STATE_UNKNOWN
self._state = None
zones = []
bypassed = False
@@ -107,6 +107,10 @@ class NX584Alarm(alarm.AlarmControlPanel):
else:
self._state = STATE_ALARM_ARMED_AWAY
for flag in part['condition_flags']:
if flag == "Siren on":
self._state = STATE_ALARM_TRIGGERED
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._alarm.disarm(code)

View File

@@ -15,7 +15,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['yalesmartalarmclient==0.1.4']
REQUIREMENTS = ['yalesmartalarmclient==0.1.6']
CONF_AREA_ID = 'area_id'

View File

@@ -32,6 +32,7 @@ CONF_DEVICE_TYPE = 'type'
CONF_PANEL_DISPLAY = 'panel_display'
CONF_ZONE_NAME = 'name'
CONF_ZONE_TYPE = 'type'
CONF_ZONE_LOOP = 'loop'
CONF_ZONE_RFID = 'rfid'
CONF_ZONES = 'zones'
CONF_RELAY_ADDR = 'relayaddr'
@@ -75,6 +76,8 @@ ZONE_SCHEMA = vol.Schema({
vol.Optional(CONF_ZONE_TYPE,
default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA),
vol.Optional(CONF_ZONE_RFID): cv.string,
vol.Optional(CONF_ZONE_LOOP):
vol.All(vol.Coerce(int), vol.Range(min=1, max=4)),
vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation',
'Relay address and channel must exist together'): cv.byte,
vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation',

View File

@@ -13,8 +13,9 @@ from homeassistant.helpers import entityfilter
from . import flash_briefings, intent, smart_home
from .const import (
CONF_AUDIO, CONF_DISPLAY_URL, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN,
CONF_FILTER, CONF_ENTITY_CONFIG)
CONF_AUDIO, CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_DISPLAY_URL,
CONF_ENDPOINT, CONF_TEXT, CONF_TITLE, CONF_UID, DOMAIN, CONF_FILTER,
CONF_ENTITY_CONFIG)
_LOGGER = logging.getLogger(__name__)
@@ -30,6 +31,9 @@ ALEXA_ENTITY_SCHEMA = vol.Schema({
})
SMART_HOME_SCHEMA = vol.Schema({
vol.Optional(CONF_ENDPOINT): cv.string,
vol.Optional(CONF_CLIENT_ID): cv.string,
vol.Optional(CONF_CLIENT_SECRET): cv.string,
vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
})

View File

@@ -0,0 +1,154 @@
"""Support for Alexa skill auth."""
import asyncio
import json
import logging
from datetime import timedelta
import aiohttp
import async_timeout
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from homeassistant.util import dt
from .const import DEFAULT_TIMEOUT
_LOGGER = logging.getLogger(__name__)
LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token"
LWA_HEADERS = {
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
}
PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300
STORAGE_KEY = 'alexa_auth'
STORAGE_VERSION = 1
STORAGE_EXPIRE_TIME = "expire_time"
STORAGE_ACCESS_TOKEN = "access_token"
STORAGE_REFRESH_TOKEN = "refresh_token"
class Auth:
"""Handle authentication to send events to Alexa."""
def __init__(self, hass, client_id, client_secret):
"""Initialize the Auth class."""
self.hass = hass
self.client_id = client_id
self.client_secret = client_secret
self._prefs = None
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._get_token_lock = asyncio.Lock(loop=hass.loop)
async def async_do_auth(self, accept_grant_code):
"""Do authentication with an AcceptGrant code."""
# access token not retrieved yet for the first time, so this should
# be an access token request
lwa_params = {
"grant_type": "authorization_code",
"code": accept_grant_code,
"client_id": self.client_id,
"client_secret": self.client_secret
}
_LOGGER.debug("Calling LWA to get the access token (first time), "
"with: %s", json.dumps(lwa_params))
return await self._async_request_new_token(lwa_params)
async def async_get_access_token(self):
"""Perform access token or token refresh request."""
async with self._get_token_lock:
if self._prefs is None:
await self.async_load_preferences()
if self.is_token_valid():
_LOGGER.debug("Token still valid, using it.")
return self._prefs[STORAGE_ACCESS_TOKEN]
if self._prefs[STORAGE_REFRESH_TOKEN] is None:
_LOGGER.debug("Token invalid and no refresh token available.")
return None
lwa_params = {
"grant_type": "refresh_token",
"refresh_token": self._prefs[STORAGE_REFRESH_TOKEN],
"client_id": self.client_id,
"client_secret": self.client_secret
}
_LOGGER.debug("Calling LWA to refresh the access token.")
return await self._async_request_new_token(lwa_params)
@callback
def is_token_valid(self):
"""Check if a token is already loaded and if it is still valid."""
if not self._prefs[STORAGE_ACCESS_TOKEN]:
return False
expire_time = dt.parse_datetime(self._prefs[STORAGE_EXPIRE_TIME])
preemptive_expire_time = expire_time - timedelta(
seconds=PREEMPTIVE_REFRESH_TTL_IN_SECONDS)
return dt.utcnow() < preemptive_expire_time
async def _async_request_new_token(self, lwa_params):
try:
session = aiohttp_client.async_get_clientsession(self.hass)
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=self.hass.loop):
response = await session.post(LWA_TOKEN_URI,
headers=LWA_HEADERS,
data=lwa_params,
allow_redirects=True)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout calling LWA to get auth token.")
return None
_LOGGER.debug("LWA response header: %s", response.headers)
_LOGGER.debug("LWA response status: %s", response.status)
if response.status != 200:
_LOGGER.error("Error calling LWA to get auth token.")
return None
response_json = await response.json()
_LOGGER.debug("LWA response body : %s", response_json)
access_token = response_json["access_token"]
refresh_token = response_json["refresh_token"]
expires_in = response_json["expires_in"]
expire_time = dt.utcnow() + timedelta(seconds=expires_in)
await self._async_update_preferences(access_token, refresh_token,
expire_time.isoformat())
return access_token
async def async_load_preferences(self):
"""Load preferences with stored tokens."""
self._prefs = await self._store.async_load()
if self._prefs is None:
self._prefs = {
STORAGE_ACCESS_TOKEN: None,
STORAGE_REFRESH_TOKEN: None,
STORAGE_EXPIRE_TIME: None
}
async def _async_update_preferences(self, access_token, refresh_token,
expire_time):
"""Update user preferences."""
if self._prefs is None:
await self.async_load_preferences()
if access_token is not None:
self._prefs[STORAGE_ACCESS_TOKEN] = access_token
if refresh_token is not None:
self._prefs[STORAGE_REFRESH_TOKEN] = refresh_token
if expire_time is not None:
self._prefs[STORAGE_EXPIRE_TIME] = expire_time
await self._store.async_save(self._prefs)

View File

@@ -10,6 +10,9 @@ CONF_DISPLAY_URL = 'display_url'
CONF_FILTER = 'filter'
CONF_ENTITY_CONFIG = 'entity_config'
CONF_ENDPOINT = 'endpoint'
CONF_CLIENT_ID = 'client_id'
CONF_CLIENT_SECRET = 'client_secret'
ATTR_UID = 'uid'
ATTR_UPDATE_DATE = 'updateDate'
@@ -21,3 +24,5 @@ ATTR_REDIRECTION_URL = 'redirectionURL'
SYN_RESOLUTION_MATCH = 'ER_SUCCESS_MATCH'
DATE_FORMAT = '%Y-%m-%dT%H:%M:%S.0Z'
DEFAULT_TIMEOUT = 30

View File

@@ -5,15 +5,22 @@ https://developer.amazon.com/docs/smarthome/understand-the-smart-home-skill-api.
https://developer.amazon.com/docs/device-apis/message-guide.html
"""
import asyncio
from collections import OrderedDict
from datetime import datetime
import json
import logging
import math
from uuid import uuid4
import aiohttp
import async_timeout
from homeassistant.components import (
alert, automation, binary_sensor, climate, cover, fan, group, http,
input_boolean, light, lock, media_player, scene, script, sensor, switch)
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.event import async_track_state_change
from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CLOUD_NEVER_EXPOSED_ENTITIES,
@@ -21,13 +28,15 @@ from homeassistant.const import (
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON, STATE_UNLOCKED,
TEMP_CELSIUS, TEMP_FAHRENHEIT)
TEMP_CELSIUS, TEMP_FAHRENHEIT, MATCH_ALL)
import homeassistant.core as ha
import homeassistant.util.color as color_util
from homeassistant.util.decorator import Registry
from homeassistant.util.temperature import convert as convert_temperature
from .const import CONF_ENTITY_CONFIG, CONF_FILTER
from .const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_ENDPOINT, \
CONF_ENTITY_CONFIG, CONF_FILTER, DATE_FORMAT, DEFAULT_TIMEOUT
from .auth import Auth
_LOGGER = logging.getLogger(__name__)
@@ -37,6 +46,8 @@ API_EVENT = 'event'
API_CONTEXT = 'context'
API_HEADER = 'header'
API_PAYLOAD = 'payload'
API_SCOPE = 'scope'
API_CHANGE = 'change'
API_TEMP_UNITS = {
TEMP_FAHRENHEIT: 'FAHRENHEIT',
@@ -66,6 +77,8 @@ HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
EVENT_ALEXA_SMART_HOME = 'alexa_smart_home'
AUTH_KEY = "alexa.smart_home.auth"
class _DisplayCategory:
"""Possible display categories for Discovery response.
@@ -375,6 +388,8 @@ class _AlexaInterface:
'name': prop_name,
'namespace': self.name(),
'value': prop_value,
'timeOfSample': datetime.now().strftime(DATE_FORMAT),
'uncertaintyInMilliseconds': 0
}
@@ -390,6 +405,9 @@ class _AlexaPowerController(_AlexaInterface):
def properties_supported(self):
return [{'name': 'powerState'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@@ -417,6 +435,9 @@ class _AlexaLockController(_AlexaInterface):
def properties_retrievable(self):
return True
def properties_proactively_reported(self):
return True
def get_property(self, name):
if name != 'lockState':
raise _UnsupportedProperty(name)
@@ -454,6 +475,9 @@ class _AlexaBrightnessController(_AlexaInterface):
def properties_supported(self):
return [{'name': 'brightness'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@@ -504,6 +528,20 @@ class _AlexaColorTemperatureController(_AlexaInterface):
def name(self):
return 'Alexa.ColorTemperatureController'
def properties_supported(self):
return [{'name': 'colorTemperatureInKelvin'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'colorTemperatureInKelvin':
raise _UnsupportedProperty(name)
if 'color_temp' in self.entity.attributes:
return color_util.color_temperature_mired_to_kelvin(
self.entity.attributes['color_temp'])
return 0
class _AlexaPercentageController(_AlexaInterface):
"""Implements Alexa.PercentageController.
@@ -571,6 +609,9 @@ class _AlexaTemperatureSensor(_AlexaInterface):
def properties_supported(self):
return [{'name': 'temperature'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@@ -611,6 +652,9 @@ class _AlexaContactSensor(_AlexaInterface):
def properties_supported(self):
return [{'name': 'detectionState'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@@ -634,6 +678,9 @@ class _AlexaMotionSensor(_AlexaInterface):
def properties_supported(self):
return [{'name': 'detectionState'}]
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@@ -672,6 +719,9 @@ class _AlexaThermostatController(_AlexaInterface):
properties.append({'name': 'thermostatMode'})
return properties
def properties_proactively_reported(self):
return True
def properties_retrievable(self):
return True
@@ -934,8 +984,11 @@ class _Cause:
class Config:
"""Hold the configuration for Alexa."""
def __init__(self, should_expose, entity_config=None):
def __init__(self, endpoint, async_get_access_token, should_expose,
entity_config=None):
"""Initialize the configuration."""
self.endpoint = endpoint
self.async_get_access_token = async_get_access_token
self.should_expose = should_expose
self.entity_config = entity_config or {}
@@ -950,12 +1003,62 @@ def async_setup(hass, config):
Even if that's disabled, the functionality in this module may still be used
by the cloud component which will call async_handle_message directly.
"""
if config.get(CONF_CLIENT_ID) and config.get(CONF_CLIENT_SECRET):
hass.data[AUTH_KEY] = Auth(hass, config[CONF_CLIENT_ID],
config[CONF_CLIENT_SECRET])
async_get_access_token = \
hass.data[AUTH_KEY].async_get_access_token if AUTH_KEY in hass.data \
else None
smart_home_config = Config(
endpoint=config.get(CONF_ENDPOINT),
async_get_access_token=async_get_access_token,
should_expose=config[CONF_FILTER],
entity_config=config.get(CONF_ENTITY_CONFIG),
)
hass.http.register_view(SmartHomeView(smart_home_config))
if AUTH_KEY in hass.data:
hass.loop.create_task(
async_enable_proactive_mode(hass, smart_home_config))
async def async_enable_proactive_mode(hass, smart_home_config):
"""Enable the proactive mode.
Proactive mode makes this component report state changes to Alexa.
"""
if smart_home_config.async_get_access_token is None:
# no function to call to get token
return
if await smart_home_config.async_get_access_token() is None:
# not ready yet
return
async def async_entity_state_listener(changed_entity, old_state,
new_state):
if not smart_home_config.should_expose(changed_entity):
_LOGGER.debug("Not exposing %s because filtered by config",
changed_entity)
return
if new_state.domain not in ENTITY_ADAPTERS:
return
alexa_changed_entity = \
ENTITY_ADAPTERS[new_state.domain](hass, smart_home_config,
new_state)
for interface in alexa_changed_entity.interfaces():
if interface.properties_proactively_reported():
await async_send_changereport_message(hass, smart_home_config,
alexa_changed_entity)
return
async_track_state_change(hass, MATCH_ALL, async_entity_state_listener)
class SmartHomeView(http.HomeAssistantView):
"""Expose Smart Home v3 payload interface via HTTP POST."""
@@ -1098,6 +1201,24 @@ class _AlexaResponse:
"""
self._response[API_EVENT][API_HEADER]['correlationToken'] = token
def set_endpoint_full(self, bearer_token, endpoint_id, cookie=None):
"""Set the endpoint dictionary.
This is used to send proactive messages to Alexa.
"""
self._response[API_EVENT][API_ENDPOINT] = {
API_SCOPE: {
'type': 'BearerToken',
'token': bearer_token
}
}
if endpoint_id is not None:
self._response[API_EVENT][API_ENDPOINT]['endpointId'] = endpoint_id
if cookie is not None:
self._response[API_EVENT][API_ENDPOINT]['cookie'] = cookie
def set_endpoint(self, endpoint):
"""Set the endpoint.
@@ -1208,6 +1329,62 @@ async def async_handle_message(
return response.serialize()
async def async_send_changereport_message(hass, config, alexa_entity):
"""Send a ChangeReport message for an Alexa entity."""
token = await config.async_get_access_token()
if not token:
_LOGGER.error("Invalid access token.")
return
headers = {
"Authorization": "Bearer {}".format(token),
"Content-Type": "application/json;charset=UTF-8"
}
endpoint = alexa_entity.entity_id()
# this sends all the properties of the Alexa Entity, whether they have
# changed or not. this should be improved, and properties that have not
# changed should be moved to the 'context' object
properties = list(alexa_entity.serialize_properties())
payload = {
API_CHANGE: {
'cause': {'type': _Cause.APP_INTERACTION},
'properties': properties
}
}
message = _AlexaResponse(name='ChangeReport', namespace='Alexa',
payload=payload)
message.set_endpoint_full(token, endpoint)
message_str = json.dumps(message.serialize())
try:
session = aiohttp_client.async_get_clientsession(hass)
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
response = await session.post(config.endpoint,
headers=headers,
data=message_str,
allow_redirects=True)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout calling LWA to get auth token.")
return None
response_text = await response.text()
_LOGGER.debug("Sent: %s", message_str)
_LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status != 202:
response_json = json.loads(response_text)
_LOGGER.error("Error when sending ChangeReport to Alexa: %s: %s",
response_json["payload"]["code"],
response_json["payload"]["description"])
@HANDLERS.register(('Alexa.Discovery', 'Discover'))
async def async_api_discovery(hass, config, directive, context):
"""Create a API formatted discovery response.
@@ -1244,8 +1421,9 @@ async def async_api_discovery(hass, config, directive, context):
i.serialize_discovery() for i in alexa_entity.interfaces()]
if not endpoint['capabilities']:
_LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id)
_LOGGER.debug(
"Not exposing %s because it has no capabilities",
entity.entity_id)
continue
discovery_endpoints.append(endpoint)
@@ -1256,6 +1434,25 @@ async def async_api_discovery(hass, config, directive, context):
)
@HANDLERS.register(('Alexa.Authorization', 'AcceptGrant'))
async def async_api_accept_grant(hass, config, directive, context):
"""Create a API formatted AcceptGrant response.
Async friendly.
"""
auth_code = directive.payload['grant']['code']
_LOGGER.debug("AcceptGrant code: %s", auth_code)
if AUTH_KEY in hass.data:
await hass.data[AUTH_KEY].async_do_auth(auth_code)
await async_enable_proactive_mode(hass, config)
return directive.response(
name='AcceptGrant.Response',
namespace='Alexa.Authorization',
payload={})
@HANDLERS.register(('Alexa.PowerController', 'TurnOn'))
async def async_api_turn_on(hass, config, directive, context):
"""Process a turn on request."""

View File

@@ -9,7 +9,9 @@ import json
import logging
from aiohttp import web
from aiohttp.web_exceptions import HTTPBadRequest
import async_timeout
import voluptuous as vol
from homeassistant.bootstrap import DATA_LOGGING
from homeassistant.components.http import HomeAssistantView
@@ -21,7 +23,8 @@ from homeassistant.const import (
URL_API_TEMPLATE, __version__)
import homeassistant.core as ha
from homeassistant.auth.permissions.const import POLICY_READ
from homeassistant.exceptions import TemplateError, Unauthorized
from homeassistant.exceptions import (
TemplateError, Unauthorized, ServiceNotFound)
from homeassistant.helpers import template
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates
@@ -339,8 +342,11 @@ class APIDomainServicesView(HomeAssistantView):
"Data should be valid JSON.", HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states:
await hass.services.async_call(
domain, service, data, True, self.context(request))
try:
await hass.services.async_call(
domain, service, data, True, self.context(request))
except (vol.Invalid, ServiceNotFound):
raise HTTPBadRequest()
return self.json(changed_states)

View File

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

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.2']
REQUIREMENTS = ['pyarlo==0.2.3']
_LOGGER = logging.getLogger(__name__)

View File

@@ -14,7 +14,7 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
REQUIREMENTS = ['aioasuswrt==1.1.11']
REQUIREMENTS = ['aioasuswrt==1.1.17']
_LOGGER = logging.getLogger(__name__)

View File

@@ -11,7 +11,6 @@ import voluptuous as vol
from requests import RequestException
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import discovery
@@ -141,11 +140,11 @@ def setup(hass, config):
from requests import Session
conf = config[DOMAIN]
api_http_session = None
try:
api_http_session = Session()
except RequestException as ex:
_LOGGER.warning("Creating HTTP session failed with: %s", str(ex))
api_http_session = None
api = Api(timeout=conf.get(CONF_TIMEOUT), http_session=api_http_session)
@@ -157,6 +156,20 @@ def setup(hass, config):
install_id=conf.get(CONF_INSTALL_ID),
access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE))
def close_http_session(event):
"""Close API sessions used to connect to August."""
_LOGGER.debug("Closing August HTTP sessions")
if api_http_session:
try:
api_http_session.close()
except RequestException:
pass
_LOGGER.debug("August HTTP session closed.")
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, close_http_session)
_LOGGER.debug("Registered for HASS stop event")
return setup_august(hass, config, api, authenticator)
@@ -178,22 +191,6 @@ class AugustData:
self._door_state_by_id = {}
self._activities_by_id = {}
@callback
def august_api_stop(event):
"""Close the API HTTP session."""
_LOGGER.debug("Closing August HTTP session")
try:
self._api.http_session.close()
self._api.http_session = None
except RequestException:
pass
_LOGGER.debug("August HTTP session closed.")
self._hass.bus.listen_once(
EVENT_HOMEASSISTANT_STOP, august_api_stop)
_LOGGER.debug("Registered for HASS stop event")
@property
def house_ids(self):
"""Return a list of house_ids."""

View File

@@ -5,28 +5,28 @@
"no_available_service": "No hi ha serveis de notificaci\u00f3 disponibles."
},
"error": {
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho."
"invalid_code": "Codi inv\u00e0lid, si us plau torna a provar-ho."
},
"step": {
"init": {
"description": "Seleccioneu un dels serveis de notificaci\u00f3:",
"title": "Configureu una contrasenya d'un sol \u00fas a trav\u00e9s del component de notificacions"
"description": "Selecciona un dels serveis de notificaci\u00f3:",
"title": "Configuraci\u00f3 d'una contrasenya d'un sol \u00fas a trav\u00e9s del component de notificacions"
},
"setup": {
"description": "**notify.{notify_service}** ha enviat una contrasenya d'un sol \u00fas. Introdu\u00efu-la a continuaci\u00f3:",
"title": "Verifiqueu la configuraci\u00f3"
"description": "S'ha enviat una contrasenya d'un sol \u00fas mitjan\u00e7ant **notify.{notify_service}**. Introdueix-la a continuaci\u00f3:",
"title": "Verificaci\u00f3 de la configuraci\u00f3"
}
},
"title": "Contrasenya d'un sol \u00fas del servei de notificacions"
},
"totp": {
"error": {
"invalid_code": "Codi inv\u00e0lid, si us plau torni a provar-ho. Si obteniu aquest error repetidament, assegureu-vos que la data i hora de Home Assistant sigui correcta i precisa."
"invalid_code": "Codi inv\u00e0lid, si us plau torna a provar-ho. Si obtens aquest error repetidament, assegura't que la data i hora de Home Assistant siguin correctes i acurades."
},
"step": {
"init": {
"description": "Per activar la verificaci\u00f3 en dos passos mitjan\u00e7ant contrasenyes d'un sol \u00fas basades en temps, escanegeu el codi QR amb la vostre aplicaci\u00f3 de verificaci\u00f3. Si no en teniu cap, us recomanem [Google Authenticator](https://support.google.com/accounts/answer/1066447) o b\u00e9 [Authy](https://authy.com/). \n\n {qr_code} \n \nDespr\u00e9s d'escanejar el codi QR, introdu\u00efu el codi de sis d\u00edgits proporcionat per l'aplicaci\u00f3. Si teniu problemes per escanejar el codi QR, feu una configuraci\u00f3 manual amb el codi **`{code}`**.",
"title": "Configureu la verificaci\u00f3 en dos passos utilitzant TOTP"
"description": "Per activar la verificaci\u00f3 en dos passos mitjan\u00e7ant contrasenyes d'un sol \u00fas basades en temps, escaneja el codi QR amb la teva aplicaci\u00f3 de verificaci\u00f3. Si no en tens cap, et recomanem [Google Authenticator](https://support.google.com/accounts/answer/1066447) o b\u00e9 [Authy](https://authy.com/). \n\n {qr_code} \n \nDespr\u00e9s d'escanejar el codi QR, introdueix el codi de sis d\u00edgits proporcionat per l'aplicaci\u00f3. Si tens problemes per escanejar el codi QR, fes una configuraci\u00f3 manual amb el codi **`{code}`**.",
"title": "Configura la verificaci\u00f3 en dos passos utilitzant TOTP"
}
},
"title": "TOTP"

View File

@@ -13,6 +13,7 @@
"title": "Nastavte jednor\u00e1zov\u00e9 heslo dodan\u00e9 komponentou notify"
},
"setup": {
"description": "Jednor\u00e1zov\u00e9 heslo bylo odesl\u00e1no prost\u0159ednictv\u00edm **notify.{notify_service}**. Zadejte jej n\u00ed\u017ee:",
"title": "Ov\u011b\u0159en\u00ed nastaven\u00ed"
}
}
@@ -20,7 +21,14 @@
"totp": {
"error": {
"invalid_code": "Neplatn\u00fd k\u00f3d, zkuste to znovu. Pokud se tato chyba opakuje, ujist\u011bte se, \u017ee hodiny syst\u00e9mu Home Assistant jsou spr\u00e1vn\u011b nastaveny."
}
},
"step": {
"init": {
"description": "Chcete-li aktivovat dvoufaktorovou autentizaci pomoc\u00ed jednor\u00e1zov\u00fdch hesel zalo\u017een\u00fdch na \u010dase, na\u010dt\u011bte k\u00f3d QR pomoc\u00ed va\u0161\u00ed autentiza\u010dn\u00ed aplikace. Pokud ji nem\u00e1te, doporu\u010dujeme bu\u010f [Google Authenticator](https://support.google.com/accounts/answer/1066447) nebo [Authy](https://authy.com/). \n\n {qr_code} \n \n Po skenov\u00e1n\u00ed k\u00f3du zadejte \u0161estcifern\u00fd k\u00f3d z aplikace a ov\u011b\u0159te nastaven\u00ed. Pokud m\u00e1te probl\u00e9my se skenov\u00e1n\u00edm k\u00f3du QR, prove\u010fte ru\u010dn\u00ed nastaven\u00ed s k\u00f3dem **`{code}`**.",
"title": "Nastavte dvoufaktorovou autentizaci pomoc\u00ed TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -2,18 +2,18 @@
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Ni na voljo storitev obve\u0161\u010danja."
"no_available_service": "Storitve obve\u0161\u010danja niso na voljo."
},
"error": {
"invalid_code": "Neveljavna koda, poskusite znova."
},
"step": {
"init": {
"description": "Prosimo, izberite eno od storitev obve\u0161\u010danja:",
"description": "Izberite eno od storitev obve\u0161\u010danja:",
"title": "Nastavite enkratno geslo, ki ga dostavite z obvestilno komponento"
},
"setup": {
"description": "Enkratno geslo je poslal **notify.{notify_service} **. Vnesite ga spodaj:",
"description": "Enkratno geslo je poslal **notify.{notify_service} **. Prosimo, vnesite ga spodaj:",
"title": "Preverite nastavitev"
}
},

View File

@@ -16,12 +16,13 @@ from homeassistant.core import CoreState
from homeassistant.loader import bind_hass
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID,
EVENT_AUTOMATION_TRIGGERED, ATTR_NAME)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
@@ -93,11 +94,11 @@ PLATFORM_SCHEMA = vol.Schema({
})
SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
TRIGGER_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_VARIABLES, default={}): dict,
})
@@ -182,7 +183,7 @@ async def async_setup(hass, config):
return True
class AutomationEntity(ToggleEntity):
class AutomationEntity(ToggleEntity, RestoreEntity):
"""Entity to show status of entity."""
def __init__(self, automation_id, name, async_attach_triggers, cond_func,
@@ -227,12 +228,13 @@ class AutomationEntity(ToggleEntity):
async def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state."""
await super().async_added_to_hass()
if self._initial_state is not None:
enable_automation = self._initial_state
_LOGGER.debug("Automation %s initial state %s from config "
"initial_state", self.entity_id, enable_automation)
else:
state = await async_get_last_state(self.hass, self.entity_id)
state = await self.async_get_last_state()
if state:
enable_automation = state.state == STATE_ON
self._last_triggered = state.attributes.get('last_triggered')
@@ -285,12 +287,17 @@ class AutomationEntity(ToggleEntity):
"""
if skip_condition or self._cond_func(variables):
self.async_set_context(context)
self.hass.bus.async_fire(EVENT_AUTOMATION_TRIGGERED, {
ATTR_NAME: self._name,
ATTR_ENTITY_ID: self.entity_id,
}, context=context)
await self._async_action(self.entity_id, variables, context)
self._last_triggered = utcnow()
await self.async_update_ha_state()
async def async_will_remove_from_hass(self):
"""Remove listeners when removing automation from HASS."""
await super().async_will_remove_from_hass()
await self.async_turn_off()
async def async_enable(self):
@@ -370,7 +377,13 @@ def _async_get_action(hass, config, name):
_LOGGER.info('Executing %s', name)
hass.components.logbook.async_log_entry(
name, 'has been triggered', DOMAIN, entity_id)
await script_obj.async_run(variables, context)
try:
await script_obj.async_run(variables, context)
except Exception as err: # pylint: disable=broad-except
script_obj.async_log_exception(
_LOGGER,
'Error while executing automation {}'.format(entity_id), err)
return action

View File

@@ -9,7 +9,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.alarmdecoder import (
ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE,
CONF_ZONE_RFID, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE,
CONF_ZONE_RFID, CONF_ZONE_LOOP, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE,
SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR,
CONF_RELAY_CHAN)
@@ -37,10 +37,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
zone_rfid = device_config_data.get(CONF_ZONE_RFID)
zone_loop = device_config_data.get(CONF_ZONE_LOOP)
relay_addr = device_config_data.get(CONF_RELAY_ADDR)
relay_chan = device_config_data.get(CONF_RELAY_CHAN)
device = AlarmDecoderBinarySensor(
zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan)
zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr,
relay_chan)
devices.append(device)
add_entities(devices)
@@ -51,7 +53,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Representation of an AlarmDecoder binary sensor."""
def __init__(self, zone_number, zone_name, zone_type, zone_rfid,
def __init__(self, zone_number, zone_name, zone_type, zone_rfid, zone_loop,
relay_addr, relay_chan):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
@@ -59,6 +61,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._state = None
self._name = zone_name
self._rfid = zone_rfid
self._loop = zone_loop
self._rfstate = None
self._relay_addr = relay_addr
self._relay_chan = relay_chan
@@ -92,14 +95,14 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Return the state attributes."""
attr = {}
if self._rfid and self._rfstate is not None:
attr[ATTR_RF_BIT0] = True if self._rfstate & 0x01 else False
attr[ATTR_RF_LOW_BAT] = True if self._rfstate & 0x02 else False
attr[ATTR_RF_SUPERVISED] = True if self._rfstate & 0x04 else False
attr[ATTR_RF_BIT3] = True if self._rfstate & 0x08 else False
attr[ATTR_RF_LOOP3] = True if self._rfstate & 0x10 else False
attr[ATTR_RF_LOOP2] = True if self._rfstate & 0x20 else False
attr[ATTR_RF_LOOP4] = True if self._rfstate & 0x40 else False
attr[ATTR_RF_LOOP1] = True if self._rfstate & 0x80 else False
attr[ATTR_RF_BIT0] = bool(self._rfstate & 0x01)
attr[ATTR_RF_LOW_BAT] = bool(self._rfstate & 0x02)
attr[ATTR_RF_SUPERVISED] = bool(self._rfstate & 0x04)
attr[ATTR_RF_BIT3] = bool(self._rfstate & 0x08)
attr[ATTR_RF_LOOP3] = bool(self._rfstate & 0x10)
attr[ATTR_RF_LOOP2] = bool(self._rfstate & 0x20)
attr[ATTR_RF_LOOP4] = bool(self._rfstate & 0x40)
attr[ATTR_RF_LOOP1] = bool(self._rfstate & 0x80)
return attr
@property
@@ -128,6 +131,8 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Update RF state."""
if self._rfid and message and message.serial_number == self._rfid:
self._rfstate = message.value
if self._loop:
self._state = 1 if message.loop[self._loop - 1] else 0
self.schedule_update_ha_state()
def _rel_message_callback(self, message):

View File

@@ -18,7 +18,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data = hass.data[BLINK_DATA]
devs = []
for camera in data.sync.cameras:
for camera in data.cameras:
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
devs.append(BlinkBinarySensor(data, camera, sensor_type))
add_entities(devs, True)
@@ -34,7 +34,7 @@ class BlinkBinarySensor(BinarySensorDevice):
name, icon = BINARY_SENSORS[sensor_type]
self._name = "{} {} {}".format(BLINK_DATA, camera, name)
self._icon = icon
self._camera = data.sync.cameras[camera]
self._camera = data.cameras[camera]
self._state = None
self._unique_id = "{}-{}".format(self._camera.serial, self._type)

View File

@@ -0,0 +1,63 @@
"""Support for ESPHome binary sensors."""
import logging
from typing import TYPE_CHECKING, Optional
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.esphome import EsphomeEntity, \
platform_async_setup_entry
if TYPE_CHECKING:
# pylint: disable=unused-import
from aioesphomeapi import BinarySensorInfo, BinarySensorState # noqa
DEPENDENCIES = ['esphome']
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up ESPHome binary sensors based on a config entry."""
# pylint: disable=redefined-outer-name
from aioesphomeapi import BinarySensorInfo, BinarySensorState # noqa
await platform_async_setup_entry(
hass, entry, async_add_entities,
component_key='binary_sensor',
info_type=BinarySensorInfo, entity_type=EsphomeBinarySensor,
state_type=BinarySensorState
)
class EsphomeBinarySensor(EsphomeEntity, BinarySensorDevice):
"""A binary sensor implementation for ESPHome."""
@property
def _static_info(self) -> 'BinarySensorInfo':
return super()._static_info
@property
def _state(self) -> Optional['BinarySensorState']:
return super()._state
@property
def is_on(self):
"""Return true if the binary sensor is on."""
if self._static_info.is_status_binary_sensor:
# Status binary sensors indicated connected state.
# So in their case what's usually _availability_ is now state
return self._entry_data.available
if self._state is None:
return None
return self._state.state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._static_info.device_class
@property
def available(self):
"""Return True if entity is available."""
if self._static_info.is_status_binary_sensor:
return True
return super().available

View File

@@ -10,12 +10,15 @@ from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT)
from homeassistant.components.fibaro import (
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
from homeassistant.const import (CONF_DEVICE_CLASS, CONF_ICON)
DEPENDENCIES = ['fibaro']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'com.fibaro.floodSensor': ['Flood', 'mdi:water', 'flood'],
'com.fibaro.motionSensor': ['Motion', 'mdi:run', 'motion'],
'com.fibaro.doorSensor': ['Door', 'mdi:window-open', 'door'],
'com.fibaro.windowSensor': ['Window', 'mdi:window-open', 'window'],
'com.fibaro.smokeSensor': ['Smoke', 'mdi:smoking', 'smoke'],
@@ -43,6 +46,7 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
super().__init__(fibaro_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
stype = None
devconf = fibaro_device.device_config
if fibaro_device.type in SENSOR_TYPES:
stype = fibaro_device.type
elif fibaro_device.baseType in SENSOR_TYPES:
@@ -53,6 +57,10 @@ class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
else:
self._device_class = None
self._icon = None
# device_config overrides:
self._device_class = devconf.get(CONF_DEVICE_CLASS,
self._device_class)
self._icon = devconf.get(CONF_ICON, self._icon)
@property
def icon(self):

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.1.8']
REQUIREMENTS = ['pyhik==0.1.9']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'

View File

@@ -28,14 +28,16 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the HomematicIP Cloud binary sensor from a config entry."""
from homematicip.aio.device import (
AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector,
AsyncWaterSensor, AsyncRotaryHandleSensor)
AsyncWaterSensor, AsyncRotaryHandleSensor,
AsyncMotionDetectorPushButton)
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
for device in home.devices:
if isinstance(device, (AsyncShutterContact, AsyncRotaryHandleSensor)):
devices.append(HomematicipShutterContact(home, device))
elif isinstance(device, AsyncMotionDetectorIndoor):
elif isinstance(device, (AsyncMotionDetectorIndoor,
AsyncMotionDetectorPushButton)):
devices.append(HomematicipMotionDetector(home, device))
elif isinstance(device, AsyncSmokeDetector):
devices.append(HomematicipSmokeDetector(home, device))

View File

@@ -3,59 +3,39 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ihc/
"""
import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA)
BinarySensorDevice)
from homeassistant.components.ihc import (
validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO)
from homeassistant.components.ihc.const import CONF_INVERTING
IHC_DATA, IHC_CONTROLLER, IHC_INFO)
from homeassistant.components.ihc.const import (
CONF_INVERTING)
from homeassistant.components.ihc.ihcdevice import IHCDevice
from homeassistant.const import (
CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS)
import homeassistant.helpers.config_validation as cv
CONF_TYPE)
DEPENDENCIES = ['ihc']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BINARY_SENSORS, default=[]):
vol.All(cv.ensure_list, [
vol.All({
vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean,
}, validate_name)
])
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the IHC binary sensor platform."""
ihc_controller = hass.data[IHC_DATA][IHC_CONTROLLER]
info = hass.data[IHC_DATA][IHC_INFO]
if discovery_info is None:
return
devices = []
if discovery_info:
for name, device in discovery_info.items():
ihc_id = device['ihc_id']
product_cfg = device['product_cfg']
product = device['product']
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
else:
binary_sensors = config[CONF_BINARY_SENSORS]
for sensor_cfg in binary_sensors:
ihc_id = sensor_cfg[CONF_ID]
name = sensor_cfg[CONF_NAME]
sensor_type = sensor_cfg.get(CONF_TYPE)
inverting = sensor_cfg[CONF_INVERTING]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
sensor_type, inverting)
devices.append(sensor)
for name, device in discovery_info.items():
ihc_id = device['ihc_id']
product_cfg = device['product_cfg']
product = device['product']
# Find controller that corresponds with device id
ctrl_id = device['ctrl_id']
ihc_key = IHC_DATA.format(ctrl_id)
info = hass.data[ihc_key][IHC_INFO]
ihc_controller = hass.data[ihc_key][IHC_CONTROLLER]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING],
product)
devices.append(sensor)
add_entities(devices)

View File

@@ -14,6 +14,7 @@ DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {'openClosedSensor': 'opening',
'ioLincSensor': 'opening',
'motionSensor': 'motion',
'doorSensor': 'door',
'wetLeakSensor': 'moisture',
@@ -58,7 +59,7 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorDevice):
on_val = bool(self._insteon_device_state.value)
if self._insteon_device_state.name in ['lightSensor',
'openClosedSensor']:
'ioLincSensor']:
return not on_val
return on_val

View File

@@ -52,7 +52,7 @@ def setup_platform(hass, config: ConfigType,
node.nid, node.parent_nid)
else:
device_type = _detect_device_type(node)
subnode_id = int(node.nid[-1])
subnode_id = int(node.nid[-1], 16)
if device_type in ('opening', 'moisture'):
# These sensors use an optional "negative" subnode 2 to snag
# all state changes

View File

@@ -16,10 +16,10 @@ from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS, CONF_DEVICE)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
subscription)
MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -45,17 +45,18 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
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
# Integrations should never expose unique_id through configuration.
# This is an exception because MQTT is a message 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)
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema).extend(
mqtt.MQTT_JSON_ATTRS_SCHEMA.schema)
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
async_add_entities, discovery_info=None):
"""Set up MQTT binary sensor through configuration.yaml."""
await _async_setup_entity(hass, config, async_add_entities)
await _async_setup_entity(config, async_add_entities)
async def async_setup_entry(hass, config_entry, async_add_entities):
@@ -63,7 +64,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async def async_discover(discovery_payload):
"""Discover and add a MQTT binary sensor."""
config = PLATFORM_SCHEMA(discovery_payload)
await _async_setup_entity(hass, config, async_add_entities,
await _async_setup_entity(config, async_add_entities,
discovery_payload[ATTR_DISCOVERY_HASH])
async_dispatcher_connect(
@@ -71,50 +72,31 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async_discover)
async def _async_setup_entity(hass, config, async_add_entities,
discovery_hash=None):
async def _async_setup_entity(config, async_add_entities, discovery_hash=None):
"""Set up the MQTT binary sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
async_add_entities([MqttBinarySensor(
config,
discovery_hash
)])
async_add_entities([MqttBinarySensor(config, discovery_hash)])
class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
class MqttBinarySensor(MqttAttributes, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, config, discovery_hash):
"""Initialize the MQTT binary sensor."""
self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._state = None
self._sub_state = None
self._delay_listener = None
self._name = None
self._state_topic = None
self._device_class = None
self._payload_on = None
self._payload_off = None
self._qos = None
self._force_update = None
self._off_delay = None
self._template = None
self._unique_id = None
# Load config
self._setup_from_config(config)
availability_topic = config.get(CONF_AVAILABILITY_TOPIC)
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)
MqttAvailability.__init__(self, availability_topic, self._qos,
MqttAttributes.__init__(self, config)
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
@@ -122,37 +104,24 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
async def async_added_to_hass(self):
"""Subscribe mqtt events."""
await MqttAvailability.async_added_to_hass(self)
await MqttDiscoveryUpdate.async_added_to_hass(self)
await super().async_added_to_hass()
await self._subscribe_topics()
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
config = PLATFORM_SCHEMA(discovery_payload)
self._setup_from_config(config)
self._config = config
await self.attributes_discovery_update(config)
await self.availability_discovery_update(config)
await self._subscribe_topics()
self.async_schedule_update_ha_state()
def _setup_from_config(self, config):
"""(Re)Setup the entity."""
self._name = config.get(CONF_NAME)
self._state_topic = config.get(CONF_STATE_TOPIC)
self._device_class = config.get(CONF_DEVICE_CLASS)
self._qos = config.get(CONF_QOS)
self._force_update = config.get(CONF_FORCE_UPDATE)
self._off_delay = config.get(CONF_OFF_DELAY)
self._payload_on = config.get(CONF_PAYLOAD_ON)
self._payload_off = config.get(CONF_PAYLOAD_OFF)
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None and value_template.hass is None:
value_template.hass = self.hass
self._template = value_template
self._unique_id = config.get(CONF_UNIQUE_ID)
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = self.hass
@callback
def off_delay_listener(now):
"""Switch device off after a delay."""
@@ -163,38 +132,43 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
@callback
def state_message_received(_topic, payload, _qos):
"""Handle a new received MQTT state message."""
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
payload)
if payload == self._payload_on:
value_template = self._config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
payload = value_template.async_render_with_possible_json_value(
payload, variables={'entity_id': self.entity_id})
if payload == self._config.get(CONF_PAYLOAD_ON):
self._state = True
elif payload == self._payload_off:
elif payload == self._config.get(CONF_PAYLOAD_OFF):
self._state = False
else: # Payload is not for this entity
_LOGGER.warning('No matching payload found'
' for entity: %s with state_topic: %s',
self._name, self._state_topic)
self._config.get(CONF_NAME),
self._config.get(CONF_STATE_TOPIC))
return
if self._delay_listener is not None:
self._delay_listener()
self._delay_listener = None
if (self._state and self._off_delay is not None):
off_delay = self._config.get(CONF_OFF_DELAY)
if (self._state and off_delay is not None):
self._delay_listener = evt.async_call_later(
self.hass, self._off_delay, off_delay_listener)
self.hass, off_delay, off_delay_listener)
self.async_schedule_update_ha_state()
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
{'state_topic': {'topic': self._state_topic,
{'state_topic': {'topic': self._config.get(CONF_STATE_TOPIC),
'msg_callback': state_message_received,
'qos': self._qos}})
'qos': self._config.get(CONF_QOS)}})
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
await subscription.async_unsubscribe_topics(self.hass, self._sub_state)
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAttributes.async_will_remove_from_hass(self)
await MqttAvailability.async_will_remove_from_hass(self)
@property
@@ -205,7 +179,7 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
@property
def name(self):
"""Return the name of the binary sensor."""
return self._name
return self._config.get(CONF_NAME)
@property
def is_on(self):
@@ -215,12 +189,12 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
@property
def device_class(self):
"""Return the class of this sensor."""
return self._device_class
return self._config.get(CONF_DEVICE_CLASS)
@property
def force_update(self):
"""Force update."""
return self._force_update
return self._config.get(CONF_FORCE_UPDATE)
@property
def unique_id(self):

View File

@@ -61,8 +61,7 @@ class MyStromView(HomeAssistantView):
'{}_{}'.format(button_id, button_action))
self.add_entities([self.buttons[entity_id]])
else:
new_state = True if self.buttons[entity_id].state == 'off' \
else False
new_state = self.buttons[entity_id].state == 'off'
self.buttons[entity_id].async_on_update(new_state)

View File

@@ -0,0 +1,81 @@
"""
Support for Ness D8X/D16X zone states - represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ness_alarm/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.ness_alarm import (
CONF_ZONES, CONF_ZONE_TYPE, CONF_ZONE_NAME, CONF_ZONE_ID,
SIGNAL_ZONE_CHANGED, ZoneChangedData)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['ness_alarm']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Ness Alarm binary sensor devices."""
if not discovery_info:
return
configured_zones = discovery_info[CONF_ZONES]
devices = []
for zone_config in configured_zones:
zone_type = zone_config[CONF_ZONE_TYPE]
zone_name = zone_config[CONF_ZONE_NAME]
zone_id = zone_config[CONF_ZONE_ID]
device = NessZoneBinarySensor(zone_id=zone_id, name=zone_name,
zone_type=zone_type)
devices.append(device)
async_add_entities(devices)
class NessZoneBinarySensor(BinarySensorDevice):
"""Representation of an Ness alarm zone as a binary sensor."""
def __init__(self, zone_id, name, zone_type):
"""Initialize the binary_sensor."""
self._zone_id = zone_id
self._name = name
self._type = zone_type
self._state = 0
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONE_CHANGED, self._handle_zone_change)
@property
def name(self):
"""Return the name of the entity."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state == 1
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return self._type
@callback
def _handle_zone_change(self, data: ZoneChangedData):
"""Handle zone state update."""
if self._zone_id == data.zone_id:
self._state = data.state
self.async_schedule_update_ha_state()

View File

@@ -7,10 +7,10 @@ https://home-assistant.io/components/binary_sensor.point/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
from homeassistant.components.point import MinutPointEntity
from homeassistant.components.point.const import (
DOMAIN as POINT_DOMAIN, NEW_DEVICE, SIGNAL_WEBHOOK)
DOMAIN as POINT_DOMAIN, POINT_DISCOVERY_NEW, SIGNAL_WEBHOOK)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -40,10 +40,16 @@ EVENTS = {
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up a Point's binary sensors based on a config entry."""
device_id = config_entry.data[NEW_DEVICE]
client = hass.data[POINT_DOMAIN][config_entry.entry_id]
async_add_entities((MinutPointBinarySensor(client, device_id, device_class)
for device_class in EVENTS), True)
async def async_discover_sensor(device_id):
"""Discover and add a discovered sensor."""
client = hass.data[POINT_DOMAIN][config_entry.entry_id]
async_add_entities(
(MinutPointBinarySensor(client, device_id, device_class)
for device_class in EVENTS), True)
async_dispatcher_connect(
hass, POINT_DISCOVERY_NEW.format(DOMAIN, POINT_DOMAIN),
async_discover_sensor)
class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice):

View File

@@ -74,7 +74,7 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def update(self):
def update():
"""Update the state."""
self.async_schedule_update_ha_state(True)

View File

@@ -8,9 +8,11 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.satel_integra import (CONF_ZONES,
CONF_OUTPUTS,
CONF_ZONE_NAME,
CONF_ZONE_TYPE,
SIGNAL_ZONES_UPDATED)
SIGNAL_ZONES_UPDATED,
SIGNAL_OUTPUTS_UPDATED)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -32,7 +34,17 @@ async def async_setup_platform(hass, config, async_add_entities,
for zone_num, device_config_data in configured_zones.items():
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type)
device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type,
SIGNAL_ZONES_UPDATED)
devices.append(device)
configured_outputs = discovery_info[CONF_OUTPUTS]
for zone_num, device_config_data in configured_outputs.items():
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
device = SatelIntegraBinarySensor(zone_num, zone_name, zone_type,
SIGNAL_OUTPUTS_UPDATED)
devices.append(device)
async_add_entities(devices)
@@ -41,17 +53,18 @@ async def async_setup_platform(hass, config, async_add_entities,
class SatelIntegraBinarySensor(BinarySensorDevice):
"""Representation of an Satel Integra binary sensor."""
def __init__(self, zone_number, zone_name, zone_type):
def __init__(self, device_number, device_name, zone_type, react_to_signal):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
self._name = zone_name
self._device_number = device_number
self._name = device_name
self._zone_type = zone_type
self._state = 0
self._react_to_signal = react_to_signal
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated)
self.hass, self._react_to_signal, self._devices_updated)
@property
def name(self):
@@ -80,9 +93,9 @@ class SatelIntegraBinarySensor(BinarySensorDevice):
return self._zone_type
@callback
def _zones_updated(self, zones):
def _devices_updated(self, zones):
"""Update the zone's state, if needed."""
if self._zone_number in zones \
and self._state != zones[self._zone_number]:
self._state = zones[self._zone_number]
if self._device_number in zones \
and self._state != zones[self._device_number]:
self._state = zones[self._device_number]
self.async_schedule_update_ha_state()

View File

@@ -14,46 +14,48 @@ DEPENDENCIES = ['sense']
_LOGGER = logging.getLogger(__name__)
BIN_SENSOR_CLASS = 'power'
MDI_ICONS = {'ac': 'air-conditioner',
'aquarium': 'fish',
'car': 'car-electric',
'computer': 'desktop-classic',
'cup': 'coffee',
'dehumidifier': 'water-off',
'dishes': 'dishwasher',
'drill': 'toolbox',
'fan': 'fan',
'freezer': 'fridge-top',
'fridge': 'fridge-bottom',
'game': 'gamepad-variant',
'garage': 'garage',
'grill': 'stove',
'heat': 'fire',
'heater': 'radiatior',
'humidifier': 'water',
'kettle': 'kettle',
'leafblower': 'leaf',
'lightbulb': 'lightbulb',
'media_console': 'set-top-box',
'modem': 'router-wireless',
'outlet': 'power-socket-us',
'papershredder': 'shredder',
'printer': 'printer',
'pump': 'water-pump',
'settings': 'settings',
'skillet': 'pot',
'smartcamera': 'webcam',
'socket': 'power-plug',
'sound': 'speaker',
'stove': 'stove',
'trash': 'trash-can',
'tv': 'television',
'vacuum': 'robot-vacuum',
'washer': 'washing-machine'}
MDI_ICONS = {
'ac': 'air-conditioner',
'aquarium': 'fish',
'car': 'car-electric',
'computer': 'desktop-classic',
'cup': 'coffee',
'dehumidifier': 'water-off',
'dishes': 'dishwasher',
'drill': 'toolbox',
'fan': 'fan',
'freezer': 'fridge-top',
'fridge': 'fridge-bottom',
'game': 'gamepad-variant',
'garage': 'garage',
'grill': 'stove',
'heat': 'fire',
'heater': 'radiatior',
'humidifier': 'water',
'kettle': 'kettle',
'leafblower': 'leaf',
'lightbulb': 'lightbulb',
'media_console': 'set-top-box',
'modem': 'router-wireless',
'outlet': 'power-socket-us',
'papershredder': 'shredder',
'printer': 'printer',
'pump': 'water-pump',
'settings': 'settings',
'skillet': 'pot',
'smartcamera': 'webcam',
'socket': 'power-plug',
'sound': 'speaker',
'stove': 'stove',
'trash': 'trash-can',
'tv': 'television',
'vacuum': 'robot-vacuum',
'washer': 'washing-machine',
}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Sense sensor."""
"""Set up the Sense binary sensor."""
if discovery_info is None:
return
@@ -67,14 +69,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
def sense_to_mdi(sense_icon):
"""Convert sense icon to mdi icon."""
return 'mdi:' + MDI_ICONS.get(sense_icon, 'power-plug')
return 'mdi:{}'.format(MDI_ICONS.get(sense_icon, 'power-plug'))
class SenseDevice(BinarySensorDevice):
"""Implementation of a Sense energy device binary sensor."""
def __init__(self, data, device):
"""Initialize the sensor."""
"""Initialize the Sense binary sensor."""
self._name = device['name']
self._id = device['id']
self._icon = sense_to_mdi(device['icon'])

View File

@@ -41,6 +41,7 @@ class TahomaBinarySensor(TahomaDevice, BinarySensorDevice):
self._state = None
self._icon = None
self._battery = None
self._available = False
@property
def is_on(self):
@@ -71,6 +72,11 @@ class TahomaBinarySensor(TahomaDevice, BinarySensorDevice):
attr[ATTR_BATTERY_LEVEL] = self._battery
return attr
@property
def available(self):
"""Return True if entity is available."""
return self._available
def update(self):
"""Update the state."""
self.controller.get_states([self.tahoma_device])
@@ -82,11 +88,13 @@ class TahomaBinarySensor(TahomaDevice, BinarySensorDevice):
self._state = STATE_ON
if 'core:SensorDefectState' in self.tahoma_device.active_states:
# Set to 'lowBattery' for low battery warning.
# 'lowBattery' for low battery warning. 'dead' for not available.
self._battery = self.tahoma_device.active_states[
'core:SensorDefectState']
self._available = bool(self._battery != 'dead')
else:
self._battery = None
self._available = True
if self._state == STATE_ON:
self._icon = "mdi:fire"

View File

@@ -9,20 +9,35 @@ https://home-assistant.io/components/binary_sensor.tellduslive/
"""
import logging
from homeassistant.components.tellduslive import TelldusLiveEntity
from homeassistant.components import binary_sensor, tellduslive
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.tellduslive.entry import TelldusLiveEntity
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up Tellstick sensors."""
if discovery_info is None:
return
add_entities(
TelldusLiveSensor(hass, binary_sensor)
for binary_sensor in discovery_info
)
"""Old way of setting up TelldusLive.
Can only be called when a user accidentally mentions the platform in their
config. But even in that case it would have been ignored.
"""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up tellduslive sensors dynamically."""
async def async_discover_binary_sensor(device_id):
"""Discover and add a discovered sensor."""
client = hass.data[tellduslive.DOMAIN]
async_add_entities([TelldusLiveSensor(client, device_id)])
async_dispatcher_connect(
hass,
tellduslive.TELLDUS_DISCOVERY_NEW.format(binary_sensor.DOMAIN,
tellduslive.DOMAIN),
async_discover_binary_sensor)
class TelldusLiveSensor(TelldusLiveEntity, BinarySensorDevice):

View File

@@ -6,17 +6,19 @@ https://home-assistant.io/components/binary_sensor.volvooncall/
"""
import logging
from homeassistant.components.volvooncall import VolvoEntity
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.volvooncall import VolvoEntity, DATA_KEY
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Volvo sensors."""
if discovery_info is None:
return
add_entities([VolvoSensor(hass, *discovery_info)])
async_add_entities([VolvoSensor(hass.data[DATA_KEY], *discovery_info)])
class VolvoSensor(VolvoEntity, BinarySensorDevice):
@@ -25,14 +27,11 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
@property
def is_on(self):
"""Return True if the binary sensor is on."""
val = getattr(self.vehicle, self._attribute)
if self._attribute == 'bulb_failures':
return bool(val)
if self._attribute in ['doors', 'windows']:
return any([val[key] for key in val if 'Open' in key])
return val != 'Normal'
return self.instrument.is_on
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""
return 'safety'
if self.instrument.device_class in DEVICE_CLASSES:
return self.instrument.device_class
return None

View File

@@ -4,7 +4,10 @@ Support for WeMo sensors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.wemo/
"""
import asyncio
import logging
import async_timeout
import requests
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -15,7 +18,7 @@ DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Register discovered WeMo binary sensors."""
from pywemo import discovery
@@ -31,7 +34,7 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
raise PlatformNotReady
if device:
add_entities_callback([WemoBinarySensor(hass, device)])
add_entities([WemoBinarySensor(hass, device)])
class WemoBinarySensor(BinarySensorDevice):
@@ -41,48 +44,90 @@ class WemoBinarySensor(BinarySensorDevice):
"""Initialize the WeMo sensor."""
self.wemo = device
self._state = None
self._available = True
self._update_lock = None
self._model_name = self.wemo.model_name
self._name = self.wemo.name
self._serialnumber = self.wemo.serialnumber
wemo = hass.components.wemo
wemo.SUBSCRIPTION_REGISTRY.register(self.wemo)
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
def _update_callback(self, _device, _type, _params):
"""Handle state changes."""
_LOGGER.info("Subscription update for %s", _device)
def _subscription_callback(self, _device, _type, _params):
"""Update the state by the Wemo sensor."""
_LOGGER.debug("Subscription update for %s", self.name)
updated = self.wemo.subscription_update(_type, _params)
self._update(force_update=(not updated))
self.hass.add_job(
self._async_locked_subscription_callback(not updated))
if not hasattr(self, 'hass'):
async def _async_locked_subscription_callback(self, force_update):
"""Handle an update from a subscription."""
# If an update is in progress, we don't do anything
if self._update_lock.locked():
return
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed with subscriptions."""
return False
await self._async_locked_update(force_update)
self.async_schedule_update_ha_state()
async def async_added_to_hass(self):
"""Wemo sensor added to HASS."""
# Define inside async context so we know our event loop
self._update_lock = asyncio.Lock()
registry = self.hass.components.wemo.SUBSCRIPTION_REGISTRY
await self.hass.async_add_executor_job(registry.register, self.wemo)
registry.on(self.wemo, None, self._subscription_callback)
async def async_update(self):
"""Update WeMo state.
Wemo has an aggressive retry logic that sometimes can take over a
minute to return. If we don't get a state after 5 seconds, assume the
Wemo sensor is unreachable. If update goes through, it will be made
available again.
"""
# If an update is in progress, we don't do anything
if self._update_lock.locked():
return
try:
with async_timeout.timeout(5):
await asyncio.shield(self._async_locked_update(True))
except asyncio.TimeoutError:
_LOGGER.warning('Lost connection to %s', self.name)
self._available = False
async def _async_locked_update(self, force_update):
"""Try updating within an async lock."""
async with self._update_lock:
await self.hass.async_add_executor_job(self._update, force_update)
def _update(self, force_update=True):
"""Update the sensor state."""
try:
self._state = self.wemo.get_state(force_update)
if not self._available:
_LOGGER.info('Reconnected to %s', self.name)
self._available = True
except AttributeError as err:
_LOGGER.warning("Could not update status for %s (%s)",
self.name, err)
self._available = False
@property
def unique_id(self):
"""Return the id of this WeMo device."""
return self.wemo.serialnumber
"""Return the id of this WeMo sensor."""
return self._serialnumber
@property
def name(self):
"""Return the name of the service if any."""
return self.wemo.name
return self._name
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
def update(self):
"""Update WeMo state."""
self._update(force_update=True)
def _update(self, force_update=True):
try:
self._state = self.wemo.get_state(force_update)
except AttributeError as err:
_LOGGER.warning(
"Could not update status for %s (%s)", self.name, err)
@property
def available(self):
"""Return true if sensor is available."""
return self._available

View File

@@ -409,10 +409,14 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'double'
elif value == 'both_click':
click_type = 'both'
elif value == 'double_both_click':
click_type = 'double_both'
elif value == 'shake':
click_type = 'shake'
elif value in ['long_click', 'long_both_click']:
return False
elif value == 'long_click':
click_type = 'long'
elif value == 'long_both_click':
click_type = 'long_both'
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)
return False
@@ -423,9 +427,7 @@ class XiaomiButton(XiaomiBinarySensor):
})
self._last_action = click_type
if value in ['long_click_press', 'long_click_release']:
return True
return False
return True
class XiaomiCube(XiaomiBinarySensor):
@@ -467,4 +469,12 @@ class XiaomiCube(XiaomiBinarySensor):
})
self._last_action = 'rotate'
if 'rotate_degree' in data:
self._hass.bus.fire('xiaomi_aqara.cube_action', {
'entity_id': self.entity_id,
'action_type': 'rotate',
'action_value': float(data['rotate_degree'].replace(",", "."))
})
self._last_action = 'rotate'
return True

View File

@@ -7,7 +7,16 @@ at https://home-assistant.io/components/binary_sensor.zha/
import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
from homeassistant.components import zha
from homeassistant.components.zha import helpers
from homeassistant.components.zha.const import (
DATA_ZHA, DATA_ZHA_DISPATCHERS, REPORT_CONFIG_IMMEDIATE, ZHA_DISCOVERY_NEW)
from homeassistant.components.zha.entities import ZhaEntity
from homeassistant.const import STATE_ON
from homeassistant.components.zha.entities.listeners import (
OnOffListener, LevelListener
)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.restore_state import RestoreEntity
_LOGGER = logging.getLogger(__name__)
@@ -22,34 +31,56 @@ CLASS_MAPPING = {
0x002b: 'gas',
0x002d: 'vibration',
}
DEVICE_CLASS_OCCUPANCY = 'occupancy'
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Zigbee Home Automation binary sensors."""
discovery_info = zha.get_discovery_info(hass, discovery_info)
if discovery_info is None:
return
"""Old way of setting up Zigbee Home Automation binary sensors."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Zigbee Home Automation binary sensor from config entry."""
async def async_discover(discovery_info):
await _async_setup_entities(hass, config_entry, async_add_entities,
[discovery_info])
unsub = async_dispatcher_connect(
hass, ZHA_DISCOVERY_NEW.format(DOMAIN), async_discover)
hass.data[DATA_ZHA][DATA_ZHA_DISPATCHERS].append(unsub)
binary_sensors = hass.data.get(DATA_ZHA, {}).get(DOMAIN)
if binary_sensors is not None:
await _async_setup_entities(hass, config_entry, async_add_entities,
binary_sensors.values())
del hass.data[DATA_ZHA][DOMAIN]
async def _async_setup_entities(hass, config_entry, async_add_entities,
discovery_infos):
"""Set up the ZHA binary sensors."""
from zigpy.zcl.clusters.general import OnOff
from zigpy.zcl.clusters.measurement import OccupancySensing
from zigpy.zcl.clusters.security import IasZone
if IasZone.cluster_id in discovery_info['in_clusters']:
await _async_setup_iaszone(hass, config, async_add_entities,
discovery_info)
elif OnOff.cluster_id in discovery_info['out_clusters']:
await _async_setup_remote(hass, config, async_add_entities,
discovery_info)
entities = []
for discovery_info in discovery_infos:
if IasZone.cluster_id in discovery_info['in_clusters']:
entities.append(await _async_setup_iaszone(discovery_info))
elif OccupancySensing.cluster_id in discovery_info['in_clusters']:
entities.append(
BinarySensor(DEVICE_CLASS_OCCUPANCY, **discovery_info))
elif OnOff.cluster_id in discovery_info['out_clusters']:
entities.append(Remote(**discovery_info))
async_add_entities(entities, update_before_add=True)
async def _async_setup_iaszone(hass, config, async_add_entities,
discovery_info):
async def _async_setup_iaszone(discovery_info):
device_class = None
from zigpy.zcl.clusters.security import IasZone
cluster = discovery_info['in_clusters'][IasZone.cluster_id]
if discovery_info['new_join']:
await cluster.bind()
ieee = cluster.endpoint.device.application.ieee
await cluster.write_attributes({'cie_addr': ieee})
try:
zone_type = await cluster['zone_type']
@@ -58,36 +89,11 @@ async def _async_setup_iaszone(hass, config, async_add_entities,
# If we fail to read from the device, use a non-specific class
pass
sensor = BinarySensor(device_class, **discovery_info)
async_add_entities([sensor], update_before_add=True)
return IasZoneSensor(device_class, **discovery_info)
async def _async_setup_remote(hass, config, async_add_entities,
discovery_info):
remote = Remote(**discovery_info)
if discovery_info['new_join']:
from zigpy.zcl.clusters.general import OnOff, LevelControl
out_clusters = discovery_info['out_clusters']
if OnOff.cluster_id in out_clusters:
cluster = out_clusters[OnOff.cluster_id]
await zha.configure_reporting(
remote.entity_id, cluster, 0, min_report=0, max_report=600,
reportable_change=1
)
if LevelControl.cluster_id in out_clusters:
cluster = out_clusters[LevelControl.cluster_id]
await zha.configure_reporting(
remote.entity_id, cluster, 0, min_report=1, max_report=600,
reportable_change=1
)
async_add_entities([remote], update_before_add=True)
class BinarySensor(zha.Entity, BinarySensorDevice):
"""The ZHA Binary Sensor."""
class IasZoneSensor(RestoreEntity, ZhaEntity, BinarySensorDevice):
"""The IasZoneSensor Binary Sensor."""
_domain = DOMAIN
@@ -98,11 +104,6 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
from zigpy.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
@@ -126,94 +127,66 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
res = self._ias_zone_cluster.enroll_response(0, 0)
self.hass.async_add_job(res)
async def async_added_to_hass(self):
"""Run when about to be added to hass."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if self._state is not None or old_state is None:
return
_LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state)
if old_state.state == STATE_ON:
self._state = 3
else:
self._state = 0
async def async_configure(self):
"""Configure IAS device."""
await self._ias_zone_cluster.bind()
ieee = self._ias_zone_cluster.endpoint.device.application.ieee
await self._ias_zone_cluster.write_attributes({'cie_addr': ieee})
_LOGGER.debug("%s: finished configuration", self.entity_id)
async def async_update(self):
"""Retrieve latest state."""
from zigpy.types.basic import uint16_t
result = await zha.safe_read(self._endpoint.ias_zone,
['zone_status'],
allow_cache=False,
only_cache=(not self._initialized))
result = await helpers.safe_read(self._endpoint.ias_zone,
['zone_status'],
allow_cache=False,
only_cache=(not self._initialized))
state = result.get('zone_status', self._state)
if isinstance(state, (int, uint16_t)):
self._state = result.get('zone_status', self._state) & 3
class Remote(zha.Entity, BinarySensorDevice):
class Remote(RestoreEntity, ZhaEntity, BinarySensorDevice):
"""ZHA switch/remote controller/button."""
_domain = DOMAIN
class OnOffListener:
"""Listener for the OnOff Zigbee cluster."""
def __init__(self, entity):
"""Initialize OnOffListener."""
self._entity = entity
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id in (0x0000, 0x0040):
self._entity.set_state(False)
elif command_id in (0x0001, 0x0041, 0x0042):
self._entity.set_state(True)
elif command_id == 0x0002:
self._entity.set_state(not self._entity.is_on)
def attribute_updated(self, attrid, value):
"""Handle attribute updates on this cluster."""
if attrid == 0:
self._entity.set_state(value)
def zdo_command(self, *args, **kwargs):
"""Handle ZDO commands on this cluster."""
pass
class LevelListener:
"""Listener for the LevelControl Zigbee cluster."""
def __init__(self, entity):
"""Initialize LevelListener."""
self._entity = entity
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id in (0x0000, 0x0004): # move_to_level, -with_on_off
self._entity.set_level(args[0])
elif command_id in (0x0001, 0x0005): # move, -with_on_off
# We should dim slowly -- for now, just step once
rate = args[1]
if args[0] == 0xff:
rate = 10 # Should read default move rate
self._entity.move_level(-rate if args[0] else rate)
elif command_id in (0x0002, 0x0006): # step, -with_on_off
# Step (technically may change on/off)
self._entity.move_level(-args[1] if args[0] else args[1])
def attribute_update(self, attrid, value):
"""Handle attribute updates on this cluster."""
if attrid == 0:
self._entity.set_level(value)
def zdo_command(self, *args, **kwargs):
"""Handle ZDO commands on this cluster."""
pass
def __init__(self, **kwargs):
"""Initialize Switch."""
super().__init__(**kwargs)
self._state = False
self._level = 0
from zigpy.zcl.clusters import general
self._out_listeners = {
general.OnOff.cluster_id: self.OnOffListener(self),
general.LevelControl.cluster_id: self.LevelListener(self),
general.OnOff.cluster_id: OnOffListener(
self,
self._out_clusters[general.OnOff.cluster_id]
)
}
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
out_clusters = kwargs.get('out_clusters')
self._zcl_reporting = {}
if general.LevelControl.cluster_id in out_clusters:
self._out_listeners.update({
general.LevelControl.cluster_id: LevelListener(
self,
out_clusters[general.LevelControl.cluster_id]
)
})
@property
def is_on(self) -> bool:
@@ -228,6 +201,11 @@ class Remote(zha.Entity, BinarySensorDevice):
})
return self._device_state_attributes
@property
def zcl_reporting_config(self):
"""Return ZCL attribute reporting configuration."""
return self._zcl_reporting
def move_level(self, change):
"""Increment the level, setting state if appropriate."""
if not self._state and change > 0:
@@ -249,13 +227,91 @@ class Remote(zha.Entity, BinarySensorDevice):
self._level = 255
self.async_schedule_update_ha_state()
async def async_configure(self):
"""Bind clusters."""
from zigpy.zcl.clusters import general
await helpers.bind_cluster(
self.entity_id,
self._out_clusters[general.OnOff.cluster_id]
)
if general.LevelControl.cluster_id in self._out_clusters:
await helpers.bind_cluster(
self.entity_id,
self._out_clusters[general.LevelControl.cluster_id]
)
async def async_added_to_hass(self):
"""Run when about to be added to hass."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if self._state is not None or old_state is None:
return
_LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state)
if 'level' in old_state.attributes:
self._level = old_state.attributes['level']
self._state = old_state.state == STATE_ON
async def async_update(self):
"""Retrieve latest state."""
from zigpy.zcl.clusters.general import OnOff
result = await zha.safe_read(
result = await helpers.safe_read(
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)
class BinarySensor(RestoreEntity, ZhaEntity, BinarySensorDevice):
"""ZHA switch."""
_domain = DOMAIN
_device_class = None
value_attribute = 0
def __init__(self, device_class, **kwargs):
"""Initialize the ZHA binary sensor."""
super().__init__(**kwargs)
self._device_class = device_class
self._cluster = list(kwargs['in_clusters'].values())[0]
def attribute_updated(self, attribute, value):
"""Handle attribute update from device."""
_LOGGER.debug("Attribute updated: %s %s %s", self, attribute, value)
if attribute == self.value_attribute:
self._state = bool(value)
self.async_schedule_update_ha_state()
async def async_added_to_hass(self):
"""Run when about to be added to hass."""
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
if self._state is not None or old_state is None:
return
_LOGGER.debug("%s restoring old state: %s", self.entity_id, old_state)
self._state = old_state.state == STATE_ON
@property
def cluster(self):
"""Zigbee cluster for this entity."""
return self._cluster
@property
def zcl_reporting_config(self):
"""ZHA reporting configuration."""
return {self.cluster: {self.value_attribute: REPORT_CONFIG_IMMEDIATE}}
@property
def is_on(self) -> bool:
"""Return if the switch is on based on the statemachine."""
if self._state is None:
return False
return self._state
@property
def device_class(self) -> str:
"""Return device class from component DEVICE_CLASSES."""
return self._device_class

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.10.3']
REQUIREMENTS = ['blinkpy==0.11.0']
_LOGGER = logging.getLogger(__name__)
@@ -111,7 +111,7 @@ def setup(hass, config):
def trigger_camera(call):
"""Trigger a camera."""
cameras = hass.data[BLINK_DATA].sync.cameras
cameras = hass.data[BLINK_DATA].cameras
name = call.data[CONF_NAME]
if name in cameras:
cameras[name].snap_picture()
@@ -148,7 +148,7 @@ async def async_handle_save_video_service(hass, call):
def _write_video(camera_name, video_path):
"""Call video write."""
all_cameras = hass.data[BLINK_DATA].sync.cameras
all_cameras = hass.data[BLINK_DATA].cameras
if camera_name in all_cameras:
all_cameras[camera_name].video_to_file(video_path)

View File

@@ -61,7 +61,7 @@ FALLBACK_STREAM_INTERVAL = 1 # seconds
MIN_STREAM_INTERVAL = 0.5 # seconds
CAMERA_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({

View File

@@ -7,7 +7,7 @@ https://home-assistant.io/components/camera.axis/
import logging
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging)
from homeassistant.const import (
CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT,
CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION)
@@ -29,6 +29,8 @@ def _get_image_url(host, port, mode):
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Axis camera."""
filter_urllib3_logging()
camera_config = {
CONF_NAME: discovery_info[CONF_NAME],
CONF_USERNAME: discovery_info[CONF_USERNAME],

View File

@@ -23,7 +23,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
return
data = hass.data[BLINK_DATA]
devs = []
for name, camera in data.sync.cameras.items():
for name, camera in data.cameras.items():
devs.append(BlinkCamera(data, name, camera))
add_entities(devs)

View File

@@ -16,7 +16,7 @@ import voluptuous as vol
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION,
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION)
HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_VERIFY_SSL)
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web)
@@ -29,6 +29,7 @@ CONF_STILL_IMAGE_URL = 'still_image_url'
CONTENT_TYPE_HEADER = 'Content-Type'
DEFAULT_NAME = 'Mjpeg Camera'
DEFAULT_VERIFY_SSL = True
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MJPEG_URL): cv.url,
@@ -38,13 +39,22 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up a MJPEG IP Camera."""
# Filter header errors from urllib3 due to a urllib3 bug
filter_urllib3_logging()
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(config)])
def filter_urllib3_logging():
"""Filter header errors from urllib3 due to a urllib3 bug."""
urllib3_logger = logging.getLogger("urllib3.connectionpool")
if not any(isinstance(x, NoHeaderErrorFilter)
for x in urllib3_logger.filters):
@@ -52,21 +62,24 @@ async def async_setup_platform(hass, config, async_add_entities,
NoHeaderErrorFilter()
)
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(config)])
def extract_image_from_mjpeg(stream):
"""Take in a MJPEG stream object, return the jpg from it."""
data = b''
for chunk in stream:
data += chunk
jpg_start = data.find(b'\xff\xd8')
jpg_end = data.find(b'\xff\xd9')
if jpg_start != -1 and jpg_end != -1:
jpg = data[jpg_start:jpg_end + 2]
return jpg
if jpg_end == -1:
continue
jpg_start = data.find(b'\xff\xd8')
if jpg_start == -1:
continue
return data[jpg_start:jpg_end + 2]
class MjpegCamera(Camera):
@@ -88,6 +101,7 @@ class MjpegCamera(Camera):
self._auth = aiohttp.BasicAuth(
self._username, password=self._password
)
self._verify_ssl = device_info.get(CONF_VERIFY_SSL)
async def async_camera_image(self):
"""Return a still image response from the camera."""
@@ -98,7 +112,10 @@ class MjpegCamera(Camera):
self.camera_image)
return image
websession = async_get_clientsession(self.hass)
websession = async_get_clientsession(
self.hass,
verify_ssl=self._verify_ssl
)
try:
with async_timeout.timeout(10, loop=self.hass.loop):
response = await websession.get(
@@ -121,7 +138,12 @@ class MjpegCamera(Camera):
else:
auth = HTTPBasicAuth(self._username, self._password)
req = requests.get(
self._mjpeg_url, auth=auth, stream=True, timeout=10)
self._mjpeg_url,
auth=auth,
stream=True,
timeout=10,
verify=self._verify_ssl
)
else:
req = requests.get(self._mjpeg_url, stream=True, timeout=10)
@@ -137,7 +159,10 @@ class MjpegCamera(Camera):
return await super().handle_async_mjpeg_stream(request)
# connect to stream
websession = async_get_clientsession(self.hass)
websession = async_get_clientsession(
self.hass,
verify_ssl=self._verify_ssl
)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
return await async_aiohttp_proxy_web(self.hass, request, stream_coro)

View File

@@ -10,14 +10,15 @@ import logging
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_MODE, \
HTTP_HEADER_HA_AUTH
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
from . import async_get_still_stream
from homeassistant.components.camera import async_get_still_stream
REQUIREMENTS = ['pillow==5.2.0']
REQUIREMENTS = ['pillow==5.3.0']
_LOGGER = logging.getLogger(__name__)
@@ -26,21 +27,34 @@ CONF_FORCE_RESIZE = 'force_resize'
CONF_IMAGE_QUALITY = 'image_quality'
CONF_IMAGE_REFRESH_RATE = 'image_refresh_rate'
CONF_MAX_IMAGE_WIDTH = 'max_image_width'
CONF_MAX_IMAGE_HEIGHT = 'max_image_height'
CONF_MAX_STREAM_WIDTH = 'max_stream_width'
CONF_MAX_STREAM_HEIGHT = 'max_stream_height'
CONF_IMAGE_TOP = 'image_top'
CONF_IMAGE_LEFT = 'image_left'
CONF_STREAM_QUALITY = 'stream_quality'
MODE_RESIZE = 'resize'
MODE_CROP = 'crop'
DEFAULT_BASENAME = "Camera Proxy"
DEFAULT_QUALITY = 75
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean,
vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean,
vol.Optional(CONF_MODE, default=MODE_RESIZE):
vol.In([MODE_RESIZE, MODE_CROP]),
vol.Optional(CONF_IMAGE_QUALITY): int,
vol.Optional(CONF_IMAGE_REFRESH_RATE): float,
vol.Optional(CONF_MAX_IMAGE_WIDTH): int,
vol.Optional(CONF_MAX_IMAGE_HEIGHT): int,
vol.Optional(CONF_MAX_STREAM_WIDTH): int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MAX_STREAM_HEIGHT): int,
vol.Optional(CONF_IMAGE_LEFT): int,
vol.Optional(CONF_IMAGE_TOP): int,
vol.Optional(CONF_STREAM_QUALITY): int,
})
@@ -51,26 +65,37 @@ async def async_setup_platform(
async_add_entities([ProxyCamera(hass, config)])
def _precheck_image(image, opts):
"""Perform some pre-checks on the given image."""
from PIL import Image
import io
if not opts:
raise ValueError()
try:
img = Image.open(io.BytesIO(image))
except IOError:
_LOGGER.warning("Failed to open image")
raise ValueError()
imgfmt = str(img.format)
if imgfmt not in ('PNG', 'JPEG'):
_LOGGER.warning("Image is of unsupported type: %s", imgfmt)
raise ValueError()
return img
def _resize_image(image, opts):
"""Resize image."""
from PIL import Image
import io
if not opts:
try:
img = _precheck_image(image, opts)
except ValueError:
return image
quality = opts.quality or DEFAULT_QUALITY
new_width = opts.max_width
try:
img = Image.open(io.BytesIO(image))
except IOError:
return image
imgfmt = str(img.format)
if imgfmt not in ('PNG', 'JPEG'):
_LOGGER.debug("Image is of unsupported type: %s", imgfmt)
return image
(old_width, old_height) = img.size
old_size = len(image)
if old_width <= new_width:
@@ -87,7 +112,7 @@ def _resize_image(image, opts):
img.save(imgbuf, 'JPEG', optimize=True, quality=quality)
newimage = imgbuf.getvalue()
if not opts.force_resize and len(newimage) >= old_size:
_LOGGER.debug("Using original image(%d bytes) "
_LOGGER.debug("Using original image (%d bytes) "
"because resized image (%d bytes) is not smaller",
old_size, len(newimage))
return image
@@ -98,12 +123,50 @@ def _resize_image(image, opts):
return newimage
def _crop_image(image, opts):
"""Crop image."""
import io
try:
img = _precheck_image(image, opts)
except ValueError:
return image
quality = opts.quality or DEFAULT_QUALITY
(old_width, old_height) = img.size
old_size = len(image)
if opts.top is None:
opts.top = 0
if opts.left is None:
opts.left = 0
if opts.max_width is None or opts.max_width > old_width - opts.left:
opts.max_width = old_width - opts.left
if opts.max_height is None or opts.max_height > old_height - opts.top:
opts.max_height = old_height - opts.top
img = img.crop((opts.left, opts.top,
opts.left+opts.max_width, opts.top+opts.max_height))
imgbuf = io.BytesIO()
img.save(imgbuf, 'JPEG', optimize=True, quality=quality)
newimage = imgbuf.getvalue()
_LOGGER.debug(
"Cropped image from (%dx%d - %d bytes) to (%dx%d - %d bytes)",
old_width, old_height, old_size, opts.max_width, opts.max_height,
len(newimage))
return newimage
class ImageOpts():
"""The representation of image options."""
def __init__(self, max_width, quality, force_resize):
def __init__(self, max_width, max_height, left, top,
quality, force_resize):
"""Initialize image options."""
self.max_width = max_width
self.max_height = max_height
self.left = left
self.top = top
self.quality = quality
self.force_resize = force_resize
@@ -125,11 +188,18 @@ class ProxyCamera(Camera):
"{} - {}".format(DEFAULT_BASENAME, self._proxied_camera))
self._image_opts = ImageOpts(
config.get(CONF_MAX_IMAGE_WIDTH),
config.get(CONF_MAX_IMAGE_HEIGHT),
config.get(CONF_IMAGE_LEFT),
config.get(CONF_IMAGE_TOP),
config.get(CONF_IMAGE_QUALITY),
config.get(CONF_FORCE_RESIZE))
self._stream_opts = ImageOpts(
config.get(CONF_MAX_STREAM_WIDTH), config.get(CONF_STREAM_QUALITY),
config.get(CONF_MAX_STREAM_WIDTH),
config.get(CONF_MAX_STREAM_HEIGHT),
config.get(CONF_IMAGE_LEFT),
config.get(CONF_IMAGE_TOP),
config.get(CONF_STREAM_QUALITY),
True)
self._image_refresh_rate = config.get(CONF_IMAGE_REFRESH_RATE)
@@ -141,6 +211,7 @@ class ProxyCamera(Camera):
self._headers = (
{HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password}
if self.hass.config.api.api_password is not None else None)
self._mode = config.get(CONF_MODE)
def camera_image(self):
"""Return camera image."""
@@ -162,8 +233,12 @@ class ProxyCamera(Camera):
_LOGGER.error("Error getting original camera image")
return self._last_image
image = await self.hass.async_add_job(
_resize_image, image.content, self._image_opts)
if self._mode == MODE_RESIZE:
job = _resize_image
else:
job = _crop_image
image = await self.hass.async_add_executor_job(
job, image.content, self._image_opts)
if self._cache_images:
self._last_image = image
@@ -192,7 +267,11 @@ class ProxyCamera(Camera):
if not image:
return None
except HomeAssistantError:
raise asyncio.CancelledError
raise asyncio.CancelledError()
return await self.hass.async_add_job(
_resize_image, image.content, self._stream_opts)
if self._mode == MODE_RESIZE:
job = _resize_image
else:
job = _crop_image
return await self.hass.async_add_executor_job(
job, image.content, self._stream_opts)

View File

@@ -5,35 +5,33 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/camera.push/
"""
import logging
import asyncio
from collections import deque
from datetime import timedelta
import voluptuous as vol
import aiohttp
import async_timeout
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\
STATE_IDLE, STATE_RECORDING
STATE_IDLE, STATE_RECORDING, DOMAIN
from homeassistant.core import callback
from homeassistant.components.http.view import KEY_AUTHENTICATED,\
HomeAssistantView
from homeassistant.const import CONF_NAME, CONF_TIMEOUT,\
HTTP_NOT_FOUND, HTTP_UNAUTHORIZED, HTTP_BAD_REQUEST
from homeassistant.const import CONF_NAME, CONF_TIMEOUT, CONF_WEBHOOK_ID
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_track_point_in_utc_time
import homeassistant.util.dt as dt_util
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['webhook']
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
CONF_BUFFER_SIZE = 'buffer'
CONF_IMAGE_FIELD = 'field'
CONF_TOKEN = 'token'
DEFAULT_NAME = "Push Camera"
ATTR_FILENAME = 'filename'
ATTR_LAST_TRIP = 'last_trip'
ATTR_TOKEN = 'token'
PUSH_CAMERA_DATA = 'push_camera'
@@ -43,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_TIMEOUT, default=timedelta(seconds=5)): vol.All(
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_IMAGE_FIELD, default='image'): cv.string,
vol.Optional(CONF_TOKEN): vol.All(cv.string, vol.Length(min=8)),
vol.Required(CONF_WEBHOOK_ID): cv.string,
})
@@ -53,69 +51,43 @@ async def async_setup_platform(hass, config, async_add_entities,
if PUSH_CAMERA_DATA not in hass.data:
hass.data[PUSH_CAMERA_DATA] = {}
cameras = [PushCamera(config[CONF_NAME],
webhook_id = config.get(CONF_WEBHOOK_ID)
cameras = [PushCamera(hass,
config[CONF_NAME],
config[CONF_BUFFER_SIZE],
config[CONF_TIMEOUT],
config.get(CONF_TOKEN))]
hass.http.register_view(CameraPushReceiver(hass,
config[CONF_IMAGE_FIELD]))
config[CONF_IMAGE_FIELD],
webhook_id)]
async_add_entities(cameras)
class CameraPushReceiver(HomeAssistantView):
"""Handle pushes from remote camera."""
async def handle_webhook(hass, webhook_id, request):
"""Handle incoming webhook POST with image files."""
try:
with async_timeout.timeout(5, loop=hass.loop):
data = dict(await request.post())
except (asyncio.TimeoutError, aiohttp.web.HTTPException) as error:
_LOGGER.error("Could not get information from POST <%s>", error)
return
url = "/api/camera_push/{entity_id}"
name = 'api:camera_push:camera_entity'
requires_auth = False
camera = hass.data[PUSH_CAMERA_DATA][webhook_id]
def __init__(self, hass, image_field):
"""Initialize CameraPushReceiver with camera entity."""
self._cameras = hass.data[PUSH_CAMERA_DATA]
self._image = image_field
if camera.image_field not in data:
_LOGGER.warning("Webhook call without POST parameter <%s>",
camera.image_field)
return
async def post(self, request, entity_id):
"""Accept the POST from Camera."""
_camera = self._cameras.get(entity_id)
if _camera is None:
_LOGGER.error("Unknown %s", entity_id)
status = HTTP_NOT_FOUND if request[KEY_AUTHENTICATED]\
else HTTP_UNAUTHORIZED
return self.json_message('Unknown {}'.format(entity_id),
status)
# Supports HA authentication and token based
# when token has been configured
authenticated = (request[KEY_AUTHENTICATED] or
(_camera.token is not None and
request.query.get('token') == _camera.token))
if not authenticated:
return self.json_message(
'Invalid authorization credentials for {}'.format(entity_id),
HTTP_UNAUTHORIZED)
try:
data = await request.post()
_LOGGER.debug("Received Camera push: %s", data[self._image])
await _camera.update_image(data[self._image].file.read(),
data[self._image].filename)
except ValueError as value_error:
_LOGGER.error("Unknown value %s", value_error)
return self.json_message('Invalid POST', HTTP_BAD_REQUEST)
except KeyError as key_error:
_LOGGER.error('In your POST message %s', key_error)
return self.json_message('{} missing'.format(self._image),
HTTP_BAD_REQUEST)
await camera.update_image(data[camera.image_field].file.read(),
data[camera.image_field].filename)
class PushCamera(Camera):
"""The representation of a Push camera."""
def __init__(self, name, buffer_size, timeout, token):
def __init__(self, hass, name, buffer_size, timeout, image_field,
webhook_id):
"""Initialize push camera component."""
super().__init__()
self._name = name
@@ -126,11 +98,28 @@ class PushCamera(Camera):
self._timeout = timeout
self.queue = deque([], buffer_size)
self._current_image = None
self.token = token
self._image_field = image_field
self.webhook_id = webhook_id
self.webhook_url = \
hass.components.webhook.async_generate_url(webhook_id)
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[PUSH_CAMERA_DATA][self.entity_id] = self
self.hass.data[PUSH_CAMERA_DATA][self.webhook_id] = self
try:
self.hass.components.webhook.async_register(DOMAIN,
self.name,
self.webhook_id,
handle_webhook)
except ValueError:
_LOGGER.error("In <%s>, webhook_id <%s> already used",
self.name, self.webhook_id)
@property
def image_field(self):
"""HTTP field containing the image file."""
return self._image_field
@property
def state(self):
@@ -189,6 +178,5 @@ class PushCamera(Camera):
name: value for name, value in (
(ATTR_LAST_TRIP, self._last_trip),
(ATTR_FILENAME, self._filename),
(ATTR_TOKEN, self.token),
) if value is not None
}

View File

@@ -8,6 +8,11 @@ from datetime import timedelta
import logging
import requests
import voluptuous as vol
from homeassistant.components.camera import PLATFORM_SCHEMA
from homeassistant.const import CONF_MONITORED_CONDITIONS
import homeassistant.helpers.config_validation as cv
from homeassistant.components.camera import Camera
from homeassistant.components.skybell import (
@@ -19,14 +24,33 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
IMAGE_AVATAR = 'avatar'
IMAGE_ACTIVITY = 'activity'
CONF_ACTIVITY_NAME = 'activity_name'
CONF_AVATAR_NAME = 'avatar_name'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=[IMAGE_AVATAR]):
vol.All(cv.ensure_list, [vol.In([IMAGE_AVATAR, IMAGE_ACTIVITY])]),
vol.Optional(CONF_ACTIVITY_NAME): cv.string,
vol.Optional(CONF_AVATAR_NAME): cv.string,
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the platform for a Skybell device."""
cond = config[CONF_MONITORED_CONDITIONS]
names = {}
names[IMAGE_ACTIVITY] = config.get(CONF_ACTIVITY_NAME)
names[IMAGE_AVATAR] = config.get(CONF_AVATAR_NAME)
skybell = hass.data.get(SKYBELL_DOMAIN)
sensors = []
for device in skybell.get_devices():
sensors.append(SkybellCamera(device))
for camera_type in cond:
sensors.append(SkybellCamera(device, camera_type,
names.get(camera_type)))
add_entities(sensors, True)
@@ -34,11 +58,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class SkybellCamera(SkybellDevice, Camera):
"""A camera implementation for Skybell devices."""
def __init__(self, device):
def __init__(self, device, camera_type, name=None):
"""Initialize a camera for a Skybell device."""
self._type = camera_type
SkybellDevice.__init__(self, device)
Camera.__init__(self)
self._name = self._device.name
if name is not None:
self._name = "{} {}".format(self._device.name, name)
else:
self._name = self._device.name
self._url = None
self._response = None
@@ -47,12 +75,19 @@ class SkybellCamera(SkybellDevice, Camera):
"""Return the name of the sensor."""
return self._name
@property
def image_url(self):
"""Get the camera image url based on type."""
if self._type == IMAGE_ACTIVITY:
return self._device.activity_image
return self._device.image
def camera_image(self):
"""Get the latest camera image."""
super().update()
if self._url != self._device.image:
self._url = self._device.image
if self._url != self.image_url:
self._url = self.image_url
try:
self._response = requests.get(

View File

@@ -10,12 +10,12 @@ import socket
import requests
import voluptuous as vol
from homeassistant.const import CONF_PORT
from homeassistant.const import CONF_PORT, CONF_SSL
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['uvcclient==0.10.1']
REQUIREMENTS = ['uvcclient==0.11.0']
_LOGGER = logging.getLogger(__name__)
@@ -25,12 +25,14 @@ CONF_PASSWORD = 'password'
DEFAULT_PASSWORD = 'ubnt'
DEFAULT_PORT = 7080
DEFAULT_SSL = False
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NVR): cv.string,
vol.Required(CONF_KEY): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
})
@@ -40,11 +42,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
key = config[CONF_KEY]
password = config[CONF_PASSWORD]
port = config[CONF_PORT]
ssl = config[CONF_SSL]
from uvcclient import nvr
try:
# Exceptions may be raised in all method calls to the nvr library.
nvrconn = nvr.UVCRemote(addr, port, key)
nvrconn = nvr.UVCRemote(addr, port, key, ssl=ssl)
cameras = nvrconn.index()
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'

View File

@@ -17,7 +17,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['aioftp==0.10.1']
REQUIREMENTS = ['aioftp==0.12.0']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)

View File

@@ -6,9 +6,9 @@ https://home-assistant.io/components/camera.zoneminder/
"""
import logging
from homeassistant.const import CONF_NAME
from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera, filter_urllib3_logging)
from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -18,6 +18,7 @@ DEPENDENCIES = ['zoneminder']
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the ZoneMinder cameras."""
filter_urllib3_logging()
zm_client = hass.data[ZONEMINDER_DOMAIN]
monitors = zm_client.get_monitors()
@@ -28,22 +29,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
cameras = []
for monitor in monitors:
_LOGGER.info("Initializing camera %s", monitor.id)
cameras.append(ZoneMinderCamera(monitor))
cameras.append(ZoneMinderCamera(monitor, zm_client.verify_ssl))
add_entities(cameras)
class ZoneMinderCamera(MjpegCamera):
"""Representation of a ZoneMinder Monitor Stream."""
def __init__(self, monitor):
def __init__(self, monitor, verify_ssl):
"""Initialize as a subclass of MjpegCamera."""
device_info = {
CONF_NAME: monitor.name,
CONF_MJPEG_URL: monitor.mjpeg_image_url,
CONF_STILL_IMAGE_URL: monitor.still_image_url
CONF_STILL_IMAGE_URL: monitor.still_image_url,
CONF_VERIFY_SSL: verify_ssl
}
super().__init__(device_info)
self._is_recording = None
self._is_available = None
self._monitor = monitor
@property
@@ -55,8 +58,14 @@ class ZoneMinderCamera(MjpegCamera):
"""Update our recording state from the ZM API."""
_LOGGER.debug("Updating camera state for monitor %i", self._monitor.id)
self._is_recording = self._monitor.is_recording
self._is_available = self._monitor.is_available
@property
def is_recording(self):
"""Return whether the monitor is in alarm mode."""
return self._is_recording
@property
def available(self):
"""Return True if entity is available."""
return self._is_available

View File

@@ -6,7 +6,7 @@
},
"step": {
"confirm": {
"description": "Voleu configurar Google Cast?",
"description": "Vols configurar Google Cast?",
"title": "Google Cast"
}
},

View File

@@ -1,7 +1,8 @@
{
"config": {
"abort": {
"no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton."
"no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton.",
"single_instance_allowed": "Csak egyetlen Google Cast konfigur\u00e1ci\u00f3 sz\u00fcks\u00e9ges."
},
"step": {
"confirm": {

View File

@@ -92,15 +92,15 @@ CONVERTIBLE_ATTRIBUTE = [
_LOGGER = logging.getLogger(__name__)
ON_OFF_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
})
SET_AWAY_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AWAY_MODE): cv.boolean,
})
SET_AUX_HEAT_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_AUX_HEAT): cv.boolean,
})
SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
@@ -110,28 +110,28 @@ SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All(
vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float),
vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float),
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_OPERATION_MODE): cv.string,
}
))
SET_FAN_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_FAN_MODE): cv.string,
})
SET_HOLD_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_HOLD_MODE): cv.string,
})
SET_OPERATION_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_OPERATION_MODE): cv.string,
})
SET_HUMIDITY_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_HUMIDITY): vol.Coerce(float),
})
SET_SWING_MODE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_SWING_MODE): cv.string,
})

View File

@@ -15,14 +15,13 @@ from homeassistant.components.climate import (
STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
from homeassistant.components.daikin import (
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE,
daikin_api_setup)
from homeassistant.components.daikin import DOMAIN as DAIKIN_DOMAIN
from homeassistant.components.daikin.const import (
ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pydaikin==0.8']
_LOGGER = logging.getLogger(__name__)
@@ -60,18 +59,18 @@ HA_ATTR_TO_DAIKIN = {
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Daikin HVAC platform."""
if discovery_info is not None:
host = discovery_info.get('ip')
name = None
_LOGGER.debug("Discovered a Daikin AC on %s", host)
else:
host = config.get(CONF_HOST)
name = config.get(CONF_NAME)
_LOGGER.debug("Added Daikin AC on %s", host)
"""Old way of setting up the Daikin HVAC platform.
api = daikin_api_setup(hass, host, name)
add_entities([DaikinClimate(api)], True)
Can only be called when a user accidentally mentions the platform in their
config. But even in that case it would have been ignored.
"""
pass
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up Daikin climate based on config_entry."""
daikin_api = hass.data[DAIKIN_DOMAIN].get(entry.entry_id)
async_add_entities([DaikinClimate(daikin_api)])
class DaikinClimate(ClimateDevice):
@@ -192,6 +191,11 @@ class DaikinClimate(ClimateDevice):
"""Return the name of the thermostat, if any."""
return self._api.name
@property
def unique_id(self):
"""Return a unique ID."""
return self._api.mac
@property
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
@@ -261,3 +265,8 @@ class DaikinClimate(ClimateDevice):
def update(self):
"""Retrieve latest state."""
self._api.update()
@property
def device_info(self):
"""Return a device description for device registry."""
return self._api.device_info

View File

@@ -9,7 +9,8 @@ import logging
import voluptuous as vol
from homeassistant.components.climate import (
STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice,
STATE_ON, STATE_OFF, STATE_HEAT, STATE_MANUAL, STATE_ECO, PLATFORM_SCHEMA,
ClimateDevice,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE,
SUPPORT_ON_OFF)
from homeassistant.const import (
@@ -21,8 +22,6 @@ REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.45']
_LOGGER = logging.getLogger(__name__)
STATE_BOOST = 'boost'
STATE_AWAY = 'away'
STATE_MANUAL = 'manual'
ATTR_STATE_WINDOW_OPEN = 'window_open'
ATTR_STATE_VALVE = 'valve'
@@ -65,10 +64,10 @@ class EQ3BTSmartThermostat(ClimateDevice):
self.modes = {
eq3.Mode.Open: STATE_ON,
eq3.Mode.Closed: STATE_OFF,
eq3.Mode.Auto: STATE_AUTO,
eq3.Mode.Auto: STATE_HEAT,
eq3.Mode.Manual: STATE_MANUAL,
eq3.Mode.Boost: STATE_BOOST,
eq3.Mode.Away: STATE_AWAY,
eq3.Mode.Away: STATE_ECO,
}
self.reverse_modes = {v: k for k, v in self.modes.items()}
@@ -140,20 +139,20 @@ class EQ3BTSmartThermostat(ClimateDevice):
def turn_away_mode_off(self):
"""Away mode off turns to AUTO mode."""
self.set_operation_mode(STATE_AUTO)
self.set_operation_mode(STATE_HEAT)
def turn_away_mode_on(self):
"""Set away mode on."""
self.set_operation_mode(STATE_AWAY)
self.set_operation_mode(STATE_ECO)
@property
def is_away_mode_on(self):
"""Return if we are away."""
return self.current_operation == STATE_AWAY
return self.current_operation == STATE_ECO
def turn_on(self):
"""Turn device on."""
self.set_operation_mode(STATE_AUTO)
self.set_operation_mode(STATE_HEAT)
def turn_off(self):
"""Turn device off."""

View File

@@ -1,7 +1,7 @@
"""Support for Honeywell evohome (EMEA/EU-based systems only).
"""Support for Climate devices of (EMEA/EU-based) Honeywell evohome systems.
Support for a temperature control system (TCS, controller) with 0+ heating
zones (e.g. TRVs, relays) and, optionally, a DHW controller.
zones (e.g. TRVs, relays).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.evohome/
@@ -13,29 +13,34 @@ import logging
from requests.exceptions import HTTPError
from homeassistant.components.climate import (
ClimateDevice,
STATE_AUTO,
STATE_ECO,
STATE_OFF,
SUPPORT_OPERATION_MODE,
STATE_AUTO, STATE_ECO, STATE_MANUAL, STATE_OFF,
SUPPORT_AWAY_MODE,
SUPPORT_ON_OFF,
SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE,
ClimateDevice
)
from homeassistant.components.evohome import (
CONF_LOCATION_IDX,
DATA_EVOHOME,
MAX_TEMP,
MIN_TEMP,
SCAN_INTERVAL_MAX
DATA_EVOHOME, DISPATCHER_EVOHOME,
CONF_LOCATION_IDX, SCAN_INTERVAL_DEFAULT,
EVO_PARENT, EVO_CHILD,
GWS, TCS,
)
from homeassistant.const import (
CONF_SCAN_INTERVAL,
PRECISION_TENTHS,
TEMP_CELSIUS,
HTTP_TOO_MANY_REQUESTS,
PRECISION_HALVES,
TEMP_CELSIUS
)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import (
dispatcher_send,
async_dispatcher_connect
)
_LOGGER = logging.getLogger(__name__)
# these are for the controller's opmode/state and the zone's state
# the Controller's opmode/state and the zone's (inherited) state
EVO_RESET = 'AutoWithReset'
EVO_AUTO = 'Auto'
EVO_AUTOECO = 'AutoWithEco'
@@ -44,7 +49,14 @@ EVO_DAYOFF = 'DayOff'
EVO_CUSTOM = 'Custom'
EVO_HEATOFF = 'HeatingOff'
EVO_STATE_TO_HA = {
# these are for Zones' opmode, and state
EVO_FOLLOW = 'FollowSchedule'
EVO_TEMPOVER = 'TemporaryOverride'
EVO_PERMOVER = 'PermanentOverride'
# for the Controller. NB: evohome treats Away mode as a mode in/of itself,
# where HA considers it to 'override' the exising operating mode
TCS_STATE_TO_HA = {
EVO_RESET: STATE_AUTO,
EVO_AUTO: STATE_AUTO,
EVO_AUTOECO: STATE_ECO,
@@ -53,171 +65,150 @@ EVO_STATE_TO_HA = {
EVO_CUSTOM: STATE_AUTO,
EVO_HEATOFF: STATE_OFF
}
HA_STATE_TO_EVO = {
HA_STATE_TO_TCS = {
STATE_AUTO: EVO_AUTO,
STATE_ECO: EVO_AUTOECO,
STATE_OFF: EVO_HEATOFF
}
TCS_OP_LIST = list(HA_STATE_TO_TCS)
HA_OP_LIST = list(HA_STATE_TO_EVO)
# the Zones' opmode; their state is usually 'inherited' from the TCS
EVO_FOLLOW = 'FollowSchedule'
EVO_TEMPOVER = 'TemporaryOverride'
EVO_PERMOVER = 'PermanentOverride'
# these are used to help prevent E501 (line too long) violations
GWS = 'gateways'
TCS = 'temperatureControlSystems'
# debug codes - these happen occasionally, but the cause is unknown
EVO_DEBUG_NO_RECENT_UPDATES = '0x01'
EVO_DEBUG_NO_STATUS = '0x02'
# for the Zones...
ZONE_STATE_TO_HA = {
EVO_FOLLOW: STATE_AUTO,
EVO_TEMPOVER: STATE_MANUAL,
EVO_PERMOVER: STATE_MANUAL
}
HA_STATE_TO_ZONE = {
STATE_AUTO: EVO_FOLLOW,
STATE_MANUAL: EVO_PERMOVER
}
ZONE_OP_LIST = list(HA_STATE_TO_ZONE)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Create a Honeywell (EMEA/EU) evohome CH/DHW system.
An evohome system consists of: a controller, with 0-12 heating zones (e.g.
TRVs, relays) and, optionally, a DHW controller (a HW boiler).
Here, we add the controller only.
"""
async def async_setup_platform(hass, hass_config, async_add_entities,
discovery_info=None):
"""Create the evohome Controller, and its Zones, if any."""
evo_data = hass.data[DATA_EVOHOME]
client = evo_data['client']
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
# evohomeclient has no defined way of accessing non-default location other
# than using a protected member, such as below
# evohomeclient has exposed no means of accessing non-default location
# (i.e. loc_idx > 0) other than using a protected member, such as below
tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa E501; pylint: disable=protected-access
_LOGGER.debug(
"setup_platform(): Found Controller: id: %s [%s], type: %s",
"setup_platform(): Found Controller, id=%s [%s], "
"name=%s (location_idx=%s)",
tcs_obj_ref.systemId,
tcs_obj_ref.modelType,
tcs_obj_ref.location.name,
tcs_obj_ref.modelType
loc_idx
)
parent = EvoController(evo_data, client, tcs_obj_ref)
add_entities([parent], update_before_add=True)
controller = EvoController(evo_data, client, tcs_obj_ref)
zones = []
for zone_idx in tcs_obj_ref.zones:
zone_obj_ref = tcs_obj_ref.zones[zone_idx]
_LOGGER.debug(
"setup_platform(): Found Zone, id=%s [%s], "
"name=%s",
zone_obj_ref.zoneId,
zone_obj_ref.zone_type,
zone_obj_ref.name
)
zones.append(EvoZone(evo_data, client, zone_obj_ref))
entities = [controller] + zones
async_add_entities(entities, update_before_add=False)
class EvoController(ClimateDevice):
"""Base for a Honeywell evohome hub/Controller device.
class EvoClimateDevice(ClimateDevice):
"""Base for a Honeywell evohome Climate device."""
The Controller (aka TCS, temperature control system) is the parent of all
the child (CH/DHW) devices.
"""
# pylint: disable=no-member
def __init__(self, evo_data, client, obj_ref):
"""Initialize the evohome entity.
Most read-only properties are set here. So are pseudo read-only,
for example name (which _could_ change between update()s).
"""
self.client = client
"""Initialize the evohome entity."""
self._client = client
self._obj = obj_ref
self._id = obj_ref.systemId
self._name = evo_data['config']['locationInfo']['name']
self._config = evo_data['config'][GWS][0][TCS][0]
self._params = evo_data['params']
self._timers = evo_data['timers']
self._timers['statusUpdated'] = datetime.min
self._status = {}
self._available = False # should become True after first update()
def _handle_requests_exceptions(self, err):
# evohomeclient v2 api (>=0.2.7) exposes requests exceptions, incl.:
# - HTTP_BAD_REQUEST, is usually Bad user credentials
# - HTTP_TOO_MANY_REQUESTS, is api usuage limit exceeded
# - HTTP_SERVICE_UNAVAILABLE, is often Vendor's fault
async def async_added_to_hass(self):
"""Run when entity about to be added."""
async_dispatcher_connect(self.hass, DISPATCHER_EVOHOME, self._connect)
@callback
def _connect(self, packet):
if packet['to'] & self._type and packet['signal'] == 'refresh':
self.async_schedule_update_ha_state(force_refresh=True)
def _handle_requests_exceptions(self, err):
if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
# execute a back off: pause, and reduce rate
old_scan_interval = self._params[CONF_SCAN_INTERVAL]
new_scan_interval = min(old_scan_interval * 2, SCAN_INTERVAL_MAX)
self._params[CONF_SCAN_INTERVAL] = new_scan_interval
# execute a backoff: pause, and also reduce rate
old_interval = self._params[CONF_SCAN_INTERVAL]
new_interval = min(old_interval, SCAN_INTERVAL_DEFAULT) * 2
self._params[CONF_SCAN_INTERVAL] = new_interval
_LOGGER.warning(
"API rate limit has been exceeded: increasing '%s' from %s to "
"%s seconds, and suspending polling for %s seconds.",
"API rate limit has been exceeded. Suspending polling for %s "
"seconds, and increasing '%s' from %s to %s seconds.",
new_interval * 3,
CONF_SCAN_INTERVAL,
old_scan_interval,
new_scan_interval,
new_scan_interval * 3
old_interval,
new_interval,
)
self._timers['statusUpdated'] = datetime.now() + \
timedelta(seconds=new_scan_interval * 3)
self._timers['statusUpdated'] = datetime.now() + new_interval * 3
else:
raise err
raise err # we dont handle any other HTTPErrors
@property
def name(self):
def name(self) -> str:
"""Return the name to use in the frontend UI."""
return self._name
@property
def available(self):
"""Return True if the device is available.
def icon(self):
"""Return the icon to use in the frontend UI."""
return self._icon
All evohome entities are initially unavailable. Once HA has started,
state data is then retrieved by the Controller, and then the children
will get a state (e.g. operating_mode, current_temperature).
@property
def device_state_attributes(self):
"""Return the device state attributes of the evohome Climate device.
However, evohome entities can become unavailable for other reasons.
This is state data that is not available otherwise, due to the
restrictions placed upon ClimateDevice properties, etc. by HA.
"""
return {'status': self._status}
@property
def available(self) -> bool:
"""Return True if the device is currently available."""
return self._available
@property
def supported_features(self):
"""Get the list of supported features of the Controller."""
return SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE
@property
def device_state_attributes(self):
"""Return the device state attributes of the controller.
This is operating mode state data that is not available otherwise, due
to the restrictions placed upon ClimateDevice properties, etc by HA.
"""
data = {}
data['systemMode'] = self._status['systemModeStatus']['mode']
data['isPermanent'] = self._status['systemModeStatus']['isPermanent']
if 'timeUntil' in self._status['systemModeStatus']:
data['timeUntil'] = self._status['systemModeStatus']['timeUntil']
data['activeFaults'] = self._status['activeFaults']
return data
"""Get the list of supported features of the device."""
return self._supported_features
@property
def operation_list(self):
"""Return the list of available operations."""
return HA_OP_LIST
@property
def current_operation(self):
"""Return the operation mode of the evohome entity."""
return EVO_STATE_TO_HA.get(self._status['systemModeStatus']['mode'])
@property
def target_temperature(self):
"""Return the average target temperature of the Heating/DHW zones."""
temps = [zone['setpointStatus']['targetHeatTemperature']
for zone in self._status['zones']]
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
return avg_temp
@property
def current_temperature(self):
"""Return the average current temperature of the Heating/DHW zones."""
tmp_list = [x for x in self._status['zones']
if x['temperatureStatus']['isAvailable'] is True]
temps = [zone['temperatureStatus']['temperature'] for zone in tmp_list]
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
return avg_temp
return self._operation_list
@property
def temperature_unit(self):
@@ -227,47 +218,313 @@ class EvoController(ClimateDevice):
@property
def precision(self):
"""Return the temperature precision to use in the frontend UI."""
return PRECISION_TENTHS
return PRECISION_HALVES
class EvoZone(EvoClimateDevice):
"""Base for a Honeywell evohome Zone device."""
def __init__(self, evo_data, client, obj_ref):
"""Initialize the evohome Zone."""
super().__init__(evo_data, client, obj_ref)
self._id = obj_ref.zoneId
self._name = obj_ref.name
self._icon = "mdi:radiator"
self._type = EVO_CHILD
for _zone in evo_data['config'][GWS][0][TCS][0]['zones']:
if _zone['zoneId'] == self._id:
self._config = _zone
break
self._status = {}
self._operation_list = ZONE_OP_LIST
self._supported_features = \
SUPPORT_OPERATION_MODE | \
SUPPORT_TARGET_TEMPERATURE | \
SUPPORT_ON_OFF
@property
def min_temp(self):
"""Return the minimum target temp (setpoint) of a evohome entity."""
return MIN_TEMP
"""Return the minimum target temperature of a evohome Zone.
The default is 5 (in Celsius), but it is configurable within 5-35.
"""
return self._config['setpointCapabilities']['minHeatSetpoint']
@property
def max_temp(self):
"""Return the maximum target temp (setpoint) of a evohome entity."""
return MAX_TEMP
"""Return the minimum target temperature of a evohome Zone.
The default is 35 (in Celsius), but it is configurable within 5-35.
"""
return self._config['setpointCapabilities']['maxHeatSetpoint']
@property
def is_on(self):
"""Return true as evohome controllers are always on.
def target_temperature(self):
"""Return the target temperature of the evohome Zone."""
return self._status['setpointStatus']['targetHeatTemperature']
Operating modes can include 'HeatingOff', but (for example) DHW would
remain on.
@property
def current_temperature(self):
"""Return the current temperature of the evohome Zone."""
return self._status['temperatureStatus']['temperature']
@property
def current_operation(self):
"""Return the current operating mode of the evohome Zone.
The evohome Zones that are in 'FollowSchedule' mode inherit their
actual operating mode from the Controller.
"""
evo_data = self.hass.data[DATA_EVOHOME]
system_mode = evo_data['status']['systemModeStatus']['mode']
setpoint_mode = self._status['setpointStatus']['setpointMode']
if setpoint_mode == EVO_FOLLOW:
# then inherit state from the controller
if system_mode == EVO_RESET:
current_operation = TCS_STATE_TO_HA.get(EVO_AUTO)
else:
current_operation = TCS_STATE_TO_HA.get(system_mode)
else:
current_operation = ZONE_STATE_TO_HA.get(setpoint_mode)
return current_operation
@property
def is_on(self) -> bool:
"""Return True if the evohome Zone is off.
A Zone is considered off if its target temp is set to its minimum, and
it is not following its schedule (i.e. not in 'FollowSchedule' mode).
"""
is_off = \
self.target_temperature == self.min_temp and \
self._status['setpointStatus']['setpointMode'] == EVO_PERMOVER
return not is_off
def _set_temperature(self, temperature, until=None):
"""Set the new target temperature of a Zone.
temperature is required, until can be:
- strftime('%Y-%m-%dT%H:%M:%SZ') for TemporaryOverride, or
- None for PermanentOverride (i.e. indefinitely)
"""
try:
self._obj.set_temperature(temperature, until)
except HTTPError as err:
self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member
def set_temperature(self, **kwargs):
"""Set new target temperature, indefinitely."""
self._set_temperature(kwargs['temperature'], until=None)
def turn_on(self):
"""Turn the evohome Zone on.
This is achieved by setting the Zone to its 'FollowSchedule' mode.
"""
self._set_operation_mode(EVO_FOLLOW)
def turn_off(self):
"""Turn the evohome Zone off.
This is achieved by setting the Zone to its minimum temperature,
indefinitely (i.e. 'PermanentOverride' mode).
"""
self._set_temperature(self.min_temp, until=None)
def set_operation_mode(self, operation_mode):
"""Set an operating mode for a Zone.
Currently limited to 'Auto' & 'Manual'. If 'Off' is needed, it can be
enabled via turn_off method.
NB: evohome Zones do not have an operating mode as understood by HA.
Instead they usually 'inherit' an operating mode from their controller.
More correctly, these Zones are in a follow mode, 'FollowSchedule',
where their setpoint temperatures are a function of their schedule, and
the Controller's operating_mode, e.g. Economy mode is their scheduled
setpoint less (usually) 3C.
Thus, you cannot set a Zone to Away mode, but the location (i.e. the
Controller) is set to Away and each Zones's setpoints are adjusted
accordingly to some lower temperature.
However, Zones can override these setpoints, either for a specified
period of time, 'TemporaryOverride', after which they will revert back
to 'FollowSchedule' mode, or indefinitely, 'PermanentOverride'.
"""
self._set_operation_mode(HA_STATE_TO_ZONE.get(operation_mode))
def _set_operation_mode(self, operation_mode):
if operation_mode == EVO_FOLLOW:
try:
self._obj.cancel_temp_override(self._obj)
except HTTPError as err:
self._handle_exception("HTTPError", str(err)) # noqa: E501; pylint: disable=no-member
elif operation_mode == EVO_TEMPOVER:
_LOGGER.error(
"_set_operation_mode(op_mode=%s): mode not yet implemented",
operation_mode
)
elif operation_mode == EVO_PERMOVER:
self._set_temperature(self.target_temperature, until=None)
else:
_LOGGER.error(
"_set_operation_mode(op_mode=%s): mode not valid",
operation_mode
)
@property
def should_poll(self) -> bool:
"""Return False as evohome child devices should never be polled.
The evohome Controller will inform its children when to update().
"""
return False
def update(self):
"""Process the evohome Zone's state data."""
evo_data = self.hass.data[DATA_EVOHOME]
for _zone in evo_data['status']['zones']:
if _zone['zoneId'] == self._id:
self._status = _zone
break
self._available = True
class EvoController(EvoClimateDevice):
"""Base for a Honeywell evohome hub/Controller device.
The Controller (aka TCS, temperature control system) is the parent of all
the child (CH/DHW) devices. It is also a Climate device.
"""
def __init__(self, evo_data, client, obj_ref):
"""Initialize the evohome Controller (hub)."""
super().__init__(evo_data, client, obj_ref)
self._id = obj_ref.systemId
self._name = '_{}'.format(obj_ref.location.name)
self._icon = "mdi:thermostat"
self._type = EVO_PARENT
self._config = evo_data['config'][GWS][0][TCS][0]
self._status = evo_data['status']
self._timers['statusUpdated'] = datetime.min
self._operation_list = TCS_OP_LIST
self._supported_features = \
SUPPORT_OPERATION_MODE | \
SUPPORT_AWAY_MODE
@property
def device_state_attributes(self):
"""Return the device state attributes of the evohome Controller.
This is state data that is not available otherwise, due to the
restrictions placed upon ClimateDevice properties, etc. by HA.
"""
status = dict(self._status)
if 'zones' in status:
del status['zones']
if 'dhw' in status:
del status['dhw']
return {'status': status}
@property
def current_operation(self):
"""Return the current operating mode of the evohome Controller."""
return TCS_STATE_TO_HA.get(self._status['systemModeStatus']['mode'])
@property
def min_temp(self):
"""Return the minimum target temperature of a evohome Controller.
Although evohome Controllers do not have a minimum target temp, one is
expected by the HA schema; the default for an evohome HR92 is used.
"""
return 5
@property
def max_temp(self):
"""Return the minimum target temperature of a evohome Controller.
Although evohome Controllers do not have a maximum target temp, one is
expected by the HA schema; the default for an evohome HR92 is used.
"""
return 35
@property
def target_temperature(self):
"""Return the average target temperature of the Heating/DHW zones.
Although evohome Controllers do not have a target temp, one is
expected by the HA schema.
"""
temps = [zone['setpointStatus']['targetHeatTemperature']
for zone in self._status['zones']]
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
return avg_temp
@property
def current_temperature(self):
"""Return the average current temperature of the Heating/DHW zones.
Although evohome Controllers do not have a target temp, one is
expected by the HA schema.
"""
tmp_list = [x for x in self._status['zones']
if x['temperatureStatus']['isAvailable'] is True]
temps = [zone['temperatureStatus']['temperature'] for zone in tmp_list]
avg_temp = round(sum(temps) / len(temps), 1) if temps else None
return avg_temp
@property
def is_on(self) -> bool:
"""Return True as evohome Controllers are always on.
For example, evohome Controllers have a 'HeatingOff' mode, but even
then the DHW would remain on.
"""
return True
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
def is_away_mode_on(self) -> bool:
"""Return True if away mode is on."""
return self._status['systemModeStatus']['mode'] == EVO_AWAY
def turn_away_mode_on(self):
"""Turn away mode on."""
"""Turn away mode on.
The evohome Controller will not remember is previous operating mode.
"""
self._set_operation_mode(EVO_AWAY)
def turn_away_mode_off(self):
"""Turn away mode off."""
"""Turn away mode off.
The evohome Controller can not recall its previous operating mode (as
intimated by the HA schema), so this method is achieved by setting the
Controller's mode back to Auto.
"""
self._set_operation_mode(EVO_AUTO)
def _set_operation_mode(self, operation_mode):
# Set new target operation mode for the TCS.
_LOGGER.debug(
"_set_operation_mode(): API call [1 request(s)]: "
"tcs._set_status(%s)...",
operation_mode
)
try:
self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access
except HTTPError as err:
@@ -279,93 +536,45 @@ class EvoController(ClimateDevice):
Currently limited to 'Auto', 'AutoWithEco' & 'HeatingOff'. If 'Away'
mode is needed, it can be enabled via turn_away_mode_on method.
"""
self._set_operation_mode(HA_STATE_TO_EVO.get(operation_mode))
self._set_operation_mode(HA_STATE_TO_TCS.get(operation_mode))
def _update_state_data(self, evo_data):
client = evo_data['client']
loc_idx = evo_data['params'][CONF_LOCATION_IDX]
_LOGGER.debug(
"_update_state_data(): API call [1 request(s)]: "
"client.locations[loc_idx].status()..."
)
try:
evo_data['status'].update(
client.locations[loc_idx].status()[GWS][0][TCS][0])
except HTTPError as err: # check if we've exceeded the api rate limit
self._handle_requests_exceptions(err)
else:
evo_data['timers']['statusUpdated'] = datetime.now()
_LOGGER.debug(
"_update_state_data(): evo_data['status'] = %s",
evo_data['status']
)
@property
def should_poll(self) -> bool:
"""Return True as the evohome Controller should always be polled."""
return True
def update(self):
"""Get the latest state data of the installation.
"""Get the latest state data of the entire evohome Location.
This includes state data for the Controller and its child devices, such
as the operating_mode of the Controller and the current_temperature
of its children.
This is not asyncio-friendly due to the underlying client api.
This includes state data for the Controller and all its child devices,
such as the operating mode of the Controller and the current temp of
its children (e.g. Zones, DHW controller).
"""
evo_data = self.hass.data[DATA_EVOHOME]
# should the latest evohome state data be retreived this cycle?
timeout = datetime.now() + timedelta(seconds=55)
expired = timeout > self._timers['statusUpdated'] + \
timedelta(seconds=evo_data['params'][CONF_SCAN_INTERVAL])
self._params[CONF_SCAN_INTERVAL]
if not expired:
return
was_available = self._available or \
self._timers['statusUpdated'] == datetime.min
self._update_state_data(evo_data)
self._status = evo_data['status']
if _LOGGER.isEnabledFor(logging.DEBUG):
tmp_dict = dict(self._status)
if 'zones' in tmp_dict:
tmp_dict['zones'] = '...'
if 'dhw' in tmp_dict:
tmp_dict['dhw'] = '...'
_LOGGER.debug(
"update(%s), self._status = %s",
self._id + " [" + self._name + "]",
tmp_dict
)
no_recent_updates = self._timers['statusUpdated'] < datetime.now() - \
timedelta(seconds=self._params[CONF_SCAN_INTERVAL] * 3.1)
if no_recent_updates:
self._available = False
debug_code = EVO_DEBUG_NO_RECENT_UPDATES
elif not self._status:
# unavailable because no status (but how? other than at startup?)
self._available = False
debug_code = EVO_DEBUG_NO_STATUS
# Retreive the latest state data via the client api
loc_idx = self._params[CONF_LOCATION_IDX]
try:
self._status.update(
self._client.locations[loc_idx].status()[GWS][0][TCS][0])
except HTTPError as err: # check if we've exceeded the api rate limit
self._handle_requests_exceptions(err)
else:
self._timers['statusUpdated'] = datetime.now()
self._available = True
if not self._available and was_available:
# only warn if available went from True to False
_LOGGER.warning(
"The entity, %s, has become unavailable, debug code is: %s",
self._id + " [" + self._name + "]",
debug_code
)
_LOGGER.debug(
"_update_state_data(): self._status = %s",
self._status
)
elif self._available and not was_available:
# this isn't the first re-available (e.g. _after_ STARTUP)
_LOGGER.debug(
"The entity, %s, has become available",
self._id + " [" + self._name + "]"
)
# inform the child devices that state data has been updated
pkt = {'sender': 'controller', 'signal': 'refresh', 'to': EVO_CHILD}
dispatcher_send(self.hass, DISPATCHER_EVOHOME, pkt)

View File

@@ -23,7 +23,7 @@ from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.helpers.restore_state import RestoreEntity
_LOGGER = logging.getLogger(__name__)
@@ -96,7 +96,7 @@ async def async_setup_platform(hass, config, async_add_entities,
precision)])
class GenericThermostat(ClimateDevice):
class GenericThermostat(ClimateDevice, RestoreEntity):
"""Representation of a Generic Thermostat device."""
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
@@ -155,8 +155,9 @@ class GenericThermostat(ClimateDevice):
async def async_added_to_hass(self):
"""Run when entity about to be added."""
await super().async_added_to_hass()
# Check If we have an old state
old_state = await async_get_last_state(self.hass, self.entity_id)
old_state = await self.async_get_last_state()
if old_state is not None:
# If we have no initial temperature, restore
if self._target_temp is None:

View File

@@ -50,23 +50,23 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
def update_characteristics(self, characteristics):
"""Synchronise device state with Home Assistant."""
# pylint: disable=import-error
from homekit import CharacteristicsTypes as ctypes
from homekit.models.characteristics import CharacteristicsTypes
for characteristic in characteristics:
ctype = characteristic['type']
if ctype == ctypes.HEATING_COOLING_CURRENT:
if ctype == CharacteristicsTypes.HEATING_COOLING_CURRENT:
self._state = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
if ctype == ctypes.HEATING_COOLING_TARGET:
if ctype == CharacteristicsTypes.HEATING_COOLING_TARGET:
self._chars['target_mode'] = characteristic['iid']
self._features |= SUPPORT_OPERATION_MODE
self._current_mode = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
self._valid_modes = [MODE_HOMEKIT_TO_HASS.get(
mode) for mode in characteristic['valid-values']]
elif ctype == ctypes.TEMPERATURE_CURRENT:
elif ctype == CharacteristicsTypes.TEMPERATURE_CURRENT:
self._current_temp = characteristic['value']
elif ctype == ctypes.TEMPERATURE_TARGET:
elif ctype == CharacteristicsTypes.TEMPERATURE_TARGET:
self._chars['target_temp'] = characteristic['iid']
self._features |= SUPPORT_TARGET_TEMPERATURE
self._target_temp = characteristic['value']

View File

@@ -20,7 +20,7 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE, CONF_REGION)
REQUIREMENTS = ['evohomeclient==0.2.7', 'somecomfort==0.5.2']
REQUIREMENTS = ['evohomeclient==0.2.8', 'somecomfort==0.5.2']
_LOGGER = logging.getLogger(__name__)

View File

@@ -6,14 +6,17 @@ https://home-assistant.io/components/climate.knx/
"""
import voluptuous as vol
from homeassistant.components.climate import (
PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.components.climate import (
PLATFORM_SCHEMA, SUPPORT_ON_OFF, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE, STATE_HEAT,
STATE_IDLE, STATE_MANUAL, STATE_DRY,
STATE_FAN_ONLY, STATE_ECO, ClimateDevice)
from homeassistant.const import (
ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS)
from homeassistant.core import callback
from homeassistant.components.knx import DATA_KNX, ATTR_DISCOVER_DEVICES
CONF_SETPOINT_SHIFT_ADDRESS = 'setpoint_shift_address'
CONF_SETPOINT_SHIFT_STATE_ADDRESS = 'setpoint_shift_state_address'
@@ -26,10 +29,17 @@ CONF_OPERATION_MODE_ADDRESS = 'operation_mode_address'
CONF_OPERATION_MODE_STATE_ADDRESS = 'operation_mode_state_address'
CONF_CONTROLLER_STATUS_ADDRESS = 'controller_status_address'
CONF_CONTROLLER_STATUS_STATE_ADDRESS = 'controller_status_state_address'
CONF_CONTROLLER_MODE_ADDRESS = 'controller_mode_address'
CONF_CONTROLLER_MODE_STATE_ADDRESS = 'controller_mode_state_address'
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = \
'operation_mode_frost_protection_address'
CONF_OPERATION_MODE_NIGHT_ADDRESS = 'operation_mode_night_address'
CONF_OPERATION_MODE_COMFORT_ADDRESS = 'operation_mode_comfort_address'
CONF_OPERATION_MODES = 'operation_modes'
CONF_ON_OFF_ADDRESS = 'on_off_address'
CONF_ON_OFF_STATE_ADDRESS = 'on_off_state_address'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
DEFAULT_NAME = 'KNX Climate'
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
@@ -37,6 +47,21 @@ DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEPENDENCIES = ['knx']
# Map KNX operation modes to HA modes. This list might not be full.
OPERATION_MODES = {
# Map DPT 201.100 HVAC operating modes
"Frost Protection": STATE_MANUAL,
"Night": STATE_IDLE,
"Standby": STATE_ECO,
"Comfort": STATE_HEAT,
# Map DPT 201.104 HVAC control modes
"Fan only": STATE_FAN_ONLY,
"Dehumidification": STATE_DRY
}
OPERATION_MODES_INV = dict((
reversed(item) for item in OPERATION_MODES.items()))
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
@@ -54,9 +79,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODES): vol.All(cv.ensure_list,
[vol.In(OPERATION_MODES)]),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
})
@@ -84,6 +117,30 @@ def async_add_entities_config(hass, config, async_add_entities):
"""Set up climate for KNX platform configured within platform."""
import xknx
climate_mode = xknx.devices.ClimateMode(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME) + " Mode",
group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get(
CONF_CONTROLLER_STATUS_ADDRESS),
group_address_controller_status_state=config.get(
CONF_CONTROLLER_STATUS_STATE_ADDRESS),
group_address_controller_mode=config.get(
CONF_CONTROLLER_MODE_ADDRESS),
group_address_controller_mode_state=config.get(
CONF_CONTROLLER_MODE_STATE_ADDRESS),
group_address_operation_mode_protection=config.get(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS),
group_address_operation_mode_night=config.get(
CONF_OPERATION_MODE_NIGHT_ADDRESS),
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS),
operation_modes=config.get(
CONF_OPERATION_MODES))
hass.data[DATA_KNX].xknx.devices.add(climate_mode)
climate = xknx.devices.Climate(
hass.data[DATA_KNX].xknx,
name=config.get(CONF_NAME),
@@ -96,20 +153,15 @@ def async_add_entities_config(hass, config, async_add_entities):
setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP),
setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX),
setpoint_shift_min=config.get(CONF_SETPOINT_SHIFT_MIN),
group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS),
group_address_controller_status=config.get(
CONF_CONTROLLER_STATUS_ADDRESS),
group_address_controller_status_state=config.get(
CONF_CONTROLLER_STATUS_STATE_ADDRESS),
group_address_operation_mode_protection=config.get(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS),
group_address_operation_mode_night=config.get(
CONF_OPERATION_MODE_NIGHT_ADDRESS),
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS))
group_address_on_off=config.get(
CONF_ON_OFF_ADDRESS),
group_address_on_off_state=config.get(
CONF_ON_OFF_STATE_ADDRESS),
min_temp=config.get(CONF_MIN_TEMP),
max_temp=config.get(CONF_MAX_TEMP),
mode=climate_mode)
hass.data[DATA_KNX].xknx.devices.add(climate)
async_add_entities([KNXClimate(climate)])
@@ -119,26 +171,25 @@ class KNXClimate(ClimateDevice):
def __init__(self, device):
"""Initialize of a KNX climate device."""
self.device = device
self._unit_of_measurement = TEMP_CELSIUS
@property
def supported_features(self):
"""Return the list of supported features."""
support = SUPPORT_TARGET_TEMPERATURE
if self.device.supports_operation_mode:
if self.device.mode.supports_operation_mode:
support |= SUPPORT_OPERATION_MODE
if self.device.supports_on_off:
support |= SUPPORT_ON_OFF
return support
def async_register_callbacks(self):
async def async_added_to_hass(self):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
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."""
@@ -157,7 +208,7 @@ class KNXClimate(ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS
return self._unit_of_measurement
@property
def current_temperature(self):
@@ -195,20 +246,37 @@ class KNXClimate(ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
if self.device.supports_operation_mode:
return self.device.operation_mode.value
if self.device.mode.supports_operation_mode:
return OPERATION_MODES.get(self.device.mode.operation_mode.value)
return None
@property
def operation_list(self):
"""Return the list of available operation modes."""
return [operation_mode.value for
return [OPERATION_MODES.get(operation_mode.value) for
operation_mode in
self.device.get_supported_operation_modes()]
self.device.mode.operation_modes]
async def async_set_operation_mode(self, operation_mode):
"""Set operation mode."""
if self.device.supports_operation_mode:
if self.device.mode.supports_operation_mode:
from xknx.knx import HVACOperationMode
knx_operation_mode = HVACOperationMode(operation_mode)
await self.device.set_operation_mode(knx_operation_mode)
knx_operation_mode = HVACOperationMode(
OPERATION_MODES_INV.get(operation_mode))
await self.device.mode.set_operation_mode(knx_operation_mode)
await self.async_update_ha_state()
@property
def is_on(self):
"""Return true if the device is on."""
if self.device.supports_on_off:
return self.device.is_on
return None
async def async_turn_on(self):
"""Turn on."""
await self.device.turn_on()
async def async_turn_off(self):
"""Turn off."""
await self.device.turn_off()

View File

@@ -19,7 +19,7 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
REQUIREMENTS = ['millheater==0.2.8']
REQUIREMENTS = ['millheater==0.3.3']
_LOGGER = logging.getLogger(__name__)

View File

@@ -18,11 +18,13 @@ from homeassistant.components.climate import (
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
ATTR_TEMPERATURE, CONF_DEVICE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_ON,
STATE_OFF)
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability, MqttDiscoveryUpdate)
MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, subscription)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -77,6 +79,20 @@ CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
CONF_TEMP_STEP = 'temp_step'
CONF_UNIQUE_ID = 'unique_id'
TEMPLATE_KEYS = (
CONF_POWER_STATE_TEMPLATE,
CONF_MODE_STATE_TEMPLATE,
CONF_TEMPERATURE_STATE_TEMPLATE,
CONF_FAN_MODE_STATE_TEMPLATE,
CONF_SWING_MODE_STATE_TEMPLATE,
CONF_AWAY_MODE_STATE_TEMPLATE,
CONF_HOLD_STATE_TEMPLATE,
CONF_AUX_STATE_TEMPLATE,
CONF_CURRENT_TEMPERATURE_TEMPLATE
)
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
@@ -126,8 +142,9 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
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_TEMP_STEP, default=1.0): vol.Coerce(float)
vol.Optional(CONF_TEMP_STEP, default=1.0): vol.Coerce(float),
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -153,124 +170,118 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
async def _async_setup_entity(hass, config, async_add_entities,
discovery_hash=None):
"""Set up the MQTT climate devices."""
template_keys = (
CONF_POWER_STATE_TEMPLATE,
CONF_MODE_STATE_TEMPLATE,
CONF_TEMPERATURE_STATE_TEMPLATE,
CONF_FAN_MODE_STATE_TEMPLATE,
CONF_SWING_MODE_STATE_TEMPLATE,
CONF_AWAY_MODE_STATE_TEMPLATE,
CONF_HOLD_STATE_TEMPLATE,
CONF_AUX_STATE_TEMPLATE,
CONF_CURRENT_TEMPERATURE_TEMPLATE
)
value_templates = {}
if CONF_VALUE_TEMPLATE in config:
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
value_templates = {key: value_template for key in template_keys}
for key in template_keys & config.keys():
value_templates[key] = config.get(key)
value_templates[key].hass = hass
async_add_entities([
MqttClimate(
hass,
config.get(CONF_NAME),
{
key: config.get(key) for key in (
CONF_POWER_COMMAND_TOPIC,
CONF_MODE_COMMAND_TOPIC,
CONF_TEMPERATURE_COMMAND_TOPIC,
CONF_FAN_MODE_COMMAND_TOPIC,
CONF_SWING_MODE_COMMAND_TOPIC,
CONF_AWAY_MODE_COMMAND_TOPIC,
CONF_HOLD_COMMAND_TOPIC,
CONF_AUX_COMMAND_TOPIC,
CONF_POWER_STATE_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_TEMPERATURE_STATE_TOPIC,
CONF_FAN_MODE_STATE_TOPIC,
CONF_SWING_MODE_STATE_TOPIC,
CONF_AWAY_MODE_STATE_TOPIC,
CONF_HOLD_STATE_TOPIC,
CONF_AUX_STATE_TOPIC,
CONF_CURRENT_TEMPERATURE_TOPIC
)
},
value_templates,
config.get(CONF_QOS),
config.get(CONF_RETAIN),
config.get(CONF_MODE_LIST),
config.get(CONF_FAN_MODE_LIST),
config.get(CONF_SWING_MODE_LIST),
config.get(CONF_INITIAL),
False, None, SPEED_LOW,
STATE_OFF, STATE_OFF, False,
config.get(CONF_SEND_IF_OFF),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_MIN_TEMP),
config.get(CONF_MAX_TEMP),
config.get(CONF_TEMP_STEP),
config,
discovery_hash,
)])
class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
ClimateDevice):
"""Representation of an MQTT climate device."""
def __init__(self, hass, name, topic, value_templates, qos, retain,
mode_list, fan_mode_list, swing_mode_list,
target_temperature, away, hold, current_fan_mode,
current_swing_mode, current_operation, aux, send_if_off,
payload_on, payload_off, availability_topic,
payload_available, payload_not_available,
min_temp, max_temp, temp_step, discovery_hash):
def __init__(self, hass, config, discovery_hash):
"""Initialize the climate device."""
self._config = config
self._unique_id = config.get(CONF_UNIQUE_ID)
self._sub_state = None
self.hass = hass
self._topic = None
self._value_templates = None
self._target_temperature = None
self._current_fan_mode = None
self._current_operation = None
self._current_swing_mode = None
self._unit_of_measurement = hass.config.units.temperature_unit
self._away = False
self._hold = None
self._current_temperature = None
self._aux = False
self._setup_from_config(config)
availability_topic = config.get(CONF_AVAILABILITY_TOPIC)
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
self.hass = hass
self._name = name
self._topic = topic
self._value_templates = value_templates
self._qos = qos
self._retain = retain
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)
async def async_added_to_hass(self):
"""Handle being added to home assistant."""
await super().async_added_to_hass()
await self._subscribe_topics()
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
config = PLATFORM_SCHEMA(discovery_payload)
self._config = config
self._setup_from_config(config)
await self.availability_discovery_update(config)
await self._subscribe_topics()
self.async_schedule_update_ha_state()
def _setup_from_config(self, config):
"""(Re)Setup the entity."""
self._topic = {
key: config.get(key) for key in (
CONF_POWER_COMMAND_TOPIC,
CONF_MODE_COMMAND_TOPIC,
CONF_TEMPERATURE_COMMAND_TOPIC,
CONF_FAN_MODE_COMMAND_TOPIC,
CONF_SWING_MODE_COMMAND_TOPIC,
CONF_AWAY_MODE_COMMAND_TOPIC,
CONF_HOLD_COMMAND_TOPIC,
CONF_AUX_COMMAND_TOPIC,
CONF_POWER_STATE_TOPIC,
CONF_MODE_STATE_TOPIC,
CONF_TEMPERATURE_STATE_TOPIC,
CONF_FAN_MODE_STATE_TOPIC,
CONF_SWING_MODE_STATE_TOPIC,
CONF_AWAY_MODE_STATE_TOPIC,
CONF_HOLD_STATE_TOPIC,
CONF_AUX_STATE_TOPIC,
CONF_CURRENT_TEMPERATURE_TOPIC
)
}
# 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._target_temperature = config.get(CONF_INITIAL)
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_fan_mode = SPEED_LOW
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 = temp_step
self._send_if_off = send_if_off
self._payload_on = payload_on
self._payload_off = payload_off
self._min_temp = min_temp
self._max_temp = max_temp
self._discovery_hash = discovery_hash
self._current_swing_mode = STATE_OFF
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = STATE_OFF
self._away = False
self._hold = None
self._aux = False
async def async_added_to_hass(self):
"""Handle being added to home assistant."""
await MqttAvailability.async_added_to_hass(self)
await MqttDiscoveryUpdate.async_added_to_hass(self)
value_templates = {}
if CONF_VALUE_TEMPLATE in config:
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = self.hass
value_templates = {key: value_template for key in TEMPLATE_KEYS}
for key in TEMPLATE_KEYS & config.keys():
value_templates[key] = config.get(key)
value_templates[key].hass = self.hass
self._value_templates = value_templates
async def _subscribe_topics(self):
"""(Re)Subscribe to topics."""
topics = {}
qos = self._config.get(CONF_QOS)
@callback
def handle_current_temp_received(topic, payload, qos):
@@ -287,9 +298,10 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
_LOGGER.error("Could not parse temperature from %s", payload)
if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_CURRENT_TEMPERATURE_TOPIC],
handle_current_temp_received, self._qos)
topics[CONF_CURRENT_TEMPERATURE_TOPIC] = {
'topic': self._topic[CONF_CURRENT_TEMPERATURE_TOPIC],
'msg_callback': handle_current_temp_received,
'qos': qos}
@callback
def handle_mode_received(topic, payload, qos):
@@ -298,16 +310,17 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload not in self._operation_list:
if payload not in self._config.get(CONF_MODE_LIST):
_LOGGER.error("Invalid mode: %s", payload)
else:
self._current_operation = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_MODE_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_MODE_STATE_TOPIC],
handle_mode_received, self._qos)
topics[CONF_MODE_STATE_TOPIC] = {
'topic': self._topic[CONF_MODE_STATE_TOPIC],
'msg_callback': handle_mode_received,
'qos': qos}
@callback
def handle_temperature_received(topic, payload, qos):
@@ -324,9 +337,10 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
_LOGGER.error("Could not parse temperature from %s", payload)
if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_TEMPERATURE_STATE_TOPIC],
handle_temperature_received, self._qos)
topics[CONF_TEMPERATURE_STATE_TOPIC] = {
'topic': self._topic[CONF_TEMPERATURE_STATE_TOPIC],
'msg_callback': handle_temperature_received,
'qos': qos}
@callback
def handle_fan_mode_received(topic, payload, qos):
@@ -336,16 +350,17 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload not in self._fan_list:
if payload not in self._config.get(CONF_FAN_MODE_LIST):
_LOGGER.error("Invalid fan mode: %s", payload)
else:
self._current_fan_mode = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_FAN_MODE_STATE_TOPIC],
handle_fan_mode_received, self._qos)
topics[CONF_FAN_MODE_STATE_TOPIC] = {
'topic': self._topic[CONF_FAN_MODE_STATE_TOPIC],
'msg_callback': handle_fan_mode_received,
'qos': qos}
@callback
def handle_swing_mode_received(topic, payload, qos):
@@ -355,32 +370,35 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload not in self._swing_list:
if payload not in self._config.get(CONF_SWING_MODE_LIST):
_LOGGER.error("Invalid swing mode: %s", payload)
else:
self._current_swing_mode = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_SWING_MODE_STATE_TOPIC],
handle_swing_mode_received, self._qos)
topics[CONF_SWING_MODE_STATE_TOPIC] = {
'topic': self._topic[CONF_SWING_MODE_STATE_TOPIC],
'msg_callback': handle_swing_mode_received,
'qos': qos}
@callback
def handle_away_mode_received(topic, payload, qos):
"""Handle receiving away mode via MQTT."""
payload_on = self._config.get(CONF_PAYLOAD_ON)
payload_off = self._config.get(CONF_PAYLOAD_OFF)
if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates:
payload = \
self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload == "True":
payload = self._payload_on
payload = payload_on
elif payload == "False":
payload = self._payload_off
payload = payload_off
if payload == self._payload_on:
if payload == payload_on:
self._away = True
elif payload == self._payload_off:
elif payload == payload_off:
self._away = False
else:
_LOGGER.error("Invalid away mode: %s", payload)
@@ -388,24 +406,27 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_AWAY_MODE_STATE_TOPIC],
handle_away_mode_received, self._qos)
topics[CONF_AWAY_MODE_STATE_TOPIC] = {
'topic': self._topic[CONF_AWAY_MODE_STATE_TOPIC],
'msg_callback': handle_away_mode_received,
'qos': qos}
@callback
def handle_aux_mode_received(topic, payload, qos):
"""Handle receiving aux mode via MQTT."""
payload_on = self._config.get(CONF_PAYLOAD_ON)
payload_off = self._config.get(CONF_PAYLOAD_OFF)
if CONF_AUX_STATE_TEMPLATE in self._value_templates:
payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\
async_render_with_possible_json_value(payload)
if payload == "True":
payload = self._payload_on
payload = payload_on
elif payload == "False":
payload = self._payload_off
payload = payload_off
if payload == self._payload_on:
if payload == payload_on:
self._aux = True
elif payload == self._payload_off:
elif payload == payload_off:
self._aux = False
else:
_LOGGER.error("Invalid aux mode: %s", payload)
@@ -413,9 +434,10 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_AUX_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_AUX_STATE_TOPIC],
handle_aux_mode_received, self._qos)
topics[CONF_AUX_STATE_TOPIC] = {
'topic': self._topic[CONF_AUX_STATE_TOPIC],
'msg_callback': handle_aux_mode_received,
'qos': qos}
@callback
def handle_hold_mode_received(topic, payload, qos):
@@ -428,9 +450,20 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
await mqtt.async_subscribe(
self.hass, self._topic[CONF_HOLD_STATE_TOPIC],
handle_hold_mode_received, self._qos)
topics[CONF_HOLD_STATE_TOPIC] = {
'topic': self._topic[CONF_HOLD_STATE_TOPIC],
'msg_callback': handle_hold_mode_received,
'qos': qos}
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
topics)
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
self._sub_state = await subscription.async_unsubscribe_topics(
self.hass, self._sub_state)
await MqttAvailability.async_will_remove_from_hass(self)
@property
def should_poll(self):
@@ -440,7 +473,12 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
@property
def name(self):
"""Return the name of the climate device."""
return self._name
return self._config.get(CONF_NAME)
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id
@property
def temperature_unit(self):
@@ -465,12 +503,12 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
@property
def operation_list(self):
"""Return the list of available operation modes."""
return self._operation_list
return self._config.get(CONF_MODE_LIST)
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return self._target_temperature_step
return self._config.get(CONF_TEMP_STEP)
@property
def is_away_mode_on(self):
@@ -495,7 +533,7 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
@property
def fan_list(self):
"""Return the list of available fan modes."""
return self._fan_list
return self._config.get(CONF_FAN_MODE_LIST)
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
@@ -508,19 +546,23 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
# optimistic mode
self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
if self._send_if_off or self._current_operation != STATE_OFF:
if (self._config.get(CONF_SEND_IF_OFF) or
self._current_operation != STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_TEMPERATURE_COMMAND_TOPIC],
kwargs.get(ATTR_TEMPERATURE), self._qos, self._retain)
kwargs.get(ATTR_TEMPERATURE), self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
self.async_schedule_update_ha_state()
async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
if self._send_if_off or self._current_operation != STATE_OFF:
if (self._config.get(CONF_SEND_IF_OFF) or
self._current_operation != STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_SWING_MODE_COMMAND_TOPIC],
swing_mode, self._qos, self._retain)
swing_mode, self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None:
self._current_swing_mode = swing_mode
@@ -528,10 +570,12 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
async def async_set_fan_mode(self, fan_mode):
"""Set new target temperature."""
if self._send_if_off or self._current_operation != STATE_OFF:
if (self._config.get(CONF_SEND_IF_OFF) or
self._current_operation != STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC],
fan_mode, self._qos, self._retain)
fan_mode, self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = fan_mode
@@ -539,22 +583,24 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
async def async_set_operation_mode(self, operation_mode) -> None:
"""Set new operation mode."""
qos = self._config.get(CONF_QOS)
retain = self._config.get(CONF_RETAIN)
if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
if (self._current_operation == STATE_OFF and
operation_mode != STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
self._config.get(CONF_PAYLOAD_ON), qos, retain)
elif (self._current_operation != STATE_OFF and
operation_mode == STATE_OFF):
mqtt.async_publish(
self.hass, self._topic[CONF_POWER_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
self._config.get(CONF_PAYLOAD_OFF), qos, retain)
if self._topic[CONF_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(
self.hass, self._topic[CONF_MODE_COMMAND_TOPIC],
operation_mode, self._qos, self._retain)
operation_mode, qos, retain)
if self._topic[CONF_MODE_STATE_TOPIC] is None:
self._current_operation = operation_mode
@@ -568,14 +614,16 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
@property
def swing_list(self):
"""List of available swing modes."""
return self._swing_list
return self._config.get(CONF_SWING_MODE_LIST)
async def async_turn_away_mode_on(self):
"""Turn away mode on."""
if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
self._config.get(CONF_PAYLOAD_ON),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
self._away = True
@@ -586,7 +634,9 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_AWAY_MODE_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
self._config.get(CONF_PAYLOAD_OFF),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None:
self._away = False
@@ -597,7 +647,8 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
self._topic[CONF_HOLD_COMMAND_TOPIC],
hold_mode, self._qos, self._retain)
hold_mode, self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_HOLD_STATE_TOPIC] is None:
self._hold = hold_mode
@@ -607,7 +658,9 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
"""Turn auxiliary heater on."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
self._config.get(CONF_PAYLOAD_ON),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_AUX_STATE_TOPIC] is None:
self._aux = True
@@ -617,7 +670,9 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
"""Turn auxiliary heater off."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)
self._config.get(CONF_PAYLOAD_OFF),
self._config.get(CONF_QOS),
self._config.get(CONF_RETAIN))
if self._topic[CONF_AUX_STATE_TOPIC] is None:
self._aux = False
@@ -661,9 +716,9 @@ class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._min_temp
return self._config.get(CONF_MIN_TEMP)
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._max_temp
return self._config.get(CONF_MAX_TEMP)

View File

@@ -17,7 +17,7 @@ from homeassistant.const import (
CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['radiotherm==1.4.1']
REQUIREMENTS = ['radiotherm==2.0.0']
_LOGGER = logging.getLogger(__name__)
@@ -235,13 +235,15 @@ class RadioThermostat(ClimateDevice):
self._name = self.device.name['raw']
# Request the current state from the thermostat.
data = self.device.tstat['raw']
import radiotherm
try:
data = self.device.tstat['raw']
except radiotherm.validate.RadiothermTstatError:
_LOGGER.error('%s (%s) was busy (invalid value returned)',
self._name, self.device.host)
return
current_temp = data['temp']
if current_temp == -1:
_LOGGER.error('%s (%s) was busy (temp == -1)', self._name,
self.device.host)
return
# Map thermostat values into various STATE_ flags.
self._current_temperature = current_temp

View File

@@ -15,6 +15,14 @@ from homeassistant.const import TEMP_CELSIUS
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
HA_TOON = {
STATE_AUTO: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep',
}
TOON_HA = {value: key for key, value in HA_TOON.items()}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Toon climate device."""
@@ -58,8 +66,7 @@ class ThermostatDevice(ClimateDevice):
@property
def current_operation(self):
"""Return current operation i.e. comfort, home, away."""
state = self.thermos.get_data('state')
return state
return TOON_HA.get(self.thermos.get_data('state'))
@property
def operation_list(self):
@@ -83,14 +90,7 @@ class ThermostatDevice(ClimateDevice):
def set_operation_mode(self, operation_mode):
"""Set new operation mode."""
toonlib_values = {
STATE_AUTO: 'Comfort',
STATE_HEAT: 'Home',
STATE_ECO: 'Away',
STATE_COOL: 'Sleep',
}
self.thermos.set_state(toonlib_values[operation_mode])
self.thermos.set_state(HA_TOON[operation_mode])
def update(self):
"""Update local state."""

View File

@@ -20,7 +20,7 @@ from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import helpers as ga_h
from homeassistant.components.google_assistant import const as ga_c
from . import http_api, iot, auth_api, prefs
from . import http_api, iot, auth_api, prefs, cloudhooks
from .const import CONFIG_DIR, DOMAIN, SERVERS
REQUIREMENTS = ['warrant==0.6.1']
@@ -37,6 +37,7 @@ CONF_RELAYER = 'relayer'
CONF_USER_POOL_ID = 'user_pool_id'
CONF_GOOGLE_ACTIONS_SYNC_URL = 'google_actions_sync_url'
CONF_SUBSCRIPTION_INFO_URL = 'subscription_info_url'
CONF_CLOUDHOOK_CREATE_URL = 'cloudhook_create_url'
DEFAULT_MODE = 'production'
DEPENDENCIES = ['http']
@@ -78,6 +79,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_RELAYER): str,
vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): str,
vol.Optional(CONF_SUBSCRIPTION_INFO_URL): str,
vol.Optional(CONF_CLOUDHOOK_CREATE_URL): str,
vol.Optional(CONF_ALEXA): ALEXA_SCHEMA,
vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA,
}),
@@ -97,6 +99,8 @@ async def async_setup(hass, config):
kwargs[CONF_GOOGLE_ACTIONS] = GACTIONS_SCHEMA({})
kwargs[CONF_ALEXA] = alexa_sh.Config(
endpoint=None,
async_get_access_token=None,
should_expose=alexa_conf[CONF_FILTER],
entity_config=alexa_conf.get(CONF_ENTITY_CONFIG),
)
@@ -113,7 +117,7 @@ class Cloud:
def __init__(self, hass, mode, alexa, google_actions,
cognito_client_id=None, user_pool_id=None, region=None,
relayer=None, google_actions_sync_url=None,
subscription_info_url=None):
subscription_info_url=None, cloudhook_create_url=None):
"""Create an instance of Cloud."""
self.hass = hass
self.mode = mode
@@ -125,6 +129,7 @@ class Cloud:
self.access_token = None
self.refresh_token = None
self.iot = iot.CloudIoT(self)
self.cloudhooks = cloudhooks.Cloudhooks(self)
if mode == MODE_DEV:
self.cognito_client_id = cognito_client_id
@@ -133,6 +138,7 @@ class Cloud:
self.relayer = relayer
self.google_actions_sync_url = google_actions_sync_url
self.subscription_info_url = subscription_info_url
self.cloudhook_create_url = cloudhook_create_url
else:
info = SERVERS[mode]
@@ -143,6 +149,7 @@ class Cloud:
self.relayer = info['relayer']
self.google_actions_sync_url = info['google_actions_sync_url']
self.subscription_info_url = info['subscription_info_url']
self.cloudhook_create_url = info['cloudhook_create_url']
@property
def is_logged_in(self):
@@ -186,9 +193,9 @@ class Cloud:
self._gactions_config = ga_h.Config(
should_expose=should_expose,
allow_unlock=self.prefs.google_allow_unlock,
agent_user_id=self.claims['cognito:username'],
entity_config=conf.get(CONF_ENTITY_CONFIG),
allow_unlock=self.prefs.google_allow_unlock,
)
return self._gactions_config
@@ -247,8 +254,7 @@ class Cloud:
return json.loads(file.read())
info = await self.hass.async_add_job(load_config)
await self.prefs.async_initialize(bool(info))
await self.prefs.async_initialize()
if info is None:
return

View File

@@ -0,0 +1,42 @@
"""Cloud APIs."""
from functools import wraps
import logging
from . import auth_api
_LOGGER = logging.getLogger(__name__)
def _check_token(func):
"""Decorate a function to verify valid token."""
@wraps(func)
async def check_token(cloud, *args):
"""Validate token, then call func."""
await cloud.hass.async_add_executor_job(auth_api.check_token, cloud)
return await func(cloud, *args)
return check_token
def _log_response(func):
"""Decorate a function to log bad responses."""
@wraps(func)
async def log_response(*args):
"""Log response if it's bad."""
resp = await func(*args)
meth = _LOGGER.debug if resp.status < 400 else _LOGGER.warning
meth('Fetched %s (%s)', resp.url, resp.status)
return resp
return log_response
@_check_token
@_log_response
async def async_create_cloudhook(cloud):
"""Create a cloudhook."""
websession = cloud.hass.helpers.aiohttp_client.async_get_clientsession()
return await websession.post(
cloud.cloudhook_create_url, headers={
'authorization': cloud.id_token
})

View File

@@ -0,0 +1,66 @@
"""Manage cloud cloudhooks."""
import async_timeout
from . import cloud_api
class Cloudhooks:
"""Class to help manage cloudhooks."""
def __init__(self, cloud):
"""Initialize cloudhooks."""
self.cloud = cloud
self.cloud.iot.register_on_connect(self.async_publish_cloudhooks)
async def async_publish_cloudhooks(self):
"""Inform the Relayer of the cloudhooks that we support."""
cloudhooks = self.cloud.prefs.cloudhooks
await self.cloud.iot.async_send_message('webhook-register', {
'cloudhook_ids': [info['cloudhook_id'] for info
in cloudhooks.values()]
}, expect_answer=False)
async def async_create(self, webhook_id):
"""Create a cloud webhook."""
cloudhooks = self.cloud.prefs.cloudhooks
if webhook_id in cloudhooks:
raise ValueError('Hook is already enabled for the cloud.')
if not self.cloud.iot.connected:
raise ValueError("Cloud is not connected")
# Create cloud hook
with async_timeout.timeout(10):
resp = await cloud_api.async_create_cloudhook(self.cloud)
data = await resp.json()
cloudhook_id = data['cloudhook_id']
cloudhook_url = data['url']
# Store hook
cloudhooks = dict(cloudhooks)
hook = cloudhooks[webhook_id] = {
'webhook_id': webhook_id,
'cloudhook_id': cloudhook_id,
'cloudhook_url': cloudhook_url
}
await self.cloud.prefs.async_update(cloudhooks=cloudhooks)
await self.async_publish_cloudhooks()
return hook
async def async_delete(self, webhook_id):
"""Delete a cloud webhook."""
cloudhooks = self.cloud.prefs.cloudhooks
if webhook_id not in cloudhooks:
raise ValueError('Hook is not enabled for the cloud.')
# Remove hook
cloudhooks = dict(cloudhooks)
cloudhooks.pop(webhook_id)
await self.cloud.prefs.async_update(cloudhooks=cloudhooks)
await self.async_publish_cloudhooks()

View File

@@ -6,6 +6,7 @@ REQUEST_TIMEOUT = 10
PREF_ENABLE_ALEXA = 'alexa_enabled'
PREF_ENABLE_GOOGLE = 'google_enabled'
PREF_GOOGLE_ALLOW_UNLOCK = 'google_allow_unlock'
PREF_CLOUDHOOKS = 'cloudhooks'
SERVERS = {
'production': {
@@ -16,7 +17,8 @@ SERVERS = {
'google_actions_sync_url': ('https://24ab3v80xd.execute-api.us-east-1.'
'amazonaws.com/prod/smart_home_sync'),
'subscription_info_url': ('https://stripe-api.nabucasa.com/payments/'
'subscription_info')
'subscription_info'),
'cloudhook_create_url': 'https://webhooks-api.nabucasa.com/generate'
}
}

View File

@@ -3,6 +3,7 @@ import asyncio
from functools import wraps
import logging
import aiohttp
import async_timeout
import voluptuous as vol
@@ -44,6 +45,20 @@ SCHEMA_WS_SUBSCRIPTION = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
})
WS_TYPE_HOOK_CREATE = 'cloud/cloudhook/create'
SCHEMA_WS_HOOK_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_HOOK_CREATE,
vol.Required('webhook_id'): str
})
WS_TYPE_HOOK_DELETE = 'cloud/cloudhook/delete'
SCHEMA_WS_HOOK_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_HOOK_DELETE,
vol.Required('webhook_id'): str
})
async def async_setup(hass):
"""Initialize the HTTP API."""
hass.components.websocket_api.async_register_command(
@@ -58,6 +73,14 @@ async def async_setup(hass):
WS_TYPE_UPDATE_PREFS, websocket_update_prefs,
SCHEMA_WS_UPDATE_PREFS
)
hass.components.websocket_api.async_register_command(
WS_TYPE_HOOK_CREATE, websocket_hook_create,
SCHEMA_WS_HOOK_CREATE
)
hass.components.websocket_api.async_register_command(
WS_TYPE_HOOK_DELETE, websocket_hook_delete,
SCHEMA_WS_HOOK_DELETE
)
hass.http.register_view(GoogleActionsSyncView)
hass.http.register_view(CloudLoginView)
hass.http.register_view(CloudLogoutView)
@@ -76,7 +99,7 @@ _CLOUD_ERRORS = {
def _handle_cloud_errors(handler):
"""Handle auth errors."""
"""Webview decorator to handle auth errors."""
@wraps(handler)
async def error_handler(view, request, *args, **kwargs):
"""Handle exceptions that raise from the wrapped request handler."""
@@ -240,17 +263,49 @@ def websocket_cloud_status(hass, connection, msg):
websocket_api.result_message(msg['id'], _account_data(cloud)))
def _require_cloud_login(handler):
"""Websocket decorator that requires cloud to be logged in."""
@wraps(handler)
def with_cloud_auth(hass, connection, msg):
"""Require to be logged into the cloud."""
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
connection.send_message(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
handler(hass, connection, msg)
return with_cloud_auth
def _handle_aiohttp_errors(handler):
"""Websocket decorator that handlers aiohttp errors.
Can only wrap async handlers.
"""
@wraps(handler)
async def with_error_handling(hass, connection, msg):
"""Handle aiohttp errors."""
try:
await handler(hass, connection, msg)
except asyncio.TimeoutError:
connection.send_message(websocket_api.error_message(
msg['id'], 'timeout', 'Command timed out.'))
except aiohttp.ClientError:
connection.send_message(websocket_api.error_message(
msg['id'], 'unknown', 'Error making request.'))
return with_error_handling
@_require_cloud_login
@websocket_api.async_response
async def websocket_subscription(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
connection.send_message(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
response = await cloud.fetch_subscription_info()
@@ -277,24 +332,37 @@ async def websocket_subscription(hass, connection, msg):
connection.send_message(websocket_api.result_message(msg['id'], data))
@_require_cloud_login
@websocket_api.async_response
async def websocket_update_prefs(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
connection.send_message(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
changes = dict(msg)
changes.pop('id')
changes.pop('type')
await cloud.prefs.async_update(**changes)
connection.send_message(websocket_api.result_message(
msg['id'], {'success': True}))
connection.send_message(websocket_api.result_message(msg['id']))
@_require_cloud_login
@websocket_api.async_response
@_handle_aiohttp_errors
async def websocket_hook_create(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
hook = await cloud.cloudhooks.async_create(msg['webhook_id'])
connection.send_message(websocket_api.result_message(msg['id'], hook))
@_require_cloud_login
@websocket_api.async_response
async def websocket_hook_delete(hass, connection, msg):
"""Handle request for account info."""
cloud = hass.data[DOMAIN]
await cloud.cloudhooks.async_delete(msg['webhook_id'])
connection.send_message(websocket_api.result_message(msg['id']))
def _account_data(cloud):

View File

@@ -2,13 +2,16 @@
import asyncio
import logging
import pprint
import uuid
from aiohttp import hdrs, client_exceptions, WSMsgType
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.components.alexa import smart_home as alexa
from homeassistant.components.google_assistant import smart_home as ga
from homeassistant.core import callback
from homeassistant.util.decorator import Registry
from homeassistant.util.aiohttp import MockRequest, serialize_response
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import auth_api
from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL
@@ -25,6 +28,19 @@ class UnknownHandler(Exception):
"""Exception raised when trying to handle unknown handler."""
class NotConnected(Exception):
"""Exception raised when trying to handle unknown handler."""
class ErrorMessage(Exception):
"""Exception raised when there was error handling message in the cloud."""
def __init__(self, error):
"""Initialize Error Message."""
super().__init__(self, "Error in Cloud")
self.error = error
class CloudIoT:
"""Class to manage the IoT connection."""
@@ -41,6 +57,19 @@ class CloudIoT:
self.tries = 0
# Current state of the connection
self.state = STATE_DISCONNECTED
# Local code waiting for a response
self._response_handler = {}
self._on_connect = []
@callback
def register_on_connect(self, on_connect_cb):
"""Register an async on_connect callback."""
self._on_connect.append(on_connect_cb)
@property
def connected(self):
"""Return if we're currently connected."""
return self.state == STATE_CONNECTED
@asyncio.coroutine
def connect(self):
@@ -91,6 +120,30 @@ class CloudIoT:
if remove_hass_stop_listener is not None:
remove_hass_stop_listener()
async def async_send_message(self, handler, payload,
expect_answer=True):
"""Send a message."""
if self.state != STATE_CONNECTED:
raise NotConnected
msgid = uuid.uuid4().hex
if expect_answer:
fut = self._response_handler[msgid] = asyncio.Future()
message = {
'msgid': msgid,
'handler': handler,
'payload': payload,
}
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("Publishing message:\n%s\n",
pprint.pformat(message))
await self.client.send_json(message)
if expect_answer:
return await fut
@asyncio.coroutine
def _handle_connection(self):
"""Connect to the IoT broker."""
@@ -134,6 +187,9 @@ class CloudIoT:
_LOGGER.info("Connected")
self.state = STATE_CONNECTED
if self._on_connect:
yield from asyncio.wait([cb() for cb in self._on_connect])
while not client.closed:
msg = yield from client.receive()
@@ -159,6 +215,17 @@ class CloudIoT:
_LOGGER.debug("Received message:\n%s\n",
pprint.pformat(msg))
response_handler = self._response_handler.pop(msg['msgid'],
None)
if response_handler is not None:
if 'payload' in msg:
response_handler.set_result(msg["payload"])
else:
response_handler.set_exception(
ErrorMessage(msg['error']))
continue
response = {
'msgid': msg['msgid'],
}
@@ -257,3 +324,43 @@ def async_handle_cloud(hass, cloud, payload):
payload['reason'])
else:
_LOGGER.warning("Received unknown cloud action: %s", action)
@HANDLERS.register('webhook')
async def async_handle_webhook(hass, cloud, payload):
"""Handle an incoming IoT message for cloud webhooks."""
cloudhook_id = payload['cloudhook_id']
found = None
for cloudhook in cloud.prefs.cloudhooks.values():
if cloudhook['cloudhook_id'] == cloudhook_id:
found = cloudhook
break
if found is None:
return {
'status': 200
}
request = MockRequest(
content=payload['body'].encode('utf-8'),
headers=payload['headers'],
method=payload['method'],
query_string=payload['query'],
)
response = await hass.components.webhook.async_handle_webhook(
found['webhook_id'], request)
response_dict = serialize_response(response)
body = response_dict.get('body')
if body:
body = body.decode('utf-8')
return {
'body': body,
'status': response_dict['status'],
'headers': {
'Content-Type': response.content_type
}
}

View File

@@ -1,7 +1,7 @@
"""Preference management for cloud."""
from .const import (
DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
PREF_GOOGLE_ALLOW_UNLOCK)
PREF_GOOGLE_ALLOW_UNLOCK, PREF_CLOUDHOOKS)
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
@@ -16,28 +16,29 @@ class CloudPreferences:
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._prefs = None
async def async_initialize(self, logged_in):
async def async_initialize(self):
"""Finish initializing the preferences."""
prefs = await self._store.async_load()
if prefs is None:
# Backwards compat: we enable alexa/google if already logged in
prefs = {
PREF_ENABLE_ALEXA: logged_in,
PREF_ENABLE_GOOGLE: logged_in,
PREF_ENABLE_ALEXA: True,
PREF_ENABLE_GOOGLE: True,
PREF_GOOGLE_ALLOW_UNLOCK: False,
PREF_CLOUDHOOKS: {}
}
await self._store.async_save(prefs)
self._prefs = prefs
async def async_update(self, *, google_enabled=_UNDEF,
alexa_enabled=_UNDEF, google_allow_unlock=_UNDEF):
alexa_enabled=_UNDEF, google_allow_unlock=_UNDEF,
cloudhooks=_UNDEF):
"""Update user preferences."""
for key, value in (
(PREF_ENABLE_GOOGLE, google_enabled),
(PREF_ENABLE_ALEXA, alexa_enabled),
(PREF_GOOGLE_ALLOW_UNLOCK, google_allow_unlock),
(PREF_CLOUDHOOKS, cloudhooks),
):
if value is not _UNDEF:
self._prefs[key] = value
@@ -62,3 +63,8 @@ class CloudPreferences:
def google_allow_unlock(self):
"""Return if Google is allowed to unlock locks."""
return self._prefs.get(PREF_GOOGLE_ALLOW_UNLOCK, False)
@property
def cloudhooks(self):
"""Return the published cloud webhooks."""
return self._prefs.get(PREF_CLOUDHOOKS, {})

View File

@@ -14,6 +14,8 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config'
DEPENDENCIES = ['http']
SECTIONS = (
'auth',
'auth_provider_homeassistant',
'automation',
'config_entries',
'core',
@@ -58,10 +60,6 @@ async def async_setup(hass, config):
tasks = [setup_panel(panel_name) for panel_name in SECTIONS]
if hass.auth.active:
tasks.append(setup_panel('auth'))
tasks.append(setup_panel('auth_provider_homeassistant'))
for panel_name in ON_DEMAND:
if panel_name in hass.config.components:
tasks.append(setup_panel(panel_name))

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