Compare commits

...

272 Commits

Author SHA1 Message Date
Paulus Schoutsen
05915775e3 Bumped version to 0.83.0b3 2018-11-28 22:47:37 +01:00
Paulus Schoutsen
311c796da7 Default to on if logged in (#18766) 2018-11-28 22:47:09 +01:00
Paulus Schoutsen
f860cac4ea 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:47:08 +01:00
Adam Mills
58e0ff0b1b Async tests for owntracks device tracker (#18681) 2018-11-28 22:47:08 +01:00
Paulus Schoutsen
f3047b9c03 Fix logbook filtering entities (#18721)
* Fix logbook filtering entities

* Fix flaky test
2018-11-27 20:16:32 +01:00
Paulus Schoutsen
775c909a8c Bumped version to 0.83.0b2 2018-11-27 20:15:57 +01:00
Paulus Schoutsen
3a8303137a 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-27 20:15:48 +01:00
Paulus Schoutsen
f9f71c4a6d Bumped version to 0.83.0b1 2018-11-26 14:20:56 +01:00
Joakim Sørensen
c3b76b40f6 Set correct default offset (#18678) 2018-11-26 14:20:49 +01:00
Bram Kragten
56c7c8ccc5 Fix vol Dict -> dict (#18637) 2018-11-26 14:20:49 +01:00
Fredrik Erlandsson
bb75a39cf1 Updated webhook_register, version bump pypoint (#18635)
* Updated webhook_register, version bump pypoint

* A binary_sensor should be a BinarySensorDevice
2018-11-26 14:20:48 +01:00
Eliseo Martelli
2f581b1a1e fixed wording that may confuse user (#18628) 2018-11-26 14:20:48 +01:00
pbalogh77
cf22060c5e 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 14:20:47 +01:00
Paulus Schoutsen
6bcedb3ac5 Updated frontend to 20181121.1 2018-11-26 14:16:30 +01:00
Paulus Schoutsen
fd7fff2ce8 Version bump to 0.83.0b0 2018-11-21 20:50:11 +01:00
Paulus Schoutsen
4e58eb8bae Updated frontend to 20181121.0 2018-11-21 20:35:46 +01:00
Paulus Schoutsen
49121f2347 Update translations 2018-11-21 20:18:56 +01:00
Fabian Affolter
708ababd78 Upgrade requests to 2.20.1 (#18615) 2018-11-21 19:58:56 +01:00
Pawel
92c0f9e4aa Fix mqtt cover inverted (#18456)
* Fixed state and position retrieval in inverted mode 100-0

* Always calculating find_percentage_in_range

* Added usage of max/min functions.
2018-11-21 15:48:44 +01:00
Martin Gross
81cac33801 Update locationsharinglib requirement to 3.0.8 (#18612) 2018-11-21 15:13:20 +01:00
Jonathan McDowell
1e3930a447 Add support for Panasonic Blu-Ray players (#18541)
* Add support for Panasonic Blu-Ray players

* Update panasonic_bluray.py

* Update panasonic_bluray.py
2018-11-21 14:22:24 +01:00
Joakim Sørensen
3cde8dc3a9 Add support for HTTPS and basic HTTP authentication for Glances (#18608)
* Add support for SSL and basic HTTP auth

* Remove blank line at the end of the file
2018-11-21 12:38:42 +01:00
Paulus Schoutsen
36c31a6293 Add permissions check in service helper (#18596)
* Add permissions check in service helper

* Lint

* Fix tests

* Lint

* Typing

* Fix unused impoert
2018-11-21 12:26:08 +01:00
Kevin Fronczak
8aa2cefd75 Upgrade blinkpy to 0.10.3 (Fixes #18341) (#18603) 2018-11-21 08:57:59 +01:00
pbalogh77
3b53003795 Fibaro components (#18487)
* Added Fibaro omcponents

Added cover, light, sensor and switch components

* Improvements based on code review

Improvements based on code review

* Fixes based on code review

Fixes based on code review

* Changes to light behavior based on code review

Changes to light behavior based on code review

* Internal changes

Changed how brightness is represented internally.
It should have no impact on functionality.
2018-11-21 06:15:54 +01:00
ehendrix23
377730a37c Change channel with play_media instead of select_source (#18474)
* Use service play_media instead of select_source

Use service play_media instead of select_source to change the channel as play_media is the right service for that.

* Log error on invalid media type

Log an error instead of raising a NotImplementedError if an invalid media type is provided.

* Changed so that success is not in else statement

Updated so that if media_type is channel that it is not in the else of an if.

* Update directv.py

Removed SELECT_SOURCE as supported feature.

* Rebased

Re-based with dev
2018-11-20 16:05:25 -07:00
Paulus Schoutsen
d9c7f777c5 Add cloud pref for Google unlock (#18600) 2018-11-20 23:23:07 +01:00
Charles Garwood
b7742999cf Update Z-Wave Tests asyncio/yield from -> async/await (#18599)
* Update lock tests

* Update binary sensor

* Update zwave component tests
2018-11-20 14:58:03 -05:00
Daniel Høyer Iversen
8742750926 Improve available for Mill heater (#18597)
* improve available for Mill heater

* typo
2018-11-20 20:00:13 +01:00
Charles Garwood
e87ecbd500 Z-Wave Lock Config Entry Support (#18209)
* Config Entry setup for zwave lock

* fix merge conflict

* lint

* Update other tests

* Fix tests

* Remove debug line and unused import
2018-11-20 14:59:34 +01:00
Fredrik Erlandsson
3838be4cb8 Add support for Daikin BRP069B41 (#18564)
* Add support for BRP069B41, fixes #18563

* Use fan_mode and swing_mode from pydaikin 0.8
2018-11-20 14:14:11 +01:00
Pascal Vizeli
0ddd502d00 Hass.io config check (#18576) 2018-11-20 13:30:09 +01:00
dapowers87
d88040eeed Revert changes that broke UI (#18495)
* Revert changes that broke UI

* Change from UNKNOWN to None

* Remove STATE_UNKNOWN import
2018-11-20 13:29:05 +01:00
Ian Richardson
44b33d45b1 Add websocket calls to shopping-list (#18392)
* Add websocket calls to shopping-list

Plan to deprecate API calls once shopping-list panel is removed from UI and replaced fully by Lovelace card

* Address ci-bot issues

* Fix violations

* Address travis complaints

* Address review comments

* Update test_shopping_list.py
2018-11-20 12:44:17 +01:00
Paulus Schoutsen
9b53b7e9e4 Bump Python-Nest to 4.0.5 (#18580)
* Fix Nest eco

* Update Python-nest to 4.0.5

* Update requirements_all.txt

* Update requirements_test_all.txt
2018-11-20 12:18:46 +01:00
Franck Nijhof
80cd8b180c Adds light switch platform (#18562)
* Adds light switch platform

* Addresses HoundCI warnings

* Addresses Flake8 reported issues

* Removes async_update call as per review
2018-11-20 11:51:34 +01:00
William Scanlon
b3e37af9b1 Added unique id to all Wink devices. (#18589) 2018-11-19 22:52:43 -05:00
Eliseo Martelli
57f7e7eedc Bumped ghlocalapi to 0.1.0 (#18584)
* mumped ghlocalapi to 0.1.0

* update requirement_all.txt
2018-11-19 15:01:26 -05:00
Sebastian Muszynski
14ad7428ea Prefix all xiaomi_aqara events (#17354) 2018-11-19 20:55:08 +01:00
Paulus Schoutsen
f86083cf52 Fix MQTT async_added_to_hass (#18575)
* Fix some invocations

* Update manual_mqtt.py
2018-11-19 20:48:26 +01:00
Soós Péter
3891f2eebe Add mikrotik SSL support (#17898)
* Update mikrotik.py

* Update mikrotik.py

* Added basic  api_ssl support

Added preliminary support to use api_ssl instead of api. It don't check the validity of the certificate need it.
At Home Assistant side add ssl = true to your sensor configuration, and don't forget to change the port too (to 8729 by default):

device_tracker:
  - platform: mikrotik
    host: 192.168.88.1
    port: 8729
    ssl: true
    username: homeassistant
    password: TopSecret

At MikroTik side you have to add or generate a certificate, and configure api_ssl to use it. Here is an example:

/certificate add common-name="Self signed demo certificate for API" days-valid=3650 name="Self signed demo certificate for API" key-usage=digital-signature,key-encipherment,tls-server,key-cert-sign,crl-sign
/certificate sign "Self signed demo certificate for API"
/ip service set api-ssl certificate="Self signed demo certificate for API"
/ip service enable api-ssl
/ip service disable api
/user group add name=homeassistant policy=read,api,!local,!telnet,!ssh,!ftp,!reboot,!write,!policy,!test,!winbox,!password,!web,!sniff,!sensitive,!romon,!dude,!tikapp
/user add group=homeassistant name=homeassistant
/user set password="TopSecret" homeassistant

* Fixed import missind ssl lib

* SSL support code cleanup, use ssl-api port by default if ssl enabled

* Restored accidentalli deleted method parameter

* Fixed Python 3.5.3 compilation errors

Fixed Python 3.5.3 compilation errors reported by Travis CI

* Removed duplicated MTK_DEFAULT_API_PORT
2018-11-19 16:54:09 +01:00
emontnemery
de9bac9ee3 Reconfigure MQTT binary_sensor component if discovery info is changed (#18169)
* Recreate component if discovery info is changed

* Update component instead of remove+add

* Set name and unique_id in __init__

* Update unit test

* Cleanup

* More cleanup

* Refactor according to review comments

* Change discovery_hash

* Review comments, add tests

* Fix handling of value_template
2018-11-19 16:49:04 +01:00
Oleksii Serdiuk
01953ab46b Darksky: Expose missing conditions for day 0 forecast (#18312)
Dark Sky Sensor didn't expose conditions for day 0 (today forecast) that
had the same name as current conditions. With this change all conditions
form day 0 (today) forecast are exposed the same way as conditions for
the rest of the days (1..7): as `dark_sky_<condition>_<day>`. As a
consequence, conditions for day 0 that were already exposed now have
`_0` suffix. This actually improves the code by removing most of
special handling, based on condition name.

To get day 0 conditions the user now has to add `- 0` to `forecast`
configuration parameter.

Conditions, for which suffix `_0` appeared: `precip_accumulation`,
`temperature_high`, `temperature_low`, `apparent_temperature_high`,
`apparent_temperature_low`, `precip_intensity_max`, `moon_phase`.

This is a breaking change!

Closes #18205
2018-11-19 14:48:52 +01:00
Daniel Perna
fc4dd4e51f Update pyhomematic to 0.1.52 and add features for lights (#18499)
* Update pyhomematic and add features for lights

* Lint

* Lint

* Update homematic.py

* Update homematic.py
2018-11-19 14:26:08 +01:00
bw3
90f3f2b1e7 Fix for epson state not updating (#18357)
* Fixed update method name

* Update epson.py
2018-11-19 13:47:52 +01:00
Fredrik Erlandsson
c1ca7beea1 Support for Point component (#17466)
* Minut Point support

* tox tests for Point

* config flow fixes

* fixes requested by @MartinHjelmare

* swedish translation :)

* fix tests
2018-11-19 12:52:21 +01:00
Anders Melchiorsen
84fd66c8a1 Template binary sensor to not track all state changes (#18573) 2018-11-19 12:10:48 +01:00
arigilder
97c493448b Correct cached stale device tracker handling (#18572)
* Fix async addition of stale devices

* Add comment to mark_stale

* Remove extraneous whitespace
2018-11-19 12:06:57 +01:00
ehendrix23
9fa34f0d77 Add support for sessions (#18518)
Added support for sessions to keep connection open with August portal, improving performance.
py-august version increased to 0.7.0
2018-11-19 11:53:27 +01:00
ehendrix23
cdcc818bf9 Remove turn_on and turn_off feature for clients (#18234)
* Enhancements for DirecTV media player

Following enhancements have been made:

1. Added debug logging
2. Added ability to change channel using select_source service of the remote platform.
3. State will now show paused if a recorded program is paused, for live TV playing will always be returned.
4. Added the following attributes:
    a. media_position: current position of the media (in seconds)
    b. media_position_updated_at: timestamp when media_position was updated.
   c. source: current source (channel).
   d. media_isbeingrecorded: if current media is being recorded or not.
   e. media_rating: TV/Movie rating of the media
   f. media_recorded: if current media is recorded or live TV
   g. media_starttime: Timestamp media was aired

Reordered properties to follow same order as how they are in __init__.py of remote platform.

* Fixed error and cleaned up few items

Fixed an issue when determining if a program is recorded or not.
Cleaned up some coding.

* Added available property

Added available property

* Disable feature TURN_ON and TURN_OFF for DVR clients

Disable the feature turn_on and turn_off for DVR clients.

* self._is_client and raise NotImplementedError

Updated setting self._is_client
Raise NotImplementedError if turn_on or turn_off is called for clients.
2018-11-19 11:47:00 +01:00
Phil Bruckner
83b4e56978 Log delay and wait_template steps in scripts (#18448)
* Log delay and wait_template steps in scripts

Help improve script debugging by logging delay and wait_template steps in scripts.

* Update tests

* Fix lint issue
2018-11-19 11:14:00 +01:00
Aleksandr Smirnov
089a2f4e71 Logbook speedup (#18376)
* filter logbook results by entity_id prior to instantiating them

* include by default, pass pep8

* pass pylint

* use entityfilter, update tests
2018-11-19 10:36:00 +01:00
emontnemery
f241becf7f Avoid race in entity_platform.async_add_entities() (#18445)
This avoids a race between multiple concurrent calls to
entity_platform.async_add_entities() which may cause
entities to be created with non-unique entity_id
2018-11-19 10:13:50 +01:00
emontnemery
7e702d3caa Fix small issue related to topic prefix (#18512)
Fix expansion of topic prefix when discovery message contains non string-type items.
2018-11-19 09:59:07 +01:00
Jeff Irion
ab8c127a4a Enable native support + ADB authentication for Fire TV (#17767)
* Enable native support + ADB authentication for Fire TV

* Remove unnecessary underscore assignments

* Bump firetv to 1.0.5.3

* Change requirements to 'firetv>=1.0.6'

* Change requirement from 'firetv>=1.0.6' to 'firetv==1.0.6'

* Address pylint errors

* Ran 'python script/gen_requirements_all.py'

* Address some minor reviewer comments

* Run 'python script/gen_requirements_all.py'

* Just use the 'requirements_all.txt' and 'requirements_test_all.txt' from the 'dev' branch...

* Edit the 'requirements_all.txt' file manually

* Pass flake8 tests

* Pass pylint tests, add extended description for 'select_source'

* More precise exception catching

* More Pythonic returns

* Import exceptions inside '__init__'

* Remove 'time.sleep' command

* Sort the imports

* Use 'config[key]' instead of 'config.get(key)'

* Remove accessing of hidden attributes; bump firetv version to 1.0.7

* Bump firetv to 1.0.7 in 'requirements_all.txt'

* Don't access 'self.firetv._adb', use 'self.available' instead

* Remove '_host' and '_adbkey' attributes

* Create the 'FireTV' object in 'setup_platform' and check the connection before instantiating the entity

* Fixed config validation for 'adbkey'

* add_devices -> add_entities

* Remove 'pylint: disable=no-name-in-module'

* Don't assume the device is available after attempting to connect

* Update the state after reconnecting

* Modifications to 'adb_decorator'

* Modifications to 'setup_platform'

* Don't update the state if the ADB reconnect attempt was unsuccessful

* 'return None' -> 'return'

* Use 'threading.Lock()' instead of a boolean for 'adb_lock'

* Use a non-blocking 'threading.Lock'
2018-11-19 07:05:58 +01:00
sdelliot
afe21b4408 Re-adding the season attribute (#18523) 2018-11-18 18:35:36 -07:00
Anders Melchiorsen
b066877453 Allow unloading of LIFX config entry (#18535) 2018-11-18 19:51:17 +01:00
Per Sandström
796933de68 Upgrade vsure to 1.5.2 (#18557)
Upgrade vsure to 1.5.2
2018-11-18 19:41:24 +01:00
Aaron Bach
8f59be2059 Make MyQ platform async (#18489)
* Make MyQ platform async

* Bumped requirements

* Member comments

* Member updates
2018-11-18 18:37:03 +01:00
Ville Skyttä
dfb8f60fe2 Upgrade pytest and pytest-cov (#18542)
* Upgrade pytest to 4.0.0

* Upgrade pytest-cov to 2.6.0
2018-11-18 09:33:01 +01:00
Daniel Høyer Iversen
3f747f1a8c Mill lib (#18529)
* Mill attr and update the lib

* lib

* heater_generation
2018-11-17 18:39:38 +01:00
Fabian Affolter
4751ad69a7 Upgrade ruamel.yaml to 0.15.78 (#18508) 2018-11-17 13:20:17 +01:00
Anders Melchiorsen
c6ca27e9b4 Improve handling of unavailable Sonos speakers (#18534) 2018-11-17 13:18:51 +01:00
Aaron Bach
e73b9b9b8f Clean up left-behind dispatcher handlers when removing RainMachine (#18488)
* Clean up left-behind dispatcher handlers when removing RainMachine

* Member comments
2018-11-17 10:42:50 +01:00
Daniel Høyer Iversen
6b2f50b29e Improve real time consumption for Tibber sensor (#18517) 2018-11-16 14:19:54 -05:00
Eliseo Martelli
fcd756d58a fixed sky_hub schema to reflect documentation (#18509) 2018-11-16 14:14:46 -05:00
Paulus Schoutsen
24db2b66ab Ban systemair-savecair (#18483) 2018-11-16 14:28:39 +01:00
Fabian Affolter
320efdb744 Upgrade sqlalchemy to 1.2.14 (#18504) 2018-11-16 14:15:53 +01:00
Fabian Affolter
9e0497875e Upgrade sphinx-autodoc-typehints to 1.5.0 (#18505) 2018-11-16 14:15:04 +01:00
Fabian Affolter
30806fa362 Upgrade numpy to 1.15.4 (#18506) 2018-11-16 14:14:40 +01:00
Fabian Affolter
9f51deb1de Upgrade youtube_dl to 2018.11.07 (#18507) 2018-11-16 14:14:20 +01:00
cdce8p
0ca94f239d Update HAP-python 2.4.1 (#18417)
* Bugfixes
2018-11-16 11:42:48 +01:00
cdce8p
ed7aea006a Add safe_mode HomeKit (#18356)
* Use: 'safe_mode: True' in case of pairing issues
2018-11-16 11:08:39 +01:00
Martin Gross
b7b8296c73 Alexa v3 name change for additionalApplianceDetails (#18485) 2018-11-15 11:15:50 -07:00
Leothlon
afb3a52b5b Fixed bug for receivers without support for new command (#18478)
* Fixed bug for receivers without support for new command

* removed extra parenthesis
2018-11-15 10:49:10 -07:00
Aaron Bach
4446b15cb0 Add Rainmachine to the device registry (#18452)
* Device registry in

* Member comments

* Hound
2018-11-15 10:43:20 -07:00
Oleksii Serdiuk
d1b5bc19da AirVisual: Show icon for air pollution level, based on its value (#18482)
Show excited, happy, neutral, sad, dead emoticon, or biohazard icon,
based on air pollution level.

Also, fix a small typo and change air quality index icon to
`mdi:chart-line`. Seems a bit more logical.
2018-11-15 10:23:46 -07:00
Paulus Schoutsen
75bb78d440 Update translations 2018-11-15 14:30:37 +01:00
Paulus Schoutsen
2d870a29c4 Merge branch 'master' into dev 2018-11-15 14:30:21 +01:00
Paulus Schoutsen
7cb7c76a83 Merge pull request #18481 from home-assistant/rc
0.82.1
2018-11-15 14:25:56 +01:00
Paulus Schoutsen
b40b934029 Bumped version to 0.82.1 2018-11-15 14:01:21 +01:00
Pascal Vizeli
5ffcb99b4f Update pyozw to 0.1.1 (#18436)
* Update pyozw to 0.1.1

* Update requirements_all.txt
2018-11-15 14:01:15 +01:00
Abílio Costa
69d358fa08 edp_redy: increase UPDATE_INTERVAL (#18429)
The server was getting a bit mad sometimes and would lock users out.
2018-11-15 14:01:14 +01:00
Fredrik Erlandsson
f36b94b376 updated pydaikin version (#18413) 2018-11-15 14:01:14 +01:00
Pawel
b5d4e18880 Changed checking of cover state closed from 0 to closed_position variable. (#18407)
Change error message to avoid expression "get_position_topic".
2018-11-15 14:01:13 +01:00
Paulus Schoutsen
43271ca0f7 Bump aioasuswrt to 1.1.6 2018-11-15 14:01:02 +01:00
Diogo Gomes
7659c33439 cancel off_delay action (#18389) 2018-11-15 13:56:14 +01:00
Clayton Nummer
b8ddbc3fdb Fix default value for optional Sense configuration parameter (#18379) 2018-11-15 13:56:13 +01:00
Bram Kragten
2aa2233d9b Fix including from sub dir (#18378)
The include path is now always relative to the root of the config dir.
2018-11-15 13:56:13 +01:00
Martin Hjelmare
6bcba1fbea Fix hangouts notify (#18372)
* Remove notify schema from hangouts platform

* Notify platforms shouldn't overwrite the notify component service
  schema. That has no effect.

* Fix hangouts service data key value
2018-11-15 13:56:12 +01:00
Jason Hunter
466d3a5ef8 catch key error when saving image (#18365) 2018-11-15 13:56:12 +01:00
Aaron Bach
8aa1283adc Add Rainmachine config entry (#18419)
* Initial stuff

* More work in place

* Starting with tests

* Device registry in place

* Hound

* Linting

* Member comments (including extracting device registry)

* Member comments (plus I forgot cleanup!)

* Hound

* More Hound

* Removed old import

* Adding config entry test to coverage

* Updated strings
2018-11-14 13:23:49 -07:00
pbalogh77
312872961f Initial support for Fibaro HomeCenter hubs (#17891)
* 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
2018-11-14 20:58:32 +01:00
Daniel Høyer Iversen
00235cf6f0 Improve support for 1. generation mill heater (#18423)
* Improve support for 1. gen mill heater

* style

* None operation_list for gen 1 heater

* Remove SUPPORT_OPERATION_MODE for gen 1
2018-11-14 19:35:12 +01:00
Rick van Hattem
80e616cacf Make mikrotik method setting optional as intended (#18454)
Pull request #17852 accidently made the `method` setting required.
2018-11-14 18:05:29 +01:00
Maikel Punie
c7ac216602 Readded climate.velbus (#18434)
* Readded climate.velbus

* houndci-bot changes

* Added more comments

* Fix flake8 error

* return TEMP_* constants

* more comments
2018-11-14 09:40:43 -05:00
Fredrik Erlandsson
0d43cb6d0e fixes for last version bump on pydaikin (#18438) 2018-11-14 09:38:16 -05:00
ehendrix23
d2e102ee2f Init statistics sensor upon HASS start (#18236)
* Update query to include maxAge

Updated the query from recorded to include MaxAge if set; reducing the amount of records retrieved that would otherwise be purged anyway for the sensor.

* Initialization upon HASS start

Register the state listener and read previous information from recorder once HASS is started.

* Updated test_statistics.py for HASS start

Updated test_statistics.py to start HASS and wait it is completed before running test.

* Added newline in docstring

Added newline in docstring.

* Added start of HASS to test_initialize_from_database_with_maxage

Added start of HASS to new test test_initialize_from_database_with_maxage.

* Updates based on review

Following updates based on review:
-) Removed self._hass and passing hass
-) Changed async_add_job to async_create_task
-) For state update, calling async_schedule_update_ha_state
2018-11-14 15:13:32 +01:00
Sebastian Muszynski
d2907b8e53 Add Philips Zhirui Downlight support (#18455) 2018-11-14 12:33:27 +01:00
Glenn Waters
419400f90b Bump elkm1_lib version (#18450) 2018-11-14 02:20:15 +01:00
Pascal Vizeli
532a75b487 Update pyozw to 0.1.1 (#18436)
* Update pyozw to 0.1.1

* Update requirements_all.txt
2018-11-13 23:43:01 +01:00
JC Connell
291fba0ba4 Update Magicseaweed sensor (#18446)
* Increment python-magicseaweed version.

* Update requirements_all.txt
2018-11-13 23:41:58 +01:00
Joakim Sørensen
597da90622 Fixes issue for returning the correct docker version. (#18439) 2018-11-13 21:00:21 +01:00
Abílio Costa
f14251bdcc edp_redy: increase UPDATE_INTERVAL (#18429)
The server was getting a bit mad sometimes and would lock users out.
2018-11-13 14:24:30 +01:00
kennedyshead
ebdfb56803 Bumping aioasuswrt (#18427) 2018-11-13 14:04:36 +01:00
Daniel Høyer Iversen
7aa41d66e9 Avg price for tibber sensor (#18426)
* Avg price for tibber sensor

* change to sum_price
2018-11-13 11:29:04 +01:00
Fabian Affolter
7113ec6073 Fix smhi docstrings (#18414)
* Fix docstrings

* Fix lint

* Fix another typo

* Fix mobile phone edit
2018-11-13 10:01:14 +01:00
kbickar
f78dcb96b0 Sense will not list removed devices (#18410)
* Sense will not list removed devices

* quote update
2018-11-13 08:34:11 +01:00
Fredrik Erlandsson
996da72a4c Daikin fixes (#18415)
* updated pydaikin version

* some Daikin models does not support fan settings
2018-11-13 08:33:34 +01:00
Aaron Bach
8547489014 Bumps pytile to 2.0.5 (#18395) 2018-11-12 19:10:03 -07:00
Fredrik Erlandsson
c6683cba7d updated pydaikin version (#18413) 2018-11-12 22:53:50 +01:00
Fabian Affolter
ea4480f170 Use existing constant (#18408) 2018-11-12 22:32:30 +01:00
Diogo Gomes
0ab81b03a8 cancel off_delay action (#18389) 2018-11-12 21:28:00 +01:00
Pawel
d0463942be Changed checking of cover state closed from 0 to closed_position variable. (#18407)
Change error message to avoid expression "get_position_topic".
2018-11-12 19:45:59 +00:00
Glenn Waters
275b485b36 Add support for keypad keypress (#18393)
* Add support for keypad keypress

* Update requirements_all
2018-11-12 16:10:28 +01:00
Levi Govaerts
15c77fe548 Add niko-home-control support (#18019)
* Add niko-home-control support

* Remove the sensor platform

* Minor changes

* Fix docstring
2018-11-12 15:59:52 +01:00
Philipp Wensauer
e5930da972 Update pynello to 2.0.2 (#18402)
* Update to pynello 2.0.2

* Update to pynello 2.0.2

* Update to pynello 2.0.2
2018-11-12 09:54:08 -05:00
Paulus Schoutsen
8fb6030f97 Bump frontend to 20181112.0 2018-11-12 15:14:37 +01:00
Jorim Tielemans
9eac11dcbe Filter Coinbase account wallets (#18167)
Only add sensor entities for accounts with the specified currencies.
This is a none breaking change.
If it's not specified then all account wallets will be loaded.
2018-11-12 11:26:05 +01:00
kennedyshead
afd9c44ffb Bumping aioasuswrt (#18400) 2018-11-12 11:10:53 +01:00
Hmmbob
1f06d6ac1a Update waze_travel_time.py (#18399) 2018-11-12 11:09:39 +01:00
Adam Belebczuk
ca86755409 Discord - Minor bugfixes (#18385)
Suppress PyNaCl warnings of Discord notify
2018-11-11 23:16:23 +01:00
Clayton Nummer
1f476936a2 Fix default value for optional Sense configuration parameter (#18379) 2018-11-11 22:55:45 +01:00
Eric Nagley
ddeeba20b9 Google assistant enable fan speed controls (#18373)
* Initial commit of Traits changes.

* Initial commit of tests chagnes for added FanSpeed trait.

* pylint fixes.

* Default reversible to false

* Ensure reversible returns True/False.

* Fix FanSpeed trait name and fix order.

* Add remaining checks to FanSpeed trait Test.

* Remove un-needed blank lines at EOF.

* Update homeassistant/components/google_assistant/trait.py

Co-Authored-By: marchingphoenix <eanagley@gmail.com>

* use fan.SPEED_* constants as keys to speed_synonyms dict.
convert True if() to bool() for reversible assignment.

* use fan.SPEED_OFF constant of 'on' check.
2018-11-11 22:02:33 +01:00
Aaron Bach
5129a48750 Fixed misspellings in some of the Pollen sensor names (#18382) 2018-11-11 13:01:26 -07:00
Fabian Affolter
95eae47438 Bump version to 0.83.0 2018-11-11 18:22:28 +01:00
bouni
372470f52a Fix and improvment of Swiss Hydrological Data component (#17166)
* Fix and improvment of Swiss Hydrological Data component

* changed component to get data from a REST API rather than from crawling the website

* fixed several issues and lint errors

* Fix and improvment of Swiss Hydrological Data component

* Minor changes

- Simplify the sensor configuration (expose value as attributes rather than sensor)
- Make the setup fail if station is not available
- Add unique ID
- Prepare for config flow
2018-11-11 17:48:44 +01:00
Martin Hjelmare
02cc6a2f9a Fix hangouts notify (#18372)
* Remove notify schema from hangouts platform

* Notify platforms shouldn't overwrite the notify component service
  schema. That has no effect.

* Fix hangouts service data key value
2018-11-11 17:46:28 +01:00
Jason Hunter
9cb6464c58 catch key error when saving image (#18365) 2018-11-11 17:44:41 +01:00
Jack Wilsdon
5b9a9d8e04 Return color information in Alexa Smart Home response (#18368)
Fixes #18367.
2018-11-11 17:43:01 +01:00
Ville Skyttä
9411fca955 Add more type hints to helpers (#18350)
* Add type hints to helpers.entityfilter

* Add type hints to helpers.deprecation
2018-11-11 17:39:50 +01:00
Bram Kragten
b8c06ad019 Fix including from sub dir (#18378)
The include path is now always relative to the root of the config dir.
2018-11-11 17:15:58 +01:00
Steven Looman
9c92151ad1 Upgrade async_upnp_client to 0.13.2 (#18377) 2018-11-11 15:10:03 +00:00
Daniel Høyer Iversen
f0a0ce504b Better error handling in Tibber (#18363)
* Better error handling in Tibber

* return if received error
2018-11-11 14:06:21 +01:00
Matthew Garrett
d9533127f9 Merge pull request #18364 from mjg59/avion
Bump python-avion dependency
2018-11-10 18:34:27 -08:00
Matthew Garrett
fa127188df Bump python-avion dependency
The current version of python-avion doesn't work correctly with Python 3.5.
Update it to one that does.
2018-11-10 18:03:55 -08:00
Chris Kacerguis
667b41dd4a Show battery_level as a percent vs a decimal (#18328) 2018-11-10 21:30:03 +01:00
Ville Skyttä
f236e14bd6 Upgrade pytest and pytest-sugar (#18338)
* Upgrade pytest to 3.10.0

* Upgrade pytest-sugar to 0.9.2
2018-11-10 21:08:32 +01:00
Antoine Meillet
e75f9b36f9 add heartbeat support to mysensors (#18359) 2018-11-10 21:08:03 +01:00
cdce8p
132bb7902a Update HAP-python to 2.4.0 (#18355) 2018-11-10 18:33:45 +01:00
Paulus Schoutsen
df2ab62ce9 Merge pull request #18335 from home-assistant/rc
0.82
2018-11-10 09:52:37 +01:00
Adam Belebczuk
f7c99ada9d WeMo - Change name of discovery option (#18348) 2018-11-10 09:32:09 +01:00
Tyler Page
8bd281d5a3 Update credstash.py (#18349)
* Update credstash.py

* Update requirements_all.txt
2018-11-10 08:21:39 +01:00
Adam Belebczuk
210eab16da WeMo - Change name of discovery option (#18348) 2018-11-10 08:17:24 +01:00
uchagani
64ada1ea5a bump total connect client to 0.22 (#18344) 2018-11-09 20:04:28 -05:00
Daniel Høyer Iversen
14ad5c0006 Switchmate library update (#18336) 2018-11-09 23:41:44 +01:00
Joakim Sørensen
d34c47a9e1 Rename sensor.launch to sensor.launch_library (#18337) 2018-11-09 22:24:26 +01:00
Aaron Bach
f971309113 Add support for sensors from Flu Near You (#18136)
* Add support for sensors from Flu Near You

* Added sensor for total reports with symptoms

* Member comments

* Member comments
2018-11-09 08:23:07 -07:00
Paulus Schoutsen
f8ca4cfd91 Merge remote-tracking branch 'origin/master' into rc 2018-11-09 15:44:17 +01:00
Paulus Schoutsen
4324d87673 Bumped version to 0.82.0 2018-11-09 15:42:17 +01:00
Abílio Costa
7f48a280ee fix last device ignored (#18329) 2018-11-09 15:41:21 +01:00
Paulus Schoutsen
f4c35a389d Remove Velbus climate platform (#18319) 2018-11-09 15:41:20 +01:00
Daniel Høyer Iversen
8ab2f669d2 Fix xiaomi binary_sensor warning (#18280)
* Fix xiaomi binary_sensor warning

* Fix xiaomi binary_sensor warning
2018-11-09 15:41:20 +01:00
Paulus Schoutsen
de37fc90c0 Bump frontend to 20181103.3 2018-11-09 15:40:46 +01:00
Daniel Høyer Iversen
c571637176 Fix xiaomi binary_sensor warning (#18280)
* Fix xiaomi binary_sensor warning

* Fix xiaomi binary_sensor warning
2018-11-09 11:36:00 +01:00
Abílio Costa
b803075eb4 fix last device ignored (#18329) 2018-11-09 10:41:08 +01:00
ehendrix23
ae85baf396 Restrict recorder query to include max age (#18231)
* Update query to include maxAge

Updated the query from recorded to include MaxAge if set; reducing the amount of records retrieved that would otherwise be purged anyway for the sensor.

* Added newline in docstring

Added newline in docstring.

* Add test + small fix

Added test to ensure query works correctly
Query should be greater then or equal instead of greater then. Fixed.

* Fixed lint issue

Fixed lint issue.
2018-11-08 23:08:36 +00:00
Brig Lamoreaux
05eac915d1 Srpenergy (#18036)
* Add srp_energy

* Update message on TypeError. Add check for None state.

* Add check for none in history

* Add srpenergy to Test requirements.

* Add srpenergy to requirments.

* Change = to ==.

* Change import for srpenergy

* Fix Flak8 errors

* Add srp to gen requirements script

* Change config name.

* Add daily usage test

* Add test for daily usage.

* Fix Flake8 message.

* Remove blank after docstring.

* Add srpenergy to coverage

* Bump requires version to srpenergy

* Fix type in coverage. Import from Sensor. Use dict.

* Update to 1.0.5. Check credentials on setup. Standalone test.

* Fix formating.

* Remove period. Rename _ variables.

* Fix rebase merge

* Add rebase requirement

* Improve Mock Patching.
2018-11-08 19:19:30 +01:00
horga83
8f107c46fe W800rf32 (#17920)
* Initial commit of w800rf32 component and binary_sensor.

The W800 family is an X10 RF receiver used with keypads and motion sensors etc.

* Initial commit of w800rf32 switch platform.

The W800 family is an X10 RF receiver used with keypads and motion sensors etc.

* Remove unused code.

* Additions for w800rf32 component and platform code

* Fix w800rf32 minor lint issues and make sure gen_requirements.py correctlly adds w800rf32

* Added dispatch_connect/send and refactor somewhat

* Fix missed indentation lint

* Removed shared entity dict and use async_dispatch code

* Fix long line not caught by lint

* One more line too long, missed it

* Remove unused code and changes for async

* Remove  @callback that shouldn't be there.

* Remove switch platform, can't have read only switch.

* Remove unused CONF_DEBUG

* Remove used vars and make CONF_DEVICES required

* Move CONF_OFF_DELAY to platform, only used there
2018-11-08 18:49:00 +01:00
Joakim Sørensen
fd2987e551 Add new launch sensor to keep track of space launches. (#18274)
* Add new launch sensor to keep track of space launches.

* Added attribution to Launch Library.

* Adds data class and throtle, reuse aiohttp session.

* Add one extra blank line before the new class..

* Change throttle to simpler SCAN_INTERVAL.

* Remove the usage of the LaunchData class.

* Bump pylaunches, remove . from log, fix line breaker for agency_country_code, remove CONF_ from ATTRIBUTION.
2018-11-08 16:37:11 +01:00
Matthias Urlichs
9472529d43 Doc fix: a circular dependency does not raise an error. (#18298)
This is easier to handle than raising an exception: a circular
dependency causes multiple error entries in the log, which is what we
want.

This is harder to achieve with an exception. Since there is only one
user of this code, I choose to fix the documentation -- instead of
adding a lot of mostly-useless exception handling.

Closes: #13147
2018-11-08 12:59:58 +01:00
Paulus Schoutsen
f7f0a4e811 System groups (#18303)
* Add read only and admin policies

* Migrate to 2 system groups

* Add system groups

* Add system groups admin & read only

* Dont' mutate parameters

* Fix types
2018-11-08 12:57:00 +01:00
Paulus Schoutsen
54b0cde52a Remove Velbus climate platform (#18319) 2018-11-08 11:17:44 +01:00
Joakim Sørensen
a016dd2140 Bump pyhaversion to 2.0.2 (#18318) 2018-11-08 10:07:49 +01:00
Pascal Vizeli
878e369c4a Fix log error message (#18305)
* Fix log error message

* Update __init__.py
2018-11-08 10:07:48 +01:00
Nick Touran
599542394a Added optional precision configuration option to generic_thermostat. (#18317)
* Added optional precision configuration option to generic_thermostat.

* Added optional precision configuration option to generic_thermostat.

* Style update.
2018-11-08 09:39:35 +01:00
Joakim Sørensen
7fed49c4ab Bump pyhaversion to 2.0.2 (#18318) 2018-11-08 09:27:51 +01:00
Aaron Bach
954191c385 Add support for 17track.net package sensors (#18038)
* Add support for 17track.net package sensors

* Updated CODEOWNERS

* Addressing comments

* Fixed requirements

* Member comments

* Revert "Member comments"

This reverts commit 61a19d7966.

* Member comments

* Member comments
2018-11-07 22:25:08 -07:00
Pascal Vizeli
e2fca0691e Fix log error message (#18305)
* Fix log error message

* Update __init__.py
2018-11-08 00:33:51 +01:00
Paulus Schoutsen
f24979c7cf Bumped version to 0.82.0b4 2018-11-07 21:58:18 +01:00
Jeff Wilson
5bab0018f5 Make flux switch async (#18277)
* Make flux switch async

* Fix line lengths

* Fix indentation

* Set tracker before await to avoid race condition

* Fix lint errors
2018-11-07 21:52:55 +01:00
Pascal Vizeli
f541b101c9 Bugfix discovery (delete/mqtt) call for Hass.io (#18159)
* Bugfix discovery delete call for Hass.io

* Fix host

* fix tests
2018-11-07 21:45:47 +01:00
Joakim Sørensen
0bf054fb59 Update pyruter to 1.1.0 to be able to reuse aiohttp session. (#18310)
* Update pyruter to 1.1.0 to be able to reuse aiohttp session.

* Taged correct version of pyruter.
2018-11-07 13:54:33 -05:00
kennedyshead
aa4da479b5 Add upload and download sensors and component for asuswrt (#17757)
* Adds upload and download sensors for asuswrt and makes it a component.

* Rebase

* removes warnings

* Fixing review issues

* More robust connection phase

* Generate dependencies

* Not needed try catch

* Rename sensors

* Revorked tests so they can be turned on again

* Using component setup

* Test through correct setup

* Forgot we dont need to worry about older py
2018-11-07 18:32:13 +01:00
Steven Looman
d93716bd84 Add SUPPORT_SEEK for DLNA DMR devices + now (better) providing media_image_url for DLNA DMR devices (#18157)
* Upgrade to async_upnp_client==0.13.2, now (better) providing media_image_url for DLNA DMR devices

* Add SUPPORT_SEEK for DLNA DMR devices
2018-11-07 13:48:51 +01:00
Tsvi Mostovicz
f99701f41a Upgrade hdate to 0.7.5 (#18296)
In 0.7.x the API to HDate was cleaned up so as to move logic from homeassistant to
the HDate external library.

This commit removes all the superfluous code, updates the required tests and changes the
requirement from version 0.6.5 to 0.7.5
2018-11-07 13:30:41 +01:00
Jorim Tielemans
29be78e08e Improve version sensor (#18292)
* Validate value against valid list

* Show correct name

Constants in alphabetical order.
Added default name when not showing the local/current version.

* Add icon

Icon was already defined but not set

* Unnecessary "elif" after "return" (no-else-return)
2018-11-07 13:28:25 +01:00
majuss
ec732c896d Add support for Lupusec alarm control panel (#17691)
* Adds support for Lupusec alarm control panel

* fixed various mostly cosmetic issues

* fixed generic type of binary sensors

* fixed some formatting; removed scan interval completely -> defaults now to 2 secs

* removed unused data caches; added check if binary sensor class exists

* cosmetics

* generic type fix

* small fix

* small fixes

* guard clause added

* small fixes
2018-11-07 12:51:12 +01:00
Paulus Schoutsen
00c1b40940 Add translations 2018-11-07 10:45:29 +01:00
Paulus Schoutsen
4287d1dd2d Bump frontend to 20181107.0 2018-11-07 10:45:15 +01:00
Paulus Schoutsen
e9b8b290fc Bumped version to 0.82.0b3 2018-11-07 10:37:57 +01:00
mvn23
06b9600069 Bump pyotgw to 0.3b1 (#18286)
* Bump pyotgw to 0.3b1

* Update requirements_all.txt
2018-11-07 10:37:41 +01:00
Johann Kellerman
cff4755708 SMA Guard against older pysma (#18278) 2018-11-07 10:37:40 +01:00
Pascal Vizeli
17f04c1736 Migrate python-openzwave to homeassistant-pyozw (#18268)
* Migrate python-openzwave to homeassistant-pyozw

* Update requirements_all.txt

* Fix requirements
2018-11-07 10:37:05 +01:00
Mikko Tapionlinna
0b6aa38b13 Update pynetgear to 0.5.1 (#18238) 2018-11-07 10:36:28 +01:00
Paulus Schoutsen
782f5c7d19 Bump frontend to 20181103.2 2018-11-07 10:36:15 +01:00
Oleksii Serdiuk
5cee9942a6 Darksky: Add icon to summary sensors (#18275)
The icon changes dynamically, based on summary.
2018-11-07 10:01:05 +01:00
Adam Mills
65be458ce0 Update input component tests to async (#18290) 2018-11-07 09:56:32 +01:00
Adam Mills
ce069be16e Update manual ACP tests to async (#18289) 2018-11-07 09:56:24 +01:00
Adam Mills
0d7cb54872 Update litejet automation tests to async (#18287) 2018-11-07 09:56:10 +01:00
Adam Mills
6935b62487 Remove skipped device tracker tests (#18291) 2018-11-07 09:55:55 +01:00
mvn23
e698fc2553 Bump pyotgw to 0.3b1 (#18286)
* Bump pyotgw to 0.3b1

* Update requirements_all.txt
2018-11-07 09:55:22 +01:00
Sebastian Muszynski
df3d82e0e3 Use async_add_executor_job at the xiaomi_miio platforms (#18294) 2018-11-07 09:03:35 +01:00
Johann Kellerman
35ae85e14e SMA Guard against older pysma (#18278) 2018-11-07 08:52:51 +02:00
Pascal Vizeli
c89dade619 Migrate python-openzwave to homeassistant-pyozw (#18268)
* Migrate python-openzwave to homeassistant-pyozw

* Update requirements_all.txt

* Fix requirements
2018-11-06 22:08:04 +01:00
Rohan Kapoor
bdba3852d0 Split out geofency with a component and platform (#17933)
* Split out geofency with a component and platform

* Make geofency component/device_tracker more async

* Move geofency tests to new package

* Remove coroutine in geofency callback

* Lint

* Fix coroutine in geofency callback

* Fix incorrect patch
2018-11-06 20:12:03 +01:00
Joakim Sørensen
c41ca37a04 Add Norwegian Public Transportation sensor (Ruter). (#18237)
* Add Norwegian Public Transportation sensor (Ruter).

* Corrected typo.

* change stopid to stop_id, actually use attributes, changed logging, corrected link, removed unused variable.

* Change to RuterSensor for the class, and move logic to me more readable.

* Use correct sensor class.

* Add return if blank list, remove else
2018-11-06 19:49:38 +01:00
Adam Mills
917ebed4c9 Update PR checklist with commented out code check (#18272)
Along with hopefully helping resolve these actual problems, a nudge for people to look at their own code might help catch other quick fixes before anyone else has to review a PR.
2018-11-06 19:47:19 +01:00
Paulus Schoutsen
43ae57cc59 Lint 2018-11-06 19:27:52 +01:00
Pascal Vizeli
f4d3d5904e HmIP thermostat fix with operations (#18068)
* HmIP thermostat fix with operations

* Update homematic.py
2018-11-06 16:36:12 +01:00
Paulus Schoutsen
bde02afe4f Normalize MAC addresses (#16916)
* Normalize MAC addresses

* Handle all mac formats
2018-11-06 16:33:31 +01:00
Matt Schmitt
42fea4fb97 Add services to set/update and cancel Nest ETA (#17836)
* Add service to cancel ETA

* Update test requirements

* Change service name and update logging

* Reformat logging to verify structures
2018-11-06 16:11:10 +01:00
Daniel Shokouhi
52074ee9bb Update Neato states, actions and alerts based on Neato docs (#17353)
* Update neato sstates actions and alerts based on neato docs

* Remove unused STATES
2018-11-06 16:10:39 +01:00
Georgi Kirichkov
eb385515c8 Switch OwnTracks HTTP to use webhook component (#17034)
* Update OwnTracks_HTTP to use the webhook component

* Update owntracks_http.py

* Update owntracks_http.py
2018-11-06 16:10:17 +01:00
emontnemery
589764900a Move more MQTT platforms to config entries (#18180)
* Move Lock MQTT platform to config entries

* Move MQTT JSON Light platform to config entries

* Review comments

* Review comments

* Revert mqtt_json changes
2018-11-06 16:09:46 +01:00
Filip Bednárik
9329ec2486 Add support for switches in homekit controller (#17916) 2018-11-06 15:32:32 +01:00
Ludovico de Nittis
47af194d06 Add iAlarm "triggered" support (#18263) 2018-11-06 15:30:41 +01:00
kennedyshead
39412dc930 Adding current_humidity to attributes if its not None. (#18261)
* Adding current humidity if not None

* Removed the logic in supported feature

* More appropriate placement
2018-11-06 15:18:46 +01:00
Fabian Affolter
2e517ab6bc Enable config flow for Luftdaten (#17700)
* Move file to new location

* Update requirement

* Enable config flow

* Add luftdaten

* Add tests

* Update

* Add constants

* Changes according to the review comments

* Remove wrong entry from flows

* Fix dict handling

* Add callback and use OrderedDict

* Remve leftover

* Fix

* Remove await
2018-11-06 14:27:52 +01:00
Matthew Parlane
7933bd7f91 Allow alexa to simply turn on and off climate components. (#16989) 2018-11-06 14:17:56 +01:00
Sebastian Muszynski
58c77e1f55 Add Xiaomi Air Purifier 2s support (#18260) 2018-11-06 14:16:15 +01:00
Tyler
e3a8f3a106 Add input_boolean reporting to Prometheus (#17966) 2018-11-06 13:19:36 +01:00
akloeckner
1aba4699b9 Add attributes of ARP table (#17987)
* Add attributes of ARP table

This adds the device attributes available in the ARP table and a few more. Implementation is inspired by the nmap scanner.

* lint spaces
2018-11-06 13:15:48 +01:00
4lloyd
114bc8ec18 Support eco mode option on Ziggo Mediabox XL (#17990)
* Added eco mode option to Ziggo Mediabox XL

* Changed eco_mode_on to eco_mode

* Removed eco_mode option, the player is unavailable when offline

* Timeout on connection, on/off states are handled via update

* Improved state detection and added available property
2018-11-06 13:14:52 +01:00
kennedyshead
c6f3c239bb Melissa state_attributes (#18201)
* Melissa attributes

* overide device_state_attributes rather than state_attributes

* Selected attributes

* Adding current humidity rather than a state_attribute
2018-11-06 13:08:58 +01:00
Glen Takahashi
34d7758b4a Correct expose_by_default interaction with expose_domains (#17745)
Based on the documentation here: https://www.home-assistant.io/components/google_assistant/#expose_by_default it seems that expose_by_default means all devices should be exposed unless explicitly set to false, and that regardless if this is set domains in exposed_domains should be exposed.
2018-11-06 12:53:47 +01:00
Joakim Sørensen
2c36b9db1f Add support for Google Home device tracking (#18190)
* Add support for Google Home device tracking.

* Use dict[key] for options.

* Delete googlehome.py.save

* Change stylling of name, and attr mac_address to btle_mac_address, removed unesssesarry attributes copying.
2018-11-06 12:47:53 +01:00
Filip Bednárik
24efda20bf Add additional property to HomeKitSwitch to show whether the Homekit outlet is in use (#17448)
* Add additional property to HomeKitSwitch to show whether the Homekit outlet is in use or not

* Fix issues from review - Remove unused property, simplify and rename the device_state_attributes method
2018-11-06 12:43:47 +01:00
akloeckner
3322fee814 Fritz keepalive (#18155)
* Add keepalive support

- adds keepalive support
- adds debug messages
- corrects CONF_PHONEBOOK and CONF_PREFIXES constants

* Add keepalive config constant

* Fix default value

* More visual indentation

* move to platform

* Move to platform

* Make keepalive default and remove option

* Forgot a few lines
2018-11-06 12:43:16 +01:00
Matthias Urlichs
4581a741bd Report *which* component didn't return a bool (#18258)
* Report *which* component didn't return a bool

* break over-long line
2018-11-06 12:41:39 +01:00
Matthias Urlichs
b506aafbb4 docstring fix (#18257) 2018-11-06 12:41:15 +01:00
Jorim Tielemans
121ec5c684 Add season icons (#18221)
* Add season icons

* Use STATE constants

* Calm down hound

* Update season.py
2018-11-06 12:34:11 +01:00
Matthew Treinish
087bffeaae Add workaround to use notification state for zwave lock state (#17386)
* Add workaround to use notification state for zwave lock state

There are several zwave lock models out there which do not seem to
update the lock state on non-rf events (see #11934 #14632 #14534 for
examples) including kwikset smartkey zwave plus locks (which I own).
In these cases it seems that the notifications for non-rf events the
access_control value is updated but not the primary value for the
lock state, which is what is used to set the is_locked property. To
properly have the lock state accurate for all types of notifications
on these models we need to use the access_control field. This commit
adds a workaround for the 4 models reported to exhibit this behavior
so that home-assistant will reliably set the lock state for all
device notifications.

* Add YRD220 as per adrum to workaround list

* Inline constants
2018-11-06 11:00:48 +01:00
Andrew Hayworth
2bf2214d51 Add support for locks in google assistant component (#18233)
* Add support for locks in google assistant component

This is supported by the smarthome API, but there is no documentation
for it. This work is based on an article I found with screenshots of
documentation that was erroneously uploaded:

https://www.androidpolice.com/2018/01/17/google-assistant-home-can-now-natively-control-smart-locks-august-vivint-first-supported/

Google Assistant now supports unlocking certain locks - Nest and August
come to mind - via this API, and this commit allows Home Assistant to
do so as well.

Notably, I've added a config option `allow_unlock` that controls
whether we actually honor requests to unlock a lock via the google
assistant. It defaults to false.

Additionally, we add the functionNotSupported error, which makes a
little more sense when we're unable to execute the desired state
transition.

https://developers.google.com/actions/reference/smarthome/errors-exceptions#exception_list

* Fix linter warnings

* Ensure that certain groups are never exposed to cloud entities

For example, the group.all_locks entity - we should probably never
expose this to third party cloud integrations. It's risky.

This is not configurable, but can be extended by adding to the
cloud.const.NEVER_EXPOSED_ENTITIES array.

It's implemented in a modestly hacky fashion, because we determine
whether or not a entity should be excluded/included in several ways.

Notably, we define this array in the top level const.py, to avoid
circular import problems between the cloud/alexa components.
2018-11-06 10:39:10 +01:00
Tom Harris
ddee5f8b86 Fix IOLinc sensor (#18250) 2018-11-06 10:36:52 +01:00
Robert Svensson
c5d0440041 deCONZ - manual input fallback in config flow (#18116)
* Add config flow step for manual input
Remove support for loading discovery config from json file

* Small cleanup
Fix all translations to step user instead of step init

* Revert to using step_init

* Small cleanup
Add test_gateway that was forgotten in a previous PR

* Fix hound comment

* Fix empty pydocstring
2018-11-06 10:34:24 +01:00
Bram Kragten
24c110ad3c Lovelace: Duplicate ID check on load config + caching (#18152)
* Add caching + dupl. ID check

* duplicate imports...

* lint

* remove for/else

* found

* Missed one...
2018-11-05 20:12:31 -05:00
Glenn Waters
7077e19cf8 Elk-M1 fixes (#18154)
* Fix default value for temperature unit

* Add defaults for subdomains

* Remove unused import

* Fix PR comment
2018-11-05 20:09:07 -05:00
Mikko Tapionlinna
6f568d1cf6 Update pynetgear to 0.5.1 (#18238) 2018-11-05 19:00:46 -05:00
Sebastian Muszynski
d951ed4d68 Add Xiaomi Smartmi Fresh Air System support (#18097)
* Add Xiaomi Air Fresh VA2 support

* Add LED property again (available now)
2018-11-06 00:09:15 +01:00
Daniel Høyer Iversen
3366d2c1ad Tibber login validate (#18235)
* tibber login validate

* requirements
2018-11-05 23:29:42 +01:00
ehendrix23
46b5b6240f Improve debug log information (#18230)
Added debug log information for when records are purged and added entity_id to existing debug information to identify the entity the debug information is for.
2018-11-05 23:12:46 +01:00
Bram Kragten
abf147ed57 Check if os has chown (#18229) 2018-11-05 21:41:19 +01:00
quthla
c59b038512 Add scenes as switches HomeKit (#17799) 2018-11-05 21:36:30 +01:00
Daniel Høyer Iversen
93b16e7efb Mill room temp (#18203)
* mill, avg room temp

* typo

* Mill device_state_attributes
2018-11-05 20:52:34 +01:00
ehendrix23
561f6996c6 Duplicate entities on discovery (#18074)
* Enhancements for DirecTV media player

Following enhancements have been made:

1. Added debug logging
2. Added ability to change channel using select_source service of the remote platform.
3. State will now show paused if a recorded program is paused, for live TV playing will always be returned.
4. Added the following attributes:
    a. media_position: current position of the media (in seconds)
    b. media_position_updated_at: timestamp when media_position was updated.
   c. source: current source (channel).
   d. media_isbeingrecorded: if current media is being recorded or not.
   e. media_rating: TV/Movie rating of the media
   f. media_recorded: if current media is recorded or live TV
   g. media_starttime: Timestamp media was aired

Reordered properties to follow same order as how they are in __init__.py of remote platform.

* Fixed error and cleaned up few items

Fixed an issue when determining if a program is recorded or not.
Cleaned up some coding.

* Fix issue in checking if DTV device is already configured

If a DTV device was configured before, then discovery would add this device again seperately if the name specified in the configuration is different from the name on the DTV.

This issue is fixed now. Part of the fix also ensure to allow multiple "primary" devices on the network to be discovered.
Further also added debug logging to the setup_platform.

* Further improvements

Some additional improvements related to handling the DATA_DIRECTV in hass.data.

* Fixed flake8 issue

Fixed flake8 issue

* Added available property

Added available property

* Updated to use get_locations()

Replaced doing the request for getLocations with the get_locations() API from DirectPy instead.

* Fix for checking if device is available

Fix for checking if device is available and small update to debug log message.

* Fixed lint issue

Fixed lint issue with unused variable by adding ingore for it as this is for a enumerate

* Updated try/except and removed available

Updated tr/except having the except by the statement we're doing except on.
Removed available, will be a different PR.

* Updated known_devices to be tupples in a set

Updated known_devices to be a tupple in a set, removing loop to determine if client was already added.
2018-11-05 19:33:59 +01:00
vetegrodd
b261c4b7f8 Activate kodi media player progress bar (#17626)
* Added code for progress bar

* Added doc string

* Using in

* More cleaning

* Only update position if needed.
2018-11-05 12:39:37 -05:00
cdce8p
26ba4a56e8 Ignore duplicate state changes GarageDoor HomeKit (#18149)
* Ignore duplicate state changes GarageDoor HomeKit

* Don't ignore service_call
2018-11-05 16:42:19 +01:00
Robert Svensson
dcdae325ea deCONZ - reflect hub status on entities (#18106)
* Support for controlling entity available attribute based on gateways availability

* Fix string not being in imperative mood
2018-11-05 16:21:44 +01:00
ehendrix23
3d4ff74761 Add available property to DirecTV (#18168)
* Enhancements for DirecTV media player

Following enhancements have been made:

1. Added debug logging
2. Added ability to change channel using select_source service of the remote platform.
3. State will now show paused if a recorded program is paused, for live TV playing will always be returned.
4. Added the following attributes:
    a. media_position: current position of the media (in seconds)
    b. media_position_updated_at: timestamp when media_position was updated.
   c. source: current source (channel).
   d. media_isbeingrecorded: if current media is being recorded or not.
   e. media_rating: TV/Movie rating of the media
   f. media_recorded: if current media is recorded or live TV
   g. media_starttime: Timestamp media was aired

Reordered properties to follow same order as how they are in __init__.py of remote platform.

* Fixed error and cleaned up few items

Fixed an issue when determining if a program is recorded or not.
Cleaned up some coding.

* Fix issue in checking if DTV device is already configured

If a DTV device was configured before, then discovery would add this device again seperately if the name specified in the configuration is different from the name on the DTV.

This issue is fixed now. Part of the fix also ensure to allow multiple "primary" devices on the network to be discovered.
Further also added debug logging to the setup_platform.

* Further improvements

Some additional improvements related to handling the DATA_DIRECTV in hass.data.

* Fixed flake8 issue

Fixed flake8 issue

* Added available property

Added available property

* Updated to use get_locations()

Replaced doing the request for getLocations with the get_locations() API from DirectPy instead.

* Fix for checking if device is available

Fix for checking if device is available and small update to debug log message.

* Fixed lint issue

Fixed lint issue with unused variable by adding ingore for it as this is for a enumerate

* Updated try/except and removed available

Updated tr/except having the except by the statement we're doing except on.
Removed available, will be a different PR.

* Add available property

Add the available property to the entiry.
2018-11-05 16:19:03 +01:00
Paulus Schoutsen
81fa74e5ca Remove unrelated scripts (#18219)
* Remove influxDB scripts

* Remove ancient db migrator

* Update requirements
2018-11-05 16:14:34 +01:00
kennedyshead
f9f53fd278 Removes melissa sensors (they should be state attributes as implemented in #18201) (#18214) 2018-11-05 16:10:30 +01:00
Adam Belebczuk
36524e9d3f Bump version of pywemo to 0.4.29 (#18217) 2018-11-05 13:23:46 +01:00
Paulus Schoutsen
bf54582d76 Cloud conf (#18216)
* Add original config to entityfilter

* Add alexa/google config to cloud status call

* Lint
2018-11-05 13:21:03 +01:00
Marcel Hoppe
8de79ed57c add service to reconnect the bot (#18142) 2018-11-05 13:14:22 +01:00
quthla
8ee0e0c6c6 Turn off not cancellable scripts automatically HomeKit (#17793) 2018-11-05 11:11:26 +01:00
Joakim Sørensen
a901c594a9 Add Traccar device tracker (#18200)
* Add Traccar device tracker.

* Updated pytraccar to 0.1.1

* Adds default values for optional options.

* Use dict[key] for options.

* remove logging, duplicate by core
2018-11-05 09:28:02 +01:00
Paulus Schoutsen
2e9132873a Webhook names (#18206)
* Add new automation_info param to async_trigger

* Add domain and name to webhook registration and add WS command
2018-11-05 09:23:58 +01:00
Dav0815
6e4ce35a69 Add destination and icon (#18210)
* Add destination and icon

* Update test_transport_nsw.py

* Error handling fix in external lib

* Reverse sensor name change to prevent break
2018-11-05 08:27:20 +01:00
Andrea Tosatto
1c3ef8be55 Implemented tplink_lte components and notify service via SMS (#17111)
* Implemented tplink_lte components and notify service

* Device discovery for the notify component

* Improved the config schema. Small fixes

* Improved login retry mechanism

* Log successful connection only on retries

* Removed CancelledError handlers and small fixes
2018-11-05 02:09:29 +01:00
Ville Skyttä
922f34f72d Add more type hints to helpers (#18196)
* Test typing for helpers.__init__ and temperature

* Add type hints to helpers.sun

* Add type hints to helpers.signal

* Add type hints to helpers.entity_values

* Add type hints to helpers.dispatcher
2018-11-04 22:46:42 +01:00
cdce8p
959fa81ea6 Fix temperature interval Thermostat HomeKit (#18192)
* Will round to nearest .0 or .5
2018-11-04 22:04:51 +01:00
Martin Hjelmare
9a6c229b1d Refactor mysensors message handling (#17214)
* Refactor mysensors message handling

* Add handler module and register handlers per message type or message
  sub-type. This will allow easier extension of message handling in the
  future.
* Move some common functions to a helpers module.

* Add node handler and signal

* Fix inconsistent return

* Upgrade pymysensors to 0.18.0

* Fix bug in message modification.
2018-11-04 21:08:27 +01:00
rafale77
4a7507bcea Update python-openzwave to 0.4.11 (#18160)
* Update Python OpenZWave to 0.4.11

* Update requirements_all.txt
2018-11-04 14:21:53 -05:00
Johann Kellerman
44556a86e3 SMA: Optional import in schema & backoff fix (#18099) 2018-11-04 19:09:14 +02:00
Fabian Affolter
e161dc3b77 Upgrade toonlib to 1.1.3 (#18189) 2018-11-04 17:22:03 +01:00
Troy Kelly
dbf721cd2c Added AU (Australia) (#18183)
Added missing Australia region
2018-11-04 15:46:02 +01:00
jjlawren
0992e83f8d Remove config (breaking change) (#18153) 2018-11-04 15:20:32 +01:00
Pascal Vizeli
a498e15910 Add support for TensorFlow in official docker (#18191) 2018-11-04 15:19:48 +01:00
Pascal Vizeli
27e159f63f Handle TensorFlow like OpenCV (#18185)
* Handle TensorFlow like OpenCV

* Update requirements_all.txt
2018-11-04 15:15:14 +01:00
Paulus Schoutsen
7b53238f9b Merge pull request #18188 from home-assistant/816
0.81.6
2018-11-04 14:17:23 +01:00
Paulus Schoutsen
075169a7a9 Bumped version to 0.81.6 2018-11-04 13:20:25 +01:00
Pascal Vizeli
3abe49bace Bugfix discovery (delete/mqtt) call for Hass.io (#18159)
* Bugfix discovery delete call for Hass.io

* Fix host

* fix tests
2018-11-04 13:20:20 +01:00
Pascal Vizeli
eb0d989c88 Bugfix discovery (delete/mqtt) call for Hass.io (#18159)
* Bugfix discovery delete call for Hass.io

* Fix host

* fix tests
2018-11-04 12:19:04 +01:00
Jorim Tielemans
42cb23f768 Update Coinbase icons (#18172)
* Add extra icons

and don't rely on the name

* Use dictionary for icons

use safe get() with default value

* Use better vars
2018-11-04 09:50:00 +01:00
Florian Klien
5418e0510d XMPP HTTP upload (#17426)
* notify.xmpp: first working http upload

* extension guessing for upload

* docstrings, flake8, pylint

* hass.async_add_executor_job(...)

* catch more errors, allow unverified SSL request

allow user to specify unverified SSL request to URL
cleaner code
catch more exceptions

* pylint

* catching XMPP exceptions, timeout for requests call

removed calls for roster and presence
added timeout for upload request call
cleared up debug, info, warning messages
cleared up requests call for secure and insecure retrieval of image
catching IqError, IqTimeout, XMPPError from slixmpp
docstring updated

* added timout for http upload of local files

* timeout, mimetypes, random filenames

guessing filetypes and mimetypes with stdlib mimetypes
setting a random filename for privacy
working around slixmpp timeout issues with asyncio.wait_for

* code cleanup

* added file upload for rooms/groupchats

* version bump for slixmpp to 1.4.1

added NotConnectedError, removed double catches of IqErrors
removed asyncio import

* slixmpp 1.4.1 in requirements_all

* added url and path templating

* Minor changes

* fixed review requests

fixed possible path issue for foo/../bar/ paths
fixed possible access for non-whitelisted files
fixed None or X
fixed try-else block, moved else block into try
fixed raising error in upload_file if url is None
fixed using data.get after it's already been checked
fixed added docstring for tiny get_url function
2018-11-04 09:17:05 +01:00
Anders Melchiorsen
164c68093b Improve netgear_lte logging when unconnected (#18163)
* Improve netgear_lte logging when unconnected

* Use callback
2018-11-04 09:15:57 +01:00
Jorim Tielemans
5dd691e55d Rename huawei_lte.py to test_huawei_lte.py (#18170) 2018-11-04 01:33:05 +01:00
Corey Edwards
155df912e5 Add option to manually specify device detection method (#17852)
* Add option to manually specify device detection method

* Fix style and lint issue
2018-11-03 23:48:08 +01:00
Joakim Sørensen
610b0b6494 Add Tautulli sensor platform (#17835)
* Adds Tautulli as a sensor platform.

* Remove blank last line.

* Rewrite the platform to comply with review.

* Linting issues.

* Remove tailing newline.

* Corrected typo

* Correcte check_connection, removed wierd defaults, added line in imports, removed unused var, use the correct user list.

* Use dict[key] for required config options.

* Minor changes
2018-11-03 23:47:31 +01:00
rafale77
f76ccb636c Add support for various load level devices (#18161) 2018-11-03 22:25:05 +01:00
Paulus Schoutsen
fbcf0880f3 Bump frontend to 20181103.1 2018-11-03 19:18:20 +01:00
455 changed files with 16597 additions and 7471 deletions

View File

@@ -120,6 +120,9 @@ omit =
homeassistant/components/eufy.py
homeassistant/components/*/eufy.py
homeassistant/components/fibaro.py
homeassistant/components/*/fibaro.py
homeassistant/components/gc100.py
homeassistant/components/*/gc100.py
@@ -203,6 +206,9 @@ omit =
homeassistant/components/logi_circle.py
homeassistant/components/*/logi_circle.py
homeassistant/components/lupusec.py
homeassistant/components/*/lupusec.py
homeassistant/components/lutron.py
homeassistant/components/*/lutron.py
@@ -256,6 +262,10 @@ omit =
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
homeassistant/components/point/__init__.py
homeassistant/components/point/const.py
homeassistant/components/*/point.py
homeassistant/components/switch/qwikswitch.py
homeassistant/components/light/qwikswitch.py
@@ -265,7 +275,7 @@ omit =
homeassistant/components/raincloud.py
homeassistant/components/*/raincloud.py
homeassistant/components/rainmachine/*
homeassistant/components/rainmachine/__init__.py
homeassistant/components/*/rainmachine.py
homeassistant/components/raspihats.py
@@ -333,6 +343,9 @@ omit =
homeassistant/components/toon.py
homeassistant/components/*/toon.py
homeassistant/components/tplink_lte.py
homeassistant/components/*/tplink_lte.py
homeassistant/components/tradfri.py
homeassistant/components/*/tradfri.py
@@ -365,6 +378,9 @@ omit =
homeassistant/components/*/webostv.py
homeassistant/components/w800rf32.py
homeassistant/components/*/w800rf32.py
homeassistant/components/wemo.py
homeassistant/components/*/wemo.py
@@ -474,6 +490,7 @@ omit =
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
homeassistant/components/device_tracker/gpslogger.py
homeassistant/components/device_tracker/hitron_coda.py
homeassistant/components/device_tracker/huawei_router.py
@@ -496,6 +513,7 @@ omit =
homeassistant/components/device_tracker/tile.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/traccar.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/downloader.py
@@ -530,6 +548,7 @@ omit =
homeassistant/components/light/lw12wifi.py
homeassistant/components/light/mystrom.py
homeassistant/components/light/nanoleaf_aurora.py
homeassistant/components/light/niko_home_control.py
homeassistant/components/light/opple.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/piglow.py
@@ -572,7 +591,7 @@ omit =
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/lg_soundbar.py
homeassistant/components/media_player/lg_soundbar.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/mediaroom.py
homeassistant/components/media_player/mpchc.py
@@ -581,6 +600,7 @@ omit =
homeassistant/components/media_player/nadtcp.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/openhome.py
homeassistant/components/media_player/panasonic_bluray.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
homeassistant/components/media_player/philips_js.py
@@ -696,6 +716,7 @@ omit =
homeassistant/components/sensor/fints.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/flunearyou.py
homeassistant/components/sensor/folder.py
homeassistant/components/sensor/foobot.py
homeassistant/components/sensor/fritzbox_callmonitor.py
@@ -720,6 +741,7 @@ omit =
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lacrosse.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/launch_library.py
homeassistant/components/sensor/linky.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
@@ -763,10 +785,12 @@ omit =
homeassistant/components/sensor/rainbird.py
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/rtorrent.py
homeassistant/components/sensor/ruter.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sensehat.py
homeassistant/components/sensor/serial_pm.py
homeassistant/components/sensor/serial.py
homeassistant/components/sensor/seventeentrack.py
homeassistant/components/sensor/sht31.py
homeassistant/components/sensor/shodan.py
homeassistant/components/sensor/sigfox.py
@@ -786,9 +810,11 @@ omit =
homeassistant/components/sensor/swiss_public_transport.py
homeassistant/components/sensor/syncthru.py
homeassistant/components/sensor/synologydsm.py
homeassistant/components/sensor/srp_energy.py
homeassistant/components/sensor/systemmonitor.py
homeassistant/components/sensor/sytadin.py
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/tautulli.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
homeassistant/components/sensor/thermoworks_smoke.py

View File

@@ -13,6 +13,7 @@
## Checklist:
- [ ] The code change is tested and works locally.
- [ ] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass**
- [ ] There is no commented out code in this PR.
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.io](https://github.com/home-assistant/home-assistant.io)

View File

@@ -102,6 +102,7 @@ homeassistant/components/sensor/darksky.py @fabaff
homeassistant/components/sensor/file.py @fabaff
homeassistant/components/sensor/filter.py @dgomes
homeassistant/components/sensor/fixer.py @fabaff
homeassistant/components/sensor/flunearyou.py.py @bachya
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/gitter.py @fabaff
homeassistant/components/sensor/glances.py @fabaff
@@ -109,7 +110,6 @@ homeassistant/components/sensor/gpsd.py @fabaff
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/jewish_calendar.py @tsvi
homeassistant/components/sensor/linux_battery.py @fabaff
homeassistant/components/sensor/luftdaten.py @fabaff
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/min_max.py @fabaff
homeassistant/components/sensor/moon.py @fabaff
@@ -121,6 +121,7 @@ homeassistant/components/sensor/pvoutput.py @fabaff
homeassistant/components/sensor/qnap.py @colinodell
homeassistant/components/sensor/scrape.py @fabaff
homeassistant/components/sensor/serial.py @fabaff
homeassistant/components/sensor/seventeentrack.py @bachya
homeassistant/components/sensor/shodan.py @fabaff
homeassistant/components/sensor/sma.py @kellerza
homeassistant/components/sensor/sql.py @dgomes
@@ -189,6 +190,8 @@ homeassistant/components/*/konnected.py @heythisisnate
# L
homeassistant/components/lifx.py @amelchio
homeassistant/components/*/lifx.py @amelchio
homeassistant/components/luftdaten/* @fabaff
homeassistant/components/*/luftdaten.py @fabaff
# M
homeassistant/components/matrix.py @tinloaf

View File

@@ -13,6 +13,7 @@ from homeassistant.core import callback, HomeAssistant
from homeassistant.util import dt as dt_util
from . import auth_store, models
from .const import GROUP_ID_ADMIN
from .mfa_modules import auth_mfa_module_from_config, MultiFactorAuthModule
from .providers import auth_provider_from_config, AuthProvider, LoginFlow
@@ -117,6 +118,10 @@ class AuthManager:
"""Retrieve a user."""
return await self._store.async_get_user(user_id)
async def async_get_group(self, group_id: str) -> Optional[models.Group]:
"""Retrieve all groups."""
return await self._store.async_get_group(group_id)
async def async_get_user_by_credentials(
self, credentials: models.Credentials) -> Optional[models.User]:
"""Get a user by credential, return None if not found."""
@@ -127,13 +132,15 @@ class AuthManager:
return None
async def async_create_system_user(self, name: str) -> models.User:
async def async_create_system_user(
self, name: str,
group_ids: Optional[List[str]] = None) -> models.User:
"""Create a system user."""
user = await self._store.async_create_user(
name=name,
system_generated=True,
is_active=True,
groups=[],
group_ids=group_ids or [],
)
self.hass.bus.async_fire(EVENT_USER_ADDED, {
@@ -144,11 +151,10 @@ class AuthManager:
async def async_create_user(self, name: str) -> models.User:
"""Create a user."""
group = (await self._store.async_get_groups())[0]
kwargs = {
'name': name,
'is_active': True,
'groups': [group]
'group_ids': [GROUP_ID_ADMIN]
} # type: Dict[str, Any]
if await self._user_should_be_owner():
@@ -213,6 +219,17 @@ class AuthManager:
'user_id': user.id
})
async def async_update_user(self, user: models.User,
name: Optional[str] = None,
group_ids: Optional[List[str]] = None) -> None:
"""Update a user."""
kwargs = {} # type: Dict[str,Any]
if name is not None:
kwargs['name'] = name
if group_ids is not None:
kwargs['group_ids'] = group_ids
await self._store.async_update_user(user, **kwargs)
async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
await self._store.async_activate_user(user)

View File

@@ -10,11 +10,14 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.util import dt as dt_util
from . import models
from .permissions import DEFAULT_POLICY
from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
from .permissions import system_policies
from .permissions.types import PolicyType # noqa: F401
STORAGE_VERSION = 1
STORAGE_KEY = 'auth'
INITIAL_GROUP_NAME = 'All Access'
GROUP_NAME_ADMIN = 'Administrators'
GROUP_NAME_READ_ONLY = 'Read Only'
class AuthStore:
@@ -42,6 +45,14 @@ class AuthStore:
return list(self._groups.values())
async def async_get_group(self, group_id: str) -> Optional[models.Group]:
"""Retrieve all users."""
if self._groups is None:
await self._async_load()
assert self._groups is not None
return self._groups.get(group_id)
async def async_get_users(self) -> List[models.User]:
"""Retrieve all users."""
if self._users is None:
@@ -63,7 +74,7 @@ class AuthStore:
is_active: Optional[bool] = None,
system_generated: Optional[bool] = None,
credentials: Optional[models.Credentials] = None,
groups: Optional[List[models.Group]] = None) -> models.User:
group_ids: Optional[List[str]] = None) -> models.User:
"""Create a new user."""
if self._users is None:
await self._async_load()
@@ -71,11 +82,18 @@ class AuthStore:
assert self._users is not None
assert self._groups is not None
groups = []
for group_id in (group_ids or []):
group = self._groups.get(group_id)
if group is None:
raise ValueError('Invalid group specified {}'.format(group_id))
groups.append(group)
kwargs = {
'name': name,
# Until we get group management, we just put everyone in the
# same group.
'groups': groups or [],
'groups': groups,
} # type: Dict[str, Any]
if is_owner is not None:
@@ -115,6 +133,33 @@ class AuthStore:
self._users.pop(user.id)
self._async_schedule_save()
async def async_update_user(
self, user: models.User, name: Optional[str] = None,
is_active: Optional[bool] = None,
group_ids: Optional[List[str]] = None) -> None:
"""Update a user."""
assert self._groups is not None
if group_ids is not None:
groups = []
for grid in group_ids:
group = self._groups.get(grid)
if group is None:
raise ValueError("Invalid group specified.")
groups.append(group)
user.groups = groups
user.invalidate_permission_cache()
for attr_name, value in (
('name', name),
('is_active', is_active),
):
if value is not None:
setattr(user, attr_name, value)
self._async_schedule_save()
async def async_activate_user(self, user: models.User) -> None:
"""Activate a user."""
user.is_active = True
@@ -238,38 +283,98 @@ class AuthStore:
users = OrderedDict() # type: Dict[str, models.User]
groups = OrderedDict() # type: Dict[str, models.Group]
# When creating objects we mention each attribute explicetely. This
# Soft-migrating data as we load. We are going to make sure we have a
# read only group and an admin group. There are two states that we can
# migrate from:
# 1. Data from a recent version which has a single group without policy
# 2. Data from old version which has no groups
has_admin_group = False
has_read_only_group = False
group_without_policy = None
# When creating objects we mention each attribute explicitly. This
# prevents crashing if user rolls back HA version after a new property
# was added.
for group_dict in data.get('groups', []):
policy = None # type: Optional[PolicyType]
if group_dict['id'] == GROUP_ID_ADMIN:
has_admin_group = True
name = GROUP_NAME_ADMIN
policy = system_policies.ADMIN_POLICY
system_generated = True
elif group_dict['id'] == GROUP_ID_READ_ONLY:
has_read_only_group = True
name = GROUP_NAME_READ_ONLY
policy = system_policies.READ_ONLY_POLICY
system_generated = True
else:
name = group_dict['name']
policy = group_dict.get('policy')
system_generated = False
# We don't want groups without a policy that are not system groups
# This is part of migrating from state 1
if policy is None:
group_without_policy = group_dict['id']
continue
groups[group_dict['id']] = models.Group(
name=group_dict['name'],
id=group_dict['id'],
policy=group_dict.get('policy', DEFAULT_POLICY),
name=name,
policy=policy,
system_generated=system_generated,
)
migrate_group = None
# If there are no groups, add all existing users to the admin group.
# This is part of migrating from state 2
migrate_users_to_admin_group = (not groups and
group_without_policy is None)
if not groups:
migrate_group = models.Group(
name=INITIAL_GROUP_NAME,
policy=DEFAULT_POLICY
)
groups[migrate_group.id] = migrate_group
# If we find a no_policy_group, we need to migrate all users to the
# admin group. We only do this if there are no other groups, as is
# the expected state. If not expected state, not marking people admin.
# This is part of migrating from state 1
if groups and group_without_policy is not None:
group_without_policy = None
# This is part of migrating from state 1 and 2
if not has_admin_group:
admin_group = _system_admin_group()
groups[admin_group.id] = admin_group
# This is part of migrating from state 1 and 2
if not has_read_only_group:
read_only_group = _system_read_only_group()
groups[read_only_group.id] = read_only_group
for user_dict in data['users']:
# Collect the users group.
user_groups = []
for group_id in user_dict.get('group_ids', []):
# This is part of migrating from state 1
if group_id == group_without_policy:
group_id = GROUP_ID_ADMIN
user_groups.append(groups[group_id])
# This is part of migrating from state 2
if (not user_dict['system_generated'] and
migrate_users_to_admin_group):
user_groups.append(groups[GROUP_ID_ADMIN])
users[user_dict['id']] = models.User(
name=user_dict['name'],
groups=[groups[group_id] for group_id
in user_dict.get('group_ids', [])],
groups=user_groups,
id=user_dict['id'],
is_owner=user_dict['is_owner'],
is_active=user_dict['is_active'],
system_generated=user_dict['system_generated'],
)
if migrate_group is not None and not user_dict['system_generated']:
users[user_dict['id']].groups = [migrate_group]
for cred_dict in data['credentials']:
users[cred_dict['user_id']].credentials.append(models.Credentials(
@@ -356,11 +461,11 @@ class AuthStore:
groups = []
for group in self._groups.values():
g_dict = {
'name': group.name,
'id': group.id,
} # type: Dict[str, Any]
if group.policy is not DEFAULT_POLICY:
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)
@@ -410,13 +515,29 @@ class AuthStore:
"""Set default values for auth store."""
self._users = OrderedDict() # type: Dict[str, models.User]
# Add default group
all_access_group = models.Group(
name=INITIAL_GROUP_NAME,
policy=DEFAULT_POLICY,
)
groups = OrderedDict() # type: Dict[str, models.Group]
groups[all_access_group.id] = all_access_group
admin_group = _system_admin_group()
groups[admin_group.id] = admin_group
read_only_group = _system_read_only_group()
groups[read_only_group.id] = read_only_group
self._groups = groups
def _system_admin_group() -> models.Group:
"""Create system admin group."""
return models.Group(
name=GROUP_NAME_ADMIN,
id=GROUP_ID_ADMIN,
policy=system_policies.ADMIN_POLICY,
system_generated=True,
)
def _system_read_only_group() -> models.Group:
"""Create read only group."""
return models.Group(
name=GROUP_NAME_READ_ONLY,
id=GROUP_ID_READ_ONLY,
policy=system_policies.READ_ONLY_POLICY,
system_generated=True,
)

View File

@@ -3,3 +3,6 @@ from datetime import timedelta
ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
MFA_SESSION_EXPIRATION = timedelta(minutes=5)
GROUP_ID_ADMIN = 'system-admin'
GROUP_ID_READ_ONLY = 'system-read-only'

View File

@@ -8,6 +8,7 @@ import attr
from homeassistant.util import dt as dt_util
from . import permissions as perm_mdl
from .const import GROUP_ID_ADMIN
from .util import generate_secret
TOKEN_TYPE_NORMAL = 'normal'
@@ -22,6 +23,7 @@ class Group:
name = attr.ib(type=str) # type: Optional[str]
policy = attr.ib(type=perm_mdl.PolicyType)
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
system_generated = attr.ib(type=bool, default=False)
@attr.s(slots=True)
@@ -47,7 +49,7 @@ class User:
) # type: Dict[str, RefreshToken]
_permissions = attr.ib(
type=perm_mdl.PolicyPermissions,
type=Optional[perm_mdl.PolicyPermissions],
init=False,
cmp=False,
default=None,
@@ -68,6 +70,19 @@ class User:
return self._permissions
@property
def is_admin(self) -> bool:
"""Return if user is part of the admin group."""
if self.is_owner:
return True
return self.is_active and any(
gr.id == GROUP_ID_ADMIN for gr in self.groups)
def invalidate_permission_cache(self) -> None:
"""Invalidate permission cache."""
self._permissions = None
@attr.s(slots=True)
class RefreshToken:

View File

@@ -5,20 +5,11 @@ from typing import ( # noqa: F401
import voluptuous as vol
from homeassistant.core import State
from .common import CategoryType, PolicyType
from .const import CAT_ENTITIES
from .types import PolicyType
from .entities import ENTITY_POLICY_SCHEMA, compile_entities
from .merge import merge_policies # noqa
# Default policy if group has no policy applied.
DEFAULT_POLICY = {
"entities": True
} # type: PolicyType
CAT_ENTITIES = 'entities'
POLICY_SCHEMA = vol.Schema({
vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA
})
@@ -29,13 +20,20 @@ _LOGGER = logging.getLogger(__name__)
class AbstractPermissions:
"""Default permissions class."""
def check_entity(self, entity_id: str, key: str) -> bool:
"""Test if we can access entity."""
_cached_entity_func = None
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
raise NotImplementedError
def filter_states(self, states: List[State]) -> List[State]:
"""Filter a list of states for what the user is allowed to see."""
raise NotImplementedError
def check_entity(self, entity_id: str, key: str) -> bool:
"""Check if we can access entity."""
entity_func = self._cached_entity_func
if entity_func is None:
entity_func = self._cached_entity_func = self._entity_func()
return entity_func(entity_id, key)
class PolicyPermissions(AbstractPermissions):
@@ -44,34 +42,10 @@ class PolicyPermissions(AbstractPermissions):
def __init__(self, policy: PolicyType) -> None:
"""Initialize the permission class."""
self._policy = policy
self._compiled = {} # type: Dict[str, Callable[..., bool]]
def check_entity(self, entity_id: str, key: str) -> bool:
"""Test if we can access entity."""
func = self._policy_func(CAT_ENTITIES, compile_entities)
return func(entity_id, (key,))
def filter_states(self, states: List[State]) -> List[State]:
"""Filter a list of states for what the user is allowed to see."""
func = self._policy_func(CAT_ENTITIES, compile_entities)
keys = ('read',)
return [entity for entity in states if func(entity.entity_id, keys)]
def _policy_func(self, category: str,
compile_func: Callable[[CategoryType], Callable]) \
-> Callable[..., bool]:
"""Get a policy function."""
func = self._compiled.get(category)
if func:
return func
func = self._compiled[category] = compile_func(
self._policy.get(category))
_LOGGER.debug("Compiled %s func: %s", category, func)
return func
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
return compile_entities(self._policy.get(CAT_ENTITIES))
def __eq__(self, other: Any) -> bool:
"""Equals check."""
@@ -85,13 +59,9 @@ class _OwnerPermissions(AbstractPermissions):
# pylint: disable=no-self-use
def check_entity(self, entity_id: str, key: str) -> bool:
"""Test if we can access entity."""
return True
def filter_states(self, states: List[State]) -> List[State]:
"""Filter a list of states for what the user is allowed to see."""
return states
def _entity_func(self) -> Callable[[str, str], bool]:
"""Return a function that can test entity access."""
return lambda entity_id, key: True
OwnerPermissions = _OwnerPermissions() # pylint: disable=invalid-name

View File

@@ -0,0 +1,7 @@
"""Permission constants."""
CAT_ENTITIES = 'entities'
SUBCAT_ALL = 'all'
POLICY_READ = 'read'
POLICY_CONTROL = 'control'
POLICY_EDIT = 'edit'

View File

@@ -5,12 +5,8 @@ from typing import ( # noqa: F401
import voluptuous as vol
from .common import CategoryType, ValueType, SUBCAT_ALL
POLICY_READ = 'read'
POLICY_CONTROL = 'control'
POLICY_EDIT = 'edit'
from .const import SUBCAT_ALL, POLICY_READ, POLICY_CONTROL, POLICY_EDIT
from .types import CategoryType, ValueType
SINGLE_ENTITY_SCHEMA = vol.Any(True, vol.Schema({
vol.Optional(POLICY_READ): True,
@@ -32,28 +28,28 @@ ENTITY_POLICY_SCHEMA = vol.Any(True, vol.Schema({
}))
def _entity_allowed(schema: ValueType, keys: Tuple[str]) \
def _entity_allowed(schema: ValueType, key: str) \
-> Union[bool, None]:
"""Test if an entity is allowed based on the keys."""
if schema is None or isinstance(schema, bool):
return schema
assert isinstance(schema, dict)
return schema.get(keys[0])
return schema.get(key)
def compile_entities(policy: CategoryType) \
-> Callable[[str, Tuple[str]], bool]:
-> Callable[[str, str], bool]:
"""Compile policy into a function that tests policy."""
# None, Empty Dict, False
if not policy:
def apply_policy_deny_all(entity_id: str, keys: Tuple[str]) -> bool:
def apply_policy_deny_all(entity_id: str, key: str) -> bool:
"""Decline all."""
return False
return apply_policy_deny_all
if policy is True:
def apply_policy_allow_all(entity_id: str, keys: Tuple[str]) -> bool:
def apply_policy_allow_all(entity_id: str, key: str) -> bool:
"""Approve all."""
return True
@@ -65,7 +61,7 @@ def compile_entities(policy: CategoryType) \
entity_ids = policy.get(ENTITY_ENTITY_IDS)
all_entities = policy.get(SUBCAT_ALL)
funcs = [] # type: List[Callable[[str, Tuple[str]], Union[None, bool]]]
funcs = [] # type: List[Callable[[str, str], Union[None, bool]]]
# The order of these functions matter. The more precise are at the top.
# If a function returns None, they cannot handle it.
@@ -74,23 +70,23 @@ def compile_entities(policy: CategoryType) \
# Setting entity_ids to a boolean is final decision for permissions
# So return right away.
if isinstance(entity_ids, bool):
def allowed_entity_id_bool(entity_id: str, keys: Tuple[str]) -> bool:
def allowed_entity_id_bool(entity_id: str, key: str) -> bool:
"""Test if allowed entity_id."""
return entity_ids # type: ignore
return allowed_entity_id_bool
if entity_ids is not None:
def allowed_entity_id_dict(entity_id: str, keys: Tuple[str]) \
def allowed_entity_id_dict(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed entity_id."""
return _entity_allowed(
entity_ids.get(entity_id), keys) # type: ignore
entity_ids.get(entity_id), key) # type: ignore
funcs.append(allowed_entity_id_dict)
if isinstance(domains, bool):
def allowed_domain_bool(entity_id: str, keys: Tuple[str]) \
def allowed_domain_bool(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed domain."""
return domains
@@ -98,31 +94,31 @@ def compile_entities(policy: CategoryType) \
funcs.append(allowed_domain_bool)
elif domains is not None:
def allowed_domain_dict(entity_id: str, keys: Tuple[str]) \
def allowed_domain_dict(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed domain."""
domain = entity_id.split(".", 1)[0]
return _entity_allowed(domains.get(domain), keys) # type: ignore
return _entity_allowed(domains.get(domain), key) # type: ignore
funcs.append(allowed_domain_dict)
if isinstance(all_entities, bool):
def allowed_all_entities_bool(entity_id: str, keys: Tuple[str]) \
def allowed_all_entities_bool(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed domain."""
return all_entities
funcs.append(allowed_all_entities_bool)
elif all_entities is not None:
def allowed_all_entities_dict(entity_id: str, keys: Tuple[str]) \
def allowed_all_entities_dict(entity_id: str, key: str) \
-> Union[None, bool]:
"""Test if allowed domain."""
return _entity_allowed(all_entities, keys)
return _entity_allowed(all_entities, key)
funcs.append(allowed_all_entities_dict)
# Can happen if no valid subcategories specified
if not funcs:
def apply_policy_deny_all_2(entity_id: str, keys: Tuple[str]) -> bool:
def apply_policy_deny_all_2(entity_id: str, key: str) -> bool:
"""Decline all."""
return False
@@ -132,16 +128,16 @@ def compile_entities(policy: CategoryType) \
func = funcs[0]
@wraps(func)
def apply_policy_func(entity_id: str, keys: Tuple[str]) -> bool:
def apply_policy_func(entity_id: str, key: str) -> bool:
"""Apply a single policy function."""
return func(entity_id, keys) is True
return func(entity_id, key) is True
return apply_policy_func
def apply_policy_funcs(entity_id: str, keys: Tuple[str]) -> bool:
def apply_policy_funcs(entity_id: str, key: str) -> bool:
"""Apply several policy functions."""
for func in funcs:
result = func(entity_id, keys)
result = func(entity_id, key)
if result is not None:
return result
return False

View File

@@ -2,7 +2,7 @@
from typing import ( # noqa: F401
cast, Dict, List, Set)
from .common import PolicyType, CategoryType
from .types import PolicyType, CategoryType
def merge_policies(policies: List[PolicyType]) -> PolicyType:

View File

@@ -0,0 +1,14 @@
"""System policies."""
from .const import CAT_ENTITIES, SUBCAT_ALL, POLICY_READ
ADMIN_POLICY = {
CAT_ENTITIES: True,
}
READ_ONLY_POLICY = {
CAT_ENTITIES: {
SUBCAT_ALL: {
POLICY_READ: True
}
}
}

View File

@@ -29,5 +29,3 @@ CategoryType = Union[
# Example: { entities: … }
PolicyType = Mapping[str, CategoryType]
SUBCAT_ALL = 'all'

View File

@@ -13,9 +13,10 @@ from homeassistant.const import (
CONF_PENDING_TIME, CONF_TRIGGER_TIME)
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 Demo alarm control panel platform."""
add_entities([
async_add_entities([
manual.ManualAlarm(hass, 'Alarm', '1234', None, False, {
STATE_ALARM_ARMED_AWAY: {
CONF_DELAY_TIME: datetime.timedelta(seconds=0),

View File

@@ -12,10 +12,10 @@ 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_ARMED_HOME, STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyialarm==0.2']
REQUIREMENTS = ['pyialarm==0.3']
_LOGGER = logging.getLogger(__name__)
@@ -89,6 +89,8 @@ class IAlarmPanel(alarm.AlarmControlPanel):
state = STATE_ALARM_ARMED_AWAY
elif status == self._client.ARMED_STAY:
state = STATE_ALARM_ARMED_HOME
elif status == self._client.TRIGGERED:
state = STATE_ALARM_TRIGGERED
else:
state = None

View File

@@ -0,0 +1,67 @@
"""
This component provides HA alarm_control_panel support for Lupusec System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.lupusec/
"""
from datetime import timedelta
from homeassistant.components.alarm_control_panel import AlarmControlPanel
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)
DEPENDENCIES = ['lupusec']
ICON = 'mdi:security'
SCAN_INTERVAL = timedelta(seconds=2)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up an alarm control panel for a Lupusec device."""
if discovery_info is None:
return
data = hass.data[LUPUSEC_DOMAIN]
alarm_devices = [LupusecAlarm(data, data.lupusec.get_alarm())]
add_entities(alarm_devices)
class LupusecAlarm(LupusecDevice, AlarmControlPanel):
"""An alarm_control_panel implementation for Lupusec."""
@property
def icon(self):
"""Return the icon."""
return ICON
@property
def state(self):
"""Return the state of the device."""
if self._device.is_standby:
state = STATE_ALARM_DISARMED
elif self._device.is_away:
state = STATE_ALARM_ARMED_AWAY
elif self._device.is_home:
state = STATE_ALARM_ARMED_HOME
else:
state = None
return state
def alarm_arm_away(self, code=None):
"""Send arm away command."""
self._device.set_away()
def alarm_disarm(self, code=None):
"""Send disarm command."""
self._device.set_standby()
def alarm_arm_home(self, code=None):
"""Send arm home command."""
self._device.set_home()

View File

@@ -335,11 +335,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
return state_attr
def async_added_to_hass(self):
"""Subscribe to MQTT events.
This method must be run in the event loop and returns a coroutine.
"""
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
async_track_state_change(
self.hass, self.entity_id, self._async_state_changed_listener
)
@@ -359,7 +356,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
_LOGGER.warning("Received unexpected payload: %s", payload)
return
return mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._command_topic, message_received, self._qos)
async def _async_state_changed_listener(self, entity_id, old_state,

View File

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

View File

@@ -16,9 +16,9 @@ from homeassistant.components import (
input_boolean, light, lock, media_player, scene, script, sensor, switch)
from homeassistant.const import (
ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES,
ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CLOUD_NEVER_EXPOSED_ENTITIES,
CONF_NAME, SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
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)
@@ -474,6 +474,26 @@ class _AlexaColorController(_AlexaInterface):
def name(self):
return 'Alexa.ColorController'
def properties_supported(self):
return [{'name': 'color'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'color':
raise _UnsupportedProperty(name)
hue, saturation = self.entity.attributes.get(
light.ATTR_HS_COLOR, (0, 0))
return {
'hue': hue,
'saturation': saturation / 100.0,
'brightness': self.entity.attributes.get(
light.ATTR_BRIGHTNESS, 0) / 255.0,
}
class _AlexaColorTemperatureController(_AlexaInterface):
"""Implements Alexa.ColorTemperatureController.
@@ -717,6 +737,9 @@ class _ClimateCapabilities(_AlexaEntity):
return [_DisplayCategory.THERMOSTAT]
def interfaces(self):
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.SUPPORT_ON_OFF:
yield _AlexaPowerController(self.entity)
yield _AlexaThermostatController(self.hass, self.entity)
yield _AlexaTemperatureSensor(self.hass, self.entity)
@@ -1194,6 +1217,11 @@ async def async_api_discovery(hass, config, directive, context):
discovery_endpoints = []
for entity in hass.states.async_all():
if entity.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
_LOGGER.debug("Not exposing %s because it is never exposed",
entity.entity_id)
continue
if not config.should_expose(entity.entity_id):
_LOGGER.debug("Not exposing %s because filtered by config",
entity.entity_id)
@@ -1205,7 +1233,7 @@ async def async_api_discovery(hass, config, directive, context):
endpoint = {
'displayCategories': alexa_entity.display_categories(),
'additionalApplianceDetails': {},
'cookie': {},
'endpointId': alexa_entity.entity_id(),
'friendlyName': alexa_entity.friendly_name(),
'description': alexa_entity.description(),

View File

@@ -20,7 +20,8 @@ from homeassistant.const import (
URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM,
URL_API_TEMPLATE, __version__)
import homeassistant.core as ha
from homeassistant.exceptions import TemplateError
from homeassistant.auth.permissions.const import POLICY_READ
from homeassistant.exceptions import TemplateError, Unauthorized
from homeassistant.helpers import template
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates
@@ -81,6 +82,8 @@ class APIEventStream(HomeAssistantView):
async def get(self, request):
"""Provide a streaming interface for the event bus."""
if not request['hass_user'].is_admin:
raise Unauthorized()
hass = request.app['hass']
stop_obj = object()
to_write = asyncio.Queue(loop=hass.loop)
@@ -185,7 +188,13 @@ class APIStatesView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get current states."""
return self.json(request.app['hass'].states.async_all())
user = request['hass_user']
entity_perm = user.permissions.check_entity
states = [
state for state in request.app['hass'].states.async_all()
if entity_perm(state.entity_id, 'read')
]
return self.json(states)
class APIEntityStateView(HomeAssistantView):
@@ -197,6 +206,10 @@ class APIEntityStateView(HomeAssistantView):
@ha.callback
def get(self, request, entity_id):
"""Retrieve state of entity."""
user = request['hass_user']
if not user.permissions.check_entity(entity_id, POLICY_READ):
raise Unauthorized(entity_id=entity_id)
state = request.app['hass'].states.get(entity_id)
if state:
return self.json(state)
@@ -204,6 +217,8 @@ class APIEntityStateView(HomeAssistantView):
async def post(self, request, entity_id):
"""Update state of entity."""
if not request['hass_user'].is_admin:
raise Unauthorized(entity_id=entity_id)
hass = request.app['hass']
try:
data = await request.json()
@@ -236,6 +251,8 @@ class APIEntityStateView(HomeAssistantView):
@ha.callback
def delete(self, request, entity_id):
"""Remove entity."""
if not request['hass_user'].is_admin:
raise Unauthorized(entity_id=entity_id)
if request.app['hass'].states.async_remove(entity_id):
return self.json_message("Entity removed.")
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
@@ -261,6 +278,8 @@ class APIEventView(HomeAssistantView):
async def post(self, request, event_type):
"""Fire events."""
if not request['hass_user'].is_admin:
raise Unauthorized()
body = await request.text()
try:
event_data = json.loads(body) if body else None
@@ -346,6 +365,8 @@ class APITemplateView(HomeAssistantView):
async def post(self, request):
"""Render a template."""
if not request['hass_user'].is_admin:
raise Unauthorized()
try:
data = await request.json()
tpl = template.Template(data['template'], request.app['hass'])
@@ -363,6 +384,8 @@ class APIErrorLog(HomeAssistantView):
async def get(self, request):
"""Retrieve API error log."""
if not request['hass_user'].is_admin:
raise Unauthorized()
return web.FileResponse(request.app['hass'].data[DATA_LOGGING])

View File

@@ -0,0 +1,68 @@
"""
Support for ASUSWRT devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/asuswrt/
"""
import logging
import voluptuous as vol
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE,
CONF_PROTOCOL)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
REQUIREMENTS = ['aioasuswrt==1.1.11']
_LOGGER = logging.getLogger(__name__)
DOMAIN = "asuswrt"
DATA_ASUSWRT = DOMAIN
CONF_PUB_KEY = 'pub_key'
CONF_SSH_KEY = 'ssh_key'
CONF_REQUIRE_IP = 'require_ip'
DEFAULT_SSH_PORT = 22
SECRET_GROUP = 'Password or SSH Key'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PROTOCOL, default='ssh'): vol.In(['ssh', 'telnet']),
vol.Optional(CONF_MODE, default='router'): vol.In(['router', 'ap']),
vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port,
vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean,
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile
}),
}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass, config):
"""Set up the asuswrt component."""
from aioasuswrt.asuswrt import AsusWrt
conf = config[DOMAIN]
api = AsusWrt(conf[CONF_HOST], conf.get(CONF_PORT),
conf.get(CONF_PROTOCOL) == 'telnet',
conf[CONF_USERNAME],
conf.get(CONF_PASSWORD, ''),
conf.get('ssh_key', conf.get('pub_key', '')),
conf.get(CONF_MODE), conf.get(CONF_REQUIRE_IP))
await api.connection.async_connect()
if not api.is_connected:
_LOGGER.error("Unable to setup asuswrt component")
return False
hass.data[DATA_ASUSWRT] = api
hass.async_create_task(async_load_platform(
hass, 'sensor', DOMAIN, {}, config))
hass.async_create_task(async_load_platform(
hass, 'device_tracker', DOMAIN, {}, config))
return True

View File

@@ -11,8 +11,9 @@ 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)
CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
@@ -20,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
REQUIREMENTS = ['py-august==0.6.0']
REQUIREMENTS = ['py-august==0.7.0']
DEFAULT_TIMEOUT = 10
ACTIVITY_FETCH_LIMIT = 10
@@ -116,7 +117,8 @@ def setup_august(hass, config, api, authenticator):
if DOMAIN in _CONFIGURING:
hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN))
hass.data[DATA_AUGUST] = AugustData(api, authentication.access_token)
hass.data[DATA_AUGUST] = AugustData(
hass, api, authentication.access_token)
for component in AUGUST_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
@@ -136,9 +138,16 @@ def setup(hass, config):
"""Set up the August component."""
from august.api import Api
from august.authenticator import Authenticator
from requests import Session
conf = config[DOMAIN]
api = Api(timeout=conf.get(CONF_TIMEOUT))
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)
authenticator = Authenticator(
api,
@@ -154,8 +163,9 @@ def setup(hass, config):
class AugustData:
"""August data object."""
def __init__(self, api, access_token):
def __init__(self, hass, api, access_token):
"""Init August data object."""
self._hass = hass
self._api = api
self._access_token = access_token
self._doorbells = self._api.get_doorbells(self._access_token) or []
@@ -168,6 +178,22 @@ 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."""
@@ -201,8 +227,11 @@ class AugustData:
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
"""Update data object with latest from August API."""
_LOGGER.debug("Updating device activities")
_LOGGER.debug("Start retrieving device activities")
for house_id in self.house_ids:
_LOGGER.debug("Updating device activity for house id %s",
house_id)
activities = self._api.get_house_activities(self._access_token,
house_id,
limit=limit)
@@ -211,6 +240,7 @@ class AugustData:
for device_id in device_ids:
self._activities_by_id[device_id] = [a for a in activities if
a.device_id == device_id]
_LOGGER.debug("Completed retrieving device activities")
def get_doorbell_detail(self, doorbell_id):
"""Return doorbell detail."""
@@ -223,7 +253,7 @@ class AugustData:
_LOGGER.debug("Start retrieving doorbell details")
for doorbell in self._doorbells:
_LOGGER.debug("Updating status for %s",
_LOGGER.debug("Updating doorbell status for %s",
doorbell.device_name)
try:
detail_by_id[doorbell.device_id] =\
@@ -267,7 +297,7 @@ class AugustData:
_LOGGER.debug("Start retrieving door status")
for lock in self._locks:
_LOGGER.debug("Updating status for %s",
_LOGGER.debug("Updating door status for %s",
lock.device_name)
try:
@@ -291,7 +321,7 @@ class AugustData:
_LOGGER.debug("Start retrieving locks status")
for lock in self._locks:
_LOGGER.debug("Updating status for %s",
_LOGGER.debug("Updating lock status for %s",
lock.device_name)
try:
status_by_id[lock.device_id] = self._api.get_lock_status(

View File

@@ -0,0 +1,26 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "\u017d\u00e1dn\u00e9 oznamovac\u00ed slu\u017eby nejsou k dispozici."
},
"error": {
"invalid_code": "Neplatn\u00fd k\u00f3d, zkuste to znovu."
},
"step": {
"init": {
"description": "Vyberte pros\u00edm jednu z oznamovac\u00edch slu\u017eeb:",
"title": "Nastavte jednor\u00e1zov\u00e9 heslo dodan\u00e9 komponentou notify"
},
"setup": {
"title": "Ov\u011b\u0159en\u00ed nastaven\u00ed"
}
}
},
"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."
}
}
}
}

View File

@@ -0,0 +1,35 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "No hay servicios de notificaci\u00f3n disponibles."
},
"error": {
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor int\u00e9ntelo de nuevo."
},
"step": {
"init": {
"description": "Seleccione uno de los servicios de notificaci\u00f3n:",
"title": "Configure una contrase\u00f1a de un solo uso entregada por el componente de notificaci\u00f3n"
},
"setup": {
"description": "Se ha enviado una contrase\u00f1a de un solo uso a trav\u00e9s de ** notificar. {notify_service} **. Por favor introd\u00facela a continuaci\u00f3n:",
"title": "Verificar la configuraci\u00f3n"
}
},
"title": "Notificar la contrase\u00f1a de un solo uso"
},
"totp": {
"error": {
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor int\u00e9ntalo de nuevo. Si recibes este error de forma consistente, por favor aseg\u00farate de que el reloj de tu Home Assistant es correcto."
},
"step": {
"init": {
"description": "Para activar la autenticaci\u00f3n de dos factores utilizando contrase\u00f1as de un solo uso basadas en el tiempo, escanea el c\u00f3digo QR con tu aplicaci\u00f3n de autenticaci\u00f3n. Si no tienes una, te recomendamos [Autenticador de Google] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \nDespu\u00e9s de escanear el c\u00f3digo, introduce el c\u00f3digo de seis d\u00edgitos de tu aplicaci\u00f3n para verificar la configuraci\u00f3n. Si tienes problemas para escanear el c\u00f3digo QR, realiza una configuraci\u00f3n manual con el c\u00f3digo ** ` {code} ` **.",
"title": "Configure la autenticaci\u00f3n de dos factores utilizando TOTP"
}
},
"title": "TOTP"
}
}
}

View File

@@ -1,6 +1,27 @@
{
"mfa_setup": {
"notify": {
"abort": {
"no_available_service": "Nessun servizio di notifica disponibile."
},
"error": {
"invalid_code": "Codice non valido, per favore riprovare."
},
"step": {
"init": {
"description": "Selezionare uno dei servizi di notifica:"
},
"setup": {
"description": "\u00c8 stata inviata una password monouso tramite **notify.{notify_service}**. Per favore, inseriscila qui sotto:",
"title": "Verifica l'installazione"
}
},
"title": "Notifica la Password monouso"
},
"totp": {
"error": {
"invalid_code": "Codice non valido, per favore riprovare. Se riscontri spesso questo errore, assicurati che l'orologio del sistema Home Assistant sia accurato."
},
"step": {
"init": {
"description": "Per attivare l'autenticazione a due fattori utilizzando password monouso basate sul tempo, eseguire la scansione del codice QR con l'app di autenticazione. Se non ne hai uno, ti consigliamo [Google Authenticator] (https://support.google.com/accounts/answer/1066447) o [Authy] (https://authy.com/). \n\n {qr_code} \n \n Dopo aver scansionato il codice, inserisci il codice a sei cifre dalla tua app per verificare la configurazione. Se riscontri problemi con la scansione del codice QR, esegui una configurazione manuale con codice ** ` {code} ` **.",

View File

@@ -400,6 +400,9 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
This method is a coroutine.
"""
removes = []
info = {
'name': name
}
for conf in trigger_configs:
platform = await async_prepare_setup_platform(
@@ -408,7 +411,7 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action):
if platform is None:
return None
remove = await platform.async_trigger(hass, conf, action)
remove = await platform.async_trigger(hass, conf, action, info)
if not remove:
_LOGGER.error("Error setting up trigger %s", name)

View File

@@ -24,7 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data_schema = vol.Schema(

View File

@@ -33,7 +33,7 @@ def source_match(state, source):
return state and state.attributes.get('source') == source
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
source = config.get(CONF_SOURCE).lower()
zone_entity_id = config.get(CONF_ZONE)

View File

@@ -22,7 +22,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)

View File

@@ -32,7 +32,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
number = config.get(CONF_NUMBER)
held_more_than = config.get(CONF_HELD_MORE_THAN)

View File

@@ -24,7 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)

View File

@@ -28,7 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__)
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW)

View File

@@ -26,7 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
}), cv.key_dependency(CONF_FOR, CONF_TO))
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)

View File

@@ -24,7 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)

View File

@@ -22,7 +22,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass

View File

@@ -28,7 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
if CONF_AT in config:
at_time = config.get(CONF_AT)

View File

@@ -14,6 +14,8 @@ from homeassistant.core import callback
from homeassistant.const import CONF_PLATFORM, CONF_WEBHOOK_ID
import homeassistant.helpers.config_validation as cv
from . import DOMAIN as AUTOMATION_DOMAIN
DEPENDENCIES = ('webhook',)
_LOGGER = logging.getLogger(__name__)
@@ -39,10 +41,11 @@ async def _handle_webhook(action, hass, webhook_id, request):
hass.async_run_job(action, {'trigger': result})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Trigger based on incoming webhooks."""
webhook_id = config.get(CONF_WEBHOOK_ID)
hass.components.webhook.async_register(
AUTOMATION_DOMAIN, automation_info['name'],
webhook_id, partial(_handle_webhook, action))
@callback

View File

@@ -26,7 +26,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
async def async_trigger(hass, config, action):
async def async_trigger(hass, config, action, automation_info):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)

View File

@@ -6,8 +6,8 @@ https://home-assistant.io/components/binary_sensor.deconz/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz.const import (
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ,
DECONZ_DOMAIN)
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DECONZ_REACHABLE,
DOMAIN as DECONZ_DOMAIN)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
@@ -24,6 +24,8 @@ async def async_setup_platform(hass, config, async_add_entities,
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ binary sensor."""
gateway = hass.data[DECONZ_DOMAIN]
@callback
def async_add_sensor(sensors):
"""Add binary sensor from deCONZ."""
@@ -33,30 +35,35 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for sensor in sensors:
if sensor.type in DECONZ_BINARY_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
entities.append(DeconzBinarySensor(sensor))
entities.append(DeconzBinarySensor(sensor, gateway))
async_add_entities(entities, True)
hass.data[DATA_DECONZ].listeners.append(
gateway.listeners.append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
async_add_sensor(hass.data[DATA_DECONZ].api.sensors.values())
async_add_sensor(gateway.api.sensors.values())
class DeconzBinarySensor(BinarySensorDevice):
"""Representation of a binary sensor."""
def __init__(self, sensor):
def __init__(self, sensor, gateway):
"""Set up sensor and add update callback to get data from websocket."""
self._sensor = sensor
self.gateway = gateway
self.unsub_dispatcher = None
async def async_added_to_hass(self):
"""Subscribe sensors events."""
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._sensor.deconz_id
self.gateway.deconz_ids[self.entity_id] = self._sensor.deconz_id
self.unsub_dispatcher = async_dispatcher_connect(
self.hass, DECONZ_REACHABLE, self.async_update_callback)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect sensor object when removed."""
if self.unsub_dispatcher is not None:
self.unsub_dispatcher()
self._sensor.remove_callback(self.async_update_callback)
self._sensor = None
@@ -101,7 +108,7 @@ class DeconzBinarySensor(BinarySensorDevice):
@property
def available(self):
"""Return True if sensor is available."""
return self._sensor.reachable
return self.gateway.available and self._sensor.reachable
@property
def should_poll(self):
@@ -128,7 +135,7 @@ class DeconzBinarySensor(BinarySensorDevice):
self._sensor.uniqueid.count(':') != 7):
return None
serial = self._sensor.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
bridgeid = self.gateway.api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@@ -0,0 +1,74 @@
"""
Support for Fibaro binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.fibaro/
"""
import logging
from homeassistant.components.binary_sensor import (
BinarySensorDevice, ENTITY_ID_FORMAT)
from homeassistant.components.fibaro import (
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
DEPENDENCIES = ['fibaro']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'com.fibaro.doorSensor': ['Door', 'mdi:window-open', 'door'],
'com.fibaro.windowSensor': ['Window', 'mdi:window-open', 'window'],
'com.fibaro.smokeSensor': ['Smoke', 'mdi:smoking', 'smoke'],
'com.fibaro.FGMS001': ['Motion', 'mdi:run', 'motion'],
'com.fibaro.heatDetector': ['Heat', 'mdi:fire', 'heat'],
}
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Perform the setup for Fibaro controller devices."""
if discovery_info is None:
return
add_entities(
[FibaroBinarySensor(device, hass.data[FIBARO_CONTROLLER])
for device in hass.data[FIBARO_DEVICES]['binary_sensor']], True)
class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
"""Representation of a Fibaro Binary Sensor."""
def __init__(self, fibaro_device, controller):
"""Initialize the binary_sensor."""
self._state = None
super().__init__(fibaro_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
stype = None
if fibaro_device.type in SENSOR_TYPES:
stype = fibaro_device.type
elif fibaro_device.baseType in SENSOR_TYPES:
stype = fibaro_device.baseType
if stype:
self._device_class = SENSOR_TYPES[stype][2]
self._icon = SENSOR_TYPES[stype][1]
else:
self._device_class = None
self._icon = None
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return self._icon
@property
def device_class(self):
"""Return the device class of the sensor."""
return self._device_class
@property
def is_on(self):
"""Return true if sensor is on."""
return self._state
def update(self):
"""Get the latest data and update the state."""
self._state = self.current_binary_state

View File

@@ -57,7 +57,8 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorDevice):
"""Return the boolean response if the node is on."""
on_val = bool(self._insteon_device_state.value)
if self._insteon_device_state.name == 'lightSensor':
if self._insteon_device_state.name in ['lightSensor',
'openClosedSensor']:
return not on_val
return on_val

View File

@@ -0,0 +1,53 @@
"""
This component provides HA binary_sensor support for Lupusec Security System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.lupusec/
"""
import logging
from datetime import timedelta
from homeassistant.components.lupusec import (LupusecDevice,
DOMAIN as LUPUSEC_DOMAIN)
from homeassistant.components.binary_sensor import (BinarySensorDevice,
DEVICE_CLASSES)
DEPENDENCIES = ['lupusec']
SCAN_INTERVAL = timedelta(seconds=2)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a sensor for an Lupusec device."""
if discovery_info is None:
return
import lupupy.constants as CONST
data = hass.data[LUPUSEC_DOMAIN]
device_types = [CONST.TYPE_OPENING]
devices = []
for device in data.lupusec.get_devices(generic_type=device_types):
devices.append(LupusecBinarySensor(data, device))
add_entities(devices)
class LupusecBinarySensor(LupusecDevice, BinarySensorDevice):
"""A binary sensor implementation for Lupusec device."""
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._device.is_on
@property
def device_class(self):
"""Return the class of the binary sensor."""
if self._device.generic_type not in DEVICE_CLASSES:
return None
return self._device.generic_type

View File

@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/
"""
import logging
from typing import Optional
import voluptuous as vol
@@ -19,7 +18,8 @@ from homeassistant.const import (
from homeassistant.components.mqtt import (
ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo)
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
@@ -79,21 +79,8 @@ async def _async_setup_entity(hass, config, async_add_entities,
value_template.hass = hass
async_add_entities([MqttBinarySensor(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_DEVICE_CLASS),
config.get(CONF_QOS),
config.get(CONF_FORCE_UPDATE),
config.get(CONF_OFF_DELAY),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template,
config.get(CONF_UNIQUE_ID),
config.get(CONF_DEVICE),
discovery_hash,
config,
discovery_hash
)])
@@ -101,35 +88,71 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class,
qos, force_update, off_delay, payload_on, payload_off,
payload_available, payload_not_available, value_template,
unique_id: Optional[str], device_config: Optional[ConfigType],
discovery_hash):
def __init__(self, config, discovery_hash):
"""Initialize the MQTT binary sensor."""
MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
MqttEntityDeviceInfo.__init__(self, device_config)
self._name = name
self._config = config
self._state = None
self._state_topic = state_topic
self._device_class = device_class
self._payload_on = payload_on
self._payload_off = payload_off
self._qos = qos
self._force_update = force_update
self._off_delay = off_delay
self._template = value_template
self._unique_id = unique_id
self._discovery_hash = discovery_hash
self._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)
device_config = config.get(CONF_DEVICE)
MqttAvailability.__init__(self, availability_topic, self._qos,
payload_available, payload_not_available)
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 self._subscribe_topics()
async def discovery_update(self, discovery_payload):
"""Handle updated discovery message."""
config = PLATFORM_SCHEMA(discovery_payload)
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._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."""
@callback
def off_delay_listener(now):
"""Switch device off after a delay."""
@@ -155,6 +178,7 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
if self._delay_listener is not None:
self._delay_listener()
self._delay_listener = None
if (self._state and self._off_delay is not None):
self._delay_listener = evt.async_call_later(
@@ -162,8 +186,16 @@ class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
self.async_schedule_update_ha_state()
await mqtt.async_subscribe(
self.hass, self._state_topic, state_message_received, self._qos)
self._sub_state = await subscription.async_subscribe_topics(
self.hass, self._sub_state,
{'state_topic': {'topic': self._state_topic,
'msg_callback': state_message_received,
'qos': self._qos}})
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
await subscription.async_unsubscribe_topics(self.hass, self._sub_state)
await MqttAvailability.async_will_remove_from_hass(self)
@property
def should_poll(self):

View File

@@ -0,0 +1,104 @@
"""
Support for Minut Point.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.point/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.point import MinutPointEntity
from homeassistant.components.point.const import (
DOMAIN as POINT_DOMAIN, NEW_DEVICE, SIGNAL_WEBHOOK)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
EVENTS = {
'battery': # On means low, Off means normal
('battery_low', ''),
'button_press': # On means the button was pressed, Off means normal
('short_button_press', ''),
'cold': # On means cold, Off means normal
('temperature_low', 'temperature_risen_normal'),
'connectivity': # On means connected, Off means disconnected
('device_online', 'device_offline'),
'dry': # On means too dry, Off means normal
('humidity_low', 'humidity_risen_normal'),
'heat': # On means hot, Off means normal
('temperature_high', 'temperature_dropped_normal'),
'moisture': # On means wet, Off means dry
('humidity_high', 'humidity_dropped_normal'),
'sound': # On means sound detected, Off means no sound (clear)
('avg_sound_high', 'sound_level_dropped_normal'),
'tamper': # On means the point was removed or attached
('tamper', ''),
}
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)
class MinutPointBinarySensor(MinutPointEntity, BinarySensorDevice):
"""The platform class required by Home Assistant."""
def __init__(self, point_client, device_id, device_class):
"""Initialize the entity."""
super().__init__(point_client, device_id, device_class)
self._async_unsub_hook_dispatcher_connect = None
self._events = EVENTS[device_class]
self._is_on = None
async def async_added_to_hass(self):
"""Call when entity is added to hass."""
await super().async_added_to_hass()
self._async_unsub_hook_dispatcher_connect = async_dispatcher_connect(
self.hass, SIGNAL_WEBHOOK, self._webhook_event)
async def async_will_remove_from_hass(self):
"""Disconnect dispatcher listener when removed."""
await super().async_will_remove_from_hass()
if self._async_unsub_hook_dispatcher_connect:
self._async_unsub_hook_dispatcher_connect()
@callback
def _update_callback(self):
"""Update the value of the sensor."""
if not self.is_updated:
return
if self._events[0] in self.device.ongoing_events:
self._is_on = True
else:
self._is_on = None
self.async_schedule_update_ha_state()
@callback
def _webhook_event(self, data, webhook):
"""Process new event from the webhook."""
if self.device.webhook != webhook:
return
_type = data.get('event', {}).get('type')
if _type not in self._events:
return
_LOGGER.debug("Recieved webhook: %s", _type)
if _type == self._events[0]:
self._is_on = True
if _type == self._events[1]:
self._is_on = None
self.async_schedule_update_ha_state()
@property
def is_on(self):
"""Return the state of the binary sensor."""
if self.device_class == 'connectivity':
# connectivity is the other way around.
return not self._is_on
return self._is_on

View File

@@ -8,28 +8,29 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rainmachine import (
BINARY_SENSORS, DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, TYPE_FREEZE,
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
from homeassistant.const import CONF_MONITORED_CONDITIONS
BINARY_SENSORS, DATA_CLIENT, DOMAIN as RAINMACHINE_DOMAIN,
SENSOR_UPDATE_TOPIC, TYPE_FREEZE, TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS,
TYPE_HOURLY, TYPE_MONTH, TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY,
RainMachineEntity)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['rainmachine']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the RainMachine Switch platform."""
if discovery_info is None:
return
"""Set up RainMachine binary sensors based on the old way."""
pass
rainmachine = hass.data[DATA_RAINMACHINE]
async def async_setup_entry(hass, entry, async_add_entities):
"""Set up RainMachine binary sensors based on a config entry."""
rainmachine = hass.data[RAINMACHINE_DOMAIN][DATA_CLIENT][entry.entry_id]
binary_sensors = []
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
for sensor_type in rainmachine.binary_sensor_conditions:
name, icon = BINARY_SENSORS[sensor_type]
binary_sensors.append(
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
@@ -70,15 +71,15 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
return '{0}_{1}'.format(
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
@callback
def _update_data(self):
"""Update the state."""
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
@callback
def update(self):
"""Update the state."""
self.async_schedule_update_ha_state(True)
self._dispatcher_handlers.append(async_dispatcher_connect(
self.hass, SENSOR_UPDATE_TOPIC, update))
async def async_update(self):
"""Update the state."""

View File

@@ -60,7 +60,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
data = hass.data[SENSE_DATA]
sense_devices = data.get_discovered_device_data()
devices = [SenseDevice(data, device) for device in sense_devices]
devices = [SenseDevice(data, device) for device in sense_devices
if device['tags']['DeviceListAllowed'] == 'true']
add_entities(devices)

View File

@@ -58,10 +58,12 @@ async def async_setup_platform(hass, config, async_add_entities,
entity_ids = set()
manual_entity_ids = device_config.get(ATTR_ENTITY_ID)
for template in (
value_template,
icon_template,
entity_picture_template,
invalid_templates = []
for tpl_name, template in (
(CONF_VALUE_TEMPLATE, value_template),
(CONF_ICON_TEMPLATE, icon_template),
(CONF_ENTITY_PICTURE_TEMPLATE, entity_picture_template),
):
if template is None:
continue
@@ -73,6 +75,8 @@ async def async_setup_platform(hass, config, async_add_entities,
template_entity_ids = template.extract_entities()
if template_entity_ids == MATCH_ALL:
entity_ids = MATCH_ALL
# Cut off _template from name
invalid_templates.append(tpl_name[:-9])
elif entity_ids != MATCH_ALL:
entity_ids |= set(template_entity_ids)
@@ -81,6 +85,14 @@ async def async_setup_platform(hass, config, async_add_entities,
elif entity_ids != MATCH_ALL:
entity_ids = list(entity_ids)
if invalid_templates:
_LOGGER.warning(
'Template binary sensor %s has no entity ids configured to'
' track nor were we able to extract the entities to track'
' from the %s template(s). This entity will only be able'
' to be updated manually.',
device, ', '.join(invalid_templates))
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
device_class = device_config.get(CONF_DEVICE_CLASS)
delay_on = device_config.get(CONF_DELAY_ON)
@@ -132,10 +144,12 @@ class BinarySensorTemplate(BinarySensorDevice):
@callback
def template_bsensor_startup(event):
"""Update template on startup."""
async_track_state_change(
self.hass, self._entities, template_bsensor_state_listener)
if self._entities != MATCH_ALL:
# Track state change only for valid templates
async_track_state_change(
self.hass, self._entities, template_bsensor_state_listener)
self.hass.async_add_job(self.async_check_state)
self.async_check_state()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_bsensor_startup)
@@ -233,3 +247,7 @@ class BinarySensorTemplate(BinarySensorDevice):
async_track_same_state(
self.hass, period, set_state, entity_ids=self._entities,
async_check_same_func=lambda *args: self._async_render() == state)
async def async_update(self):
"""Force update of the state from the template."""
self.async_check_state()

View File

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

View File

@@ -0,0 +1,132 @@
"""
Support for w800rf32 binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.w800rf32/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.w800rf32 import (W800RF32_DEVICE)
from homeassistant.const import (CONF_DEVICE_CLASS, CONF_NAME, CONF_DEVICES)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import event as evt
from homeassistant.util import dt as dt_util
from homeassistant.helpers.dispatcher import (async_dispatcher_connect)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['w800rf32']
CONF_OFF_DELAY = 'off_delay'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): {
cv.string: vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_OFF_DELAY):
vol.All(cv.time_period, cv.positive_timedelta)
})
},
}, extra=vol.ALLOW_EXTRA)
async def async_setup_platform(hass, config,
add_entities, discovery_info=None):
"""Set up the Binary Sensor platform to w800rf32."""
binary_sensors = []
# device_id --> "c1 or a3" X10 device. entity (type dictionary)
# --> name, device_class etc
for device_id, entity in config[CONF_DEVICES].items():
_LOGGER.debug("Add %s w800rf32.binary_sensor (class %s)",
entity[CONF_NAME], entity.get(CONF_DEVICE_CLASS))
device = W800rf32BinarySensor(
device_id, entity.get(CONF_NAME), entity.get(CONF_DEVICE_CLASS),
entity.get(CONF_OFF_DELAY))
binary_sensors.append(device)
add_entities(binary_sensors)
class W800rf32BinarySensor(BinarySensorDevice):
"""A representation of a w800rf32 binary sensor."""
def __init__(self, device_id, name, device_class=None, off_delay=None):
"""Initialize the w800rf32 sensor."""
self._signal = W800RF32_DEVICE.format(device_id)
self._name = name
self._device_class = device_class
self._off_delay = off_delay
self._state = False
self._delay_listener = None
@callback
def _off_delay_listener(self, now):
"""Switch device off after a delay."""
self._delay_listener = None
self.update_state(False)
@property
def name(self):
"""Return the device name."""
return self._name
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def device_class(self):
"""Return the sensor class."""
return self._device_class
@property
def is_on(self):
"""Return true if the sensor state is True."""
return self._state
@callback
def binary_sensor_update(self, event):
"""Call for control updates from the w800rf32 gateway."""
import W800rf32 as w800rf32mod
if not isinstance(event, w800rf32mod.W800rf32Event):
return
dev_id = event.device
command = event.command
_LOGGER.debug(
"BinarySensor update (Device ID: %s Command %s ...)",
dev_id, command)
# Update the w800rf32 device state
if command in ('On', 'Off'):
is_on = command == 'On'
self.update_state(is_on)
if (self.is_on and self._off_delay is not None and
self._delay_listener is None):
self._delay_listener = evt.async_track_point_in_time(
self.hass, self._off_delay_listener,
dt_util.utcnow() + self._off_delay)
def update_state(self, state):
"""Update the state of the device."""
self._state = state
self.async_schedule_update_ha_state()
async def async_added_to_hass(self):
"""Register update callback."""
async_dispatcher_connect(self.hass, self._signal,
self.binary_sensor_update)

View File

@@ -209,7 +209,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
else:
self._should_poll = True
if self.entity_id is not None:
self._hass.bus.fire('motion', {
self._hass.bus.fire('xiaomi_aqara.motion', {
'entity_id': self.entity_id
})
@@ -357,6 +357,9 @@ class XiaomiVibration(XiaomiBinarySensor):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value is None:
return False
if value not in ('vibrate', 'tilt', 'free_fall'):
_LOGGER.warning("Unsupported movement_type detected: %s",
value)
@@ -414,7 +417,7 @@ class XiaomiButton(XiaomiBinarySensor):
_LOGGER.warning("Unsupported click_type detected: %s", value)
return False
self._hass.bus.fire('click', {
self._hass.bus.fire('xiaomi_aqara.click', {
'entity_id': self.entity_id,
'click_type': click_type
})
@@ -450,14 +453,14 @@ class XiaomiCube(XiaomiBinarySensor):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if self._data_key in data:
self._hass.bus.fire('cube_action', {
self._hass.bus.fire('xiaomi_aqara.cube_action', {
'entity_id': self.entity_id,
'action_type': data[self._data_key]
})
self._last_action = data[self._data_key]
if 'rotate' in data:
self._hass.bus.fire('cube_action', {
self._hass.bus.fire('xiaomi_aqara.cube_action', {
'entity_id': self.entity_id,
'action_type': 'rotate',
'action_value': float(data['rotate'].replace(",", "."))

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.1']
REQUIREMENTS = ['blinkpy==0.10.3']
_LOGGER = logging.getLogger(__name__)

View File

@@ -89,13 +89,12 @@ class MqttCamera(Camera):
"""Return a unique ID."""
return self._unique_id
@asyncio.coroutine
def async_added_to_hass(self):
async def async_added_to_hass(self):
"""Subscribe MQTT events."""
@callback
def message_received(topic, payload, qos):
"""Handle new MQTT messages."""
self._last_image = payload
return mqtt.async_subscribe(
await mqtt.async_subscribe(
self.hass, self._topic, message_received, self._qos, None)

View File

@@ -1,5 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "No se encontraron dispositivos de Google Cast en la red.",
"single_instance_allowed": "S\u00f3lo es necesaria una \u00fanica configuraci\u00f3n de Google Cast."
},
"step": {
"confirm": {
"description": "\u00bfQuieres configurar Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -249,9 +249,11 @@ class ClimateDevice(Entity):
self.hass, self.target_temperature_low, self.temperature_unit,
self.precision)
if self.current_humidity is not None:
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY:
data[ATTR_HUMIDITY] = self.target_humidity
data[ATTR_CURRENT_HUMIDITY] = self.current_humidity
if supported_features & SUPPORT_TARGET_HUMIDITY_LOW:
data[ATTR_MIN_HUMIDITY] = self.min_humidity

View File

@@ -22,7 +22,7 @@ from homeassistant.const import (
ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pydaikin==0.4']
REQUIREMENTS = ['pydaikin==0.8']
_LOGGER = logging.getLogger(__name__)
@@ -82,7 +82,6 @@ class DaikinClimate(ClimateDevice):
from pydaikin import appliance
self._api = api
self._force_refresh = False
self._list = {
ATTR_OPERATION_MODE: list(HA_STATE_TO_DAIKIN),
ATTR_FAN_MODE: list(
@@ -102,19 +101,11 @@ class DaikinClimate(ClimateDevice):
self._supported_features = SUPPORT_TARGET_TEMPERATURE \
| SUPPORT_OPERATION_MODE
daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE]
if self._api.device.values.get(daikin_attr) is not None:
if self._api.device.support_fan_mode:
self._supported_features |= SUPPORT_FAN_MODE
else:
# even devices without support must have a default valid value
self._api.device.values[daikin_attr] = 'A'
daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]
if self._api.device.values.get(daikin_attr) is not None:
if self._api.device.support_swing_mode:
self._supported_features |= SUPPORT_SWING_MODE
else:
# even devices without support must have a default valid value
self._api.device.values[daikin_attr] = '0'
def get(self, key):
"""Retrieve device settings from API library cache."""
@@ -189,7 +180,6 @@ class DaikinClimate(ClimateDevice):
_LOGGER.error("Invalid temperature %s", value)
if values:
self._force_refresh = True
self._api.device.set(values)
@property
@@ -270,5 +260,4 @@ class DaikinClimate(ClimateDevice):
def update(self):
"""Retrieve latest state."""
self._api.update(no_throttle=self._force_refresh)
self._force_refresh = False
self._api.update()

View File

@@ -17,7 +17,8 @@ from homeassistant.components.climate import (
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, ATTR_ENTITY_ID,
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN)
SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN, PRECISION_HALVES,
PRECISION_TENTHS, PRECISION_WHOLE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
@@ -43,6 +44,7 @@ CONF_HOT_TOLERANCE = 'hot_tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode'
CONF_AWAY_TEMP = 'away_temp'
CONF_PRECISION = 'precision'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_OPERATION_MODE)
@@ -63,7 +65,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_INITIAL_OPERATION_MODE):
vol.In([STATE_AUTO, STATE_OFF]),
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float)
vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float),
vol.Optional(CONF_PRECISION): vol.In(
[PRECISION_TENTHS, PRECISION_HALVES, PRECISION_WHOLE]),
})
@@ -83,11 +87,13 @@ async def async_setup_platform(hass, config, async_add_entities,
keep_alive = config.get(CONF_KEEP_ALIVE)
initial_operation_mode = config.get(CONF_INITIAL_OPERATION_MODE)
away_temp = config.get(CONF_AWAY_TEMP)
precision = config.get(CONF_PRECISION)
async_add_entities([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, cold_tolerance,
hot_tolerance, keep_alive, initial_operation_mode, away_temp)])
hot_tolerance, keep_alive, initial_operation_mode, away_temp,
precision)])
class GenericThermostat(ClimateDevice):
@@ -96,7 +102,7 @@ class GenericThermostat(ClimateDevice):
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
cold_tolerance, hot_tolerance, keep_alive,
initial_operation_mode, away_temp):
initial_operation_mode, away_temp, precision):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
@@ -109,6 +115,7 @@ class GenericThermostat(ClimateDevice):
self._initial_operation_mode = initial_operation_mode
self._saved_target_temp = target_temp if target_temp is not None \
else away_temp
self._temp_precision = precision
if self.ac_mode:
self._current_operation = STATE_COOL
self._operation_list = [STATE_COOL, STATE_OFF]
@@ -202,6 +209,13 @@ class GenericThermostat(ClimateDevice):
"""Return the name of the thermostat."""
return self._name
@property
def precision(self):
"""Return the precision of the system."""
if self._temp_precision is not None:
return self._temp_precision
return super().precision
@property
def temperature_unit(self):
"""Return the unit of measurement."""

View File

@@ -7,17 +7,16 @@ https://home-assistant.io/components/climate.homematic/
import logging
from homeassistant.components.climate import (
STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
ClimateDevice)
STATE_AUTO, STATE_MANUAL, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
from homeassistant.components.homematic import (
ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice)
from homeassistant.const import ATTR_TEMPERATURE, STATE_UNKNOWN, TEMP_CELSIUS
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
DEPENDENCIES = ['homematic']
_LOGGER = logging.getLogger(__name__)
STATE_MANUAL = 'manual'
STATE_BOOST = 'boost'
STATE_COMFORT = 'comfort'
STATE_LOWERING = 'lowering'
@@ -41,7 +40,7 @@ HM_HUMI_MAP = [
]
HM_CONTROL_MODE = 'CONTROL_MODE'
HM_IP_CONTROL_MODE = 'SET_POINT_MODE'
HMIP_CONTROL_MODE = 'SET_POINT_MODE'
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE
@@ -78,21 +77,17 @@ class HMThermostat(HMDevice, ClimateDevice):
if HM_CONTROL_MODE not in self._data:
return None
set_point_mode = self._data.get('SET_POINT_MODE', -1)
control_mode = self._data.get('CONTROL_MODE', -1)
boost_mode = self._data.get('BOOST_MODE', False)
# boost mode is active
if boost_mode:
if self._data.get('BOOST_MODE', False):
return STATE_BOOST
# HM ip etrv 2 uses the set_point_mode to say if its
# HmIP uses the set_point_mode to say if its
# auto or manual
if not set_point_mode == -1:
code = set_point_mode
if HMIP_CONTROL_MODE in self._data:
code = self._data[HMIP_CONTROL_MODE]
# Other devices use the control_mode
else:
code = control_mode
code = self._data['CONTROL_MODE']
# get the name of the mode
name = HM_ATTRIBUTE_SUPPORT[HM_CONTROL_MODE][1][code]
@@ -101,12 +96,15 @@ class HMThermostat(HMDevice, ClimateDevice):
@property
def operation_list(self):
"""Return the list of available operation modes."""
op_list = []
# HMIP use set_point_mode for operation
if HMIP_CONTROL_MODE in self._data:
return [STATE_MANUAL, STATE_AUTO, STATE_BOOST]
# HM
op_list = []
for mode in self._hmdevice.ACTIONNODE:
if mode in HM_STATE_MAP:
op_list.append(HM_STATE_MAP.get(mode))
return op_list
@property
@@ -157,11 +155,11 @@ class HMThermostat(HMDevice, ClimateDevice):
def _init_data_struct(self):
"""Generate a data dict (self._data) from the Homematic metadata."""
self._state = next(iter(self._hmdevice.WRITENODE.keys()))
self._data[self._state] = STATE_UNKNOWN
self._data[self._state] = None
if HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE or \
HM_IP_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE:
self._data[HM_CONTROL_MODE] = STATE_UNKNOWN
HMIP_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE:
self._data[HM_CONTROL_MODE] = None
for node in self._hmdevice.SENSORNODE.keys():
self._data[node] = STATE_UNKNOWN
self._data[node] = None

View File

@@ -88,6 +88,12 @@ class MelissaClimate(ClimateDevice):
if self._data:
return self._data[self._api.TEMP]
@property
def current_humidity(self):
"""Return the current humidity value."""
if self._data:
return self._data[self._api.HUMIDITY]
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
@@ -113,8 +119,9 @@ class MelissaClimate(ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._cur_settings is not None:
return self._cur_settings[self._api.TEMP]
if self._cur_settings is None:
return None
return self._cur_settings[self._api.TEMP]
@property
def state(self):

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.2']
REQUIREMENTS = ['millheater==0.2.8']
_LOGGER = logging.getLogger(__name__)
@@ -32,8 +32,7 @@ MIN_TEMP = 5
SERVICE_SET_ROOM_TEMP = 'mill_set_room_temperature'
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE |
SUPPORT_FAN_MODE | SUPPORT_ON_OFF |
SUPPORT_OPERATION_MODE)
SUPPORT_FAN_MODE)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
@@ -92,12 +91,14 @@ class MillHeater(ClimateDevice):
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
if self._heater.is_gen1:
return SUPPORT_FLAGS
return SUPPORT_FLAGS | SUPPORT_ON_OFF | SUPPORT_OPERATION_MODE
@property
def available(self):
"""Return True if entity is available."""
return self._heater.device_status == 0 # weird api choice
return self._heater.available
@property
def unique_id(self):
@@ -112,16 +113,18 @@ class MillHeater(ClimateDevice):
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._heater.room:
room = self._heater.room.name
else:
room = "Independent device"
return {
"room": room,
res = {
"open_window": self._heater.open_window,
"heating": self._heater.is_heating,
"controlled_by_tibber": self._heater.tibber_control,
"heater_generation": 1 if self._heater.is_gen1 else 2,
}
if self._heater.room:
res['room'] = self._heater.room.name
res['avg_room_temp'] = self._heater.room.avg_temp
else:
res['room'] = "Independent device"
return res
@property
def temperature_unit(self):
@@ -156,6 +159,8 @@ class MillHeater(ClimateDevice):
@property
def is_on(self):
"""Return true if heater is on."""
if self._heater.is_gen1:
return True
return self._heater.power_status == 1
@property
@@ -176,6 +181,8 @@ class MillHeater(ClimateDevice):
@property
def operation_list(self):
"""List of available operation modes."""
if self._heater.is_gen1:
return None
return [STATE_HEAT, STATE_OFF]
async def async_set_temperature(self, **kwargs):
@@ -210,7 +217,7 @@ class MillHeater(ClimateDevice):
"""Set operation mode."""
if operation_mode == STATE_HEAT:
await self.async_turn_on()
elif operation_mode == STATE_OFF:
elif operation_mode == STATE_OFF and not self._heater.is_gen1:
await self.async_turn_off()
else:
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)

View File

@@ -168,18 +168,14 @@ class NestThermostat(ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._mode != NEST_MODE_HEAT_COOL and \
self._mode != STATE_ECO and \
not self.is_away_mode_on:
if self._mode not in (NEST_MODE_HEAT_COOL, STATE_ECO):
return self._target_temperature
return None
@property
def target_temperature_low(self):
"""Return the lower bound temperature we try to reach."""
if (self.is_away_mode_on or self._mode == STATE_ECO) and \
self._eco_temperature[0]:
# eco_temperature is always a low, high tuple
if self._mode == STATE_ECO:
return self._eco_temperature[0]
if self._mode == NEST_MODE_HEAT_COOL:
return self._target_temperature[0]
@@ -188,9 +184,7 @@ class NestThermostat(ClimateDevice):
@property
def target_temperature_high(self):
"""Return the upper bound temperature we try to reach."""
if (self.is_away_mode_on or self._mode == STATE_ECO) and \
self._eco_temperature[1]:
# eco_temperature is always a low, high tuple
if self._mode == STATE_ECO:
return self._eco_temperature[1]
if self._mode == NEST_MODE_HEAT_COOL:
return self._target_temperature[1]

View File

@@ -7,18 +7,17 @@ https://home-assistant.io/components/climate.velbus/
import logging
from homeassistant.components.climate import (
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
STATE_HEAT, SUPPORT_TARGET_TEMPERATURE, ClimateDevice)
from homeassistant.components.velbus import (
DOMAIN as VELBUS_DOMAIN, VelbusEntity)
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['velbus']
OPERATION_LIST = ['comfort', 'day', 'night', 'safe']
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE)
async def async_setup_platform(
@@ -47,7 +46,9 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
@property
def temperature_unit(self):
"""Return the unit this state is expressed in."""
return self._module.get_unit(self._channel)
if self._module.get_unit(self._channel) == '°C':
return TEMP_CELSIUS
return TEMP_FAHRENHEIT
@property
def current_temperature(self):
@@ -56,26 +57,18 @@ class VelbusClimate(VelbusEntity, ClimateDevice):
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._module.get_climate_mode()
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST
"""Return current operation."""
return STATE_HEAT
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._module.get_climate_target()
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
self._module.set_mode(operation_mode)
self.schedule_update_ha_state()
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_TEMPERATURE) is not None:
self._module.set_temp(kwargs.get(ATTR_TEMPERATURE))
self.schedule_update_ha_state()
temp = kwargs.get(ATTR_TEMPERATURE)
if temp is None:
return
self._module.set_temp(temp)
self.schedule_update_ha_state()

View File

@@ -12,24 +12,20 @@ import os
import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME)
EVENT_HOMEASSISTANT_START, CLOUD_NEVER_EXPOSED_ENTITIES, CONF_REGION,
CONF_MODE, CONF_NAME)
from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import helpers as ga_h
from homeassistant.components.google_assistant import const as ga_c
from . import http_api, iot, auth_api
from . import http_api, iot, auth_api, prefs
from .const import CONFIG_DIR, DOMAIN, SERVERS
REQUIREMENTS = ['warrant==0.6.1']
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
STORAGE_ENABLE_ALEXA = 'alexa_enabled'
STORAGE_ENABLE_GOOGLE = 'google_enabled'
_LOGGER = logging.getLogger(__name__)
_UNDEF = object()
CONF_ALEXA = 'alexa'
CONF_ALIASES = 'aliases'
@@ -68,7 +64,7 @@ ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({
})
GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend({
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA}
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA},
})
CONFIG_SCHEMA = vol.Schema({
@@ -124,12 +120,11 @@ class Cloud:
self.alexa_config = alexa
self.google_actions_user_conf = google_actions
self._gactions_config = None
self._prefs = None
self.prefs = prefs.CloudPreferences(hass)
self.id_token = None
self.access_token = None
self.refresh_token = None
self.iot = iot.CloudIoT(self)
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
if mode == MODE_DEV:
self.cognito_client_id = cognito_client_id
@@ -184,26 +179,20 @@ class Cloud:
def should_expose(entity):
"""If an entity should be exposed."""
if entity.entity_id in CLOUD_NEVER_EXPOSED_ENTITIES:
return False
return conf['filter'](entity.entity_id)
self._gactions_config = ga_h.Config(
should_expose=should_expose,
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
@property
def alexa_enabled(self):
"""Return if Alexa is enabled."""
return self._prefs[STORAGE_ENABLE_ALEXA]
@property
def google_enabled(self):
"""Return if Google is enabled."""
return self._prefs[STORAGE_ENABLE_GOOGLE]
def path(self, *parts):
"""Get config path inside cloud dir.
@@ -243,20 +232,6 @@ class Cloud:
async def async_start(self, _):
"""Start the cloud component."""
prefs = await self._store.async_load()
if prefs is None:
prefs = {}
if self.mode not in prefs:
# Default to True if already logged in to make this not a
# breaking change.
enabled = await self.hass.async_add_executor_job(
os.path.isfile, self.user_info_path)
prefs = {
STORAGE_ENABLE_ALEXA: enabled,
STORAGE_ENABLE_GOOGLE: enabled,
}
self._prefs = prefs
def load_config():
"""Load config."""
# Ensure config dir exists
@@ -273,6 +248,8 @@ class Cloud:
info = await self.hass.async_add_job(load_config)
await self.prefs.async_initialize(bool(info))
if info is None:
return
@@ -282,15 +259,6 @@ class Cloud:
self.hass.add_job(self.iot.connect())
async def update_preferences(self, *, google_enabled=_UNDEF,
alexa_enabled=_UNDEF):
"""Update user preferences."""
if google_enabled is not _UNDEF:
self._prefs[STORAGE_ENABLE_GOOGLE] = google_enabled
if alexa_enabled is not _UNDEF:
self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled
await self._store.async_save(self._prefs)
def _decode_claims(self, token): # pylint: disable=no-self-use
"""Decode the claims in a token."""
from jose import jwt

View File

@@ -3,6 +3,10 @@ DOMAIN = 'cloud'
CONFIG_DIR = '.cloud'
REQUEST_TIMEOUT = 10
PREF_ENABLE_ALEXA = 'alexa_enabled'
PREF_ENABLE_GOOGLE = 'google_enabled'
PREF_GOOGLE_ALLOW_UNLOCK = 'google_allow_unlock'
SERVERS = {
'production': {
'cognito_client_id': '60i2uvhvbiref2mftj7rgcrt9u',

View File

@@ -15,7 +15,9 @@ from homeassistant.components.alexa import smart_home as alexa_sh
from homeassistant.components.google_assistant import smart_home as google_sh
from . import auth_api
from .const import DOMAIN, REQUEST_TIMEOUT
from .const import (
DOMAIN, REQUEST_TIMEOUT, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
PREF_GOOGLE_ALLOW_UNLOCK)
from .iot import STATE_DISCONNECTED, STATE_CONNECTED
_LOGGER = logging.getLogger(__name__)
@@ -30,8 +32,9 @@ SCHEMA_WS_STATUS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
WS_TYPE_UPDATE_PREFS = 'cloud/update_prefs'
SCHEMA_WS_UPDATE_PREFS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_UPDATE_PREFS,
vol.Optional('google_enabled'): bool,
vol.Optional('alexa_enabled'): bool,
vol.Optional(PREF_ENABLE_GOOGLE): bool,
vol.Optional(PREF_ENABLE_ALEXA): bool,
vol.Optional(PREF_GOOGLE_ALLOW_UNLOCK): bool,
})
@@ -288,7 +291,7 @@ async def websocket_update_prefs(hass, connection, msg):
changes = dict(msg)
changes.pop('id')
changes.pop('type')
await cloud.update_preferences(**changes)
await cloud.prefs.async_update(**changes)
connection.send_message(websocket_api.result_message(
msg['id'], {'success': True}))
@@ -308,10 +311,9 @@ def _account_data(cloud):
'logged_in': True,
'email': claims['email'],
'cloud': cloud.iot.state,
'google_enabled': cloud.google_enabled,
'prefs': cloud.prefs.as_dict(),
'google_entities': cloud.google_actions_user_conf['filter'].config,
'google_domains': list(google_sh.DOMAIN_TO_GOOGLE_TYPES),
'alexa_enabled': cloud.alexa_enabled,
'alexa_entities': cloud.alexa_config.should_expose.config,
'alexa_domains': list(alexa_sh.ENTITY_ADAPTERS),
}

View File

@@ -229,7 +229,7 @@ def async_handle_alexa(hass, cloud, payload):
"""Handle an incoming IoT message for Alexa."""
result = yield from alexa.async_handle_message(
hass, cloud.alexa_config, payload,
enabled=cloud.alexa_enabled)
enabled=cloud.prefs.alexa_enabled)
return result
@@ -237,7 +237,7 @@ def async_handle_alexa(hass, cloud, payload):
@asyncio.coroutine
def async_handle_google_actions(hass, cloud, payload):
"""Handle an incoming IoT message for Google Actions."""
if not cloud.google_enabled:
if not cloud.prefs.google_enabled:
return ga.turned_off_response(payload)
result = yield from ga.async_handle_message(

View File

@@ -0,0 +1,64 @@
"""Preference management for cloud."""
from .const import (
DOMAIN, PREF_ENABLE_ALEXA, PREF_ENABLE_GOOGLE,
PREF_GOOGLE_ALLOW_UNLOCK)
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
_UNDEF = object()
class CloudPreferences:
"""Handle cloud preferences."""
def __init__(self, hass):
"""Initialize cloud prefs."""
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._prefs = None
async def async_initialize(self, logged_in):
"""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_GOOGLE_ALLOW_UNLOCK: False,
}
await self._store.async_save(prefs)
self._prefs = prefs
async def async_update(self, *, google_enabled=_UNDEF,
alexa_enabled=_UNDEF, google_allow_unlock=_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),
):
if value is not _UNDEF:
self._prefs[key] = value
await self._store.async_save(self._prefs)
def as_dict(self):
"""Return dictionary version."""
return self._prefs
@property
def alexa_enabled(self):
"""Return if Alexa is enabled."""
return self._prefs[PREF_ENABLE_ALEXA]
@property
def google_enabled(self):
"""Return if Google is enabled."""
return self._prefs[PREF_ENABLE_GOOGLE]
@property
def google_allow_unlock(self):
"""Return if Google is allowed to unlock locks."""
return self._prefs.get(PREF_GOOGLE_ALLOW_UNLOCK, False)

View File

@@ -21,6 +21,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'coinbase'
CONF_API_SECRET = 'api_secret'
CONF_ACCOUNT_CURRENCIES = 'account_balance_currencies'
CONF_EXCHANGE_CURRENCIES = 'exchange_rate_currencies'
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
@@ -31,6 +32,8 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_API_SECRET): cv.string,
vol.Optional(CONF_ACCOUNT_CURRENCIES):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EXCHANGE_CURRENCIES, default=[]):
vol.All(cv.ensure_list, [cv.string])
})
@@ -45,6 +48,7 @@ def setup(hass, config):
"""
api_key = config[DOMAIN].get(CONF_API_KEY)
api_secret = config[DOMAIN].get(CONF_API_SECRET)
account_currencies = config[DOMAIN].get(CONF_ACCOUNT_CURRENCIES)
exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES)
hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(
@@ -53,7 +57,13 @@ def setup(hass, config):
if not hasattr(coinbase_data, 'accounts'):
return False
for account in coinbase_data.accounts.data:
load_platform(hass, 'sensor', DOMAIN, {'account': account}, config)
if (account_currencies is None or
account.currency in account_currencies):
load_platform(hass,
'sensor',
DOMAIN,
{'account': account},
config)
for currency in exchange_currencies:
if currency not in coinbase_data.exchange_rates.rates:
_LOGGER.warning("Currency %s not found", currency)

View File

@@ -5,7 +5,8 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.deconz/
"""
from homeassistant.components.deconz.const import (
COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, WINDOW_COVERS)
COVER_TYPES, DAMPERS, DECONZ_REACHABLE, DOMAIN as DECONZ_DOMAIN,
WINDOW_COVERS)
from homeassistant.components.cover import (
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP,
SUPPORT_SET_POSITION)
@@ -29,6 +30,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Covers are based on same device class as lights in deCONZ.
"""
gateway = hass.data[DECONZ_DOMAIN]
@callback
def async_add_cover(lights):
"""Add cover from deCONZ."""
@@ -36,23 +39,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for light in lights:
if light.type in COVER_TYPES:
if light.modelid in ZIGBEE_SPEC:
entities.append(DeconzCoverZigbeeSpec(light))
entities.append(DeconzCoverZigbeeSpec(light, gateway))
else:
entities.append(DeconzCover(light))
entities.append(DeconzCover(light, gateway))
async_add_entities(entities, True)
hass.data[DATA_DECONZ].listeners.append(
gateway.listeners.append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover))
async_add_cover(hass.data[DATA_DECONZ].api.lights.values())
async_add_cover(gateway.api.lights.values())
class DeconzCover(CoverDevice):
"""Representation of a deCONZ cover."""
def __init__(self, cover):
def __init__(self, cover, gateway):
"""Set up cover and add update callback to get data from websocket."""
self._cover = cover
self.gateway = gateway
self.unsub_dispatcher = None
self._features = SUPPORT_OPEN
self._features |= SUPPORT_CLOSE
self._features |= SUPPORT_STOP
@@ -61,11 +67,14 @@ class DeconzCover(CoverDevice):
async def async_added_to_hass(self):
"""Subscribe to covers events."""
self._cover.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._cover.deconz_id
self.gateway.deconz_ids[self.entity_id] = self._cover.deconz_id
self.unsub_dispatcher = async_dispatcher_connect(
self.hass, DECONZ_REACHABLE, self.async_update_callback)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect cover object when removed."""
if self.unsub_dispatcher is not None:
self.unsub_dispatcher()
self._cover.remove_callback(self.async_update_callback)
self._cover = None
@@ -112,7 +121,7 @@ class DeconzCover(CoverDevice):
@property
def available(self):
"""Return True if light is available."""
return self._cover.reachable
return self.gateway.available and self._cover.reachable
@property
def should_poll(self):
@@ -150,7 +159,7 @@ class DeconzCover(CoverDevice):
self._cover.uniqueid.count(':') != 7):
return None
serial = self._cover.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
bridgeid = self.gateway.api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@@ -0,0 +1,92 @@
"""
Support for Fibaro cover - curtains, rollershutters etc.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.fibaro/
"""
import logging
from homeassistant.components.cover import (
CoverDevice, ENTITY_ID_FORMAT, ATTR_POSITION, ATTR_TILT_POSITION)
from homeassistant.components.fibaro import (
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
DEPENDENCIES = ['fibaro']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Fibaro covers."""
if discovery_info is None:
return
add_entities(
[FibaroCover(device, hass.data[FIBARO_CONTROLLER]) for
device in hass.data[FIBARO_DEVICES]['cover']], True)
class FibaroCover(FibaroDevice, CoverDevice):
"""Representation a Fibaro Cover."""
def __init__(self, fibaro_device, controller):
"""Initialize the Vera device."""
super().__init__(fibaro_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
@staticmethod
def bound(position):
"""Normalize the position."""
if position is None:
return None
position = int(position)
if position <= 5:
return 0
if position >= 95:
return 100
return position
@property
def current_cover_position(self):
"""Return current position of cover. 0 is closed, 100 is open."""
return self.bound(self.level)
@property
def current_cover_tilt_position(self):
"""Return the current tilt position for venetian blinds."""
return self.bound(self.level2)
def set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
self.set_level(kwargs.get(ATTR_POSITION))
def set_cover_tilt_position(self, **kwargs):
"""Move the cover to a specific position."""
self.set_level2(kwargs.get(ATTR_TILT_POSITION))
@property
def is_closed(self):
"""Return if the cover is closed."""
if self.current_cover_position is None:
return None
return self.current_cover_position == 0
def open_cover(self, **kwargs):
"""Open the cover."""
self.action("open")
def close_cover(self, **kwargs):
"""Close the cover."""
self.action("close")
def open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
self.set_level2(100)
def close_cover_tilt(self, **kwargs):
"""Close the cover."""
self.set_level2(0)
def stop_cover(self, **kwargs):
"""Stop the cover."""
self.action("stop")

View File

@@ -87,7 +87,7 @@ def validate_options(value):
if (CONF_SET_POSITION_TOPIC in value and
CONF_GET_POSITION_TOPIC not in value):
raise vol.Invalid(
"Set position topic must be set together with get position topic.")
"set_position_topic must be set together with position_topic.")
return value
@@ -279,21 +279,19 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
payload)
if payload.isnumeric():
if 0 <= int(payload) <= 100:
percentage_payload = int(payload)
else:
percentage_payload = self.find_percentage_in_range(
float(payload), COVER_PAYLOAD)
if 0 <= percentage_payload <= 100:
self._position = percentage_payload
self._state = self._position == 0
percentage_payload = self.find_percentage_in_range(
float(payload), COVER_PAYLOAD)
self._position = percentage_payload
self._state = percentage_payload == DEFAULT_POSITION_CLOSED
else:
_LOGGER.warning(
"Payload is not integer within range: %s",
payload)
return
self.async_schedule_update_ha_state()
if self._get_position_topic:
await mqtt.async_subscribe(
self.hass, self._get_position_topic,
@@ -374,7 +372,8 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
# Optimistically assume that cover has changed state.
self._state = False
if self._get_position_topic:
self._position = self._position_open
self._position = self.find_percentage_in_range(
self._position_open, COVER_PAYLOAD)
self.async_schedule_update_ha_state()
async def async_close_cover(self, **kwargs):
@@ -389,7 +388,8 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
# Optimistically assume that cover has changed state.
self._state = True
if self._get_position_topic:
self._position = self._position_closed
self._position = self.find_percentage_in_range(
self._position_closed, COVER_PAYLOAD)
self.async_schedule_update_ha_state()
async def async_stop_cover(self, **kwargs):
@@ -451,7 +451,7 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
mqtt.async_publish(self.hass, self._set_position_topic,
position, self._qos, self._retain)
if self._optimistic:
self._state = percentage_position == 0
self._state = percentage_position == self._position_closed
self._position = percentage_position
self.async_schedule_update_ha_state()
@@ -469,6 +469,11 @@ class MqttCover(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
offset_position = position - min_range
position_percentage = round(
float(offset_position) / current_range * 100.0)
max_percent = 100
min_percent = 0
position_percentage = min(max(position_percentage, min_percent),
max_percent)
if range_type == TILT_PAYLOAD and self._tilt_invert:
return 100 - position_percentage
return position_percentage

View File

@@ -9,18 +9,15 @@ import logging
import voluptuous as vol
from homeassistant.components.cover import (
CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN)
PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, CoverDevice)
from homeassistant.const import (
CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
STATE_OPEN, STATE_OPENING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymyq==0.0.15']
from homeassistant.helpers import aiohttp_client, config_validation as cv
REQUIREMENTS = ['pymyq==1.0.0']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'myq'
MYQ_TO_HASS = {
'closed': STATE_CLOSED,
'closing': STATE_CLOSING,
@@ -28,95 +25,69 @@ MYQ_TO_HASS = {
'opening': STATE_OPENING
}
NOTIFICATION_ID = 'myq_notification'
NOTIFICATION_TITLE = 'MyQ Cover Setup'
COVER_SCHEMA = vol.Schema({
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_TYPE): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the MyQ component."""
from pymyq import MyQAPI as pymyq
async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the platform."""
from pymyq import login
from pymyq.errors import MyQError, UnsupportedBrandError
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
brand = config.get(CONF_TYPE)
myq = pymyq(username, password, brand)
websession = aiohttp_client.async_get_clientsession(hass)
username = config[CONF_USERNAME]
password = config[CONF_PASSWORD]
brand = config[CONF_TYPE]
try:
if not myq.is_supported_brand():
raise ValueError("Unsupported type. See documentation")
myq = await login(username, password, brand, websession)
except UnsupportedBrandError:
_LOGGER.error('Unsupported brand: %s', brand)
return
except MyQError as err:
_LOGGER.error('There was an error while logging in: %s', err)
return
if not myq.is_login_valid():
raise ValueError("Username or Password is incorrect")
add_entities(MyQDevice(myq, door) for door in myq.get_garage_doors())
return True
except (TypeError, KeyError, NameError, ValueError) as ex:
_LOGGER.error("%s", ex)
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'
''.format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
devices = await myq.get_devices()
async_add_entities([MyQDevice(device) for device in devices], True)
class MyQDevice(CoverDevice):
"""Representation of a MyQ cover."""
def __init__(self, myq, device):
def __init__(self, device):
"""Initialize with API object, device id."""
self.myq = myq
self.device_id = device['deviceid']
self._name = device['name']
self._status = None
self._device = device
@property
def device_class(self):
"""Define this cover as a garage door."""
return 'garage'
@property
def should_poll(self):
"""Poll for state."""
return True
@property
def name(self):
"""Return the name of the garage door if any."""
return self._name if self._name else DEFAULT_NAME
return self._device.name
@property
def is_closed(self):
"""Return true if cover is closed, else False."""
if self._status in [None, False]:
return None
return MYQ_TO_HASS.get(self._status) == STATE_CLOSED
return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSED
@property
def is_closing(self):
"""Return if the cover is closing or not."""
return MYQ_TO_HASS.get(self._status) == STATE_CLOSING
return MYQ_TO_HASS.get(self._device.state) == STATE_CLOSING
@property
def is_opening(self):
"""Return if the cover is opening or not."""
return MYQ_TO_HASS.get(self._status) == STATE_OPENING
def close_cover(self, **kwargs):
"""Issue close command to cover."""
self.myq.close_device(self.device_id)
def open_cover(self, **kwargs):
"""Issue open command to cover."""
self.myq.open_device(self.device_id)
return MYQ_TO_HASS.get(self._device.state) == STATE_OPENING
@property
def supported_features(self):
@@ -126,8 +97,16 @@ class MyQDevice(CoverDevice):
@property
def unique_id(self):
"""Return a unique, HASS-friendly identifier for this entity."""
return self.device_id
return self._device.device_id
def update(self):
async def async_close_cover(self, **kwargs):
"""Issue close command to cover."""
await self._device.close()
async def async_open_cover(self, **kwargs):
"""Issue open command to cover."""
await self._device.open()
async def async_update(self):
"""Update status of cover."""
self._status = self.myq.get_status(self.device_id)
await self._device.update()

View File

@@ -19,12 +19,11 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = ['pydaikin==0.4']
REQUIREMENTS = ['pydaikin==0.8']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'daikin'
HTTP_RESOURCES = ['aircon/get_sensor_info', 'aircon/get_control_info']
ATTR_TARGET_TEMPERATURE = 'target_temperature'
ATTR_INSIDE_TEMPERATURE = 'inside_temperature'
@@ -128,10 +127,7 @@ class DaikinApi:
def update(self, **kwargs):
"""Pull the latest data from Daikin."""
try:
for resource in HTTP_RESOURCES:
self.device.values.update(
self.device.get_resource(resource)
)
self.device.update_status()
except timeout:
_LOGGER.warning(
"Connection failed for %s", self.ip_address

View File

@@ -12,7 +12,7 @@
"init": {
"data": {
"host": "Host",
"port": "Port (default value: '80')"
"port": "Port"
},
"title": "Define deCONZ gateway"
},

View File

@@ -0,0 +1,33 @@
{
"config": {
"abort": {
"already_configured": "El puente ya esta configurado",
"no_bridges": "No se han descubierto puentes deCONZ",
"one_instance_only": "El componente s\u00f3lo soporta una instancia deCONZ"
},
"error": {
"no_key": "No se pudo obtener una clave API"
},
"step": {
"init": {
"data": {
"host": "Host",
"port": "Puerto"
},
"title": "Definir pasarela deCONZ"
},
"link": {
"description": "Desbloquee su pasarela deCONZ para registrarse con Home Assistant. \n\n 1. Ir a la configuraci\u00f3n del sistema deCONZ \n 2. Presione el bot\u00f3n \"Desbloquear Gateway\"",
"title": "Enlazar con deCONZ"
},
"options": {
"data": {
"allow_clip_sensor": "Permitir importar sensores virtuales",
"allow_deconz_groups": "Permitir la importaci\u00f3n de grupos deCONZ"
},
"title": "Opciones de configuraci\u00f3n adicionales para deCONZ"
}
},
"title": "Pasarela Zigbee deCONZ"
}
}

View File

@@ -12,7 +12,7 @@
"init": {
"data": {
"host": "Vert",
"port": "Port (standardverdi: '80')"
"port": "Port"
},
"title": "Definer deCONZ-gatewayen"
},

View File

@@ -11,11 +11,10 @@ from homeassistant.const import (
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.util.json import load_json
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
from .const import CONFIG_FILE, DOMAIN, _LOGGER
from .const import DEFAULT_PORT, DOMAIN, _LOGGER
from .gateway import DeconzGateway
REQUIREMENTS = ['pydeconz==47']
@@ -27,7 +26,7 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_API_KEY): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=80): cv.port,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
})
}, extra=vol.ALLOW_EXTRA)
@@ -53,11 +52,7 @@ async def async_setup(hass, config):
"""
if DOMAIN in config:
deconz_config = None
config_file = await hass.async_add_job(
load_json, hass.config.path(CONFIG_FILE))
if config_file:
deconz_config = config_file
elif CONF_HOST in config[DOMAIN]:
if CONF_HOST in config[DOMAIN]:
deconz_config = config[DOMAIN]
if deconz_config and not configured_hosts(hass):
hass.async_add_job(hass.config_entries.flow.async_init(

View File

@@ -6,11 +6,9 @@ from homeassistant import config_entries
from homeassistant.core import callback
from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.helpers import aiohttp_client
from homeassistant.util.json import load_json
from .const import (
CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DOMAIN)
CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_CLIP_SENSOR, DEFAULT_PORT, DOMAIN)
CONF_BRIDGEID = 'bridgeid'
@@ -35,6 +33,10 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
self.deconz_config = {}
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
return await self.async_step_init(user_input)
async def async_step_init(self, user_input=None):
"""Handle a deCONZ config flow start.
Only allows one instance to be set up.
@@ -51,6 +53,8 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
if bridge[CONF_HOST] == user_input[CONF_HOST]:
self.deconz_config = bridge
return await self.async_step_link()
self.deconz_config = user_input
return await self.async_step_link()
session = aiohttp_client.async_get_clientsession(self.hass)
self.bridges = await async_discovery(session)
@@ -58,19 +62,24 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
if len(self.bridges) == 1:
self.deconz_config = self.bridges[0]
return await self.async_step_link()
if len(self.bridges) > 1:
hosts = []
for bridge in self.bridges:
hosts.append(bridge[CONF_HOST])
return self.async_show_form(
step_id='user',
step_id='init',
data_schema=vol.Schema({
vol.Required(CONF_HOST): vol.In(hosts)
})
)
return self.async_abort(
reason='no_bridges'
return self.async_show_form(
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}),
)
async def async_step_link(self, user_input=None):
@@ -135,13 +144,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
deconz_config[CONF_BRIDGEID] = discovery_info.get('serial')
config_file = await self.hass.async_add_job(
load_json, self.hass.config.path(CONFIG_FILE))
if config_file and \
config_file[CONF_HOST] == deconz_config[CONF_HOST] and \
CONF_API_KEY in config_file:
deconz_config[CONF_API_KEY] = config_file[CONF_API_KEY]
return await self.async_step_import(deconz_config)
async def async_step_import(self, import_config):

View File

@@ -4,11 +4,8 @@ import logging
_LOGGER = logging.getLogger('homeassistant.components.deconz')
DOMAIN = 'deconz'
CONFIG_FILE = 'deconz.conf'
DATA_DECONZ_EVENT = 'deconz_events'
DATA_DECONZ_ID = 'deconz_entities'
DATA_DECONZ_UNSUB = 'deconz_dispatchers'
DECONZ_DOMAIN = 'deconz'
DEFAULT_PORT = 80
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
@@ -16,6 +13,8 @@ CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
'light', 'scene', 'sensor', 'switch']
DECONZ_REACHABLE = 'deconz_reachable'
ATTR_DARK = 'dark'
ATTR_ON = 'on'

View File

@@ -8,7 +8,7 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.util import slugify
from .const import (
_LOGGER, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS)
_LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS)
class DeconzGateway:
@@ -18,6 +18,7 @@ class DeconzGateway:
"""Initialize the system."""
self.hass = hass
self.config_entry = config_entry
self.available = True
self.api = None
self._cancel_retry_setup = None
@@ -30,7 +31,8 @@ class DeconzGateway:
hass = self.hass
self.api = await get_gateway(
hass, self.config_entry.data, self.async_add_device_callback
hass, self.config_entry.data, self.async_add_device_callback,
self.async_connection_status_callback
)
if self.api is False:
@@ -65,6 +67,13 @@ class DeconzGateway:
return True
@callback
def async_connection_status_callback(self, available):
"""Handle signals of gateway connection status."""
self.available = available
async_dispatcher_send(
self.hass, DECONZ_REACHABLE, {'state': True, 'attr': 'reachable'})
@callback
def async_add_device_callback(self, device_type, device):
"""Handle event of new device creation in deCONZ."""
@@ -122,13 +131,15 @@ class DeconzGateway:
return True
async def get_gateway(hass, config, async_add_device_callback):
async def get_gateway(hass, config, async_add_device_callback,
async_connection_status_callback):
"""Create a gateway object and verify configuration."""
from pydeconz import DeconzSession
session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **config,
async_add_device=async_add_device_callback)
async_add_device=async_add_device_callback,
connection_status=async_connection_status_callback)
result = await deconz.async_load_parameters()
if result:

View File

@@ -6,7 +6,7 @@
"title": "Define deCONZ gateway",
"data": {
"host": "Host",
"port": "Port (default value: '80')"
"port": "Port"
}
},
"link": {

View File

@@ -182,6 +182,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
setup = await hass.async_add_job(
platform.setup_scanner, hass, p_config, tracker.see,
disc_info)
elif hasattr(platform, 'async_setup_entry'):
setup = await platform.async_setup_entry(
hass, p_config, tracker.async_see)
else:
raise HomeAssistantError("Invalid device_tracker platform.")
@@ -197,6 +200,8 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Error setting up platform %s", p_type)
hass.data[DOMAIN] = async_setup_platform
setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config
in config_per_platform(config, DOMAIN)]
if setup_tasks:
@@ -230,6 +235,12 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType):
return True
async def async_setup_entry(hass, entry):
"""Set up an entry."""
await hass.data[DOMAIN](entry.domain, entry)
return True
class DeviceTracker:
"""Representation of a device tracker."""
@@ -373,6 +384,7 @@ class DeviceTracker:
for device in self.devices.values():
if (device.track and device.last_update_home) and \
device.stale(now):
device.mark_stale()
self.hass.async_create_task(device.async_update_ha_state(True))
async def async_setup_tracked_device(self):
@@ -528,9 +540,15 @@ class Device(Entity):
Async friendly.
"""
return self.last_seen and \
return self.last_seen is None or \
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
def mark_stale(self):
"""Mark the device state as stale."""
self._state = STATE_NOT_HOME
self.gps = None
self.last_update_home = False
async def async_update(self):
"""Update state of entity.
@@ -550,9 +568,7 @@ class Device(Entity):
else:
self._state = zone_state.name
elif self.stale():
self._state = STATE_NOT_HOME
self.gps = None
self.last_update_home = False
self.mark_stale()
else:
self._state = STATE_HOME
self.last_update_home = True
@@ -563,6 +579,7 @@ class Device(Entity):
if not state:
return
self._state = state.state
self.last_update_home = (state.state == STATE_HOME)
for attr, var in (
(ATTR_SOURCE_TYPE, 'source_type'),

View File

@@ -6,43 +6,17 @@ https://home-assistant.io/components/device_tracker.asuswrt/
"""
import logging
import voluptuous as vol
from homeassistant.components.asuswrt import DATA_ASUSWRT
from homeassistant.components.device_tracker import DeviceScanner
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE,
CONF_PROTOCOL)
REQUIREMENTS = ['aioasuswrt==1.1.2']
DEPENDENCIES = ['asuswrt']
_LOGGER = logging.getLogger(__name__)
CONF_PUB_KEY = 'pub_key'
CONF_SSH_KEY = 'ssh_key'
CONF_REQUIRE_IP = 'require_ip'
DEFAULT_SSH_PORT = 22
SECRET_GROUP = 'Password or SSH Key'
PLATFORM_SCHEMA = vol.All(
cv.has_at_least_one_key(CONF_PASSWORD, CONF_PUB_KEY, CONF_SSH_KEY),
PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PROTOCOL, default='ssh'): vol.In(['ssh', 'telnet']),
vol.Optional(CONF_MODE, default='router'): vol.In(['router', 'ap']),
vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port,
vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean,
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile
}))
async def async_get_scanner(hass, config):
"""Validate the configuration and return an ASUS-WRT scanner."""
scanner = AsusWrtDeviceScanner(config[DOMAIN])
scanner = AsusWrtDeviceScanner(hass.data[DATA_ASUSWRT])
await scanner.async_connect()
return scanner if scanner.success_init else None
@@ -51,19 +25,11 @@ class AsusWrtDeviceScanner(DeviceScanner):
"""This class queries a router running ASUSWRT firmware."""
# Eighth attribute needed for mode (AP mode vs router mode)
def __init__(self, config):
def __init__(self, api):
"""Initialize the scanner."""
from aioasuswrt.asuswrt import AsusWrt
self.last_results = {}
self.success_init = False
self.connection = AsusWrt(config[CONF_HOST], config[CONF_PORT],
config[CONF_PROTOCOL] == 'telnet',
config[CONF_USERNAME],
config.get(CONF_PASSWORD, ''),
config.get('ssh_key',
config.get('pub_key', '')),
config[CONF_MODE], config[CONF_REQUIRE_IP])
self.connection = api
async def async_connect(self):
"""Initialize connection to the router."""

View File

@@ -4,129 +4,26 @@ Support for the Geofency platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.geofency/
"""
from functools import partial
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
ATTR_LATITUDE, ATTR_LONGITUDE, HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import slugify
from homeassistant.components.geofency import TRACKER_UPDATE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
ATTR_CURRENT_LATITUDE = 'currentLatitude'
ATTR_CURRENT_LONGITUDE = 'currentLongitude'
BEACON_DEV_PREFIX = 'beacon'
CONF_MOBILE_BEACONS = 'mobile_beacons'
LOCATION_ENTRY = '1'
LOCATION_EXIT = '0'
URL = '/api/geofency'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MOBILE_BEACONS): vol.All(
cv.ensure_list, [cv.string]),
})
DEPENDENCIES = ['geofency']
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up an endpoint for the Geofency application."""
mobile_beacons = config.get(CONF_MOBILE_BEACONS) or []
hass.http.register_view(GeofencyView(see, mobile_beacons))
return True
class GeofencyView(HomeAssistantView):
"""View to handle Geofency requests."""
url = URL
name = 'api:geofency'
def __init__(self, see, mobile_beacons):
"""Initialize Geofency url endpoints."""
self.see = see
self.mobile_beacons = [slugify(beacon) for beacon in mobile_beacons]
async def post(self, request):
"""Handle Geofency requests."""
data = await request.post()
hass = request.app['hass']
data = self._validate_data(data)
if not data:
return ("Invalid data", HTTP_UNPROCESSABLE_ENTITY)
if self._is_mobile_beacon(data):
return await self._set_location(hass, data, None)
if data['entry'] == LOCATION_ENTRY:
location_name = data['name']
else:
location_name = STATE_NOT_HOME
if ATTR_CURRENT_LATITUDE in data:
data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE]
data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE]
return await self._set_location(hass, data, location_name)
@staticmethod
def _validate_data(data):
"""Validate POST payload."""
data = data.copy()
required_attributes = ['address', 'device', 'entry',
'latitude', 'longitude', 'name']
valid = True
for attribute in required_attributes:
if attribute not in data:
valid = False
_LOGGER.error("'%s' not specified in message", attribute)
if not valid:
return False
data['address'] = data['address'].replace('\n', ' ')
data['device'] = slugify(data['device'])
data['name'] = slugify(data['name'])
gps_attributes = [ATTR_LATITUDE, ATTR_LONGITUDE,
ATTR_CURRENT_LATITUDE, ATTR_CURRENT_LONGITUDE]
for attribute in gps_attributes:
if attribute in data:
data[attribute] = float(data[attribute])
return data
def _is_mobile_beacon(self, data):
"""Check if we have a mobile beacon."""
return 'beaconUUID' in data and data['name'] in self.mobile_beacons
@staticmethod
def _device_name(data):
"""Return name of device tracker."""
if 'beaconUUID' in data:
return "{}_{}".format(BEACON_DEV_PREFIX, data['name'])
return data['device']
async def _set_location(self, hass, data, location_name):
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up the Geofency device tracker."""
async def _set_location(device, gps, location_name, attributes):
"""Fire HA event to set location."""
device = self._device_name(data)
await async_see(
dev_id=device,
gps=gps,
location_name=location_name,
attributes=attributes
)
await hass.async_add_job(
partial(self.see, dev_id=device,
gps=(data[ATTR_LATITUDE], data[ATTR_LONGITUDE]),
location_name=location_name,
attributes=data))
return "Setting location for {}".format(device)
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
return True

View File

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

View File

@@ -0,0 +1,92 @@
"""
Support for Google Home bluetooth tacker.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.googlehome/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST
REQUIREMENTS = ['ghlocalapi==0.1.0']
_LOGGER = logging.getLogger(__name__)
CONF_RSSI_THRESHOLD = 'rssi_threshold'
PLATFORM_SCHEMA = vol.All(
PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_RSSI_THRESHOLD, default=-70): vol.Coerce(int),
}))
async def async_get_scanner(hass, config):
"""Validate the configuration and return an Google Home scanner."""
scanner = GoogleHomeDeviceScanner(hass, config[DOMAIN])
await scanner.async_connect()
return scanner if scanner.success_init else None
class GoogleHomeDeviceScanner(DeviceScanner):
"""This class queries a Google Home unit."""
def __init__(self, hass, config):
"""Initialize the scanner."""
from ghlocalapi.device_info import DeviceInfo
from ghlocalapi.bluetooth import Bluetooth
self.last_results = {}
self.success_init = False
self._host = config[CONF_HOST]
self.rssi_threshold = config[CONF_RSSI_THRESHOLD]
session = async_get_clientsession(hass)
self.deviceinfo = DeviceInfo(hass.loop, session, self._host)
self.scanner = Bluetooth(hass.loop, session, self._host)
async def async_connect(self):
"""Initialize connection to Google Home."""
await self.deviceinfo.get_device_info()
data = self.deviceinfo.device_info
self.success_init = data is not None
async def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
await self.async_update_info()
return list(self.last_results.keys())
async def async_get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
if device not in self.last_results:
return None
return '{}_{}'.format(self._host,
self.last_results[device]['btle_mac_address'])
async def get_extra_attributes(self, device):
"""Return the extra attributes of the device."""
return self.last_results[device]
async def async_update_info(self):
"""Ensure the information from Google Home is up to date."""
_LOGGER.debug('Checking Devices...')
await self.scanner.scan_for_devices()
await self.scanner.get_scan_result()
ghname = self.deviceinfo.device_info['name']
devices = {}
for device in self.scanner.devices:
if device['rssi'] > self.rssi_threshold:
uuid = '{}_{}'.format(self._host, device['mac_address'])
devices[uuid] = {}
devices[uuid]['rssi'] = device['rssi']
devices[uuid]['btle_mac_address'] = device['mac_address']
devices[uuid]['ghname'] = ghname
devices[uuid]['source_type'] = 'bluetooth'
self.last_results = devices

View File

@@ -39,7 +39,7 @@ Device = namedtuple('Device', ['name', 'ip', 'mac', 'state'])
class HuaweiDeviceScanner(DeviceScanner):
"""This class queries a router running HUAWEI firmware."""
ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*),null\);')
ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*)null\);')
DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),')
DEVICE_ATTR_REGEX = re.compile(
'"(?P<Domain>.*?)","(?P<IpAddr>.*?)",'

View File

@@ -7,6 +7,7 @@ https://home-assistant.io/components/device_tracker.luci/
import json
import logging
import re
from collections import namedtuple
import requests
import voluptuous as vol
@@ -43,14 +44,17 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
Device = namedtuple('Device', ['mac', 'ip', 'flags', 'device', 'host'])
class LuciDeviceScanner(DeviceScanner):
"""This class queries a wireless router running OpenWrt firmware."""
def __init__(self, config):
"""Initialize the scanner."""
host = config[CONF_HOST]
self.host = config[CONF_HOST]
protocol = 'http' if not config[CONF_SSL] else 'https'
self.origin = '{}://{}'.format(protocol, host)
self.origin = '{}://{}'.format(protocol, self.host)
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
@@ -68,7 +72,7 @@ class LuciDeviceScanner(DeviceScanner):
def scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
self._update_info()
return self.last_results
return [device.mac for device in self.last_results]
def get_device_name(self, device):
"""Return the name of the given device or None if we don't know."""
@@ -88,6 +92,18 @@ class LuciDeviceScanner(DeviceScanner):
return
return self.mac2name.get(device.upper(), None)
def get_extra_attributes(self, device):
"""Return the IP of the given device."""
filter_att = next((
{
'ip': result.ip,
'flags': result.flags,
'device': result.device,
'host': result.host
} for result in self.last_results
if result.mac == device), None)
return filter_att
def _update_info(self):
"""Ensure the information from the Luci router is up to date.
@@ -114,7 +130,11 @@ class LuciDeviceScanner(DeviceScanner):
# Check if the Flags for each device contain
# NUD_REACHABLE and if so, add it to last_results
if int(device_entry['Flags'], 16) & 0x2:
self.last_results.append(device_entry['HW address'])
self.last_results.append(Device(device_entry['HW address'],
device_entry['IP address'],
device_entry['Flags'],
device_entry['Device'],
self.host))
return True

View File

@@ -6,25 +6,30 @@ https://home-assistant.io/components/device_tracker.mikrotik/
"""
import logging
import ssl
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL, CONF_METHOD)
REQUIREMENTS = ['librouteros==2.1.1']
MTK_DEFAULT_API_PORT = '8728'
_LOGGER = logging.getLogger(__name__)
MTK_DEFAULT_API_PORT = '8728'
MTK_DEFAULT_API_SSL_PORT = '8729'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PORT, default=MTK_DEFAULT_API_PORT): cv.port
vol.Optional(CONF_METHOD): cv.string,
vol.Optional(CONF_PORT): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean
})
@@ -42,9 +47,17 @@ class MikrotikScanner(DeviceScanner):
self.last_results = {}
self.host = config[CONF_HOST]
self.port = config[CONF_PORT]
self.ssl = config[CONF_SSL]
try:
self.port = config[CONF_PORT]
except KeyError:
if self.ssl:
self.port = MTK_DEFAULT_API_SSL_PORT
else:
self.port = MTK_DEFAULT_API_PORT
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.method = config.get(CONF_METHOD)
self.connected = False
self.success_init = False
@@ -53,27 +66,29 @@ class MikrotikScanner(DeviceScanner):
self.success_init = self.connect_to_device()
if self.success_init:
_LOGGER.info(
"Start polling Mikrotik (%s) router...",
self.host
)
_LOGGER.info("Start polling Mikrotik (%s) router...", self.host)
self._update_info()
else:
_LOGGER.error(
"Connection to Mikrotik (%s) failed",
self.host
)
_LOGGER.error("Connection to Mikrotik (%s) failed", self.host)
def connect_to_device(self):
"""Connect to Mikrotik method."""
import librouteros
try:
kwargs = {
'port': self.port,
'encoding': 'utf-8'
}
if self.ssl:
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
kwargs['ssl_wrapper'] = ssl_context.wrap_socket
self.client = librouteros.connect(
self.host,
self.username,
self.password,
port=int(self.port),
encoding='utf-8'
**kwargs
)
try:
@@ -86,16 +101,15 @@ class MikrotikScanner(DeviceScanner):
raise
if routerboard_info:
_LOGGER.info("Connected to Mikrotik %s with IP %s",
routerboard_info[0].get('model', 'Router'),
self.host)
_LOGGER.info(
"Connected to Mikrotik %s with IP %s",
routerboard_info[0].get('model', 'Router'), self.host)
self.connected = True
try:
self.capsman_exist = self.client(
cmd='/caps-man/interface/getall'
)
cmd='/caps-man/interface/getall')
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionError):
@@ -103,27 +117,27 @@ class MikrotikScanner(DeviceScanner):
if not self.capsman_exist:
_LOGGER.info(
'Mikrotik %s: Not a CAPSman controller. Trying '
'local interfaces ',
self.host
)
"Mikrotik %s: Not a CAPSman controller. Trying "
"local interfaces", self.host)
try:
self.wireless_exist = self.client(
cmd='/interface/wireless/getall'
)
cmd='/interface/wireless/getall')
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
librouteros.exceptions.ConnectionError):
self.wireless_exist = False
if not self.wireless_exist:
if not self.wireless_exist or self.method == 'ip':
_LOGGER.info(
'Mikrotik %s: Wireless adapters not found. Try to '
'use DHCP lease table as presence tracker source. '
'Please decrease lease time as much as possible.',
self.host
)
"Mikrotik %s: Wireless adapters not found. Try to "
"use DHCP lease table as presence tracker source. "
"Please decrease lease time as much as possible",
self.host)
if self.method:
_LOGGER.info(
"Mikrotik %s: Manually selected polling method %s",
self.host, self.method)
except (librouteros.exceptions.TrapError,
librouteros.exceptions.MultiTrapError,
@@ -143,28 +157,27 @@ class MikrotikScanner(DeviceScanner):
def _update_info(self):
"""Retrieve latest information from the Mikrotik box."""
if self.capsman_exist:
devices_tracker = 'capsman'
elif self.wireless_exist:
devices_tracker = 'wireless'
if self.method:
devices_tracker = self.method
else:
devices_tracker = 'ip'
if self.capsman_exist:
devices_tracker = 'capsman'
elif self.wireless_exist:
devices_tracker = 'wireless'
else:
devices_tracker = 'ip'
_LOGGER.info(
"Loading %s devices from Mikrotik (%s) ...",
devices_tracker,
self.host
)
devices_tracker, self.host)
device_names = self.client(cmd='/ip/dhcp-server/lease/getall')
if devices_tracker == 'capsman':
devices = self.client(
cmd='/caps-man/registration-table/getall'
)
cmd='/caps-man/registration-table/getall')
elif devices_tracker == 'wireless':
devices = self.client(
cmd='/interface/wireless/registration-table/getall'
)
cmd='/interface/wireless/registration-table/getall')
else:
devices = device_names
@@ -172,21 +185,17 @@ class MikrotikScanner(DeviceScanner):
return False
mac_names = {device.get('mac-address'): device.get('host-name')
for device in device_names
if device.get('mac-address')}
for device in device_names if device.get('mac-address')}
if self.wireless_exist or self.capsman_exist:
if devices_tracker in ('wireless', 'capsman'):
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in devices
}
for device in devices}
else:
self.last_results = {
device.get('mac-address'):
mac_names.get(device.get('mac-address'))
for device in device_names
if device.get('active-address')
}
for device in device_names if device.get('active-address')}
return True

View File

@@ -19,11 +19,16 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
return False
for device in new_devices:
gateway_id = id(device.gateway)
dev_id = (
id(device.gateway), device.node_id, device.child_id,
gateway_id, device.node_id, device.child_id,
device.value_type)
async_dispatcher_connect(
hass, mysensors.const.SIGNAL_CALLBACK.format(*dev_id),
hass, mysensors.const.CHILD_CALLBACK.format(*dev_id),
device.async_update_callback)
async_dispatcher_connect(
hass,
mysensors.const.NODE_CALLBACK.format(gateway_id, device.node_id),
device.async_update_callback)
return True

View File

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

View File

@@ -7,55 +7,29 @@ https://home-assistant.io/components/device_tracker.owntracks/
import base64
import json
import logging
from collections import defaultdict
import voluptuous as vol
from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, ATTR_SOURCE_TYPE, SOURCE_TYPE_BLUETOOTH_LE,
SOURCE_TYPE_GPS
ATTR_SOURCE_TYPE, SOURCE_TYPE_BLUETOOTH_LE, SOURCE_TYPE_GPS
)
from homeassistant.components.owntracks import DOMAIN as OT_DOMAIN
from homeassistant.const import STATE_HOME
from homeassistant.core import callback
from homeassistant.util import slugify, decorator
REQUIREMENTS = ['libnacl==1.6.1']
DEPENDENCIES = ['owntracks']
_LOGGER = logging.getLogger(__name__)
HANDLERS = decorator.Registry()
BEACON_DEV_ID = 'beacon'
CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy'
CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
CONF_MQTT_TOPIC = 'mqtt_topic'
CONF_REGION_MAPPING = 'region_mapping'
CONF_EVENTS_ONLY = 'events_only'
DEPENDENCIES = ['mqtt']
DEFAULT_OWNTRACKS_TOPIC = 'owntracks/#'
REGION_MAPPING = {}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MAX_GPS_ACCURACY): vol.Coerce(float),
vol.Optional(CONF_WAYPOINT_IMPORT, default=True): cv.boolean,
vol.Optional(CONF_EVENTS_ONLY, default=False): cv.boolean,
vol.Optional(CONF_MQTT_TOPIC, default=DEFAULT_OWNTRACKS_TOPIC):
mqtt.valid_subscribe_topic,
vol.Optional(CONF_WAYPOINT_WHITELIST): vol.All(
cv.ensure_list, [cv.string]),
vol.Optional(CONF_SECRET): vol.Any(
vol.Schema({vol.Optional(cv.string): cv.string}),
cv.string),
vol.Optional(CONF_REGION_MAPPING, default=REGION_MAPPING): dict
})
async def async_setup_entry(hass, entry, async_see):
"""Set up OwnTracks based off an entry."""
hass.data[OT_DOMAIN]['context'].async_see = async_see
hass.helpers.dispatcher.async_dispatcher_connect(
OT_DOMAIN, async_handle_message)
return True
def get_cipher():
@@ -72,29 +46,6 @@ def get_cipher():
return (KEYLEN, decrypt)
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
context = context_from_config(async_see, config)
async def async_handle_mqtt_message(topic, payload, qos):
"""Handle incoming OwnTracks message."""
try:
message = json.loads(payload)
except ValueError:
# If invalid JSON
_LOGGER.error("Unable to parse payload as JSON: %s", payload)
return
message['topic'] = topic
await async_handle_message(hass, context, message)
await mqtt.async_subscribe(
hass, context.mqtt_topic, async_handle_mqtt_message, 1)
return True
def _parse_topic(topic, subscribe_topic):
"""Parse an MQTT topic {sub_topic}/user/dev, return (user, dev) tuple.
@@ -202,93 +153,6 @@ def _decrypt_payload(secret, topic, ciphertext):
return None
def context_from_config(async_see, config):
"""Create an async context from Home Assistant config."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
region_mapping = config.get(CONF_REGION_MAPPING)
events_only = config.get(CONF_EVENTS_ONLY)
mqtt_topic = config.get(CONF_MQTT_TOPIC)
return OwnTracksContext(async_see, secret, max_gps_accuracy,
waypoint_import, waypoint_whitelist,
region_mapping, events_only, mqtt_topic)
class OwnTracksContext:
"""Hold the current OwnTracks context."""
def __init__(self, async_see, secret, max_gps_accuracy, import_waypoints,
waypoint_whitelist, region_mapping, events_only, mqtt_topic):
"""Initialize an OwnTracks context."""
self.async_see = async_see
self.secret = secret
self.max_gps_accuracy = max_gps_accuracy
self.mobile_beacons_active = defaultdict(set)
self.regions_entered = defaultdict(list)
self.import_waypoints = import_waypoints
self.waypoint_whitelist = waypoint_whitelist
self.region_mapping = region_mapping
self.events_only = events_only
self.mqtt_topic = mqtt_topic
@callback
def async_valid_accuracy(self, message):
"""Check if we should ignore this message."""
acc = message.get('acc')
if acc is None:
return False
try:
acc = float(acc)
except ValueError:
return False
if acc == 0:
_LOGGER.warning(
"Ignoring %s update because GPS accuracy is zero: %s",
message['_type'], message)
return False
if self.max_gps_accuracy is not None and \
acc > self.max_gps_accuracy:
_LOGGER.info("Ignoring %s update because expected GPS "
"accuracy %s is not met: %s",
message['_type'], self.max_gps_accuracy,
message)
return False
return True
async def async_see_beacons(self, hass, dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# Mobile beacons should always be set to the location of the
# tracking device. I get the device state and make the necessary
# changes to kwargs.
device_tracker_state = hass.states.get(
"device_tracker.{}".format(dev_id))
if device_tracker_state is not None:
acc = device_tracker_state.attributes.get("gps_accuracy")
lat = device_tracker_state.attributes.get("latitude")
lon = device_tracker_state.attributes.get("longitude")
kwargs['gps_accuracy'] = acc
kwargs['gps'] = (lat, lon)
# the battery state applies to the tracking device, not the beacon
# kwargs location is the beacon's configured lat/lon
kwargs.pop('battery', None)
for beacon in self.mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
await self.async_see(**kwargs)
@HANDLERS.register('location')
async def async_handle_location_message(hass, context, message):
"""Handle a location message."""
@@ -485,6 +349,8 @@ async def async_handle_message(hass, context, message):
"""Handle an OwnTracks message."""
msgtype = message.get('_type')
_LOGGER.debug("Received %s", message)
handler = HANDLERS.get(msgtype, async_handle_unsupported_msg)
await handler(hass, context, message)

View File

@@ -1,55 +0,0 @@
"""
Device tracker platform that adds support for OwnTracks over HTTP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks_http/
"""
import re
from aiohttp.web_exceptions import HTTPInternalServerError
from homeassistant.components.http import HomeAssistantView
# pylint: disable=unused-import
from .owntracks import ( # NOQA
REQUIREMENTS, PLATFORM_SCHEMA, context_from_config, async_handle_message)
DEPENDENCIES = ['http']
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
context = context_from_config(async_see, config)
hass.http.register_view(OwnTracksView(context))
return True
class OwnTracksView(HomeAssistantView):
"""View to handle OwnTracks HTTP requests."""
url = '/api/owntracks/{user}/{device}'
name = 'api:owntracks'
def __init__(self, context):
"""Initialize OwnTracks URL endpoints."""
self.context = context
async def post(self, request, user, device):
"""Handle an OwnTracks message."""
hass = request.app['hass']
subscription = self.context.mqtt_topic
topic = re.sub('/#$', '', subscription)
message = await request.json()
message['topic'] = '{}/{}/{}'.format(topic, user, device)
try:
await async_handle_message(hass, self.context, message)
return self.json([])
except ValueError:
raise HTTPInternalServerError

View File

@@ -19,7 +19,7 @@ _LOGGER = logging.getLogger(__name__)
_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})')
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string
vol.Optional(CONF_HOST): cv.string
})

View File

@@ -18,7 +18,7 @@ from homeassistant.util import slugify
from homeassistant.util.json import load_json, save_json
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pytile==2.0.2']
REQUIREMENTS = ['pytile==2.0.5']
CLIENT_UUID_CONFIG_FILE = '.tile.conf'
DEVICE_TYPES = ['PHONE', 'TILE']

View File

@@ -0,0 +1,90 @@
"""
Support for Traccar device tracking.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.traccar/
"""
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_SSL, CONF_VERIFY_SSL,
CONF_PASSWORD, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.util import slugify
REQUIREMENTS = ['pytraccar==0.1.2']
_LOGGER = logging.getLogger(__name__)
ATTR_ADDRESS = 'address'
ATTR_CATEGORY = 'category'
ATTR_GEOFENCE = 'geofence'
ATTR_TRACKER = 'tracker'
DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=8082): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean,
vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
})
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Validate the configuration and return a Traccar scanner."""
from pytraccar.api import API
session = async_get_clientsession(hass, config[CONF_VERIFY_SSL])
api = API(hass.loop, session, config[CONF_USERNAME], config[CONF_PASSWORD],
config[CONF_HOST], config[CONF_PORT], config[CONF_SSL])
scanner = TraccarScanner(api, hass, async_see)
return await scanner.async_init()
class TraccarScanner:
"""Define an object to retrieve Traccar data."""
def __init__(self, api, hass, async_see):
"""Initialize."""
self._async_see = async_see
self._api = api
self._hass = hass
async def async_init(self):
"""Further initialize connection to Traccar."""
await self._api.test_connection()
if self._api.authenticated:
await self._async_update()
async_track_time_interval(self._hass,
self._async_update,
DEFAULT_SCAN_INTERVAL)
return self._api.authenticated
async def _async_update(self, now=None):
"""Update info from Traccar."""
_LOGGER.debug('Updating device data.')
await self._api.get_device_info()
for devicename in self._api.device_info:
device = self._api.device_info[devicename]
device_attributes = {
ATTR_ADDRESS: device['address'],
ATTR_GEOFENCE: device['geofence'],
ATTR_CATEGORY: device['category'],
ATTR_TRACKER: 'traccar'
}
await self._async_see(
dev_id=slugify(device['device_id']),
gps=(device['latitude'], device['longitude']),
attributes=device_attributes)

View File

@@ -61,7 +61,8 @@ class XiaomiMiioDeviceScanner(DeviceScanner):
devices = []
try:
station_info = await self.hass.async_add_job(self.device.status)
station_info = \
await self.hass.async_add_executor_job(self.device.status)
_LOGGER.debug("Got new station info: %s", station_info)
for device in station_info.associated_stations:

View File

@@ -0,0 +1,18 @@
{
"config": {
"abort": {
"not_internet_accessible": "Va\u0161e Home Assistant instance mus\u00ed b\u00fdt p\u0159\u00edstupn\u00e1 z internetu aby mohla p\u0159ij\u00edmat zpr\u00e1vy Dialogflow.",
"one_instance_allowed": "Povolena je pouze jedna instance."
},
"create_entry": {
"default": "Chcete-li odeslat ud\u00e1losti do aplikace Home Assistant, budete muset nastavit [integraci Dialogflow]({dialogflow_url}). \n\n Vypl\u0148te n\u00e1sleduj\u00edc\u00ed informace: \n\n - URL: `{webhook_url}' \n - Metoda: POST \n - Typ obsahu: aplikace/json \n\n Podrobn\u011bj\u0161\u00ed informace naleznete v [dokumentaci]({docs_url})."
},
"step": {
"user": {
"description": "Opravdu chcete nastavit Dialogflow?",
"title": "Nastavit Dialogflow Webhook"
}
},
"title": "Dialogflow"
}
}

View File

@@ -0,0 +1,10 @@
{
"config": {
"step": {
"user": {
"title": "Dialogflow Webhook einrichten"
}
},
"title": "Dialogflow"
}
}

View File

@@ -0,0 +1,14 @@
{
"config": {
"abort": {
"not_internet_accessible": "Su instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Dialogflow.",
"one_instance_allowed": "Solo una instancia es necesaria."
},
"step": {
"user": {
"description": "\u00bfEst\u00e1 seguro de que desea configurar Dialogflow?"
}
},
"title": "Dialogflow"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"not_internet_accessible": "Uw Home Assistant instantie moet toegankelijk zijn vanaf het internet om Dialogflow-berichten te ontvangen.",
"one_instance_allowed": "Slechts \u00e9\u00e9n instantie is nodig."
},
"step": {
"user": {
"description": "Weet u zeker dat u Dialogflow wilt instellen?",
"title": "Stel de Twilio Dialogflow in"
}
},
"title": "Dialogflow"
}
}

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