Compare commits

...

234 Commits

Author SHA1 Message Date
Paulus Schoutsen
69934a9598 Bumped version to 0.76.0b0 2018-08-11 08:58:52 +02:00
Paulus Schoutsen
f24773933c Update frontend to 20180811.0 2018-08-11 08:58:20 +02:00
Franck Nijhof
e17e080639 ✏️ Corrects typo in code comments (#15923)
`MomematicIP` -> `HomematicIP`
2018-08-11 08:47:41 +02:00
cgtobi
055e35b297 Add RMV public transport sensor (#15814)
* Add new public transport sensor for RMV (Rhein-Main area).

* Add required module.

* Fix naming problem.

* Add unit test.

* Update dependency version to 0.0.5.

* Add new requirements.

* Fix variable name.

* Fix issues pointed out in review.

* Remove unnecessary code.

* Fix linter error.

* Fix config value validation.

* Replace minutes as state by departure timestamp. (see ##14983)

* More work on the timestamp. (see ##14983)

* Revert timestamp work until #14983 gets merged.

* Simplify product validation.

* Remove redundant code.

* Address code change requests.

* Address more code change requests.

* Address even more code change requests.

* Simplify destination check.

* Fix linter problem.

* Bump dependency version to 0.0.7.

* Name variable more explicit.

* Only query once a minute.

* Update test case.

* Fix config validation.

* Remove unneeded import.
2018-08-10 19:35:09 +02:00
Robert Svensson
81604a9326 deCONZ - Add support for sirens (#15896)
* Add support for sirenes

* Too quick...

* Fix test

* Use siren instead of sirene
2018-08-10 19:22:12 +02:00
Paulus Schoutsen
a0e9f9f218 Merge remote-tracking branch 'origin/master' into dev 2018-08-10 18:10:56 +02:00
Paulus Schoutsen
0ab3e7a92a Add IndieAuth 4.2.2 redirect uri at client id (#15911)
* Add IndieAuth 4.2.2 redirect uri at client id

* Fix tests

* Add comment

* Limit to first 10kB of each page
2018-08-10 18:09:42 +02:00
Paulus Schoutsen
9512bb9587 Add and restore context in recorder (#15859) 2018-08-10 18:09:01 +02:00
Adam Mills
da916d7b27 Fix bug in translations upload script (#15922) 2018-08-10 11:35:01 -04:00
clayton craft
b370b6a4e4 Update radiotherm to 1.4.1 (#15910) 2018-08-10 16:10:19 +02:00
Ville Skyttä
1911168855 Misc cleanups (#15907)
* device_tracker.huawei_router: Pylint logging-not-lazy fix

* sensor.irish_rail_transport: Clean up redundant self.info test
2018-08-10 16:09:08 +02:00
Joe Lu
f98629b895 Update August component to use py-august:0.6.0 (#15916) 2018-08-10 07:27:49 +02:00
Ville Skyttä
dc01b17260 Some typing related fixes (#15899)
* Fix FlowManager.async_init handler type

It's not a Callable, but typically a key pointing to one in a dict.

* Mark pip_kwargs return type hint as Any-valued dict

install_package takes other than str args too.
2018-08-09 22:53:12 +02:00
Benoit Louy
ef61c0c3a4 Add PJLink media player platform (#15083)
* add pjlink media player component

* retrieve pjlink device name from projector if name isn't specified in configuration

* update .coveragerc

* fix style

* add missing docstrings

* address PR comments from @MartinHjelmare

* fix code style

* use snake case string for source names

* add missing period at the end of comment string

* rewrite method as function

* revert to use source name provided by projector
2018-08-09 19:58:16 +02:00
mountainsandcode
664eae72d1 Add realtime true/false switch for Waze (#15228) 2018-08-09 16:27:29 +02:00
rafale77
86658f310d Fix for multiple camera switches naming of entity (#14028)
* Fix for multiple camera switches naming of entity

appended camera name to the switch entity name.

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Update amcrest.py

* Add digest authentification

* Update rest_command.py

* Update config.py

* Update rest_command.py

* Update config.py
2018-08-09 15:59:23 +02:00
Mattias Welponer
a29f867908 Add HomematicIP Cloud smoke detector device (#15621)
* Add smoke detector device

* Remove not needed __init__ functions
2018-08-09 14:43:13 +02:00
Paulus Schoutsen
28de2d6f75 Merge pull request #15903 from home-assistant/rc
0.75.3
2018-08-09 14:34:31 +02:00
Paulus Schoutsen
37d98474d5 Bumped version to 0.75.3 2018-08-09 13:41:24 +02:00
Jason Hu
5116f02290 Fix downgrade hassio cannot get refresh_token issue (#15874)
* Fix downgrade hassio issue

* Update __init__.py
2018-08-09 13:38:52 +02:00
Jason Hu
2233d7ca98 Fix downgrade hassio cannot get refresh_token issue (#15874)
* Fix downgrade hassio issue

* Update __init__.py
2018-08-09 13:31:48 +02:00
Jason Hu
f58425dd3c Refactor data entry flow (#15883)
* Refactoring data_entry_flow and config_entry_flow

Move SOURCE_* to config_entries
Change data_entry_flow.FlowManager.async_init() source param default
 to None
Change this first step_id as source or init if source is None
_BaseFlowManagerView pass in SOURCE_USER as default source

* First step of data entry flow decided by _async_create_flow() now

* Lint

* Change helpers.config_entry_flow.DiscoveryFlowHandler default step

* Change FlowManager.async_init source param to context dict param
2018-08-09 13:24:14 +02:00
Fabian Affolter
39d19f2183 Upgrade locationsharinglib to 2.0.11 (#15902) 2018-08-09 13:05:28 +02:00
Paulus Schoutsen
99c4c65f69 Add auth/authorize endpoint (#15887) 2018-08-09 09:27:54 +02:00
Fabian Affolter
61901496ec Upgrade pylast to 2.4.0 (#15886) 2018-08-08 22:32:21 +02:00
Steven Looman
0ab65f1ac5 Follow changes to netdisco, separating DLNA into DLNA_DMS and DLNA_DMR (#15877)
* Follow changes to netdisco, separating DLNA into DLNA_DMS and DLNA_DMR

* No uppercase for names of netdisco discoverables
2018-08-08 11:54:22 +02:00
Fabian Affolter
debdc707e9 Upgrade netdisco to 2.0.0 (#15885) 2018-08-08 11:53:43 +02:00
DubhAd
fcc918a146 Update based upon forum post (#15876)
Based upon [this post](https://community.home-assistant.io/t/device-tracker-ping-on-windows-not-working-solved/61474/3) it looks like we've found why people couldn't get the ping tracker working on Windows.
2018-08-07 18:12:36 +02:00
Fabian Affolter
b6bc0097b8 Upgrade requests_mock to 1.5.2 (#15867) 2018-08-07 16:12:16 +02:00
Fabian Affolter
d556edae31 Upgrade Sphinx to 1.7.6 (#15868) 2018-08-07 16:12:01 +02:00
Fabian Affolter
1fb2ea70c2 Upgrade asynctest to 0.12.2 (#15869) 2018-08-07 16:11:47 +02:00
Ville Skyttä
4cbcb4c3a2 Upgrade pylint to 2.1.1 (#15872) 2018-08-07 16:09:19 +02:00
Paulus Schoutsen
d071df0dec Do not make internet connection during tests (#15858)
* Do not make internet connection

* Small improvement
2018-08-07 09:27:40 +02:00
cdce8p
f09f153014 Fix HomeKit test (#15860)
* Don't raise NotImplementedError during test
2018-08-07 09:26:58 +02:00
Fabian Affolter
1d8678c431 Upgrade pysnmp to 4.4.5 (#15854) 2018-08-07 09:13:01 +02:00
Fabian Affolter
51c30980df Upgrade holidays to 0.9.6 (#15831) 2018-08-07 09:12:09 +02:00
Fabian Affolter
cb20c9b1ea Revert "Upgrade requests_mock to 1.5.2"
This reverts commit a7db2ebbe1.
2018-08-07 09:02:54 +02:00
Fabian Affolter
a7db2ebbe1 Upgrade requests_mock to 1.5.2 2018-08-07 09:01:32 +02:00
Robin
61721478f3 Add facebox auth (#15439)
* Adds auth

* Update facebox.py

* Update test_facebox.py

* Update facebox.py

* Update facebox.py

* Update facebox.py

* Update facebox.py

* Remove TIMEOUT

* Update test_facebox.py

* fix lint

* Update facebox.py

* Update test_facebox.py

* Update facebox.py

* Adds check_box_health

* Adds test auth

* Update test_facebox.py

* Update test_facebox.py

* Update test_facebox.py

* Update test_facebox.py

* Ups coverage

* Update test_facebox.py

* Update facebox.py

* Update test_facebox.py

* Update facebox.py

* Update test_facebox.py

* Update facebox.py

* Update facebox.py

* Update facebox.py
2018-08-07 07:30:36 +02:00
Paulus Schoutsen
47fa928425 Bumped version to 0.76.0.dev0 2018-08-06 13:01:32 +02:00
Paulus Schoutsen
10a7accd00 Merge branch 'master' into dev 2018-08-06 13:01:04 +02:00
Paulus Schoutsen
527585ff9c Merge pull request #15856 from home-assistant/rc
0.75.2
2018-08-06 12:59:54 +02:00
Paulus Schoutsen
2f15a40e97 Bumped version to 0.75.2 2018-08-06 12:38:01 +02:00
Dan Cinnamon
ccef9a3e43 Fix envisalink reconnect (#15832)
* Fix logic for handling connection lost/reconnect

* Fixed line length issue.
2018-08-06 12:37:45 +02:00
Fabian Affolter
479dfd1710 Upgrade voluptuous to 0.11.5 (#15830) 2018-08-06 12:37:45 +02:00
Paulus Schoutsen
34ad4bd32d Fix requirements 2018-08-06 12:37:28 +02:00
psike
6031801206 Fix error when Series missing 'episodeFileCount' or 'episodeCount' (#15824)
* Fix error when Series missing 'episodeFileCount' or 'episodeCount'

* Update sonarr.py

* Update sonarr.py
2018-08-06 12:18:36 +02:00
John Arild Berentsen
9cfe0db3c8 Add different pop 012501 ID (#15838) 2018-08-06 11:10:26 +02:00
Jason Hu
8ef2cfa364 Try to fix coveralls unstable result (#15800)
* Create one tox env for code coverage report

pytest-cov generated report in project root folder, not tox env folder.

* Add cov tox env to travis

* Coveralls seems expecting all build jobs upload

* Only upload coverage after cov env success
2018-08-06 10:51:37 +02:00
Jason Hu
12e69202f8 Change to call_service async_stop non-blocking to allow service call finish (#15803)
* Call later sync_stop to allow service call finish

* Change to use non-blocking service all for restart and stop
2018-08-06 10:25:37 +02:00
ahobsonsayers
e4b2ae29bd Fix bt_home_hub_5 device tracker (#15096)
* Fix bt_home_hub_5 device tracker

Updated BT Home Hub 5 device tracker component to get it working again. The old parsing method of the DNS table has been broken for a while causing the component to fail to get connected devices. A new parsing method has been implemened and fixes all previous issues.

* Moved part of code to a published PyPi library

* Fixed Violations

* Fixed bugs in device tracker

* Moved API Specific Code to PyPi Repository

* Updated to fit requested changes, removed test as it is no longer valid and updated requirement_all.txt

* Update to fit style requirements and remove redundant code

* Removed Unnecessary Comment
2018-08-06 07:38:02 +02:00
Ryan Davies
ac4674fdb0 Add max_gps_accuracy option to Google Maps (#15833)
* Google Maps - Add max_gps_accuracy option

* Remove else statement and add continue
2018-08-06 07:17:21 +02:00
Fabian Affolter
f86702e8ab Upgrade shodan to 1.9.0 (#15839) 2018-08-05 22:48:14 +02:00
mattwing
9a84f8b763 Remove 'volume' from return dict (#15842)
https://github.com/home-assistant/home-assistant/issues/15271

intraday results do not return the volume. See https://www.alphavantage.co/documentation/#intraday
2018-08-05 22:11:51 +02:00
Dan Cinnamon
6a32b9bf87 Fix envisalink reconnect (#15832)
* Fix logic for handling connection lost/reconnect

* Fixed line length issue.
2018-08-05 18:51:23 +02:00
Steven Looman
b152becbe0 Add media_player.dlna_dmr component (#14749)
* Add media_player.dlna_dmr component

* PEP 492

* Move DIDL-template up

* Remove max_volume-override option

* Remove picky_device support

* Use DEFAULT_NAME

* Make supported_features static

* Remove unneeded argument

* Proper module-docstring

* Add http dependency

* Remove additional_configuration options, no longer used

* Change default name to 'DLNA Digital Media Renderer'

* Use python-didl-lite for DIDL-Lite-xml construction/parsing

* Handle NOT_IMPLEMENTED for UPnP state variables RelativeTimePosition and CurrentMediaDuration

* Use UPnP-UDN for unique_id

* Proper handling of upnp events

* Keeping flake8 happy

* Update requirements_all.txt

* Make UDN optional

* Ensure NotifyView is started, before using it

* Only subscribe to services we're interested in

* Don't update state_variables if value has not been changed + minor refactoring

* Improve play_media, follow flow of DLNA more closely

* Hopefully fix ClientOSError problems

* Flake8 fixes

* Keep pylint happy

* Catch errors and report gracefully

* Update async_upnp_client to 0.11.0

* Don't be so noisy

* Define/use constants for HTTP status codes

* Add discovery entry for dlna_dmr

* More robustness with regard to state variable not being set (yet)

* Keep privates hidden

* Handle NOT_IMPLEMENTED for CurrentTrackMetaData state variable

* Fixes in async_upnp_client + renew UPnP subscriptions regularly

* Not too eager

* Refactor duplicate code to _current_transport_actions and improve parsing of actions

* Support RC:1 to RC:3 and AVT:1 to AVT:3

* Moved DLNA-specifics to async_upnp_client.dlna.DmrDevice

* Use our own HTTP server to listen for events.

* More clear and explicit log message for easier troubleshooting

* Follow changes by hass, fixes traceback

* Fix not being able to do next

* Changes after review by @MartinHjelmare

* Linting

* Use homeassistant.util.get_local_ip

* Moved upnp event handling to async_upnp_client

* Keeping pylint happy

* Changes after review by @MartinHjelmare
2018-08-05 14:41:18 +02:00
Fabian Affolter
c41aa12d1d Upgrade youtube_dl to 2018.08.04 (#15837) 2018-08-05 13:29:06 +02:00
Thomas Delaet
8a81ee3b4f Velbus auto-discovery (#13742)
* remove velbus fan and light platforms

these platforms should not be there since they can be created with template components based on switch platform

* use latest version of python-velbus which supports auto-discovery of modules

* fix linting errors

* fix linting errors

* fix linting errors

* address review comments from @MartinHjelmare

* update based on automatic feedback

* fix linting errors

* update dependency

* syntax corrections

* fix lint warning

* split out common functionality in VelbusEntity
use sync methods for loading platforms
support unique_ids so that entities are registred in entity registry

* fix linting errors

* fix linting errors

* fix linting errors

* integrate review comments (common functionality in VelbusEntity class)

* rename DOMAIN import to VELBUS_DOMAIN

* revert change created by requirements script

* regen
2018-08-05 10:47:17 +02:00
fucm
5e1836f3a2 Add support for 2 Tahoma IO awning covers (#15660)
* Add Tahoma io:VerticalExteriorAwningIOComponent and io:HorizontalAwningIOComponent

* Fix position of horizontal awning cover

* Add timestamps for lock time

* Adjust open-close actions for horizontal awning cover

* Fix stop action for io:RollerShutterGenericIOComponent

* Remove redundant information

* Use get for dict lookup
2018-08-05 10:44:57 +02:00
Fabian Affolter
9ea3be4dc1 Upgrade voluptuous to 0.11.5 (#15830) 2018-08-04 17:46:14 -07:00
Martin Hjelmare
bce47eb9a4 Fix frontend requirements after bump (#15829) 2018-08-04 22:35:41 +02:00
Pascal Vizeli
018bd8544c Fix lint with wrong frontend version inside requirements_test_all 2018-08-04 22:26:13 +02:00
Pascal Vizeli
bfb9f2a00b Fix lint with wrong frontend version inside requirements_all 2018-08-04 22:24:17 +02:00
Paulus Schoutsen
3f8c91d77c Merge pull request #15823 from home-assistant/rc
0.75.1
2018-08-04 15:26:51 +02:00
Paulus Schoutsen
ef5095cf53 Bumped version to 0.75.1 2018-08-04 15:24:44 +02:00
Daniel Høyer Iversen
5015071816 Fix rfxtrx device id matching (#15819)
* Issue #15773

Fix PT2262 devices are incorrectly matched in rfxtrx component

* style
2018-08-04 15:24:34 +02:00
superpuffin
b110a80fbd Upgrade Adafruit-DHT to 1.3.3 (#15706)
* Change to newer pip package

The package Adafruit_Python_DHT==1.3.2 was broken and would not install, breaking DHT sensor support in Home assistant. It has since been fixed in Adafruit-DHT==1.3.3.

See: https://github.com/adafruit/Adafruit_Python_DHT/issues/99

* Update requirements_all.txt

New or updated dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.

* Comment out Adafruit-DHT

Adafruit_Python_DHT changed name to Adafruit-DHT, which still need pyx support breaking our CI, need to be comment out.

* Update requirements_all.txt
2018-08-04 15:24:33 +02:00
Daniel Høyer Iversen
c7a8f1143c Fix rfxtrx device id matching (#15819)
* Issue #15773

Fix PT2262 devices are incorrectly matched in rfxtrx component

* style
2018-08-04 15:23:57 +02:00
Ville Skyttä
dbe44c076e Upgrade pytest to 3.7.1 and pytest-timeout to 1.3.1 (#15809) 2018-08-04 15:22:37 +02:00
Ville Skyttä
3246b49a45 Upgrade pylint to 2.1.0 (#15811)
* Upgrade pylint to 2.1.0

* Remove no longer needed pylint disables
2018-08-04 15:22:22 +02:00
Paulus Schoutsen
c482d48fde Bump frontend to 20180804.0 2018-08-04 15:21:32 +02:00
Paulus Schoutsen
0c7d46927e Bump frontend to 20180804.0 2018-08-04 15:21:11 +02:00
Paulus Schoutsen
7d9f8b0d4c Merge pull request #15806 from home-assistant/rc
0.75.0
2018-08-03 16:45:24 +02:00
Paulus Schoutsen
b8981b2675 Merge remote-tracking branch 'origin/master' into rc 2018-08-03 14:25:36 +02:00
Jason Hu
f6935b5d27 Upgrade voluptuous-serialize to 2.0.0 (#15763)
* Upgrade voluptuous-serialize to 2.0.0

* Change to 2.0.0
2018-08-03 05:23:26 -07:00
Paulus Schoutsen
6028db21ab Bumped version to 0.75.0 2018-08-03 14:23:12 +02:00
Paulus Schoutsen
c63fd974fb Return True from Nest setup (#15797) 2018-08-03 14:22:40 +02:00
Robert Svensson
cdb86ed154 Only report color temp when in the correct color mode (#15791) 2018-08-03 14:22:40 +02:00
Bryan York
0f844311c9 Fix Min/Max Kelvin color temp attribute for Google (#15697)
* Fix Min/Max Kelvin color temp attribute for Google

Max Kelvin is actually Min Mireds and vice-versa. K = 1000000 / mireds

* Update test_smart_home.py

* Update test_trait.py
2018-08-03 14:22:39 +02:00
Robert Svensson
91e8680fc5 Only report color temp when in the correct color mode (#15791) 2018-08-03 13:56:54 +02:00
Jason Hu
6f2000f5e2 Make sure use_x_forward_for and trusted_proxies must config together (#15804)
* Make sure use_x_forward_for and trusted_proxies must config together

* Fix unit test
2018-08-03 13:52:34 +02:00
Paulus Schoutsen
8d2359026c Bump frontend to 20180803.0 2018-08-03 13:48:48 +02:00
Paulus Schoutsen
ee180c51cf Bump frontend to 20180803.0 2018-08-03 13:48:32 +02:00
Conrad Juhl Andersen
b63312ff2e Vacuum component: start_pause to individual start and pause commands. (#15751)
* Add start and pause to StateVacuumDevice, move start_pause to VacuumDevice

* Updated demo vacuum and tests

* Add a few more tests
2018-08-02 19:49:38 -07:00
Paulus Schoutsen
59f8a73676 Return True from Nest setup (#15797) 2018-08-02 16:36:37 -06:00
Jesse Rizzo
affd4e7df3 Add Enphase Envoy component (#15081)
* add enphase envoy component

* Add Enphase Envoy component for energy monitoring

* Fix formatting problems

* Fix formatting errors

* Fix formatting errors

* Fix formatting errors

* Change unit of measurement to W or Wh. Return sensor states as integers

* Fix formatting errors

* Fix formatting errors

* Fix formatting errors

* Move import json to update function

* Fix formatting. Add file to .coveragerc

* Add new component to requirements_all.txt

* Move API call to third party library on PyPi

* Refactor

* Run gen_requirements_all.py

* Minor refactor

* Fix indentation

* Fix indentation
2018-08-02 23:14:43 +02:00
Bryan York
38928c4c0e Fix Min/Max Kelvin color temp attribute for Google (#15697)
* Fix Min/Max Kelvin color temp attribute for Google

Max Kelvin is actually Min Mireds and vice-versa. K = 1000000 / mireds

* Update test_smart_home.py

* Update test_trait.py
2018-08-02 22:09:19 +02:00
Diogo Gomes
48af5116b3 Update pymediaroom to 0.6.4 (#15786)
* Dependency version bump

* bump version
2018-08-02 20:13:48 +02:00
Paulus Schoutsen
a5112f317d Update frontend to 20180802.0 2018-08-02 14:23:56 +02:00
Paulus Schoutsen
eb5f6efb43 Update frontend to 20180802.0 2018-08-02 14:23:40 +02:00
Paulus Schoutsen
3ed47b05a5 Update translations 2018-08-02 13:43:36 +02:00
Paulus Schoutsen
7972d6a0c6 Update translations 2018-08-02 13:42:45 +02:00
Paulus Schoutsen
163cd72b7a Bumped version to 0.75.0b1 2018-08-02 12:23:36 +02:00
Aaron Bach
bdea9e1333 Add support for OpenUV binary sensors and sensors (#15769)
* Initial commit

* Adjusted ownership and coverage

* Member-requested changes

* Updated Ozone to a value, not an index

* Verbiage update
2018-08-02 07:42:12 +02:00
Wim Haanstra
2f8d66ef2b RitAssist / FleetGO support (#15780)
* RitAssist / FleetGO support

* Fix lint issue
Add to .coveragerc
2018-08-02 07:01:40 +02:00
Jason Hu
589b23b7e2 Revert "Add support for STATE_AUTO of generic_thermostat (#15678)" (#15783)
This reverts commit 2e5131bb21.
2018-08-01 10:04:41 -07:00
Niklas
2e5131bb21 Add support for STATE_AUTO of generic_thermostat (#15678)
Add support for STATE_AUTO of generic_thermostat
2018-08-01 08:07:27 -07:00
Conrad Juhl Andersen
2ff5b4ce95 Add support for STATES of vacuums (#15573)
* Vacuum: Added support for STATES

* Added debug logging and corrected state order

* typo

* Fix travis error, STATE = STATE for readability

* status -> state

* Changed to Entity instead of ToogleEntity

* Updated some vacuums

* Revert changes

* Revert Changes

* added SUPPORT_STATE

* Woof?

* Implement on/off if STATE not supported

* Moved new state vaccum to Class StateVacuumDevice

* Error: I should go to bed

* Moved around methods for easier reading

* Added StateVacuumDevice demo vacuum

* Added tests for StateVacuumDevice demo vacuum

* Fix styling errors

* Refactored to BaseVaccum

* Vacuum will now go back to dock

* Class BaseVacuum is for internal use only

* return -> await

* return -> await
2018-08-01 05:51:38 -07:00
Robert Svensson
f8a478946e deCONZ - support for power plugs (#15752)
* Initial commit for deCONZ switch support

* Fix hound comment

* Fix martins comment; platforms shouldn't depend on another platform

* Fix existing tests

* New tests

* Clean up unnecessary methods

* Bump requirement to v43

* Added device state attributes to light
2018-08-01 11:03:08 +02:00
Oscar Tin Yiu Lai
623f6c841b Expose internal states and fixed on/off state of Dyson Fans (#15716)
* exposing internal state and fixed onoff state

* fixed styling

* revert file mode changes

* removed self type hints

* better unit test and changed the way to return attributes

* made wolfie happy
2018-07-31 21:38:34 -07:00
Ioan Loosley
0b6f2f5b91 Opensky altitude (#15273)
* Added Altitude to opensky

* decided to take all metadata

* Final Tidy

* More formatting

* moving CONF_ALTITUDE to platform

* Moved CONF_ALTITUDE to platform
2018-07-31 21:45:18 +02:00
Mathieu Velten
3445dc1f00 Update pynetgear to 0.4.1 (bugfixes) (#15768) 2018-07-31 21:40:13 +02:00
Fabian Affolter
a11c2a0bd8 Fix docstrings (#15770) 2018-07-31 21:39:37 +02:00
Diogo Gomes
95da41aa15 This component API has been decomissioned on the 31st of May 2018 by Telstra (#15757)
See #15668
2018-07-31 21:27:43 +02:00
Fabian Affolter
27401f4975 Upgrade Mastodon.py to 1.3.1 (#15766) 2018-07-31 21:17:55 +02:00
Scott Albertson
d902a9f279 Add a "Reviewed by Hound" badge (#15767) 2018-07-31 21:17:33 +02:00
priiduonu
03847e6c41 Round precipitation forecast to 1 decimal place (#15759)
The OWM returns precipitation forecast values as they are submitted to their network. It can lead to values like `0.0025000000000004 mm` which does not make sense and messes up the display card. This PR rounds the value to 1 decimal place.
2018-07-31 19:18:11 +02:00
Fabian Affolter
a4f9602405 Convert wind speed to km/h (fixes #15710) (#15740)
* Convert wind speed to km/h (fixes #15710)

* Round speed
2018-07-31 19:11:29 +02:00
John Arild Berentsen
5f214ffa98 Update pyozw to 0.4.9 (#15758)
* update pyozw to 0.4.8

* add requirements_all.txt

* use 0.4.9
2018-07-31 15:14:14 +01:00
Andrey
8ee3b535ef Add disallow_untyped_calls to mypy check. (#15661)
* Add disallow_untyped_calls to mypy check.

* Fix generator
2018-07-31 15:00:17 +01:00
Andrey Kupreychik
951372491c Fixed NDMS for latest firmware (#15511)
* Fixed NDMS for latest firmware.
Now using telnet instead of Web Interface

* Using external library for NDMS interactions

* updated requirements_all

* renamed `mac` to `device` back

* Using generators for name and attributes fetching
2018-07-31 11:14:49 +02:00
Jason Hu
eeb79476de Decouple login flow view and data entry flow view (#15715) 2018-07-30 21:59:18 -07:00
Aaron Bach
1b2d0e7a6f Better handling of Yi camera being disconnected (#15754)
* Better handling of Yi camera being disconnected

* Handling video processing as well

* Cleanup

* Member-requested changes

* Member-requested changes
2018-07-30 21:56:52 -07:00
Teemu R
3208ad27ac Add kodi unique id based on discovery (#15093)
* kodi add unique id based on discovery

* initialize unique_id to None

* use netdisco-extracted mac_address

* use an uuid instead of mac for real uniqueness

* add missing docstring

* verify that there is no entity already for the given unique id

* whitespace fix
2018-07-30 17:09:38 +02:00
superpuffin
cf87b76b0c Upgrade Adafruit-DHT to 1.3.3 (#15706)
* Change to newer pip package

The package Adafruit_Python_DHT==1.3.2 was broken and would not install, breaking DHT sensor support in Home assistant. It has since been fixed in Adafruit-DHT==1.3.3.

See: https://github.com/adafruit/Adafruit_Python_DHT/issues/99

* Update requirements_all.txt

New or updated dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.

* Comment out Adafruit-DHT

Adafruit_Python_DHT changed name to Adafruit-DHT, which still need pyx support breaking our CI, need to be comment out.

* Update requirements_all.txt
2018-07-30 15:15:13 +01:00
Paulus Schoutsen
5e71f0f0d7 Bumped version to 0.75.0b0 2018-07-30 13:45:43 +02:00
Paulus Schoutsen
be61e2e714 Merge branch 'dev' into rc 2018-07-30 13:45:35 +02:00
Jason Hu
1e5596b594 Remove self type hints (#15732)
* Remove self type hints

* Lint
2018-07-30 12:44:31 +01:00
Dan Faulknor
744c277123 Add other wemo motion sensor identifier (#15627)
* Add other motion sensor identifier

* Fix order
2018-07-30 10:38:49 +02:00
David Straub
460bb69ade Add mvglive option to store multiple departures in attributes (#15454)
* MVG Live sensor: add option to store multiple departures in attributes

* Fix lint error

* mvglive: take into account timeoffset in API call

* Prevent exception if departure list is empty

* Rename state_attributes -> device_state_attributes
2018-07-30 10:32:39 +02:00
Josh Shoemaker
8dbe78a21a Add Genie Aladdin Connect cover component (#15699)
* Add Genie Aladdin Connect cover component

* Fix lines being too long

* Fix issues found in review

* remove Unknown state, use None instead

* Fixed requirements_all
2018-07-30 07:19:34 +02:00
Juha Niemi
3959f82030 Make FutureNow light remember last brightness when turning on (#15733)
* Remember last brightness value and use it on turn_on()

* Pyfnip-0.2 now returns state reliably, no manual changes needed.

* Split too long line of code

* Updated pyfnip library version
2018-07-30 07:09:59 +02:00
starkillerOG
48ba13bc6c Denonavr version push to 0.7.5 (#15743)
* Version push to 0.7.5

Improve logger warning

* Denonavr v.0.7.5
2018-07-29 15:42:23 -07:00
Fabian Affolter
681082a3ad Various updates (#15738) 2018-07-29 23:39:01 +02:00
Fabian Affolter
4013a90f33 Upgrade pyowm to 2.9.0 (#15736) 2018-07-29 23:37:38 +02:00
Fabian Affolter
316ef89541 Upgrade youtube_dl to 2018.07.29 (#15734) 2018-07-29 23:37:10 +02:00
Fabian Affolter
a8dd81e986 Upgrade voluptuous to 0.11.3 (#15735) 2018-07-29 23:36:28 +02:00
Fabian Affolter
4b257c3d01 Upgrade sqlalchemy to 1.2.10 (#15737) 2018-07-29 23:35:47 +02:00
Fabian Affolter
491bc006b2 Upgrade mutagen to 1.41.0 (#15739) 2018-07-29 23:35:27 +02:00
Fabian Affolter
28ad0017e1 Upgrade beautifulsoup4 to 4.6.1 (#15727) 2018-07-29 19:55:49 +02:00
Peter Nijssen
5849381dfb Upgrade spiderpy to 1.2.0 (#15729) 2018-07-29 19:49:36 +02:00
Fabian Affolter
baa974a487 Upgrade numpy to 1.15.0 (#15722) 2018-07-29 08:46:20 +02:00
Fabian Affolter
1a97ba1b46 Upgrade youtube_dl to 2018.07.21 (#15718) 2018-07-29 08:46:06 +02:00
Alexander Hardwicke
1d68f4e279 Command Line Sensor - json_attributes (#15679)
* Add tests to command_line for json_attrs

* Add json_attrs to command_line

* Remove whitespace on blank line

* Stick to <80 row length

* Use collections.Mapping, not dict

* Rename *attrs to *attributes

* Remove extraneous + for string concat

* Test multiple keys

* Add test

Makes sure the sensor's attributes don't contain a value for a missing key,
even if we want that key.

* Test that unwanted keys are skipped

* Remove additional log line

* Update tests for log changes

* Fix ordering
2018-07-29 08:37:34 +02:00
Jonathan Keljo
a2b793c61b Add a component for Sisyphus Kinetic Art Tables (#14472)
* Add a component for Sisyphus Kinetic Art Tables

The [Sisyphus Kinetic Art Table](https://sisyphus-industries.com/) uses a
steel ball to draw intricate patterns in sand, thrown into sharp relief by a
ring of LED lights around the outside.

This component enables basic control of these tables through Home Assistant.

* Fix lints

* Docstrings, other lints

* More lints

* Yet more.

* Feedback

* Lint

* Missed one piece of feedback

* - Use async_added_to_hass in media player
- async_schedule_update_ha_state in listeners
- constants for supported features
- subscripting for required keys
- asyncio.wait
- update to sisyphus-control with passed-in session

* Update requirements

* lint
2018-07-29 07:34:43 +02:00
Jason Hu
93d6fb8c60 Break up components/auth (#15713) 2018-07-28 17:54:26 -07:00
Paulus Schoutsen
c7f4bdafc0 Context (#15674)
* Add context

* Add context to switch/light services

* Test set_state API

* Lint

* Fix tests

* Do not include context yet in comparison

* Do not pass in loop

* Fix Z-Wave tests

* Add websocket test without user
2018-07-28 17:53:37 -07:00
Jens Østergaard Nielsen
867f80715e Remove IHC XML Element from discovery data (#15719) 2018-07-28 19:37:12 +02:00
Alan Fischer
29e668e887 Upgrade pyvera to 0.2.44 (#15708) 2018-07-28 19:17:04 +02:00
JC Connell
944f4f7c05 Add Magicseaweed API support (#15132)
* Added support for magicseaweed surf forecasting

* Added support for magicseaweed surf forecasting

* Added support for magicseaweed surf forecasting

* Incorporate @bachya requested changes.

* Adding support for magicseaweed package.

* Run tests and fix errors.

* Incorporate @balloob requested changes.

* Attempt to fix pylint error e1101.

* Two spaces before inline comments

* Add @MartinHjelmare & @balloob requested changes.

* Remove MagicSeaweedData object inheritance.

* Fix variable logic.
2018-07-27 23:48:56 +02:00
Richard Orr
cd6544d32a Add support for alarm_control_panel to MQTT Discovery. (#15689) 2018-07-27 17:16:49 +02:00
Jason Hu
b2f4bbf93b Only log change to use access token warning once (#15690) 2018-07-27 15:53:46 +02:00
Juha Niemi
a99b4472a8 Add support for P5 FutureNow light platform (#15662)
* Added support for FutureNow light platform and relay/dimmer units

* Pinned specific version for requirement

* Added support for FutureNow light platform and relay/dimmer units

* Added futurenow.py to .coveragerc.

* Minor fixes and enhancements as requested in the code review.

* Minor fixes and enhancements as requested in the code review.

* Use device_config's value directly as it's validated as boolean.

* Simplify state check.

* Fixed brightness update that was broken in previous commit.
2018-07-27 11:11:32 +02:00
Peter Nijssen
33f3e72dda Add spider power plug component (#15682)
* Add spider power plug component

* rounding down the numbers

* ability to throttle the API

* updated to the lastest api

* resolved an issue within the API
2018-07-26 21:43:20 -07:00
Aaron Bach
e30510a688 Fixes a bug with showing a subset of Pollen index conditions (#15694) 2018-07-26 12:31:44 -06:00
Paulus Schoutsen
974fe4d923 Fix frontend tests 2018-07-26 10:25:57 +02:00
Paulus Schoutsen
feb8aff46b Bump frontend to 20180726.0 2018-07-26 09:38:10 +02:00
Ville Skyttä
eee9b50b70 Upgrade pylint to 2.0.1 (#15683)
* Upgrade pylint to 2.0.1

* Pylint 2 bad-whitespace fix

* Pylint 2 possibly-unused-variable fixes

* Pylint 2 try-except-raise fixes

* Disable pylint fixme for todoist for now

https://github.com/PyCQA/pylint/pull/2320

* Disable pylint 2 useless-return for now

https://github.com/PyCQA/pylint/issues/2300

* Disable pylint 2 invalid-name for type variables for now

https://github.com/PyCQA/pylint/issues/1290

* Disable pylint 2 not-an-iterable for now

https://github.com/PyCQA/pylint/issues/2311

* Pylint 2 unsubscriptable-object workarounds

* Disable intentional pylint 2 assignment-from-nones

* Disable pylint 2 unsupported-membership-test apparent false positives

* Disable pylint 2 assignment-from-no-return apparent false positives

* Disable pylint 2 comparison-with-callable false positives

https://github.com/PyCQA/pylint/issues/2306
2018-07-26 08:55:42 +02:00
Jason Hu
9fb8bc8991 Allow Nest Cam turn on/off (#15681)
* Allow Nest Cam turn on/off

* Don't raise Error

* Remove unnecessary state update
2018-07-25 23:17:38 +02:00
Ville Skyttä
1c42caba76 Pylint 2 useless-return fixes (#15677) 2018-07-25 19:35:57 +02:00
Paulus Schoutsen
9d59bfbe00 0.74.2 (#15671)
* Fix CORS duplicate registration (#15670)

* Bumped version to 0.74.2
2018-07-25 13:09:32 +02:00
Eduard van Valkenburg
95dc06cca6 Add Brunt Cover Device (#15653)
* New Brunt Branch

* Some small changes and updates based on review.
2018-07-25 12:17:12 +02:00
Peter Nijssen
9ecbf86fa0 Add spider thermostat (#15499)
* add spider thermostats

* Added load_platform. Added operation dictionary. Minor improvements

* loop over spider components for load_platform

* added empty dict to load_platform. changed add_devices

* moved logic to the API

* fix requirements_all.txt

* minor code improvements
2018-07-25 11:51:48 +02:00
Paulus Schoutsen
588fd1923f Bumped version to 0.74.2 2018-07-25 11:37:17 +02:00
Paulus Schoutsen
2824efd505 Fix CORS duplicate registration (#15670) 2018-07-25 11:37:11 +02:00
Paulus Schoutsen
169c8d793a Fix CORS duplicate registration (#15670) 2018-07-25 11:36:44 +02:00
Ville Skyttä
68f03dcc67 Auth typing improvements (#15640)
* Always return bytes from auth.providers.homeassistant.hash_password

Good for interface cleanliness, typing etc.

* Add some homeassistant auth provider type annotations
2018-07-25 11:36:03 +02:00
Ville Skyttä
397f551e6d Import collections abstract base classes from collections.abc (#15649)
Accessing them directly through collections is deprecated since 3.7, and
will no longer work in 3.8.
2018-07-25 11:35:22 +02:00
Jerad Meisner
cbb5d34167 Added user credentials to current_user ws endpoint. (#15558)
* Added user credentials to current_user ws endpoint.

* Comments. Added another test.

* Return list of credentials.
2018-07-25 10:34:18 +02:00
Daniel Kalmar
0cc9798c8f Allow defining default turn-on values for lights in the profiles file. (#15493)
* Allow defining default turn-on values for lights in the profiles file.

* Mock out file operations in unit test.

* Fix unit test flakiness.

* Avoid unnecessary copy
2018-07-24 20:29:59 +02:00
Jason Hu
45a7ca62ae Add turn_on/off service to camera (#15051)
* Add turn_on/off to camera

* Add turn_on/off supported features to camera.

Add turn_on/off service implementation to camera, add turn_on/off
 supported features and services to Demo camera.

* Add camera supported_features tests

* Resolve code review comment

* Fix unit test

* Use async_add_executor_job

* Address review comment, change DemoCamera to local push

* Rewrite tests/components/camera/test_demo

* raise HTTPError instead return response
2018-07-24 10:13:26 -07:00
Giuseppe
2eb125e90e Downgrade netatmo warning log to info (#15652) 2018-07-24 18:35:57 +02:00
Paulus Schoutsen
264c618b11 Bump frontend to 20180724.0 2018-07-24 14:16:25 +02:00
Paulus Schoutsen
d9cf8fcfe8 Allow changing entity ID (#15637)
* Allow changing entity ID

* Add support to websocket command

* Address comments

* Error handling
2018-07-24 14:12:53 +02:00
Jan Collijs
fbeaa57604 Update smappy library version (#15636)
Adding latest smappy lib version

Updated smappy library version
2018-07-24 10:41:24 +02:00
huangyupeng
c1f5ead61d Add Tuya cover and scene platform (#15587)
* Add Tuya cover platform

* Add Tuya cover and scene

* fix description

* remove scene default method
2018-07-24 10:29:43 +02:00
Jason Hu
d7690c5fda Add ipban for failed login attempt in new login flow (#15551)
* Add ipban for failed login attempt in new login flow

* Address review comment

* Use decorator to clean up code
2018-07-24 10:09:52 +02:00
Cheong Yip
45c35ceb2b Fix typo asayn_init instead of async_init (#15645) 2018-07-23 20:19:01 -06:00
Daniel Shokouhi
bc481fa366 Update Neato library to allow for dynamic endpoints (#15639) 2018-07-24 00:46:12 +02:00
John Arild Berentsen
1b94fe3613 Add ability to set Zwave protection commandclass (#15390)
* Add API for protection commandclass

* Adjusting

* tests

* Spelling

* Missed flake8

* Period

* spelling

* Review changes

* removing additional .keys()

* period

* Move i/o out into executor pool

* Move i/o out into executor pool

* Forgot get method

* Do it right... I feel stupid

* Long lines

* Merging
2018-07-23 15:31:12 +02:00
Paulus Schoutsen
3204501174 Cast/Sonos: create config entry if manually configured (#15630)
* Cast/Sonos: create config entry if manually configured

* Add test for helper
2018-07-23 15:08:03 +02:00
Pascal Vizeli
f3dfc433c2 Fix aiohttp connection reset errors (#15577)
* Fix aiohttp connection reset errors

* Update aiohttp_client.py

* Update aiohttp_client.py

* Update __init__.py

* Update mjpeg.py

* Update mjpeg.py

* Update ffmpeg.py

* Update ffmpeg.py

* Update ffmpeg.py

* Update proxy.py

* Update __init__.py

* Update aiohttp_client.py

* Update aiohttp_client.py

* Update proxy.py

* Update proxy.py

* Fix await inside coroutine

* Fix async syntax

* Lint
2018-07-23 14:36:36 +02:00
Paulus Schoutsen
8213b1476f WIP: Hass.io sent token to supervisor (#15536)
Hass.io sent token to supervisor
2018-07-23 14:14:57 +02:00
Paulus Schoutsen
4e7dbf9ce5 Allow system users to refresh tokens (#15574) 2018-07-23 14:06:09 +02:00
Paulus Schoutsen
ea2ff6aae3 Use async_create_task (#15633)
* Use async_create_task

* Fix test
2018-07-23 14:05:38 +02:00
starkillerOG
50b6c5948d Suppress error between 00:00 and 01:00 (#15555)
* Suppress error between 00:00 and 01:00

Suppress an error that often occers between 00:00 and 01:00 CE(S)T during that time, probably because buienradar.nl is then updating its forcast for the next day. The API does not always work between these times (in the middle of the night).

* white space & import

* unnecessary brackets
2018-07-23 12:37:23 +02:00
Muhammad Sheraz Lodhi
3acbd5a769 The tense is wrong (#15614)
Instead of spent, we should be using spend :)
2018-07-23 12:31:54 +02:00
Anders Melchiorsen
fddfb9e412 Refresh Sonos source list on changes (#15605) 2018-07-23 12:31:03 +02:00
Anders Melchiorsen
1325682d82 Use case insensitive comparison for Sonos model check (#15604) 2018-07-23 12:29:37 +02:00
Andrey
140a874917 Add typing to homeassistant/*.py and homeassistant/util/ (#15569)
* Add typing to homeassistant/*.py and homeassistant/util/

* Fix wrong merge

* Restore iterable in OrderedSet

* Fix tests
2018-07-23 10:24:39 +02:00
Ville Skyttä
b7c336a687 Pylint cleanups (#15626)
* Pylint 2 no-else-return fixes

* Remove unneeded abstract-class-not-used pylint disable
2018-07-23 10:16:05 +02:00
Ville Skyttä
a38c0d6d15 Upgrade mypy to 0.620 (#15612) 2018-07-22 13:37:26 +02:00
Paulus Schoutsen
75f40ccb06 Remove entity picture of Tuya entity (#15611) 2018-07-22 12:10:32 +02:00
cdce8p
4de847f84e Bugfix HomeKit name and serial_number (#15600)
* Bugfix HomeKit name and serial_number

* Revert serial_number changes
2018-07-22 09:51:42 +02:00
Jason Hu
33f1577dac Frontend component should auto load auth coomponent (#15606) 2018-07-22 09:49:58 +02:00
Anders Melchiorsen
ef3a83048c Throttle unavailability warnings for tplink light/switch (#15591) 2018-07-22 00:51:45 +02:00
Daniel Perna
ae2ee8f006 Update pyhomematic, fixes #15054, #15190 (#15603) 2018-07-22 00:18:50 +02:00
digiblur
6f6d86c700 Add relay addr & chan config to alarmdecoder zones (#15242)
Add relay addr & chan config to alarmdecoder zones
2018-07-21 17:31:07 +02:00
Anders Melchiorsen
d1b16e287c Add unique_id to netgear_lte sensors (#15584) 2018-07-21 10:14:56 +02:00
Ryan Davies
ee8a815e6b Allow MQTT Switch to have an optional state configuration (#15430)
Switches by default use the payload_on and payload_off configuration parameters to specify both the payload the switch should send for a state but also what will be returned for the current state - which isnt always the same
As a toggle switch might always send an ON or TOGGLE to toggle the switch, but still receive an ON or an OFF for the state topic - This change allows for splitting them apart
2018-07-20 23:04:06 +02:00
Paulus Schoutsen
7bc2362e33 Merge branch 'master' into dev 2018-07-20 15:19:06 +02:00
Eugenio Panadero
9a8389060c fix aiohttp InvalidURL exception when fetching media player image (#15572)
* fix aiohttp InvalidURL exception when fetching media player image

The first call for the HA proxy (`/api/media_player_proxy/media_player.kodi?token=...&cache=...`)
is receiving relative urls that are failing, this is a simple fix to precede the base_url when hostname is None.

* fix import location and sort stdlib imports
2018-07-20 15:18:02 +02:00
Teemu R
5cf9cd686c light.tplink: initialize min & max mireds only once, avoid i/o outside update (#15571)
* light.tplink: initialize min & max mireds only once, avoid i/o outside update

* revert the index change

* fix indent, sorry for overwriting your fix, balloob
2018-07-20 14:40:10 +02:00
Paulus Schoutsen
3341c5cf21 Update the frontend to 20180720.0 2018-07-20 12:30:10 +02:00
Jason Hu
f1286f8e6b Reset failed login attempts counter when login success (#15564) 2018-07-20 12:09:48 +02:00
huangyupeng
f2a99e83cd Add Tuya fan support (#15525)
* Add Tuya fan platform

* Add Tuya fan platform

* fix as review required
2018-07-20 11:23:09 +02:00
Ville Skyttä
2f7b79764a More pylint 2 fixes (#15565)
## Description:

More fixes flagged by pylint 2 that don't hurt to have before the actual pylint 2 upgrade (which I'll submit soon).

## Checklist:
  - [ ] The code change is tested and works locally.
  - [x] Local tests pass with `tox`. **Your PR cannot be merged unless tests pass**
2018-07-20 11:45:20 +03:00
Paulus Schoutsen
ea18e06b08 Remove relative time from state machine (#15560) 2018-07-19 23:12:17 +02:00
Martin Hjelmare
a0193e8e42 Upgrade pymysensors to 0.16.0 (#15554) 2018-07-19 22:52:03 +02:00
Paulus Schoutsen
2fcacbff23 Allow auth providers to influence is_active (#15557)
* Allow auth providers to influence is_active

* Fix auth script test
2018-07-19 22:10:36 +02:00
William Scanlon
a42288d056 Upgrade to simplisafe-python v2 to use new SimpliSafe API (#15542)
* Upgrade to simplisafe-python v2 to use new SimpliSafe API
2018-07-19 13:13:46 -04:00
Paulus Schoutsen
33ee91a748 Bump frontend to 20180719.0 2018-07-19 10:52:28 +02:00
Jerad Meisner
396895d077 Added WS endpoint for changing homeassistant password. (#15527)
* Added WS endpoint for changing homeassistant password.

* Remove change password helper. Don't require current password.

* Restore current password verification.

* Added tests.

* Use correct send method
2018-07-19 09:39:51 +02:00
Paulus Schoutsen
8b04d48ffd Update config entry id in entity registry (#15531) 2018-07-19 08:37:13 +02:00
Paulus Schoutsen
2a76a0852f Allow CORS requests to token endpoint (#15519)
* Allow CORS requests to token endpoint

* Tests

* Fuck emulated hue

* Clean up

* Only cors existing methods
2018-07-19 08:37:00 +02:00
quthla
22d961de70 Update reading when device is added (#15548) 2018-07-18 23:39:37 +02:00
Paulus Schoutsen
4650366f07 Don't be so strict client-side (#15546) 2018-07-18 23:00:26 +02:00
Paulus Schoutsen
dfe17491f8 Bump frontend to 20180718.0 2018-07-18 17:34:16 +02:00
Giel Janssens
a8c7425e17 Update pyatmo (#15540) 2018-07-18 16:58:45 +02:00
Tom Harris
e5f0da75e2 Mini-Remote events (#15523)
* Add event handler to capture binary sensor on messages

* Log event trigger

* Log event firing

* Capture platform correctly

* Fix test for platform eq binary_sensor

* Create sensor events

* Add light and battery sensors

* Bump insteonplm version to 0.11.6

* Fix naming of BUTTON_PRESSED_STATE_NAME

* Fix naming of fire event methods

* Add logging

* Add DOMAIN definition

* Get state name from plm.devices

* Remove stale reference to button ID

* Fix reference to state name

* Remove incorrect ref to self

* Log remote button pressed event

* Change mode to button_mode and fix values to array

* Rename CONF_MODE to CONF_BUTTON_MODE

* Log platform create with mode

* Properly assign button_mode to track mode

* Implement is_on

* Change mini-remotes to events only

* Remove button_mode config option

* Fix reference to _fire_button_on_off_event

* Bump insteon version to 0.11.7

* Flake8 clean up

* Flake8 cleanup

* Use % format in logging per pylint

* Code review updates

* Resolve conflict

* Lint
2018-07-18 16:11:54 +02:00
fucm
6834e00be6 Add support for Tahoma Soke Sensor (#15441) 2018-07-18 12:38:34 +02:00
John Arild Berentsen
26375a3014 Make RS room thermostat discoverable (#15451)
* Make RS room thermostat discoverable

* Reversed generic type name
2018-07-18 12:20:02 +02:00
Daniel Shokouhi
06c3f756b1 Implement locate service for neato (#15467)
* Implement locate service for neato

* Hound
2018-07-18 12:19:38 +02:00
Mattias Welponer
9c5bbfe96d Cleanup of HomematicIP Cloud code (#15475)
* Check if device supports lowBat and shows it only if battery is low

* Show empty battery icon if lowBat is true

* Default return None

* Sabotage attribute and icon if device has this feature

* Bug fix and cleanup

* Use dedicated function for security state

* Cleanup of sensor attributes and icons

* Empty
2018-07-18 12:19:08 +02:00
Anders Melchiorsen
e427f9ee38 RFC: Only use supported light properties (#15484)
* Only use supported light properties

* Fix tests
2018-07-18 12:18:22 +02:00
Andrey
e62e2bb131 Make sure that only pypi dependencies are used (#15490) 2018-07-18 12:16:27 +02:00
Ville Skyttä
bf17ed0917 More pylint 2 fixes (#15516)
* Pylint 2 useless-import-alias fixes

* Pylint 2 chained-comparison fixes

* Pylint 2 consider-using-get fixes

* Pylint 2 len-as-condition fixes
2018-07-18 11:54:27 +02:00
Pascal Vizeli
058081b1f5 Moon translate (#15498)
* Translate moon

* Create strings.moon.json

* Update moon.py

* Update strings.moon.json

* Update test_moon.py
2018-07-18 10:54:54 +02:00
Paulus Schoutsen
98722e10fc Decouple emulated hue from http server (#15530) 2018-07-18 10:47:06 +02:00
Ville Skyttä
2781796d9c Remove some unused imports (#15529) 2018-07-18 10:46:14 +02:00
Andrey
24d2261060 Add check_untyped_defs (#15510)
* Add check_untyped_defs

* Change to regular if-else
2018-07-18 00:28:44 +02:00
lich
7d7c2104ea Customizable command timeout (#15442)
* Customizable command timeout

* Change string to int

* update the tests. Do the same thing on the binary_sensor.command_line.
2018-07-17 22:58:30 +02:00
Dario Iacampo
4ab502a691 Support latest tplink Archer D9 Firmware version / Device Scanner (#15356)
* Support latest tplink Archer D9 Firmware version / Device Scanner

* tplink integration on pypi package

* initialize the client only once

* remove unnecessary instance attributes
2018-07-17 22:47:32 +02:00
huangyupeng
9292d9255c Add Tuya climate platform (#15500)
* Add Tuya climate platform

* fix as review required

* fix as review required
2018-07-17 20:33:54 +02:00
Jason Hu
2022d39339 Disallow use insecure_example auth provider in configuration.yml (#15504)
* Disallow use insecure_example auth provider in configuration.yml

* Add unit test for auth provider config validate
2018-07-17 19:36:33 +02:00
Ville Skyttä
e31dd4404e Pylint 2 fixes (#15487)
* pylint 2 inline disable syntax fixes

* pylint 2 logging-not-lazy fixes

* pylint 2 consider-using-in fixes

* Revert pylint 2 inline disable syntax fixes addressing unused-imports

Will have a go at removing more unused imports altogether first.
2018-07-17 19:34:29 +02:00
Paulus Schoutsen
d2f4bce6c0 Bump frontend to 20180717.0 2018-07-17 10:57:05 +02:00
Paulus Schoutsen
b0a3207454 Add onboarding support (#15492)
* Add onboarding support

* Lint

* Address comments

* Mark user step as done if owner user already created
2018-07-17 10:49:15 +02:00
Matthew Garrett
db3cdb288e Update HomeKit module code (#15502)
This fixes a bunch of bugs, including issues with concurrency in devices
that present multiple accessories, devices that insist on the TLV entries
being in the order that Apple use, and handling devices that send headers
and data in separate chunks. This should improve compatibility with
a whole bunch of HomeKit devices.
2018-07-17 10:06:06 +02:00
Paulus Schoutsen
8797cb78a9 Add current user WS command (#15485) 2018-07-17 09:24:51 +02:00
Luke Fritz
7eb5cd1267 Bump pyarlo==0.2.0, fixes #15486 (#15503) 2018-07-17 07:56:50 +02:00
squirtbrnr
0b2aff61bb Delay setup of waze travel time component (#15455)
* delay setup of component

Copied the necessary lines of code from the google travel time component to fix the setup delay in waze travel time component.  Previously it was only watching the homeassistant start event on the bus, but doing nothing with it.

* Update waze_travel_time.py

* Update waze_travel_time.py
2018-07-16 22:50:56 -06:00
Paulus Schoutsen
ad4cba70a0 Extract SSL context creation to helper (#15483)
* Extract SSL context creation to helper

* Lint
2018-07-16 10:32:07 +02:00
Paulus Schoutsen
dd7890c848 Version bump to 0.75.0.dev0 2018-07-16 08:52:37 +02:00
772 changed files with 10277 additions and 3651 deletions

View File

@@ -215,6 +215,9 @@ omit =
homeassistant/components/opencv.py
homeassistant/components/*/opencv.py
homeassistant/components/openuv.py
homeassistant/components/*/openuv.py
homeassistant/components/pilight.py
homeassistant/components/*/pilight.py
@@ -251,6 +254,9 @@ omit =
homeassistant/components/scsgate.py
homeassistant/components/*/scsgate.py
homeassistant/components/sisyphus.py
homeassistant/components/*/sisyphus.py
homeassistant/components/skybell.py
homeassistant/components/*/skybell.py
@@ -346,6 +352,9 @@ omit =
homeassistant/components/tuya.py
homeassistant/components/*/tuya.py
homeassistant/components/spider.py
homeassistant/components/*/spider.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/canary.py
homeassistant/components/alarm_control_panel/concord232.py
@@ -398,6 +407,8 @@ omit =
homeassistant/components/climate/touchline.py
homeassistant/components/climate/venstar.py
homeassistant/components/climate/zhong_hong.py
homeassistant/components/cover/aladdin_connect.py
homeassistant/components/cover/brunt.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/gogogate2.py
homeassistant/components/cover/homematic.py
@@ -432,6 +443,7 @@ omit =
homeassistant/components/device_tracker/netgear.py
homeassistant/components/device_tracker/nmap_tracker.py
homeassistant/components/device_tracker/ping.py
homeassistant/components/device_tracker/ritassist.py
homeassistant/components/device_tracker/sky_hub.py
homeassistant/components/device_tracker/snmp.py
homeassistant/components/device_tracker/swisscom.py
@@ -461,6 +473,7 @@ omit =
homeassistant/components/light/decora_wifi.py
homeassistant/components/light/decora.py
homeassistant/components/light/flux_led.py
homeassistant/components/light/futurenow.py
homeassistant/components/light/greenwave.py
homeassistant/components/light/hue.py
homeassistant/components/light/hyperion.py
@@ -500,6 +513,7 @@ omit =
homeassistant/components/media_player/denon.py
homeassistant/components/media_player/denonavr.py
homeassistant/components/media_player/directv.py
homeassistant/components/media_player/dlna_dmr.py
homeassistant/components/media_player/dunehd.py
homeassistant/components/media_player/emby.py
homeassistant/components/media_player/epson.py
@@ -523,6 +537,7 @@ omit =
homeassistant/components/media_player/pandora.py
homeassistant/components/media_player/philips_js.py
homeassistant/components/media_player/pioneer.py
homeassistant/components/media_player/pjlink.py
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/russound_rio.py
@@ -623,6 +638,7 @@ omit =
homeassistant/components/sensor/eddystone_temperature.py
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/emoncms.py
homeassistant/components/sensor/enphase_envoy.py
homeassistant/components/sensor/envirophat.py
homeassistant/components/sensor/etherscan.py
homeassistant/components/sensor/fastdotcom.py
@@ -657,6 +673,7 @@ omit =
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/luftdaten.py
homeassistant/components/sensor/lyft.py
homeassistant/components/sensor/magicseaweed.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mitemp_bt.py

View File

@@ -13,7 +13,8 @@ matrix:
- python: "3.5.3"
env: TOXENV=typing
- python: "3.5.3"
env: TOXENV=py35
env: TOXENV=cov
after_success: coveralls
- python: "3.6"
env: TOXENV=py36
- python: "3.7"
@@ -45,4 +46,3 @@ deploy:
on:
branch: dev
condition: $TOXENV = lint
after_success: coveralls

View File

@@ -98,6 +98,8 @@ homeassistant/components/konnected.py @heythisisnate
homeassistant/components/*/konnected.py @heythisisnate
homeassistant/components/matrix.py @tinloaf
homeassistant/components/*/matrix.py @tinloaf
homeassistant/components/openuv.py @bachya
homeassistant/components/*/openuv.py @bachya
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
homeassistant/components/rainmachine/* @bachya

View File

@@ -1,6 +1,6 @@
# Contributing to Home Assistant
Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spent a couple of hours and help to integrate them?
Everybody is invited and welcome to contribute to Home Assistant. There is a lot to do...if you are not a developer perhaps you would like to help with the documentation on [home-assistant.io](https://home-assistant.io/)? If you are a developer and have devices in your home which aren't working with Home Assistant yet, why not spend a couple of hours and help to integrate them?
The process is straight-forward.

View File

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

View File

@@ -8,7 +8,7 @@ import subprocess
import sys
import threading
from typing import Optional, List, Dict, Any # noqa #pylint: disable=unused-import
from typing import List, Dict, Any # noqa pylint: disable=unused-import
from homeassistant import monkey_patch
@@ -20,7 +20,7 @@ from homeassistant.const import (
)
def attempt_use_uvloop():
def attempt_use_uvloop() -> None:
"""Attempt to use uvloop."""
import asyncio
@@ -280,11 +280,11 @@ def setup_and_run_hass(config_dir: str,
# Imported here to avoid importing asyncio before monkey patch
from homeassistant.util.async_ import run_callback_threadsafe
def open_browser(event):
"""Open the webinterface in a browser."""
if hass.config.api is not None:
def open_browser(_: Any) -> None:
"""Open the web interface in a browser."""
if hass.config.api is not None: # type: ignore
import webbrowser
webbrowser.open(hass.config.api.base_url)
webbrowser.open(hass.config.api.base_url) # type: ignore
run_callback_threadsafe(
hass.loop,

View File

@@ -2,9 +2,10 @@
import asyncio
import logging
from collections import OrderedDict
from typing import List, Awaitable
from homeassistant import data_entry_flow
from homeassistant.core import callback
from homeassistant.core import callback, HomeAssistant
from . import models
from . import auth_store
@@ -13,7 +14,9 @@ from .providers import auth_provider_from_config
_LOGGER = logging.getLogger(__name__)
async def auth_manager_from_config(hass, provider_configs):
async def auth_manager_from_config(
hass: HomeAssistant,
provider_configs: List[dict]) -> Awaitable['AuthManager']:
"""Initialize an auth manager from config."""
store = auth_store.AuthStore(hass)
if provider_configs:
@@ -208,7 +211,7 @@ class AuthManager:
return tkn
async def _async_create_login_flow(self, handler, *, source, data):
async def _async_create_login_flow(self, handler, *, context, data):
"""Create a login flow."""
auth_provider = self._providers[handler]

View File

@@ -3,6 +3,7 @@ import base64
from collections import OrderedDict
import hashlib
import hmac
from typing import Dict # noqa: F401 pylint: disable=unused-import
import voluptuous as vol
@@ -68,12 +69,12 @@ class Data:
"""Return users."""
return self._data['users']
def validate_login(self, username, password):
def validate_login(self, username: str, password: str) -> None:
"""Validate a username and password.
Raises InvalidAuth if auth invalid.
"""
password = self.hash_password(password)
hashed = self.hash_password(password)
found = None
@@ -84,33 +85,33 @@ class Data:
if found is None:
# Do one more compare to make timing the same as if user was found.
hmac.compare_digest(password, password)
hmac.compare_digest(hashed, hashed)
raise InvalidAuth
if not hmac.compare_digest(password,
if not hmac.compare_digest(hashed,
base64.b64decode(found['password'])):
raise InvalidAuth
def hash_password(self, password, for_storage=False):
def hash_password(self, password: str, for_storage: bool = False) -> bytes:
"""Encode a password."""
hashed = hashlib.pbkdf2_hmac(
'sha512', password.encode(), self._data['salt'].encode(), 100000)
if for_storage:
hashed = base64.b64encode(hashed).decode()
hashed = base64.b64encode(hashed)
return hashed
def add_auth(self, username, password):
def add_auth(self, username: str, password: str) -> None:
"""Add a new authenticated user/pass."""
if any(user['username'] == username for user in self.users):
raise InvalidUser
self.users.append({
'username': username,
'password': self.hash_password(password, True),
'password': self.hash_password(password, True).decode(),
})
@callback
def async_remove_auth(self, username):
def async_remove_auth(self, username: str) -> None:
"""Remove authentication."""
index = None
for i, user in enumerate(self.users):
@@ -123,14 +124,15 @@ class Data:
self.users.pop(index)
def change_password(self, username, new_password):
def change_password(self, username: str, new_password: str) -> None:
"""Update the password.
Raises InvalidUser if user cannot be found.
"""
for user in self.users:
if user['username'] == username:
user['password'] = self.hash_password(new_password, True)
user['password'] = self.hash_password(
new_password, True).decode()
break
else:
raise InvalidUser
@@ -160,7 +162,7 @@ class HassAuthProvider(AuthProvider):
"""Return a flow to login."""
return LoginFlow(self)
async def async_validate_login(self, username, password):
async def async_validate_login(self, username: str, password: str):
"""Helper to validate a username and password."""
if self.data is None:
await self.async_initialize()
@@ -225,7 +227,7 @@ class LoginFlow(data_entry_flow.FlowHandler):
data=user_input
)
schema = OrderedDict()
schema = OrderedDict() # type: Dict[str, type]
schema['username'] = str
schema['password'] = str

View File

@@ -221,8 +221,8 @@ async def async_from_config_file(config_path: str,
@core.callback
def async_enable_logging(hass: core.HomeAssistant,
verbose: bool = False,
log_rotate_days=None,
log_file=None,
log_rotate_days: Optional[int] = None,
log_file: Optional[str] = None,
log_no_color: bool = False) -> None:
"""Set up the logging.
@@ -291,9 +291,9 @@ def async_enable_logging(hass: core.HomeAssistant,
async_handler = AsyncHandler(hass.loop, err_handler)
async def async_stop_async_handler(event):
async def async_stop_async_handler(_: Any) -> None:
"""Cleanup async handler."""
logging.getLogger('').removeHandler(async_handler)
logging.getLogger('').removeHandler(async_handler) # type: ignore
await async_handler.async_close(blocking=True)
hass.bus.async_listen_once(

View File

@@ -10,6 +10,7 @@ Component design guidelines:
import asyncio
import itertools as it
import logging
from typing import Awaitable
import homeassistant.core as ha
import homeassistant.config as conf_util
@@ -109,7 +110,7 @@ def async_reload_core_config(hass):
@asyncio.coroutine
def async_setup(hass, config):
def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
"""Set up general services related to Home Assistant."""
@asyncio.coroutine
def async_handle_turn_service(service):
@@ -167,7 +168,7 @@ def async_setup(hass, config):
def async_handle_core_service(call):
"""Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP:
hass.async_add_job(hass.async_stop())
hass.async_create_task(hass.async_stop())
return
try:
@@ -183,7 +184,7 @@ def async_setup(hass, config):
return
if call.service == SERVICE_HOMEASSISTANT_RESTART:
hass.async_add_job(hass.async_stop(RESTART_EXIT_CODE))
hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE))
hass.services.async_register(
ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service)

View File

@@ -85,7 +85,7 @@ ABODE_PLATFORMS = [
]
class AbodeSystem(object):
class AbodeSystem:
"""Abode System class."""
def __init__(self, username, password, cache,

View File

@@ -110,7 +110,7 @@ NotificationItem = namedtuple(
)
class AdsHub(object):
class AdsHub:
"""Representation of an ADS connection."""
def __init__(self, ads_client):

View File

@@ -187,7 +187,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_disarm, code)
return self.hass.async_add_executor_job(self.alarm_disarm, code)
def alarm_arm_home(self, code=None):
"""Send arm home command."""
@@ -198,7 +198,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_home, code)
return self.hass.async_add_executor_job(self.alarm_arm_home, code)
def alarm_arm_away(self, code=None):
"""Send arm away command."""
@@ -209,7 +209,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_away, code)
return self.hass.async_add_executor_job(self.alarm_arm_away, code)
def alarm_arm_night(self, code=None):
"""Send arm night command."""
@@ -220,7 +220,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_night, code)
return self.hass.async_add_executor_job(self.alarm_arm_night, code)
def alarm_trigger(self, code=None):
"""Send alarm trigger command."""
@@ -231,7 +231,7 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_trigger, code)
return self.hass.async_add_executor_job(self.alarm_trigger, code)
def alarm_arm_custom_bypass(self, code=None):
"""Send arm custom bypass command."""
@@ -242,7 +242,8 @@ class AlarmControlPanel(Entity):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.async_add_job(self.alarm_arm_custom_bypass, code)
return self.hass.async_add_executor_job(
self.alarm_arm_custom_bypass, code)
@property
def state_attributes(self):

View File

@@ -83,7 +83,7 @@ class AlarmDotCom(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@@ -92,9 +92,9 @@ class AlarmDotCom(alarm.AlarmControlPanel):
"""Return the state of the device."""
if self._alarm.state.lower() == 'disarmed':
return STATE_ALARM_DISARMED
elif self._alarm.state.lower() == 'armed stay':
if self._alarm.state.lower() == 'armed stay':
return STATE_ALARM_ARMED_HOME
elif self._alarm.state.lower() == 'armed away':
if self._alarm.state.lower() == 'armed away':
return STATE_ALARM_ARMED_AWAY
return STATE_UNKNOWN

View File

@@ -122,10 +122,10 @@ class ArloBaseStation(AlarmControlPanel):
"""Convert Arlo mode to Home Assistant state."""
if mode == ARMED:
return STATE_ALARM_ARMED_AWAY
elif mode == DISARMED:
if mode == DISARMED:
return STATE_ALARM_DISARMED
elif mode == self._home_mode_name:
if mode == self._home_mode_name:
return STATE_ALARM_ARMED_HOME
elif mode == self._away_mode_name:
if mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY
return mode

View File

@@ -55,9 +55,9 @@ class CanaryAlarm(AlarmControlPanel):
mode = location.mode
if mode.name == LOCATION_MODE_AWAY:
return STATE_ALARM_ARMED_AWAY
elif mode.name == LOCATION_MODE_HOME:
if mode.name == LOCATION_MODE_HOME:
return STATE_ALARM_ARMED_HOME
elif mode.name == LOCATION_MODE_NIGHT:
if mode.name == LOCATION_MODE_NIGHT:
return STATE_ALARM_ARMED_NIGHT
return None

View File

@@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import datetime
import homeassistant.components.alarm_control_panel.manual as manual
from homeassistant.components.alarm_control_panel import manual
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,

View File

@@ -20,7 +20,6 @@ DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
HMIP_OPEN = 'OPEN'
HMIP_ZONE_AWAY = 'EXTERNAL'
HMIP_ZONE_HOME = 'INTERNAL'
@@ -57,14 +56,18 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
from homematicip.base.enums import WindowState
if self._device.active:
if (self._device.sabotage or self._device.motionDetected or
self._device.windowState == HMIP_OPEN):
self._device.windowState == WindowState.OPEN):
return STATE_ALARM_TRIGGERED
if self._device.label == HMIP_ZONE_HOME:
active = self._home.get_security_zones_activation()
if active == (True, True):
return STATE_ALARM_ARMED_AWAY
if active == (False, True):
return STATE_ALARM_ARMED_HOME
return STATE_ALARM_ARMED_AWAY
return STATE_ALARM_DISARMED
@@ -79,10 +82,3 @@ class HomematicipSecurityZone(HomematicipGenericDevice, AlarmControlPanel):
async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
await self._home.set_security_zones_activation(True, True)
@property
def device_state_attributes(self):
"""Return the state attributes of the alarm control device."""
# The base class is loading the battery property, but device doesn't
# have this property - base class needs clean-up.
return None

View File

@@ -128,7 +128,7 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'

View File

@@ -205,7 +205,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'

View File

@@ -19,7 +19,7 @@ from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED,
CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_DELAY_TIME, CONF_PENDING_TIME,
CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER)
import homeassistant.components.mqtt as mqtt
from homeassistant.components import mqtt
from homeassistant.helpers.event import async_track_state_change
from homeassistant.core import callback
@@ -241,7 +241,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'

View File

@@ -12,7 +12,7 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.alarm_control_panel as alarm
import homeassistant.components.mqtt as mqtt
from homeassistant.components import mqtt
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
@@ -49,6 +49,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MQTT Alarm Control Panel platform."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
async_add_devices([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
@@ -123,7 +126,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'

View File

@@ -9,23 +9,22 @@ import re
import voluptuous as vol
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA
from homeassistant.components.alarm_control_panel import (
PLATFORM_SCHEMA, AlarmControlPanel)
from homeassistant.const import (
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED, STATE_UNKNOWN)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['simplisafe-python==1.0.5']
REQUIREMENTS = ['simplisafe-python==2.0.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe'
DOMAIN = 'simplisafe'
NOTIFICATION_ID = 'simplisafe_notification'
NOTIFICATION_TITLE = 'SimpliSafe Setup'
ATTR_ALARM_ACTIVE = "alarm_active"
ATTR_TEMPERATURE = "temperature"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
@@ -37,36 +36,27 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the SimpliSafe platform."""
from simplipy.api import SimpliSafeApiInterface, get_systems
from simplipy.api import SimpliSafeApiInterface, SimpliSafeAPIException
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
simplisafe = SimpliSafeApiInterface()
status = simplisafe.set_credentials(username, password)
if status:
hass.data[DOMAIN] = simplisafe
locations = get_systems(simplisafe)
for location in locations:
add_devices([SimpliSafeAlarm(location, name, code)])
else:
message = 'Failed to log into SimpliSafe. Check credentials.'
_LOGGER.error(message)
hass.components.persistent_notification.create(
message,
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
try:
simplisafe = SimpliSafeApiInterface(username, password)
except SimpliSafeAPIException:
_LOGGER.error("Failed to setup SimpliSafe")
return
def logout(event):
"""Logout of the SimpliSafe API."""
hass.data[DOMAIN].logout()
systems = []
hass.bus.listen(EVENT_HOMEASSISTANT_STOP, logout)
for system in simplisafe.get_systems():
systems.append(SimpliSafeAlarm(system, name, code))
add_devices(systems)
class SimpliSafeAlarm(alarm.AlarmControlPanel):
class SimpliSafeAlarm(AlarmControlPanel):
"""Representation of a SimpliSafe alarm."""
def __init__(self, simplisafe, name, code):
@@ -75,31 +65,37 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
self._name = name
self._code = str(code) if code else None
@property
def unique_id(self):
"""Return the unique ID."""
return self.simplisafe.location_id
@property
def name(self):
"""Return the name of the device."""
if self._name is not None:
return self._name
return 'Alarm {}'.format(self.simplisafe.location_id())
return 'Alarm {}'.format(self.simplisafe.location_id)
@property
def code_format(self):
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):
"""Return the state of the device."""
status = self.simplisafe.state()
if status == 'off':
status = self.simplisafe.state
if status.lower() == 'off':
state = STATE_ALARM_DISARMED
elif status == 'home':
elif status.lower() == 'home' or status.lower() == 'home_count':
state = STATE_ALARM_ARMED_HOME
elif status == 'away':
elif (status.lower() == 'away' or status.lower() == 'exitDelay' or
status.lower() == 'away_count'):
state = STATE_ALARM_ARMED_AWAY
else:
state = STATE_UNKNOWN
@@ -108,14 +104,13 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'alarm': self.simplisafe.alarm(),
'co': self.simplisafe.carbon_monoxide(),
'fire': self.simplisafe.fire(),
'flood': self.simplisafe.flood(),
'last_event': self.simplisafe.last_event(),
'temperature': self.simplisafe.temperature(),
}
attributes = {}
attributes[ATTR_ALARM_ACTIVE] = self.simplisafe.alarm_active
if self.simplisafe.temperature is not None:
attributes[ATTR_TEMPERATURE] = self.simplisafe.temperature
return attributes
def update(self):
"""Update alarm status."""

View File

@@ -34,6 +34,8 @@ CONF_ZONE_NAME = 'name'
CONF_ZONE_TYPE = 'type'
CONF_ZONE_RFID = 'rfid'
CONF_ZONES = 'zones'
CONF_RELAY_ADDR = 'relayaddr'
CONF_RELAY_CHAN = 'relaychan'
DEFAULT_DEVICE_TYPE = 'socket'
DEFAULT_DEVICE_HOST = 'localhost'
@@ -53,6 +55,7 @@ SIGNAL_PANEL_DISARM = 'alarmdecoder.panel_disarm'
SIGNAL_ZONE_FAULT = 'alarmdecoder.zone_fault'
SIGNAL_ZONE_RESTORE = 'alarmdecoder.zone_restore'
SIGNAL_RFX_MESSAGE = 'alarmdecoder.rfx_message'
SIGNAL_REL_MESSAGE = 'alarmdecoder.rel_message'
DEVICE_SOCKET_SCHEMA = vol.Schema({
vol.Required(CONF_DEVICE_TYPE): 'socket',
@@ -71,7 +74,11 @@ ZONE_SCHEMA = vol.Schema({
vol.Required(CONF_ZONE_NAME): cv.string,
vol.Optional(CONF_ZONE_TYPE,
default=DEFAULT_ZONE_TYPE): vol.Any(DEVICE_CLASSES_SCHEMA),
vol.Optional(CONF_ZONE_RFID): cv.string})
vol.Optional(CONF_ZONE_RFID): cv.string,
vol.Inclusive(CONF_RELAY_ADDR, 'relaylocation',
'Relay address and channel must exist together'): cv.byte,
vol.Inclusive(CONF_RELAY_CHAN, 'relaylocation',
'Relay address and channel must exist together'): cv.byte})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
@@ -153,6 +160,11 @@ def setup(hass, config):
hass.helpers.dispatcher.dispatcher_send(
SIGNAL_ZONE_RESTORE, zone)
def handle_rel_message(sender, message):
"""Handle relay message from AlarmDecoder."""
hass.helpers.dispatcher.dispatcher_send(
SIGNAL_REL_MESSAGE, message)
controller = False
if device_type == 'socket':
host = device.get(CONF_DEVICE_HOST)
@@ -171,6 +183,7 @@ def setup(hass, config):
controller.on_zone_fault += zone_fault_callback
controller.on_zone_restore += zone_restore_callback
controller.on_close += handle_closed_connection
controller.on_relay_changed += handle_rel_message
hass.data[DATA_AD] = controller

View File

@@ -68,7 +68,7 @@ def turn_on(hass, entity_id):
def async_turn_on(hass, entity_id):
"""Async reset the alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.async_create_task(
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
@@ -81,7 +81,7 @@ def turn_off(hass, entity_id):
def async_turn_off(hass, entity_id):
"""Async acknowledge the alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.async_create_task(
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
@@ -94,7 +94,7 @@ def toggle(hass, entity_id):
def async_toggle(hass, entity_id):
"""Async toggle acknowledgement of alert."""
data = {ATTR_ENTITY_ID: entity_id}
hass.async_add_job(
hass.async_create_task(
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
@@ -217,7 +217,7 @@ class Alert(ToggleEntity):
else:
yield from self._schedule_notify()
self.hass.async_add_job(self.async_update_ha_state)
self.async_schedule_update_ha_state()
@asyncio.coroutine
def end_alerting(self):
@@ -228,7 +228,7 @@ class Alert(ToggleEntity):
self._firing = False
if self._done_message and self._send_done_message:
yield from self._notify_done_message()
self.hass.async_add_job(self.async_update_ha_state)
self.async_schedule_update_ha_state()
@asyncio.coroutine
def _schedule_notify(self):

View File

@@ -210,7 +210,7 @@ def resolve_slot_synonyms(key, request):
return resolved_value
class AlexaResponse(object):
class AlexaResponse:
"""Help generating the response for Alexa."""
def __init__(self, hass, intent_info):

View File

@@ -55,7 +55,7 @@ HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
class _DisplayCategory(object):
class _DisplayCategory:
"""Possible display categories for Discovery response.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#display-categories
@@ -153,7 +153,7 @@ class _UnsupportedProperty(Exception):
"""This entity does not support the requested Smart Home API property."""
class _AlexaEntity(object):
class _AlexaEntity:
"""An adaptation of an entity, expressed in Alexa's terms.
The API handlers should manipulate entities only through this interface.
@@ -208,7 +208,7 @@ class _AlexaEntity(object):
raise NotImplementedError
class _AlexaInterface(object):
class _AlexaInterface:
def __init__(self, entity):
self.entity = entity
@@ -315,7 +315,7 @@ class _AlexaLockController(_AlexaInterface):
if self.entity.state == STATE_LOCKED:
return 'LOCKED'
elif self.entity.state == STATE_UNLOCKED:
if self.entity.state == STATE_UNLOCKED:
return 'UNLOCKED'
return 'JAMMED'
@@ -615,7 +615,7 @@ class _SensorCapabilities(_AlexaEntity):
yield _AlexaTemperatureSensor(self.entity)
class _Cause(object):
class _Cause:
"""Possible causes for property changes.
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#cause-object

View File

@@ -164,7 +164,7 @@ def setup(hass, config):
return True
class AmcrestDevice(object):
class AmcrestDevice:
"""Representation of a base Amcrest discovery device."""
def __init__(self, camera, name, authentication, ffmpeg_arguments,

View File

@@ -214,11 +214,11 @@ def async_setup(hass, config):
CONF_PASSWORD: password
})
hass.async_add_job(discovery.async_load_platform(
hass.async_create_task(discovery.async_load_platform(
hass, 'camera', 'mjpeg', mjpeg_camera, config))
if sensors:
hass.async_add_job(discovery.async_load_platform(
hass.async_create_task(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
@@ -226,7 +226,7 @@ def async_setup(hass, config):
}, config))
if switches:
hass.async_add_job(discovery.async_load_platform(
hass.async_create_task(discovery.async_load_platform(
hass, 'switch', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
@@ -234,7 +234,7 @@ def async_setup(hass, config):
}, config))
if motion:
hass.async_add_job(discovery.async_load_platform(
hass.async_create_task(discovery.async_load_platform(
hass, 'binary_sensor', DOMAIN, {
CONF_HOST: host,
CONF_NAME: name,

View File

@@ -58,7 +58,7 @@ def setup(hass, config):
return True
class APCUPSdData(object):
class APCUPSdData:
"""Stores the data retrieved from APCUPSd.
For each entity to use, acts as the single point responsible for fetching

View File

@@ -220,7 +220,8 @@ class APIEntityStateView(HomeAssistantView):
is_new_state = hass.states.get(entity_id) is None
# Write state
hass.states.async_set(entity_id, new_state, attributes, force_update)
hass.states.async_set(entity_id, new_state, attributes, force_update,
self.context(request))
# Read the state back for our response
status_code = HTTP_CREATED if is_new_state else 200
@@ -279,7 +280,8 @@ class APIEventView(HomeAssistantView):
event_data[key] = state
request.app['hass'].bus.async_fire(
event_type, event_data, ha.EventOrigin.remote)
event_type, event_data, ha.EventOrigin.remote,
self.context(request))
return self.json_message("Event {} fired.".format(event_type))
@@ -316,7 +318,8 @@ class APIDomainServicesView(HomeAssistantView):
"Data should be valid JSON.", HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states:
await hass.services.async_call(domain, service, data, True)
await hass.services.async_call(
domain, service, data, True, self.context(request))
return self.json(changed_states)

View File

@@ -45,7 +45,7 @@ NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication'
NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification'
NOTIFICATION_SCAN_TITLE = 'Apple TV Scan'
T = TypeVar('T')
T = TypeVar('T') # pylint: disable=invalid-name
# This version of ensure_list interprets an empty dict as no value
@@ -218,10 +218,10 @@ def _setup_atv(hass, atv_config):
ATTR_POWER: power
}
hass.async_add_job(discovery.async_load_platform(
hass.async_create_task(discovery.async_load_platform(
hass, 'media_player', DOMAIN, atv_config))
hass.async_add_job(discovery.async_load_platform(
hass.async_create_task(discovery.async_load_platform(
hass, 'remote', DOMAIN, atv_config))

View File

@@ -62,7 +62,7 @@ def setup(hass, config):
return True
class ArduinoBoard(object):
class ArduinoBoard:
"""Representation of an Arduino board."""
def __init__(self, port):

View File

@@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
REQUIREMENTS = ['pyarlo==0.1.9']
REQUIREMENTS = ['pyarlo==0.2.0']
_LOGGER = logging.getLogger(__name__)

View File

@@ -48,7 +48,7 @@ def setup(hass, config):
return True
class AsteriskData(object):
class AsteriskData:
"""Store Asterisk mailbox data."""
def __init__(self, hass, host, port, password):

View File

@@ -21,7 +21,7 @@ _LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
REQUIREMENTS = ['py-august==0.4.0']
REQUIREMENTS = ['py-august==0.6.0']
DEFAULT_TIMEOUT = 10
ACTIVITY_FETCH_LIMIT = 10
@@ -123,9 +123,9 @@ def setup_august(hass, config, api, authenticator):
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
elif state == AuthenticationState.BAD_PASSWORD:
if state == AuthenticationState.BAD_PASSWORD:
return False
elif state == AuthenticationState.REQUIRES_VALIDATION:
if state == AuthenticationState.REQUIRES_VALIDATION:
request_configuration(hass, config, api, authenticator)
return True

View File

@@ -1,62 +1,5 @@
"""Component to allow users to login and get tokens.
All requests will require passing in a valid client ID and secret via HTTP
Basic Auth.
# GET /auth/providers
Return a list of auth providers. Example:
[
{
"name": "Local",
"id": null,
"type": "local_provider",
}
]
# POST /auth/login_flow
Create a login flow. Will return the first step of the flow.
Pass in parameter 'handler' to specify the auth provider to use. Auth providers
are identified by type and id.
{
"handler": ["local_provider", null]
}
Return value will be a step in a data entry flow. See the docs for data entry
flow for details.
{
"data_schema": [
{"name": "username", "type": "string"},
{"name": "password", "type": "string"}
],
"errors": {},
"flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
"handler": ["insecure_example", null],
"step_id": "init",
"type": "form"
}
# POST /auth/login_flow/{flow_id}
Progress the flow. Most flows will be 1 page, but could optionally add extra
login challenges, like TFA. Once the flow has finished, the returned step will
have type "create_entry" and "result" key will contain an authorization code.
{
"flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
"handler": ["insecure_example", null],
"result": "411ee2f916e648d691e937ae9344681e",
"source": "user",
"title": "Example",
"type": "create_entry",
"version": 1
}
# POST /auth/token
This is an OAuth2 endpoint for granting tokens. We currently support the grant
@@ -102,24 +45,20 @@ a limited expiration.
"token_type": "Bearer"
}
"""
from datetime import timedelta
import logging
import uuid
from datetime import timedelta
import aiohttp.web
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.core import callback
from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
from homeassistant.components import websocket_api
from homeassistant.components.http.view import HomeAssistantView
from homeassistant.components.http.ban import log_invalid_auth
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.http.view import HomeAssistantView
from homeassistant.core import callback
from homeassistant.util import dt as dt_util
from . import indieauth
from . import login_flow
DOMAIN = 'auth'
DEPENDENCIES = ['http']
@@ -136,10 +75,6 @@ async def async_setup(hass, config):
"""Component to allow users to login."""
store_credentials, retrieve_credentials = _create_cred_store()
hass.http.register_view(AuthProvidersView)
hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow))
hass.http.register_view(
LoginFlowResourceView(hass.auth.login_flow, store_credentials))
hass.http.register_view(GrantTokenView(retrieve_credentials))
hass.http.register_view(LinkUserView(retrieve_credentials))
@@ -148,93 +83,11 @@ async def async_setup(hass, config):
SCHEMA_WS_CURRENT_USER
)
await login_flow.async_setup(hass, store_credentials)
return True
class AuthProvidersView(HomeAssistantView):
"""View to get available auth providers."""
url = '/auth/providers'
name = 'api:auth:providers'
requires_auth = False
async def get(self, request):
"""Get available auth providers."""
return self.json([{
'name': provider.name,
'id': provider.id,
'type': provider.type,
} for provider in request.app['hass'].auth.auth_providers])
class LoginFlowIndexView(FlowManagerIndexView):
"""View to create a config flow."""
url = '/auth/login_flow'
name = 'api:auth:login_flow'
requires_auth = False
async def get(self, request):
"""Do not allow index of flows in progress."""
return aiohttp.web.Response(status=405)
@RequestDataValidator(vol.Schema({
vol.Required('client_id'): str,
vol.Required('handler'): vol.Any(str, list),
vol.Required('redirect_uri'): str,
}))
async def post(self, request, data):
"""Create a new login flow."""
if not indieauth.verify_redirect_uri(data['client_id'],
data['redirect_uri']):
return self.json_message('invalid client id or redirect uri', 400)
# pylint: disable=no-value-for-parameter
return await super().post(request)
class LoginFlowResourceView(FlowManagerResourceView):
"""View to interact with the flow manager."""
url = '/auth/login_flow/{flow_id}'
name = 'api:auth:login_flow:resource'
requires_auth = False
def __init__(self, flow_mgr, store_credentials):
"""Initialize the login flow resource view."""
super().__init__(flow_mgr)
self._store_credentials = store_credentials
async def get(self, request, flow_id):
"""Do not allow getting status of a flow in progress."""
return self.json_message('Invalid flow specified', 404)
@RequestDataValidator(vol.Schema({
'client_id': str
}, extra=vol.ALLOW_EXTRA))
async def post(self, request, flow_id, data):
"""Handle progressing a login flow request."""
client_id = data.pop('client_id')
if not indieauth.verify_client_id(client_id):
return self.json_message('Invalid client id', 400)
try:
result = await self._flow_mgr.async_configure(flow_id, data)
except data_entry_flow.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
except vol.Invalid:
return self.json_message('User input malformed', 400)
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
return self.json(self._prepare_result_json(result))
result.pop('data')
result['result'] = self._store_credentials(client_id, result['result'])
return self.json(result)
class GrantTokenView(HomeAssistantView):
"""View to grant tokens."""
@@ -247,11 +100,26 @@ class GrantTokenView(HomeAssistantView):
"""Initialize the grant token view."""
self._retrieve_credentials = retrieve_credentials
@log_invalid_auth
async def post(self, request):
"""Grant a token."""
hass = request.app['hass']
data = await request.post()
grant_type = data.get('grant_type')
if grant_type == 'authorization_code':
return await self._async_handle_auth_code(hass, data)
if grant_type == 'refresh_token':
return await self._async_handle_refresh_token(hass, data)
return self.json({
'error': 'unsupported_grant_type',
}, status_code=400)
async def _async_handle_auth_code(self, hass, data):
"""Handle authorization code request."""
client_id = data.get('client_id')
if client_id is None or not indieauth.verify_client_id(client_id):
return self.json({
@@ -259,21 +127,6 @@ class GrantTokenView(HomeAssistantView):
'error_description': 'Invalid client id',
}, status_code=400)
grant_type = data.get('grant_type')
if grant_type == 'authorization_code':
return await self._async_handle_auth_code(hass, client_id, data)
elif grant_type == 'refresh_token':
return await self._async_handle_refresh_token(
hass, client_id, data)
return self.json({
'error': 'unsupported_grant_type',
}, status_code=400)
async def _async_handle_auth_code(self, hass, client_id, data):
"""Handle authorization code request."""
code = data.get('code')
if code is None:
@@ -309,8 +162,15 @@ class GrantTokenView(HomeAssistantView):
int(refresh_token.access_token_expiration.total_seconds()),
})
async def _async_handle_refresh_token(self, hass, client_id, data):
async def _async_handle_refresh_token(self, hass, data):
"""Handle authorization code request."""
client_id = data.get('client_id')
if client_id is not None and not indieauth.verify_client_id(client_id):
return self.json({
'error': 'invalid_request',
'error_description': 'Invalid client id',
}, status_code=400)
token = data.get('refresh_token')
if token is None:
@@ -320,11 +180,16 @@ class GrantTokenView(HomeAssistantView):
refresh_token = await hass.auth.async_get_refresh_token(token)
if refresh_token is None or refresh_token.client_id != client_id:
if refresh_token is None:
return self.json({
'error': 'invalid_grant',
}, status_code=400)
if refresh_token.client_id != client_id:
return self.json({
'error': 'invalid_request',
}, status_code=400)
access_token = hass.auth.async_create_access_token(refresh_token)
return self.json({
@@ -412,4 +277,7 @@ def websocket_current_user(hass, connection, msg):
'id': user.id,
'name': user.name,
'is_owner': user.is_owner,
'credentials': [{'auth_provider_type': c.auth_provider_type,
'auth_provider_id': c.auth_provider_id}
for c in user.credentials]
}))

View File

@@ -1,6 +1,10 @@
"""Helpers to resolve client ID/secret."""
import asyncio
from html.parser import HTMLParser
from ipaddress import ip_address, ip_network
from urllib.parse import urlparse
from urllib.parse import urlparse, urljoin
from aiohttp.client_exceptions import ClientError
# IP addresses of loopback interfaces
ALLOWED_IPS = (
@@ -16,7 +20,7 @@ ALLOWED_NETWORKS = (
)
def verify_redirect_uri(client_id, redirect_uri):
async def verify_redirect_uri(hass, client_id, redirect_uri):
"""Verify that the client and redirect uri match."""
try:
client_id_parts = _parse_client_id(client_id)
@@ -25,16 +29,75 @@ def verify_redirect_uri(client_id, redirect_uri):
redirect_parts = _parse_url(redirect_uri)
# IndieAuth 4.2.2 allows for redirect_uri to be on different domain
# but needs to be specified in link tag when fetching `client_id`.
# This is not implemented.
# Verify redirect url and client url have same scheme and domain.
return (
is_valid = (
client_id_parts.scheme == redirect_parts.scheme and
client_id_parts.netloc == redirect_parts.netloc
)
if is_valid:
return True
# IndieAuth 4.2.2 allows for redirect_uri to be on different domain
# but needs to be specified in link tag when fetching `client_id`.
redirect_uris = await fetch_redirect_uris(hass, client_id)
return redirect_uri in redirect_uris
class LinkTagParser(HTMLParser):
"""Parser to find link tags."""
def __init__(self, rel):
"""Initialize a link tag parser."""
super().__init__()
self.rel = rel
self.found = []
def handle_starttag(self, tag, attrs):
"""Handle finding a start tag."""
if tag != 'link':
return
attrs = dict(attrs)
if attrs.get('rel') == self.rel:
self.found.append(attrs.get('href'))
async def fetch_redirect_uris(hass, url):
"""Find link tag with redirect_uri values.
IndieAuth 4.2.2
The client SHOULD publish one or more <link> tags or Link HTTP headers with
a rel attribute of redirect_uri at the client_id URL.
We limit to the first 10kB of the page.
We do not implement extracting redirect uris from headers.
"""
session = hass.helpers.aiohttp_client.async_get_clientsession()
parser = LinkTagParser('redirect_uri')
chunks = 0
try:
resp = await session.get(url, timeout=5)
async for data in resp.content.iter_chunked(1024):
parser.feed(data.decode())
chunks += 1
if chunks == 10:
break
except (asyncio.TimeoutError, ClientError):
pass
# Authorization endpoints verifying that a redirect_uri is allowed for use
# by a client MUST look for an exact match of the given redirect_uri in the
# request against the list of redirect_uris discovered after resolving any
# relative URLs.
return [urljoin(url, found) for found in parser.found]
def verify_client_id(client_id):
"""Verify that the client id is valid."""

View File

@@ -0,0 +1,218 @@
"""HTTP views handle login flow.
# GET /auth/providers
Return a list of auth providers. Example:
[
{
"name": "Local",
"id": null,
"type": "local_provider",
}
]
# POST /auth/login_flow
Create a login flow. Will return the first step of the flow.
Pass in parameter 'client_id' and 'redirect_url' validate by indieauth.
Pass in parameter 'handler' to specify the auth provider to use. Auth providers
are identified by type and id.
{
"client_id": "https://hassbian.local:8123/",
"handler": ["local_provider", null],
"redirect_url": "https://hassbian.local:8123/"
}
Return value will be a step in a data entry flow. See the docs for data entry
flow for details.
{
"data_schema": [
{"name": "username", "type": "string"},
{"name": "password", "type": "string"}
],
"errors": {},
"flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
"handler": ["insecure_example", null],
"step_id": "init",
"type": "form"
}
# POST /auth/login_flow/{flow_id}
Progress the flow. Most flows will be 1 page, but could optionally add extra
login challenges, like TFA. Once the flow has finished, the returned step will
have type "create_entry" and "result" key will contain an authorization code.
{
"flow_id": "8f7e42faab604bcab7ac43c44ca34d58",
"handler": ["insecure_example", null],
"result": "411ee2f916e648d691e937ae9344681e",
"source": "user",
"title": "Example",
"type": "create_entry",
"version": 1
}
"""
import aiohttp.web
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.http.ban import process_wrong_login, \
log_invalid_auth
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.http.view import HomeAssistantView
from . import indieauth
async def async_setup(hass, store_credentials):
"""Component to allow users to login."""
hass.http.register_view(AuthProvidersView)
hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow))
hass.http.register_view(
LoginFlowResourceView(hass.auth.login_flow, store_credentials))
class AuthProvidersView(HomeAssistantView):
"""View to get available auth providers."""
url = '/auth/providers'
name = 'api:auth:providers'
requires_auth = False
async def get(self, request):
"""Get available auth providers."""
return self.json([{
'name': provider.name,
'id': provider.id,
'type': provider.type,
} for provider in request.app['hass'].auth.auth_providers])
def _prepare_result_json(result):
"""Convert result to JSON."""
if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
data = result.copy()
data.pop('result')
data.pop('data')
return data
if result['type'] != data_entry_flow.RESULT_TYPE_FORM:
return result
import voluptuous_serialize
data = result.copy()
schema = data['data_schema']
if schema is None:
data['data_schema'] = []
else:
data['data_schema'] = voluptuous_serialize.convert(schema)
return data
class LoginFlowIndexView(HomeAssistantView):
"""View to create a config flow."""
url = '/auth/login_flow'
name = 'api:auth:login_flow'
requires_auth = False
def __init__(self, flow_mgr):
"""Initialize the flow manager index view."""
self._flow_mgr = flow_mgr
async def get(self, request):
"""Do not allow index of flows in progress."""
return aiohttp.web.Response(status=405)
@RequestDataValidator(vol.Schema({
vol.Required('client_id'): str,
vol.Required('handler'): vol.Any(str, list),
vol.Required('redirect_uri'): str,
}))
@log_invalid_auth
async def post(self, request, data):
"""Create a new login flow."""
if not await indieauth.verify_redirect_uri(
request.app['hass'], data['client_id'], data['redirect_uri']):
return self.json_message('invalid client id or redirect uri', 400)
if isinstance(data['handler'], list):
handler = tuple(data['handler'])
else:
handler = data['handler']
try:
result = await self._flow_mgr.async_init(handler)
except data_entry_flow.UnknownHandler:
return self.json_message('Invalid handler specified', 404)
except data_entry_flow.UnknownStep:
return self.json_message('Handler does not support init', 400)
return self.json(_prepare_result_json(result))
class LoginFlowResourceView(HomeAssistantView):
"""View to interact with the flow manager."""
url = '/auth/login_flow/{flow_id}'
name = 'api:auth:login_flow:resource'
requires_auth = False
def __init__(self, flow_mgr, store_credentials):
"""Initialize the login flow resource view."""
self._flow_mgr = flow_mgr
self._store_credentials = store_credentials
async def get(self, request):
"""Do not allow getting status of a flow in progress."""
return self.json_message('Invalid flow specified', 404)
@RequestDataValidator(vol.Schema({
'client_id': str
}, extra=vol.ALLOW_EXTRA))
@log_invalid_auth
async def post(self, request, flow_id, data):
"""Handle progressing a login flow request."""
client_id = data.pop('client_id')
if not indieauth.verify_client_id(client_id):
return self.json_message('Invalid client id', 400)
try:
result = await self._flow_mgr.async_configure(flow_id, data)
except data_entry_flow.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
except vol.Invalid:
return self.json_message('User input malformed', 400)
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
# @log_invalid_auth does not work here since it returns HTTP 200
# need manually log failed login attempts
if result['errors'] is not None and \
result['errors'].get('base') == 'invalid_auth':
await process_wrong_login(request)
return self.json(_prepare_result_json(result))
result.pop('data')
result['result'] = self._store_credentials(client_id, result['result'])
return self.json(result)
async def delete(self, request, flow_id):
"""Cancel a flow in progress."""
try:
self._flow_mgr.async_abort(flow_id)
except data_entry_flow.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
return self.json_message('Flow aborted')

View File

@@ -297,7 +297,7 @@ class AutomationEntity(ToggleEntity):
return
# HomeAssistant is starting up
elif self.hass.state == CoreState.not_running:
if self.hass.state == CoreState.not_running:
@asyncio.coroutine
def async_enable_automation(event):
"""Start automation on startup."""

View File

@@ -44,7 +44,7 @@ def async_trigger(hass, config, action):
# Automation are enabled while hass is starting up, fire right away
# Check state because a config reload shouldn't trigger it.
elif hass.state == CoreState.starting:
if hass.state == CoreState.starting:
hass.async_run_job(action, {
'trigger': {
'platform': 'homeassistant',

View File

@@ -10,7 +10,7 @@ import json
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components import mqtt
from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD)
import homeassistant.helpers.config_validation as cv

View File

@@ -19,7 +19,7 @@ DOMAIN = 'bbb_gpio'
def setup(hass, config):
"""Set up the BeagleBone Black GPIO component."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
from Adafruit_BBIO import GPIO
def cleanup_gpio(event):
"""Stuff to do before stopping."""
@@ -36,14 +36,14 @@ def setup(hass, config):
def setup_output(pin):
"""Set up a GPIO as output."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
from Adafruit_BBIO import GPIO
GPIO.setup(pin, GPIO.OUT)
def setup_input(pin, pull_mode):
"""Set up a GPIO as input."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
from Adafruit_BBIO import GPIO
GPIO.setup(pin, GPIO.IN,
GPIO.PUD_DOWN if pull_mode == 'DOWN'
else GPIO.PUD_UP)
@@ -52,20 +52,20 @@ def setup_input(pin, pull_mode):
def write_output(pin, value):
"""Write a value to a GPIO."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
from Adafruit_BBIO import GPIO
GPIO.output(pin, value)
def read_input(pin):
"""Read a value from a GPIO."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
from Adafruit_BBIO import GPIO
return GPIO.input(pin) is GPIO.HIGH
def edge_detect(pin, event_callback, bounce):
"""Add detection for RISING and FALLING events."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
from Adafruit_BBIO import GPIO
GPIO.add_event_detect(
pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce)

View File

@@ -11,7 +11,8 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.alarmdecoder import (
ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE,
CONF_ZONE_RFID, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE,
SIGNAL_RFX_MESSAGE)
SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR,
CONF_RELAY_CHAN)
DEPENDENCIES = ['alarmdecoder']
@@ -37,8 +38,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
zone_type = device_config_data[CONF_ZONE_TYPE]
zone_name = device_config_data[CONF_ZONE_NAME]
zone_rfid = device_config_data.get(CONF_ZONE_RFID)
relay_addr = device_config_data.get(CONF_RELAY_ADDR)
relay_chan = device_config_data.get(CONF_RELAY_CHAN)
device = AlarmDecoderBinarySensor(
zone_num, zone_name, zone_type, zone_rfid)
zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan)
devices.append(device)
add_devices(devices)
@@ -49,7 +52,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class AlarmDecoderBinarySensor(BinarySensorDevice):
"""Representation of an AlarmDecoder binary sensor."""
def __init__(self, zone_number, zone_name, zone_type, zone_rfid):
def __init__(self, zone_number, zone_name, zone_type, zone_rfid,
relay_addr, relay_chan):
"""Initialize the binary_sensor."""
self._zone_number = zone_number
self._zone_type = zone_type
@@ -57,6 +61,8 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._name = zone_name
self._rfid = zone_rfid
self._rfstate = None
self._relay_addr = relay_addr
self._relay_chan = relay_chan
@asyncio.coroutine
def async_added_to_hass(self):
@@ -70,6 +76,9 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_RFX_MESSAGE, self._rfx_message_callback)
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_REL_MESSAGE, self._rel_message_callback)
@property
def name(self):
"""Return the name of the entity."""
@@ -122,3 +131,12 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
if self._rfid and message and message.serial_number == self._rfid:
self._rfstate = message.value
self.schedule_update_ha_state()
def _rel_message_callback(self, message):
"""Update relay state."""
if (self._relay_addr == message.address and
self._relay_chan == message.channel):
_LOGGER.debug("Relay %d:%d value:%d", message.address,
message.channel, message.value)
self._state = message.value
self.schedule_update_ha_state()

View File

@@ -89,7 +89,7 @@ class ArestBinarySensor(BinarySensorDevice):
self.arest.update()
class ArestData(object):
class ArestData:
"""Class for handling the data retrieval for pins."""
def __init__(self, resource, pin):

View File

@@ -99,7 +99,7 @@ class AuroraSensor(BinarySensorDevice):
self.aurora_data.update()
class AuroraData(object):
class AuroraData:
"""Get aurora forecast."""
def __init__(self, latitude, longitude, threshold):

View File

@@ -122,7 +122,6 @@ class BayesianBinarySensor(BinarySensorDevice):
def async_added_to_hass(self):
"""Call when entity about to be added."""
@callback
# pylint: disable=invalid-name
def async_threshold_sensor_state_listener(entity, old_state,
new_state):
"""Handle sensor state changes."""

View File

@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
import homeassistant.components.bbb_gpio as bbb_gpio
from homeassistant.components import bbb_gpio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME)

View File

@@ -25,6 +25,9 @@ DEFAULT_PAYLOAD_OFF = 'OFF'
SCAN_INTERVAL = timedelta(seconds=60)
CONF_COMMAND_TIMEOUT = 'command_timeout'
DEFAULT_TIMEOUT = 15
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@@ -32,6 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(
CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
})
@@ -43,9 +48,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
payload_on = config.get(CONF_PAYLOAD_ON)
device_class = config.get(CONF_DEVICE_CLASS)
value_template = config.get(CONF_VALUE_TEMPLATE)
command_timeout = config.get(CONF_COMMAND_TIMEOUT)
if value_template is not None:
value_template.hass = hass
data = CommandSensorData(hass, command)
data = CommandSensorData(hass, command, command_timeout)
add_devices([CommandBinarySensor(
hass, data, name, device_class, payload_on, payload_off,

View File

@@ -117,7 +117,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
add_entities(entities)
class HikvisionData(object):
class HikvisionData:
"""Hikvision device event stream object."""
def __init__(self, hass, url, port, name, username, password):

View File

@@ -16,10 +16,7 @@ DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
ATTR_WINDOW_STATE = 'window_state'
ATTR_EVENT_DELAY = 'event_delay'
ATTR_MOTION_DETECTED = 'motion_detected'
ATTR_ILLUMINATION = 'illumination'
STATE_SMOKE_OFF = 'IDLE_OFF'
async def async_setup_platform(hass, config, async_add_devices,
@@ -30,15 +27,18 @@ async def async_setup_platform(hass, config, async_add_devices,
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the HomematicIP binary sensor from a config entry."""
from homematicip.device import (ShutterContact, MotionDetectorIndoor)
from homematicip.aio.device import (
AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector)
home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home
devices = []
for device in home.devices:
if isinstance(device, ShutterContact):
if isinstance(device, AsyncShutterContact):
devices.append(HomematicipShutterContact(home, device))
elif isinstance(device, MotionDetectorIndoor):
elif isinstance(device, AsyncMotionDetectorIndoor):
devices.append(HomematicipMotionDetector(home, device))
elif isinstance(device, AsyncSmokeDetector):
devices.append(HomematicipSmokeDetector(home, device))
if devices:
async_add_devices(devices)
@@ -47,10 +47,6 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
"""HomematicIP shutter contact."""
def __init__(self, home, device):
"""Initialize the shutter contact."""
super().__init__(home, device)
@property
def device_class(self):
"""Return the class of this sensor."""
@@ -69,11 +65,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
"""MomematicIP motion detector."""
def __init__(self, home, device):
"""Initialize the shutter contact."""
super().__init__(home, device)
"""HomematicIP motion detector."""
@property
def device_class(self):
@@ -86,3 +78,17 @@ class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
if self._device.sabotage:
return True
return self._device.motionDetected
class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice):
"""HomematicIP smoke detector."""
@property
def device_class(self):
"""Return the class of this sensor."""
return 'smoke'
@property
def is_on(self):
"""Return true if smoke is detected."""
return self._device.smokeDetectorAlarmType != STATE_SMOKE_OFF

View File

@@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ihc/
"""
from xml.etree.ElementTree import Element
import voluptuous as vol
from homeassistant.components.binary_sensor import (
@@ -70,7 +68,7 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice):
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool,
product: Element = None) -> None:
product=None) -> None:
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None

View File

@@ -17,7 +17,9 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {'openClosedSensor': 'opening',
'motionSensor': 'motion',
'doorSensor': 'door',
'wetLeakSensor': 'moisture'}
'wetLeakSensor': 'moisture',
'lightSensor': 'light',
'batterySensor': 'battery'}
@asyncio.coroutine
@@ -54,4 +56,9 @@ class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
@property
def is_on(self):
"""Return the boolean response if the node is on."""
return bool(self._insteon_device_state.value)
on_val = bool(self._insteon_device_state.value)
if self._insteon_device_state.name == 'lightSensor':
return not on_val
return on_val

View File

@@ -101,7 +101,7 @@ class IssBinarySensor(BinarySensorDevice):
self.iss_data.update()
class IssData(object):
class IssData:
"""Get data from the ISS API."""
def __init__(self, latitude, longitude):

View File

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

View File

@@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.modbus/
import logging
import voluptuous as vol
import homeassistant.components.modbus as modbus
from homeassistant.components import modbus
from homeassistant.const import CONF_NAME, CONF_SLAVE
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers import config_validation as cv

View File

@@ -11,7 +11,7 @@ from typing import Optional
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components import mqtt
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (

View File

@@ -142,7 +142,7 @@ class NetatmoBinarySensor(BinarySensorDevice):
"""Return the class of this sensor, from DEVICE_CLASSES."""
if self._cameratype == 'NACamera':
return WELCOME_SENSOR_TYPES.get(self._sensor_name)
elif self._cameratype == 'NOC':
if self._cameratype == 'NOC':
return PRESENCE_SENSOR_TYPES.get(self._sensor_name)
return TAG_SENSOR_TYPES.get(self._sensor_name)

View File

@@ -0,0 +1,103 @@
"""
This platform provides binary sensors for OpenUV data.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.openuv/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.openuv import (
BINARY_SENSORS, DATA_PROTECTION_WINDOW, DOMAIN, TOPIC_UPDATE,
TYPE_PROTECTION_WINDOW, OpenUvEntity)
from homeassistant.util.dt import as_local, parse_datetime, utcnow
DEPENDENCIES = ['openuv']
_LOGGER = logging.getLogger(__name__)
ATTR_PROTECTION_WINDOW_STARTING_TIME = 'start_time'
ATTR_PROTECTION_WINDOW_STARTING_UV = 'start_uv'
ATTR_PROTECTION_WINDOW_ENDING_TIME = 'end_time'
ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv'
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up the OpenUV binary sensor platform."""
if discovery_info is None:
return
openuv = hass.data[DOMAIN]
binary_sensors = []
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
name, icon = BINARY_SENSORS[sensor_type]
binary_sensors.append(
OpenUvBinarySensor(openuv, sensor_type, name, icon))
async_add_devices(binary_sensors, True)
class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
"""Define a binary sensor for OpenUV."""
def __init__(self, openuv, sensor_type, name, icon):
"""Initialize the sensor."""
super().__init__(openuv)
self._icon = icon
self._latitude = openuv.client.latitude
self._longitude = openuv.client.longitude
self._name = name
self._sensor_type = sensor_type
self._state = None
@property
def icon(self):
"""Return the icon."""
return self._icon
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def unique_id(self) -> str:
"""Return a unique, HASS-friendly identifier for this entity."""
return '{0}_{1}_{2}'.format(
self._latitude, self._longitude, self._sensor_type)
@callback
def _update_data(self):
"""Update the state."""
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, TOPIC_UPDATE, self._update_data)
async def async_update(self):
"""Update the state."""
data = self.openuv.data[DATA_PROTECTION_WINDOW]['result']
if self._sensor_type == TYPE_PROTECTION_WINDOW:
self._state = parse_datetime(
data['from_time']) <= utcnow() <= parse_datetime(
data['to_time'])
self._attrs.update({
ATTR_PROTECTION_WINDOW_ENDING_TIME:
as_local(parse_datetime(data['to_time'])),
ATTR_PROTECTION_WINDOW_ENDING_UV: data['to_uv'],
ATTR_PROTECTION_WINDOW_STARTING_UV: data['from_uv'],
ATTR_PROTECTION_WINDOW_STARTING_TIME:
as_local(parse_datetime(data['from_time'])),
})

View File

@@ -96,7 +96,7 @@ class PingBinarySensor(BinarySensorDevice):
self.ping.update()
class PingData(object):
class PingData:
"""The Class for handling the data retrieval."""
def __init__(self, host, count):

View File

@@ -111,11 +111,10 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
if data[KEY_STATUS] == STATUS_ONLINE:
return True
elif data[KEY_STATUS] == STATUS_OFFLINE:
if data[KEY_STATUS] == STATUS_OFFLINE:
return False
else:
_LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
data[KEY_STATUS])
_LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
data[KEY_STATUS])
def _handle_update(self, *args, **kwargs) -> None:
"""Handle an update to the state of this sensor."""

View File

@@ -67,6 +67,6 @@ class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice):
"""Return the icon of this device."""
if self._sensor_type == 'is_watering':
return 'mdi:water' if self.is_on else 'mdi:water-off'
elif self._sensor_type == 'status':
if self._sensor_type == 'status':
return 'mdi:pipe' if self.is_on else 'mdi:pipe-disconnected'
return ICON_MAP.get(self._sensor_type)

View File

@@ -8,7 +8,7 @@ import logging
import voluptuous as vol
import homeassistant.components.rpi_gpio as rpi_gpio
from homeassistant.components import rpi_gpio
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import DEVICE_DEFAULT_NAME

View File

@@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
import homeassistant.components.rpi_pfio as rpi_pfio
from homeassistant.components import rpi_pfio
from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME
import homeassistant.helpers.config_validation as cv

View File

@@ -0,0 +1,98 @@
"""
Support for Tahoma binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.tahoma/
"""
import logging
from datetime import timedelta
from homeassistant.components.binary_sensor import (
BinarySensorDevice)
from homeassistant.components.tahoma import (
DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
from homeassistant.const import (STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL)
DEPENDENCIES = ['tahoma']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=120)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Tahoma controller devices."""
_LOGGER.debug("Setup Tahoma Binary sensor platform")
controller = hass.data[TAHOMA_DOMAIN]['controller']
devices = []
for device in hass.data[TAHOMA_DOMAIN]['devices']['smoke']:
devices.append(TahomaBinarySensor(device, controller))
add_devices(devices, True)
class TahomaBinarySensor(TahomaDevice, BinarySensorDevice):
"""Representation of a Tahoma Binary Sensor."""
def __init__(self, tahoma_device, controller):
"""Initialize the sensor."""
super().__init__(tahoma_device, controller)
self._state = None
self._icon = None
self._battery = None
@property
def is_on(self):
"""Return the state of the sensor."""
return bool(self._state == STATE_ON)
@property
def device_class(self):
"""Return the class of the device."""
if self.tahoma_device.type == 'rtds:RTDSSmokeSensor':
return 'smoke'
return None
@property
def icon(self):
"""Icon for device by its type."""
return self._icon
@property
def device_state_attributes(self):
"""Return the device state attributes."""
attr = {}
super_attr = super().device_state_attributes
if super_attr is not None:
attr.update(super_attr)
if self._battery is not None:
attr[ATTR_BATTERY_LEVEL] = self._battery
return attr
def update(self):
"""Update the state."""
self.controller.get_states([self.tahoma_device])
if self.tahoma_device.type == 'rtds:RTDSSmokeSensor':
if self.tahoma_device.active_states['core:SmokeState']\
== 'notDetected':
self._state = STATE_OFF
else:
self._state = STATE_ON
if 'core:SensorDefectState' in self.tahoma_device.active_states:
# Set to 'lowBattery' for low battery warning.
self._battery = self.tahoma_device.active_states[
'core:SensorDefectState']
else:
self._battery = None
if self._state == STATE_ON:
self._icon = "mdi:fire"
elif self._battery == 'lowBattery':
self._icon = "mdi:battery-alert"
else:
self._icon = None
_LOGGER.debug("Update %s, state: %s", self._name, self._state)

View File

@@ -63,7 +63,7 @@ class TapsAffSensor(BinarySensorDevice):
self.data.update()
class TapsAffData(object):
class TapsAffData:
"""Class for handling the data retrieval for pins."""
def __init__(self, location):

View File

@@ -86,7 +86,6 @@ class ThresholdSensor(BinarySensorDevice):
self._state = False
self.sensor_value = None
# pylint: disable=invalid-name
@callback
def async_threshold_sensor_state_listener(
entity, old_state, new_state):
@@ -129,9 +128,9 @@ class ThresholdSensor(BinarySensorDevice):
if self._threshold_lower is not None and \
self._threshold_upper is not None:
return TYPE_RANGE
elif self._threshold_lower is not None:
if self._threshold_lower is not None:
return TYPE_LOWER
elif self._threshold_upper is not None:
if self._threshold_upper is not None:
return TYPE_UPPER
@property

View File

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

View File

@@ -4,93 +4,34 @@ Support for Velbus Binary Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.velbus/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.const import CONF_NAME, CONF_DEVICES
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA
from homeassistant.components.velbus import DOMAIN
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['velbus']
from homeassistant.components.velbus import (
DOMAIN as VELBUS_DOMAIN, VelbusEntity)
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [
{
vol.Required('module'): cv.positive_int,
vol.Required('channel'): cv.positive_int,
vol.Required(CONF_NAME): cv.string,
vol.Optional('is_pushbutton'): cv.boolean
}
])
})
DEPENDENCIES = ['velbus']
def setup_platform(hass, config, add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up Velbus binary sensors."""
velbus = hass.data[DOMAIN]
add_devices(VelbusBinarySensor(sensor, velbus)
for sensor in config[CONF_DEVICES])
if discovery_info is None:
return
sensors = []
for sensor in discovery_info:
module = hass.data[VELBUS_DOMAIN].get_module(sensor[0])
channel = sensor[1]
sensors.append(VelbusBinarySensor(module, channel))
async_add_devices(sensors)
class VelbusBinarySensor(BinarySensorDevice):
class VelbusBinarySensor(VelbusEntity, BinarySensorDevice):
"""Representation of a Velbus Binary Sensor."""
def __init__(self, binary_sensor, velbus):
"""Initialize a Velbus light."""
self._velbus = velbus
self._name = binary_sensor[CONF_NAME]
self._module = binary_sensor['module']
self._channel = binary_sensor['channel']
self._is_pushbutton = 'is_pushbutton' in binary_sensor \
and binary_sensor['is_pushbutton']
self._state = False
@asyncio.coroutine
def async_added_to_hass(self):
"""Add listener for Velbus messages on bus."""
yield from self.hass.async_add_job(
self._velbus.subscribe, self._on_message)
def _on_message(self, message):
import velbus
if isinstance(message, velbus.PushButtonStatusMessage):
if message.address == self._module and \
self._channel in message.get_channels():
if self._is_pushbutton:
if self._channel in message.closed:
self._toggle()
else:
pass
else:
self._toggle()
def _toggle(self):
if self._state is True:
self._state = False
else:
self._state = True
self.schedule_update_ha_state()
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def name(self):
"""Return the display name of this sensor."""
return self._name
@property
def is_on(self):
"""Return true if the sensor is on."""
return self._state
return self._module.is_closed(self._channel)

View File

@@ -28,7 +28,7 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice):
val = getattr(self.vehicle, self._attribute)
if self._attribute == 'bulb_failures':
return bool(val)
elif self._attribute in ['doors', 'windows']:
if self._attribute in ['doors', 'windows']:
return any([val[key] for key in val if 'Open' in key])
return val != 'Normal'

View File

@@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Register discovered WeMo binary sensors."""
import pywemo.discovery as discovery
from pywemo import discovery
if discovery_info is not None:
location = discovery_info['ssdp_description']

View File

@@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.9.5']
REQUIREMENTS = ['holidays==0.9.6']
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime
@@ -25,9 +25,9 @@ ALL_COUNTRIES = ['Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT',
'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech',
'CZ', 'Denmark', 'DK', 'England', 'EuropeanCentralBank',
'ECB', 'TAR', 'Finland', 'FI', 'France', 'FRA', 'Germany',
'DE', 'Hungary', 'HU', 'Ireland', 'Isle of Man', 'Italy',
'IT', 'Japan', 'JP', 'Mexico', 'MX', 'Netherlands', 'NL',
'NewZealand', 'NZ', 'Northern Ireland',
'DE', 'Hungary', 'HU', 'India', 'IND', 'Ireland',
'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI',
'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES',
@@ -135,7 +135,7 @@ class IsWorkdaySensor(BinarySensorDevice):
"""Check if given day is in the includes list."""
if day in self._workdays:
return True
elif 'holiday' in self._workdays and now in self._obj_holidays:
if 'holiday' in self._workdays and now in self._obj_holidays:
return True
return False
@@ -144,7 +144,7 @@ class IsWorkdaySensor(BinarySensorDevice):
"""Check if given day is in the excludes list."""
if day in self._excludes:
return True
elif 'holiday' in self._excludes and now in self._obj_holidays:
if 'holiday' in self._excludes and now in self._obj_holidays:
return True
return False

View File

@@ -124,7 +124,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
return False
self._state = True
return True
elif value == '0':
if value == '0':
if self._state:
self._state = False
return True
@@ -184,7 +184,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
return False
self._state = True
return True
elif value == NO_MOTION:
if value == NO_MOTION:
if not self._state:
return False
self._state = False
@@ -224,7 +224,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
return False
self._state = True
return True
elif value == 'close':
if value == 'close':
self._open_since = 0
if self._state:
self._state = False
@@ -254,7 +254,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
return False
self._state = True
return True
elif value == 'no_leak':
if value == 'no_leak':
if self._state:
self._state = False
return True
@@ -290,7 +290,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
return False
self._state = True
return True
elif value == '0':
if value == '0':
if self._state:
self._state = False
return True

View File

@@ -10,7 +10,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)

View File

@@ -40,7 +40,7 @@ SNAP_PICTURE_SCHEMA = vol.Schema({
})
class BlinkSystem(object):
class BlinkSystem:
"""Blink System class."""
def __init__(self, config_info):

View File

@@ -50,7 +50,7 @@ def setup(hass, config):
return True
class BloomSky(object):
class BloomSky:
"""Handle all communication with the BloomSky API."""
# API documentation at http://weatherlution.com/bloomsky-api/

View File

@@ -118,7 +118,7 @@ def setup_account(account_config: dict, hass, name: str) \
return cd_account
class BMWConnectedDriveAccount(object):
class BMWConnectedDriveAccount:
"""Representation of a BMW vehicle."""
def __init__(self, username: str, password: str, region_str: str,

View File

@@ -130,7 +130,7 @@ class CalendarEventDevice(Entity):
now = dt.now()
if start <= now and end > now:
if start <= now < end:
return STATE_ON
if now >= end:

View File

@@ -125,7 +125,7 @@ class WebDavCalendarEventDevice(CalendarEventDevice):
return await self.data.async_get_events(hass, start_date, end_date)
class WebDavCalendarData(object):
class WebDavCalendarData:
"""Class to utilize the calendar dav client object to get next event."""
def __init__(self, calendar, include_all_day, search):

View File

@@ -28,7 +28,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
class DemoGoogleCalendarData(object):
class DemoGoogleCalendarData:
"""Representation of a Demo Calendar element."""
event = {}

View File

@@ -55,7 +55,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice):
return await self.data.async_get_events(hass, start_date, end_date)
class GoogleCalendarData(object):
class GoogleCalendarData:
"""Class to utilize calendar service object to get next event."""
def __init__(self, calendar_service, calendar_id, search,

View File

@@ -280,7 +280,7 @@ class TodoistProjectDevice(CalendarEventDevice):
return attributes
class TodoistProjectData(object):
class TodoistProjectData:
"""
Class used by the Task Device service object to hold all Todoist Tasks.
@@ -503,7 +503,7 @@ class TodoistProjectData(object):
time_format = '%a %d %b %Y %H:%M:%S %z'
for task in project_task_data:
due_date = datetime.strptime(task['due_date_utc'], time_format)
if due_date > start_date and due_date < end_date:
if start_date < due_date < end_date:
event = {
'uid': task['id'],
'title': task['content'],

View File

@@ -19,7 +19,8 @@ import async_timeout
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
SERVICE_TURN_ON
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import Entity
@@ -47,6 +48,9 @@ STATE_RECORDING = 'recording'
STATE_STREAMING = 'streaming'
STATE_IDLE = 'idle'
# Bitfield of features supported by the camera entity
SUPPORT_ON_OFF = 1
DEFAULT_CONTENT_TYPE = 'image/jpeg'
ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
@@ -79,6 +83,35 @@ class Image:
content = attr.ib(type=bytes)
@bind_hass
def turn_off(hass, entity_id=None):
"""Turn off camera."""
hass.add_job(async_turn_off, hass, entity_id)
@bind_hass
async def async_turn_off(hass, entity_id=None):
"""Turn off camera."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)
@bind_hass
def turn_on(hass, entity_id=None):
"""Turn on camera."""
hass.add_job(async_turn_on, hass, entity_id)
@bind_hass
async def async_turn_on(hass, entity_id=None):
"""Turn on camera, and set operation mode."""
data = {}
if entity_id is not None:
data[ATTR_ENTITY_ID] = entity_id
await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)
@bind_hass
def enable_motion_detection(hass, entity_id=None):
"""Enable Motion Detection."""
@@ -119,6 +152,9 @@ async def async_get_image(hass, entity_id, timeout=10):
if camera is None:
raise HomeAssistantError('Camera not found')
if not camera.is_on:
raise HomeAssistantError('Camera is off')
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(timeout, loop=hass.loop):
image = await camera.async_camera_image()
@@ -163,6 +199,12 @@ async def async_setup(hass, config):
await camera.async_enable_motion_detection()
elif service.service == SERVICE_DISABLE_MOTION:
await camera.async_disable_motion_detection()
elif service.service == SERVICE_TURN_OFF and \
camera.supported_features & SUPPORT_ON_OFF:
await camera.async_turn_off()
elif service.service == SERVICE_TURN_ON and \
camera.supported_features & SUPPORT_ON_OFF:
await camera.async_turn_on()
if not camera.should_poll:
continue
@@ -200,6 +242,12 @@ async def async_setup(hass, config):
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)
hass.services.async_register(
DOMAIN, SERVICE_TURN_OFF, async_handle_camera_service,
schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_TURN_ON, async_handle_camera_service,
schema=CAMERA_SERVICE_SCHEMA)
hass.services.async_register(
DOMAIN, SERVICE_ENABLE_MOTION, async_handle_camera_service,
schema=CAMERA_SERVICE_SCHEMA)
@@ -243,6 +291,11 @@ class Camera(Entity):
"""Return a link to the camera feed as entity picture."""
return ENTITY_IMAGE_URL.format(self.entity_id, self.access_tokens[-1])
@property
def supported_features(self):
"""Flag supported features."""
return 0
@property
def is_recording(self):
"""Return true if the device is recording."""
@@ -301,32 +354,23 @@ class Camera(Entity):
last_image = None
try:
while True:
img_bytes = await self.async_camera_image()
if not img_bytes:
break
while True:
img_bytes = await self.async_camera_image()
if not img_bytes:
break
if img_bytes and img_bytes != last_image:
if img_bytes and img_bytes != last_image:
await write_to_mjpeg_stream(img_bytes)
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
await write_to_mjpeg_stream(img_bytes)
last_image = img_bytes
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
await write_to_mjpeg_stream(img_bytes)
await asyncio.sleep(interval)
last_image = img_bytes
await asyncio.sleep(interval)
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
response = None
raise
finally:
if response is not None:
await response.write_eof()
return response
async def handle_async_mjpeg_stream(self, request):
"""Serve an HTTP MJPEG stream from the camera.
@@ -342,14 +386,38 @@ class Camera(Entity):
"""Return the camera state."""
if self.is_recording:
return STATE_RECORDING
elif self.is_streaming:
if self.is_streaming:
return STATE_STREAMING
return STATE_IDLE
@property
def is_on(self):
"""Return true if on."""
return True
def turn_off(self):
"""Turn off camera."""
raise NotImplementedError()
@callback
def async_turn_off(self):
"""Turn off camera."""
return self.hass.async_add_job(self.turn_off)
def turn_on(self):
"""Turn off camera."""
raise NotImplementedError()
@callback
def async_turn_on(self):
"""Turn off camera."""
return self.hass.async_add_job(self.turn_on)
def enable_motion_detection(self):
"""Enable motion detection in the camera."""
raise NotImplementedError()
@callback
def async_enable_motion_detection(self):
"""Call the job and enable motion detection."""
return self.hass.async_add_job(self.enable_motion_detection)
@@ -358,6 +426,7 @@ class Camera(Entity):
"""Disable motion detection in camera."""
raise NotImplementedError()
@callback
def async_disable_motion_detection(self):
"""Call the job and disable motion detection."""
return self.hass.async_add_job(self.disable_motion_detection)
@@ -402,17 +471,19 @@ class CameraView(HomeAssistantView):
camera = self.component.get_entity(entity_id)
if camera is None:
status = 404 if request[KEY_AUTHENTICATED] else 401
return web.Response(status=status)
raise web.HTTPNotFound()
authenticated = (request[KEY_AUTHENTICATED] or
request.query.get('token') in camera.access_tokens)
if not authenticated:
return web.Response(status=401)
raise web.HTTPUnauthorized()
response = await self.handle(request, camera)
return response
if not camera.is_on:
_LOGGER.debug('Camera is off.')
raise web.HTTPServiceUnavailable()
return await self.handle(request, camera)
async def handle(self, request, camera):
"""Handle the camera request."""
@@ -435,7 +506,7 @@ class CameraImageView(CameraView):
return web.Response(body=image,
content_type=camera.content_type)
return web.Response(status=500)
raise web.HTTPInternalServerError()
class CameraMjpegStream(CameraView):
@@ -448,8 +519,7 @@ class CameraMjpegStream(CameraView):
"""Serve camera stream, possibly with interval."""
interval = request.query.get('interval')
if interval is None:
await camera.handle_async_mjpeg_stream(request)
return
return await camera.handle_async_mjpeg_stream(request)
try:
# Compose camera stream from stills
@@ -457,10 +527,9 @@ class CameraMjpegStream(CameraView):
if interval < MIN_STREAM_INTERVAL:
raise ValueError("Stream interval must be be > {}"
.format(MIN_STREAM_INTERVAL))
await camera.handle_async_still_stream(request, interval)
return
return await camera.handle_async_still_stream(request, interval)
except ValueError:
return web.Response(status=400)
raise web.HTTPBadRequest()
@callback

View File

@@ -64,7 +64,7 @@ class AmcrestCam(Camera):
yield from super().handle_async_mjpeg_stream(request)
return
elif self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
if self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
# stream an MJPEG image stream directly from the camera
websession = async_get_clientsession(self.hass)
streaming_url = self._camera.mjpeg_url(typeno=self._resolution)

View File

@@ -23,7 +23,7 @@ def _get_image_url(host, port, mode):
"""Set the URL to get the image."""
if mode == 'mjpeg':
return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port)
elif mode == 'single':
if mode == 'single':
return 'http://{}:{}/axis-cgi/jpg/image.cgi'.format(host, port)

View File

@@ -4,10 +4,10 @@ Demo camera platform that has a fake camera.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import os
import logging
import homeassistant.util.dt as dt_util
from homeassistant.components.camera import Camera
import os
from homeassistant.components.camera import Camera, SUPPORT_ON_OFF
_LOGGER = logging.getLogger(__name__)
@@ -16,26 +16,29 @@ async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Demo camera platform."""
async_add_devices([
DemoCamera(hass, config, 'Demo camera')
DemoCamera('Demo camera')
])
class DemoCamera(Camera):
"""The representation of a Demo camera."""
def __init__(self, hass, config, name):
def __init__(self, name):
"""Initialize demo camera component."""
super().__init__()
self._parent = hass
self._name = name
self._motion_status = False
self.is_streaming = True
self._images_index = 0
def camera_image(self):
"""Return a faked still image response."""
now = dt_util.utcnow()
self._images_index = (self._images_index + 1) % 4
image_path = os.path.join(
os.path.dirname(__file__), 'demo_{}.jpg'.format(now.second % 4))
os.path.dirname(__file__),
'demo_{}.jpg'.format(self._images_index))
_LOGGER.debug('Loading camera_image: %s', image_path)
with open(image_path, 'rb') as file:
return file.read()
@@ -46,8 +49,21 @@ class DemoCamera(Camera):
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
"""Demo camera doesn't need poll.
Need explicitly call schedule_update_ha_state() after state changed.
"""
return False
@property
def supported_features(self):
"""Camera support turn on/off features."""
return SUPPORT_ON_OFF
@property
def is_on(self):
"""Whether camera is on (streaming)."""
return self.is_streaming
@property
def motion_detection_enabled(self):
@@ -57,7 +73,19 @@ class DemoCamera(Camera):
def enable_motion_detection(self):
"""Enable the Motion detection in base station (Arm)."""
self._motion_status = True
self.schedule_update_ha_state()
def disable_motion_detection(self):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.schedule_update_ha_state()
def turn_off(self):
"""Turn off camera."""
self.is_streaming = False
self.schedule_update_ha_state()
def turn_on(self):
"""Turn on camera."""
self.is_streaming = True
self.schedule_update_ha_state()

View File

@@ -29,8 +29,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up a FFmpeg camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
@@ -49,30 +49,30 @@ class FFmpegCamera(Camera):
self._input = config.get(CONF_INPUT)
self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS)
@asyncio.coroutine
def async_camera_image(self):
async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
image = yield from asyncio.shield(ffmpeg.get_image(
image = await asyncio.shield(ffmpeg.get_image(
self._input, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
yield from stream.open_camera(
await stream.open_camera(
self._input, extra_cmd=self._extra_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
try:
return await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
finally:
await stream.close()
@property
def name(self):

View File

@@ -123,19 +123,18 @@ class MjpegCamera(Camera):
with closing(req) as response:
return extract_image_from_mjpeg(response.iter_content(102400))
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
# aiohttp don't support DigestAuth -> Fallback
if self._authentication == HTTP_DIGEST_AUTHENTICATION:
yield from super().handle_async_mjpeg_stream(request)
await super().handle_async_mjpeg_stream(request)
return
# connect to stream
websession = async_get_clientsession(self.hass)
stream_coro = websession.get(self._mjpeg_url, auth=self._auth)
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
return await async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):

View File

@@ -11,7 +11,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components import mqtt
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.helpers import config_validation as cv

View File

@@ -9,8 +9,9 @@ from datetime import timedelta
import requests
import homeassistant.components.nest as nest
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera)
from homeassistant.components import nest
from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera,
SUPPORT_ON_OFF)
from homeassistant.util.dt import utcnow
_LOGGER = logging.getLogger(__name__)
@@ -76,7 +77,36 @@ class NestCamera(Camera):
"""Return the brand of the camera."""
return NEST_BRAND
# This doesn't seem to be getting called regularly, for some reason
@property
def supported_features(self):
"""Nest Cam support turn on and off."""
return SUPPORT_ON_OFF
@property
def is_on(self):
"""Return true if on."""
return self._online and self._is_streaming
def turn_off(self):
"""Turn off camera."""
_LOGGER.debug('Turn off camera %s', self._name)
# Calling Nest API in is_streaming setter.
# device.is_streaming would not immediately change until the process
# finished in Nest Cam.
self.device.is_streaming = False
def turn_on(self):
"""Turn on camera."""
if not self._online:
_LOGGER.error('Camera %s is offline.', self._name)
return
_LOGGER.debug('Turn on camera %s', self._name)
# Calling Nest API in is_streaming setter.
# device.is_streaming would not immediately change until the process
# finished in Nest Cam.
self.device.is_streaming = True
def update(self):
"""Cache value from Python-nest."""
self._location = self.device.where

View File

@@ -105,6 +105,6 @@ class NetatmoCamera(Camera):
"""Return the camera model."""
if self._cameratype == "NOC":
return "Presence"
elif self._cameratype == "NACamera":
if self._cameratype == "NACamera":
return "Welcome"
return None

View File

@@ -2,56 +2,53 @@
Proxy camera platform that enables image processing of camera data.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/proxy
https://www.home-assistant.io/components/camera.proxy/
"""
import logging
import asyncio
import logging
import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.util.async_ import run_coroutine_threadsafe
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, HTTP_HEADER_HA_AUTH
from homeassistant.helpers import config_validation as cv
import homeassistant.util.dt as dt_util
from homeassistant.const import (
CONF_NAME, CONF_ENTITY_ID, HTTP_HEADER_HA_AUTH)
from homeassistant.components.camera import (
PLATFORM_SCHEMA, Camera)
from homeassistant.helpers.aiohttp_client import (
async_get_clientsession, async_aiohttp_proxy_web)
async_aiohttp_proxy_web, async_get_clientsession)
from homeassistant.util.async_ import run_coroutine_threadsafe
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['pillow==5.0.0']
REQUIREMENTS = ['pillow==5.2.0']
_LOGGER = logging.getLogger(__name__)
CONF_MAX_IMAGE_WIDTH = "max_image_width"
CONF_IMAGE_QUALITY = "image_quality"
CONF_IMAGE_REFRESH_RATE = "image_refresh_rate"
CONF_FORCE_RESIZE = "force_resize"
CONF_MAX_STREAM_WIDTH = "max_stream_width"
CONF_STREAM_QUALITY = "stream_quality"
CONF_CACHE_IMAGES = "cache_images"
CONF_CACHE_IMAGES = 'cache_images'
CONF_FORCE_RESIZE = 'force_resize'
CONF_IMAGE_QUALITY = 'image_quality'
CONF_IMAGE_REFRESH_RATE = 'image_refresh_rate'
CONF_MAX_IMAGE_WIDTH = 'max_image_width'
CONF_MAX_STREAM_WIDTH = 'max_stream_width'
CONF_STREAM_QUALITY = 'stream_quality'
DEFAULT_BASENAME = "Camera Proxy"
DEFAULT_QUALITY = 75
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MAX_IMAGE_WIDTH): int,
vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean,
vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean,
vol.Optional(CONF_IMAGE_QUALITY): int,
vol.Optional(CONF_IMAGE_REFRESH_RATE): float,
vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean,
vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean,
vol.Optional(CONF_MAX_IMAGE_WIDTH): int,
vol.Optional(CONF_MAX_STREAM_WIDTH): int,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_STREAM_QUALITY): int,
})
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up the Proxy camera platform."""
async_add_devices([ProxyCamera(hass, config)])
@@ -69,7 +66,7 @@ def _resize_image(image, opts):
img = Image.open(io.BytesIO(image))
imgfmt = str(img.format)
if imgfmt != 'PNG' and imgfmt != 'JPEG':
if imgfmt not in ('PNG', 'JPEG'):
_LOGGER.debug("Image is of unsupported type: %s", imgfmt)
return image
@@ -77,7 +74,7 @@ def _resize_image(image, opts):
old_size = len(image)
if old_width <= new_width:
if opts.quality is None:
_LOGGER.debug("Image is smaller-than / equal-to requested width")
_LOGGER.debug("Image is smaller-than/equal-to requested width")
return image
new_width = old_width
@@ -86,7 +83,7 @@ def _resize_image(image, opts):
img = img.resize((new_width, new_height), Image.ANTIALIAS)
imgbuf = io.BytesIO()
img.save(imgbuf, "JPEG", optimize=True, quality=quality)
img.save(imgbuf, 'JPEG', optimize=True, quality=quality)
newimage = imgbuf.getvalue()
if not opts.force_resize and len(newimage) >= old_size:
_LOGGER.debug("Using original image(%d bytes) "
@@ -94,11 +91,9 @@ def _resize_image(image, opts):
old_size, len(newimage))
return image
_LOGGER.debug("Resized image "
"from (%dx%d - %d bytes) "
"to (%dx%d - %d bytes)",
old_width, old_height, old_size,
new_width, new_height, len(newimage))
_LOGGER.debug(
"Resized image from (%dx%d - %d bytes) to (%dx%d - %d bytes)",
old_width, old_height, old_size, new_width, new_height, len(newimage))
return newimage
@@ -112,7 +107,7 @@ class ImageOpts():
self.force_resize = force_resize
def __bool__(self):
"""Bool evalution rules."""
"""Bool evaluation rules."""
return bool(self.max_width or self.quality)
@@ -133,8 +128,7 @@ class ProxyCamera(Camera):
config.get(CONF_FORCE_RESIZE))
self._stream_opts = ImageOpts(
config.get(CONF_MAX_STREAM_WIDTH),
config.get(CONF_STREAM_QUALITY),
config.get(CONF_MAX_STREAM_WIDTH), config.get(CONF_STREAM_QUALITY),
True)
self._image_refresh_rate = config.get(CONF_IMAGE_REFRESH_RATE)
@@ -145,8 +139,7 @@ class ProxyCamera(Camera):
self._last_image = None
self._headers = (
{HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password}
if self.hass.config.api.api_password is not None
else None)
if self.hass.config.api.api_password is not None else None)
def camera_image(self):
"""Return camera image."""
@@ -191,12 +184,12 @@ class ProxyCamera(Camera):
stream_coro = websession.get(url, headers=self._headers)
if not self._stream_opts:
await async_aiohttp_proxy_web(self.hass, request, stream_coro)
return
return await async_aiohttp_proxy_web(
self.hass, request, stream_coro)
response = aiohttp.web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--frameboundary')
response.content_type = (
'multipart/x-mixed-replace; boundary=--frameboundary')
await response.prepare(request)
async def write(img_bytes):
@@ -229,15 +222,10 @@ class ProxyCamera(Camera):
_resize_image, image, self._stream_opts)
await write(image)
data = data[jpg_end + 2:]
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
req.close()
response = None
raise
finally:
if response is not None:
await response.write_eof()
req.close()
return response
@property
def name(self):

View File

@@ -1,5 +1,19 @@
# Describes the format for available camera services
turn_off:
description: Turn off camera.
fields:
entity_id:
description: Entity id.
example: 'camera.living_room'
turn_on:
description: Turn on camera.
fields:
entity_id:
description: Entity id.
example: 'camera.living_room'
enable_motion_detection:
description: Enable the motion detection in a camera.
fields:

View File

@@ -171,10 +171,9 @@ class UnifiVideoCamera(Camera):
if retry:
self._login()
return _get_image(retry=False)
else:
_LOGGER.error(
"Unable to log into camera, unable to get snapshot")
raise
_LOGGER.error(
"Unable to log into camera, unable to get snapshot")
raise
return _get_image()

View File

@@ -66,8 +66,7 @@ class VerisureSmartcam(Camera):
if not image_ids:
return
new_image_id = image_ids[0]
if (new_image_id == '-1' or
self._image_id == new_image_id):
if new_image_id in ('-1', self._image_id):
_LOGGER.debug("The image is the same, or loading image_id")
return
_LOGGER.debug("Download new image %s", new_image_id)

View File

@@ -57,6 +57,7 @@ class YiCamera(Camera):
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config[CONF_NAME]
self._is_on = True
self.host = config[CONF_HOST]
self.port = config[CONF_PORT]
self.path = config[CONF_PATH]
@@ -68,6 +69,11 @@ class YiCamera(Camera):
"""Camera brand."""
return DEFAULT_BRAND
@property
def is_on(self):
"""Determine whether the camera is on."""
return self._is_on
@property
def name(self):
"""Return the name of this camera."""
@@ -81,7 +87,7 @@ class YiCamera(Camera):
try:
await ftp.connect(self.host)
await ftp.login(self.user, self.passwd)
except StatusCodeError as err:
except (ConnectionRefusedError, StatusCodeError) as err:
raise PlatformNotReady(err)
try:
@@ -101,12 +107,13 @@ class YiCamera(Camera):
return None
await ftp.quit()
self._is_on = True
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1])
except (ConnectionRefusedError, StatusCodeError) as err:
_LOGGER.error('Error while fetching video: %s', err)
self._is_on = False
return None
async def async_camera_image(self):
@@ -114,7 +121,7 @@ class YiCamera(Camera):
from haffmpeg import ImageFrame, IMAGE_JPEG
url = await self._get_latest_video_url()
if url != self._last_url:
if url and url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = await asyncio.shield(
ffmpeg.get_image(
@@ -130,6 +137,9 @@ class YiCamera(Camera):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
if not self._is_on:
return
stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop)
await stream.open_camera(
self._last_url, extra_cmd=self._extra_arguments)

View File

@@ -12,7 +12,7 @@ from homeassistant.const import CONF_NAME
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera)
import homeassistant.components.zoneminder as zoneminder
from homeassistant.components import zoneminder
_LOGGER = logging.getLogger(__name__)

View File

@@ -65,7 +65,7 @@ def setup(hass, config):
return True
class CanaryData(object):
class CanaryData:
"""Get the latest data and update the states."""
def __init__(self, username, password, timeout):

View File

@@ -1,14 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Keine Google Cast Ger\u00e4te im Netzwerk gefunden."
"no_devices_found": "Keine Google Cast Ger\u00e4te im Netzwerk gefunden.",
"single_instance_allowed": "Nur eine einzige Konfiguration von Google Cast ist notwendig."
},
"step": {
"confirm": {
"description": "M\u00f6chten Sie Google Cast einrichten?",
"title": ""
"title": "Google Cast"
}
},
"title": ""
"title": "Google Cast"
}
}

View File

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

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