Compare commits

...

160 Commits

Author SHA1 Message Date
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
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
Paulus Schoutsen
8d2359026c Bump frontend to 20180803.0 2018-08-03 13:48:48 +02:00
Paulus Schoutsen
a5112f317d Update frontend to 20180802.0 2018-08-02 14:23:56 +02:00
Paulus Schoutsen
3ed47b05a5 Update translations 2018-08-02 13:43:36 +02:00
Paulus Schoutsen
163cd72b7a Bumped version to 0.75.0b1 2018-08-02 12:23:36 +02: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
Paulus Schoutsen
5e9c1098c0 Merge pull request #15651 from home-assistant/rc
0.74.1
2018-07-24 13:43:25 +02:00
Paulus Schoutsen
d65bd7b7ea Bumped version to 0.74.1 2018-07-24 11:20:13 +02:00
Paulus Schoutsen
45a5ae1f23 Cast/Sonos: create config entry if manually configured (#15630)
* Cast/Sonos: create config entry if manually configured

* Add test for helper
2018-07-24 11:20:07 +02:00
Jason Hu
3eda6db227 Frontend component should auto load auth coomponent (#15606) 2018-07-24 11:20:07 +02:00
Anders Melchiorsen
58f287f551 Use case insensitive comparison for Sonos model check (#15604) 2018-07-24 11:20:07 +02:00
cdce8p
f62f64311d Bugfix HomeKit name and serial_number (#15600)
* Bugfix HomeKit name and serial_number

* Revert serial_number changes
2018-07-24 11:20:06 +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
Paulus Schoutsen
da3366859d Merge pull request #15570 from home-assistant/rc
0.74
2018-07-20 15:11:18 +02:00
Teemu R
200c0a8778 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:38 +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
8e659baf25 Bumped version to 0.74.0 2018-07-20 12:44:15 +02:00
Jason Hu
2aa54ce22b Reset failed login attempts counter when login success (#15564) 2018-07-20 12:33:21 +02:00
Paulus Schoutsen
eff334a1d0 Remove relative time from state machine (#15560) 2018-07-20 12:32:45 +02:00
Paulus Schoutsen
b3bed7fb37 Allow auth providers to influence is_active (#15557)
* Allow auth providers to influence is_active

* Fix auth script test
2018-07-20 12:32:44 +02:00
Martin Hjelmare
61b3822374 Upgrade pymysensors to 0.16.0 (#15554) 2018-07-20 12:32:44 +02:00
Paulus Schoutsen
9fb04b5280 Update the frontend to 20180720.0 2018-07-20 12:30:32 +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
7aa2a9e506 Bumped version to 0.74.0b4 2018-07-19 12:26:15 +02:00
Paulus Schoutsen
2fc0d83085 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 12:26:08 +02:00
Paulus Schoutsen
ca0d4226aa Decouple emulated hue from http server (#15530) 2018-07-19 12:25:47 +02:00
Paulus Schoutsen
dff2e4ebc2 Don't be so strict client-side (#15546) 2018-07-19 12:23:14 +02:00
Jerad Meisner
9c337bc621 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 12:23:14 +02:00
Paulus Schoutsen
5a1360678b Bump frontend to 20180719.0 2018-07-19 10:55:33 +02: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
7b8ad64ba5 Bumped version to 0.74.0b3 2018-07-18 17:41:36 +02:00
Jason Hu
e64761b15e 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-18 17:41:22 +02:00
Paulus Schoutsen
61273ff606 Bump frontend to 20180718.0 2018-07-18 17:34:28 +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
5dc29bd2c3 Bumped version to 0.74.0b2 2018-07-17 10:59:07 +02:00
Paulus Schoutsen
20c316bce4 Bump frontend to 20180717.0 2018-07-17 10:58:58 +02:00
Matthew Garrett
8b475f45e9 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:58:51 +02:00
Paulus Schoutsen
a4318682f7 Add onboarding support (#15492)
* Add onboarding support

* Lint

* Address comments

* Mark user step as done if owner user already created
2018-07-17 10:58:51 +02:00
Paulus Schoutsen
a14d8057ed Add current user WS command (#15485) 2018-07-17 10:58:50 +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
55f8b0a2f5 Bumped version to 0.74.0b1 2018-07-16 22:14:51 +02:00
Paulus Schoutsen
bb37300a48 Merge branch 'master' into rc 2018-07-16 22:14:45 +02:00
Paulus Schoutsen
0f12b37977 0.73.2 - security release (#15494)
* Extract SSL context creation to helper (#15483)

* Extract SSL context creation to helper

* Lint

* Bumped version to 0.73.2
2018-07-16 22:13:12 +02: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
718 changed files with 7852 additions and 2526 deletions

View File

@@ -251,6 +251,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 +349,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 +404,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
@@ -461,6 +469,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
@@ -657,6 +666,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

@@ -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

@@ -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

@@ -124,6 +124,7 @@ class AuthManager:
return await self._store.async_create_user(
credentials=credentials,
name=info.get('name'),
is_active=info.get('is_active', False)
)
async def async_link_user(self, user, credentials):
@@ -194,9 +195,14 @@ class AuthManager:
tkn = self._access_tokens.get(token)
if tkn is None:
_LOGGER.debug('Attempt to get non-existing access token')
return None
if tkn.expired or not tkn.refresh_token.user.is_active:
if tkn.expired:
_LOGGER.debug('Attempt to get expired access token')
else:
_LOGGER.debug('Attempt to get access token for inactive user')
self._access_tokens.pop(token)
return None

View File

@@ -135,5 +135,9 @@ class AuthProvider:
"""Return extra user metadata for credentials.
Will be used to populate info when creating a new user.
Values to populate:
- name: string
- is_active: boolean
"""
return {}

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
@@ -150,6 +152,9 @@ class HassAuthProvider(AuthProvider):
async def async_initialize(self):
"""Initialize the auth provider."""
if self.data is not None:
return
self.data = Data(self.hass)
await self.data.async_load()
@@ -157,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()
@@ -181,7 +186,8 @@ class HassAuthProvider(AuthProvider):
async def async_user_meta_for_credentials(self, credentials):
"""Get extra info for this credential."""
return {
'name': credentials.data['username']
'name': credentials.data['username'],
'is_active': True,
}
async def async_will_remove_credentials(self, credentials):
@@ -221,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

@@ -75,14 +75,16 @@ class ExampleAuthProvider(AuthProvider):
Will be used to populate info when creating a new user.
"""
username = credentials.data['username']
info = {
'is_active': True,
}
for user in self.config['users']:
if user['username'] == username:
return {
'name': user.get('name')
}
info['name'] = user.get('name')
break
return {}
return info
class LoginFlow(data_entry_flow.FlowHandler):

View File

@@ -70,7 +70,10 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
Will be used to populate info when creating a new user.
"""
return {'name': LEGACY_USER}
return {
'name': LEGACY_USER,
'is_active': True,
}
class LoginFlow(data_entry_flow.FlowHandler):

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

@@ -167,7 +167,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 +183,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

@@ -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,26 +45,29 @@ 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.http.view import HomeAssistantView
from homeassistant.components import websocket_api
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']
WS_TYPE_CURRENT_USER = 'auth/current_user'
SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_CURRENT_USER,
})
_LOGGER = logging.getLogger(__name__)
@@ -129,116 +75,51 @@ 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))
hass.components.websocket_api.async_register_command(
WS_TYPE_CURRENT_USER, websocket_current_user,
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."""
url = '/auth/token'
name = 'api:auth:token'
requires_auth = False
cors_allowed = True
def __init__(self, retrieve_credentials):
"""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({
@@ -246,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:
@@ -296,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:
@@ -307,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({
@@ -383,3 +261,23 @@ def _create_cred_store():
return None
return store_credentials, retrieve_credentials
@callback
def websocket_current_user(hass, connection, msg):
"""Return the current user."""
user = connection.request.get('hass_user')
if user is None:
connection.to_write.put_nowait(websocket_api.error_message(
msg['id'], 'no_user', 'Not authenticated as a user'))
return
connection.to_write.put_nowait(websocket_api.result_message(msg['id'], {
'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

@@ -0,0 +1,172 @@
"""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 homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
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])
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,
}))
@log_invalid_auth
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))
@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(self._prepare_result_json(result))
result.pop('data')
result['result'] = self._store_credentials(client_id, result['result'])
return self.json(result)

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

@@ -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

@@ -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

@@ -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

@@ -129,9 +129,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

@@ -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

@@ -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

@@ -26,6 +26,9 @@ CONF_PROJECT_DUE_DATE = 'due_date_days'
CONF_PROJECT_LABEL_WHITELIST = 'labels'
CONF_PROJECT_WHITELIST = 'include_projects'
# https://github.com/PyCQA/pylint/pull/2320
# pylint: disable=fixme
# Calendar Platform: Does this calendar event last all day?
ALL_DAY = 'all_day'
# Attribute: All tasks in this project
@@ -280,7 +283,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 +506,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

@@ -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"
}
}

View File

@@ -0,0 +1,14 @@
{
"config": {
"abort": {
"no_devices_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u4e0a\u306bGoogle Cast\u30c7\u30d0\u30a4\u30b9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002"
},
"step": {
"confirm": {
"description": "Google Cast\u3092\u30bb\u30c3\u30c8\u30a2\u30c3\u30d7\u3057\u307e\u3059\u304b\uff1f",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"no_devices_found": "Googgle Cast \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "Google Cast\uc758 \ub2e8\uc77c \uad6c\uc131 \ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
"single_instance_allowed": "\ud558\ub098\uc758 Google Cast \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
},
"step": {
"confirm": {

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Nenhum dispositivo Google Cast encontrado na rede.",
"single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do Google Cast \u00e9 necess\u00e1ria."
},
"step": {
"confirm": {
"description": "Deseja configurar o Google Cast?",
"title": ""
}
},
"title": ""
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Nenhum dispositivo Google Cast descoberto na rede.",
"single_instance_allowed": "Apenas uma \u00fanica configura\u00e7\u00e3o do Google Cast \u00e9 necess\u00e1ria."
},
"step": {
"confirm": {
"description": "Deseja configurar o Google Cast?",
"title": ""
}
},
"title": ""
}
}

View File

@@ -2,7 +2,7 @@
"config": {
"abort": {
"no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 Google Cast \u8bbe\u5907\u3002",
"single_instance_allowed": "\u53ea\u6709\u4e00\u6b21 Google Cast \u914d\u7f6e\u662f\u5fc5\u8981\u7684\u3002"
"single_instance_allowed": "Google Cast \u53ea\u9700\u8981\u914d\u7f6e\u4e00\u6b21\u3002"
},
"step": {
"confirm": {

View File

@@ -1,4 +1,5 @@
"""Component to embed Google Cast."""
from homeassistant import data_entry_flow
from homeassistant.helpers import config_entry_flow
@@ -8,13 +9,20 @@ REQUIREMENTS = ['pychromecast==2.1.0']
async def async_setup(hass, config):
"""Set up the Cast component."""
hass.data[DOMAIN] = config.get(DOMAIN, {})
conf = config.get(DOMAIN)
hass.data[DOMAIN] = conf or {}
if conf is not None:
hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, source=data_entry_flow.SOURCE_IMPORT))
return True
async def async_setup_entry(hass, entry):
"""Set up Cast from a config entry."""
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, 'media_player'))
return True
@@ -23,7 +31,7 @@ async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pychromecast.discovery import discover_chromecasts
return await hass.async_add_job(discover_chromecasts)
return await hass.async_add_executor_job(discover_chromecasts)
config_entry_flow.register_discovery_flow(

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