Compare commits

..

604 Commits

Author SHA1 Message Date
Paulus Schoutsen
370c3f28b8 Merge pull request #15088 from home-assistant/rc
0.72
2018-06-22 13:38:44 -04:00
Paulus Schoutsen
66110a7d57 Bump frontend to 20180622.1 2018-06-22 12:48:01 -04:00
Paulus Schoutsen
a02d7989d5 Use older syntax for version bump 2018-06-22 11:07:26 -04:00
Paulus Schoutsen
7325847fa9 Bumped version to 0.72.0 2018-06-22 10:24:45 -04:00
Paulus Schoutsen
124495dd84 Update frontend to 20180622.0 2018-06-22 10:24:38 -04:00
Paulus Schoutsen
0ea2d99910 Bumped version to 0.72.0b9 2018-06-21 17:39:02 -04:00
Paulus Schoutsen
6456f66b47 Frontend bump to 20180621.2 2018-06-21 17:38:57 -04:00
Paulus Schoutsen
6e5a2a77ab Bumped version to 0.72.0b8 2018-06-21 17:27:08 -04:00
Paulus Schoutsen
35b609dd8b Allow writing commit with version bump 2018-06-21 17:27:01 -04:00
Paulus Schoutsen
0df99f8762 Bump frontend to 20180621.1 2018-06-21 17:22:08 -04:00
Paulus Schoutsen
a4b843eb2d Version bump to 0.72.0b7 2018-06-21 15:02:29 -04:00
Daniel Shokouhi
302717e8a1 Update Neato Library And Reduce Cloud Calls (#15072)
* Update Neato library to 0.0.6 and reduce the amount of calls to the cloud

* Remove file commited in error

* Lint
2018-06-21 15:02:13 -04:00
Bob Clough
617647c5fd Fix MQTT Light with RGB and Brightness (#15053)
* Fix MQTT Light with RGB and Brightness

When an MQTT light is given an RGB and Brightness topic, the RGB
is scaled by the brightness *as well* as the brightness being set

This causes 255,0,0 at 50% brightness to be sent as 127,0,0 at 50%
brightness, which ends up as 63,0,0 after the RGB bulb has applied
its brightness scaling.

Fixes the same issue in mqtt, mqtt-json and mqtt-template.

Related Issue: #13725

* Add comment to mqtt_json as well
2018-06-21 15:00:06 -04:00
Tom Harris
4b5d578c08 X10 (#14741)
* Implement X10

* Add X10 after add_device_callback

* Ref device by id not hex and add x10OnOffSwitch name

* X10 services and add sensor device

* Correctly reference X10_HOUSECODE_SCHEMA

* Log adding of X10 devices

* Add X10 All Units Off, All Lights On and All Lights Off devices

* Correct ref to X10 states vs devices

* Add X10 All Units Off, All Lights On and All Lights Off devices

* Correct X10 config

* Debug x10 device additions

* Config x10 from bool to housecode char

* Pass PLM to X10 device create

* Remove PLM to call to add_x10_device

* Unconfuse x10 config and method names

* Correct spelling of x10_all_lights_off_housecode

* Bump insteonplm to 0.10.0 to support X10
2018-06-21 15:00:06 -04:00
Paulus Schoutsen
e98e7e2751 Update frontend to 20180621.0 2018-06-21 14:57:19 -04:00
Paulus Schoutsen
c84f1d7d33 Version bump to 0.72.0b6 2018-06-20 15:13:33 -04:00
Paulus Schoutsen
49845d9398 Rename experimental UI to lovelace (#15065)
* Rename experimental UI to lovelace

* Bump frontend to 20180620.0
2018-06-20 15:13:22 -04:00
Paulus Schoutsen
659616a4eb Version bump to 0.72.0b5 2018-06-19 10:58:57 -04:00
Paulus Schoutsen
3b4f7b4f5d Update frontend to 20180619.0 2018-06-19 10:56:44 -04:00
Paulus Schoutsen
9800b74a6d Version bump to 0.72.0b4 2018-06-18 10:00:47 -04:00
Paulus Schoutsen
ef5b2a2492 Version bump to 0.72.0b3 2018-06-18 10:00:24 -04:00
Pascal Vizeli
60179a1cbb Bugfix empty entity lists (#15035)
* Bugfix empty entity lists

* Add tests

* Update test_entity_platform.py

* Update entity_platform.py
2018-06-18 09:59:58 -04:00
Paulus Schoutsen
e0cea2d18d Make zone entries work without radius (#15032) 2018-06-18 09:59:58 -04:00
Fabian Affolter
e29dfa8609 Upgrade aiohttp to 3.3.2 (#15025) 2018-06-18 09:59:57 -04:00
Martin Hjelmare
ef39bca52e Fix linode I/O in state property (#15010)
* Fix linode I/O in state property

* Move update of all attrs to update
2018-06-18 09:58:58 -04:00
Paulus Schoutsen
5a3ea74a26 Bump frontend to 20180618.0 2018-06-18 09:58:35 -04:00
Pascal Vizeli
86c6b4d8e3 Fix panel URL authentication for Hass.io (#15024)
* Update http.py

* Update http.py

* fix tests

* Update test_http.py
2018-06-18 07:26:41 +02:00
Paulus Schoutsen
1642502a70 Update translations 2018-06-17 23:05:10 -04:00
Pascal Vizeli
da3695dccc Update test_http.py 2018-06-17 20:37:46 +02:00
Paulus Schoutsen
471d6e45eb Version bump to 0.72.0b2 2018-06-16 22:37:13 -04:00
Paulus Schoutsen
7238205adb Frontend bump to 20180617.0 2018-06-16 22:36:51 -04:00
Paulus Schoutsen
65970a2248 Version bump to 0.72.0b1 2018-06-16 17:36:35 -04:00
Paulus Schoutsen
5d82f48c02 Add experimental UI backend (#15002)
* Add experimental UI

* Add test

* Lint
2018-06-16 17:36:10 -04:00
Teemu R
8e185bc300 Bump pyhs100 version (#15001)
Fixes #13925
2018-06-16 17:36:10 -04:00
Andrey
a013908115 Switch to own packaged version of spotipy (#14997) 2018-06-16 17:36:09 -04:00
Sebastian Muszynski
bdf6257640 Remove load power attribute for channel USB (#14996)
* Remove load power attribute for channel USB

* Fix format
2018-06-16 17:36:09 -04:00
Paulus Schoutsen
1f50e335fa Bump frontend to 20180616.0 2018-06-16 17:35:52 -04:00
Paulus Schoutsen
87f9f17335 Version bump to 0.72.0b0 2018-06-16 10:51:07 -04:00
Paulus Schoutsen
f101f6b7cb Merge remote-tracking branch 'origin/master' into dev 2018-06-16 10:50:43 -04:00
Jason Hu
abf07b60f0 Refactoring camera component to use async/await syntax. (#14990)
* Refactoring camera component to use async/await syntax

Also updated camera demo platform to encourage use of async

* Code review
2018-06-16 10:49:11 -04:00
Paulus Schoutsen
0b114f0755 Do not mount deps folder when running in virtual env (#14993)
* Do not mount deps folder when inside virtual env

* Add tests

* Fix package test
2018-06-16 10:48:41 -04:00
Marcelo Moreira de Mello
3ee8f58fdf Upgraded RainCloudy to version 0.0.5 (#14986) 2018-06-16 06:58:18 -04:00
Marcelo Moreira de Mello
ff4da05267 Upgraded python-amcrest to 1.2.3 (#14988) 2018-06-16 06:57:58 -04:00
Marcelo Moreira de Mello
17308a2730 Upgraded PyArlo to 0.1.7 (#14987) 2018-06-16 06:57:27 -04:00
cdce8p
7d9bce2153 Fix extended package support (#14980)
* Fix package recurive merge bug

* Fixed extended package support
2018-06-16 06:55:32 -04:00
Marcelo Moreira de Mello
2839f0ff5f Upgrade ring_doorbell to 0.2.1 to fix oauth issues (#14984)
* Upgraded to ring_doorbell to 0.2.1 to fix oauth issues

* Updated unittest to cover Ring oauth
2018-06-16 08:58:39 +02:00
Johan Bloemberg
2ec295a6f8 Add availability to Rflink entities. (#14977) 2018-06-16 00:26:48 +02:00
Ville Skyttä
4bd7a7eee3 Remove inline pylint disables for messages disabled in pylintrc (#14978) 2018-06-16 00:15:46 +02:00
c727
d0cbbe6141 Return ISO formated datetime in forecast (#14975)
* Return ISO formated datetime in forecast

* Lint
2018-06-15 17:09:01 -04:00
John Mihalic
9efa31ef9f Eight Sleep add REM type, Update async syntax, Catch API quirks (#14937) 2018-06-15 15:24:09 -04:00
Paulus Schoutsen
8a777f6e78 Show notification when user configures Nest client_id/secret (#14970)
* Show notification when user configures Nest client_id/secret

* Lint
2018-06-15 15:19:58 -04:00
Robert Svensson
ac13a2736b Deconz make groups configurable (#14704)
* Make groups configurable

* Config flow and tests in place

* Fix too long line
2018-06-15 14:31:22 -04:00
Albert Lee
940577e105 Fix binary_sensor.skybell state update when there are no events (#14927) 2018-06-15 14:30:35 -04:00
Sriram Vaidyanathan
c917470836 Xiaomi Cameras - multiple models (#14244)
* Added support for Xiaofang Camera

* Added entry for Xiaofang 1080p camera

* Code fix

* Minor comment fix

* Updated coveragerc for Xiaomi cameras

* Added Xiaomi Camera

Added Xiaomi Camera to accommodate multiple models like Yi, Xiaofang, etc.

* Minor code fix

* Minor code fix

* Added model property

* Update xiaomi.py

* Minor code fix

* Update xiaomi.py

* Update xiaomi.py

* Minor code fix

* Package requirement fix due to Version conflict

* To fix conflicts

* Update package_constraints.txt

* Minor fix

* Update xiaomi.py

* Update xiaomi.py

Changes made per comment

* Update xiaomi.py

* Don't update on add.
2018-06-15 14:27:52 -04:00
Paulus Schoutsen
47a344f3a1 Bump frontend to 20180615.0 2018-06-15 13:46:31 -04:00
Paulus Schoutsen
f744a29d9d Add calendar panel, add tests (#14973) 2018-06-15 13:37:46 -04:00
Thibault Cohen
3cd4cb741c Add Calendar API endpoint to get events (#14702)
* Add Calendar API endpoint to get events

* Set default event color

* Fix PR comments

* Fix PR comments

* Fix PR comments

* Remote local.py file

* Use iso 8601

* Fix lint

* Fix PR comments

* Fix PR comments

* Add Support for todoist and demo calendar

* Todoist events are allday events

* Add calendar demo api endpoint test

* Register only one api endpoint for calendar

* Rename demo calendar
2018-06-15 11:16:31 -04:00
Paulus Schoutsen
1128104281 Adhere to scan_interval in platforms when setup via config entry (#14969) 2018-06-15 16:59:13 +02:00
Aaron Bach
d6d685a483 Fix smappee component - "Error on device update" (#14883) 2018-06-14 19:35:53 -06:00
Paulus Schoutsen
2c6e6c2a6f Add config entry for Sonos + Cast (#14955)
* Add config entry for Sonos

* Lint

* Use add_job

* Add Cast config entry

* Lint

* Rename DOMAIN import

* Mock pychromecast in test
2018-06-14 15:17:54 -04:00
Benedict Aas
c8e0de19b6 add relative time option to simulated sensors (#14038)
By default simulated sensors are relative to when they're activated,
instead we make this togglable with this new option 'relative_to_epoch',
and instead they become relative to 1970-01-01 00:00:00.
2018-06-14 14:06:49 -04:00
Paulus Schoutsen
b2440a6d95 Fix tests (#14959)
* Fix tests

* Lint
2018-06-14 11:57:09 -04:00
ruohan.chen
c36c3f0d64 Add support for ZhongHong HVAC Controllers (#14552)
* first blood for ZhongHong HVAC Controller

* add requirements

* requirements_all.txt updated

* add zhong_hong.py to coveragerc

* add comments

* unique_id add platform name

* zhong_hong_hvac version bump to 1.0.1

* improve some coding style to match the project standard

* zhong_hong_hvac version bump to 1.0.4

* zhong_hong_hvac version require 1.0.7

* update requirements by script/gen_requirements_all.py

* zhong_hong_hvac version bump to 1.0.8

* fix startup problem

* remove unused import

* zhong_hong_hvac version bump to 1.0.9

- operation_mode: cold -> cool

* start hub listen event when all climate entities is ready

* use dispatcher to setup hub

* var name change

SIGNAL_DEVICE_SETTED_UP -> SIGNAL_DEVICE_ADDED

* async problem fix

* bugfix: set_operation_mode forget to use upper case

* stringify the exception instead of print full stack of traceback

* avoid to call str(exception) explicity

* remove unnecessary try...except clause

* remove unused import
2018-06-14 09:47:17 -04:00
Aaron Bach
0e7d284c83 Make AirVisual platform async + other adjustments (#14943)
* Changes complete

* Updated requirements

* Add support for scan_interval

* Small style update

* Owner-requested changes
2018-06-14 09:30:47 -04:00
Nick Whyte
cdd111df49 Add sensor.nsw_fuel_station component (#14757)
* Add sensor.nsw_fuel_station component

* bump dependency

* PR Changes

* flake8

* Use MockPrice

* Fix requirements

* Fix tests

* line length

* wip

* Handle errors and show persistent notification

* update tests

* Address @MartinHjelmare's comments

* Fetch station name from API

* Update tests

* Update requirements

* Address comments
2018-06-14 13:56:04 +02:00
Robin
cccd0deb65 Fix Facebox face data parsing (#14951)
* Adds parse_faces

* Update facebox.py
2018-06-13 21:02:46 +02:00
Paulus Schoutsen
e014a84215 Nest config flow (#14921)
* Move nest to dir based component

* Add config flow for Nest

* Load Nest platforms via config entry

* Add tests for Nest config flow

* Import existing access tokens as config entries

* Lint

* Update coverage

* Update translation

* Fix tests

* Address strings

* Use python-nest token resolution

* Lint

* Do not do I/O inside constructor

* Lint

* Update test requirements
2018-06-13 11:14:52 -04:00
Aaron Bach
d549e26a9b Make Yi platform async (#14944)
* Conversion complete

* Updated requirements

* Got rid of 3.6-specific syntax

* Removed more 3.6-specific syntax

* Contributor-requested changes
2018-06-13 11:00:33 -04:00
Marius
08adfd87f7 Add unique_id for mqtt binary sensor (#14929)
* Added unique_id for mqtt binary sensor

* Added missing mqtt message fire in test
2018-06-13 16:20:38 +02:00
Vignesh Venkat
65b0ec6615 Update python-wink to 1.8.0 (#14894)
* wink: Update to python-wink 1.8.0

This pulls in a patch to expose the GE Z-Wave in wall fan switch
as a fan component instead of a light dimmer switch component.

* Update requirements_all.txt
2018-06-13 07:09:42 -04:00
Ville Skyttä
cb646e48d0 Upgrade pylint to 1.9.2 (#14916) 2018-06-13 07:08:39 -04:00
ArrayLabs
fecce206a9 Myq update from 0.0.8 to 0.0.11 (#14947)
* Update requirements_all.txt

Update myq from 0.0.8 to 0.0.11

* Update myq.py

Update myq from 0.0.8 to 0.0.11
2018-06-13 08:02:27 +02:00
Aaron Bach
176ef411de Add scan_interval to RainMachine (#14945) 2018-06-13 07:30:06 +02:00
Pawel
2ac23c8be6 Epson projector support (#14841)
* Epson projector support. Version based on external library

* Epson projector support. Version based on external library

* modified epson according to MartinHjelmare review.
Added description of cmode to services.yaml

* renamed EPSON_SCHEMA to epson_schema

* removed method of getting cmode property

* removed unnecessary checks
change name of cmode service

* renamed SERVICE_ATTR_CMODE to SERVICE_SELECT_CMODE
2018-06-13 07:28:59 +02:00
Hate-Usernames
a373793029 pytradfri 5.5.1: Improved 3rd party bulb support (#14887)
* Bump pytradfri version

* Update light component

* Add tests

* lint

* Docstring typos

* Blank line

* lint

* 5.5.1

* Fix tests on py3.5
2018-06-13 07:17:52 +02:00
Paulus Schoutsen
3153b0c8fc Bump frontend to 20180613.0 2018-06-12 21:20:23 -04:00
Ing. Jaroslav Šafka
89d008d1f3 Fix snapcast uuid to be more unique (#14925)
Current uuid is ok when using only 1 snapserver
New uuid is needed when using multiple snapserver

Because the client can connect to more snapservers and
then uuid based on client MAC is not enough
2018-06-12 15:46:53 +02:00
Christoph Gerneth
6755ae2605 Add support for KIWI Door Locks (#14485)
* initial commit for kiwi door locks

bugfixes

improved attribute display

flake8

more style adjustments

* added session handling

flake8

* added requirements_all

reordered imports and flake8

attempt to pelase a very picky linter

also pleasing pylint now :)

* re-try the build

* added kiwi.py to .coveragerc

* reorganized datetime handling and attribute naming

* created pypi package for door lock library

* updated requirements_all.txt

* code review changes

* added async lock state reset for locking state

* refactored lat/lon attribute updates

* initial locked state changed from undefined to locked

* refactored is_locked property check

* handling authentication exception in setup_platform

* added more check in setup_platform

* code review changes: return type in setup_platform

* fixed logging issue

* event handling in main thread

* updated kiwiki-client to version 0.1.1

* renamed alias e to exc
2018-06-12 12:36:02 +02:00
Jason Hu
c18033ba85 Use cv.time_period instead of cv.time_period_str (#14938) 2018-06-12 09:32:13 +02:00
Marcelo Moreira de Mello
cdc5388dc9 Refactored Arlo component and enhanced Arlo API queries and times (#14823)
* start arlo refactoring

* Refactored Arlo Hub to avoid uncessary and duplicated GETs to Arlo API

* Refactored Arlo camera component to avoid duplicate queries

* Added debug and error messages when video is not found

* Transformed Arlo Control Panel to Sync

* Makes linter happy

* Uses total_seconds() for scan_interval

* Added callback and fixed scan_interval issue

* Disable multiple tries and supported custom modes set in Arlo

* Bump PyArlo version to 0.1.4

* Makes lint happy

* Removed ArloHub object and added some tweaks

* Fixed hub_refresh method

* Makes lint happy

* Ajusted async syntax and added callbacks decorators

* Bump PyArlo version to 0.1.6 to include some enhacements

* Refined code
2018-06-12 08:01:26 +02:00
Ong Vairoj
be4776d039 Add more test cases for samsungtv (#14900)
More test cases to cover retry logic added in 58a1c3839
2018-06-12 07:33:21 +02:00
Jason Hu
30111ea417 Upgrade python-nest, add security_state sensor, nest.set_mode service set ETA as well (#14901) 2018-06-12 07:28:16 +02:00
Erik Eriksson
576c806e86 Update mqtt_eventstream.py (#14923)
* Update mqtt_eventstream.py

Remove a line setting an internal state mqtt_eventstream.initialized to True since:
1. No other platform is doing this
2. This will create an annoying entity/item in the user interface which the user will have to explicitly hide

* Update mqtt_eventstream.py
2018-06-11 15:29:04 +02:00
Matt Snyder
1c561eaf0d Add support for multiple Doorbird stations (#13994) 2018-06-10 19:02:44 +02:00
Ben Lebherz
1da30032a0 Add support for the Unitymedia Horizon HD Recorder (#14275)
* added new platform for the Unitymedia Horizon HD Recorder

* improve connection handling of the horizon platform

* remove unneeded parameters and fix spelling in the horizon platform

* abort or raise exception if connection to the device could not be established

* remove channel/source list and SELECT_SOURCE feature

* remove useless type check after cast and use a try block instead

* abort or raise exception if reconnect to device fails

* remove protocol specific code and restructure sending logic accordingly

* fix indentation to be pep8 complaint

* remove unused methods/properties

* fix unnecessary pylint commands and use a return to abert outside of setup_platform

* directly access config values
2018-06-10 15:38:55 +02:00
Nate Clark
d5bbb6ffd2 Add api_host option to Konnected config (#14896) 2018-06-10 13:50:25 +02:00
Dan Klaffenbach
b4e5695bbd Bump to denonavr 0.7.3 (#14907)
Closes #14792

See #14794
2018-06-10 13:00:14 +02:00
Yevgeniy
716ab0433f Added daily and hourly modes to Openweathermap (#14875)
* Added daily and hourly modes

Added wind speed and bearing to forecast

* Fix mixed spaces and tabs

* Fix lint

* Fix pylint

* Revert one attribution, order alphabetically
2018-06-10 12:35:10 +02:00
Fabian Affolter
7d9ef97bda Upgrade pylast to 2.3.0 (#14888) 2018-06-10 11:38:35 +02:00
Fabian Affolter
703b4354e0 Upgrade python-mystrom to 0.4.4 (#14889) 2018-06-10 11:38:23 +02:00
Fabian Affolter
ce0ca7ff90 Upgrade sendgrid to 5.4.0 (#14891) 2018-06-10 11:38:11 +02:00
Fabian Affolter
54e87836f6 Upgrade psutil to 5.4.6 (#14892) 2018-06-10 11:37:58 +02:00
Fabian Affolter
5f4aa6d2ba Upgrade python_opendata_transport to 0.1.3 (#14905) 2018-06-10 11:37:44 +02:00
Fabian Affolter
dc447a75c6 Upgrade pyuptimerobot to 0.0.5 2018-06-10 10:58:45 +02:00
Joakim Sørensen
ce7e9e36dd Add Uptime Robot sensor (#14631)
* Added Uptime Robot sensor

* added newline at the end and corrected doclink

* Added changes form @cdce8p

* Convert to binary_sensor

* updated requirements

* moved to correct dir

* Update uptimerobot.py
2018-06-10 10:28:53 +02:00
Aaron Bach
8aca2e84dc Make RainMachine async (#14879)
* Make RainMachine async

* Updated requirements

* Dispatcher adjustments

* Small verbiage change

* Member-requested changes

* Style consistency

* Updated requirements
2018-06-10 10:23:07 +02:00
Fabian Affolter
f3e55ce330 Allow different identifiers for the CPU temperature (fixes #10104) (#14898) 2018-06-10 08:39:48 +02:00
Ing. Jaroslav Šafka
20caeb5383 Add entity registry support to media_player.snapcast (#14895)
Unique id for client is generated from prefix 'snapcast_client_'
and MAC address

Unique id for group is generated from prefix 'snapcast_group_'
and UUID provided by snapcast library
2018-06-10 08:31:42 +02:00
hanzoh
bc0d0751b9 Add missing mapping of RotaryHandleSensorIP states (#14885) 2018-06-09 16:12:42 +02:00
John Arild Berentsen
5393b073fe Discover Qubino ZMHTDx smart meter switches (#14884) 2018-06-09 15:34:36 +02:00
Ong Vairoj
d7b7370c82 Samsung TV can't turn off after idle period (#14587)
When Samsung TV is idle for a period of time after issued a command,
subsequent 'turn_off' command won't turn off the TV. The issue is seen
in Samsung models with websocket as discussed in #12302.

== Reproducible Steps
1. Turn on TV (either via HA or directly).
2. Issue some commands e.g. volume ups / downs.
3. Wait for ~1 minute.
4. Issue turn_off command via HA. TV won't turn off.
5. Issue subsequent turn off commands won't turn off TV still.
6. However, issue some other commands e.g. volume ups / downs multiple
times in a row and then turn_off will turn off the TV.

== Root Cause
The underlying websocket connection opened by samsungctl get closed
after some idle time. There was no retry mechanism so issued commands
would intermittently fail but the subsequent one would succeed when
`_remote` get recreated. With `turn_off()`, however, there is an
additional call to `self.get_remote().close()` which indirectly caused
new connection to be created and then closed immediately. This causes the
component to stuck in failure mode when turn_off command is repeatly
issued.

== The Fix
Recreate the connection and retry the command if connection is closed
to avoid silent failures due to connection closed. Also set `_remote`
to None after calling close() to put it in correct state.

This fix eliminates intermittent command failure and failure mode in
turn_off().
2018-06-09 09:22:34 -04:00
vandenberghev
5f65f67f1e Removed semicolon 2018-06-09 12:37:06 +02:00
Malte Franken
f242418986 UVC camera platform handling unavailable NVR or cameras better (#14864)
* fixed tests: using correct camera configuration now and error handling tests must be separated out to ensure that the setup_component call is actually executed

* better error handling during setup; raising PlatformNotReady in likely recoverable cases; added tests
2018-06-09 07:22:17 +02:00
Jason Hu
d3d9d9ebf2 Add color_status sensor for Nest Protect (#14868) 2018-06-09 07:16:11 +02:00
Paulus Schoutsen
8ceb57752b Merge pull request #14876 from home-assistant/rc
0.71.0
2018-06-08 18:07:39 -04:00
Paulus Schoutsen
bd1af8c3d8 Version bump to 0.71.0 2018-06-08 16:57:59 -04:00
Paulus Schoutsen
6af995026b Add support for new hass.io panel (#14873) 2018-06-08 16:50:19 -04:00
Paulus Schoutsen
19a30b0ce6 Version bump to 0.71.0b1 2018-06-08 15:04:58 -04:00
Robert Svensson
9a659a5d1d Zone - Hass configuration name is optional (#14449)
* Hass configuration name is optional

* Check explicitly if name is none

* Reverted back to old logic for zones configured in configuration.yaml, where many zones can have the same name

* New test to verify use case of allowing multiple zones having the same name

* Fix too long line
2018-06-08 15:04:42 -04:00
Paulus Schoutsen
1cfd770b95 Use hass iconset (#14185) 2018-06-08 15:04:41 -04:00
Paulus Schoutsen
3fda97eed7 Bump frontend to 20180608.0b0 2018-06-08 15:04:28 -04:00
Anders Melchiorsen
b657cff6ba Add netgear_lte component (#14687)
* Add netgear_lte component

* Improvements after review

* Allow multiple notify targets

* Require default notify target
2018-06-08 07:46:34 +02:00
Mal Curtis
e3fba79126 Disable volume control for Onkyo when unavailable (Closes: #14774) (#14863) 2018-06-08 07:45:21 +02:00
Diogo Gomes
bb0068908d Fix unit conversion (#14730)
* reviewing this code min_temp and max_temp are always present and always in celsius

* revert change (preserve unit conversion)

* revert (due to unit conversion)

* self

* clean

* cleaner
2018-06-07 22:44:07 -04:00
Robert Svensson
0748466ffc Zone - Hass configuration name is optional (#14449)
* Hass configuration name is optional

* Check explicitly if name is none

* Reverted back to old logic for zones configured in configuration.yaml, where many zones can have the same name

* New test to verify use case of allowing multiple zones having the same name

* Fix too long line
2018-06-07 17:06:13 -04:00
Dale Higgs
fe018fd58c Add set_default_level to logger (#14703)
* Add set_default_service to logger

* Fix 2-line lint error

* Add set_default_level to services.yaml

* Add tests for set_default_level

* Remove function and add else when setting default
2018-06-07 17:03:04 -04:00
Steve Edson
10317a0f71 Rename Hive hub friendly name (#14747)
Right now, "Hub Status" is very generic, I didn't know what component it was coming from, and the only way to tell was searching the source code to find the reference.
2018-06-07 16:57:07 -04:00
Alexei Chetroi
87d55834be zha: handle "step_with_on_off" cluster command in LevelListener. (#14756) 2018-06-07 16:56:07 -04:00
Sebastian Muszynski
d4cc806cd5 Fix door/window sensor support of the Xiaomi Aqara LAN protocol V2 (Closes: #14775) (#14777) 2018-06-07 16:55:18 -04:00
Sergiy Maysak
1a7e8c88a3 Wireless tags platform (#13495)
* Initial version of wirelesstags platform support.

* Pinned wirelesstagpy, generated requirements_all.

* Fixed temperature units in imperial units systems, make binary events more tags specific.

* Lowercased tag name during entity_id generation.

* Fixed: tag_id template for tag_binary_events, support of light sensor for homebridge.

* Minor style cleanup.

* Removed state, define_name, icon. Reworked async arm/disarm update. Removed static attrs. Introduced available property. Custom events contains components name now. Cleaned dedundant items from schema definition.

* Removed comment and beep duration from attributes. Minor cleanup of documentation comment.

* Ignoring Wemo switches linked in iOS app.

* Reworked passing data from platform to components using signals.
2018-06-07 16:30:20 -04:00
Paulus Schoutsen
90a51160c4 Don't run unnecessary methods in executor pool (#14853)
* Don't run unnecessary methods in executor pool

* Lint

* Lint 2
2018-06-07 15:31:21 -04:00
Fabian Affolter
50321a29b5 Catch ConnectionError (fixes #14241) (#14748) 2018-06-07 20:25:26 +02:00
Paulus Schoutsen
67d137cfd5 Store config entry id in entity registry (#14851)
* Store config entry id in entity registry

* Lint
2018-06-07 20:23:09 +02:00
Philip Rosenberg-Watt
bb4d1773d3 Add min_temp and max_temp to MQTT climate device (#14690)
* Add min_temp and max_temp to MQTT climate device

* Add unit tests

* Remove blank line

* Fix unit tests & temp return values

* PEP-8 fixes

* Remove unused import
2018-06-07 13:50:12 -04:00
Fabian Affolter
a6c1192bfc Upgrade aiohttp to 3.3.0 (#14766) 2018-06-07 13:49:14 -04:00
Matthew Treinish
d14d2fe588 Add IBM Watson IoT Platform component (#13664)
This commit adds a new history component for the IBM Watson IoT
Platform. The IBM Watson IoT Platform allows for tracking of devices
and analytics on top of the device data. This new component allows
users to have home assistant automatically populate a watson iot
platform board with device data from devices managed by home assistant.
2018-06-07 13:43:51 -04:00
starkillerOG
f696331563 Add general sound mode support (#14729)
* Media player: general sound mode support

* General sound mode support

* White spaces

* Add sound mode support to demo media player

* white space

* remove unnessesary code
2018-06-07 10:57:45 -04:00
Anders Melchiorsen
6b2b92a732 Improvements to LIFX reliability (#14848)
* Improve fault tolerance of LIFX initialization

* Update aiolifx to 0.6.3

* Use list comprehension
2018-06-07 10:06:29 -04:00
Fabian Affolter
83ce9450f7 Upgrade Mastodon.py to 1.3.0 (#14858) 2018-06-07 10:04:58 -04:00
Paulus Schoutsen
0b405c33c4 Update Hue flow title (#14852) 2018-06-07 16:00:42 +02:00
Paulus Schoutsen
bf74cab7af Fix non awaited test (#14854) 2018-06-07 15:58:54 +02:00
Paulus Schoutsen
d8adb4bdb0 Bump frontend to 20180607.0 2018-06-06 22:42:01 -04:00
Sebastian Muszynski
bef15264b7 Ignore the mistaken long_both_click event of the 86sw (Closes: #14802) (#14808) 2018-06-06 19:51:59 +02:00
Roman
6d26915c69 Feature/gearbest library update (Closes: #14813) (#14833) 2018-06-06 11:38:50 +02:00
Paulus Schoutsen
fa2e6ada26 Route themes and translations over websocket (#14828) 2018-06-06 10:12:43 +02:00
Paulus Schoutsen
a6880c452f Migrate entity registry to using websocket (#14830)
* Migrate to using websocket

* Lint
2018-06-06 10:08:36 +02:00
Diogo Gomes
4bccb0d2a1 Merge pull request #14831 from home-assistant/sim-sensor
Limit sensor.simulated to 3 decimals (fixes #14773)
2018-06-05 21:06:11 +01:00
Luc Touraille
103639455c Add Freebox device tracker (#12727)
* Add a device tracker for Freebox routers

* Automatic setup of Freebox device tracker based on discovery

* Make the Freebox device tracker asynchronous
2018-06-05 20:38:50 +02:00
Thomas Krüger
549abd9c7e Improved Fritz!Box thermostat support (#14789) 2018-06-05 20:06:25 +02:00
Fabian Affolter
f1aba5511f Limit to 3 decimals (fixes #14773) 2018-06-05 19:44:41 +02:00
Simon Nørager Sørensen
21d05a8b4d Fixes an issue in Xiaomi TV platform that would some TVs not sleep correctly (#14829) 2018-06-05 19:13:16 +02:00
Mischa Gruber
cb6c869c2f Action parameter doesn't longer have to be the first parameter (#14815)
* Action parameter doesn't longer have to be the first parameter

* Minified code upon suggestion
2018-06-05 18:15:34 +02:00
Hugo Dupras
640e499964 netatmo api is now in pip as pyatmo (#14824)
Signed-off-by: Hugo D. (jabesq) <jabesq@gmail.com>
2018-06-05 11:55:53 -04:00
Paulus Schoutsen
b3b4f7468d Further cleanup frontend (#14805)
* Remove registering panels

* Remove unused image

* Lint
2018-06-05 10:50:16 -04:00
Paulus Schoutsen
ad9621ebe5 Use hass iconset (#14185) 2018-06-05 10:49:54 -04:00
Sebastian Muszynski
e370d523ec Bump python-miio version (Closes: #13749) (#14796)
* Bump python-miio version

* Fix Xiaomi Power Strip V1 support (Closes: #13749)
2018-06-04 23:50:18 +02:00
vandenberghev
61a41bb8fc Fix issue #14426: [homeassistant.components.sensor] smappee: Error on device update!
https://github.com/home-assistant/home-assistant/issues/14426
2018-06-04 20:08:17 +02:00
vandenberghev
2da6d3c223 Merge branch 'dev' of https://github.com/home-assistant/home-assistant into dev
# Conflicts:
#	homeassistant/components/sensor/smappee.py
2018-06-04 19:44:04 +02:00
Fabian Affolter
816efa02d1 Use pihole module to get data (#14809) 2018-06-04 18:49:26 +02:00
Fabian Affolter
bd1b1a9ff9 Update syntax (#14812) 2018-06-04 14:44:55 +02:00
quthla
1d23f7f900 Allow Kodi live streams to be recognized as paused (#14623) 2018-06-04 12:24:28 +01:00
Sebastian Muszynski
39843a73de Add additional 86sw model identifier of the LAN protocol V2 (#14799) 2018-06-04 07:39:50 +02:00
Diogo Gomes
aec425d1f6 Weather Platform - IPMA (#14716)
* initial commit

* lint

* update with pyipma

* Added test

* Added test

* lint

* missing dep

* address comments

* lint

* make sure list is iterable

* don't bother with list

* mock dependency

* no need to add test requirements

* last correction
2018-06-03 23:01:48 +02:00
Paulus Schoutsen
855ed2b4e4 Version bump to 0.72.0.dev0 2018-06-03 16:54:23 -04:00
Paulus Schoutsen
2dc40fe16e Version bump to 0.71.0b0 2018-06-03 16:53:48 -04:00
Paulus Schoutsen
bf8376ddcb Merge remote-tracking branch 'origin/master' into dev 2018-06-03 16:53:17 -04:00
Mattias Welponer
8f696193f0 Add homematicip_cloud illuminance sensor (#14720)
* Add iluminance sensor and device_class for sensors

* Fix lint
2018-06-03 18:48:51 +02:00
Paulus Schoutsen
70edb2492a Version bump to 20180603.0 2018-06-03 12:29:57 -04:00
quthla
e35d4beb95 Fix media_title empty when title is empty but label is set (#14791) 2018-06-03 09:27:17 -04:00
quthla
919b431a24 Add Kodi OnResume event (#14790) 2018-06-03 09:26:23 -04:00
Jason Woodford
7f59a8ea0c Update total-connect-client to 0.18 for Honeywell Lynx Touch-Wifi support (#14778) 2018-06-03 13:55:49 +02:00
Sebastian Muszynski
1ac3f0da63 Ignore the mistaken long_click event of the 86sw (Closes: #14694) (#14785) 2018-06-03 11:54:03 +02:00
Jason Hu
12e679c14d Assign device class to nest sensors (#14746)
* Assign device class to nest sensors

sensor/nest.NestSensor => /nest.NestSensorDevice,
so that BinarySensor platform do not reference Sensor platform anymore

* Resolve code review comment

* Follow code review comment
2018-06-03 03:54:48 +02:00
Fabian Affolter
28ef94c3fa Update syntax (#14772) 2018-06-02 15:08:10 +02:00
Fabian Affolter
27df4cca6c Upgrade shodan to 1.8.1 (#14760) 2018-06-02 08:34:47 -04:00
Fabian Affolter
a8413249c2 Upgrade sqlalchemy to 1.2.8 (#14765) 2018-06-02 08:34:30 -04:00
Fabian Affolter
f2dacb2570 Upgrade youtube_dl to 2018.06.02 (#14763) 2018-06-02 08:33:48 -04:00
Fabian Affolter
5aaf81f2c9 Upgrade Sphinx to 1.7.5 (#14764) 2018-06-02 08:31:43 -04:00
Fabian Affolter
b86cd325fe Update syntax (#14770) 2018-06-02 08:31:06 -04:00
Fabian Affolter
74b7dabf2d Update syntax (#14768) 2018-06-02 08:30:54 -04:00
Fabian Affolter
1ce4c2092a Update syntax (#14771) 2018-06-02 08:30:07 -04:00
Fabian Affolter
875e05ff38 Remove swagger file (#14762) 2018-06-02 08:29:38 -04:00
Mick Vleeshouwer
fe0e49db4b Update postnl api to 1.0.2 (#14769) 2018-06-02 13:45:48 +02:00
Fabian Affolter
ad86e68c1e Update syntax of platform random (#14767) 2018-06-02 12:00:01 +02:00
Tristan Caulfield
e7985c970b Upgrade directpy to 0.5 (#14750)
* Version Requirement bump

Bump required version to 0.5 to allow component to work with Genie Mini clients using the clientAddr variable.

* Ran script/gen_requirements_all.py as requested.
2018-06-02 09:30:15 +02:00
austinlg96
cfac537f51 Added option to block Osram Lightify individual lights in the same way that groups can be (#14470) 2018-06-02 09:23:51 +02:00
Fabian Affolter
d6e76969cc Tweak about the requirements 2018-06-01 23:33:04 +02:00
Fabian Affolter
77dca8272c Upgrade blockchain to 1.4.4 (#14738) 2018-06-01 19:41:35 +02:00
Fabian Affolter
3b8ee196be Update syntax (#14742) 2018-06-01 19:41:20 +02:00
michaeldavie
4935043f4a Add battery attribute to Sensibo (#14735)
* Added battery attribute

* Simplify current_battery
2018-06-01 19:41:04 +02:00
Matt Schmitt
f5d74e07d5 Add support for outlets in HomeKit (#14628) 2018-06-01 18:04:54 +02:00
Paulus Schoutsen
0a724a5473 Update frontend 2018-06-01 10:52:25 -04:00
Jason Hu
cba8333a13 Change nest to cloud push (#14656)
* Change nest component to Cloud Push

Change sensors.nest, binary_sensors.nest and climate.nest to push mode
nest camera still need poll to update snapshot image
Also change nest component to async

* Flake8 lint

* Fix async_notify_errors, it is not a coroutine

* Fix pylint

* Fix pylint, function name should shall shorter than 32

* Use dispatcher helper instead event bus

* Use async_update_ha_state(True)

* Refactoring load_platform

Move service registration into async_setup_nest(),
 resolve an issue that before the first time configuration done,
 set_mode service should not be registered

* Fix an issue that authorization failure may leave a blocked thread

* Pylinting

* async_nest_update_callback => async_update_state to avoid confusion

* Move signal handler register to async_added_to_hass

* Better handle nest api error

* Remove unnecessary register for binary_sensor

* Remove unused import

* Upgrade to python-nest 4.0.1

Fix a thread race condition issue

* Address my own comments

* Address my own comment
2018-06-01 10:44:58 -04:00
Anders Melchiorsen
fcbc399809 Disallow automation.trigger without entity_id (#14724) 2018-06-01 10:27:12 -04:00
Paulus Schoutsen
f6eb9e79d5 Custom panel (#14708)
* Add support for custom panels in JS

* Allow specifying JS custom panels

* Add trust external option

* Fix tests

* Do I/O outside event loop

* Change config to avoid breaking change
2018-06-01 10:06:17 -04:00
roiff
ab3717af76 Homekit Thermostat: Better support for temperature ranges (#14679)
* Support for obtaining temperature range
* Fallback to Defaults
* Fixed unit conversion
* Added test
2018-06-01 13:49:16 +02:00
Pierre Ståhl
6cd69b413c Bump pyatv to 0.3.10 (#14736)
* Bump pyatv to 0.3.10

* Update requirements_all.txt
2018-06-01 08:41:40 +02:00
Fabian Affolter
de56a0d021 Upgrade shodan to 1.8.0 (#14717) 2018-06-01 08:40:27 +02:00
glenn20
99fdd3e358 Add device_descriptor and device_name to keyboard event (#14642)
* Add device_descriptor and device_name to keyboard event

This allows automations to identify which device has generated the
keypress. This is especially useful for bluetooth remotes to control different
devices.

* Remove line breaks

* Fix
2018-06-01 00:32:09 +02:00
Paulus Schoutsen
9a3107aa66 Merge pull request #14727 from home-assistant/rc
0.70.1
2018-05-31 18:11:34 -04:00
Paulus Schoutsen
d31e01b877 Revert "Remove simplepush.io (#14358)"
This reverts commit 612a37b2dd.
2018-05-31 18:11:15 -04:00
Paulus Schoutsen
f8c8900297 Merge pull request #14728 from home-assistant/reinstate-simplepush
Revert "Remove simplepush.io (#14358)"
2018-05-31 18:09:50 -04:00
Paulus Schoutsen
ed9cf994c2 Revert "Remove simplepush.io (#14358)"
This reverts commit 612a37b2dd.
2018-05-31 17:58:03 -04:00
Paulus Schoutsen
d4a4938fce Version bump to 0.70.1 2018-05-31 17:27:55 -04:00
Paulus Schoutsen
f7f0138cff Bump frontend to 20180531.0 2018-05-31 17:27:44 -04:00
Marius
753ffdaffd Fix Eco mode display on Nest (#14706)
* Fix Eco mode display on Nest

* Fix Hound problems
2018-05-31 17:27:35 -04:00
Otto Winter
40aba3d785 MQTT Cover Fix Assumed State (#14672) 2018-05-31 17:27:35 -04:00
Anders Melchiorsen
64f157a036 Ignore unsupported Sonos favorite lists (#14665) 2018-05-31 17:27:35 -04:00
MizterB
0eddd287c5 Update Hue platform to aiohue 1.5.0, and re-implement logic for duplicate scene names. (#14653) 2018-05-31 17:27:34 -04:00
Marius
f32b50cb80 Fix Eco mode display on Nest (#14706)
* Fix Eco mode display on Nest

* Fix Hound problems
2018-05-31 17:26:59 -04:00
Paulus Schoutsen
a58a566ae8 Bump frontend to 20180531.0 2018-05-31 17:25:35 -04:00
Diogo Gomes
2f1d40e014 Merge pull request #14721 from PhilRW/climate-constants
Change climate default limits to constants
2018-05-31 22:19:25 +01:00
Fabian Affolter
14ee6178f9 Add Flock notification platform (#14533)
* Add Flock notification platform

* Use async syntax and move session and loop
2018-05-31 23:07:50 +02:00
Philip Rosenberg-Watt
753fe8279b Remove deprecated comments 2018-05-31 13:59:26 -06:00
Philip Rosenberg-Watt
cc264f415e Fix PEP-8 issues 2018-05-31 11:32:31 -06:00
Philip Rosenberg-Watt
dae90abb34 Change climate default limits to constants
Min and max temp and humidity are now defined in climate __init__.py
and are available for import in subclasses.
2018-05-31 11:23:04 -06:00
Aaron Bach
60f692c7bb Fixes (and stabilizes) some incorrect zone codes in RainMachine (#14719)
* Fixes (and stabilizes) some incorrect zone codes

* Fixed a misspelling
2018-05-31 18:55:50 +02:00
c727
7094d6d61e Change ACP code_format to None|"Number"|"Any" (#14686) 2018-05-31 14:31:40 +02:00
Paulus Schoutsen
08fc73aa20 Bump to 0.71.0.dev0 2018-05-30 11:19:27 -04:00
Michael Nosthoff
c14e41f431 Netatmo Sensor: Implement device_class (#14634)
added device_class and removed icon for temperature and humidity.
2018-05-30 10:53:35 -04:00
cdce8p
f1f4d80f24 Homekit Bugfixes (#14689)
* Fix async bug
* Fix debounce bug
2018-05-30 12:39:27 +02:00
Paulus Schoutsen
e746b92e0e Fix deprecated code (#14681) 2018-05-29 23:14:58 +02:00
cdce8p
7d2563eb1f Update HAP-python to 2.2.2 (#14674)
* Pass driver to accessory
* Added 'hk_driver' fixture for tests
2018-05-29 22:43:26 +02:00
Aaron Bach
084b3287ab Add sensors and services to RainMachine (#14326)
* Starting to add attributes

* All attributes added to programs

* Basic zone attributes in place

* Added advanced properties for zones

* We shouldn't calculate the MAC with every entity

* Small fixes

* Basic framework for push in play

* I THINK IT'S WORKING

* Some state cleanup

* Restart

* Restart part 2

* Added stub for service schema

* Update

* Added services

* Small service description update

* Lint

* Updated CODEOWNERS

* Moving to async methods

* Fixed coverage test

* Lint

* Removed unnecessary hass reference

* Lint

* Lint

* Round 1 of Owner-requested changes

* Round 2 of Owner-requested changes

* Round 3 of Owner-requested changes

* Round 4 (final for now) of Owner-requested changes

* Hound

* Updated package requirements

* Lint

* Collaborator-requested changes

* Collaborator-requested changes

* More small tweaks

* One more small tweak

* Bumping Travis and Coveralls
2018-05-29 21:02:16 +02:00
Thibault Cohen
4105429639 Add asyncio support for Ebox (#14183)
* Fix Ebox sensor

* Fix #14183 comments

* Update ebox.py

* Update ebox.py

* Continue fixing comments
2018-05-29 10:23:12 -04:00
Robert Svensson
8c93b484c4 deCONZ - Option to load or not to load clip sensors on start (#14480)
* Option to load or not to load clip sensors on start

* Full flow

* Fix config flow and add tests

* Fix attribute dark reporting properly

* Imported and properly configured deCONZ shouldn't need extra input to create config entry
2018-05-29 10:09:53 -04:00
Fabian Affolter
3b38de63ea Allow user-defined sensors (#14613)
* Allow user-defined sensors

* Require element for resources

* Don't use .get()
2018-05-29 10:03:00 -04:00
Alexei Chetroi
eff1d1f14e zha: fix temperature rounding for ZHA temperature sensors. (#14669) 2018-05-29 09:05:07 -04:00
Otto Winter
fcb60d472e MQTT Cover Fix Assumed State (#14672) 2018-05-29 09:03:45 -04:00
Anders Melchiorsen
f2a2f2cca5 Ignore unsupported Sonos favorite lists (#14665) 2018-05-29 10:15:30 +02:00
Paulus Schoutsen
8c7f0669c6 Allow hassio frontend development (#14675)
* Allow hassio frontend development

* Fix tests
2018-05-29 08:51:08 +02:00
Matthew Garrett
d36c7c3de7 Increase Eufy's requirement on lakeside (#14671)
python-lakeside was broken with at least some versions of the Python
protobuf code, so bump the requirement to a fixed version.
2018-05-29 08:42:27 +02:00
Bakkoda
79efb0e607 Update mfi.py (#14667)
Add ability to read door sensor states from the mPort.
2018-05-29 07:51:14 +02:00
Robert Accettura
9bc26e93a4 Add pin pad to alarm panel (#14178)
* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Update regex

* Update regex

* Update regex

* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Add pin pad to alarm panel

* Fix typos

* Fix typos

* Fix typos

* Add pin pad to alarm panel

* Fix errors
2018-05-29 07:50:27 +02:00
Andrey
6c3e2021df Give unknown zwave nodes a better name (#14353)
* Give unknown zwave nodes a better name

* Update util.py
2018-05-28 21:49:38 -04:00
Enrico Berndt
07255a29b4 Add tv channel and volume level for philips js API 5 (#14276)
* PhilipsTV API 5: Added tv channel change and setting of volume level.

* set_volume only sets volume via api now and nothing else.
2018-05-28 10:41:51 -04:00
Alexei Chetroi
144bb3492a zha/light: Properly parse currentX and currentY on async_update() (#14605) 2018-05-28 10:32:47 -04:00
cdce8p
6f4dd7b057 Improve Homekit media_player options (#14637)
* Optimize imports

* Optimize name

* Optimize config schema

* Rename mode to feature

* Replace mode with feature_list
2018-05-28 10:26:33 -04:00
David F. Mulcahey
27f3285d17 Force update ZHA electrical sensor (#14649)
* force state update because we have a real reading

* hound

* docstring
2018-05-28 10:22:29 -04:00
MizterB
9a87e62e0e Update Hue platform to aiohue 1.5.0, and re-implement logic for duplicate scene names. (#14653) 2018-05-28 10:21:00 -04:00
koreth
9044a9157f Reduce log churn from Envisalink binary sensors (#14659)
The Envisalink binary sensor was logging events with a relative
timestamp that updated every time it polled, so even when nothing
new was happening, the event log would be full of meaningless
state changes. Modify the sensor code to use an absolute time
which stays stable when there isn't new activity.
2018-05-28 10:19:03 -04:00
Michaël Arnauts
799ae894a8 Remove docker prereqs scripts that only install a package. Add informational message for this. (#14661) 2018-05-28 10:17:01 -04:00
Fabian Affolter
bff1e1ff6c Upgrade python_opendata_transport to 0.1.0 (#14652) 2018-05-28 08:17:10 +02:00
Fabian Affolter
cc2437614b Upgrade youtube_dl to 2018.05.26 (#14654) 2018-05-28 08:16:55 +02:00
Paulus Schoutsen
0700886d1a Merge pull request #14657 from home-assistant/rc
0.70.0
2018-05-27 20:22:19 -04:00
Paulus Schoutsen
cd0e321668 Version bump to 0.70.0 2018-05-27 17:18:53 -04:00
Paulus Schoutsen
94a82ab7dc Allow Hass.io panel dir (#14655) 2018-05-27 17:18:16 -04:00
Paulus Schoutsen
b6e4a7771a Allow Hass.io panel dir (#14655) 2018-05-27 17:17:19 -04:00
Fabian Affolter
13859388c1 Upgrade locationsharinglib to 2.0.7 (#14640) 2018-05-27 20:16:47 +02:00
Fabian Affolter
2f4c5f949b Use constants (#14647) 2018-05-27 20:16:30 +02:00
Fabian Affolter
36e8157268 Upgrade TwitterAPI to 2.5.4 (#14639) 2018-05-27 15:46:58 +02:00
Fabian Affolter
2d88f47795 Upgrade gitterpy to 0.1.7 (#14643) 2018-05-27 15:45:43 +02:00
Jason Hu
5acfe5da68 Upgrade python-nest to 4.0.0 (#14638)
* Upgrade python-nest to 4.0.0

Drop in replace to use nest stream API
Didn't change any logic from HA side

* Update requirements_all.txt
2018-05-27 11:31:05 +02:00
Fabian Affolter
5f9e4ae136 Upgrade luftdaten to 0.2.0 (#14620) 2018-05-27 09:53:53 +02:00
Paulus Schoutsen
a9b0f92afe Version bump to 0.70.0b7 2018-05-26 20:02:54 -04:00
Paulus Schoutsen
07b2728380 Bump frontend to 20180526.4 2018-05-26 20:02:36 -04:00
Paulus Schoutsen
a5e66ce6ba Bump frontend to 20180526.4 2018-05-26 20:02:24 -04:00
David F. Mulcahey
eae9726bec Add electrical measurement sensor to ZHA (#14561)
* Add electrical measurement sensor

* correct state update

* hound fix

* zha: Add metering sensor (#14562)

* Add IlluminanceMeasurementSensor to ZHA (#14563)

* add IlluminanceMeasurementSensor

* address review comment

* Fix whitespace error during merge

* Add electrical measurement sensor

* correct state update

* hound / flake8
2018-05-26 22:50:05 +02:00
guillaume1410
c425afe50e Adding ryobi garage door opener (#14618)
* Initial component for Ryobi cover

* Initial component for Ryobi cover

* Adding Ryobi cover

* Adding Ryobi cover

* Adding Ryobi cover

* Minor changes

* Remove import
2018-05-26 22:46:53 +02:00
Paulus Schoutsen
a5b9e59cee Version bump to 0.70.0b6 2018-05-26 14:30:03 -04:00
Paulus Schoutsen
fb447cab82 Bump frontend to 20180526.3 2018-05-26 14:29:53 -04:00
Paulus Schoutsen
bcde57bff8 Bump frontend to 20180526.3 2018-05-26 14:29:26 -04:00
David Ryan
dfd7ef1fce Add Hydrawise component (#14055)
* Added the Hydrawise component.

* Fixed lint errors.

* Multiple changes due to review comments addressed.

* Simplified boolean test. Passes pylint.

* Need hydrawiser package version 0.1.1.

* Added a docstring to the device_class method.

* Addressed all review comments from MartinHjelmare.

* Changed keys to single quote. Removed unnecessary duplicate method.

* Removed unused imports.

* Changed state to lowercase snakecase.

* Changes & fixes from review comments.
2018-05-26 18:42:52 +02:00
Paulus Schoutsen
6b9addfeea Update release script 2018-05-26 11:54:50 -04:00
Paulus Schoutsen
6c62f7231b Version bump to 0.70.0b5 2018-05-26 11:53:57 -04:00
Paulus Schoutsen
52c21a53b3 Bump frontend to 20180526.2 2018-05-26 11:53:44 -04:00
Paulus Schoutsen
fdb250d86c Bump frontend to 20180526.2 2018-05-26 11:53:36 -04:00
Fabian Affolter
8de56cfc10 Upgrade speedtest-cli to 2.0.2 (#14633) 2018-05-26 17:35:16 +02:00
Mattias Welponer
7ea25cd360 Add homematicip cloud climate platform (#14388)
* Add support for climatic devices

* Update requirements_all

* Change icon to mdi:thermostat

* Update of homematicip-rest-api lib version

* Remove all mode or state relevant things - will come later

* Add current_operation again to see proper status

* Remove STATE_PERFORMANCE import

* Remove trailing whitespace

* Update requirements file
2018-05-26 16:03:53 +02:00
Paulus Schoutsen
c9498d9f09 Version bump to 0.70.0b4 2018-05-26 08:35:18 -04:00
Paulus Schoutsen
2f0435ebd8 No longer use backports for ffmpeg (#14626) 2018-05-26 08:34:56 -04:00
Paulus Schoutsen
19351fc429 Use libsodium18 (#14624) 2018-05-26 08:34:55 -04:00
Paulus Schoutsen
bfc16428da Bump frontend to 20180526.1 2018-05-26 08:34:40 -04:00
Paulus Schoutsen
41fc44b27c Bump frontend to 20180526.1 2018-05-26 08:33:22 -04:00
Max Muth
a55fbd2be7 Add services for adding and removing items to shopping list (#14574) 2018-05-26 13:53:48 +02:00
Marcelo Moreira de Mello
28d6910e56 Added UDP and parallel streams support to Iperf3 (#14629) 2018-05-26 13:43:31 +02:00
Lorenz Schmid
edfc54b2eb Added option to connect via SSL for OpenWRT(luci) device tracker (#14627)
* Added option to connect via HTTPS for OpenWRT(luci) device tracker

* Use string formatting

* Update quotes

* Remove whitespace
2018-05-26 09:51:21 +02:00
cdce8p
6ceafabd78 Extend package support (#14611) 2018-05-25 16:41:50 -04:00
Paulus Schoutsen
48972c7570 No longer use backports for ffmpeg (#14626) 2018-05-25 13:49:45 -04:00
Paulus Schoutsen
bf3ead3359 Use libsodium18 (#14624) 2018-05-25 11:32:45 -04:00
Marius Kotlarz
b4f8d52fb1 Add configurable decimal rounding of display value for CoinMarketCap sensor and upgrade to 5.0.3 (#14437) (#14604) 2018-05-25 15:39:04 +02:00
Matt Schmitt
143be49c66 Add HomeKit support for automations (#14595) 2018-05-25 11:38:48 +02:00
Matt Schmitt
a9f19a16ee Add HomeKit support for media players (#14446) 2018-05-25 11:37:20 +02:00
Nik Klever
d53a8c0823 Adding illumination sensor (#14615)
* Adding illumination sensor

Adding Illumination sensor of 1wire device DS2438 (DEVICE_SENSOR type 26) according to [OWFS API](http://owfs.org/index.php?page=ds2438)

* Correcting typo illumination -> illuminance
2018-05-25 10:29:20 +02:00
bastshoes
6e5c541a00 Add support container status for Glances on RPi3 (#14529)
* Add support container status for Glances on RPi3

Glances on RPi3 return different container status. 
```
"containers": [
        {
            "Status": "Up 2 hours",
            "name": "HASS",
            "io": {
                "iow": 0,
                "time_since_update": 5.1789350509643555,
                "cumulative_ior": 94208,
                "ior": 0,
                "cumulative_iow": 4096
            },
```
This small PR adds support dealing with this differences.

* Making line shorter

* Fixing indentation

* Fix lint error

* Fix ident

* Fix intend
2018-05-25 09:58:53 +02:00
Gregory Benner
2cd127921a Update pyrainbird (#14617) 2018-05-25 06:39:41 +02:00
Paulus Schoutsen
43d2e436b9 Version bump to 0.70.0b3 2018-05-24 14:25:15 -04:00
Paulus Schoutsen
ef35b8d428 Fix hue discovery popping up (#14614)
* Fix hue discovery popping up

* Fix result

* Fix tests
2018-05-24 14:24:58 -04:00
Tom Harris
69e86c29a6 Bump insteonplm version to fix device hanging (#14582)
* Update inteonplm to 0.9.2

* Change to force Travis CI

* Change to force Travis CI
2018-05-24 14:24:58 -04:00
Greg Laabs
a4d45c46e8 Fix ISY moisure sensors showing unknown until a leak is detected (#14496)
* Fix ISY leak sensors always showing UNKNOWN until a leak is detected

Added some logic that handles both moisture sensors and door/window sensors

* Handle edge case of leak sensor status update after ISY reboot

If a leak sensor is unknown, due to a recent reboot of the ISY, the status will get updated to dry upon the first heartbeat. This status update is the only way that a leak sensor's status changes without an accompanying Control event, so we need to watch for it.

* Fixes from overnight testing

State was checking the incorrect parameter, and wasn't calling schedule update

* Remove leftover debug log line

* Remove unnecessary pylint instruction

* Remove access of protected property

We can't cast _.status directly to a bool for some unknown reason (possibly with the VarEvents library), but casting to an int then bool does work.
2018-05-24 14:24:57 -04:00
Paulus Schoutsen
45d1d30a8b Update frontend to 20180524.0 2018-05-24 14:24:37 -04:00
Paulus Schoutsen
fa9b9105a8 Fix hue discovery popping up (#14614)
* Fix hue discovery popping up

* Fix result

* Fix tests
2018-05-24 14:24:14 -04:00
Paulus Schoutsen
4fb4838bde Update frontend to 20180524.0 2018-05-24 13:08:12 -04:00
Robert Beal
3a487e54a2 Upgrade linode-api to 4.1.9b1 (#13863) (#14610) 2018-05-24 17:16:35 +02:00
Marcelo Moreira de Mello
36da82aa8d Add Iperf3 client sensor (#14213) 2018-05-24 09:25:27 +02:00
Aaron Bach
5205354cb7 Adds a device class of 'garage' to MyQ covers (#14602) 2018-05-24 07:58:35 +02:00
Jason Hu
3498234448 Add Nest away binary sensor and eta sensor (#14406) 2018-05-23 21:40:33 +02:00
Charles Garwood
c13ebacce1 Remove nma component (#14594)
* Remove nma component

* Update .coveragerc
2018-05-23 20:36:51 +02:00
Fabian Affolter
ad49942201 Upgrade TwitterAPI to 2.5.3 (#14596) 2018-05-23 16:47:58 +02:00
SchumyHao
82770faad7 Add Xiaomi Aqara Lock support (#14419) 2018-05-22 10:40:11 +02:00
Fabian Affolter
a2f9fdf339 Add new transmission sensor types (#14530) 2018-05-22 10:06:14 +02:00
Malte Franken
a2decdaaa3 NUT sensor enhancements (#14570) (Fixes #14324)
* removed default value from required parameter; raising PlatformNotReady when connection to nut unavailable; output human-readable state name by default

* removed superfluous sensor name part; showing human-readable form and raw value of current status in more info dialog

* introduced a new virtual sensor type based on the raw status value but used to display a human-readable form of the status

* renamed method

* format string instead of concatenation

* revert the change to the device state attributes - only output the human-readable status without the raw value
2018-05-22 09:34:02 +02:00
Julian Knauer
72a1b7ae3f Lagute LW-12 Wifi LED control (#13307)
* Added platform lw12wifi for Lagute LW-12 Wifi Lights

Supported features:
* RGB colors
* Variable brightness
* 29 effects
* Changing transitions speed for animated effects

* Added lw12wifi to the list of omitted files to test

* Added lw12 module as new requirement for lw12wifi platform

* Added configuration example docstring for platform lw12wifi

* Updating code according to review in PR:

* Removed unused imports: enum, socket.
* Unused and not imported feature SUPPORT_FLASH was removed.
* Unused import lw12 in setup_platform method removed.
* Fixed indention for valuptuous.
* Changed check if effect is None.
* Removed personal debug output.
* Blocking function are not async anymore.

* Further improvements to satisfy PR.

* Unused import asyncio removed.
* Fixed: Return value and docstring no match up for `assumed_state`.

* Check if the set effect is supported, otherwise revert to normal light.

* Added describing missing docstrings to all functions.

* Adopted code to work with HS color setting.

* Syntactical change in comment.

* Removed redefinition of DOMAIN.

* Refactored lw12 controller setup: removed requirement for host and port in LW12Wifi class.

* Rewritten supported feature setup to a more static  expression.

* Removed unused rgb_color property

* Fixed typo in comment for set_light_option

* Changed RGB option validation schema

* Removed instance properties as config options

* Removed optional settings to be more inline with code style.

* Removed unused option from config example

* Removal of unused import

* Added property to disable state polling for this entity.

* Raise an exception if an unknown effect was selected.

* Fixed an issue with the check for known effects.

* As we do not need to set a default, use simple accessing by key.

* Log if an unknown effect was selected.

* Added link to future documentation.
2018-05-22 09:25:10 +02:00
Marcelo Moreira de Mello
2753dd0c5e Removed attribute current_time from Raincloudy sensors to avoid being triggered by recorder component (#14584) 2018-05-22 08:19:45 +02:00
Daniel Perna
118c49ecaa Update pyhomematic to 0.1.43 (#14583)
* Update __init__.py

* Update requirements_all.txt
2018-05-22 01:50:08 +02:00
Tom Harris
0d9b3bea10 Bump insteonplm version to fix device hanging (#14582)
* Update inteonplm to 0.9.2

* Change to force Travis CI

* Change to force Travis CI
2018-05-22 01:46:20 +02:00
Greg Laabs
23afdec767 Fix ISY moisure sensors showing unknown until a leak is detected (#14496)
* Fix ISY leak sensors always showing UNKNOWN until a leak is detected

Added some logic that handles both moisture sensors and door/window sensors

* Handle edge case of leak sensor status update after ISY reboot

If a leak sensor is unknown, due to a recent reboot of the ISY, the status will get updated to dry upon the first heartbeat. This status update is the only way that a leak sensor's status changes without an accompanying Control event, so we need to watch for it.

* Fixes from overnight testing

State was checking the incorrect parameter, and wasn't calling schedule update

* Remove leftover debug log line

* Remove unnecessary pylint instruction

* Remove access of protected property

We can't cast _.status directly to a bool for some unknown reason (possibly with the VarEvents library), but casting to an int then bool does work.
2018-05-21 21:00:01 +02:00
Paulus Schoutsen
4671bd95c6 Version bump to 0.70.0b2 2018-05-21 11:10:53 -04:00
Marco Orovecchia
1bc916927c fix nanoleaf aurora lights min and max temperature (#14571)
* fixed nanoleaf aurora lights min and max temperature

* review changes
2018-05-21 11:10:36 -04:00
cdce8p
2f8865d6cb Homekit style cleanup (#14556)
* Style cleanup

* Sorted imports
* Harmonized service calls

* Test improvements

* Small update
2018-05-21 11:10:35 -04:00
Martin Hjelmare
cfdea8d20f Wait for future mysensors gateway ready (#14398)
* Wait for future mysensors gateway ready

* Add an asyncio future that is done when the gateway reports the
  gateway ready message, I_GATEWAY_READY.
* This will make sure that the gateway is ready before home assistant
  fires the home assistant start event. Automations can now send
  messages to the gateway when home assistant is started.
* Use async timeout to wait max 15 seconds for ready gateway.

* Address comments
2018-05-21 11:10:35 -04:00
Marco Orovecchia
6e941af9b2 fix nanoleaf aurora lights min and max temperature (#14571)
* fixed nanoleaf aurora lights min and max temperature

* review changes
2018-05-21 11:02:50 -04:00
Paulus Schoutsen
ba9bb90cf7 Update frontend to 20180521.0 2018-05-21 11:01:47 -04:00
Paulus Schoutsen
2ff61786bc Update frontend to 20180521.0 2018-05-21 11:01:35 -04:00
Russell Cloran
9791c6b21b zha: Bump to zigpy-xbee 0.1.1 (#14566) 2018-05-20 21:57:09 -07:00
David F. Mulcahey
a183043d5d Add IlluminanceMeasurementSensor to ZHA (#14563)
* add IlluminanceMeasurementSensor

* address review comment

* Fix whitespace error during merge
2018-05-20 21:56:41 -07:00
cdce8p
0589379de5 Homekit style cleanup (#14556)
* Style cleanup

* Sorted imports
* Harmonized service calls

* Test improvements

* Small update
2018-05-20 22:25:53 -04:00
damarco
ee7e59fe68 zha: Set default binary_sensor state to false (#14553) 2018-05-20 16:14:18 -07:00
David F. Mulcahey
b489519930 zha: Add metering sensor (#14562) 2018-05-20 16:01:56 -07:00
David F. Mulcahey
4395217031 zha: Don't poll switch devices (#14560) 2018-05-20 16:00:51 -07:00
Marco Orovecchia
c8ad9c4daa Add auto discovery for nanoleaf aurora lights (#14301)
* auto discovery added for nanoleaf aurora lights

* changes requested by review

* visual indentation

* line too long

* hide autocreated config
2018-05-20 20:53:57 +02:00
Oliver
c050eb4100 Pushed to version 0.7.2 of denonavr (#14551) 2018-05-20 09:50:12 +02:00
Martin Hjelmare
c8a53c564a Wait for future mysensors gateway ready (#14398)
* Wait for future mysensors gateway ready

* Add an asyncio future that is done when the gateway reports the
  gateway ready message, I_GATEWAY_READY.
* This will make sure that the gateway is ready before home assistant
  fires the home assistant start event. Automations can now send
  messages to the gateway when home assistant is started.
* Use async timeout to wait max 15 seconds for ready gateway.

* Address comments
2018-05-19 18:33:52 -04:00
Kerwin Bryant
c316d5b0b9 Add support to ignore a xiaomi aqara gateway (#14428) 2018-05-19 22:36:47 +02:00
Fabian Affolter
e88fc33eef Fix sensor name (fixes #14535) (#14541) 2018-05-19 17:14:53 +02:00
Paulus Schoutsen
8854efd685 Version bump to 0.70.0b1 2018-05-19 10:45:18 -04:00
Paulus Schoutsen
b0e850ba5d Bump frontend to 20180519.0 2018-05-19 10:45:03 -04:00
Paulus Schoutsen
74f1f08ab5 Bump frontend to 20180519.0 2018-05-19 10:44:54 -04:00
Greg Dowling
aa51bb6cb9 Bump pyvera version (improve stability of poll loop). (#14540) 2018-05-19 10:49:52 +02:00
Fabian Affolter
8deb462471 Upgrade restrictedpython to 4.0b4 (#14537) 2018-05-19 10:05:02 +02:00
Fabian Affolter
46dc9322a2 Upgrade keyring to 12.2.1 (#14521) 2018-05-19 10:04:42 +02:00
Fabian Affolter
daf8143d01 Upgrade youtube_dl to 2018.05.18 (#14519) 2018-05-19 10:04:20 +02:00
Fabian Affolter
54dfe045b2 Upgrade aiohttp to 3.2.1 (#14517)
* Upgrade aiohttp to 3.2.1

* Upgrade async_timeout to 3.0.0

* Update the order of the requirements
2018-05-19 10:04:00 +02:00
Paulus Schoutsen
f2dfc84d52 Version bump to 0.70.0b0 2018-05-18 19:31:16 -04:00
Paulus Schoutsen
e55f7ebf81 Merge branch 'master' into dev 2018-05-18 19:30:48 -04:00
Paulus Schoutsen
8d06469efe Bump frontend to 20180518.1 2018-05-18 18:15:49 -04:00
Fabian Affolter
c1127133ea Set certifi to >=2018.04.16 (#14536) 2018-05-18 18:14:40 -04:00
Sean Dague
25970027c6 Update mychevy to 0.4.0 (#14372)
After 2 months of being offline, the my.chevy website seems to be
working again. Some data structures changed in the mean time. The new
library will handle multiple cars. This involves a breaking change in
slug urls for devices where these now include the car make, model, and
year in them.

Discovery has to be delayed until after the initial site login to get
the car metadata.
2018-05-18 13:37:43 -04:00
Greg Laabs
d7640e6ec3 Fix some ISY sensors not getting detected as binary sensors (#14497)
Sensors that were defined via sensor_string were not getting properly identified as binary sensors when they had a uom defining them as binary (the other three methods of detecting binary sensors worked though.)
2018-05-18 08:42:09 -07:00
cdce8p
12e76ef7c1 Update HAP-python to 2.1.0 (#14528) 2018-05-18 16:32:57 +02:00
hanzoh
d36996c8f0 Add Homematic IP RotaryHandleSensor support (#14522)
* Add Homematic IP RotaryHandleSensor support

HmIP-SRH was in the RotaryHandleSensor class and threw errors that LOWBAT and ERROR could not be found (they are LOW_BAT and SABOTAGE).

* Revert REQUIREMENTS change
2018-05-18 16:20:30 +02:00
Fabian Affolter
e929f45ab8 Set pytz to >=2018.04 (#14520) 2018-05-18 09:42:41 -04:00
cdce8p
4c328baaa6 Add code to HomeKit lock (#14524) 2018-05-18 13:52:52 +02:00
Fabian Affolter
cc5edf69e3 Show warning if no locations are shared (fixes #14177) (#14511) 2018-05-18 09:04:47 +02:00
Matt Snyder
909f2448ca Flux bug fix (#14476)
* Simplify conditionals.

* Send white_value on service call.

* Remove extra blank line

* Further simplification of conditionals

* Requested changes

* Do not call getRgb if not needed

* Update log message
2018-05-18 08:50:57 +02:00
Diogo Gomes
97076aa3fd Fix probability_threshold in binary_sensor.bayesian (#14512) (Closes: #14362) 2018-05-18 07:48:16 +02:00
Malte Franken
a3777c4ea8 Feedreader configurable update interval and max entries (#14487) 2018-05-18 07:25:08 +02:00
Paulus Schoutsen
1c3293ac85 Update frontend to 20180518.0 (#14510)
* Update frontend to 20180517.0

* Update requirements

* Bump frontend to 20180518.0
2018-05-17 21:29:37 -04:00
thelittlefireman
f06a0ba373 Bump locationsharinglib to 2.0.2 (#14359)
* Bump locationsharinglib to 2.0.2

* Bump locationsharinglib to 2.0.2
2018-05-17 23:06:39 +02:00
Anders Melchiorsen
9afc2634c6 Adjust LimitlessLED properties for running effects (#14481) 2018-05-17 20:54:25 +02:00
Nate Clark
ed3efc8712 Konnected component follow up (#14491)
* make device_discovered synchronous

* small fixes from code review

* use dispatcher to update sensor state

* update switch state based on response from the device

* interpolate entity_id into dispatcher signal

* cleanup lint

* change coroutine to callback
2018-05-17 20:19:05 +02:00
Mike
144524fbbb Update hitron_coda.py (#14506)
missed a typo that wasn't caught with testing since I don't have a Rogers router.
2018-05-17 19:44:01 +02:00
ChristianKuehnel
298d31e42b New Sensor FinTS (#14334) 2018-05-17 02:45:47 +02:00
Paulus Schoutsen
3e7d4fc902 Bump frontend to 20180516.1 2018-05-16 09:39:14 -04:00
Paulus Schoutsen
64223cea72 Update frontend to 20180516.0 2018-05-16 09:01:30 -04:00
Nathan Henrie
1053473111 Add stdout and stderr to debug output for shell_command (#14465) 2018-05-16 13:47:41 +02:00
Matt Schmitt
25dcddfeef Add HomeKit support for fans (#14351) 2018-05-16 13:15:59 +02:00
Ville Skyttä
e20f88c143 Use "Returns" consistently to avoid being treated as section (#14448)
Otherwise, by side effect, results in error D413 by recent pydocstyle.
2018-05-16 10:01:48 +02:00
William Scanlon
1533a68c06 Added option to invert aREST pin switch logic for active low relays (#14467)
* Added option to invert aREST pin switch logic for active low relays

* Fixed line lengths

* Changed naming and set optional invert default value.

* Fixed line length

* Removed default from get
2018-05-16 09:58:49 +02:00
Greg Laabs
5ff5c73e2b "unavailable" Media players should be considered off in Universal player (#14466)
The Universal media player inherits the states of the first child player that is not in some sort of "Off" state (including idle.) It was not considering the "unavailable" state to be off. Now it does.
2018-05-16 08:00:57 +02:00
Anders Melchiorsen
6ba49e12a2 Improve handling of offline Sonos devices (#14479) 2018-05-16 07:35:43 +02:00
nordlead2005
852ce9f990 Added temperature (apparent) high/low, deprecated max/min (#12233) 2018-05-15 21:26:41 +02:00
Paulus Schoutsen
df69680d24 Don't add a url to built-in panels (#14456)
* Don't add a url to built-in panels

* Add url_path back

* Lint

* Frontend bump to 20180515.0

* Fix tests
2018-05-15 14:47:46 -04:00
Gerard
2e7b5dcd19 BMW code cleanup (#14424)
* Some cleanup for BMW sensors

* Changed dict sort

* Updates based on review and Travis
2018-05-15 20:47:32 +02:00
Malte Franken
e49e0b5a13 Make Feedreader component more extendable (#14342)
* moved regular updates definition to own method to be able to override behaviour in subclass

* moved filter by max entries to own method to be able to override behaviour in subclass

* event type used when firing events to the bus now based on variable to be able to override behaviour in subclass

* feed id introduced instead of url for storing meta-data about the feed to be able to fetch the same feed from different configs with different filtering rules applied

* keep the status of the last update; continue processing the entries retrieved even if a recoverable error was detected while fetching the feed

* added test cases for feedreader component

* better explanation around breaking change

* fixing lint issues and hound violations

* fixing lint issue

* using assert_called_once_with instead of assert_called_once to make it compatible with python 3.5
2018-05-15 20:43:26 +02:00
Nate Clark
de50d5d9c1 Add Konnected component with support for discovery, binary sensor and switch (#13670)
* Add Konnected component with support for discovery, binary sensor, and switch

Co-authored-by: Eitan Mosenkis <eitan@mosenkis.net>

* Use more built-in constants from const.py

* Fix switch actuation with low-level trigger

* Quiet logging; Improve schema validation.

* Execute sync request outside of event loop

* Whitespace cleanup

* Cleanup config validation; async device setup

* Update API endpoint for Konnected 2.2.0 changes

* Update async coroutines via @OttoWinter

* Make backwards compatible with Konnected < 2.2.0

* Add constants suggested by @syssi

* Add to CODEOWNERS

* Remove TODO comment
2018-05-15 19:58:00 +02:00
c727
612a37b2dd Remove simplepush.io (#14358) 2018-05-15 16:57:51 +02:00
Diogo Gomes
d47006c98f Optimistic MQTT light (#14401)
* Restores light state, case the light is optimistic

* lint

* hound

* hound

* Added mqtt_json

* hound

* added mqtt_template

* lint

* cleanup

* use ATTR
2018-05-15 12:25:50 +02:00
Sebastian Muszynski
16bf10b1a2 Don't poll the Samsung Family hub camera (#14473) 2018-05-15 08:50:07 +02:00
William Scanlon
710533ae8a Minor Wink fixes (#14468)
* Updated Wink light supported feature to reflect what features a given light support.

* Fix typo in wink climate
2018-05-15 07:53:12 +02:00
Matthew Garrett
11c57f9345 Bump lakeside version (#14471)
This should fix a couple of issues with T1013 bulbs, and also handle
accounts that contain unknown devices.
2018-05-15 07:51:32 +02:00
Martin Hjelmare
7562b4164b Fix key error upon missing node (#14460)
* This is needed after gateway ready message generates an update while
  persistence is off, or while the gateway node hasn't been presented
  yet.
2018-05-14 22:52:44 +02:00
Gregory Benner
cf44b77225 Samsung Family hub camera component (#14458)
* add familyhub.py camera

* fix import and REQUIREMENTS

* add to coveragerc

* fix formatting to make houndci-bot happy

* ran scripts/gen_requirements_all.py

* use CONF_IP_ADDRESS

* Revert "ran scripts/gen_requirements_all.py"

This reverts commit 3a38681d8a.

* fix library name

* add missing docstrings and enable polling

* Sort imports
2018-05-14 22:52:35 +02:00
Matt Schmitt
44e9783c7c Add support for direction to fan template (#14371)
* Initial commit

* Update and add tests
2018-05-14 22:37:49 +02:00
Fabian Affolter
1b5c02ff67 Upgrade pygatt to 3.2.0 (#14447) 2018-05-14 21:52:54 +02:00
Russell Cloran
2f74ffcf81 zha: Fix cluster class check in single-cluster device type (#14303)
zigpy now allows custom devices, which might mean that devices have cluster
objects which are not instances of the default, but may be instances of
sub-classes of the default. This fixes the check for finding single-cluster
device entities to handle sub-classes properly.
2018-05-14 16:50:09 +02:00
Fabian Affolter
954e4796b8 Use ATTR_NAME from const.py (#14450) 2018-05-14 13:05:52 +02:00
Fabian Affolter
fb501282cc Add SpaceAPI support (#14204)
* Add SpaceAPI support

* Changes according PR comments

* Add tests

* Remove print

* Minor changes
2018-05-14 09:13:59 +02:00
Robert Svensson
c06351f2a9 Bump requirement to pydeconz v38 (#14452) 2018-05-14 08:41:17 +02:00
Johann Kellerman
6b9c65c9ce Allow qwikswitch sensors as part of devices (#14454) 2018-05-14 08:40:25 +02:00
Philip Rosenberg-Watt
8ae3caa292 Add priority and cycles to LaMetric (#14414)
* Add priority and cycles to LaMetric

Priority can be "info", "warning" (default), or "critical" and
cycles is the number of times the message is displayed. If cycles
is set to 0 we get a persistent notification that has to be dismissed
manually.

* Fix for schema and style

* Fix for style
2018-05-13 18:04:21 +02:00
Fabian Affolter
391e3196ea Upgrade distro to 1.3.0 (#14436) 2018-05-13 18:01:10 +02:00
Fabian Affolter
cb709931e4 Upgrade youtube_dl to 2018.05.09 (#14438) 2018-05-13 18:00:37 +02:00
Fabian Affolter
a750f8444e Upgrade Sphinx to 1.7.4 (#14439) 2018-05-13 18:00:08 +02:00
Fabian Affolter
a5bff4cd8d Upgrade python-telegram-bot to 10.1.0 (#14441) 2018-05-13 17:59:25 +02:00
Fabian Affolter
e0bc894cbb Upgrade pyota to 2.0.5 (#14442)
* Use constants

* Upgrade pyota to 2.0.5
2018-05-13 17:58:57 +02:00
Fabian Affolter
3ec56d55c5 Upgrade requests_mock to 1.5 (#14444) 2018-05-13 17:58:18 +02:00
Martin Hjelmare
b904a4e770 Remove universal wheel setting (#14445)
* Home assistant should not build a universal wheel since we don't
  support Python 2.
2018-05-13 17:57:52 +02:00
Ville Skyttä
146a9492ec Clean up some Python 3.4 remnants (#14433) 2018-05-13 17:56:42 +02:00
cdce8p
e5d714ef52 Fix fan service description (#14423) 2018-05-13 14:41:42 +02:00
Ville Skyttä
4d63baf705 Invoke pytest instead of py.test per upstream recommendation, #dropthedot (#14434)
http://blog.pytest.org/2016/whats-new-in-pytest-30/
https://twitter.com/hashtag/dropthedot
2018-05-13 12:11:55 +02:00
Ville Skyttä
234bf1f0ea Spelling, grammar etc fixes (#14432)
* Spelling, grammar etc fixes

* s/an api data/data of an api/
2018-05-13 12:09:28 +02:00
Ville Skyttä
843789528e Remove extra quotes from docstrings (#14431) 2018-05-13 11:06:15 +02:00
Krasimir Chariyski
ea2c073612 Add Bulgarian to Google TTS (#14422) 2018-05-12 17:46:00 -04:00
Andrey
d1228d5cf4 Look at registry before pulling zwave config values (#14408)
* Look at registry before deciding on ID for zwave values

* Reuse the new function
2018-05-12 17:45:36 -04:00
Andrey
7aec098a05 Bring back typing check. Meanwhile just for homeassistant/*.py (#14410)
* Bring back typing check. Meanwhile just for homeassistant/.py

* Change follow-imports to silent. Add a few more checks.
2018-05-12 17:44:53 -04:00
Ville Skyttä
70af7e5fad Update pylint to 1.8.4 (#14421) 2018-05-12 22:22:20 +02:00
Daniel Høyer Iversen
99e272fc8d Upgrade PyXiaomiGatewa to 0.9.3 (#14420) (Closes: #14417) 2018-05-12 21:12:53 +02:00
cdce8p
990f476ac9 Homekit test cleanup (#14416) 2018-05-12 17:10:19 +02:00
Paulus Schoutsen
9abc13aaa6 Merge pull request #14413 from home-assistant/rc
0.69.1
2018-05-12 09:35:30 -04:00
Paulus Schoutsen
d17186a8b7 Version bump to 0.69.1 2018-05-12 09:34:28 -04:00
Lukas Barth
6fedad7890 Fix waiting for setup that never happens (#14346) 2018-05-12 09:34:13 -04:00
Sebastian Muszynski
b371bf700f Bump PyXiaomiGateway version (#14412) 2018-05-12 15:09:48 +02:00
damarco
01ce43ec7c Use None as initial state in zha component (#14389)
* Return None if state is unknown

* Use None as initial state
2018-05-12 14:41:44 +02:00
Lukas Barth
b903bbc042 Fix waiting for setup that never happens (#14346) 2018-05-12 10:30:21 +02:00
Martin Hjelmare
304137e7ff Fix name of tox pylint env (#14402) 2018-05-12 10:07:10 +02:00
Matthew Treinish
e80628d45b Bump pycmus version (#14395)
This commit bumps the pycmus version used by the cmus component. There
was a bug in the previous version used, 1.0.0, when running in local
mode. This was caused by a mtreinish/pycmus#1 and also was reported in
the home-assistant forums (but not as an issue):

https://community.home-assistant.io/t/cant-install-cmus-component/7961

Version 0.1.1 of pycmus fixes this issue so it should work properly for
users running cmus and home-assistant on the same machine.
2018-05-12 07:51:48 +02:00
Sebastian Muszynski
d6b81fb345 Xiaomi Aqara: Add new cube model (sensor_cube.aqgl01) (#14393) 2018-05-11 22:40:32 +02:00
Paulus Schoutsen
c5cac04e54 Merge pull request #14392 from home-assistant/rc
0.69
2018-05-11 12:38:08 -04:00
Matt Schmitt
621c653fed Allow HomeKit name to be customized (#14159) 2018-05-11 14:22:45 +02:00
Malte Franken
48d70e520f more detailed error message (#14385) 2018-05-11 12:28:28 +02:00
Robin
528ad56530 Adds facebox (#14356)
* Adds facebox

* Update .coveragerc

* Remove facebox

* Add test of faces attribute

* Add event test

* Adds more tests

* Adds tests to increase coverage

* Rename MOCK_FACES to MOCK_FACE

* Adds STATE_UNKNOWN
2018-05-11 09:57:00 +02:00
Martin Hjelmare
be3b227a87 Make mysensors component async (#13641)
* Make mysensors component async

* Use async dispatcher and discovery.
* Run I/O in executor.
* Make mysensors actuator methods async.
* Upgrade pymysensors to 0.13.0.
* Use async serial gateway.
* Use async TCP gateway.
* Use async mqtt gateway.

* Start gateway before hass start event

* Make sure gateway is started after discovery of persistent devices
  and after corresponding platforms have been loaded.
* Don't wait to start gateway until after hass start.

* Bump pymysensors to 0.14.0
2018-05-11 09:39:18 +02:00
damarco
ef8fc1f201 Update sensor state before adding device (#14357) 2018-05-10 22:32:16 -07:00
Paulus Schoutsen
8c0b45af1e Version bump to 0.69.0 2018-05-10 22:40:04 -04:00
Paulus Schoutsen
3b39ab5b94 Remove domain expiry sensor (#14381) 2018-05-10 22:39:35 -04:00
cdce8p
8fcf085829 Rewritten HomeKit tests (#14377)
* Use pytest fixtures and parametrize
* Use async
2018-05-11 01:21:59 +02:00
Abílio Costa
6843893d9f Add "framerate" parameter to generic camera (#14079)
* add "framerate" parameter to generic camera

* fix lint
2018-05-11 00:22:02 +02:00
damarco
e963fc5acf Add support for pressure sensors (#14361) 2018-05-10 23:55:32 +02:00
Paulus Schoutsen
bc664c276c Bump frontend to 20180510.1 2018-05-10 17:38:41 -04:00
Paulus Schoutsen
f192ef8219 Remove domain expiry sensor (#14381) 2018-05-10 23:13:00 +02:00
damarco
db31cdf075 Fix binary_sensor device_state_attributes (#14375) 2018-05-10 21:28:57 +02:00
Andrey
f168226be9 Update to sensibo 1.0.3 with better error reporting (#14380) 2018-05-10 21:11:02 +02:00
Paulus Schoutsen
ea01b127c2 Add local auth provider (#14365)
* Add local auth provider

* Lint

* Docstring
2018-05-10 14:09:22 -04:00
damarco
6e831138b4 Fix binary_sensor async_update (#14376) 2018-05-10 10:59:23 -07:00
Pascal Vizeli
eb2671f4bb Update .coveragerc (#14368) 2018-05-10 13:18:13 +02:00
cdce8p
8d017b7678 script/lint: Ensure there are files to test with pylint (#14363) 2018-05-10 12:47:04 +02:00
Paulus Schoutsen
5ec7fc7ddb Backend tweaks to make authorization work (#14339)
* Backend tweaks to make authorization work

* Lint

* Add test

* Validate redirect uris

* Fix tests

* Fix tests

* Lint
2018-05-10 10:38:11 +02:00
Teemu R
0f3ec94fba debug++ for multiple volume controls (#14349)
Be less noisy for those who have more volume controls than one, mentioned in #13022.
2018-05-09 13:44:42 +02:00
Fabian Affolter
2c566072f5 Upgrade keyring to 12.2.0 and keyrings.alt to 3.1 (#14355) 2018-05-09 11:31:18 +02:00
Nash Kaminski
cf8562a030 Support control of away mode and hold mode in Venstar component. Correctly detect humidifiers. (#14256)
* Implement support for away mode and hold mode in Venstar component

* Fix Venstar humidifier capability detection

* Add option to configure humidifier control in Venstar component

* style fix: add missing space and resolve pylint issues

* Remove quotes
2018-05-09 11:26:29 +02:00
Mal Curtis
a91c1bc668 Add zone 3 for Onkyo media player (#14295)
* Add zone 3 for Onkyo media player

* CR Updates

* Fix travis lint errors
2018-05-08 22:33:38 -04:00
Paulus Schoutsen
f406fd57ac Version bump to 0.69.0b3 2018-05-08 20:55:35 -04:00
Anders Melchiorsen
2d0e3c1402 Ignore NaN values for influxdb (#14347)
* Ignore NaN values for influxdb

* Catch TypeError
2018-05-08 20:55:12 -04:00
Paulus Schoutsen
01ec4a7afd Bump frontend to 20180509.0 2018-05-08 20:54:49 -04:00
Anders Melchiorsen
d43e6a2888 Ignore NaN values for influxdb (#14347)
* Ignore NaN values for influxdb

* Catch TypeError
2018-05-08 20:54:38 -04:00
Paulus Schoutsen
50cea77887 Bump frontend to 20180509.0 2018-05-08 20:48:46 -04:00
Mario Di Raimondo
6231394614 Waze Travel Time: optional inclusive/exclusive filters (#14000)
* Waze Travel Time: optional inclusive/exclusive filters

Added optional `inc_filter` and `excl_filter' params that allow to refine the reported routes: the first is not always the best/desired. A simple case-insensitive filtering (no regular expression) is used.

* fix line lenght

* fix spaces

* Rename var

* Fix typo

* Fix missing var
2018-05-09 00:35:03 +02:00
Aaron Bach
f516cc7dc6 Adds useful attributes to RainMachine programs and zones (#14087)
* Starting to add attributes

* All attributes added to programs

* Basic zone attributes in place

* Added advanced properties for zones

* Working to move common logic into component + dispatcher

* We shouldn't calculate the MAC with every entity

* Small fixes

* Small adjustments

* Owner-requested changes

* Restart

* Restart part 2

* Added ID attribute to each switch

* Collaborator-requested changes
2018-05-08 18:10:03 -04:00
Evgeniy
9c7523d7b0 Improving icloud device tracker (#14078)
* Improving icloud device tracker

* Adding config validations for new values

* Adding config validations for new values

* Moving icloud specific setup to platform schema. Setting default in platform schema.
2018-05-08 23:42:57 +02:00
Andrey
10505d542a Make sure zwave nodes/entities enter the registry is proper state. (#14251)
* When zwave node's info is parsed remove it and re-add back.

* Delay value entity if not ready

* If node is ready consider it parsed even if manufacturer/product are missing.

* Add annotations
2018-05-08 15:30:28 -04:00
Nick Whyte
e12994a0cd Fix BOM weather '-' value (#14042) 2018-05-08 13:35:55 -04:00
stephanerosi
ff01aa40c9 Add help for conversation/process service (#14323)
* Add help for conversation/process service

* Add logging to debug text received when service is called

* Move conversation to specific folder
2018-05-08 13:24:27 -04:00
Paulus Schoutsen
6199e50e80 Fix Insteon PLM coverage 2018-05-08 11:55:04 -04:00
Tod Schmidt
c664c20165 Snips: Added slot values for siteId and probability (#14315)
* Added solt values for siteId and probability

* Update snips.py

* Update test_snips.py
2018-05-08 11:43:31 -04:00
David Broadfoot
eb551a6d5a Gogogate2 0.1.1 (#14294)
* Gogogate2 - bump version

Uses latest version of library which ensures commands to device are idempotent

* Update requirements_all

* Expose sensor temperature

* update version

* import attribute

* Set temperature

* Remove temperature attribute

Removed temperature attribute until it can be re-implemented as a separate sensor.

* Update ordering

* Fix copy-&-paste issue
2018-05-08 11:42:18 -04:00
m4dmin
4343659742 add 2 devices (#14321)
* add 2 devices

io:RollerShutterUnoIOComponent
io:ExteriorVenetianBlindIOComponent

* add 2 devices

* Update tahoma.py

* Fix hounci-bot violation

* Fixed Travis CI build failure

./homeassistant/components/cover/tahoma.py:83:13: E125 continuation line with same indent as next logical line

* Fixed Travis CI build failure

E125 continuation line with same indent as next logical line

* Fixed Travis CI build failure

E127 continuation line over-indented for visual indent

* Fix indent

* Change check
2018-05-08 13:43:07 +02:00
Mattias Welponer
230bd3929c Add more homematicip cloud components (#14084)
* Add support for shutter contact and motion detector device

* Add support for power switch devices

* Add support for light switch device

* Cleanup binary_switch and light platform

* Update comment
2018-05-08 09:57:51 +02:00
Gerard
ba7333e804 Add sensors for BMW electric cars (#14293)
* Add sensors for electric cars

* Updates based on review of @MartinHjelmare

* Fix Travis error

* Another fix for Travis
2018-05-08 09:52:21 +02:00
Mike
48b13cc865 Update hitron_coda.py to fix login for Shaw modems (#14306)
I have a Hitron modem provided by Shaw communications rather than from Rogers as the Docs specify for this device_tracker but it seems like the api/code is all the same except that the login failed due to the password being passed as "pws" instead of "pwd". Making that one character change allowed HASS to read the connected device details from my Hitron modem. If this difference is actually one that stands between the Rogers-provided Hitron modems and the Shaw-provided variant, I am happy to create another device-tracker file for the Shaw modem. I just figured I would go with the simplest solution first.
2018-05-08 09:26:46 +02:00
Aaron Bach
e7c7b9b2a9 Adds unique ID to Roku for entity registry inclusion (#14325)
* Adds unique ID to Roku for entity registry inclusion

* Owner-requested changes
2018-05-07 13:18:51 -04:00
Paulus Schoutsen
8d24541ffe Version bump to 0.69.0b2 2018-05-07 13:12:54 -04:00
Paulus Schoutsen
b1eb35ee11 Ignore more loading errors (#14331) 2018-05-07 13:12:43 -04:00
Paulus Schoutsen
c7166241f7 Ignore more loading errors (#14331) 2018-05-07 13:12:12 -04:00
Paulus Schoutsen
a4e1615127 Version bump to 0.69.0b1 2018-05-07 10:05:34 -04:00
Javier Gonel
3e5d76efb2 fix(hbmqtt): partial packets breaking hbmqtt (#14329)
This issue was fixed in hbmqtt/issues#95 that was released in hbmqtt 0.9.2
2018-05-07 10:01:23 -04:00
Paulus Schoutsen
6a74fa344d Revert custom component loading logic (#14327)
* Revert custom component loading logic

* Lint

* Fix tests

* Guard for infinite inserts into sys.path
2018-05-07 10:01:22 -04:00
Paulus Schoutsen
c4ec2e3434 Fix module names for custom components (#14317)
* Fix module names for custom components

* Also set __package__ correctly

* bla

* Remove print
2018-05-07 10:01:22 -04:00
cdce8p
c48986a467 Add debounce to move_cover (#14314)
* Add debounce to move_cover

* Fix spelling mistake
2018-05-07 10:01:22 -04:00
Justin Loutsenhizer
ab621808bd Add missing 'sensor' to ABODE_PLATFORMS (#14313)
This fixes missing light, humidity, temperature sensors from abode component.
2018-05-07 10:01:21 -04:00
Paulus Schoutsen
5c88e897af Update netdisco to 1.4.1 2018-05-07 10:01:06 -04:00
Paulus Schoutsen
6318178a8b Update netdisco to 1.4.1 2018-05-07 10:00:54 -04:00
Javier Gonel
a2b8ad50f2 fix(hbmqtt): partial packets breaking hbmqtt (#14329)
This issue was fixed in hbmqtt/issues#95 that was released in hbmqtt 0.9.2
2018-05-07 09:52:33 -04:00
Paulus Schoutsen
5c95c53c6c Revert custom component loading logic (#14327)
* Revert custom component loading logic

* Lint

* Fix tests

* Guard for infinite inserts into sys.path
2018-05-07 11:25:48 +02:00
Jerad Meisner
e60d066514 Converted SABnzbd to a component (#12915)
* Converted SABnzbd to a component

* fixed async issues

* Made sabnzbd scan interval static. More async fixes.

* Sabnzbd component code cleanup

* Skip sensor platform setup if discovery_info is None
2018-05-07 09:35:55 +02:00
cdce8p
91fe6e4e56 Add debounce to move_cover (#14314)
* Add debounce to move_cover

* Fix spelling mistake
2018-05-06 20:55:38 -04:00
Paulus Schoutsen
34727be5ac Fix module names for custom components (#14317)
* Fix module names for custom components

* Also set __package__ correctly

* bla

* Remove print
2018-05-06 20:54:56 -04:00
Justin Loutsenhizer
107769ab81 Add missing 'sensor' to ABODE_PLATFORMS (#14313)
This fixes missing light, humidity, temperature sensors from abode component.
2018-05-06 19:18:26 +02:00
Russell Cloran
63cc179ea2 zha: Bump to zigpy 0.1.0 (#14305) 2018-05-06 11:17:05 +02:00
thepotoo
2bb1a95098 Add unique_id to MQTT switch (#13719) 2018-05-06 08:21:02 +02:00
Paulus Schoutsen
f3411f8db2 Version bump to 0.70.0.dev0 2018-05-05 11:42:32 -04:00
Paulus Schoutsen
1e31af77de Version bump to 0.69.0b0 2018-05-05 11:41:55 -04:00
Paulus Schoutsen
2326312bee Merge branch 'master' into dev 2018-05-05 11:41:29 -04:00
Paulus Schoutsen
83e342daf2 Update frontend to 20180505.0 2018-05-05 11:35:42 -04:00
Paulus Schoutsen
a4b69833d4 Update translations 2018-05-05 11:35:02 -04:00
Tom Harris
64ba2c63c7 Add All-Linking capabilities (#14065)
* Setup all-linking service

* Remove extra line

* Remove linefeed and tab escape chars

* Add services delete_all_link, load_all_link_database and print_all_link_database

* Check if reload is set

* Confirm entity is InsteonPLMEntity before attempting to load or print ALDB

* Debug load and print ALDB

* Debug print aldb

* Debug print_aldb

* Get entity via platform

* Track Insteon entities in component

* Store entity list in hass.data

* Add entity to hass.data

* Add ref to hass in InsteonPLMEntity

* Pass hass correctly to InsteonPLMBinarySensor

* Fix reference to ALDBStatus.PARTIAL

* Print ALDB record as string

* Get ALDB record from memory address

* Reformat ALDB log output

* Add print_im_aldb service

* Remove reference to self in print_aldb_to_log

* Remove reference to self in print_aldb_to_log

* Fix spelling issue with load_all_link_database service

* Bump insteonplm to 0.9.1

* Changes from code review

* Code review changes

* Fix syntax error

* Correct reference to cv.boolean and update requirements

* Update requirements

* Fix flake8 errors

* Reload as boolean test

* Remove hass from entity init
2018-05-05 11:15:20 -04:00
Ron Šmeral
2e8eaf40f7 Onkyo: SUPPORT_VOLUME_STEP (#14299) 2018-05-05 17:06:32 +02:00
Robin
b9e893184a Refactor ImageProcessingFaceEntity (#14296)
* Refactor ImageProcessingFaceEntity

* Replace STATE_UNKNOWN with None
2018-05-05 16:57:53 +02:00
Jason Kingsbury
4d085882d5 Add support for max_volume (#13822)
* onkyo: add support for max volume range

* onkyo: make flake8 happy

* onkyo: fix PEP8 D205 on line 181

* onkyo: use range for max_volume configuration

* onkyo: fix line too long
2018-05-05 10:30:54 -04:00
Jesse Newland
f6e29a6647 Add domain to labels and count state changes to Prometheus (#14253)
* Add domain to labels

* Count state changes
2018-05-05 10:23:01 -04:00
Jesse Newland
1a936220e9 Add alarmdotcom sensor status (#14254)
* bump to match Xorso/pyalarmdotcom#9

* Load additional status attributes

* missed a spot
2018-05-05 10:21:58 -04:00
Robert Svensson
8410b63d9c deCONZ add new device without restart (#14221)
* Add new device without restarting hass

* Remove debug prints

* Fix copy paste error

* Fix comments from balloob
Add tests to verify signalling with new added devices

* Fix hound comments
Add test to verify when new sensor is added

* Fix tests

* Unload entry should unsubscribe all deconz dispatchers

* Make sure mock setup also creates unsub in hass data

* Fix copy paste issue

* Lint
2018-05-05 10:11:00 -04:00
Lukas Barth
af8cd63838 Matrix Chatbot (#13355)
* Add first version of the Matrix bot

* It's a stupid but necessary change…

* Dont list it twice

* All hail the linter!

* More linter-pleasing

* Use the correct user ID

* Add expression commands

* Add tests for new validators

* Fix room alias handling

* Wording

* Defer setup

* Simplify commands

* Handle exceptions

* Update requirements

* Review

* Move login back to constructor

* Fix review comments
2018-05-05 10:00:36 -04:00
cdce8p
95d27bd1fa Sensor device classes (#14282)
* Added light device class, moved device classes to const

* Removed unnecessary icons

* Replace 'lux' with 'lx'

* Fix comment

* Changed device_class name
2018-05-05 09:37:40 -04:00
blackwind
ec3ce4c80d Publish attributes unconditionally (#14179)
* Publish attributes unconditionally

Because the attribute publish command was previously hidden behind `if val:`, falsy values like False and 0.0 weren't being published, thereby making Statestream -- particularly in the case of booleans, where the first True would be retained indefinitely -- a completely worthless indicator of state.

* Change bool test to False to confirm falsy values pass
2018-05-05 09:31:39 -04:00
Nick Whyte
5ade84d75f BOM Weather throttle fix (#14234) 2018-05-05 11:17:27 +02:00
Fabian Affolter
75bf483071 Upgrade astral to 1.6.1 (#14297) 2018-05-05 10:45:09 +02:00
Diogo Gomes
354470469f Fix filter sensor missing window_size argument (#14252)
* missing window_size argument

* test throttle filter configuration
2018-05-05 03:10:08 +02:00
Matt Schmitt
255a85ad02 HomeKit: Support triggered state for alarm_control_panel (#14285) 2018-05-05 00:09:16 +02:00
cdce8p
bb76ba67f3 Homekit: Changed device_class requirement Humidity Sensor (#14277) 2018-05-04 22:48:38 +02:00
Fabian Affolter
7900ba30bf Upgrade holidays to 0.9.5 (#14274) 2018-05-04 17:09:05 +02:00
cdce8p
e37fd5b132 Update HAP-python to 2.0.0 (#14278)
* Fixed async (added 'async_add_job' and 'add_job')

* Driver status

* Use pyhap category constants

* Changed 'set_broker' to 'set_driver'

* Changed loader method names

* Use 'serv.configure_char'

* Use 'self.set_info_service'

* Use 'self.add_preload_service'

* Fix hound issue

* Updated HAP-python to 2.0.0
2018-05-04 16:46:00 +02:00
Fabian Affolter
f98525acbf Upgrade attrs to 18.1.0 (#14281) 2018-05-04 08:58:34 -04:00
Otto Winter
36cf2125ce Issue Template Fix CRLF (#14283) 2018-05-04 13:49:13 +02:00
Boyi C
c80b752d0e fix check config not working after #14211 (#14259) 2018-05-04 12:29:07 +02:00
Anders Melchiorsen
fa0ad7b317 Color fixes for Wink lights (#14263) 2018-05-04 12:28:56 +02:00
Fabian Affolter
b49d98407c Remove feature request 2018-05-04 10:56:35 +02:00
Fabian Affolter
5f8f6666e6 Update issue templates 2018-05-04 10:55:55 +02:00
Fabian Affolter
54ccbbcb64 Update issue templates 2018-05-04 10:54:55 +02:00
Fabian Affolter
a7a3cff0f1 Update issue templates 2018-05-04 10:52:20 +02:00
Fabian Affolter
9859840b9c Update issue templates 2018-05-04 10:48:13 +02:00
cdce8p
8cabec7ac1 Fix ZWave light brightness (#14261)
* Fix ZWave light brightness
* The brightness should always be an integer
* Changed to round
2018-05-03 23:28:03 +02:00
Paulus Schoutsen
15e75b07d8 Allow fetching media player covers via websocket connection (#14233)
Lint
2018-05-03 22:03:26 +02:00
Paulus Schoutsen
58257af289 Add fetching camera thumbnails over websocket (#14231)
* Add fetching camera thumbnails over websocket

* Lint
2018-05-03 22:02:59 +02:00
Erik Eriksson
4ecce2598a Re-enable eliqonline requirement (#14265) 2018-05-03 19:54:37 +02:00
cdce8p
e68b52d50d Demo Sensor - Added device_class support (#14269) 2018-05-03 19:51:36 +02:00
roiff
c9de2f015b HomeKit - Climate: power state on/off support (#14082)
* add power state  support on off
* Added check for current operation mode
* Extended 'set_heat_cool'
* Added tests
2018-05-03 18:22:43 +02:00
giangvo
ef4498ec27 Issue/add template fans (#12027)
* add template fan

* add-template: address PR comments

* add-template: remove unused import

* add-template: revert async_track_state_change change

* add-template: use yield from

* Revert "add-template: use yield from"

This reverts commit 1e053714a7.

* add-template: use yield

* add-template: remove unused import

* add-template: remove async_add_job usages

* use components

* add-template: use async/await

* add-template: fix style

* add-template: remove str()

* address pr comments

* fix style
2018-05-02 17:45:31 -04:00
Diogo Gomes
c851dfa2c7 Restores switch state, case the switch is optimistic (#14151)
* Add restore_state to optimistic switch

* no need to schedule update

* test added

* lint

* new async syntax

* lint
2018-05-02 17:29:07 -04:00
Mark Coombes
64b9fbd8d9 Add prereqs for HomeKit Controller (#14172) 2018-05-02 16:28:43 -04:00
Andrey
f72d568374 Add unique_id to zwave node entity (#14201)
* Add unique_id to zwave node entity

* Wait 30s before adding zwave node if its unique_id is not ready

* Use only node_id in unique_id. Update name, manufacturer, and product attributes on node update.
2018-05-02 16:10:26 -04:00
Per Osbäck
351e8921fa python_openzwave update config service (#12060)
* update python-openzwave to 4.1.0

* add service which updates the configuration files from github

* 0.4.3
2018-05-02 15:06:09 -04:00
Mick Vleeshouwer
b66be59598 Add PostNL sensor (Dutch Postal Services) (#12366)
* Add basic PostNL sensor (WIP)

* Update PostNL sensor

* Bump version

* Small updates to PostNL package based on feedback

* Remove unused import

* Pass api to sensor

* Refactor based on feedback

* Update based on feedback

* Fix feedback

* Clean up

* Bugfiix

* Bugfix

* SCAN_INTERVAL fix

* Remove unused import

* Refactor for new wrapper implementation

* Update postnl package requirement

* Change throttle logic

* Update package version

* Add new line

* Minor changes

* Change refresh time to 30 minutes

* Update requirements_all.txt
2018-05-02 14:37:41 -04:00
Otto Winter
14c7fa8882 WUnderground unique ids (#13311)
* WUnderground unique_id

* Remove async_generate_entity_id

* Lint

* Address comment
2018-05-02 14:23:07 -04:00
Mathieu Velten
ce98dfe395 Add support for tracking devices on Netgear access points (#13331)
* Netgear: add support for tracking devices on access points

* Netgear: add SSL support and autodetection
2018-05-02 09:38:24 -04:00
Anders Melchiorsen
bf056b6f01 Fix Hue color state for missing xy (#14230) 2018-05-02 09:25:08 -04:00
Sebastian Muszynski
8b13658d3b Improve config schema of the blackbird component (#14007)
* Import moved, return values removed and redundant log message removed

* Improve config schema of the blackbird component

* Tests updated

* Handle updated

* Schema fixed
2018-05-02 09:21:50 -04:00
Diogo Gomes
e968b1a0f4 UPnP code cleanup (#14235)
* missing async calls

* lint

* cleanup
2018-05-02 09:15:30 -04:00
Mohamad Tarbin
6453ea4e61 Add Social Blade Sensor (#14060)
* Adding Dominion Energy Sensor

* Update : remove white spacves and set the update time to be daily

* Update : update spacing as per hound suggestions, Move imports

* Update :  Fix  Travis CI build errors

* Update Documentations on method levels

* Update Documentations on method levels

* Update Documentations on method levels

* Add Exception Handeling if login failed, add PLATFORM_SCHEMA

* Add Exception Handeling if login failed, add PLATFORM_SCHEMA

* Add Exception Handeling if login failed, add PLATFORM_SCHEMA

* Update dominionenergy.py

* Adding Selenium to requirements_all.txt

* Checking the username/password while setup

* Checking the username/password while setup

* removing extra white space

* Update : Adding the Platform only if credentials works

* Update : Add PlatformNotReady exception

* Update : Add PlatformNotReady exception

* Update .coveragerc

* Remove change

* Adding USCIS component

* Adding Line after the class DOC

* Update : Extract USCIS logic code to Component

* Update : Extract USCIS logic code to Component

* Adding CURRENT_STATUS

* Change Error handling, remove date from attributes

* Update the Version for USCIS

* Add Social Blade Sensor

* Update class documentation

* Update coverage and requirements_all

* Update : houndci error with intent

* Update : Add coverage

* Update uscis.py

* Add comments

* Add comments

* Delete dominionenergy.py

* Update requirements_all.txt

* Update .coveragerc

* Update .coveragerc

* Update .coveragerc

* Update : update after code review

* Fix remaining issues
2018-05-01 22:27:20 +02:00
Matt Snyder
c2d00be91e Allow independent control of white level on flux_led component (#13985)
* Allow independent control of white level on flux_led component.

Also preserve brightness on color change.

* Limit white value support to RGBW mode.

* Requested changes.

* Correct liniting issues

* Formatting
2018-05-01 15:38:45 -04:00
Otto Winter
e4655a7e63 Add MQTT Sensor device_class (#14033)
* Add MQTT Sensor device_class

* Add test
2018-05-01 15:38:08 -04:00
Aaron Bach
7a05471912 Converts RainMachine to hub model (part 2) (#14225)
* Converts RainMachine to hub model (part 2)

* Small style adjustments for consistency

* Moving MAC calculation to one-time call in component

* Removing unneeded attribute

* Bumping Travis

* Lint
2018-05-01 15:36:43 -04:00
escoand
8d5c3a2b91 add volumio discovery (#14220)
* add volumio discovery

* add missing library

* Update volumio.py
2018-05-01 15:20:38 -04:00
corneyl
2f0fc0934f Buienradar improvements: continuous sensors and unique ID's (#13249)
* Force update continuous sensors when new measurement available.

* Added unique ID's based on coordinates, sensor type and client name.

* Fixed over-indentation (hound review)

* Revert "Added unique ID's based on coordinates, sensor type and client name."

This reverts commit 3345e67a15.

* Fix lint errors.

* Re-added unique ID's based on location.

* Removed wrong error logging.

* Removed creating UUID from unique id

* Lint
2018-05-01 15:06:41 -04:00
Paulus Schoutsen
83d300fd11 Custom component loading cleanup (#14211)
* Clean up custom component loading

* Fix some tests

* Fix some stuff

* Make imports work again

* Fix tests

* Remove debug print

* Lint
2018-05-01 20:57:30 +02:00
Ville Skyttä
5d96751168 panasonic_viera: Provide unique_id from SSDP UDN, if available (#13541) 2018-05-01 14:54:06 -04:00
NovapaX
38560cda1c Allow to set a desired update interval for camera_proxy_stream view (#13350)
* allow to set a desired update interval for camera_proxy_stream view

* lint

* refactor into a seperate method.
Keep the handle_async_mjpeg_stream method to be overridden by platforms
so they can keep proxying the direct streams from the camera

* change descriptions

* consolidate

* lint

* travis

* async/await and force min stream interval for fallback stream.

* guard clause. Let the method raise error on interval.

* is is not =

* what to except when you're excepting

* raise ValueError, remove unnecessary 500 response
2018-05-01 14:49:33 -04:00
blackwind
bf53cbe08d Support setting explicit mute value for Panasonic Viera TV (#13954)
* Use module's methods instead of API calls

* Use module's methods instead of API calls for media commands
2018-05-01 14:41:36 -04:00
Ruben
b00f771541 Add more parameters for DSMR sensor (#13967)
* Add more parameters for DSMR component

* Add suiting icon for power failure

* Add suiting icon for swells & sags

* Fix tab indentation -> spaces

* Fix too long lines (PEP8)
2018-05-01 14:40:48 -04:00
Otto Winter
9bc8f6649b Template Sensor add device_class support (#14034)
* Template Sensor Device Class Support

* Lint

* Add tests
2018-05-01 14:32:44 -04:00
Fabian Affolter
b0cccbfd9f Upgrade mypy to 0.590 (#14207) 2018-05-01 14:14:28 -04:00
Simon Hörrle
e78497789b Change the divisor for total consumption output (#14215)
According to my observations, the "switch_energy" value displayed by Pyfritzhome is the sum of Wh over the last week since measurement.
As a result, the correct divisor for representing output as kWh would be 1000 instead of 10000.
2018-05-01 14:13:35 -04:00
Paulus Schoutsen
d82693b460 Allow easy extension of websocket API (#14186)
* Allow easy extension of websocket API

* Lint

* Move panel test to frontend

* Register websocket commands

* Simplify test

* Lint
2018-05-01 13:35:23 -04:00
Paulus Schoutsen
cdd45e7878 Foundation for users (#13968)
* Add initial user foundation to Home Assistant

* Address comments

* Address comments

* Allow non-ascii passwords

* One more utf-8 hmac compare digest

* Add new line
2018-05-01 18:20:41 +02:00
sander76
b994c10d7f HomematicIP cloud: Add logic to check accesspoint connection state (#14203)
* Add logic to check accesspoint connection state

* lint

* changes as per @balloobs comments.

* pylint fix
2018-05-01 11:01:13 -04:00
Russell Cloran
9d4d1c8233 zha: Clean up binary_sensor listener registration/state updates (#14197)
- Instead of registering listeners in the entity __init__, do it in
   async_added_to_hass to avoid errors updating an entity which isn't fully
   set up yet
 - Change from schedule_update_ha_state to async_schedule_update_ha_state
2018-05-01 08:55:25 -04:00
Diogo Gomes
a4e0c9c251 Fixes #14169 (Upgrade pyupnp-async to 0.1.0.2) (#14210)
* Fixes #14169 (upstream version bump)

* bump pyupnp-async version
2018-05-01 08:51:47 -04:00
Philipp Schmitt
626d6df545 Update CODEOWNERS (#14214) 2018-05-01 10:14:33 +02:00
Paulus Schoutsen
12dff5baa8 Add room hint support to Google Assistant cloud (#14180) 2018-04-30 21:05:29 +02:00
mvn23
6e0a3abf66 Fix TypeError on round(self.humidity) (fixes #13116) (#14174)
* Fix TypeError on round(self.humidity)

Some weather platforms postpone the first data fetch for a while on init. As a result round(self.humidity is called before it is assigned a value, producing an error. This is a fix for that.

* Rewrite to avoid false negative evaluation

As per the suggestion from @OttoWinter, rewrite to avoid matching e.g. 0.0 as false.
2018-04-30 13:27:45 -04:00
Mahesh Subramaniya
eceece866d Updating darksky default update interval to 5 mins (#14195)
With Darksky allowing only 1000 API requests per day, 2 minutes retry seems to be bit closer to running over the limit and actually it did for 5 days in my account. Hence proposing a change to 5 minutes to keep the API happy and also it doesn't hurt to check the weather for every 5 mins than 2 mins someone lives in Jupiter :-P
2018-04-30 11:48:51 -04:00
Paulus Schoutsen
853a16938b Fix poorly formatted automations (#14196) 2018-04-30 09:56:42 -04:00
Paulus Schoutsen
5dcad89a0d Do not sync entities with an empty name (#14181) 2018-04-30 09:18:18 -04:00
cdce8p
46c260fd85 Added CONF_IP_ADDRESS to HomeKit (#14163) 2018-04-30 08:58:17 -04:00
Otto Winter
76c9c0179b Improve chromecast disconnection logic (#14190)
* Attempt Cast Fix

* Cleanup
2018-04-30 08:46:44 -04:00
Fabian Affolter
d7eced95fa Upgrade numpy to 1.14.3 (#14187) 2018-04-30 09:28:00 +02:00
Russell Cloran
02a12a0bb4 zha: Support remotes/buttons (#12528) 2018-04-29 23:31:27 -07:00
Anders Melchiorsen
30d987f59f Revert Hue color state to be xy-based (#14154) 2018-04-30 00:49:19 +02:00
Jens Østergaard Nielsen
aa8bd37143 Added update_interval to maxcube (#14143) 2018-04-29 20:57:57 +02:00
Paulus Schoutsen
4c0024fd97 Another coverage fix 2018-04-29 14:15:39 -04:00
Paulus Schoutsen
74320306a1 Add mitemp_bt to coverage 2018-04-29 14:08:33 -04:00
Hate-Usernames
113bdc493a Allow transitioning to colour temp for tradfri (#14157) 2018-04-29 16:54:44 +01:00
escoand
8e7f500f28 Add precipitation to OpenWeatherMap forecast (#13971)
* add initial precipitation support

* move attr to component

* remove blank line

* add forecast attributes to platform and update demo

* add tests

* break long lines

* calc lower temp correctly

* move all new attributes to component

* convert temp low only when existing
2018-04-29 17:50:49 +02:00
Fabian Affolter
d352dee9b7 Upgrade netdisco to 1.4.0 (#14152) 2018-04-29 16:21:46 +02:00
Kane610
3fd4987baf deCONZ allow unloading of config entry (#14115)
* Working but incomplete

* Remove events on unload

* Add unload test

* Fix failing sensor test

* Improve unload test

* Move DeconzEvent to init

* Fix visual under-indentation
2018-04-29 10:16:20 -04:00
Matthew Garrett
ef48a7ca2c Fix Python 3.6 compatibility for HomeKit controller (#14160)
Python 3.6's http client passes an additional argument to _send_output,
so add that to the function definition.
2018-04-29 09:46:36 +02:00
Paulus Schoutsen
fd038b6de9 Disable eliqonline requirement (#14156)
* Disable eliqonline requirement

* Disable pylint import error
2018-04-28 20:15:00 -04:00
Otto Winter
a4bf421044 Convert more files to async/await syntax (#14142)
* Move more files to async/await syntax

* Attempt Work around pylint bug

Using lazytox :P
2018-04-28 19:26:20 -04:00
Otto Winter
a0b14c2913 Light mqtt_json: Add HS color support (#14029)
* Light mqtt_json HS color support

* Lint

* Catch float ValueError
2018-04-29 00:33:10 +02:00
engrbm87
44ddc6ba62 deluge-components-update (#14016) 2018-04-29 00:16:22 +02:00
Gabe
07f94eaa92 Fixed datetime values (#14153) 2018-04-29 00:12:40 +02:00
Fabian Affolter
4205dc0f7c Upgrade psutil to 5.4.5 (#14135) 2018-04-28 23:17:38 +02:00
Matt Schmitt
2091f86e25 Clean up HomeKit accessory information characteristics (#14114)
* Update accessory information characteristics
* Add firmware revision characteristic
2018-04-28 23:17:30 +02:00
Fabian Affolter
84f163252a Upgrade youtube_dl to 2018.04.25 (#14136) 2018-04-28 23:17:10 +02:00
Fabian Affolter
9a9161477f Upgrade python-telegram-bot to 10.0.2 (#14144) 2018-04-28 23:16:51 +02:00
Fabian Affolter
449085313b Upgrade tapsaff to 0.2.0 (#14137) 2018-04-28 23:16:34 +02:00
Fabian Affolter
95f2ad2299 Upgrade sqlalchemy to 1.2.7 (#14138) 2018-04-28 23:16:01 +02:00
Fabian Affolter
7bdd4dd960 Upgrade pylast to 2.2.0 (#14139) 2018-04-28 23:15:32 +02:00
Anders Melchiorsen
e6d4501ee3 Fix color setting of tplink lights (#14108) 2018-04-28 17:12:11 -04:00
Paulus Schoutsen
93fe61bf13 System log: make firing event optional (#14102)
* Syste log: make firing event optional

* Add test

* Lint

* Doc string
2018-04-28 17:09:38 -04:00
Charles Garwood
b352b761f3 Bump pyvizio to 0.0.3 (#14147)
* Bumping pyvizio version

* Bump pyvizio version
2018-04-28 21:05:27 +02:00
Marcus
8d87b9fed5 Logitech Pop support for emulated_hue component (#12833)
* Update hue_api.py

add dummy group handler for logitech-pop

* Update __init__.py

add HueGroupView for logitech pop

* Update __init__.py

removed whitespace on blankline

* fix line limit and space

* fix indents

* fix more docstring and formatting issues.

* fix more whitespace issues

* Fix pylint issue
2018-04-28 20:39:21 +02:00
Fabian Affolter
ea5c336ab4 Upgrade restrictedpython to 4.0b3 (#14140) 2018-04-28 19:21:37 +02:00
Maciej Bieniek
c78e8eb578 Add support for light sensors with 'lx' unit to HomeKit (#14131)
* add support for light sensors with lx unit

* add test for light sensor with 'lx' unit
2018-04-28 17:14:34 +02:00
Aaron Bach
8bc497ba1d Move RainMachine to component/hub model (#14085)
* Moves RainMachine to component/hub model

* Updated requirements

* Updated coverage

* Hound violations

* Collaborator-requested changes

* Small formatting updates

* Removed references to remote API

* Collaborator-requested changes

* Collaborator-requested changes

* Fixed attribution
2018-04-28 15:46:58 +02:00
Fabian Affolter
1d41321f8f Upgrade colorlog to 3.1.4 (#14132) 2018-04-28 14:03:09 +02:00
ratcash
00706ad90c Support Xiaomi Mijia Bluetooth Wireless Temperature and Humidity Sensor (#13955) 2018-04-28 13:35:51 +02:00
Colin O'Dell
2749ca4ef4 Update QNAP lib to 0.2.6; handle null temps gracefully (#14117)
There's one particular QNAP model which sometimes return empty/null temperatures
for certain disks. This commit ensures that this model can be integrated with HASS
without causing KeyErrors or other exceptions - if this edge case is hit, the
sensor will simply show `0` instead.
2018-04-28 12:39:45 +02:00
Sebastian Muszynski
58ae8d91f9 Fix the optional friendly name of the Yeelight (Closes: #14088) (#14110) 2018-04-28 12:35:19 +02:00
Matthew Garrett
7e39a5c4d5 Change Eufy brightness handling (#14111)
Eufy device state isn't reported if the bulb is off, so avoid stamping on
the previous values if the bulb isn't going to give us useful information.
In addition, improve handling of bulb turn on if we aren't provided with a
brightness - this should avoid the bulb tending to end up with a brightness of
1 after power cycling.
2018-04-27 16:39:06 -04:00
Anders Melchiorsen
0b350993b5 Improve precision of Hue color state (#14113) 2018-04-27 13:18:58 +02:00
Otto Winter
9d1f9fe204 Improve MQTT topic validation (#14099)
* Improve MQTT topic validation

* Fix test

* Improve length check
2018-04-27 13:15:45 +02:00
Kane610
4b06392442 Zone component config entry support (#14059)
* Initial commit

* Add error handling to config flow
Change unique identifyer to name
Clean up hound comments

* Ensure hass home zone is created with correct entity id
Fix failing tests

* Fix rest of tests

* Move zone tests to zone folder
Create config flow tests

* Add possibility to unload entry

* Use hass.data instead of globas

* Don't calculate configures zones every loop iteration

* No need to know about home zone during setup of entry

* Only use name as title

* Don't cache hass home zone

* Add new tests for setup and setup entry

* Break out functionality from init to zone.py

* Make hass home zone be created directly

* Make sure that config flow doesn't override hass home zone

* A newline was missing in const

* Configured zones shall not be imported
Removed config flow import functionality
Improved tests
2018-04-26 17:59:22 -04:00
Paulus Schoutsen
f5de2b9e5b Bump frontend to 20180426 2018-04-26 16:39:14 -04:00
Robin
3e18078700 Adds update file_path service to local_file camera (#13976)
* WIP: Add update_file service to local_file camera

* Add event on update

* Update local_file.py

* Update services.yaml

* Fix indent

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update local_file.py

* Update test_local_file.py

* Update local_file.py

* Adds file_path to device_state_attributes

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update local_file.py

* Update test_local_file.py

* fixed test_update_file_path

* Update local_file.py

* Update test_local_file.py

* Update test_local_file.py

* Update services.yaml

* Update local_file.py

* Update local_file.py

* Update test_local_file.py

* Update local_file.py
2018-04-26 15:01:58 -04:00
GotoCode
ff7b51259e Updated list of AWS regions for Amazon Polly (#14097)
Fixes #14052
2018-04-26 19:35:28 +02:00
Daniel Perna
47e143d5a1 Update pyhomematic to 0.1.42 (#14095)
* Updated pyhomematic to 0.1.42

* Updated pyhomematic to 0.1.42
2018-04-26 19:30:28 +02:00
Daniel Høyer Iversen
d7f7735490 Fix timezone issue when calculating min/max values in tibber #14009 (#14080)
* fix timezone issue in tibber #14009

* remove debug print
2018-04-26 09:49:35 +02:00
Mattias Welponer
8c2dedab52 Re-implement HomematicIP cloud to async (#13468)
* Recode to async version of homematicip-rest-api

* Remove blank line

* Cleanup of access point status class

* Fix to loong line

* Fix import errors

* Bugfix missing wait the _retry_task for sleep command

* Update comment

* Updates after review

* Small updates of logging and property name

* Fix DOMAIN and revert back to lowercase snakecase strings

* Fix intention and tripple double quotes

* Fix travis build

* Remove unnecessary state attributes

* Fix optional name in configuration

* Further reduction of state attributes
2018-04-25 15:57:44 -04:00
Anders Melchiorsen
241a0793bb Add Sonos device attribute with grouping information (#13553) 2018-04-25 20:31:42 +02:00
c727
a94864c86f Modify weather components for "new" frontend card (#14076)
* Enable weather condition for all forecasts (OWM)

* Remove entity_picture from BR

* Remove summary texts from Dark Sky

* Update test_darksky.py
2018-04-25 12:37:57 +02:00
Mitko Masarliev
f23f9465d3 New sensor domain expiry (#14067)
* domain expiry

* domain expiry

* domain expiry

* scan interval

* change host to domain
2018-04-25 12:33:47 +02:00
Thijs de Jong
558b659f7c Add devices to Tahoma (#14075) 2018-04-25 07:09:45 +02:00
Kerwin Bryant
0a0d34d394 Support new Xiaomi Aqara device model names and LAN protocol 2.0 (#13540) 2018-04-25 07:05:00 +02:00
Paulus Schoutsen
75fffb6a86 Bump frontend to 20180425.0 2018-04-24 23:18:28 -04:00
stephanerosi
4e97954bbe Remove excessive debugging in webostv module (#14056) 2018-04-24 22:45:57 -04:00
Daniel Høyer Iversen
18137733f9 Upgrade broadlink lib (#14074) 2018-04-24 22:45:16 -04:00
thelittlefireman
ca29224846 Bump locationsharinglib to 1.2.2 (#14070)
* Bump locationsharinglib to 1.2.2

* Bump locationsharinglib to 1.2.2
2018-04-24 18:46:17 +02:00
John Mihalic
31554e8368 Bump pyEight version to update API & reduce connection issues (#14058) 2018-04-23 22:43:59 +02:00
Paulus Schoutsen
5ed73fecd3 Order the output of the automation editor (#14019)
* Order the output of the automation editor

* Lint
2018-04-23 13:47:06 -04:00
Kane610
8a10fcd985 deCONZ use forward entry setup (#13990)
* Use forward entry setup with light platform

* Move sensor to forward entry setup

* Use forward entry setup with binary sensors

* Use forward entry setup with scene platform

* Remove import of unused functionality

* Move deconz setup in to setup entry
Create initial negative tests for setup entry

* Fix hound comment

* Improved tests

* Add test for scene platform

* Add test for binary sensor platform

* Add test for light platform

* Add test for light platform

* Add test for sensor platform

* Fix hound comment

* More asserts on sensor types
2018-04-23 12:00:16 -04:00
Mark Coombes
5fe4053021 Update device classes for contact sensor HomeKit (#14051) 2018-04-23 13:52:39 +02:00
Matthew Garrett
e4cb3af76d Handle HomeKit configuration failure more cleanly (#14041)
* Handle HomeKit configuration failure more cleanly

Add support for handling cases where HomeKit configuration fails, and give
the user more information about what to do.

* Don't consume the exception for a homekit.UnknownError

If we get an UnknownError then we should alert the user but also still
generate the backtrace so there's actually something for them to file in
a bug report.
2018-04-22 16:38:01 -04:00
Otto Winter
7f634c6ed0 Revert cast platform polling mode (#14027) 2018-04-22 16:32:15 -04:00
Paulus Schoutsen
5d3471269a Show a notification when a config entry is discovered (#14022)
* Show a notification when a config entry is discovered

* update comment

* Inline functions

* Lint
2018-04-22 21:00:24 +02:00
Stijn Tintel
1fbc650871 device_tracker.ubus: catch ConnectionError (#14045)
When an OpenWrt device monitored via ubus is offline, this causes the
log to be flooded with several exceptions. Avoid this by catching
requests.exceptions.ConnectionError in addition to
requests.exceptions.Timeout.

Signed-off-by: Stijn Tintel <stijn@linux-ipv6.be>
2018-04-22 12:55:45 +02:00
David Broadfoot
86374ad809 bump gogogate2 version (#14044)
* bump gogogate2 version

* Update - requirements_all
2018-04-22 12:54:48 +02:00
Ryan Bahm
c2bee496e2 Add Accuracy to Google Location Sharing (#14039)
* Update locationsharinglib to 1.2.1 and add accuracy.

* Change indents to match HA style
2018-04-22 08:42:18 +02:00
Matt Schmitt
51f55bddb7 HomeKit Alarm Control Panel Code Exception Fix (#14025)
* Catch exception for KeyError
* Use get and added test
2018-04-21 16:16:46 +02:00
Daniel Høyer Iversen
4c23a61853 upgrade rfxtrx lib, dimming support for Lighting3 (#14026) 2018-04-21 10:54:11 +02:00
Jon Maddox
f12ff6f297 Expose the condition code on condition sensors (#14011)
* expose the condition code on condition sensors

* 💄

* like thisss duh

* add test for condition_code

* It’s a string
2018-04-21 10:20:33 +02:00
Aaron Bach
cb490780c9 Pollen.com: Added attributes on top 3 allergens (#14018) 2018-04-21 10:16:52 +02:00
Johann Kellerman
6ccb83584e Qwikswitch binary sensors (#14008) 2018-04-21 08:34:42 +02:00
Vincent Van Den Berghe
29e659cf4c Fixed SI units for current consumption 2018-03-13 22:20:56 +01:00
908 changed files with 29188 additions and 9412 deletions

View File

@@ -4,6 +4,8 @@ source = homeassistant
omit =
homeassistant/__main__.py
homeassistant/scripts/*.py
homeassistant/util/async.py
homeassistant/monkey_patch.py
homeassistant/helpers/typing.py
homeassistant/helpers/signal.py
@@ -29,7 +31,7 @@ omit =
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/bmw_connected_drive.py
homeassistant/components/bmw_connected_drive/*.py
homeassistant/components/*/bmw_connected_drive.py
homeassistant/components/android_ip_webcam.py
@@ -59,6 +61,9 @@ omit =
homeassistant/components/coinbase.py
homeassistant/components/sensor/coinbase.py
homeassistant/components/cast/*
homeassistant/components/*/cast.py
homeassistant/components/comfoconnect.py
homeassistant/components/*/comfoconnect.py
@@ -95,7 +100,7 @@ omit =
homeassistant/components/*/envisalink.py
homeassistant/components/fritzbox.py
homeassistant/components/*/fritzbox.py
homeassistant/components/switch/fritzbox.py
homeassistant/components/eufy.py
homeassistant/components/*/eufy.py
@@ -121,13 +126,16 @@ omit =
homeassistant/components/homematicip_cloud.py
homeassistant/components/*/homematicip_cloud.py
homeassistant/components/hydrawise.py
homeassistant/components/*/hydrawise.py
homeassistant/components/ihc/*
homeassistant/components/*/ihc.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
homeassistant/components/insteon_plm.py
homeassistant/components/insteon_plm/*
homeassistant/components/*/insteon_plm.py
homeassistant/components/ios.py
@@ -151,6 +159,9 @@ omit =
homeassistant/components/knx.py
homeassistant/components/*/knx.py
homeassistant/components/konnected.py
homeassistant/components/*/konnected.py
homeassistant/components/lametric.py
homeassistant/components/*/lametric.py
@@ -166,6 +177,9 @@ omit =
homeassistant/components/mailgun.py
homeassistant/components/*/mailgun.py
homeassistant/components/matrix.py
homeassistant/components/*/matrix.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
@@ -184,12 +198,15 @@ omit =
homeassistant/components/neato.py
homeassistant/components/*/neato.py
homeassistant/components/nest.py
homeassistant/components/nest/__init__.py
homeassistant/components/*/nest.py
homeassistant/components/netatmo.py
homeassistant/components/*/netatmo.py
homeassistant/components/netgear_lte.py
homeassistant/components/*/netgear_lte.py
homeassistant/components/octoprint.py
homeassistant/components/*/octoprint.py
@@ -208,6 +225,9 @@ omit =
homeassistant/components/raincloud.py
homeassistant/components/*/raincloud.py
homeassistant/components/rainmachine/*
homeassistant/components/*/rainmachine.py
homeassistant/components/raspihats.py
homeassistant/components/*/raspihats.py
@@ -220,6 +240,9 @@ omit =
homeassistant/components/rpi_pfio.py
homeassistant/components/*/rpi_pfio.py
homeassistant/components/sabnzbd.py
homeassistant/components/*/sabnzbd.py
homeassistant/components/satel_integra.py
homeassistant/components/*/satel_integra.py
@@ -232,6 +255,9 @@ omit =
homeassistant/components/smappee.py
homeassistant/components/*/smappee.py
homeassistant/components/sonos/__init__.py
homeassistant/components/*/sonos.py
homeassistant/components/tado.py
homeassistant/components/*/tado.py
@@ -294,6 +320,9 @@ omit =
homeassistant/components/wink/*
homeassistant/components/*/wink.py
homeassistant/components/wirelesstag.py
homeassistant/components/*/wirelesstag.py
homeassistant/components/xiaomi_aqara.py
homeassistant/components/*/xiaomi_aqara.py
@@ -331,11 +360,13 @@ omit =
homeassistant/components/binary_sensor/ping.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/binary_sensor/tapsaff.py
homeassistant/components/binary_sensor/uptimerobot.py
homeassistant/components/browser.py
homeassistant/components/calendar/caldav.py
homeassistant/components/calendar/todoist.py
homeassistant/components/camera/bloomsky.py
homeassistant/components/camera/canary.py
homeassistant/components/camera/familyhub.py
homeassistant/components/camera/ffmpeg.py
homeassistant/components/camera/foscam.py
homeassistant/components/camera/mjpeg.py
@@ -345,6 +376,7 @@ omit =
homeassistant/components/camera/rpi_camera.py
homeassistant/components/camera/synology.py
homeassistant/components/camera/xeoma.py
homeassistant/components/camera/xiaomi.py
homeassistant/components/camera/yi.py
homeassistant/components/climate/econet.py
homeassistant/components/climate/ephember.py
@@ -360,6 +392,7 @@ omit =
homeassistant/components/climate/sensibo.py
homeassistant/components/climate/touchline.py
homeassistant/components/climate/venstar.py
homeassistant/components/climate/zhong_hong.py
homeassistant/components/cover/garadget.py
homeassistant/components/cover/gogogate2.py
homeassistant/components/cover/homematic.py
@@ -367,6 +400,7 @@ omit =
homeassistant/components/cover/myq.py
homeassistant/components/cover/opengarage.py
homeassistant/components/cover/rpi_gpio.py
homeassistant/components/cover/ryobi_gdo.py
homeassistant/components/cover/scsgate.py
homeassistant/components/device_tracker/actiontec.py
homeassistant/components/device_tracker/aruba.py
@@ -378,6 +412,7 @@ omit =
homeassistant/components/device_tracker/bt_home_hub_5.py
homeassistant/components/device_tracker/cisco_ios.py
homeassistant/components/device_tracker/ddwrt.py
homeassistant/components/device_tracker/freebox.py
homeassistant/components/device_tracker/fritz.py
homeassistant/components/device_tracker/google_maps.py
homeassistant/components/device_tracker/gpslogger.py
@@ -406,7 +441,6 @@ omit =
homeassistant/components/emoncms_history.py
homeassistant/components/emulated_hue/upnp.py
homeassistant/components/fan/mqtt.py
homeassistant/components/feedreader.py
homeassistant/components/folder_watcher.py
homeassistant/components/foursquare.py
homeassistant/components/goalfeed.py
@@ -429,6 +463,7 @@ omit =
homeassistant/components/light/lifx_legacy.py
homeassistant/components/light/lifx.py
homeassistant/components/light/limitlessled.py
homeassistant/components/light/lw12wifi.py
homeassistant/components/light/mystrom.py
homeassistant/components/light/nanoleaf_aurora.py
homeassistant/components/light/osramlightify.py
@@ -443,6 +478,7 @@ omit =
homeassistant/components/light/yeelightsunflower.py
homeassistant/components/light/zengge.py
homeassistant/components/lirc.py
homeassistant/components/lock/kiwi.py
homeassistant/components/lock/lockitron.py
homeassistant/components/lock/nello.py
homeassistant/components/lock/nuki.py
@@ -453,7 +489,6 @@ omit =
homeassistant/components/media_player/aquostv.py
homeassistant/components/media_player/bluesound.py
homeassistant/components/media_player/braviatv.py
homeassistant/components/media_player/cast.py
homeassistant/components/media_player/channels.py
homeassistant/components/media_player/clementine.py
homeassistant/components/media_player/cmus.py
@@ -462,10 +497,12 @@ omit =
homeassistant/components/media_player/directv.py
homeassistant/components/media_player/dunehd.py
homeassistant/components/media_player/emby.py
homeassistant/components/media_player/epson.py
homeassistant/components/media_player/firetv.py
homeassistant/components/media_player/frontier_silicon.py
homeassistant/components/media_player/gpmdp.py
homeassistant/components/media_player/gstreamer.py
homeassistant/components/media_player/horizon.py
homeassistant/components/media_player/itunes.py
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
@@ -487,7 +524,6 @@ omit =
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/songpal.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/media_player/ue_smart_radio.py
@@ -504,9 +540,10 @@ omit =
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/clickatell.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/clicksend.py
homeassistant/components/notify/clicksend_tts.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/flock.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/gntp.py
homeassistant/components/notify/group.py
@@ -516,11 +553,9 @@ omit =
homeassistant/components/notify/lannouncer.py
homeassistant/components/notify/llamalab_automate.py
homeassistant/components/notify/mastodon.py
homeassistant/components/notify/matrix.py
homeassistant/components/notify/message_bird.py
homeassistant/components/notify/mycroft.py
homeassistant/components/notify/nfandroidtv.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/prowl.py
homeassistant/components/notify/pushbullet.py
homeassistant/components/notify/pushetta.py
@@ -574,6 +609,7 @@ omit =
homeassistant/components/sensor/discogs.py
homeassistant/components/sensor/dnsip.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/domain_expiry.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/dwd_weather_warnings.py
@@ -586,6 +622,7 @@ omit =
homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fedex.py
homeassistant/components/sensor/filesize.py
homeassistant/components/sensor/fints.py
homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/folder.py
@@ -605,6 +642,7 @@ omit =
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/iperf3.py
homeassistant/components/sensor/irish_rail_transport.py
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lacrosse.py
@@ -615,6 +653,7 @@ omit =
homeassistant/components/sensor/lyft.py
homeassistant/components/sensor/metoffice.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/mitemp_bt.py
homeassistant/components/sensor/modem_callerid.py
homeassistant/components/sensor/mopar.py
homeassistant/components/sensor/mqtt_room.py
@@ -622,6 +661,7 @@ omit =
homeassistant/components/sensor/nederlandse_spoorwegen.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py
homeassistant/components/sensor/nsw_fuel_station.py
homeassistant/components/sensor/nut.py
homeassistant/components/sensor/nzbget.py
homeassistant/components/sensor/ohmconnect.py
@@ -635,6 +675,7 @@ omit =
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pollen.py
homeassistant/components/sensor/postnl.py
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/pyload.py
@@ -642,7 +683,6 @@ omit =
homeassistant/components/sensor/radarr.py
homeassistant/components/sensor/rainbird.py
homeassistant/components/sensor/ripple.py
homeassistant/components/sensor/sabnzbd.py
homeassistant/components/sensor/scrape.py
homeassistant/components/sensor/sense.py
homeassistant/components/sensor/sensehat.py
@@ -656,6 +696,7 @@ omit =
homeassistant/components/sensor/sma.py
homeassistant/components/sensor/snmp.py
homeassistant/components/sensor/sochain.py
homeassistant/components/sensor/socialblade.py
homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/spotcrime.py
@@ -710,7 +751,6 @@ omit =
homeassistant/components/switch/orvibo.py
homeassistant/components/switch/pulseaudio_loopback.py
homeassistant/components/switch/rainbird.py
homeassistant/components/switch/rainmachine.py
homeassistant/components/switch/rest.py
homeassistant/components/switch/rpi_rf.py
homeassistant/components/switch/snmp.py
@@ -726,6 +766,7 @@ omit =
homeassistant/components/tts/picotts.py
homeassistant/components/vacuum/mqtt.py
homeassistant/components/vacuum/roomba.py
homeassistant/components/watson_iot.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/buienradar.py
homeassistant/components/weather/darksky.py

50
.github/ISSUE_TEMPLATE/Bug_report.md vendored Normal file
View File

@@ -0,0 +1,50 @@
---
name: Bug report
about: Create a report to help us improve
---
<!-- READ THIS FIRST:
- If you need additional help with this template please refer to https://www.home-assistant.io/help/reporting_issues/
- Make sure you are running the latest version of Home Assistant before reporting an issue: https://github.com/home-assistant/home-assistant/releases
- Do not report issues for components if you are using custom components: files in <config-dir>/custom_components
- This is for bugs only. Feature and enhancement requests should go in our community forum: https://community.home-assistant.io/c/feature-requests
- Provide as many details as possible. Paste logs, configuration sample and code into the backticks. Do not delete any text from this template!
-->
**Home Assistant release with the issue:**
<!--
- Frontend -> Developer tools -> Info
- Or use this command: hass --version
-->
**Last working Home Assistant release (if known):**
**Operating environment (Hass.io/Docker/Windows/etc.):**
<!--
Please provide details about your environment.
-->
**Component/platform:**
<!--
Please add the link to the documentation at https://www.home-assistant.io/components/ of the component/platform in question.
-->
**Description of problem:**
**Problem-relevant `configuration.yaml` entries and (fill out even if it seems unimportant):**
```yaml
```
**Traceback (if applicable):**
```
```
**Additional information:**

View File

@@ -20,7 +20,7 @@ If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
- [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]).
- [ ] New dependencies are only imported inside functions that use them ([example][ex-import]).
- [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New or updated dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`.
- [ ] New files were added to `.coveragerc`.
If the code does not interact with devices:

View File

@@ -10,8 +10,8 @@ matrix:
env: TOXENV=lint
- python: "3.5.3"
env: TOXENV=pylint
# - python: "3.5"
# env: TOXENV=typing
- python: "3.5.3"
env: TOXENV=typing
- python: "3.5.3"
env: TOXENV=py35
- python: "3.6"

View File

@@ -54,8 +54,11 @@ homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti
homeassistant/components/lock/nello.py @pschmitt
homeassistant/components/lock/nuki.py @pschmitt
homeassistant/components/media_player/emby.py @mezz64
homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/liveboxplaytv.py @pschmitt
homeassistant/components/media_player/mediaroom.py @dgomes
homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/sonos.py @amelchio
@@ -67,6 +70,7 @@ homeassistant/components/sensor/filter.py @dgomes
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/nsw_fuel_station.py @nickw444
homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/qnap.py @colinodell
homeassistant/components/sensor/sma.py @kellerza
@@ -75,8 +79,8 @@ homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/upnp.py @dgomes
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/rainmachine.py @bachya
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/axis.py @kane610
@@ -90,8 +94,14 @@ homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/homekit/* @cdce8p
homeassistant/components/knx.py @Julius2342
homeassistant/components/*/knx.py @Julius2342
homeassistant/components/konnected.py @heythisisnate
homeassistant/components/*/konnected.py @heythisisnate
homeassistant/components/matrix.py @tinloaf
homeassistant/components/*/matrix.py @tinloaf
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
homeassistant/components/rainmachine/* @bachya
homeassistant/components/*/rainmachine.py @bachya
homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/tahoma.py @philklei
homeassistant/components/*/tahoma.py @philklei

View File

@@ -12,6 +12,7 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
#ENV INSTALL_LIBCEC no
#ENV INSTALL_PHANTOMJS no
#ENV INSTALL_SSOCR no
#ENV INSTALL_IPERF3 no
VOLUME /config

View File

@@ -1,606 +0,0 @@
swagger: '2.0'
info:
title: Home Assistant
description: Home Assistant REST API
version: "1.0.1"
# the domain of the service
host: localhost:8123
# array of all schemes that your API supports
schemes:
- http
- https
securityDefinitions:
#api_key:
# type: apiKey
# description: API password
# name: api_password
# in: query
api_key:
type: apiKey
description: API password
name: x-ha-access
in: header
# will be prefixed to all paths
basePath: /api
consumes:
- application/json
produces:
- application/json
paths:
/:
get:
summary: API alive message
description: Returns message if API is up and running.
tags:
- Core
security:
- api_key: []
responses:
200:
description: API is up and running
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/config:
get:
summary: API alive message
description: Returns the current configuration as JSON.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Current configuration
schema:
$ref: '#/definitions/ApiConfig'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/discovery_info:
get:
summary: Basic information about Home Assistant instance
tags:
- Core
responses:
200:
description: Basic information
schema:
$ref: '#/definitions/DiscoveryInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/bootstrap:
get:
summary: Returns all data needed to bootstrap Home Assistant.
tags:
- Core
security:
- api_key: []
responses:
200:
description: Bootstrap information
schema:
$ref: '#/definitions/BootstrapInfo'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events:
get:
summary: Array of event objects.
description: Returns an array of event objects. Each event object contain event name and listener count.
tags:
- Events
security:
- api_key: []
responses:
200:
description: Events
schema:
type: array
items:
$ref: '#/definitions/Event'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services:
get:
summary: Array of service objects.
description: Returns an array of service objects. Each object contains the domain and which services it contains.
tags:
- Services
security:
- api_key: []
responses:
200:
description: Services
schema:
type: array
items:
$ref: '#/definitions/Service'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/history:
get:
summary: Array of state changes in the past.
description: Returns an array of state changes in the past. Each object contains further detail for the entities.
tags:
- State
security:
- api_key: []
responses:
200:
description: State changes
schema:
type: array
items:
$ref: '#/definitions/History'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states:
get:
summary: Array of state objects.
description: |
Returns an array of state objects. Each state has the following attributes: entity_id, state, last_changed and attributes.
tags:
- State
security:
- api_key: []
responses:
200:
description: States
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/states/{entity_id}:
get:
summary: Specific state object.
description: |
Returns a state object for specified entity_id.
tags:
- State
security:
- api_key: []
parameters:
- name: entity_id
in: path
description: entity_id of the entity to query
required: true
type: string
responses:
200:
description: State
schema:
$ref: '#/definitions/State'
404:
description: Not found
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
post:
description: |
Updates or creates the current state of an entity.
tags:
- State
consumes:
- application/json
parameters:
- name: entity_id
in: path
description: entity_id to set the state of
required: true
type: string
- $ref: '#/parameters/State'
responses:
200:
description: State of existing entity was set
schema:
$ref: '#/definitions/State'
201:
description: State of new entity was set
schema:
$ref: '#/definitions/State'
headers:
location:
type: string
description: location of the new entity
default:
description: Error
schema:
$ref: '#/definitions/Message'
/error_log:
get:
summary: Error log
description: |
Retrieve all errors logged during the current session of Home Assistant as a plaintext response.
tags:
- Core
security:
- api_key: []
produces:
- text/plain
responses:
200:
description: Plain text error log
default:
description: Error
schema:
$ref: '#/definitions/Message'
/camera_proxy/camera.{entity_id}:
get:
summary: Camera image.
description: |
Returns the data (image) from the specified camera entity_id.
tags:
- Camera
security:
- api_key: []
produces:
- image/jpeg
parameters:
- name: entity_id
in: path
description: entity_id of the camera to query
required: true
type: string
responses:
200:
description: Camera image
schema:
type: file
default:
description: Error
schema:
$ref: '#/definitions/Message'
/events/{event_type}:
post:
description: |
Fires an event with event_type
tags:
- Events
security:
- api_key: []
consumes:
- application/json
parameters:
- name: event_type
in: path
description: event_type to fire event with
required: true
type: string
- $ref: '#/parameters/EventData'
responses:
200:
description: Response message
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/services/{domain}/{service}:
post:
description: |
Calls a service within a specific domain. Will return when the service has been executed or 10 seconds has past, whichever comes first.
tags:
- Services
security:
- api_key: []
consumes:
- application/json
parameters:
- name: domain
in: path
description: domain of the service
required: true
type: string
- name: service
in: path
description: service to call
required: true
type: string
- $ref: '#/parameters/ServiceData'
responses:
200:
description: List of states that have changed while the service was being executed. The result will include any changed states that changed while the service was being executed, even if their change was the result of something else happening in the system.
schema:
type: array
items:
$ref: '#/definitions/State'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/template:
post:
description: |
Render a Home Assistant template.
tags:
- Template
security:
- api_key: []
consumes:
- application/json
produces:
- text/plain
parameters:
- $ref: '#/parameters/Template'
responses:
200:
description: Returns the rendered template in plain text.
schema:
type: string
default:
description: Error
schema:
$ref: '#/definitions/Message'
/event_forwarding:
post:
description: |
Setup event forwarding to another Home Assistant instance.
tags:
- Core
security:
- api_key: []
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was setup successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
delete:
description: |
Cancel event forwarding to another Home Assistant instance.
tags:
- Core
consumes:
- application/json
parameters:
- $ref: '#/parameters/EventForwarding'
responses:
200:
description: It will return a message if event forwarding was cancelled successful.
schema:
$ref: '#/definitions/Message'
default:
description: Error
schema:
$ref: '#/definitions/Message'
/stream:
get:
summary: Server-sent events
description: The server-sent events feature is a one-way channel from your Home Assistant server to a client which is acting as a consumer.
tags:
- Core
- Events
security:
- api_key: []
produces:
- text/event-stream
parameters:
- name: restrict
in: query
description: comma-separated list of event_types to filter
required: false
type: string
responses:
default:
description: Stream of events
schema:
type: object
x-events:
state_changed:
type: object
properties:
entity_id:
type: string
old_state:
$ref: '#/definitions/State'
new_state:
$ref: '#/definitions/State'
definitions:
ApiConfig:
type: object
properties:
components:
type: array
description: List of component types
items:
type: string
description: Component type
latitude:
type: number
format: float
description: Latitude of Home Assistant server
longitude:
type: number
format: float
description: Longitude of Home Assistant server
location_name:
type: string
unit_system:
type: object
properties:
length:
type: string
mass:
type: string
temperature:
type: string
volume:
type: string
time_zone:
type: string
version:
type: string
DiscoveryInfo:
type: object
properties:
base_url:
type: string
location_name:
type: string
requires_api_password:
type: boolean
version:
type: string
BootstrapInfo:
type: object
properties:
config:
$ref: '#/definitions/ApiConfig'
events:
type: array
items:
$ref: '#/definitions/Event'
services:
type: array
items:
$ref: '#/definitions/Service'
states:
type: array
items:
$ref: '#/definitions/State'
Event:
type: object
properties:
event:
type: string
listener_count:
type: integer
Service:
type: object
properties:
domain:
type: string
services:
type: object
additionalProperties:
$ref: '#/definitions/DomainService'
DomainService:
type: object
properties:
description:
type: string
fields:
type: object
description: Object with service fields that can be called
State:
type: object
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
entity_id:
type: string
last_changed:
type: string
format: date-time
StateAttributes:
type: object
additionalProperties:
type: string
History:
allOf:
- $ref: '#/definitions/State'
- type: object
properties:
last_updated:
type: string
format: date-time
Message:
type: object
properties:
message:
type: string
parameters:
State:
name: body
in: body
description: State parameter
required: false
schema:
type: object
required:
- state
properties:
attributes:
$ref: '#/definitions/StateAttributes'
state:
type: string
EventData:
name: body
in: body
description: event_data
required: false
schema:
type: object
ServiceData:
name: body
in: body
description: service_data
required: false
schema:
type: object
Template:
name: body
in: body
description: Template to render
required: true
schema:
type: object
required:
- template
properties:
template:
description: Jinja2 template string
type: string
EventForwarding:
name: body
in: body
description: Event Forwarding parameter
required: true
schema:
type: object
required:
- host
- api_password
properties:
host:
type: string
api_password:
type: string
port:
type: integer

View File

@@ -8,7 +8,8 @@ import subprocess
import sys
import threading
from typing import Optional, List
from typing import Optional, List, Dict, Any # noqa #pylint: disable=unused-import
from homeassistant import monkey_patch
from homeassistant.const import (
@@ -259,7 +260,7 @@ def setup_and_run_hass(config_dir: str,
config = {
'frontend': {},
'demo': {}
}
} # type: Dict[str, Any]
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days,

503
homeassistant/auth.py Normal file
View File

@@ -0,0 +1,503 @@
"""Provide an authentication layer for Home Assistant."""
import asyncio
import binascii
from collections import OrderedDict
from datetime import datetime, timedelta
import os
import importlib
import logging
import uuid
import attr
import voluptuous as vol
from voluptuous.humanize import humanize_error
from homeassistant import data_entry_flow, requirements
from homeassistant.core import callback
from homeassistant.const import CONF_TYPE, CONF_NAME, CONF_ID
from homeassistant.util.decorator import Registry
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
AUTH_PROVIDERS = Registry()
AUTH_PROVIDER_SCHEMA = vol.Schema({
vol.Required(CONF_TYPE): str,
vol.Optional(CONF_NAME): str,
# Specify ID if you have two auth providers for same type.
vol.Optional(CONF_ID): str,
}, extra=vol.ALLOW_EXTRA)
ACCESS_TOKEN_EXPIRATION = timedelta(minutes=30)
DATA_REQS = 'auth_reqs_processed'
def generate_secret(entropy: int = 32) -> str:
"""Generate a secret.
Backport of secrets.token_hex from Python 3.6
Event loop friendly.
"""
return binascii.hexlify(os.urandom(entropy)).decode('ascii')
class AuthProvider:
"""Provider of user authentication."""
DEFAULT_TITLE = 'Unnamed auth provider'
initialized = False
def __init__(self, hass, store, config):
"""Initialize an auth provider."""
self.hass = hass
self.store = store
self.config = config
@property
def id(self): # pylint: disable=invalid-name
"""Return id of the auth provider.
Optional, can be None.
"""
return self.config.get(CONF_ID)
@property
def type(self):
"""Return type of the provider."""
return self.config[CONF_TYPE]
@property
def name(self):
"""Return the name of the auth provider."""
return self.config.get(CONF_NAME, self.DEFAULT_TITLE)
async def async_credentials(self):
"""Return all credentials of this provider."""
return await self.store.credentials_for_provider(self.type, self.id)
@callback
def async_create_credentials(self, data):
"""Create credentials."""
return Credentials(
auth_provider_type=self.type,
auth_provider_id=self.id,
data=data,
)
# Implement by extending class
async def async_initialize(self):
"""Initialize the auth provider.
Optional.
"""
async def async_credential_flow(self):
"""Return the data flow for logging in with auth provider."""
raise NotImplementedError
async def async_get_or_create_credentials(self, flow_result):
"""Get credentials based on the flow result."""
raise NotImplementedError
async def async_user_meta_for_credentials(self, credentials):
"""Return extra user metadata for credentials.
Will be used to populate info when creating a new user.
"""
return {}
@attr.s(slots=True)
class User:
"""A user."""
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
is_owner = attr.ib(type=bool, default=False)
is_active = attr.ib(type=bool, default=False)
name = attr.ib(type=str, default=None)
# For persisting and see if saved?
# store = attr.ib(type=AuthStore, default=None)
# List of credentials of a user.
credentials = attr.ib(type=list, default=attr.Factory(list))
# Tokens associated with a user.
refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict))
def as_dict(self):
"""Convert user object to a dictionary."""
return {
'id': self.id,
'is_owner': self.is_owner,
'is_active': self.is_active,
'name': self.name,
}
@attr.s(slots=True)
class RefreshToken:
"""RefreshToken for a user to grant new access tokens."""
user = attr.ib(type=User)
client_id = attr.ib(type=str)
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
access_token_expiration = attr.ib(type=timedelta,
default=ACCESS_TOKEN_EXPIRATION)
token = attr.ib(type=str,
default=attr.Factory(lambda: generate_secret(64)))
access_tokens = attr.ib(type=list, default=attr.Factory(list))
@attr.s(slots=True)
class AccessToken:
"""Access token to access the API.
These will only ever be stored in memory and not be persisted.
"""
refresh_token = attr.ib(type=RefreshToken)
created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
token = attr.ib(type=str,
default=attr.Factory(generate_secret))
@property
def expires(self):
"""Return datetime when this token expires."""
return self.created_at + self.refresh_token.access_token_expiration
@attr.s(slots=True)
class Credentials:
"""Credentials for a user on an auth provider."""
auth_provider_type = attr.ib(type=str)
auth_provider_id = attr.ib(type=str)
# Allow the auth provider to store data to represent their auth.
data = attr.ib(type=dict)
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
is_new = attr.ib(type=bool, default=True)
@attr.s(slots=True)
class Client:
"""Client that interacts with Home Assistant on behalf of a user."""
name = attr.ib(type=str)
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
secret = attr.ib(type=str, default=attr.Factory(generate_secret))
redirect_uris = attr.ib(type=list, default=attr.Factory(list))
async def load_auth_provider_module(hass, provider):
"""Load an auth provider."""
try:
module = importlib.import_module(
'homeassistant.auth_providers.{}'.format(provider))
except ImportError:
_LOGGER.warning('Unable to find auth provider %s', provider)
return None
if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'):
return module
processed = hass.data.get(DATA_REQS)
if processed is None:
processed = hass.data[DATA_REQS] = set()
elif provider in processed:
return module
req_success = await requirements.async_process_requirements(
hass, 'auth provider {}'.format(provider), module.REQUIREMENTS)
if not req_success:
return None
return module
async def auth_manager_from_config(hass, provider_configs):
"""Initialize an auth manager from config."""
store = AuthStore(hass)
if provider_configs:
providers = await asyncio.gather(
*[_auth_provider_from_config(hass, store, config)
for config in provider_configs])
else:
providers = []
# So returned auth providers are in same order as config
provider_hash = OrderedDict()
for provider in providers:
if provider is None:
continue
key = (provider.type, provider.id)
if key in provider_hash:
_LOGGER.error(
'Found duplicate provider: %s. Please add unique IDs if you '
'want to have the same provider twice.', key)
continue
provider_hash[key] = provider
manager = AuthManager(hass, store, provider_hash)
return manager
async def _auth_provider_from_config(hass, store, config):
"""Initialize an auth provider from a config."""
provider_name = config[CONF_TYPE]
module = await load_auth_provider_module(hass, provider_name)
if module is None:
return None
try:
config = module.CONFIG_SCHEMA(config)
except vol.Invalid as err:
_LOGGER.error('Invalid configuration for auth provider %s: %s',
provider_name, humanize_error(config, err))
return None
return AUTH_PROVIDERS[provider_name](hass, store, config)
class AuthManager:
"""Manage the authentication for Home Assistant."""
def __init__(self, hass, store, providers):
"""Initialize the auth manager."""
self._store = store
self._providers = providers
self.login_flow = data_entry_flow.FlowManager(
hass, self._async_create_login_flow,
self._async_finish_login_flow)
self.access_tokens = {}
@property
def async_auth_providers(self):
"""Return a list of available auth providers."""
return self._providers.values()
async def async_get_user(self, user_id):
"""Retrieve a user."""
return await self._store.async_get_user(user_id)
async def async_get_or_create_user(self, credentials):
"""Get or create a user."""
return await self._store.async_get_or_create_user(
credentials, self._async_get_auth_provider(credentials))
async def async_link_user(self, user, credentials):
"""Link credentials to an existing user."""
await self._store.async_link_user(user, credentials)
async def async_remove_user(self, user):
"""Remove a user."""
await self._store.async_remove_user(user)
async def async_create_refresh_token(self, user, client_id):
"""Create a new refresh token for a user."""
return await self._store.async_create_refresh_token(user, client_id)
async def async_get_refresh_token(self, token):
"""Get refresh token by token."""
return await self._store.async_get_refresh_token(token)
@callback
def async_create_access_token(self, refresh_token):
"""Create a new access token."""
access_token = AccessToken(refresh_token)
self.access_tokens[access_token.token] = access_token
return access_token
@callback
def async_get_access_token(self, token):
"""Get an access token."""
return self.access_tokens.get(token)
async def async_create_client(self, name, *, redirect_uris=None,
no_secret=False):
"""Create a new client."""
return await self._store.async_create_client(
name, redirect_uris, no_secret)
async def async_get_client(self, client_id):
"""Get a client."""
return await self._store.async_get_client(client_id)
async def _async_create_login_flow(self, handler, *, source, data):
"""Create a login flow."""
auth_provider = self._providers[handler]
if not auth_provider.initialized:
auth_provider.initialized = True
await auth_provider.async_initialize()
return await auth_provider.async_credential_flow()
async def _async_finish_login_flow(self, result):
"""Result of a credential login flow."""
if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY:
return None
auth_provider = self._providers[result['handler']]
return await auth_provider.async_get_or_create_credentials(
result['data'])
@callback
def _async_get_auth_provider(self, credentials):
"""Helper to get auth provider from a set of credentials."""
auth_provider_key = (credentials.auth_provider_type,
credentials.auth_provider_id)
return self._providers[auth_provider_key]
class AuthStore:
"""Stores authentication info.
Any mutation to an object should happen inside the auth store.
The auth store is lazy. It won't load the data from disk until a method is
called that needs it.
"""
def __init__(self, hass):
"""Initialize the auth store."""
self.hass = hass
self.users = None
self.clients = None
self._load_lock = asyncio.Lock(loop=hass.loop)
async def credentials_for_provider(self, provider_type, provider_id):
"""Return credentials for specific auth provider type and id."""
if self.users is None:
await self.async_load()
return [
credentials
for user in self.users.values()
for credentials in user.credentials
if (credentials.auth_provider_type == provider_type and
credentials.auth_provider_id == provider_id)
]
async def async_get_user(self, user_id):
"""Retrieve a user."""
if self.users is None:
await self.async_load()
return self.users.get(user_id)
async def async_get_or_create_user(self, credentials, auth_provider):
"""Get or create a new user for given credentials.
If link_user is passed in, the credentials will be linked to the passed
in user if the credentials are new.
"""
if self.users is None:
await self.async_load()
# New credentials, store in user
if credentials.is_new:
info = await auth_provider.async_user_meta_for_credentials(
credentials)
# Make owner and activate user if it's the first user.
if self.users:
is_owner = False
is_active = False
else:
is_owner = True
is_active = True
new_user = User(
is_owner=is_owner,
is_active=is_active,
name=info.get('name'),
)
self.users[new_user.id] = new_user
await self.async_link_user(new_user, credentials)
return new_user
for user in self.users.values():
for creds in user.credentials:
if (creds.auth_provider_type == credentials.auth_provider_type
and creds.auth_provider_id ==
credentials.auth_provider_id):
return user
raise ValueError('We got credentials with ID but found no user')
async def async_link_user(self, user, credentials):
"""Add credentials to an existing user."""
user.credentials.append(credentials)
await self.async_save()
credentials.is_new = False
async def async_remove_user(self, user):
"""Remove a user."""
self.users.pop(user.id)
await self.async_save()
async def async_create_refresh_token(self, user, client_id):
"""Create a new token for a user."""
refresh_token = RefreshToken(user, client_id)
user.refresh_tokens[refresh_token.token] = refresh_token
await self.async_save()
return refresh_token
async def async_get_refresh_token(self, token):
"""Get refresh token by token."""
if self.users is None:
await self.async_load()
for user in self.users.values():
refresh_token = user.refresh_tokens.get(token)
if refresh_token is not None:
return refresh_token
return None
async def async_create_client(self, name, redirect_uris, no_secret):
"""Create a new client."""
if self.clients is None:
await self.async_load()
kwargs = {
'name': name,
'redirect_uris': redirect_uris
}
if no_secret:
kwargs['secret'] = None
client = Client(**kwargs)
self.clients[client.id] = client
await self.async_save()
return client
async def async_get_client(self, client_id):
"""Get a client."""
if self.clients is None:
await self.async_load()
return self.clients.get(client_id)
async def async_load(self):
"""Load the users."""
async with self._load_lock:
self.users = {}
self.clients = {}
async def async_save(self):
"""Save users."""
pass

View File

@@ -0,0 +1 @@
"""Auth providers for Home Assistant."""

View File

@@ -0,0 +1,181 @@
"""Home Assistant auth provider."""
import base64
from collections import OrderedDict
import hashlib
import hmac
import voluptuous as vol
from homeassistant import auth, data_entry_flow
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import json
PATH_DATA = '.users.json'
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
class InvalidAuth(HomeAssistantError):
"""Raised when we encounter invalid authentication."""
class InvalidUser(HomeAssistantError):
"""Raised when invalid user is specified.
Will not be raised when validating authentication.
"""
class Data:
"""Hold the user data."""
def __init__(self, path, data):
"""Initialize the user data store."""
self.path = path
if data is None:
data = {
'salt': auth.generate_secret(),
'users': []
}
self._data = data
@property
def users(self):
"""Return users."""
return self._data['users']
def validate_login(self, username, password):
"""Validate a username and password.
Raises InvalidAuth if auth invalid.
"""
password = self.hash_password(password)
found = None
# Compare all users to avoid timing attacks.
for user in self._data['users']:
if username == user['username']:
found = user
if found is None:
# Do one more compare to make timing the same as if user was found.
hmac.compare_digest(password, password)
raise InvalidAuth
if not hmac.compare_digest(password,
base64.b64decode(found['password'])):
raise InvalidAuth
def hash_password(self, password, for_storage=False):
"""Encode a password."""
hashed = hashlib.pbkdf2_hmac(
'sha512', password.encode(), self._data['salt'].encode(), 100000)
if for_storage:
hashed = base64.b64encode(hashed).decode()
return hashed
def add_user(self, username, password):
"""Add a user."""
if any(user['username'] == username for user in self.users):
raise InvalidUser
self.users.append({
'username': username,
'password': self.hash_password(password, True),
})
def change_password(self, username, new_password):
"""Update the password of a user.
Raises InvalidUser if user cannot be found.
"""
for user in self.users:
if user['username'] == username:
user['password'] = self.hash_password(new_password, True)
break
else:
raise InvalidUser
def save(self):
"""Save data."""
json.save_json(self.path, self._data)
def load_data(path):
"""Load auth data."""
return Data(path, json.load_json(path, None))
@auth.AUTH_PROVIDERS.register('homeassistant')
class HassAuthProvider(auth.AuthProvider):
"""Auth provider based on a local storage of users in HASS config dir."""
DEFAULT_TITLE = 'Home Assistant Local'
async def async_credential_flow(self):
"""Return a flow to login."""
return LoginFlow(self)
async def async_validate_login(self, username, password):
"""Helper to validate a username and password."""
def validate():
"""Validate creds."""
data = self._auth_data()
data.validate_login(username, password)
await self.hass.async_add_job(validate)
async def async_get_or_create_credentials(self, flow_result):
"""Get credentials based on the flow result."""
username = flow_result['username']
for credential in await self.async_credentials():
if credential.data['username'] == username:
return credential
# Create new credentials.
return self.async_create_credentials({
'username': username
})
def _auth_data(self):
"""Return the auth provider data."""
return load_data(self.hass.config.path(PATH_DATA))
class LoginFlow(data_entry_flow.FlowHandler):
"""Handler for the login flow."""
def __init__(self, auth_provider):
"""Initialize the login flow."""
self._auth_provider = auth_provider
async def async_step_init(self, user_input=None):
"""Handle the step of the form."""
errors = {}
if user_input is not None:
try:
await self._auth_provider.async_validate_login(
user_input['username'], user_input['password'])
except InvalidAuth:
errors['base'] = 'invalid_auth'
if not errors:
return self.async_create_entry(
title=self._auth_provider.name,
data=user_input
)
schema = OrderedDict()
schema['username'] = str
schema['password'] = str
return self.async_show_form(
step_id='init',
data_schema=vol.Schema(schema),
errors=errors,
)

View File

@@ -0,0 +1,118 @@
"""Example auth provider."""
from collections import OrderedDict
import hmac
import voluptuous as vol
from homeassistant.exceptions import HomeAssistantError
from homeassistant import auth, data_entry_flow
from homeassistant.core import callback
USER_SCHEMA = vol.Schema({
vol.Required('username'): str,
vol.Required('password'): str,
vol.Optional('name'): str,
})
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
vol.Required('users'): [USER_SCHEMA]
}, extra=vol.PREVENT_EXTRA)
class InvalidAuthError(HomeAssistantError):
"""Raised when submitting invalid authentication."""
@auth.AUTH_PROVIDERS.register('insecure_example')
class ExampleAuthProvider(auth.AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
async def async_credential_flow(self):
"""Return a flow to login."""
return LoginFlow(self)
@callback
def async_validate_login(self, username, password):
"""Helper to validate a username and password."""
user = None
# Compare all users to avoid timing attacks.
for usr in self.config['users']:
if hmac.compare_digest(username.encode('utf-8'),
usr['username'].encode('utf-8')):
user = usr
if user is None:
# Do one more compare to make timing the same as if user was found.
hmac.compare_digest(password.encode('utf-8'),
password.encode('utf-8'))
raise InvalidAuthError
if not hmac.compare_digest(user['password'].encode('utf-8'),
password.encode('utf-8')):
raise InvalidAuthError
async def async_get_or_create_credentials(self, flow_result):
"""Get credentials based on the flow result."""
username = flow_result['username']
for credential in await self.async_credentials():
if credential.data['username'] == username:
return credential
# Create new credentials.
return self.async_create_credentials({
'username': username
})
async def async_user_meta_for_credentials(self, credentials):
"""Return extra user metadata for credentials.
Will be used to populate info when creating a new user.
"""
username = credentials.data['username']
for user in self.config['users']:
if user['username'] == username:
return {
'name': user.get('name')
}
return {}
class LoginFlow(data_entry_flow.FlowHandler):
"""Handler for the login flow."""
def __init__(self, auth_provider):
"""Initialize the login flow."""
self._auth_provider = auth_provider
async def async_step_init(self, user_input=None):
"""Handle the step of the form."""
errors = {}
if user_input is not None:
try:
self._auth_provider.async_validate_login(
user_input['username'], user_input['password'])
except InvalidAuthError:
errors['base'] = 'invalid_auth'
if not errors:
return self.async_create_entry(
title=self._auth_provider.name,
data=user_input
)
schema = OrderedDict()
schema['username'] = str
schema['password'] = str
return self.async_show_form(
step_id='init',
data_schema=vol.Schema(schema),
errors=errors,
)

View File

@@ -1,5 +1,4 @@
"""Provide methods to bootstrap a Home Assistant instance."""
import asyncio
import logging
import logging.handlers
import os
@@ -12,13 +11,12 @@ from typing import Any, Optional, Dict
import voluptuous as vol
from homeassistant import (
core, config as conf_util, config_entries, loader,
components as core_components)
core, config as conf_util, config_entries, components as core_components)
from homeassistant.components import persistent_notification
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.package import async_get_user_site, get_user_site
from homeassistant.util.package import async_get_user_site, is_virtual_env
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.signal import async_register_signal_handling
@@ -54,8 +52,9 @@ def from_config_dict(config: Dict[str, Any],
if config_dir is not None:
config_dir = os.path.abspath(config_dir)
hass.config.config_dir = config_dir
hass.loop.run_until_complete(
async_mount_local_lib_path(config_dir, hass.loop))
if not is_virtual_env():
hass.loop.run_until_complete(
async_mount_local_lib_path(config_dir))
# run task
hass = hass.loop.run_until_complete(
@@ -67,16 +66,15 @@ def from_config_dict(config: Dict[str, Any],
return hass
@asyncio.coroutine
def async_from_config_dict(config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str] = None,
enable_log: bool = True,
verbose: bool = False,
skip_pip: bool = False,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False) \
async def async_from_config_dict(config: Dict[str, Any],
hass: core.HomeAssistant,
config_dir: Optional[str] = None,
enable_log: bool = True,
verbose: bool = False,
skip_pip: bool = False,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False) \
-> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary.
@@ -92,27 +90,24 @@ def async_from_config_dict(config: Dict[str, Any],
core_config = config.get(core.DOMAIN, {})
try:
yield from conf_util.async_process_ha_core_config(hass, core_config)
await conf_util.async_process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
conf_util.async_log_exception(ex, 'homeassistant', core_config, hass)
return None
yield from hass.async_add_job(conf_util.process_ha_config_upgrade, hass)
await hass.async_add_job(conf_util.process_ha_config_upgrade, hass)
hass.config.skip_pip = skip_pip
if skip_pip:
_LOGGER.warning("Skipping pip installation of required modules. "
"This may cause issues")
if not loader.PREPARED:
yield from hass.async_add_job(loader.prepare, hass)
# Make a copy because we are mutating it.
config = OrderedDict(config)
# Merge packages
conf_util.merge_packages_config(
config, core_config.get(conf_util.CONF_PACKAGES, {}))
hass, config, core_config.get(conf_util.CONF_PACKAGES, {}))
# Ensure we have no None values after merge
for key, value in config.items():
@@ -120,7 +115,7 @@ def async_from_config_dict(config: Dict[str, Any],
config[key] = {}
hass.config_entries = config_entries.ConfigEntries(hass, config)
yield from hass.config_entries.async_load()
await hass.config_entries.async_load()
# Filter out the repeating and common config section [homeassistant]
components = set(key.split(' ')[0] for key in config.keys()
@@ -129,13 +124,13 @@ def async_from_config_dict(config: Dict[str, Any],
# setup components
# pylint: disable=not-an-iterable
res = yield from core_components.async_setup(hass, config)
res = await core_components.async_setup(hass, config)
if not res:
_LOGGER.error("Home Assistant core failed to initialize. "
"further initialization aborted")
return hass
yield from persistent_notification.async_setup(hass, config)
await persistent_notification.async_setup(hass, config)
_LOGGER.info("Home Assistant core initialized")
@@ -145,7 +140,7 @@ def async_from_config_dict(config: Dict[str, Any],
continue
hass.async_add_job(async_setup_component(hass, component, config))
yield from hass.async_block_till_done()
await hass.async_block_till_done()
# stage 2
for component in components:
@@ -153,7 +148,7 @@ def async_from_config_dict(config: Dict[str, Any],
continue
hass.async_add_job(async_setup_component(hass, component, config))
yield from hass.async_block_till_done()
await hass.async_block_till_done()
stop = time()
_LOGGER.info("Home Assistant initialized in %.2fs", stop-start)
@@ -187,14 +182,13 @@ def from_config_file(config_path: str,
return hass
@asyncio.coroutine
def async_from_config_file(config_path: str,
hass: core.HomeAssistant,
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False):
async def async_from_config_file(config_path: str,
hass: core.HomeAssistant,
verbose: bool = False,
skip_pip: bool = True,
log_rotate_days: Any = None,
log_file: Any = None,
log_no_color: bool = False):
"""Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter.
@@ -203,13 +197,15 @@ def async_from_config_file(config_path: str,
# Set config dir to directory holding config file
config_dir = os.path.abspath(os.path.dirname(config_path))
hass.config.config_dir = config_dir
yield from async_mount_local_lib_path(config_dir, hass.loop)
if not is_virtual_env():
await async_mount_local_lib_path(config_dir)
async_enable_logging(hass, verbose, log_rotate_days, log_file,
log_no_color)
try:
config_dict = yield from hass.async_add_job(
config_dict = await hass.async_add_job(
conf_util.load_yaml_config_file, config_path)
except HomeAssistantError as err:
_LOGGER.error("Error loading %s: %s", config_path, err)
@@ -217,9 +213,8 @@ def async_from_config_file(config_path: str,
finally:
clear_secret_cache()
hass = yield from async_from_config_dict(
return await async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip)
return hass
@core.callback
@@ -284,7 +279,8 @@ def async_enable_logging(hass: core.HomeAssistant,
if log_rotate_days:
err_handler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when='midnight', backupCount=log_rotate_days)
err_log_path, when='midnight',
backupCount=log_rotate_days) # type: logging.FileHandler
else:
err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True)
@@ -294,17 +290,16 @@ def async_enable_logging(hass: core.HomeAssistant,
async_handler = AsyncHandler(hass.loop, err_handler)
@asyncio.coroutine
def async_stop_async_handler(event):
async def async_stop_async_handler(event):
"""Cleanup async handler."""
logging.getLogger('').removeHandler(async_handler)
yield from async_handler.async_close(blocking=True)
await async_handler.async_close(blocking=True)
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler)
logger = logging.getLogger('')
logger.addHandler(async_handler)
logger.addHandler(async_handler) # type: ignore
logger.setLevel(logging.INFO)
# Save the log file location for access by other components.
@@ -314,24 +309,13 @@ def async_enable_logging(hass: core.HomeAssistant,
"Unable to setup error log %s (access denied)", err_log_path)
def mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path."""
deps_dir = os.path.join(config_dir, 'deps')
lib_dir = get_user_site(deps_dir)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir
@asyncio.coroutine
def async_mount_local_lib_path(config_dir: str,
loop: asyncio.AbstractEventLoop) -> str:
async def async_mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path.
This function is a coroutine.
"""
deps_dir = os.path.join(config_dir, 'deps')
lib_dir = yield from async_get_user_site(deps_dir, loop=loop)
lib_dir = await async_get_user_site(deps_dir)
if lib_dir not in sys.path:
sys.path.insert(0, lib_dir)
return deps_dir

View File

@@ -81,7 +81,7 @@ TRIGGER_SCHEMA = vol.Schema({
ABODE_PLATFORMS = [
'alarm_control_panel', 'binary_sensor', 'lock', 'switch', 'cover',
'camera', 'light'
'camera', 'light', 'sensor'
]

View File

@@ -100,8 +100,8 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the regex for code format or None if no code is required."""
return '^\\d{4,6}$'
"""Return one or more digits/characters."""
return 'Number'
@property
def state(self):

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
import asyncio
import logging
import re
import voluptuous as vol
@@ -17,7 +18,7 @@ from homeassistant.const import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyalarmdotcom==0.3.1']
REQUIREMENTS = ['pyalarmdotcom==0.3.2']
_LOGGER = logging.getLogger(__name__)
@@ -79,8 +80,12 @@ class AlarmDotCom(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters if code is defined."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):
@@ -93,6 +98,13 @@ class AlarmDotCom(alarm.AlarmControlPanel):
return STATE_ALARM_ARMED_AWAY
return STATE_UNKNOWN
@property
def device_state_attributes(self):
"""Return the state attributes."""
return {
'sensor_status': self._alarm.sensor_status
}
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -4,15 +4,17 @@ Support for Arlo Alarm Control Panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.arlo/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.alarm_control_panel import (
AlarmControlPanel, PLATFORM_SCHEMA)
from homeassistant.components.arlo import (DATA_ARLO, CONF_ATTRIBUTION)
from homeassistant.components.arlo import (
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
from homeassistant.const import (
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED)
@@ -36,21 +38,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Arlo Alarm Control Panels."""
data = hass.data[DATA_ARLO]
arlo = hass.data[DATA_ARLO]
if not data.base_stations:
if not arlo.base_stations:
return
home_mode_name = config.get(CONF_HOME_MODE_NAME)
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
base_stations = []
for base_station in data.base_stations:
for base_station in arlo.base_stations:
base_stations.append(ArloBaseStation(base_station, home_mode_name,
away_mode_name))
async_add_devices(base_stations, True)
add_devices(base_stations, True)
class ArloBaseStation(AlarmControlPanel):
@@ -68,6 +69,16 @@ class ArloBaseStation(AlarmControlPanel):
"""Return icon."""
return ICON
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state(True)
@property
def state(self):
"""Return the state of the device."""
@@ -75,30 +86,22 @@ class ArloBaseStation(AlarmControlPanel):
def update(self):
"""Update the state of the device."""
# PyArlo sometimes returns None for mode. So retry 3 times before
# returning None.
num_retries = 3
i = 0
while i < num_retries:
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
return
i += 1
self._state = None
_LOGGER.debug("Updating Arlo Alarm Control Panel %s", self.name)
mode = self._base_station.mode
if mode:
self._state = self._get_state_from_mode(mode)
else:
self._state = None
@asyncio.coroutine
def async_alarm_disarm(self, code=None):
async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
self._base_station.mode = DISARMED
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
async def async_alarm_arm_away(self, code=None):
"""Send arm away command. Uses custom mode."""
self._base_station.mode = self._away_mode_name
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
async def async_alarm_arm_home(self, code=None):
"""Send arm home command. Uses custom mode."""
self._base_station.mode = self._home_mode_name
@@ -125,4 +128,4 @@ class ArloBaseStation(AlarmControlPanel):
return STATE_ALARM_ARMED_HOME
elif mode == self._away_mode_name:
return STATE_ALARM_ARMED_AWAY
return None
return mode

View File

@@ -80,7 +80,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
return 'Number'
@property
def state(self):

View File

@@ -106,7 +106,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
"""Regex for code format or None if no code is required."""
if self._code:
return None
return '^\\d{4,6}$'
return 'Number'
@property
def state(self):

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.ifttt/
"""
import logging
import re
import voluptuous as vol
@@ -124,8 +125,12 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -7,6 +7,7 @@ https://home-assistant.io/components/alarm_control_panel.manual/
import copy
import datetime
import logging
import re
import voluptuous as vol
@@ -201,8 +202,12 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -8,6 +8,7 @@ import asyncio
import copy
import datetime
import logging
import re
import voluptuous as vol
@@ -237,8 +238,12 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
def alarm_disarm(self, code=None):
"""Send disarm command."""

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/alarm_control_panel.mqtt/
"""
import asyncio
import logging
import re
import voluptuous as vol
@@ -117,8 +118,12 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
@property
def code_format(self):
"""One or more characters if code is defined."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@asyncio.coroutine
def async_alarm_disarm(self, code=None):

View File

@@ -69,8 +69,8 @@ class NX584Alarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return che characters if code is defined."""
return '[0-9]{4}([0-9]{2})?'
"""Return one or more digits/characters."""
return 'Number'
@property
def state(self):

View File

@@ -66,7 +66,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the regex for code format or None if no code is required."""
return '^\\d{4,6}$'
return 'Number'
@property
def state(self):

View File

@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.simplisafe/
"""
import logging
import re
import voluptuous as vol
@@ -83,8 +84,12 @@ class SimpliSafeAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return one or more characters if code is defined."""
return None if self._code is None else '.+'
"""Return one or more digits/characters."""
if self._code is None:
return None
elif isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
return 'Any'
@property
def state(self):

View File

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

View File

@@ -60,8 +60,8 @@ class VerisureAlarm(alarm.AlarmControlPanel):
@property
def code_format(self):
"""Return the code format as regex."""
return '^\\d{%s}$' % self._digits
"""Return one or more digits/characters."""
return 'Number'
@property
def changed_by(self):

View File

@@ -18,7 +18,7 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['amcrest==1.2.2']
REQUIREMENTS = ['amcrest==1.2.3']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)

View File

@@ -2,7 +2,7 @@
Rest API for Home Assistant.
For more details about the RESTful API, please refer to the documentation at
https://home-assistant.io/developers/api/
https://developers.home-assistant.io/docs/en/external_api_rest.html
"""
import asyncio
import json
@@ -11,31 +11,34 @@ import logging
from aiohttp import web
import async_timeout
import homeassistant.core as ha
import homeassistant.remote as rem
from homeassistant.bootstrap import DATA_LOGGING
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED,
HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND,
MATCH_ALL, URL_API, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG,
URL_API_EVENTS, URL_API_SERVICES,
URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, URL_API_TEMPLATE,
__version__)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.state import AsyncTrackStates
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers import template
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, HTTP_BAD_REQUEST,
HTTP_CREATED, HTTP_NOT_FOUND, MATCH_ALL, URL_API, URL_API_COMPONENTS,
URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG, URL_API_EVENTS,
URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM,
URL_API_TEMPLATE, __version__)
import homeassistant.core as ha
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates
import homeassistant.remote as rem
_LOGGER = logging.getLogger(__name__)
ATTR_BASE_URL = 'base_url'
ATTR_LOCATION_NAME = 'location_name'
ATTR_REQUIRES_API_PASSWORD = 'requires_api_password'
ATTR_VERSION = 'version'
DOMAIN = 'api'
DEPENDENCIES = ['http']
STREAM_PING_PAYLOAD = "ping"
STREAM_PING_PAYLOAD = 'ping'
STREAM_PING_INTERVAL = 50 # seconds
_LOGGER = logging.getLogger(__name__)
def setup(hass, config):
"""Register the API with the HTTP interface."""
@@ -62,22 +65,21 @@ class APIStatusView(HomeAssistantView):
"""View to handle Status requests."""
url = URL_API
name = "api:status"
name = 'api:status'
@ha.callback
def get(self, request):
"""Retrieve if API is running."""
return self.json_message('API running.')
return self.json_message("API running.")
class APIEventStream(HomeAssistantView):
"""View to handle EventStream requests."""
url = URL_API_STREAM
name = "api:stream"
name = 'api:stream'
@asyncio.coroutine
def get(self, request):
async def get(self, request):
"""Provide a streaming interface for the event bus."""
# pylint: disable=no-self-use
hass = request.app['hass']
@@ -88,8 +90,7 @@ class APIEventStream(HomeAssistantView):
if restrict:
restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP]
@asyncio.coroutine
def forward_events(event):
async def forward_events(event):
"""Forward events to the open request."""
if event.event_type == EVENT_TIME_CHANGED:
return
@@ -97,56 +98,56 @@ class APIEventStream(HomeAssistantView):
if restrict and event.event_type not in restrict:
return
_LOGGER.debug('STREAM %s FORWARDING %s', id(stop_obj), event)
_LOGGER.debug("STREAM %s FORWARDING %s", id(stop_obj), event)
if event.event_type == EVENT_HOMEASSISTANT_STOP:
data = stop_obj
else:
data = json.dumps(event, cls=rem.JSONEncoder)
yield from to_write.put(data)
await to_write.put(data)
response = web.StreamResponse()
response.content_type = 'text/event-stream'
yield from response.prepare(request)
await response.prepare(request)
unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events)
try:
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
_LOGGER.debug("STREAM %s ATTACHED", id(stop_obj))
# Fire off one message so browsers fire open event right away
yield from to_write.put(STREAM_PING_PAYLOAD)
await to_write.put(STREAM_PING_PAYLOAD)
while True:
try:
with async_timeout.timeout(STREAM_PING_INTERVAL,
loop=hass.loop):
payload = yield from to_write.get()
payload = await to_write.get()
if payload is stop_obj:
break
msg = "data: {}\n\n".format(payload)
_LOGGER.debug('STREAM %s WRITING %s', id(stop_obj),
msg.strip())
yield from response.write(msg.encode("UTF-8"))
_LOGGER.debug(
"STREAM %s WRITING %s", id(stop_obj), msg.strip())
await response.write(msg.encode('UTF-8'))
except asyncio.TimeoutError:
yield from to_write.put(STREAM_PING_PAYLOAD)
await to_write.put(STREAM_PING_PAYLOAD)
except asyncio.CancelledError:
_LOGGER.debug('STREAM %s ABORT', id(stop_obj))
_LOGGER.debug("STREAM %s ABORT", id(stop_obj))
finally:
_LOGGER.debug('STREAM %s RESPONSE CLOSED', id(stop_obj))
_LOGGER.debug("STREAM %s RESPONSE CLOSED", id(stop_obj))
unsub_stream()
class APIConfigView(HomeAssistantView):
"""View to handle Config requests."""
"""View to handle Configuration requests."""
url = URL_API_CONFIG
name = "api:config"
name = 'api:config'
@ha.callback
def get(self, request):
@@ -155,22 +156,22 @@ class APIConfigView(HomeAssistantView):
class APIDiscoveryView(HomeAssistantView):
"""View to provide discovery info."""
"""View to provide Discovery information."""
requires_auth = False
url = URL_API_DISCOVERY_INFO
name = "api:discovery"
name = 'api:discovery'
@ha.callback
def get(self, request):
"""Get discovery info."""
"""Get discovery information."""
hass = request.app['hass']
needs_auth = hass.config.api.api_password is not None
return self.json({
'base_url': hass.config.api.base_url,
'location_name': hass.config.location_name,
'requires_api_password': needs_auth,
'version': __version__
ATTR_BASE_URL: hass.config.api.base_url,
ATTR_LOCATION_NAME: hass.config.location_name,
ATTR_REQUIRES_API_PASSWORD: needs_auth,
ATTR_VERSION: __version__,
})
@@ -189,8 +190,8 @@ class APIStatesView(HomeAssistantView):
class APIEntityStateView(HomeAssistantView):
"""View to handle EntityState requests."""
url = "/api/states/{entity_id}"
name = "api:entity-state"
url = '/api/states/{entity_id}'
name = 'api:entity-state'
@ha.callback
def get(self, request, entity_id):
@@ -198,22 +199,21 @@ class APIEntityStateView(HomeAssistantView):
state = request.app['hass'].states.get(entity_id)
if state:
return self.json(state)
return self.json_message('Entity not found', HTTP_NOT_FOUND)
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
@asyncio.coroutine
def post(self, request, entity_id):
async def post(self, request, entity_id):
"""Update state of entity."""
hass = request.app['hass']
try:
data = yield from request.json()
data = await request.json()
except ValueError:
return self.json_message('Invalid JSON specified',
HTTP_BAD_REQUEST)
return self.json_message(
"Invalid JSON specified.", HTTP_BAD_REQUEST)
new_state = data.get('state')
if new_state is None:
return self.json_message('No state specified', HTTP_BAD_REQUEST)
return self.json_message("No state specified.", HTTP_BAD_REQUEST)
attributes = data.get('attributes')
force_update = data.get('force_update', False)
@@ -235,15 +235,15 @@ class APIEntityStateView(HomeAssistantView):
def delete(self, request, entity_id):
"""Remove entity."""
if request.app['hass'].states.async_remove(entity_id):
return self.json_message('Entity removed')
return self.json_message('Entity not found', HTTP_NOT_FOUND)
return self.json_message("Entity removed.")
return self.json_message("Entity not found.", HTTP_NOT_FOUND)
class APIEventListenersView(HomeAssistantView):
"""View to handle EventListeners requests."""
url = URL_API_EVENTS
name = "api:event-listeners"
name = 'api:event-listeners'
@ha.callback
def get(self, request):
@@ -255,21 +255,20 @@ class APIEventView(HomeAssistantView):
"""View to handle Event requests."""
url = '/api/events/{event_type}'
name = "api:event"
name = 'api:event'
@asyncio.coroutine
def post(self, request, event_type):
async def post(self, request, event_type):
"""Fire events."""
body = yield from request.text()
body = await request.text()
try:
event_data = json.loads(body) if body else None
except ValueError:
return self.json_message('Event data should be valid JSON',
HTTP_BAD_REQUEST)
return self.json_message(
"Event data should be valid JSON.", HTTP_BAD_REQUEST)
if event_data is not None and not isinstance(event_data, dict):
return self.json_message('Event data should be a JSON object',
HTTP_BAD_REQUEST)
return self.json_message(
"Event data should be a JSON object", HTTP_BAD_REQUEST)
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
@@ -280,8 +279,8 @@ class APIEventView(HomeAssistantView):
if state:
event_data[key] = state
request.app['hass'].bus.async_fire(event_type, event_data,
ha.EventOrigin.remote)
request.app['hass'].bus.async_fire(
event_type, event_data, ha.EventOrigin.remote)
return self.json_message("Event {} fired.".format(event_type))
@@ -290,37 +289,35 @@ class APIServicesView(HomeAssistantView):
"""View to handle Services requests."""
url = URL_API_SERVICES
name = "api:services"
name = 'api:services'
@asyncio.coroutine
def get(self, request):
async def get(self, request):
"""Get registered services."""
services = yield from async_services_json(request.app['hass'])
services = await async_services_json(request.app['hass'])
return self.json(services)
class APIDomainServicesView(HomeAssistantView):
"""View to handle DomainServices requests."""
url = "/api/services/{domain}/{service}"
name = "api:domain-services"
url = '/api/services/{domain}/{service}'
name = 'api:domain-services'
@asyncio.coroutine
def post(self, request, domain, service):
async def post(self, request, domain, service):
"""Call a service.
Returns a list of changed states.
"""
hass = request.app['hass']
body = yield from request.text()
body = await request.text()
try:
data = json.loads(body) if body else None
except ValueError:
return self.json_message('Data should be valid JSON',
HTTP_BAD_REQUEST)
return self.json_message(
"Data should be valid JSON.", HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states:
yield from hass.services.async_call(domain, service, data, True)
await hass.services.async_call(domain, service, data, True)
return self.json(changed_states)
@@ -329,7 +326,7 @@ class APIComponentsView(HomeAssistantView):
"""View to handle Components requests."""
url = URL_API_COMPONENTS
name = "api:components"
name = 'api:components'
@ha.callback
def get(self, request):
@@ -338,43 +335,41 @@ class APIComponentsView(HomeAssistantView):
class APITemplateView(HomeAssistantView):
"""View to handle requests."""
"""View to handle Template requests."""
url = URL_API_TEMPLATE
name = "api:template"
name = 'api:template'
@asyncio.coroutine
def post(self, request):
async def post(self, request):
"""Render a template."""
try:
data = yield from request.json()
data = await request.json()
tpl = template.Template(data['template'], request.app['hass'])
return tpl.async_render(data.get('variables'))
except (ValueError, TemplateError) as ex:
return self.json_message('Error rendering template: {}'.format(ex),
HTTP_BAD_REQUEST)
return self.json_message(
"Error rendering template: {}".format(ex), HTTP_BAD_REQUEST)
class APIErrorLog(HomeAssistantView):
"""View to fetch the error log."""
"""View to fetch the API error log."""
url = URL_API_ERROR_LOG
name = "api:error_log"
name = 'api:error_log'
async def get(self, request):
"""Retrieve API error log."""
return await self.file(request, request.app['hass'].data[DATA_LOGGING])
return web.FileResponse(request.app['hass'].data[DATA_LOGGING])
@asyncio.coroutine
def async_services_json(hass):
async def async_services_json(hass):
"""Generate services data to JSONify."""
descriptions = yield from async_get_all_descriptions(hass)
return [{"domain": key, "services": value}
descriptions = await async_get_all_descriptions(hass)
return [{'domain': key, 'services': value}
for key, value in descriptions.items()]
def async_events_json(hass):
"""Generate event data to JSONify."""
return [{"event": key, "listener_count": value}
return [{'event': key, 'listener_count': value}
for key, value in hass.bus.async_listeners().items()]

View File

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

View File

@@ -5,14 +5,18 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/arlo/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from requests.exceptions import HTTPError, ConnectTimeout
from homeassistant.helpers import config_validation as cv
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL)
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.dispatcher import dispatcher_send
REQUIREMENTS = ['pyarlo==0.1.2']
REQUIREMENTS = ['pyarlo==0.1.7']
_LOGGER = logging.getLogger(__name__)
@@ -25,10 +29,16 @@ DOMAIN = 'arlo'
NOTIFICATION_ID = 'arlo_notification'
NOTIFICATION_TITLE = 'Arlo Component Setup'
SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_ARLO = "arlo_update"
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
}),
}, extra=vol.ALLOW_EXTRA)
@@ -38,6 +48,7 @@ def setup(hass, config):
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
scan_interval = conf.get(CONF_SCAN_INTERVAL)
try:
from pyarlo import PyArlo
@@ -45,7 +56,17 @@ def setup(hass, config):
arlo = PyArlo(username, password, preload=False)
if not arlo.is_connected:
return False
# assign refresh period to base station thread
arlo_base_station = next((
station for station in arlo.base_stations), None)
if arlo_base_station is None:
return False
arlo_base_station.refresh_rate = scan_interval.total_seconds()
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
hass.components.persistent_notification.create(
@@ -55,4 +76,17 @@ def setup(hass, config):
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
return False
def hub_refresh(event_time):
"""Call ArloHub to refresh information."""
_LOGGER.info("Updating Arlo Hub component")
hass.data[DATA_ARLO].update(update_cameras=True,
update_base_station=True)
dispatcher_send(hass, SIGNAL_UPDATE_ARLO)
# register service
hass.services.register(DOMAIN, 'update', hub_refresh)
# register scan interval for ArloHub
track_time_interval(hass, hub_refresh, scan_interval)
return True

View File

@@ -0,0 +1,351 @@
"""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
types "authorization_code" and "refresh_token". Because we follow the OAuth2
spec, data should be send in formatted as x-www-form-urlencoded. Examples will
be in JSON as it's more readable.
## Grant type authorization_code
Exchange the authorization code retrieved from the login flow for tokens.
{
"grant_type": "authorization_code",
"code": "411ee2f916e648d691e937ae9344681e"
}
Return value will be the access and refresh tokens. The access token will have
a limited expiration. New access tokens can be requested using the refresh
token.
{
"access_token": "ABCDEFGH",
"expires_in": 1800,
"refresh_token": "IJKLMNOPQRST",
"token_type": "Bearer"
}
## Grant type refresh_token
Request a new access token using a refresh token.
{
"grant_type": "refresh_token",
"refresh_token": "IJKLMNOPQRST"
}
Return value will be a new access token. The access token will have
a limited expiration.
{
"access_token": "ABCDEFGH",
"expires_in": 1800,
"token_type": "Bearer"
}
"""
import logging
import uuid
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.http.data_validator import RequestDataValidator
from .client import verify_client
DOMAIN = 'auth'
DEPENDENCIES = ['http']
_LOGGER = logging.getLogger(__name__)
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))
return True
class AuthProvidersView(HomeAssistantView):
"""View to get available auth providers."""
url = '/auth/providers'
name = 'api:auth:providers'
requires_auth = False
@verify_client
async def get(self, request, client):
"""Get available auth providers."""
return self.json([{
'name': provider.name,
'id': provider.id,
'type': provider.type,
} for provider in request.app['hass'].auth.async_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)
# pylint: disable=arguments-differ
@verify_client
@RequestDataValidator(vol.Schema({
vol.Required('handler'): vol.Any(str, list),
vol.Required('redirect_uri'): str,
}))
async def post(self, request, client, data):
"""Create a new login flow."""
if data['redirect_uri'] not in client.redirect_uris:
return self.json_message('invalid redirect uri', )
# 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
# pylint: disable=arguments-differ
async def get(self, request):
"""Do not allow getting status of a flow in progress."""
return self.json_message('Invalid flow specified', 404)
# pylint: disable=arguments-differ
@verify_client
@RequestDataValidator(vol.Schema(dict), allow_empty=True)
async def post(self, request, client, flow_id, data):
"""Handle progressing a login flow request."""
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
def __init__(self, retrieve_credentials):
"""Initialize the grant token view."""
self._retrieve_credentials = retrieve_credentials
@verify_client
async def post(self, request, client):
"""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, 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:
return self.json({
'error': 'invalid_request',
}, status_code=400)
credentials = self._retrieve_credentials(client_id, code)
if credentials is None:
return self.json({
'error': 'invalid_request',
}, status_code=400)
user = await hass.auth.async_get_or_create_user(credentials)
refresh_token = await hass.auth.async_create_refresh_token(user,
client_id)
access_token = hass.auth.async_create_access_token(refresh_token)
return self.json({
'access_token': access_token.token,
'token_type': 'Bearer',
'refresh_token': refresh_token.token,
'expires_in':
int(refresh_token.access_token_expiration.total_seconds()),
})
async def _async_handle_refresh_token(self, hass, client_id, data):
"""Handle authorization code request."""
token = data.get('refresh_token')
if token is None:
return self.json({
'error': 'invalid_request',
}, status_code=400)
refresh_token = await hass.auth.async_get_refresh_token(token)
if refresh_token is None or refresh_token.client_id != client_id:
return self.json({
'error': 'invalid_grant',
}, status_code=400)
access_token = hass.auth.async_create_access_token(refresh_token)
return self.json({
'access_token': access_token.token,
'token_type': 'Bearer',
'expires_in':
int(refresh_token.access_token_expiration.total_seconds()),
})
class LinkUserView(HomeAssistantView):
"""View to link existing users to new credentials."""
url = '/auth/link_user'
name = 'api:auth:link_user'
def __init__(self, retrieve_credentials):
"""Initialize the link user view."""
self._retrieve_credentials = retrieve_credentials
@RequestDataValidator(vol.Schema({
'code': str,
'client_id': str,
}))
async def post(self, request, data):
"""Link a user."""
hass = request.app['hass']
user = request['hass_user']
credentials = self._retrieve_credentials(
data['client_id'], data['code'])
if credentials is None:
return self.json_message('Invalid code', status_code=400)
await hass.auth.async_link_user(user, credentials)
return self.json_message('User linked')
@callback
def _create_cred_store():
"""Create a credential store."""
temp_credentials = {}
@callback
def store_credentials(client_id, credentials):
"""Store credentials and return a code to retrieve it."""
code = uuid.uuid4().hex
temp_credentials[(client_id, code)] = credentials
return code
@callback
def retrieve_credentials(client_id, code):
"""Retrieve credentials."""
return temp_credentials.pop((client_id, code), None)
return store_credentials, retrieve_credentials

View File

@@ -0,0 +1,79 @@
"""Helpers to resolve client ID/secret."""
import base64
from functools import wraps
import hmac
import aiohttp.hdrs
def verify_client(method):
"""Decorator to verify client id/secret on requests."""
@wraps(method)
async def wrapper(view, request, *args, **kwargs):
"""Verify client id/secret before doing request."""
client = await _verify_client(request)
if client is None:
return view.json({
'error': 'invalid_client',
}, status_code=401)
return await method(
view, request, *args, **kwargs, client=client)
return wrapper
async def _verify_client(request):
"""Method to verify the client id/secret in consistent time.
By using a consistent time for looking up client id and comparing the
secret, we prevent attacks by malicious actors trying different client ids
and are able to derive from the time it takes to process the request if
they guessed the client id correctly.
"""
if aiohttp.hdrs.AUTHORIZATION not in request.headers:
return None
auth_type, auth_value = \
request.headers.get(aiohttp.hdrs.AUTHORIZATION).split(' ', 1)
if auth_type != 'Basic':
return None
decoded = base64.b64decode(auth_value).decode('utf-8')
try:
client_id, client_secret = decoded.split(':', 1)
except ValueError:
# If no ':' in decoded
client_id, client_secret = decoded, None
return await async_secure_get_client(
request.app['hass'], client_id, client_secret)
async def async_secure_get_client(hass, client_id, client_secret):
"""Get a client id/secret in consistent time."""
client = await hass.auth.async_get_client(client_id)
if client is None:
if client_secret is not None:
# Still do a compare so we run same time as if a client was found.
hmac.compare_digest(client_secret.encode('utf-8'),
client_secret.encode('utf-8'))
return None
if client.secret is None:
return client
elif client_secret is None:
# Still do a compare so we run same time as if a secret was passed.
hmac.compare_digest(client.secret.encode('utf-8'),
client.secret.encode('utf-8'))
return None
elif hmac.compare_digest(client_secret.encode('utf-8'),
client.secret.encode('utf-8')):
return client
return None

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/automation/
"""
import asyncio
from functools import partial
import importlib
import logging
import voluptuous as vol
@@ -22,7 +23,6 @@ from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
@@ -58,12 +58,14 @@ _LOGGER = logging.getLogger(__name__)
def _platform_validator(config):
"""Validate it is a valid platform."""
platform = get_platform(DOMAIN, config[CONF_PLATFORM])
try:
platform = importlib.import_module(
'homeassistant.components.automation.{}'.format(
config[CONF_PLATFORM]))
except ImportError:
raise vol.Invalid('Invalid platform specified') from None
if not hasattr(platform, 'TRIGGER_SCHEMA'):
return config
return getattr(platform, 'TRIGGER_SCHEMA')(config)
return platform.TRIGGER_SCHEMA(config)
_TRIGGER_SCHEMA = vol.All(
@@ -71,7 +73,7 @@ _TRIGGER_SCHEMA = vol.All(
[
vol.All(
vol.Schema({
vol.Required(CONF_PLATFORM): cv.platform_validator(DOMAIN)
vol.Required(CONF_PLATFORM): str
}, extra=vol.ALLOW_EXTRA),
_platform_validator
),
@@ -96,7 +98,7 @@ SERVICE_SCHEMA = vol.Schema({
})
TRIGGER_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(ATTR_VARIABLES, default={}): dict,
})

View File

@@ -145,7 +145,7 @@ def request_configuration(hass, config, name, host, serialnumber):
def setup(hass, config):
"""Set up for Axis devices."""
def _shutdown(call): # pylint: disable=unused-argument
def _shutdown(call):
"""Stop the event stream on shutdown."""
for serialnumber, device in AXIS_DEVICES.items():
_LOGGER.info("Stopping event stream for %s.", serialnumber)
@@ -272,8 +272,7 @@ class AxisDeviceEvent(Entity):
def _update_callback(self):
"""Update the sensor's state, if needed."""
self.update()
self.schedule_update_ha_state()
self.schedule_update_ha_state(True)
@property
def name(self):

View File

@@ -50,13 +50,23 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
async def async_setup(hass, config):
"""Track states and offer events for binary sensors."""
component = EntityComponent(
component = hass.data[DOMAIN] = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
# pylint: disable=no-self-use
class BinarySensorDevice(Entity):
"""Represent a binary sensor."""

View File

@@ -217,4 +217,4 @@ class BayesianBinarySensor(BinarySensorDevice):
@asyncio.coroutine
def async_update(self):
"""Get the latest data and update the states."""
self._deviation = bool(self.probability > self._probability_threshold)
self._deviation = bool(self.probability >= self._probability_threshold)

View File

@@ -11,7 +11,6 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.loader import get_component
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -31,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available BloomSky weather binary sensors."""
bloomsky = get_component('bloomsky')
bloomsky = hass.components.bloomsky
# Default needed in case of discovery
sensors = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES)

View File

@@ -17,9 +17,19 @@ _LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'lids': ['Doors', 'opening'],
'windows': ['Windows', 'opening'],
'door_lock_state': ['Door lock state', 'safety']
'door_lock_state': ['Door lock state', 'safety'],
'lights_parking': ['Parking lights', 'light'],
'condition_based_services': ['Condition based services', 'problem'],
'check_control_messages': ['Control messages', 'problem']
}
SENSOR_TYPES_ELEC = {
'charging_status': ['Charging status', 'power'],
'connection_status': ['Connection status', 'plug']
}
SENSOR_TYPES_ELEC.update(SENSOR_TYPES)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the BMW sensors."""
@@ -29,10 +39,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = []
for account in accounts:
for vehicle in account.account.vehicles:
for key, value in sorted(SENSOR_TYPES.items()):
device = BMWConnectedDriveSensor(account, vehicle, key,
value[0], value[1])
devices.append(device)
if vehicle.has_hv_battery:
_LOGGER.debug('BMW with a high voltage battery')
for key, value in sorted(SENSOR_TYPES_ELEC.items()):
device = BMWConnectedDriveSensor(account, vehicle, key,
value[0], value[1])
devices.append(device)
elif vehicle.has_internal_combustion_engine:
_LOGGER.debug('BMW with an internal combustion engine')
for key, value in sorted(SENSOR_TYPES.items()):
device = BMWConnectedDriveSensor(account, vehicle, key,
value[0], value[1])
devices.append(device)
add_devices(devices, True)
@@ -92,12 +110,34 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
result[window.name] = window.state.value
elif self._attribute == 'door_lock_state':
result['door_lock_state'] = vehicle_state.door_lock_state.value
result['last_update_reason'] = vehicle_state.last_update_reason
elif self._attribute == 'lights_parking':
result['lights_parking'] = vehicle_state.parking_lights.value
elif self._attribute == 'condition_based_services':
for report in vehicle_state.condition_based_services:
result.update(self._format_cbs_report(report))
elif self._attribute == 'check_control_messages':
check_control_messages = vehicle_state.check_control_messages
if not check_control_messages:
result['check_control_messages'] = 'OK'
else:
result['check_control_messages'] = check_control_messages
elif self._attribute == 'charging_status':
result['charging_status'] = vehicle_state.charging_status.value
# pylint: disable=W0212
result['last_charging_end_result'] = \
vehicle_state._attributes['lastChargingEndResult']
if self._attribute == 'connection_status':
# pylint: disable=W0212
result['connection_status'] = \
vehicle_state._attributes['connectionStatus']
return result
return sorted(result.items())
def update(self):
"""Read new state data from the library."""
from bimmer_connected.state import LockState
from bimmer_connected.state import ChargingState
vehicle_state = self._vehicle.state
# device class opening: On means open, Off means closed
@@ -111,6 +151,37 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
# Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED
self._state = vehicle_state.door_lock_state not in \
[LockState.LOCKED, LockState.SECURED]
# device class light: On means light detected, Off means no light
if self._attribute == 'lights_parking':
self._state = vehicle_state.are_parking_lights_on
# device class problem: On means problem detected, Off means no problem
if self._attribute == 'condition_based_services':
self._state = not vehicle_state.are_all_cbs_ok
if self._attribute == 'check_control_messages':
self._state = vehicle_state.has_check_control_messages
# device class power: On means power detected, Off means no power
if self._attribute == 'charging_status':
self._state = vehicle_state.charging_status in \
[ChargingState.CHARGING]
# device class plug: On means device is plugged in,
# Off means device is unplugged
if self._attribute == 'connection_status':
# pylint: disable=W0212
self._state = (vehicle_state._attributes['connectionStatus'] ==
'CONNECTED')
@staticmethod
def _format_cbs_report(report):
result = {}
service_type = report.service_type.lower().replace('_', ' ')
result['{} status'.format(service_type)] = report.state.value
if report.due_date is not None:
result['{} date'.format(service_type)] = \
report.due_date.strftime('%Y-%m-%d')
if report.due_distance is not None:
result['{} distance'.format(service_type)] = \
'{} km'.format(report.due_distance)
return result
def update_callback(self):
"""Schedule a state update."""

View File

@@ -35,7 +35,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Command line Binary Sensor."""
name = config.get(CONF_NAME)

View File

@@ -6,27 +6,39 @@ https://home-assistant.io/components/binary_sensor.deconz/
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['deconz']
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Old way of setting up deCONZ binary sensors."""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the deCONZ binary sensor."""
if discovery_info is None:
return
@callback
def async_add_sensor(sensors):
"""Add binary sensor from deCONZ."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_BINARY_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True)
from pydeconz.sensor import DECONZ_BINARY_SENSOR
sensors = hass.data[DATA_DECONZ].sensors
entities = []
hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
for sensor in sensors.values():
if sensor and sensor.type in DECONZ_BINARY_SENSOR:
entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True)
async_add_sensor(hass.data[DATA_DECONZ].sensors.values())
class DeconzBinarySensor(BinarySensorDevice):
@@ -95,6 +107,6 @@ class DeconzBinarySensor(BinarySensorDevice):
attr = {}
if self._sensor.battery:
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
if self._sensor.type in PRESENCE and self._sensor.dark:
if self._sensor.type in PRESENCE and self._sensor.dark is not None:
attr['dark'] = self._sensor.dark
return attr

View File

@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.eight_sleep/
"""
import logging
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.eight_sleep import (
@@ -16,8 +15,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['eight_sleep']
@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 the eight sleep binary sensor."""
if discovery_info is None:
return
@@ -63,7 +62,6 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice):
"""Return true if the binary sensor is on."""
return self._state
@asyncio.coroutine
def async_update(self):
async def async_update(self):
"""Retrieve latest state."""
self._state = self._usrobj.bed_presence

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/binary_sensor.envisalink/
"""
import asyncio
import logging
import datetime
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -14,6 +15,7 @@ from homeassistant.components.envisalink import (
DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice,
SIGNAL_ZONE_UPDATE)
from homeassistant.const import ATTR_LAST_TRIP_TIME
from homeassistant.util import dt as dt_util
_LOGGER = logging.getLogger(__name__)
@@ -63,7 +65,25 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
def device_state_attributes(self):
"""Return the state attributes."""
attr = {}
attr[ATTR_LAST_TRIP_TIME] = self._info['last_fault']
# The Envisalink library returns a "last_fault" value that's the
# number of seconds since the last fault, up to a maximum of 327680
# seconds (65536 5-second ticks).
#
# We don't want the HA event log to fill up with a bunch of no-op
# "state changes" that are just that number ticking up once per poll
# interval, so we subtract it from the current second-accurate time
# unless it is already at the maximum value, in which case we set it
# to None since we can't determine the actual value.
seconds_ago = self._info['last_fault']
if seconds_ago < 65536 * 5:
now = dt_util.now().replace(microsecond=0)
delta = datetime.timedelta(seconds=seconds_ago)
last_trip_time = (now - delta).isoformat()
else:
last_trip_time = None
attr[ATTR_LAST_TRIP_TIME] = last_trip_time
return attr
@property

View File

@@ -23,7 +23,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the GC100 devices."""
binary_sensors = []

View File

@@ -0,0 +1,85 @@
"""
Support for HomematicIP binary sensor.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.homematicip_cloud/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.homematicip_cloud import (
HomematicipGenericDevice, DOMAIN as HOMEMATICIP_CLOUD_DOMAIN,
ATTR_HOME_ID)
DEPENDENCIES = ['homematicip_cloud']
_LOGGER = logging.getLogger(__name__)
ATTR_WINDOW_STATE = 'window_state'
ATTR_EVENT_DELAY = 'event_delay'
ATTR_MOTION_DETECTED = 'motion_detected'
ATTR_ILLUMINATION = 'illumination'
HMIP_OPEN = 'open'
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the HomematicIP binary sensor devices."""
from homematicip.device import (ShutterContact, MotionDetectorIndoor)
if discovery_info is None:
return
home = hass.data[HOMEMATICIP_CLOUD_DOMAIN][discovery_info[ATTR_HOME_ID]]
devices = []
for device in home.devices:
if isinstance(device, ShutterContact):
devices.append(HomematicipShutterContact(home, device))
elif isinstance(device, MotionDetectorIndoor):
devices.append(HomematicipMotionDetector(home, device))
if devices:
async_add_devices(devices)
class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice):
"""HomematicIP shutter contact."""
def __init__(self, home, device):
"""Initialize the shutter contact."""
super().__init__(home, device)
@property
def device_class(self):
"""Return the class of this sensor."""
return 'door'
@property
def is_on(self):
"""Return true if the shutter contact is on/open."""
if self._device.sabotage:
return True
if self._device.windowState is None:
return None
return self._device.windowState.lower() == HMIP_OPEN
class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice):
"""MomematicIP motion detector."""
def __init__(self, home, device):
"""Initialize the shutter contact."""
super().__init__(home, device)
@property
def device_class(self):
"""Return the class of this sensor."""
return 'motion'
@property
def is_on(self):
"""Return true if motion is detected."""
if self._device.sabotage:
return True
return self._device.motionDetected

View File

@@ -0,0 +1,81 @@
"""
Support for Hydrawise sprinkler.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hydrawise/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.hydrawise import (
BINARY_SENSORS, DATA_HYDRAWISE, HydrawiseEntity, DEVICE_MAP,
DEVICE_MAP_INDEX)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['hydrawise']
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=BINARY_SENSORS):
vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a Hydrawise device."""
hydrawise = hass.data[DATA_HYDRAWISE].data
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type in ['status', 'rain_sensor']:
sensors.append(
HydrawiseBinarySensor(
hydrawise.controller_status, sensor_type))
else:
# create a sensor for each zone
for zone in hydrawise.relays:
zone_data = zone
zone_data['running'] = \
hydrawise.controller_status.get('running', False)
sensors.append(HydrawiseBinarySensor(zone_data, sensor_type))
add_devices(sensors, True)
class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorDevice):
"""A sensor implementation for Hydrawise device."""
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
def update(self):
"""Get the latest data and updates the state."""
_LOGGER.debug("Updating Hydrawise binary sensor: %s", self._name)
mydata = self.hass.data[DATA_HYDRAWISE].data
if self._sensor_type == 'status':
self._state = mydata.status == 'All good!'
elif self._sensor_type == 'rain_sensor':
for sensor in mydata.sensors:
if sensor['name'] == 'Rain':
self._state = sensor['active'] == 1
elif self._sensor_type == 'is_watering':
if not mydata.running:
self._state = False
elif int(mydata.running[0]['relay']) == self.data['relay']:
self._state = True
else:
self._state = False
@property
def device_class(self):
"""Return the device class of the sensor type."""
return DEVICE_MAP[self._sensor_type][
DEVICE_MAP_INDEX.index('DEVICE_CLASS_INDEX')]

View File

@@ -23,7 +23,7 @@ SENSOR_TYPES = {'openClosedSensor': 'opening',
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the INSTEON PLM device class for the hass platform."""
plm = hass.data['insteon_plm']
plm = hass.data['insteon_plm'].get('plm')
address = discovery_info['address']
device = plm.devices[address]

View File

@@ -28,7 +28,6 @@ ISY_DEVICE_TYPES = {
}
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Set up the ISY994 binary sensor platform."""
@@ -117,8 +116,10 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
# pylint: disable=protected-access
if _is_val_unknown(self._node.status._val):
self._computed_state = None
self._status_was_unknown = True
else:
self._computed_state = bool(self._node.status._val)
self._status_was_unknown = False
@asyncio.coroutine
def async_added_to_hass(self) -> None:
@@ -156,9 +157,13 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
# pylint: disable=protected-access
if not _is_val_unknown(self._negative_node.status._val):
# If the negative node has a value, it means the negative node is
# in use for this device. Therefore, we cannot determine the state
# of the sensor until we receive our first ON event.
self._computed_state = None
# in use for this device. Next we need to check to see if the
# negative and positive nodes disagree on the state (both ON or
# both OFF).
if self._negative_node.status._val == self._node.status._val:
# The states disagree, therefore we cannot determine the state
# of the sensor until we receive our first ON event.
self._computed_state = None
def _negative_node_control_handler(self, event: object) -> None:
"""Handle an "On" control event from the "negative" node."""
@@ -189,14 +194,21 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
self.schedule_update_ha_state()
self._heartbeat()
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Ignore primary node status updates.
"""Primary node status updates.
We listen directly to the Control events on all nodes for this
device.
We MOSTLY ignore these updates, as we listen directly to the Control
events on all nodes for this device. However, there is one edge case:
If a leak sensor is unknown, due to a recent reboot of the ISY, the
status will get updated to dry upon the first heartbeat. This status
update is the only way that a leak sensor's status changes without
an accompanying Control event, so we need to watch for it.
"""
pass
if self._status_was_unknown and self._computed_state is None:
self._computed_state = bool(int(self._node.status))
self._status_was_unknown = False
self.schedule_update_ha_state()
self._heartbeat()
@property
def value(self) -> object:
@@ -286,7 +298,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
# No heartbeat timer is active
pass
# pylint: disable=unused-argument
@callback
def timer_elapsed(now) -> None:
"""Heartbeat missed; set state to indicate dead battery."""
@@ -301,7 +312,6 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
self._heartbeat_timer = async_track_point_in_utc_time(
self.hass, timer_elapsed, point_in_time)
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Ignore node status updates.

View File

@@ -115,7 +115,6 @@ class KNXBinarySensor(BinarySensorDevice):
"""Register callbacks to update hass after device was changed."""
async def after_update_callback(device):
"""Call after device was updated."""
# pylint: disable=unused-argument
await self.async_update_ha_state()
self.device.register_device_updated_cb(after_update_callback)

View File

@@ -0,0 +1,82 @@
"""
Support for wired binary sensors attached to a Konnected device.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.konnected/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.konnected import (
DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, SIGNAL_SENSOR_UPDATE)
from homeassistant.const import (
CONF_DEVICES, CONF_TYPE, CONF_NAME, CONF_BINARY_SENSORS, ATTR_ENTITY_ID,
ATTR_STATE)
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['konnected']
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up binary sensors attached to a Konnected device."""
if discovery_info is None:
return
data = hass.data[KONNECTED_DOMAIN]
device_id = discovery_info['device_id']
sensors = [KonnectedBinarySensor(device_id, pin_num, pin_data)
for pin_num, pin_data in
data[CONF_DEVICES][device_id][CONF_BINARY_SENSORS].items()]
async_add_devices(sensors)
class KonnectedBinarySensor(BinarySensorDevice):
"""Representation of a Konnected binary sensor."""
def __init__(self, device_id, pin_num, data):
"""Initialize the binary sensor."""
self._data = data
self._device_id = device_id
self._pin_num = pin_num
self._state = self._data.get(ATTR_STATE)
self._device_class = self._data.get(CONF_TYPE)
self._name = self._data.get(CONF_NAME, 'Konnected {} Zone {}'.format(
device_id, PIN_TO_ZONE[pin_num]))
_LOGGER.debug('Created new Konnected sensor: %s', self._name)
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the sensor."""
return self._state
@property
def should_poll(self):
"""No polling needed."""
return False
@property
def device_class(self):
"""Return the device class."""
return self._device_class
async def async_added_to_hass(self):
"""Store entity_id and register state change callback."""
self._data[ATTR_ENTITY_ID] = self.entity_id
async_dispatcher_connect(
self.hass, SIGNAL_SENSOR_UPDATE.format(self.entity_id),
self.async_set_state)
@callback
def async_set_state(self, state):
"""Update the sensor's state."""
self._state = state
self.async_schedule_update_ha_state()

View File

@@ -52,19 +52,18 @@ class LinodeBinarySensor(BinarySensorDevice):
self._node_id = node_id
self._state = None
self.data = None
self._attrs = {}
self._name = None
@property
def name(self):
"""Return the name of the sensor."""
if self.data is not None:
return self.data.label
return self._name
@property
def is_on(self):
"""Return true if the binary sensor is on."""
if self.data is not None:
return self.data.status == 'running'
return False
return self._state
@property
def device_class(self):
@@ -74,8 +73,18 @@ class LinodeBinarySensor(BinarySensorDevice):
@property
def device_state_attributes(self):
"""Return the state attributes of the Linode Node."""
if self.data:
return {
return self._attrs
def update(self):
"""Update state of sensor."""
self._linode.update()
if self._linode.data is not None:
for node in self._linode.data:
if node.id == self._node_id:
self.data = node
if self.data is not None:
self._state = self.data.status == 'running'
self._attrs = {
ATTR_CREATED: self.data.created,
ATTR_NODE_ID: self.data.id,
ATTR_NODE_NAME: self.data.label,
@@ -85,12 +94,4 @@ class LinodeBinarySensor(BinarySensorDevice):
ATTR_REGION: self.data.region.country,
ATTR_VCPUS: self.data.specs.vcpus,
}
return {}
def update(self):
"""Update state of sensor."""
self._linode.update()
if self._linode.data is not None:
for node in self._linode.data:
if node.id == self._node_id:
self.data = node
self._name = self.data.label

View File

@@ -6,6 +6,7 @@ https://home-assistant.io/components/binary_sensor.mqtt/
"""
import asyncio
import logging
from typing import Optional
import voluptuous as vol
@@ -24,7 +25,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor'
CONF_UNIQUE_ID = 'unique_id'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_FORCE_UPDATE = False
@@ -37,6 +38,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
# Integrations shouldn't never expose unique_id through configuration
# this here is an exception because MQTT is a msg transport, not a protocol
vol.Optional(CONF_UNIQUE_ID): cv.string,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -61,7 +65,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template
value_template,
config.get(CONF_UNIQUE_ID),
)])
@@ -70,7 +75,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def __init__(self, name, state_topic, availability_topic, device_class,
qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template):
payload_not_available, value_template,
unique_id: Optional[str]):
"""Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
@@ -83,6 +89,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._qos = qos
self._force_update = force_update
self._template = value_template
self._unique_id = unique_id
@asyncio.coroutine
def async_added_to_hass(self):
@@ -134,3 +141,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def force_update(self):
"""Force update."""
return self._force_update
@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id

View File

@@ -31,7 +31,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
sensors = []
hub = hass.data[MYCHEVY_DOMAIN]
for sconfig in SENSORS:
sensors.append(EVBinarySensor(hub, sconfig))
for car in hub.cars:
sensors.append(EVBinarySensor(hub, sconfig, car.vid))
async_add_devices(sensors)
@@ -45,16 +46,18 @@ class EVBinarySensor(BinarySensorDevice):
"""
def __init__(self, connection, config):
def __init__(self, connection, config, car_vid):
"""Initialize sensor with car connection."""
self._conn = connection
self._name = config.name
self._attr = config.attr
self._type = config.device_class
self._is_on = None
self._car_vid = car_vid
self.entity_id = ENTITY_ID_FORMAT.format(
'{}_{}'.format(MYCHEVY_DOMAIN, slugify(self._name)))
'{}_{}_{}'.format(MYCHEVY_DOMAIN,
slugify(self._car.name),
slugify(self._name)))
@property
def name(self):
@@ -66,6 +69,11 @@ class EVBinarySensor(BinarySensorDevice):
"""Return if on."""
return self._is_on
@property
def _car(self):
"""Return the car."""
return self._conn.get_car(self._car_vid)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
@@ -75,8 +83,8 @@ class EVBinarySensor(BinarySensorDevice):
@callback
def async_update_callback(self):
"""Update state."""
if self._conn.car is not None:
self._is_on = getattr(self._conn.car, self._attr, None)
if self._car is not None:
self._is_on = getattr(self._car, self._attr, None)
self.async_schedule_update_ha_state()
@property

View File

@@ -29,6 +29,7 @@ class MyStromView(HomeAssistantView):
url = '/api/mystrom'
name = 'api:mystrom'
supported_actions = ['single', 'double', 'long', 'touch']
def __init__(self, add_devices):
"""Initialize the myStrom URL endpoint."""
@@ -44,16 +45,18 @@ class MyStromView(HomeAssistantView):
@asyncio.coroutine
def _handle(self, hass, data):
"""Handle requests to the myStrom endpoint."""
button_action = list(data.keys())[0]
button_id = data[button_action]
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
button_action = next((
parameter for parameter in data
if parameter in self.supported_actions), None)
if button_action not in ['single', 'double', 'long', 'touch']:
if button_action is None:
_LOGGER.error(
"Received unidentified message from myStrom button: %s", data)
return ("Received unidentified message: {}".format(data),
HTTP_UNPROCESSABLE_ENTITY)
button_id = data[button_action]
entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action)
if entity_id not in self.buttons:
_LOGGER.info("New myStrom button/action detected: %s/%s",
button_id, button_action)

View File

@@ -7,27 +7,37 @@ https://home-assistant.io/components/binary_sensor.nest/
from itertools import chain
import logging
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.components.sensor.nest import NestSensor
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.nest import (
DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.components.nest import DATA_NEST
DEPENDENCIES = ['nest']
BINARY_TYPES = ['online']
BINARY_TYPES = {'online': 'connectivity'}
CLIMATE_BINARY_TYPES = [
'fan',
'is_using_emergency_heat',
'is_locked',
'has_leaf',
]
CLIMATE_BINARY_TYPES = {
'fan': None,
'is_using_emergency_heat': 'heat',
'is_locked': None,
'has_leaf': None,
}
CAMERA_BINARY_TYPES = [
'motion_detected',
'sound_detected',
'person_detected',
]
CAMERA_BINARY_TYPES = {
'motion_detected': 'motion',
'sound_detected': 'sound',
'person_detected': 'occupancy',
}
STRUCTURE_BINARY_TYPES = {
'away': None,
# 'security_state', # pending python-nest update
}
STRUCTURE_BINARY_STATE_MAP = {
'away': {'away': True, 'home': False},
'security_state': {'deter': True, 'ok': False},
}
_BINARY_TYPES_DEPRECATED = [
'hvac_ac_state',
@@ -40,19 +50,26 @@ _BINARY_TYPES_DEPRECATED = [
'hvac_emer_heat_state',
]
_VALID_BINARY_SENSOR_TYPES = BINARY_TYPES + CLIMATE_BINARY_TYPES \
+ CAMERA_BINARY_TYPES
_VALID_BINARY_SENSOR_TYPES = {**BINARY_TYPES, **CLIMATE_BINARY_TYPES,
**CAMERA_BINARY_TYPES, **STRUCTURE_BINARY_TYPES}
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest binary sensors."""
if discovery_info is None:
return
"""Set up the Nest binary sensors.
No longer used.
"""
async def async_setup_entry(hass, entry, async_add_devices):
"""Set up a Nest binary sensor based on a config entry."""
nest = hass.data[DATA_NEST]
discovery_info = \
hass.data.get(DATA_NEST_CONFIG, {}).get(CONF_BINARY_SENSORS, {})
# Add all available binary sensors if no Nest binary sensor config is set
if discovery_info == {}:
conditions = _VALID_BINARY_SENSOR_TYPES
@@ -67,32 +84,40 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"for valid options.")
_LOGGER.error(wstr)
sensors = []
device_chain = chain(nest.thermostats(),
nest.smoke_co_alarms(),
nest.cameras())
for structure, device in device_chain:
sensors += [NestBinarySensor(structure, device, variable)
for variable in conditions
if variable in BINARY_TYPES]
sensors += [NestBinarySensor(structure, device, variable)
for variable in conditions
if variable in CLIMATE_BINARY_TYPES
and device.is_thermostat]
if device.is_camera:
def get_binary_sensors():
"""Get the Nest binary sensors."""
sensors = []
for structure in nest.structures():
sensors += [NestBinarySensor(structure, None, variable)
for variable in conditions
if variable in STRUCTURE_BINARY_TYPES]
device_chain = chain(nest.thermostats(),
nest.smoke_co_alarms(),
nest.cameras())
for structure, device in device_chain:
sensors += [NestBinarySensor(structure, device, variable)
for variable in conditions
if variable in CAMERA_BINARY_TYPES]
for activity_zone in device.activity_zones:
sensors += [NestActivityZoneSensor(structure,
device,
activity_zone)]
if variable in BINARY_TYPES]
sensors += [NestBinarySensor(structure, device, variable)
for variable in conditions
if variable in CLIMATE_BINARY_TYPES
and device.is_thermostat]
add_devices(sensors, True)
if device.is_camera:
sensors += [NestBinarySensor(structure, device, variable)
for variable in conditions
if variable in CAMERA_BINARY_TYPES]
for activity_zone in device.activity_zones:
sensors += [NestActivityZoneSensor(structure,
device,
activity_zone)]
return sensors
async_add_devices(await hass.async_add_job(get_binary_sensors), True)
class NestBinarySensor(NestSensor, BinarySensorDevice):
class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
"""Represents a Nest binary sensor."""
@property
@@ -100,9 +125,19 @@ class NestBinarySensor(NestSensor, BinarySensorDevice):
"""Return true if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the device class of the binary sensor."""
return _VALID_BINARY_SENSOR_TYPES.get(self.variable)
def update(self):
"""Retrieve latest state."""
self._state = bool(getattr(self.device, self.variable))
value = getattr(self.device, self.variable)
if self.variable in STRUCTURE_BINARY_TYPES:
self._state = bool(STRUCTURE_BINARY_STATE_MAP
[self.variable][value])
else:
self._state = bool(value)
class NestActivityZoneSensor(NestBinarySensor):
@@ -115,9 +150,9 @@ class NestActivityZoneSensor(NestBinarySensor):
self._name = "{} {} activity".format(self._name, self.zone.name)
@property
def name(self):
"""Return the name of the nest, if any."""
return self._name
def device_class(self):
"""Return the device class of the binary sensor."""
return 'motion'
def update(self):
"""Retrieve latest state."""

View File

@@ -13,7 +13,6 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.netatmo import CameraData
from homeassistant.loader import get_component
from homeassistant.const import CONF_TIMEOUT
from homeassistant.helpers import config_validation as cv
@@ -58,10 +57,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the access to Netatmo binary sensor."""
netatmo = get_component('netatmo')
netatmo = hass.components.netatmo
home = config.get(CONF_HOME)
timeout = config.get(CONF_TIMEOUT)
if timeout is None:
@@ -69,12 +67,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
module_name = None
import lnetatmo
import pyatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
if not data.get_camera_names():
return None
except lnetatmo.NoDevice:
except pyatmo.NoDevice:
return None
welcome_sensors = config.get(

View File

@@ -33,7 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the available OctoPrint binary sensors."""
octoprint_api = hass.data[DOMAIN]["api"]

View File

@@ -44,7 +44,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Pilight Binary Sensor."""
disarm = config.get(CONF_DISARM_AFTER_TRIGGER)

View File

@@ -0,0 +1,103 @@
"""
This platform provides binary sensors for key RainMachine data.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rainmachine/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rainmachine import (
BINARY_SENSORS, DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, TYPE_FREEZE,
TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH,
TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity)
from homeassistant.const import CONF_MONITORED_CONDITIONS
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
DEPENDENCIES = ['rainmachine']
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up the RainMachine Switch platform."""
if discovery_info is None:
return
rainmachine = hass.data[DATA_RAINMACHINE]
binary_sensors = []
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
name, icon = BINARY_SENSORS[sensor_type]
binary_sensors.append(
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
async_add_devices(binary_sensors, True)
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
"""A sensor implementation for raincloud device."""
def __init__(self, rainmachine, sensor_type, name, icon):
"""Initialize the sensor."""
super().__init__(rainmachine)
self._icon = icon
self._name = name
self._sensor_type = sensor_type
self._state = None
@property
def icon(self) -> str:
"""Return the icon."""
return self._icon
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
@property
def should_poll(self):
"""Disable polling."""
return False
@property
def unique_id(self) -> str:
"""Return a unique, HASS-friendly identifier for this entity."""
return '{0}_{1}'.format(
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
@callback
def _update_data(self):
"""Update the state."""
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
async def async_update(self):
"""Update the state."""
if self._sensor_type == TYPE_FREEZE:
self._state = self.rainmachine.restrictions['current']['freeze']
elif self._sensor_type == TYPE_FREEZE_PROTECTION:
self._state = self.rainmachine.restrictions['global'][
'freezeProtectEnabled']
elif self._sensor_type == TYPE_HOT_DAYS:
self._state = self.rainmachine.restrictions['global'][
'hotDaysExtraWatering']
elif self._sensor_type == TYPE_HOURLY:
self._state = self.rainmachine.restrictions['current']['hourly']
elif self._sensor_type == TYPE_MONTH:
self._state = self.rainmachine.restrictions['current']['month']
elif self._sensor_type == TYPE_RAINDELAY:
self._state = self.rainmachine.restrictions['current']['rainDelay']
elif self._sensor_type == TYPE_RAINSENSOR:
self._state = self.rainmachine.restrictions['current'][
'rainSensor']
elif self._sensor_type == TYPE_WEEKDAY:
self._state = self.rainmachine.restrictions['current']['weekDay']

View File

@@ -4,7 +4,6 @@ Support for showing random states.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.random/
"""
import asyncio
import logging
import voluptuous as vol
@@ -24,8 +23,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 the Random binary sensor."""
name = config.get(CONF_NAME)
device_class = config.get(CONF_DEVICE_CLASS)
@@ -57,8 +56,7 @@ class RandomSensor(BinarySensorDevice):
"""Return the sensor class of the sensor."""
return self._device_class
@asyncio.coroutine
def async_update(self):
async def async_update(self):
"""Get new state and update the sensor's state."""
from random import getrandbits
self._state = bool(getrandbits(1))

View File

@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the raspihats binary_sensor devices."""
I2CHatBinarySensor.I2C_HATS_MANAGER = hass.data[I2C_HATS_MANAGER]

View File

@@ -10,7 +10,7 @@ import voluptuous as vol
from homeassistant.components import rfxtrx
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA, BinarySensorDevice)
DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.components.rfxtrx import (
ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES,
CONF_FIRE_EVENT, CONF_OFF_DELAY)
@@ -29,8 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS):
DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_OFF_DELAY):
vol.Any(cv.time_period, cv.positive_timedelta),

View File

@@ -39,7 +39,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Raspberry PI GPIO devices."""
pull_mode = config.get(CONF_PULL_MODE)

View File

@@ -94,4 +94,4 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorDevice):
self._state = bool(event and event.get('id') != self._event.get('id'))
self._event = event
self._event = event or {}

View File

@@ -14,7 +14,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.const import CONF_NAME
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['tapsaff==0.1.3']
REQUIREMENTS = ['tapsaff==0.2.0']
_LOGGER = logging.getLogger(__name__)

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.2']
REQUIREMENTS = ['numpy==1.14.3']
_LOGGER = logging.getLogger(__name__)
@@ -57,7 +57,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the trend sensors."""
sensors = []

View File

@@ -0,0 +1,92 @@
"""
A platform that to monitor Uptime Robot monitors.
For more details about this platform, please refer to the documentation at
https://www.home-assistant.io/components/binary_sensor.uptimerobot/
"""
import logging
import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyuptimerobot==0.0.5']
_LOGGER = logging.getLogger(__name__)
ATTR_TARGET = 'target'
CONF_ATTRIBUTION = "Data provided by Uptime Robot"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Uptime Robot binary_sensors."""
from pyuptimerobot import UptimeRobot
up_robot = UptimeRobot()
api_key = config.get(CONF_API_KEY)
monitors = up_robot.getMonitors(api_key)
devices = []
if not monitors or monitors.get('stat') != 'ok':
_LOGGER.error("Error connecting to Uptime Robot")
return
for monitor in monitors['monitors']:
devices.append(UptimeRobotBinarySensor(
api_key, up_robot, monitor['id'], monitor['friendly_name'],
monitor['url']))
add_devices(devices, True)
class UptimeRobotBinarySensor(BinarySensorDevice):
"""Representation of a Uptime Robot binary sensor."""
def __init__(self, api_key, up_robot, monitor_id, name, target):
"""Initialize Uptime Robot the binary sensor."""
self._api_key = api_key
self._monitor_id = str(monitor_id)
self._name = name
self._target = target
self._up_robot = up_robot
self._state = None
@property
def name(self):
"""Return the name of the binary sensor."""
return self._name
@property
def is_on(self):
"""Return the state of the binary sensor."""
return self._state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'connectivity'
@property
def device_state_attributes(self):
"""Return the state attributes of the binary sensor."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_TARGET: self._target,
}
def update(self):
"""Get the latest state of the binary sensor."""
monitor = self._up_robot.getMonitors(self._api_key, self._monitor_id)
if not monitor or monitor.get('stat') != 'ok':
_LOGGER.warning("Failed to get new state")
return
status = monitor['monitors'][0]['status']
self._state = 1 if status == 2 else 0

View File

@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Vera controller devices."""
add_devices(
VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['binary_sensor'])
[VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]['binary_sensor']], True)
class VeraBinarySensor(VeraDevice, BinarySensorDevice):

View File

@@ -54,6 +54,7 @@ class VerisureDoorWindowSensor(BinarySensorDevice):
"$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]",
self._device_label) is not None
# pylint: disable=no-self-use
def update(self):
"""Update the state of the sensor."""
hub.update_overview()

View File

@@ -7,14 +7,13 @@ https://home-assistant.io/components/binary_sensor.wemo/
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.loader import get_component
DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument, too-many-function-args
# pylint: disable=too-many-function-args
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Register discovered WeMo binary sensors."""
import pywemo.discovery as discovery
@@ -25,18 +24,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
device = discovery.device_from_description(location, mac)
if device:
add_devices_callback([WemoBinarySensor(device)])
add_devices_callback([WemoBinarySensor(hass, device)])
class WemoBinarySensor(BinarySensorDevice):
"""Representation a WeMo binary sensor."""
def __init__(self, device):
def __init__(self, hass, device):
"""Initialize the WeMo sensor."""
self.wemo = device
self._state = None
wemo = get_component('wemo')
wemo = hass.components.wemo
wemo.SUBSCRIPTION_REGISTRY.register(self.wemo)
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)

View File

@@ -0,0 +1,214 @@
"""
Binary sensor support for Wireless Sensor Tags.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.wirelesstag/
"""
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.components.wirelesstag import (
DOMAIN as WIRELESSTAG_DOMAIN,
WIRELESSTAG_TYPE_13BIT, WIRELESSTAG_TYPE_WATER,
WIRELESSTAG_TYPE_ALSPRO,
WIRELESSTAG_TYPE_WEMO_DEVICE,
SIGNAL_BINARY_EVENT_UPDATE,
WirelessTagBaseSensor)
from homeassistant.const import (
CONF_MONITORED_CONDITIONS, STATE_ON, STATE_OFF)
import homeassistant.helpers.config_validation as cv
DEPENDENCIES = ['wirelesstag']
_LOGGER = logging.getLogger(__name__)
# On means in range, Off means out of range
SENSOR_PRESENCE = 'presence'
# On means motion detected, Off means cear
SENSOR_MOTION = 'motion'
# On means open, Off means closed
SENSOR_DOOR = 'door'
# On means temperature become too cold, Off means normal
SENSOR_COLD = 'cold'
# On means hot, Off means normal
SENSOR_HEAT = 'heat'
# On means too dry (humidity), Off means normal
SENSOR_DRY = 'dry'
# On means too wet (humidity), Off means normal
SENSOR_WET = 'wet'
# On means light detected, Off means no light
SENSOR_LIGHT = 'light'
# On means moisture detected (wet), Off means no moisture (dry)
SENSOR_MOISTURE = 'moisture'
# On means tag battery is low, Off means normal
SENSOR_BATTERY = 'low_battery'
# Sensor types: Name, device_class, push notification type representing 'on',
# attr to check
SENSOR_TYPES = {
SENSOR_PRESENCE: ['Presence', 'presence', 'is_in_range', {
"on": "oor",
"off": "back_in_range"
}, 2],
SENSOR_MOTION: ['Motion', 'motion', 'is_moved', {
"on": "motion_detected",
}, 5],
SENSOR_DOOR: ['Door', 'door', 'is_door_open', {
"on": "door_opened",
"off": "door_closed"
}, 5],
SENSOR_COLD: ['Cold', 'cold', 'is_cold', {
"on": "temp_toolow",
"off": "temp_normal"
}, 4],
SENSOR_HEAT: ['Heat', 'heat', 'is_heat', {
"on": "temp_toohigh",
"off": "temp_normal"
}, 4],
SENSOR_DRY: ['Too dry', 'dry', 'is_too_dry', {
"on": "too_dry",
"off": "cap_normal"
}, 2],
SENSOR_WET: ['Too wet', 'wet', 'is_too_humid', {
"on": "too_humid",
"off": "cap_normal"
}, 2],
SENSOR_LIGHT: ['Light', 'light', 'is_light_on', {
"on": "too_bright",
"off": "light_normal"
}, 1],
SENSOR_MOISTURE: ['Leak', 'moisture', 'is_leaking', {
"on": "water_detected",
"off": "water_dried",
}, 1],
SENSOR_BATTERY: ['Low Battery', 'battery', 'is_battery_low', {
"on": "low_battery"
}, 3]
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the platform for a WirelessTags."""
platform = hass.data.get(WIRELESSTAG_DOMAIN)
sensors = []
tags = platform.tags
for tag in tags.values():
allowed_sensor_types = WirelessTagBinarySensor.allowed_sensors(tag)
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
if sensor_type in allowed_sensor_types:
sensors.append(WirelessTagBinarySensor(platform, tag,
sensor_type))
add_devices(sensors, True)
hass.add_job(platform.install_push_notifications, sensors)
class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice):
"""A binary sensor implementation for WirelessTags."""
@classmethod
def allowed_sensors(cls, tag):
"""Return list of allowed sensor types for specific tag type."""
sensors_map = {
# 13-bit tag - allows everything but not light and moisture
WIRELESSTAG_TYPE_13BIT: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_MOTION, SENSOR_DOOR,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_DRY, SENSOR_WET],
# Moister/water sensor - temperature and moisture only
WIRELESSTAG_TYPE_WATER: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_MOISTURE],
# ALS Pro: allows everything, but not moisture
WIRELESSTAG_TYPE_ALSPRO: [
SENSOR_PRESENCE, SENSOR_BATTERY,
SENSOR_MOTION, SENSOR_DOOR,
SENSOR_COLD, SENSOR_HEAT,
SENSOR_DRY, SENSOR_WET,
SENSOR_LIGHT],
# Wemo are power switches.
WIRELESSTAG_TYPE_WEMO_DEVICE: [SENSOR_PRESENCE]
}
# allow everything if tag type is unknown
# (i just dont have full catalog of them :))
tag_type = tag.tag_type
fullset = SENSOR_TYPES.keys()
return sensors_map[tag_type] if tag_type in sensors_map else fullset
def __init__(self, api, tag, sensor_type):
"""Initialize a binary sensor for a Wireless Sensor Tags."""
super().__init__(api, tag)
self._sensor_type = sensor_type
self._name = '{0} {1}'.format(self._tag.name,
SENSOR_TYPES[self._sensor_type][0])
self._device_class = SENSOR_TYPES[self._sensor_type][1]
self._tag_attr = SENSOR_TYPES[self._sensor_type][2]
self.binary_spec = SENSOR_TYPES[self._sensor_type][3]
self.tag_id_index_template = SENSOR_TYPES[self._sensor_type][4]
async def async_added_to_hass(self):
"""Register callbacks."""
tag_id = self.tag_id
event_type = self.device_class
async_dispatcher_connect(
self.hass,
SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type),
self._on_binary_event_callback)
@property
def is_on(self):
"""Return True if the binary sensor is on."""
return self._state == STATE_ON
@property
def device_class(self):
"""Return the class of the binary sensor."""
return self._device_class
@property
def principal_value(self):
"""Return value of tag.
Subclasses need override based on type of sensor.
"""
return (
STATE_ON if getattr(self._tag, self._tag_attr, False)
else STATE_OFF)
def updated_state_value(self):
"""Use raw princial value."""
return self.principal_value
@callback
def _on_binary_event_callback(self, event):
"""Update state from arrive push notification."""
# state should be 'on' or 'off'
self._state = event.data.get('state')
self.async_schedule_update_ha_state()

View File

@@ -17,16 +17,17 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['holidays==0.9.4']
REQUIREMENTS = ['holidays==0.9.5']
# List of all countries currently supported by holidays
# There seems to be no way to get the list out at runtime
ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Belgium', 'BE', 'Canada',
'CA', 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK',
'England', 'EuropeanCentralBank', 'ECB', 'TAR', 'Finland',
'FI', 'France', 'FRA', 'Germany', 'DE', 'Ireland',
'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX',
'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland',
ALL_COUNTRIES = ['Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT',
'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech',
'CZ', 'Denmark', 'DK', 'England', 'EuropeanCentralBank',
'ECB', 'TAR', 'Finland', 'FI', 'France', 'FRA', 'Germany',
'DE', 'Hungary', 'HU', 'Ireland', 'Isle of Man', 'Italy',
'IT', 'Japan', 'JP', 'Mexico', 'MX', 'Netherlands', 'NL',
'NewZealand', 'NZ', 'Northern Ireland',
'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI',
'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES',

View File

@@ -25,30 +25,39 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items():
for device in gateway.devices['binary_sensor']:
model = device['model']
if model in ['motion', 'sensor_motion.aq2']:
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model in ['magnet', 'sensor_magnet.aq2']:
devices.append(XiaomiDoorSensor(device, gateway))
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
data_key = 'window_status'
devices.append(XiaomiDoorSensor(device, data_key, gateway))
elif model == 'sensor_wleak.aq1':
devices.append(XiaomiWaterLeakSensor(device, gateway))
elif model == 'smoke':
elif model in ['smoke', 'sensor_smoke']:
devices.append(XiaomiSmokeSensor(device, gateway))
elif model == 'natgas':
elif model in ['natgas', 'sensor_natgas']:
devices.append(XiaomiNatgasSensor(device, gateway))
elif model in ['switch', 'sensor_switch.aq2', 'sensor_switch.aq3']:
devices.append(XiaomiButton(device, 'Switch', 'status',
elif model in ['switch', 'sensor_switch',
'sensor_switch.aq2', 'sensor_switch.aq3']:
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
data_key = 'channel_0'
devices.append(XiaomiButton(device, 'Switch', data_key,
hass, gateway))
elif model == '86sw1':
elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
hass, gateway))
elif model == '86sw2':
elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch (Left)',
'channel_0', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Right)',
'channel_1', hass, gateway))
devices.append(XiaomiButton(device, 'Wall Switch (Both)',
'dual_channel', hass, gateway))
elif model == 'cube':
elif model in ['cube', 'sensor_cube', 'sensor_cube.aqgl01']:
devices.append(XiaomiCube(device, hass, gateway))
add_devices(devices)
@@ -129,8 +138,12 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
"""Initialize the XiaomiMotionSensor."""
self._hass = hass
self._no_motion_since = 0
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
data_key = 'motion_status'
XiaomiBinarySensor.__init__(self, device, 'Motion Sensor', xiaomi_hub,
'status', 'motion')
data_key, 'motion')
@property
def device_state_attributes(self):
@@ -181,11 +194,11 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
class XiaomiDoorSensor(XiaomiBinarySensor):
"""Representation of a XiaomiDoorSensor."""
def __init__(self, device, xiaomi_hub):
def __init__(self, device, data_key, xiaomi_hub):
"""Initialize the XiaomiDoorSensor."""
self._open_since = 0
XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor',
xiaomi_hub, 'status', 'opening')
xiaomi_hub, data_key, 'opening')
@property
def device_state_attributes(self):
@@ -321,6 +334,8 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'both'
elif value == 'shake':
click_type = 'shake'
elif value in ['long_click', 'long_both_click']:
return False
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)
return False

View File

@@ -31,12 +31,21 @@ async def async_setup_platform(hass, config, async_add_devices,
if discovery_info is None:
return
from zigpy.zcl.clusters.general import OnOff
from zigpy.zcl.clusters.security import IasZone
if IasZone.cluster_id in discovery_info['in_clusters']:
await _async_setup_iaszone(hass, config, async_add_devices,
discovery_info)
elif OnOff.cluster_id in discovery_info['out_clusters']:
await _async_setup_remote(hass, config, async_add_devices,
discovery_info)
in_clusters = discovery_info['in_clusters']
async def _async_setup_iaszone(hass, config, async_add_devices,
discovery_info):
device_class = None
cluster = in_clusters[IasZone.cluster_id]
from zigpy.zcl.clusters.security import IasZone
cluster = discovery_info['in_clusters'][IasZone.cluster_id]
if discovery_info['new_join']:
await cluster.bind()
ieee = cluster.endpoint.device.application.ieee
@@ -53,8 +62,34 @@ async def async_setup_platform(hass, config, async_add_devices,
async_add_devices([sensor], update_before_add=True)
async def _async_setup_remote(hass, config, async_add_devices, discovery_info):
async def safe(coro):
"""Run coro, catching ZigBee delivery errors, and ignoring them."""
import zigpy.exceptions
try:
await coro
except zigpy.exceptions.DeliveryError as exc:
_LOGGER.warning("Ignoring error during setup: %s", exc)
if discovery_info['new_join']:
from zigpy.zcl.clusters.general import OnOff, LevelControl
out_clusters = discovery_info['out_clusters']
if OnOff.cluster_id in out_clusters:
cluster = out_clusters[OnOff.cluster_id]
await safe(cluster.bind())
await safe(cluster.configure_reporting(0, 0, 600, 1))
if LevelControl.cluster_id in out_clusters:
cluster = out_clusters[LevelControl.cluster_id]
await safe(cluster.bind())
await safe(cluster.configure_reporting(0, 1, 600, 1))
sensor = Switch(**discovery_info)
async_add_devices([sensor], update_before_add=True)
class BinarySensor(zha.Entity, BinarySensorDevice):
"""THe ZHA Binary Sensor."""
"""The ZHA Binary Sensor."""
_domain = DOMAIN
@@ -73,7 +108,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
@property
def is_on(self) -> bool:
"""Return True if entity is on."""
if self._state == 'unknown':
if self._state is None:
return False
return bool(self._state)
@@ -98,7 +133,126 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
from bellows.types.basic import uint16_t
result = await zha.safe_read(self._endpoint.ias_zone,
['zone_status'])
['zone_status'],
allow_cache=False)
state = result.get('zone_status', self._state)
if isinstance(state, (int, uint16_t)):
self._state = result.get('zone_status', self._state) & 3
class Switch(zha.Entity, BinarySensorDevice):
"""ZHA switch/remote controller/button."""
_domain = DOMAIN
class OnOffListener:
"""Listener for the OnOff ZigBee cluster."""
def __init__(self, entity):
"""Initialize OnOffListener."""
self._entity = entity
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id in (0x0000, 0x0040):
self._entity.set_state(False)
elif command_id in (0x0001, 0x0041, 0x0042):
self._entity.set_state(True)
elif command_id == 0x0002:
self._entity.set_state(not self._entity.is_on)
def attribute_updated(self, attrid, value):
"""Handle attribute updates on this cluster."""
if attrid == 0:
self._entity.set_state(value)
def zdo_command(self, *args, **kwargs):
"""Handle ZDO commands on this cluster."""
pass
class LevelListener:
"""Listener for the LevelControl ZigBee cluster."""
def __init__(self, entity):
"""Initialize LevelListener."""
self._entity = entity
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id in (0x0000, 0x0004): # move_to_level, -with_on_off
self._entity.set_level(args[0])
elif command_id in (0x0001, 0x0005): # move, -with_on_off
# We should dim slowly -- for now, just step once
rate = args[1]
if args[0] == 0xff:
rate = 10 # Should read default move rate
self._entity.move_level(-rate if args[0] else rate)
elif command_id in (0x0002, 0x0006): # step, -with_on_off
# Step (technically may change on/off)
self._entity.move_level(-args[1] if args[0] else args[1])
def attribute_update(self, attrid, value):
"""Handle attribute updates on this cluster."""
if attrid == 0:
self._entity.set_level(value)
def zdo_command(self, *args, **kwargs):
"""Handle ZDO commands on this cluster."""
pass
def __init__(self, **kwargs):
"""Initialize Switch."""
super().__init__(**kwargs)
self._state = False
self._level = 0
from zigpy.zcl.clusters import general
self._out_listeners = {
general.OnOff.cluster_id: self.OnOffListener(self),
general.LevelControl.cluster_id: self.LevelListener(self),
}
@property
def should_poll(self) -> bool:
"""Let zha handle polling."""
return False
@property
def is_on(self) -> bool:
"""Return true if the binary sensor is on."""
return self._state
@property
def device_state_attributes(self):
"""Return the device state attributes."""
self._device_state_attributes.update({
'level': self._state and self._level or 0
})
return self._device_state_attributes
def move_level(self, change):
"""Increment the level, setting state if appropriate."""
if not self._state and change > 0:
self._level = 0
self._level = min(255, max(0, self._level + change))
self._state = bool(self._level)
self.async_schedule_update_ha_state()
def set_level(self, level):
"""Set the level, setting state if appropriate."""
self._level = level
self._state = bool(self._level)
self.async_schedule_update_ha_state()
def set_state(self, state):
"""Set the state."""
self._state = state
if self._level == 0:
self._level = 255
self.async_schedule_update_ha_state()
async def async_update(self):
"""Retrieve latest state."""
from zigpy.zcl.clusters.general import OnOff
result = await zha.safe_read(
self._endpoint.out_clusters[OnOff.cluster_id], ['on_off'])
self._state = result.get('on_off', self._state)

View File

@@ -34,7 +34,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
# pylint: disable=unused-argument
def setup(hass, config):
"""Set up the BloomSky component."""
api_key = config[DOMAIN][CONF_API_KEY]

View File

@@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bimmer_connected==0.5.0']
REQUIREMENTS = ['bimmer_connected==0.5.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -27,7 +27,7 @@ activate_air_conditioning:
description: >
Start the air conditioning of the vehicle. What exactly is started here
depends on the type of vehicle. It might range from just ventilation over
auxilary heating to real air conditioning. The vehicle is identified via
auxiliary heating to real air conditioning. The vehicle is identified via
the vin (see below).
fields:
vin:
@@ -39,4 +39,4 @@ update_state:
description: >
Fetch the last state of the vehicles of all your accounts from the BMW
server. This does *not* trigger an update from the vehicle, it just gets
the data from the BMW servers. This service does not require any attributes.
the data from the BMW servers. This service does not require any attributes.

View File

@@ -4,11 +4,12 @@ Support for Google Calendar event device sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/calendar/
"""
import asyncio
import logging
from datetime import timedelta
import re
from aiohttp import web
from homeassistant.components.google import (
CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME)
from homeassistant.const import STATE_OFF, STATE_ON
@@ -18,23 +19,32 @@ from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.template import DATE_STR_FORMAT
from homeassistant.util import dt
from homeassistant.components import http
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'calendar'
DEPENDENCIES = ['http']
ENTITY_ID_FORMAT = DOMAIN + '.{}'
SCAN_INTERVAL = timedelta(seconds=60)
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Track states and offer events for calendars."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
yield from component.async_setup(config)
hass.http.register_view(CalendarListView(component))
hass.http.register_view(CalendarEventView(component))
await hass.components.frontend.async_register_built_in_panel(
'calendar', 'calendar', 'hass:calendar')
await component.async_setup(config)
return True
@@ -42,7 +52,14 @@ DEFAULT_CONF_TRACK_NEW = True
DEFAULT_CONF_OFFSET = '!!'
# pylint: disable=too-many-instance-attributes
def get_date(date):
"""Get the dateTime from date or dateTime as a local."""
if 'date' in date:
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
return dt.as_local(dt.parse_datetime(date['dateTime']))
class CalendarEventDevice(Entity):
"""A calendar event device."""
@@ -50,7 +67,6 @@ class CalendarEventDevice(Entity):
# with an update() method
data = None
# pylint: disable=too-many-arguments
def __init__(self, hass, data):
"""Create the Calendar Event Device."""
self._name = data.get(CONF_NAME)
@@ -144,15 +160,8 @@ class CalendarEventDevice(Entity):
self.cleanup()
return
def _get_date(date):
"""Get the dateTime from date or dateTime as a local."""
if 'date' in date:
return dt.start_of_local_day(dt.dt.datetime.combine(
dt.parse_date(date['date']), dt.dt.time.min))
return dt.as_local(dt.parse_datetime(date['dateTime']))
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
start = get_date(self.data.event['start'])
end = get_date(self.data.event['end'])
summary = self.data.event.get('summary', '')
@@ -176,10 +185,61 @@ class CalendarEventDevice(Entity):
# cleanup the string so we don't have a bunch of double+ spaces
self._cal_data['message'] = re.sub(' +', '', summary).strip()
self._cal_data['offset_time'] = offset_time
self._cal_data['location'] = self.data.event.get('location', '')
self._cal_data['description'] = self.data.event.get('description', '')
self._cal_data['start'] = start
self._cal_data['end'] = end
self._cal_data['all_day'] = 'date' in self.data.event['start']
class CalendarEventView(http.HomeAssistantView):
"""View to retrieve calendar content."""
url = '/api/calendars/{entity_id}'
name = 'api:calendars:calendar'
def __init__(self, component):
"""Initialize calendar view."""
self.component = component
async def get(self, request, entity_id):
"""Return calendar events."""
entity = self.component.get_entity(entity_id)
start = request.query.get('start')
end = request.query.get('end')
if None in (start, end, entity):
return web.Response(status=400)
try:
start_date = dt.parse_datetime(start)
end_date = dt.parse_datetime(end)
except (ValueError, AttributeError):
return web.Response(status=400)
event_list = await entity.async_get_events(
request.app['hass'], start_date, end_date)
return self.json(event_list)
class CalendarListView(http.HomeAssistantView):
"""View to retrieve calendar list."""
url = '/api/calendars'
name = "api:calendars"
def __init__(self, component):
"""Initialize calendar view."""
self.component = component
async def get(self, request):
"""Retrieve calendar list."""
get_state = request.app['hass'].states.get
calendar_list = []
for entity in self.component.entities:
state = get_state(entity.entity_id)
calendar_list.append({
"name": state.name,
"entity_id": entity.entity_id,
})
return self.json(sorted(calendar_list, key=lambda x: x['name']))

View File

@@ -11,7 +11,7 @@ import re
import voluptuous as vol
from homeassistant.components.calendar import (
PLATFORM_SCHEMA, CalendarEventDevice)
PLATFORM_SCHEMA, CalendarEventDevice, get_date)
from homeassistant.const import (
CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
@@ -92,7 +92,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
if not config.get(CONF_CUSTOM_CALENDARS):
device_data = {
CONF_NAME: calendar.name,
CONF_DEVICE_ID: calendar.name
CONF_DEVICE_ID: calendar.name,
}
calendar_devices.append(
WebDavCalendarEventDevice(hass, device_data, calendar)
@@ -120,6 +120,10 @@ class WebDavCalendarEventDevice(CalendarEventDevice):
attributes = super().device_state_attributes
return attributes
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
class WebDavCalendarData(object):
"""Class to utilize the calendar dav client object to get next event."""
@@ -131,6 +135,33 @@ class WebDavCalendarData(object):
self.search = search
self.event = None
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
# Get event list from the current calendar
vevent_list = await hass.async_add_job(self.calendar.date_search,
start_date, end_date)
event_list = []
for event in vevent_list:
vevent = event.instance.vevent
uid = None
if hasattr(vevent, 'uid'):
uid = vevent.uid.value
data = {
"uid": uid,
"title": vevent.summary.value,
"start": self.get_hass_date(vevent.dtstart.value),
"end": self.get_hass_date(self.get_end_date(vevent)),
"location": self.get_attr_value(vevent, "location"),
"description": self.get_attr_value(vevent, "description"),
}
data['start'] = get_date(data['start']).isoformat()
data['end'] = get_date(data['end']).isoformat()
event_list.append(data)
return event_list
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""

View File

@@ -4,8 +4,10 @@ Demo platform that has two fake binary sensors.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import copy
import homeassistant.util.dt as dt_util
from homeassistant.components.calendar import CalendarEventDevice
from homeassistant.components.calendar import CalendarEventDevice, get_date
from homeassistant.components.google import CONF_DEVICE_ID, CONF_NAME
@@ -15,13 +17,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
calendar_data_current = DemoGoogleCalendarDataCurrent()
add_devices([
DemoGoogleCalendar(hass, calendar_data_future, {
CONF_NAME: 'Future Event',
CONF_DEVICE_ID: 'future_event',
CONF_NAME: 'Calendar 1',
CONF_DEVICE_ID: 'calendar_1',
}),
DemoGoogleCalendar(hass, calendar_data_current, {
CONF_NAME: 'Current Event',
CONF_DEVICE_ID: 'current_event',
CONF_NAME: 'Calendar 2',
CONF_DEVICE_ID: 'calendar_2',
}),
])
@@ -29,11 +31,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DemoGoogleCalendarData(object):
"""Representation of a Demo Calendar element."""
event = {}
# pylint: disable=no-self-use
def update(self):
"""Return true so entity knows we have new data."""
return True
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
event = copy.copy(self.event)
event['title'] = event['summary']
event['start'] = get_date(event['start']).isoformat()
event['end'] = get_date(event['end']).isoformat()
return [event]
class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData):
"""Representation of a Demo Calendar for a future event."""
@@ -80,3 +92,7 @@ class DemoGoogleCalendar(CalendarEventDevice):
"""Initialize Google Calendar but without the API calls."""
self.data = calendar_data
super().__init__(hass, data)
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)

View File

@@ -51,6 +51,10 @@ class GoogleCalendarEventDevice(CalendarEventDevice):
super().__init__(hass, data)
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
class GoogleCalendarData(object):
"""Class to utilize calendar service object to get next event."""
@@ -64,9 +68,7 @@ class GoogleCalendarData(object):
self.ignore_availability = ignore_availability
self.event = None
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
def _prepare_query(self):
from httplib2 import ServerNotFoundError
try:
@@ -74,13 +76,41 @@ class GoogleCalendarData(object):
except ServerNotFoundError:
_LOGGER.warning("Unable to connect to Google, using cached data")
return False
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
params['timeMin'] = dt.now().isoformat('T')
params['calendarId'] = self.calendar_id
if self.search:
params['q'] = self.search
return service, params
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
service, params = await hass.async_add_job(self._prepare_query)
params['timeMin'] = start_date.isoformat('T')
params['timeMax'] = end_date.isoformat('T')
# pylint: disable=no-member
events = await hass.async_add_job(service.events)
# pylint: enable=no-member
result = await hass.async_add_job(events.list(**params).execute)
items = result.get('items', [])
event_list = []
for item in items:
if (not self.ignore_availability
and 'transparency' in item.keys()):
if item['transparency'] == 'opaque':
event_list.append(item)
else:
event_list.append(item)
return event_list
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""
service, params = self._prepare_query()
params['timeMin'] = dt.now().isoformat('T')
events = service.events() # pylint: disable=no-member
result = events.list(**params).execute()

View File

@@ -257,6 +257,10 @@ class TodoistProjectDevice(CalendarEventDevice):
super().cleanup()
self._cal_data[ALL_TASKS] = []
async def async_get_events(self, hass, start_date, end_date):
"""Get all events in a specific time frame."""
return await self.data.async_get_events(hass, start_date, end_date)
@property
def device_state_attributes(self):
"""Return the device state attributes."""
@@ -485,6 +489,31 @@ class TodoistProjectData(object):
continue
return event
async def async_get_events(self, hass, start_date, end_date):
"""Get all tasks in a specific time frame."""
if self._id is None:
project_task_data = [
task for task in self._api.state[TASKS]
if not self._project_id_whitelist or
task[PROJECT_ID] in self._project_id_whitelist]
else:
project_task_data = self._api.projects.get_data(self._id)[TASKS]
events = []
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:
event = {
'uid': task['id'],
'title': task['content'],
'start': due_date.isoformat(),
'end': due_date.isoformat(),
'allDay': True,
}
events.append(event)
return events
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data."""

View File

@@ -1,4 +1,3 @@
# pylint: disable=too-many-lines
"""
Component to interface with cameras.
@@ -6,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/camera/
"""
import asyncio
import base64
import collections
from contextlib import suppress
from datetime import timedelta
@@ -13,20 +13,20 @@ import logging
import hashlib
from random import SystemRandom
import aiohttp
import attr
from aiohttp import web
import async_timeout
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.exceptions import HomeAssistantError
from homeassistant.loader import bind_hass
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED
from homeassistant.components import websocket_api
import homeassistant.helpers.config_validation as cv
DOMAIN = 'camera'
@@ -53,6 +53,9 @@ ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}'
TOKEN_CHANGE_INTERVAL = timedelta(minutes=5)
_RND = SystemRandom()
FALLBACK_STREAM_INTERVAL = 1 # seconds
MIN_STREAM_INTERVAL = 0.5 # seconds
CAMERA_SERVICE_SCHEMA = vol.Schema({
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
})
@@ -61,6 +64,20 @@ CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({
vol.Required(ATTR_FILENAME): cv.template
})
WS_TYPE_CAMERA_THUMBNAIL = 'camera_thumbnail'
SCHEMA_WS_CAMERA_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
'type': WS_TYPE_CAMERA_THUMBNAIL,
'entity_id': cv.entity_id
})
@attr.s
class Image:
"""Represent an image."""
content_type = attr.ib(type=str)
content = attr.ib(type=bytes)
@bind_hass
def enable_motion_detection(hass, entity_id=None):
@@ -79,6 +96,7 @@ def disable_motion_detection(hass, entity_id=None):
@bind_hass
@callback
def async_snapshot(hass, filename, entity_id=None):
"""Make a snapshot from a camera."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
@@ -89,45 +107,41 @@ def async_snapshot(hass, filename, entity_id=None):
@bind_hass
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
async def async_get_image(hass, entity_id, timeout=10):
"""Fetch an image from a camera entity."""
websession = async_get_clientsession(hass)
state = hass.states.get(entity_id)
component = hass.data.get(DOMAIN)
if state is None:
raise HomeAssistantError(
"No entity '{0}' for grab an image".format(entity_id))
if component is None:
raise HomeAssistantError('Camera component not setup')
url = "{0}{1}".format(
hass.config.api.base_url,
state.attributes.get(ATTR_ENTITY_PICTURE)
)
camera = component.get_entity(entity_id)
try:
if camera is None:
raise HomeAssistantError('Camera not found')
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(timeout, loop=hass.loop):
response = yield from websession.get(url)
image = await camera.async_camera_image()
if response.status != 200:
raise HomeAssistantError("Error {0} on {1}".format(
response.status, url))
if image:
return Image(camera.content_type, image)
image = yield from response.read()
return image
except (asyncio.TimeoutError, aiohttp.ClientError):
raise HomeAssistantError("Can't connect to {0}".format(url))
raise HomeAssistantError('Unable to get image')
@asyncio.coroutine
def async_setup(hass, config):
async def async_setup(hass, config):
"""Set up the camera component."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
hass.http.register_view(CameraImageView(component))
hass.http.register_view(CameraMjpegStream(component))
hass.components.websocket_api.async_register_command(
WS_TYPE_CAMERA_THUMBNAIL, websocket_camera_thumbnail,
SCHEMA_WS_CAMERA_THUMBNAIL
)
yield from component.async_setup(config)
await component.async_setup(config)
@callback
def update_tokens(time):
@@ -139,27 +153,25 @@ def async_setup(hass, config):
hass.helpers.event.async_track_time_interval(
update_tokens, TOKEN_CHANGE_INTERVAL)
@asyncio.coroutine
def async_handle_camera_service(service):
async def async_handle_camera_service(service):
"""Handle calls to the camera services."""
target_cameras = component.async_extract_from_service(service)
update_tasks = []
for camera in target_cameras:
if service.service == SERVICE_ENABLE_MOTION:
yield from camera.async_enable_motion_detection()
await camera.async_enable_motion_detection()
elif service.service == SERVICE_DISABLE_MOTION:
yield from camera.async_disable_motion_detection()
await camera.async_disable_motion_detection()
if not camera.should_poll:
continue
update_tasks.append(camera.async_update_ha_state(True))
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
await asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_handle_snapshot_service(service):
async def async_handle_snapshot_service(service):
"""Handle snapshot services calls."""
target_cameras = component.async_extract_from_service(service)
filename = service.data[ATTR_FILENAME]
@@ -175,7 +187,7 @@ def async_setup(hass, config):
"Can't write %s, no access to path!", snapshot_file)
continue
image = yield from camera.async_camera_image()
image = await camera.async_camera_image()
def _write_image(to_file, image_data):
"""Executor helper to write image."""
@@ -183,7 +195,7 @@ def async_setup(hass, config):
img_file.write(image_data)
try:
yield from hass.async_add_job(
await hass.async_add_job(
_write_image, snapshot_file, image)
except OSError as err:
_LOGGER.error("Can't write image to file: %s", err)
@@ -201,6 +213,16 @@ def async_setup(hass, config):
return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
async def async_unload_entry(hass, entry):
"""Unload a config entry."""
return await hass.data[DOMAIN].async_unload_entry(entry)
class Camera(Entity):
"""The base class for camera entities."""
@@ -241,10 +263,16 @@ class Camera(Entity):
"""Return the camera model."""
return None
@property
def frame_interval(self):
"""Return the interval between frames of the mjpeg stream."""
return 0.5
def camera_image(self):
"""Return bytes of camera image."""
raise NotImplementedError()
@callback
def async_camera_image(self):
"""Return bytes of camera image.
@@ -252,19 +280,17 @@ class Camera(Entity):
"""
return self.hass.async_add_job(self.camera_image)
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
async def handle_async_still_stream(self, request, interval):
"""Generate an HTTP MJPEG stream from camera images.
This method must be run in the event loop.
"""
response = web.StreamResponse()
response.content_type = ('multipart/x-mixed-replace; '
'boundary=--frameboundary')
yield from response.prepare(request)
await response.prepare(request)
async def write(img_bytes):
async def write_to_mjpeg_stream(img_bytes):
"""Write image to stream."""
await response.write(bytes(
'--frameboundary\r\n'
@@ -277,21 +303,21 @@ class Camera(Entity):
try:
while True:
img_bytes = yield from self.async_camera_image()
img_bytes = await self.async_camera_image()
if not img_bytes:
break
if img_bytes and img_bytes != last_image:
yield from write(img_bytes)
await write_to_mjpeg_stream(img_bytes)
# Chrome seems to always ignore first picture,
# print it twice.
if last_image is None:
yield from write(img_bytes)
await write_to_mjpeg_stream(img_bytes)
last_image = img_bytes
yield from asyncio.sleep(.5)
await asyncio.sleep(interval)
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
@@ -299,7 +325,16 @@ class Camera(Entity):
finally:
if response is not None:
yield from response.write_eof()
await response.write_eof()
async def handle_async_mjpeg_stream(self, request):
"""Serve an HTTP MJPEG stream from the camera.
This method can be overridden by camera plaforms to proxy
a direct stream from the camera.
This method must be run in the event loop.
"""
await self.handle_async_still_stream(request, self.frame_interval)
@property
def state(self):
@@ -329,20 +364,20 @@ class Camera(Entity):
@property
def state_attributes(self):
"""Return the camera state attributes."""
attr = {
attrs = {
'access_token': self.access_tokens[-1],
}
if self.model:
attr['model_name'] = self.model
attrs['model_name'] = self.model
if self.brand:
attr['brand'] = self.brand
attrs['brand'] = self.brand
if self.motion_detection_enabled:
attr['motion_detection'] = self.motion_detection_enabled
attrs['motion_detection'] = self.motion_detection_enabled
return attr
return attrs
@callback
def async_update_token(self):
@@ -361,8 +396,7 @@ class CameraView(HomeAssistantView):
"""Initialize a basic camera view."""
self.component = component
@asyncio.coroutine
def get(self, request, entity_id):
async def get(self, request, entity_id):
"""Start a GET request."""
camera = self.component.get_entity(entity_id)
@@ -376,11 +410,10 @@ class CameraView(HomeAssistantView):
if not authenticated:
return web.Response(status=401)
response = yield from self.handle(request, camera)
response = await self.handle(request, camera)
return response
@asyncio.coroutine
def handle(self, request, camera):
async def handle(self, request, camera):
"""Handle the camera request."""
raise NotImplementedError()
@@ -391,12 +424,11 @@ class CameraImageView(CameraView):
url = '/api/camera_proxy/{entity_id}'
name = 'api:camera:image'
@asyncio.coroutine
def handle(self, request, camera):
async def handle(self, request, camera):
"""Serve camera image."""
with suppress(asyncio.CancelledError, asyncio.TimeoutError):
with async_timeout.timeout(10, loop=request.app['hass'].loop):
image = yield from camera.async_camera_image()
image = await camera.async_camera_image()
if image:
return web.Response(body=image,
@@ -411,7 +443,43 @@ class CameraMjpegStream(CameraView):
url = '/api/camera_proxy_stream/{entity_id}'
name = 'api:camera:stream'
@asyncio.coroutine
def handle(self, request, camera):
"""Serve camera image."""
yield from camera.handle_async_mjpeg_stream(request)
async def handle(self, request, camera):
"""Serve camera stream, possibly with interval."""
interval = request.query.get('interval')
if interval is None:
await camera.handle_async_mjpeg_stream(request)
return
try:
# Compose camera stream from stills
interval = float(request.query.get('interval'))
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
except ValueError:
return web.Response(status=400)
@callback
def websocket_camera_thumbnail(hass, connection, msg):
"""Handle get camera thumbnail websocket command.
Async friendly.
"""
async def send_camera_still():
"""Send a camera still."""
try:
image = await async_get_image(hass, msg['entity_id'])
connection.send_message_outside(websocket_api.result_message(
msg['id'], {
'content_type': image.content_type,
'content': base64.b64encode(image.content).decode('utf-8')
}
))
except HomeAssistantError:
connection.send_message_outside(websocket_api.error_message(
msg['id'], 'image_fetch_failed', 'Unable to fetch image'))
hass.async_add_job(send_camera_still())

View File

@@ -4,23 +4,22 @@ Support for Netgear Arlo IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.arlo/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO
from homeassistant.components.arlo import (
DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=90)
ARLO_MODE_ARMED = 'armed'
ARLO_MODE_DISARMED = 'disarmed'
@@ -44,22 +43,19 @@ POWERSAVE_MODE_MAPPING = {
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS):
cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up an Arlo IP Camera."""
arlo = hass.data.get(DATA_ARLO)
if not arlo:
return False
arlo = hass.data[DATA_ARLO]
cameras = []
for camera in arlo.cameras:
cameras.append(ArloCam(hass, camera, config))
add_devices(cameras, True)
add_devices(cameras)
class ArloCam(Camera):
@@ -74,31 +70,41 @@ class ArloCam(Camera):
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
self._last_refresh = None
if self._camera.base_station:
self._camera.base_station.refresh_rate = \
SCAN_INTERVAL.total_seconds()
self.attrs = {}
def camera_image(self):
"""Return a still image response from the camera."""
return self._camera.last_image
return self._camera.last_image_from_cache
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_ARLO, self._update_callback)
@callback
def _update_callback(self):
"""Call update method."""
self.async_schedule_update_ha_state()
async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
video = self._camera.last_video
if not video:
error_msg = \
'Video not found for {0}. Is it older than {1} days?'.format(
self.name, self._camera.min_days_vdo_cache)
_LOGGER.error(error_msg)
return
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
yield from stream.open_camera(
await stream.open_camera(
video.video_url, extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream(
await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
await stream.close()
@property
def name(self):
@@ -132,11 +138,6 @@ class ArloCam(Camera):
"""Return the camera brand."""
return DEFAULT_BRAND
@property
def should_poll(self):
"""Camera should poll periodically."""
return True
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
@@ -164,7 +165,3 @@ class ArloCam(Camera):
"""Disable the motion detection in base station (Disarm)."""
self._motion_status = False
self.set_base_station_mode(ARLO_MODE_DISARMED)
def update(self):
"""Add an attribute-update task to the executor pool."""
self._camera.update()

View File

@@ -9,15 +9,13 @@ import logging
import requests
from homeassistant.components.camera import Camera
from homeassistant.loader import get_component
DEPENDENCIES = ['bloomsky']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up access to BloomSky cameras."""
bloomsky = get_component('bloomsky')
bloomsky = hass.components.bloomsky
for device in bloomsky.BLOOMSKY.devices.values():
add_devices([BloomSkyCamera(bloomsky.BLOOMSKY, device)])

View File

@@ -12,9 +12,10 @@ from homeassistant.components.camera import Camera
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the Demo camera platform."""
add_devices([
async_add_devices([
DemoCamera(hass, config, 'Demo camera')
])

View File

@@ -17,9 +17,9 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
DEPENDENCIES = ['doorbird']
_CAMERA_LAST_VISITOR = "DoorBird Last Ring"
_CAMERA_LAST_MOTION = "DoorBird Last Motion"
_CAMERA_LIVE = "DoorBird Live"
_CAMERA_LAST_VISITOR = "{} Last Ring"
_CAMERA_LAST_MOTION = "{} Last Motion"
_CAMERA_LIVE = "{} Live"
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
@@ -30,16 +30,22 @@ _TIMEOUT = 10 # seconds
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the DoorBird camera platform."""
device = hass.data.get(DOORBIRD_DOMAIN)
async_add_devices([
DoorBirdCamera(device.live_image_url, _CAMERA_LIVE, _LIVE_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR,
_LAST_VISITOR_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'motionsensor'), _CAMERA_LAST_MOTION,
_LAST_MOTION_INTERVAL),
])
for doorstation in hass.data[DOORBIRD_DOMAIN]:
device = doorstation.device
async_add_devices([
DoorBirdCamera(
device.live_image_url,
_CAMERA_LIVE.format(doorstation.name),
_LIVE_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'doorbell'),
_CAMERA_LAST_VISITOR.format(doorstation.name),
_LAST_VISITOR_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'motionsensor'),
_CAMERA_LAST_MOTION.format(doorstation.name),
_LAST_MOTION_INTERVAL),
])
class DoorBirdCamera(Camera):

View File

@@ -0,0 +1,58 @@
"""
Family Hub camera for Samsung Refrigerators.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/camera.familyhub/
"""
import logging
import voluptuous as vol
from homeassistant.components.camera import Camera
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_IP_ADDRESS, CONF_NAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['python-family-hub-local==0.0.2']
DEFAULT_NAME = 'FamilyHub Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
async def async_setup_platform(
hass, config, async_add_devices, discovery_info=None):
"""Set up the Family Hub Camera."""
from pyfamilyhublocal import FamilyHubCam
address = config.get(CONF_IP_ADDRESS)
name = config.get(CONF_NAME)
session = async_get_clientsession(hass)
family_hub_cam = FamilyHubCam(address, hass.loop, session)
async_add_devices([FamilyHubCamera(name, family_hub_cam)], True)
class FamilyHubCamera(Camera):
"""The representation of a Family Hub camera."""
def __init__(self, name, family_hub_cam):
"""Initialize camera component."""
super().__init__()
self._name = name
self.family_hub_cam = family_hub_cam
async def async_camera_image(self):
"""Return a still image response."""
return await self.family_hub_cam.async_get_cam_image()
@property
def name(self):
"""Return the name of this camera."""
return self._name

View File

@@ -33,7 +33,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Foscam IP Camera."""
add_devices([FoscamCam(config)])

View File

@@ -28,6 +28,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_CONTENT_TYPE = 'content_type'
CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change'
CONF_STILL_IMAGE_URL = 'still_image_url'
CONF_FRAMERATE = 'framerate'
DEFAULT_NAME = 'Generic Camera'
@@ -40,11 +41,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_USERNAME): cv.string,
vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string,
vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int,
})
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a generic IP Camera."""
async_add_devices([GenericCamera(hass, config)])
@@ -62,6 +63,7 @@ class GenericCamera(Camera):
self._still_image_url = device_info[CONF_STILL_IMAGE_URL]
self._still_image_url.hass = hass
self._limit_refetch = device_info[CONF_LIMIT_REFETCH_TO_URL_CHANGE]
self._frame_interval = 1 / device_info[CONF_FRAMERATE]
self.content_type = device_info[CONF_CONTENT_TYPE]
username = device_info.get(CONF_USERNAME)
@@ -78,6 +80,11 @@ class GenericCamera(Camera):
self._last_url = None
self._last_image = None
@property
def frame_interval(self):
"""Return the interval between frames of the mjpeg stream."""
return self._frame_interval
def camera_image(self):
"""Return bytes of camera image."""
return run_coroutine_threadsafe(

View File

@@ -11,31 +11,44 @@ import os
import voluptuous as vol
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.camera import (
Camera, CAMERA_SERVICE_SCHEMA, DOMAIN, PLATFORM_SCHEMA)
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_FILE_PATH = 'file_path'
DEFAULT_NAME = 'Local File'
SERVICE_UPDATE_FILE_PATH = 'local_file_update_file_path'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_FILE_PATH): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
CAMERA_SERVICE_UPDATE_FILE_PATH = CAMERA_SERVICE_SCHEMA.extend({
vol.Required(CONF_FILE_PATH): cv.string
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Camera that works with local files."""
file_path = config[CONF_FILE_PATH]
camera = LocalFile(config[CONF_NAME], file_path)
# check filepath given is readable
if not os.access(file_path, os.R_OK):
_LOGGER.warning("Could not read camera %s image from file: %s",
config[CONF_NAME], file_path)
def update_file_path_service(call):
"""Update the file path."""
file_path = call.data.get(CONF_FILE_PATH)
camera.update_file_path(file_path)
return True
add_devices([LocalFile(config[CONF_NAME], file_path)])
hass.services.register(
DOMAIN,
SERVICE_UPDATE_FILE_PATH,
update_file_path_service,
schema=CAMERA_SERVICE_UPDATE_FILE_PATH)
add_devices([camera])
class LocalFile(Camera):
@@ -46,6 +59,7 @@ class LocalFile(Camera):
super().__init__()
self._name = name
self.check_file_path_access(file_path)
self._file_path = file_path
# Set content type of local file
content, _ = mimetypes.guess_type(file_path)
@@ -61,7 +75,26 @@ class LocalFile(Camera):
_LOGGER.warning("Could not read camera %s image from file: %s",
self._name, self._file_path)
def check_file_path_access(self, file_path):
"""Check that filepath given is readable."""
if not os.access(file_path, os.R_OK):
_LOGGER.warning("Could not read camera %s image from file: %s",
self._name, file_path)
def update_file_path(self, file_path):
"""Update the file_path."""
self.check_file_path_access(file_path)
self._file_path = file_path
self.schedule_update_ha_state()
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def device_state_attributes(self):
"""Return the camera state attributes."""
return {
'file_path': self._file_path,
}

View File

@@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a MJPEG IP Camera."""
if discovery_info:

View File

@@ -45,7 +45,7 @@ class NeatoCleaningMap(Camera):
self.update()
return self._image
@Throttle(timedelta(seconds=10))
@Throttle(timedelta(seconds=60))
def update(self):
"""Check the contents of the map list."""
self.neato.update_robots()

View File

@@ -23,14 +23,19 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a Nest Cam."""
if discovery_info is None:
return
"""Set up a Nest Cam.
camera_devices = hass.data[nest.DATA_NEST].cameras()
No longer in use.
"""
async def async_setup_entry(hass, entry, async_add_devices):
"""Set up a Nest sensor based on a config entry."""
camera_devices = \
await hass.async_add_job(hass.data[nest.DATA_NEST].cameras)
cameras = [NestCamera(structure, device)
for structure, device in camera_devices]
add_devices(cameras, True)
async_add_devices(cameras, True)
class NestCamera(Camera):

View File

@@ -12,7 +12,6 @@ import voluptuous as vol
from homeassistant.const import CONF_VERIFY_SSL
from homeassistant.components.netatmo import CameraData
from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA)
from homeassistant.loader import get_component
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ['netatmo']
@@ -30,13 +29,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up access to Netatmo cameras."""
netatmo = get_component('netatmo')
netatmo = hass.components.netatmo
home = config.get(CONF_HOME)
verify_ssl = config.get(CONF_VERIFY_SSL, True)
import lnetatmo
import pyatmo
try:
data = CameraData(netatmo.NETATMO_AUTH, home)
for camera_name in data.get_camera_names():
@@ -47,7 +45,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
continue
add_devices([NetatmoCamera(data, camera_name, home,
camera_type, verify_ssl)])
except lnetatmo.NoDevice:
except pyatmo.NoDevice:
return None

View File

@@ -24,6 +24,16 @@ snapshot:
description: Template of a Filename. Variable is entity_id.
example: '/tmp/snapshot_{{ entity_id }}'
local_file_update_file_path:
description: Update the file_path for a local_file camera.
fields:
entity_id:
description: Name(s) of entities to update.
example: 'camera.local_file'
file_path:
description: Path to the new image file.
example: '/images/newimage.jpg'
onvif_ptz:
description: Pan/Tilt/Zoom service for ONVIF camera.
fields:
@@ -39,4 +49,3 @@ onvif_ptz:
zoom:
description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT"
example: "ZOOM_IN"

View File

@@ -13,6 +13,7 @@ import voluptuous as vol
from homeassistant.const import CONF_PORT
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['uvcclient==0.10.1']
@@ -41,25 +42,26 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
port = config[CONF_PORT]
from uvcclient import nvr
nvrconn = nvr.UVCRemote(addr, port, key)
try:
# Exceptions may be raised in all method calls to the nvr library.
nvrconn = nvr.UVCRemote(addr, port, key)
cameras = nvrconn.index()
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
# Filter out airCam models, which are not supported in the latest
# version of UnifiVideo and which are EOL by Ubiquiti
cameras = [
camera for camera in cameras
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
except nvr.NotAuthorized:
_LOGGER.error("Authorization failure while connecting to NVR")
return False
except nvr.NvrError:
_LOGGER.error("NVR refuses to talk to me")
return False
except nvr.NvrError as ex:
_LOGGER.error("NVR refuses to talk to me: %s", str(ex))
raise PlatformNotReady
except requests.exceptions.ConnectionError as ex:
_LOGGER.error("Unable to connect to NVR: %s", str(ex))
return False
identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid'
# Filter out airCam models, which are not supported in the latest
# version of UnifiVideo and which are EOL by Ubiquiti
cameras = [
camera for camera in cameras
if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']]
raise PlatformNotReady
add_devices([UnifiVideoCamera(nvrconn,
camera[identifier],

View File

@@ -0,0 +1,166 @@
"""
This component provides support for Xiaomi Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.xiaomi/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
DEFAULT_BRAND = 'Xiaomi Home Camera'
DEFAULT_PATH = '/media/mmcblk0p1/record'
DEFAULT_PORT = 21
DEFAULT_USERNAME = 'root'
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
CONF_MODEL = 'model'
MODEL_YI = 'yi'
MODEL_XIAOFANG = 'xiaofang'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_MODEL): vol.Any(MODEL_YI,
MODEL_XIAOFANG),
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string,
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string
})
async def async_setup_platform(hass,
config,
async_add_devices,
discovery_info=None):
"""Set up a Xiaomi Camera."""
_LOGGER.debug('Received configuration for model %s', config[CONF_MODEL])
async_add_devices([XiaomiCamera(hass, config)])
class XiaomiCamera(Camera):
"""Define an implementation of a Xiaomi Camera."""
def __init__(self, hass, config):
"""Initialize."""
super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._last_image = None
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config[CONF_NAME]
self.host = config[CONF_HOST]
self._model = config[CONF_MODEL]
self.port = config[CONF_PORT]
self.path = config[CONF_PATH]
self.user = config[CONF_USERNAME]
self.passwd = config[CONF_PASSWORD]
@property
def name(self):
"""Return the name of this camera."""
return self._name
@property
def brand(self):
"""Return the camera brand."""
return DEFAULT_BRAND
@property
def model(self):
"""Return the camera model."""
return self._model
def get_latest_video_url(self):
"""Retrieve the latest video file from the Xiaomi Camera FTP server."""
from ftplib import FTP, error_perm
ftp = FTP(self.host)
try:
ftp.login(self.user, self.passwd)
except error_perm as exc:
_LOGGER.error('Camera login failed: %s', exc)
return False
try:
ftp.cwd(self.path)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s - %s', self.path, exc)
return False
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
if self._model == MODEL_YI:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
elif self._model == MODEL_XIAOFANG:
_LOGGER.warning("There don't appear to be any folders")
return False
first_dir = dirs[-1]
try:
ftp.cwd(first_dir)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s - %s', first_dir, exc)
return False
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
latest_dir = dirs[-1]
ftp.cwd(latest_dir)
videos = [v for v in ftp.nlst() if '.tmp' not in v]
if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
return False
if self._model == MODEL_XIAOFANG:
video = videos[-2]
else:
video = videos[-1]
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}'.format(
self.user, self.passwd, self.host, self.port, ftp.pwd(), video)
async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
url = await self.hass.async_add_job(self.get_latest_video_url)
if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = await asyncio.shield(ffmpeg.get_image(
url, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
self._last_url = url
return self._last_image
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)
await stream.open_camera(
self._last_url, extra_cmd=self._extra_arguments)
await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
await stream.close()

View File

@@ -11,11 +11,13 @@ import voluptuous as vol
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH,
CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PATH, CONF_PASSWORD, CONF_PORT, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.exceptions import PlatformNotReady
REQUIREMENTS = ['aioftp==0.10.1']
DEPENDENCIES = ['ffmpeg']
_LOGGER = logging.getLogger(__name__)
@@ -38,12 +40,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
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 a Yi Camera."""
_LOGGER.debug('Received configuration: %s', config)
async_add_devices([YiCamera(hass, config)], True)
@@ -54,71 +53,81 @@ class YiCamera(Camera):
"""Initialize."""
super().__init__()
self._extra_arguments = config.get(CONF_FFMPEG_ARGUMENTS)
self._ftp = None
self._last_image = None
self._last_url = None
self._manager = hass.data[DATA_FFMPEG]
self._name = config.get(CONF_NAME)
self.host = config.get(CONF_HOST)
self.port = config.get(CONF_PORT)
self.path = config.get(CONF_PATH)
self.user = config.get(CONF_USERNAME)
self.passwd = config.get(CONF_PASSWORD)
self._name = config[CONF_NAME]
self.host = config[CONF_HOST]
self.port = config[CONF_PORT]
self.path = config[CONF_PATH]
self.user = config[CONF_USERNAME]
self.passwd = config[CONF_PASSWORD]
@property
def name(self):
"""Return the name of this camera."""
return self._name
hass.async_add_job(self._connect_to_client)
@property
def brand(self):
"""Camera brand."""
return DEFAULT_BRAND
def get_latest_video_url(self):
@property
def name(self):
"""Return the name of this camera."""
return self._name
async def _connect_to_client(self):
"""Attempt to establish a connection via FTP."""
from aioftp import Client, StatusCodeError
ftp = Client()
try:
await ftp.connect(self.host)
await ftp.login(self.user, self.passwd)
self._ftp = ftp
except StatusCodeError as err:
raise PlatformNotReady(err)
async def _get_latest_video_url(self):
"""Retrieve the latest video file from the customized Yi FTP server."""
from ftplib import FTP, error_perm
ftp = FTP(self.host)
try:
ftp.login(self.user, self.passwd)
except error_perm as exc:
_LOGGER.error('There was an error while logging into the camera')
_LOGGER.debug(exc)
return False
from aioftp import StatusCodeError
try:
ftp.cwd(self.path)
except error_perm as exc:
_LOGGER.error('Unable to find path: %s', self.path)
_LOGGER.debug(exc)
return False
await self._ftp.change_directory(self.path)
dirs = []
for path, attrs in await self._ftp.list():
if attrs['type'] == 'dir' and '.' not in str(path):
dirs.append(path)
latest_dir = dirs[-1]
await self._ftp.change_directory(latest_dir)
dirs = [d for d in ftp.nlst() if '.' not in d]
if not dirs:
_LOGGER.warning("There don't appear to be any uploaded videos")
return False
videos = []
for path, _ in await self._ftp.list():
videos.append(path)
if not videos:
_LOGGER.info('Video folder "%s" empty; delaying', latest_dir)
return None
latest_dir = dirs[-1]
ftp.cwd(latest_dir)
videos = ftp.nlst()
if not videos:
_LOGGER.info('Video folder "%s" is empty; delaying', latest_dir)
return False
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1])
return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format(
self.user, self.passwd, self.host, self.port, self.path,
latest_dir, videos[-1])
except (ConnectionRefusedError, StatusCodeError) as err:
_LOGGER.error('Error while fetching video: %s', err)
return None
async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
url = await self.hass.async_add_job(self.get_latest_video_url)
url = await self._get_latest_video_url()
if url != self._last_url:
ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop)
self._last_image = await asyncio.shield(ffmpeg.get_image(
url, output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments), loop=self.hass.loop)
self._last_image = await asyncio.shield(
ffmpeg.get_image(
url,
output_format=IMAGE_JPEG,
extra_cmd=self._extra_arguments),
loop=self.hass.loop)
self._last_url = url
return self._last_image

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