Compare commits

...

298 Commits

Author SHA1 Message Date
Paulus Schoutsen
0f12b37977 0.73.2 - security release (#15494)
* Extract SSL context creation to helper (#15483)

* Extract SSL context creation to helper

* Lint

* Bumped version to 0.73.2
2018-07-16 22:13:12 +02:00
Paulus Schoutsen
a2a4c633f3 Merge branch 'rc' 2018-07-08 17:35:59 +02:00
Paulus Schoutsen
e6dd4f6e13 Bumped version to 0.73.1 2018-07-08 17:35:30 +02:00
Paulus Schoutsen
b327ea2023 Bump frontend to 20180708.0 2018-07-08 17:31:03 +02:00
Paulus Schoutsen
a1d8b0e9b3 Merge pull request #15330 from home-assistant/rc
0.73
2018-07-06 16:41:09 -04:00
Paulus Schoutsen
1e7cfc04af Bumped version to 0.73.0 2018-07-06 22:31:09 +02:00
Paulus Schoutsen
46de89e1a3 Bumped version to 0.73.0b6 2018-07-04 12:11:52 -04:00
Paulus Schoutsen
852526e10a Bump frontend to 20180704.0 2018-07-04 12:11:39 -04:00
Paulus Schoutsen
07dde62e70 Bumped version to 0.73.0b5 2018-07-03 14:58:31 -04:00
Paulus Schoutsen
cb458b7745 Bump frontend to 20180703.1 2018-07-03 14:58:27 -04:00
Paulus Schoutsen
b82371f44b Bumped version to 0.73.0b4 2018-07-03 11:11:14 -04:00
Paulus Schoutsen
1c525968d1 Bump frontend to 20180703.0 2018-07-03 11:10:07 -04:00
Paulus Schoutsen
d7fd9247a9 Bumped version to 0.73.0b3 2018-07-02 14:44:15 -04:00
Paulus Schoutsen
0dc155c4d3 Bump frontend to 20180702.1 2018-07-02 14:44:11 -04:00
Paulus Schoutsen
c39e6b9618 Bumped version to 0.73.0b2 2018-07-02 08:57:26 -04:00
Paulus Schoutsen
855cbc0aed Update frontend to 20180702.0 2018-07-02 08:56:45 -04:00
Paulus Schoutsen
3c3a53a137 Update translations 2018-07-02 08:53:47 -04:00
Jason Hu
63b28aa39d By default to use access_token if hass.auth.active (#15212)
* Force to use access_token if hass.auth.active

* Not allow Basic auth with api_password if hass.auth.active

* Block websocket api_password auth when hass.auth.active

* Add legacy_api_password auth provider

* lint

* lint
2018-07-01 21:53:38 -04:00
Paulus Schoutsen
279fd39677 Bumped version to 0.73.0b1 2018-07-01 13:40:55 -04:00
cdce8p
c978281d1e Revert some changes to setup.py (#15248) 2018-07-01 13:40:48 -04:00
Jason Hu
311a44007c Fix an issue when user's nest developer account don't have permission (#15237) 2018-07-01 13:40:48 -04:00
Anders Melchiorsen
47401739ea Make LIFX color/temperature attributes mutually exclusive (#15234) 2018-07-01 13:40:48 -04:00
Jason Hu
11ba7cc8ce Only create front-end client_id once (#15214)
* Only create frontend client_id once

* Check user and client_id before create refresh token

* Lint

* Follow code review comment

* Minor clenaup

* Update doc string
2018-07-01 13:40:47 -04:00
Andy Castille
c3ad30ec87 Rachio webhooks (#15111)
* Make fewer requests to the Rachio API

* BREAKING: Rewrite Rachio component
2018-07-01 13:40:47 -04:00
Paulus Schoutsen
5d6db9a915 Bump frontend to 20180701.0 2018-07-01 13:00:48 -04:00
Paulus Schoutsen
56bbadb501 Version bump to 0.73.0b0 2018-06-29 18:06:32 -04:00
Paulus Schoutsen
fa79aead9a Bumped version to 0.74.0b0 2018-06-29 18:04:00 -04:00
Paulus Schoutsen
24fec3e826 Merge remote-tracking branch 'origin/master' into dev 2018-06-29 18:01:40 -04:00
Daniel Shokouhi
2524dca7bf Use cached states for neato when possible (#15218) 2018-06-29 17:27:18 -04:00
Sebastian Muszynski
56f17b8651 Fix 'AirQualityMonitorStatus' object has no attribute ‘time_state’ (#15216) 2018-06-29 17:26:48 -04:00
Vignesh Venkat
49623d2dad Update python-wink to 1.9.1 (#15215)
Fixes a bug for when GE Z-Wave fan speeds are adjusted using the
wink app.
2018-06-29 17:26:06 -04:00
Anders Melchiorsen
66479dc2e5 Update eternalegypt (#15180)
* Update eternalegypt to 0.0.2

* Share websession

* Renames
2018-06-29 17:25:49 -04:00
Paulus Schoutsen
bbbec5a056 Bump frontend to 20180629.1 2018-06-29 17:21:54 -04:00
Hmmbob
94b55efef3 Stop supporting deprecated TLS ciphers (#15217)
* Stop supporting deprecated TLS ciphers

* Lint
2018-06-29 17:18:44 -04:00
Colin O'Dell
fd38caa287 X-Forwarded-For improvements and bug fixes (#15204)
* Use new trusted_proxies setting for X-Forwarded-For whitelist

* Only use the last IP in the header

Per Wikipedia (https://en.wikipedia.org/wiki/X-Forwarded-For#Format):

 > The last IP address is always the IP address that connects to the last proxy,
 > which means it is the most reliable source of information.

* Add two additional tests

* Ignore nonsense header values instead of failing
2018-06-29 16:27:06 -04:00
Sriram Vaidyanathan
c61a652c90 Fixed Indentation error (#15210)
* Fixed Indentation error

* Update xiaomi.py
2018-06-29 16:23:14 +02:00
cdce8p
e3e014bccc Fix zwave climate operation mode mappings (#15162) 2018-06-29 16:09:46 +02:00
Paulus Schoutsen
26590e244c Migrate home assistant auth provider to use storage helper (#15200) 2018-06-29 00:02:45 -04:00
Paulus Schoutsen
39971ee919 Make sure we check access token expiration (#15207)
* Make sure we check access token expiration

* Use correct access token websocket
2018-06-29 00:02:33 -04:00
Paulus Schoutsen
2205090795 Storage auth (#15192)
* Support parallel loading

* Add storage mock

* Store auth

* Fix tests
2018-06-28 22:14:26 -04:00
Alex Barcelo
a277470363 Adding 'namespace' for prometheus metrics (#13738)
* Updating prometheus client version

* Using `entity_filter` as filter mechanism

* New optional `namespace` configuration
2018-06-28 16:49:33 +02:00
Colin O'Dell
19f2bbf52f Only use the X-Forwarded-For header if connection is from a trusted network (#15182)
See https://github.com/home-assistant/home-assistant/issues/14345#issuecomment-400854569
2018-06-28 09:16:11 -04:00
Pascal Vizeli
dbb786c548 DarkSky weather / Fix states (#15174)
* DarkSky weather / Fix states

* fix lint

* fix tests
2018-06-28 12:23:32 +02:00
Daniel Shokouhi
4fbe3bb070 Finalize BotVac D7 Support And Further Reduce Cloud Calls (#15161)
* Finalize BotVac D7 Support And Further Reduce Cloud Calls

* Lint

* Lint Again

* Implement requested changes

* Hound

* Lint
2018-06-27 22:55:27 +02:00
MizterB
9066ac44fe Philips Hue Scene Activation: Simplified scene lookup logic, improved error handling (#15175)
* Simplified scene lookup logic, improved error handling

* Lint
2018-06-27 15:22:29 -04:00
Paulus Schoutsen
742144f401 Warn when using custom components (#15172)
* Warn when using custom components

* Update text
2018-06-27 15:21:32 -04:00
Paulus Schoutsen
c0b6a857f7 Version bump to 20180627.0 2018-06-27 14:20:24 -04:00
Tom Harris
d6dee62c92 Add Mini remote support to insteon_plm (#15152)
* Add mini-remote

* Bump insteonplm version to 0.11.3 to support mini-remotes
2018-06-27 12:19:56 +02:00
Fabian Affolter
41017f10a3 Upgrade sendgrid to 5.4.1 (#15166) 2018-06-27 12:12:02 +02:00
Fabian Affolter
ba50a5c329 Upgrade keyring to 13.0.0 (#15167) 2018-06-27 12:11:41 +02:00
Fabian Affolter
4208bb457d Upgrade youtube_dl to 2018.06.25 (#15168) 2018-06-27 12:11:26 +02:00
Matt Snyder
15af6b1ad9 Address inconsistent behavior on flux_led component (#14713)
* Address inconsistent behavior between different controllers.
Correct issue with comparison that was preventing white value slider from being shown.

* Add white mode for Flux LED

* Call _bulb.turnOn() after bulb properties have been set to prevent immediate on action

* Only use existing brightness if rgb is None to prevent unexpected recalculation of passed rgb values.

* Remove blank line

* Undo change so current brightness is used in all cases.
2018-06-26 21:23:57 +02:00
Robert Kiss
3921dc77a6 Add SSL peer certificate support to HTTP server (#15043)
* adding SSL peer certificate support to HTTP server

* remove unnecessary exception block
2018-06-26 11:44:08 -04:00
Matt LeBrun
0094fd5c34 Add channel changing support to SamsungTV component (#14451)
Add channel changing support to SamsungTV component
2018-06-26 16:22:10 +02:00
Paulus Schoutsen
d58e401812 Merge pull request #15149 from home-assistant/rc
0.72.1
2018-06-25 17:25:44 -04:00
Paulus Schoutsen
c79c94550f Return None to indicate no config found (#15147)
* Return None to indicate no config found

* Fix tests
2018-06-25 17:21:38 -04:00
Paulus Schoutsen
9b950f5192 Bumped version to 0.72.1 2018-06-25 16:59:14 -04:00
Jason Hu
2520fddbdf Bump python-nest to 4.0.3 (#15098)
Resolve network reconnect issue
2018-06-25 16:59:00 -04:00
Paulus Schoutsen
3f21966ec9 Fix cast config (#15143) 2018-06-25 16:58:27 -04:00
Jason Hu
69502163bd Skip nest security state sensor if no Nest Cam exists (#15112) 2018-06-25 16:57:27 -04:00
Aaron Bach
893e0f8db6 Fix socket bug with Yi in 0.72 (#15109)
* Fixes BrokenPipeError exceptions with Yi (#15108)

* Make sure to close the socket
2018-06-25 16:57:26 -04:00
Jason Hu
1c8b52f630 Prevent Nest component setup crash due insufficient permission. (#14966)
* Prevent Nest component setup crash due insufficient permission.

* Trigger CI

* Better error handle and address code review comments

* Lint

* Tiny wording adjust

* Notify user if async_setup_entry failed

* Return False if exception occurred in NestDevice.initialize
2018-06-25 16:57:26 -04:00
Jason Hu
6e4fb7a937 Prevent Nest component setup crash due insufficient permission. (#14966)
* Prevent Nest component setup crash due insufficient permission.

* Trigger CI

* Better error handle and address code review comments

* Lint

* Tiny wording adjust

* Notify user if async_setup_entry failed

* Return False if exception occurred in NestDevice.initialize
2018-06-25 16:06:00 -04:00
Paulus Schoutsen
ab1939f56f Bump frontend to 20180625.0 2018-06-25 16:04:30 -04:00
Paulus Schoutsen
15507df407 Bump frontend to 20180625.0 2018-06-25 16:04:17 -04:00
Paulus Schoutsen
46ea28a4f8 Fix cast config (#15143) 2018-06-25 15:59:05 -04:00
Sriram Vaidyanathan
c8458fd7c5 Update xiaomi.py (#15136)
* Update xiaomi.py

Minor logic fix for Xiaofang cameras.

* Removed whitespace

* Removed whitespace
2018-06-25 13:14:36 -04:00
Jason Hu
e681a7929c Skip nest security state sensor if no Nest Cam exists (#15112) 2018-06-25 13:13:41 -04:00
Martin Hjelmare
b2d37ccef6 Fix mysensors climate supported features (#15110) 2018-06-25 13:06:12 -04:00
Luc Touraille
9dd2c36de4 Update aiofreepybox to fix HTTPS connection issues (#15104)
The previous version of aiofreepybox was not working with custom
domain names, which uses a Let's Encrypt certificates. Also, it
was not working with the default domain name when connecting to
Freebox v6. This should be fixed in aiofreepybox 0.0.4.

See https://github.com/stilllman/freepybox/pull/1,
https://github.com/stilllman/freepybox/pull/3 and
https://github.com/stilllman/freepybox/issues/2 for more info.
2018-06-25 13:05:33 -04:00
Ville Skyttä
b92350fb55 Lint cleanup (#15103)
* Remove unneeded inline pylint disables

* Remove unneeded noqa's

* Use symbol names instead of message ids in inline pylint disables
2018-06-25 13:05:07 -04:00
Jason Hu
6c0fc65eaf Bump python-nest to 4.0.3 (#15098)
Resolve network reconnect issue
2018-06-25 13:04:32 -04:00
Pascal Vizeli
42ba2a68ce Revert "Add language to dark sky weather component" (#15142)
* Revert "Fix #14919. Should throw exception when camera stream closed by frontend (#15028)"

This reverts commit 508d0459a7.

* Revert "Fix pylintrc section order and option placements (#15120)"

This reverts commit dbae410cf4.

* Revert "Add storage helper and migrate config entries (#15045)"

This reverts commit ae51dc08bf.

* Revert "Add language to dark sky weather component (#15130)"

This reverts commit 672a3c7178.
2018-06-25 19:04:07 +02:00
Jason Hu
508d0459a7 Fix #14919. Should throw exception when camera stream closed by frontend (#15028)
* Fix #14919. Should throw exception when camera stream closed by frontend

* Re-trigger CI

* pythonic re-raise
2018-06-25 13:03:39 -04:00
Ville Skyttä
dbae410cf4 Fix pylintrc section order and option placements (#15120) 2018-06-25 12:55:03 -04:00
Paulus Schoutsen
ae51dc08bf Add storage helper and migrate config entries (#15045)
* Add storage helper

* Migrate config entries to use the storage helper

* Make sure tests do not do I/O

* Lint

* Add versions to stored data

* Add more instance variables

* Make migrator load config if nothing to migrate

* Address comments
2018-06-25 12:53:49 -04:00
Pascal Vizeli
672a3c7178 Add language to dark sky weather component (#15130)
* Add language to dark sky weather component

* Update darksky.py
2018-06-25 08:35:44 -06:00
cdce8p
f8bc3411ad PyPi: Fix description and setup.cfg (#15107)
* Fix description and extend use of setup.cfg

* Fix lint
2018-06-25 09:57:26 -04:00
Adam Mills
038168c417 Support for Homekit Controller climate devices (#15057)
* Support for Homekit Controller climate devices

* Handle stale state when operating mode off
2018-06-25 09:45:26 -04:00
dreizehnelf
73034c933e Add discovery support to mqtt climate component. (#15085)
* Add discovery support to mqtt climate component.

* - Fix flake8 error (./homeassistant/components/climate/mqtt.py:130:1: D202 No blank lines allowed after function docstring)
- Fix test error (since climate component was expected not to work - changed it to "lock" component, which also does not have MQTT discovery support yet)

* Fix old assert statement to reflect new lock component usage

* Change invalid MQTT discovery component type from 'lock' to 'timer', since contrary to the documentation the lock component is properly supported when using MQTT discovery.

* Make configuration of invalid MQTT config component a single point of entry to prevent missing the assertion later in the code when changing.

* Add new testcases to cover not-yet-covered code paths in https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/climate/mqtt.py
2018-06-25 15:13:19 +02:00
b3nj1
d3ceb9080c MQTT Alarm Control Panel: add retain option for publishing for cases... (#15134)
* MQTT Alarm Control Panel: add retain option for publishing for cases where receiver is asleep

* MQTT Alarm Control Panel: add retain option for publishing for cases where receiver is asleep

* MQTT Alarm Control Panel: add retain option for publishing for cases where receiver is asleep
2018-06-25 14:04:16 +02:00
Martin Hjelmare
3893d8a876 Reorganize mysensors (#15123)
* Move mysensors.py to package

* Move mysensors component to package

* Split code into multiple modules.

* Update coveragerc
2018-06-25 13:58:16 +02:00
Tom Harris
05924a2868 Bump insteonplm version to 0.11.2 (#15133)
* Bump insteonplm version to 0.11.2

* Gratuitous commit to force travis again.

* Reverse change made to force Travis CI
2018-06-25 13:46:55 +02:00
Aaron Bach
021d08a9c4 Make sure Yi utilizes existing event loop (#15131) 2018-06-24 19:09:08 -06:00
Robert Svensson
5a71a22fb9 deCONZ small improvements (#15128)
* Make sure that bridge id is available for config entry

* Fix so deconz reports proper color values

* Bump dependency to v39
2018-06-24 15:48:59 -06:00
Aaron Bach
6064932e2e Make Pollen.com platform async (#14963)
* Most of the work in place

* Final touches

* Small style updates

* Owner-requested changes

* Member-requested changes
2018-06-24 11:04:31 -06:00
Marcelo Moreira de Mello
9de7034d0e Added attribute attribution to Digital Ocean component (#15114) 2018-06-24 13:36:27 +02:00
Andrey
96d5684a89 Switch to pypi version of pybotvac (#15115) 2018-06-24 11:06:25 +02:00
Aaron Bach
91962e2681 Fix socket bug with Yi in 0.72 (#15109)
* Fixes BrokenPipeError exceptions with Yi (#15108)

* Make sure to close the socket
2018-06-23 13:22:48 -06:00
Paulus Schoutsen
ee31f89049 Merge branch 'master' into dev 2018-06-22 13:40:53 -04:00
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
c419cbb46f Bump frontend to 20180622.1 2018-06-22 12:46:45 -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
0c01f3a0fe Update frontend to 20180622.0 2018-06-22 10:24:04 -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
94eee6d069 Frontend bump to 20180621.2 2018-06-21 17:38:44 -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
6781ecf159 Bump frontend to 20180621.1 2018-06-21 17:15:16 -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
Bob Clough
bfc55137ea 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 14:59:02 -04:00
Paulus Schoutsen
e98e7e2751 Update frontend to 20180621.0 2018-06-21 14:57:19 -04:00
Paulus Schoutsen
b687de879c Update frontend to 20180621.0 2018-06-21 14:57:08 -04:00
Martin Hjelmare
4048ad36a8 Add script to run monkeytype typing on test suite (#14440)
* The monkeytype script takes an optional argument to specify a test
  module or directory to run. Otherwise the whole test suite will run.
* Add monkeytype sqlite db to gitignore.
2018-06-21 15:06:05 +02:00
hanzoh
8c2f0e3b30 Homematic: Add optional port for resolvenames via JSON (#15029)
* Add optional JSON port
2018-06-21 14:52:02 +02:00
Daniel Shokouhi
6cabbd2592 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-20 21:46:15 -04:00
Daniel Perna
8d22754a06 Update pyhomematic to 0.1.44 (#15069)
* Update __init__.py

* Update requirements_all.txt
2018-06-20 21:44:50 -04:00
Tom Harris
be6d1b5e94 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-20 21:44:05 -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
895306f822 Rename experimental UI to lovelace (#15065)
* Rename experimental UI to lovelace

* Bump frontend to 20180620.0
2018-06-20 15:13:08 -04:00
Thibault Cohen
a729742757 Fix tplink max/min kelvin for temperature adjustment (#15020) 2018-06-20 19:29:36 +02:00
William Scanlon
6bc03ee763 Python wink update (#15048)
* Updated python-wink to 1.9.0

* Added support for groups of Wink shades.
2018-06-20 09:41:28 -04:00
Dejan Dakić
75580dfade Upgraded librouteros since it has support for authenticatoin in new RouterOS. (#15056) 2018-06-20 09:15:40 +02:00
Marcelo Moreira de Mello
1f8699d9b4 Upgrade pyarlo to 0.1.8 to support Arlo Baby monitor (#15060) 2018-06-20 07:50:38 +02:00
gstorer
fca5d55b43 Update pywemo to version 0.4.28. (#15052) 2018-06-19 11:16:31 -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
62432ced90 Update frontend to 20180619.0 2018-06-19 10:56:33 -04:00
Raoul Teeuwen
27873b4457 Update condition.py (#15021)
* Update condition.py

Added code that writes to warning-log what entity causes a problem when 'value cannot be processed as a number', making troubleshooting easier.

* Make a one-line warning
2018-06-19 13:26:52 +02:00
Andrey
1e7333eeb6 Switch to own packaged version of pyflic (#15041) 2018-06-19 10:31:21 +02:00
Andrey
7cd620d30f Switch upstream adafruit package (#15038) 2018-06-19 10:30:43 +02:00
Fabian Affolter
7a180ac205 Update loopenergy link to docs (#15050) 2018-06-19 09:56:29 +02:00
Hate-Usernames
153ccda853 Patch save_json (#15046) 2018-06-18 21:34:36 -04:00
Jeff Irion
067e4f6d9a Show running apps as sources for Fire TV (#15033)
* Show running apps as sources for Fire TV

* Fix unnecessary 'else' after 'return' (no-else-return)

* Remove 'pylint: disable=unused-argument'

* cleanup
2018-06-18 18:13:50 +02:00
Fabian Affolter
9d6ce609f9 Upgrade requests to 2.19.1 (#15019) 2018-06-18 18:13:21 +02: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
Paulus Schoutsen
8869617890 Bump frontend to 20180618.0 2018-06-18 09:58:16 -04:00
Pascal Vizeli
62f970e486 Bugfix empty entity lists (#15035)
* Bugfix empty entity lists

* Add tests

* Update test_entity_platform.py

* Update entity_platform.py
2018-06-18 09:22:52 -04:00
Martin Hjelmare
f9a21dbfda 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:21:41 -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
Jeff Irion
7bfa81c592 Improve volume support for Vizio Smartcast (#14981)
* Improve volume support for Vizio Smartcast

* Vizio: avoid an error when 'self._device.get_current_volume()' returns 'None'

* Improve volume support for Vizio Smartcast

* Vizio: avoid an error when 'self._device.get_current_volume()' returns 'None'

* First line should end with a period
2018-06-17 22:06:53 -06:00
gstorer
d07e40c483 Expose Wemo component availability to home assistant (#14995)
* Expose Wemo component availability to home assistant

* Do not add availability feature to dimmer - it works differently

* Brain fade, deleted completely the wrong thing.

Revert "Do not add availability feature to dimmer - it works differently"

This reverts commit f64e717981.

* (2nd attempt) Do not add availability feature to dimmer - it works differently
2018-06-17 22:05:14 -06:00
Kees Schollaart
0e7e58f172 Update PostNL unit of measure to align with UPS (#15023)
I'm using both the UPS and PostNL package trackers. I'd like to have the unit of measure to be the same, now they appear in two different graphs in the history view. 

If we prefer ```package(s)``` over ```package``` then I'll do a PR for [this line](https://github.com/home-assistant/home-assistant/blob/dev/homeassistant/components/sensor/ups.py#L81)
2018-06-17 21:57:08 -06:00
Paulus Schoutsen
cbdfc95cc8 Make zone entries work without radius (#15032) 2018-06-17 21:55:35 -06:00
Paulus Schoutsen
1642502a70 Update translations 2018-06-17 23:05:10 -04:00
Paulus Schoutsen
33ebd99068 Update translations 2018-06-17 23:04:52 -04:00
Fabian Affolter
9c17e95fc5 Upgrade aiohttp to 3.3.2 (#15025) 2018-06-17 20:24:11 -04:00
Matt Schmitt
1533bc1e1f Add support for Homekit battery service (#14288) 2018-06-17 20:54:34 +02:00
Pascal Vizeli
da3695dccc Update test_http.py 2018-06-17 20:37:46 +02:00
Pascal Vizeli
40c8f5f70e Fix panel URL authentication for Hass.io (#15024)
* Update http.py

* Update http.py

* fix tests

* Update test_http.py
2018-06-17 20:34:47 +02:00
Fabian Affolter
3ceee66e1b Remove typing (#15018) 2018-06-17 19:13:39 +02:00
Andrey
6b908b6f4e Switch nuimo to a hopefully working pypi version (#15006) 2018-06-17 09:41:49 -04:00
Fabian Affolter
a74b081d44 Upgrade youtube_dl to 2018.06.14 (#15013) 2018-06-17 09:41:06 -04:00
Fabian Affolter
bc8093c73b Upgrade numpy to 1.14.5 (#15015) 2018-06-17 09:39:27 -04:00
Fabian Affolter
ca2712506b Update to hole to 0.3.0 (#15014) 2018-06-17 09:38:56 -04:00
Fabian Affolter
c871e8da5d Upgrade netdisco to 1.5.0 (#15016) 2018-06-17 09:38:00 -04:00
Matt Schmitt
722c27f1e2 HomeKit style clean up (#14793) 2018-06-17 13:37:44 +02:00
Andrey
e3fcf46566 Switch to own packaged version of braviarc (#15009) 2018-06-17 13:07:10 +02:00
Andrey
1117371b31 Switch to own packaged version of anel_pwrctrl (#15011) 2018-06-17 13:05:25 +02:00
Petro31
addca54118 Add entity support to Waze Travel Time (#14934)
Current version only supports latitude and longitude or an address for the origin and destination fields. This update allows those fields to use entity IDs of device_tracker, zone, and sensor.
2018-06-17 07:13:47 +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
3db5d5bbf9 Frontend bump to 20180617.0 2018-06-16 22:35:19 -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
1375adfeab Bump frontend to 20180616.0 2018-06-16 17:32:49 -04:00
Paulus Schoutsen
00cbdffa12 Add experimental UI backend (#15002)
* Add experimental UI

* Add test

* Lint
2018-06-16 17:12:03 -04:00
Sebastian Muszynski
c5f012c85a Remove load power attribute for channel USB (#14996)
* Remove load power attribute for channel USB

* Fix format
2018-06-16 15:53:25 -04:00
Andrey
656eae288e Switch to own packaged version of spotipy (#14997) 2018-06-16 15:52:23 -04:00
Teemu R
5898307715 Bump pyhs100 version (#15001)
Fixes #13925
2018-06-16 15:52:03 -04:00
Paulus Schoutsen
9b0efdc8c8 Version bump to 0.73.0.dev0 2018-06-16 10:51:55 -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
Vincent Van Den Berghe
29e659cf4c Fixed SI units for current consumption 2018-03-13 22:20:56 +01:00
676 changed files with 14307 additions and 4314 deletions

View File

@@ -61,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
@@ -97,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
@@ -189,18 +192,21 @@ omit =
homeassistant/components/mychevy.py
homeassistant/components/*/mychevy.py
homeassistant/components/mysensors.py
homeassistant/components/mysensors/*
homeassistant/components/*/mysensors.py
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
@@ -249,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
@@ -311,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
@@ -348,6 +360,7 @@ 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
@@ -363,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
@@ -378,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
@@ -397,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
@@ -462,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
@@ -472,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
@@ -481,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
@@ -506,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
@@ -644,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
@@ -748,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

3
.gitignore vendored
View File

@@ -107,3 +107,6 @@ desktop.ini
# Secrets
.lokalise_token
# monkeytype
monkeytype.sqlite3

View File

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

View File

@@ -1,26 +1,27 @@
"""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 os
import uuid
from collections import OrderedDict
from datetime import datetime, timedelta
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.core import callback
from homeassistant.util import dt as dt_util
from homeassistant.util.decorator import Registry
_LOGGER = logging.getLogger(__name__)
STORAGE_VERSION = 1
STORAGE_KEY = 'auth'
AUTH_PROVIDERS = Registry()
@@ -121,23 +122,12 @@ class User:
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))
credentials = attr.ib(type=list, default=attr.Factory(list), cmp=False)
# 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,
}
refresh_tokens = attr.ib(type=dict, default=attr.Factory(dict), cmp=False)
@attr.s(slots=True)
@@ -152,7 +142,7 @@ class RefreshToken:
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))
access_tokens = attr.ib(type=list, default=attr.Factory(list), cmp=False)
@attr.s(slots=True)
@@ -168,9 +158,10 @@ class AccessToken:
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
def expired(self):
"""Return if this token has expired."""
expires = self.created_at + self.refresh_token.access_token_expiration
return dt_util.utcnow() > expires
@attr.s(slots=True)
@@ -281,7 +272,24 @@ class AuthManager:
self.login_flow = data_entry_flow.FlowManager(
hass, self._async_create_login_flow,
self._async_finish_login_flow)
self.access_tokens = {}
self._access_tokens = {}
@property
def active(self):
"""Return if any auth providers are registered."""
return bool(self._providers)
@property
def support_legacy(self):
"""
Return if legacy_api_password auth providers are registered.
Should be removed when we removed legacy_api_password auth providers.
"""
for provider_type, _ in self._providers:
if provider_type == 'legacy_api_password':
return True
return False
@property
def async_auth_providers(self):
@@ -317,13 +325,22 @@ class AuthManager:
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
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)
tkn = self._access_tokens.get(token)
if tkn is None:
return None
if tkn.expired:
self._access_tokens.pop(token)
return None
return tkn
async def async_create_client(self, name, *, redirect_uris=None,
no_secret=False):
@@ -331,6 +348,16 @@ class AuthManager:
return await self._store.async_create_client(
name, redirect_uris, no_secret)
async def async_get_or_create_client(self, name, *, redirect_uris=None,
no_secret=False):
"""Find a client, if not exists, create a new one."""
for client in await self._store.async_get_clients():
if client.name == name:
return 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)
@@ -374,29 +401,36 @@ class AuthStore:
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)
self._users = None
self._clients = None
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
async def credentials_for_provider(self, provider_type, provider_id):
"""Return credentials for specific auth provider type and id."""
if self.users is None:
if self._users is None:
await self.async_load()
return [
credentials
for user in self.users.values()
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:
async def async_get_users(self):
"""Retrieve all users."""
if self._users is None:
await self.async_load()
return self.users.get(user_id)
return list(self._users.values())
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.
@@ -404,7 +438,7 @@ class AuthStore:
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:
if self._users is None:
await self.async_load()
# New credentials, store in user
@@ -412,7 +446,7 @@ class AuthStore:
info = await auth_provider.async_user_meta_for_credentials(
credentials)
# Make owner and activate user if it's the first user.
if self.users:
if self._users:
is_owner = False
is_active = False
else:
@@ -424,11 +458,11 @@ class AuthStore:
is_active=is_active,
name=info.get('name'),
)
self.users[new_user.id] = new_user
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 user in self._users.values():
for creds in user.credentials:
if (creds.auth_provider_type == credentials.auth_provider_type
and creds.auth_provider_id ==
@@ -445,11 +479,19 @@ class AuthStore:
async def async_remove_user(self, user):
"""Remove a user."""
self.users.pop(user.id)
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."""
local_user = await self.async_get_user(user.id)
if local_user is None:
raise ValueError('Invalid user')
local_client = await self.async_get_client(client_id)
if local_client is None:
raise ValueError('Invalid client_id')
refresh_token = RefreshToken(user, client_id)
user.refresh_tokens[refresh_token.token] = refresh_token
await self.async_save()
@@ -457,10 +499,10 @@ class AuthStore:
async def async_get_refresh_token(self, token):
"""Get refresh token by token."""
if self.users is None:
if self._users is None:
await self.async_load()
for user in self.users.values():
for user in self._users.values():
refresh_token = user.refresh_tokens.get(token)
if refresh_token is not None:
return refresh_token
@@ -469,7 +511,7 @@ class AuthStore:
async def async_create_client(self, name, redirect_uris, no_secret):
"""Create a new client."""
if self.clients is None:
if self._clients is None:
await self.async_load()
kwargs = {
@@ -481,23 +523,148 @@ class AuthStore:
kwargs['secret'] = None
client = Client(**kwargs)
self.clients[client.id] = client
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:
async def async_get_clients(self):
"""Return all clients."""
if self._clients is None:
await self.async_load()
return self.clients.get(client_id)
return list(self._clients.values())
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 = {}
data = await self._store.async_load()
# Make sure that we're not overriding data if 2 loads happened at the
# same time
if self._users is not None:
return
if data is None:
self._users = {}
self._clients = {}
return
users = {
user_dict['id']: User(**user_dict) for user_dict in data['users']
}
for cred_dict in data['credentials']:
users[cred_dict['user_id']].credentials.append(Credentials(
id=cred_dict['id'],
is_new=False,
auth_provider_type=cred_dict['auth_provider_type'],
auth_provider_id=cred_dict['auth_provider_id'],
data=cred_dict['data'],
))
refresh_tokens = {}
for rt_dict in data['refresh_tokens']:
token = RefreshToken(
id=rt_dict['id'],
user=users[rt_dict['user_id']],
client_id=rt_dict['client_id'],
created_at=dt_util.parse_datetime(rt_dict['created_at']),
access_token_expiration=timedelta(
seconds=rt_dict['access_token_expiration']),
token=rt_dict['token'],
)
refresh_tokens[token.id] = token
users[rt_dict['user_id']].refresh_tokens[token.token] = token
for ac_dict in data['access_tokens']:
refresh_token = refresh_tokens[ac_dict['refresh_token_id']]
token = AccessToken(
refresh_token=refresh_token,
created_at=dt_util.parse_datetime(ac_dict['created_at']),
token=ac_dict['token'],
)
refresh_token.access_tokens.append(token)
clients = {
cl_dict['id']: Client(**cl_dict) for cl_dict in data['clients']
}
self._users = users
self._clients = clients
async def async_save(self):
"""Save users."""
pass
users = [
{
'id': user.id,
'is_owner': user.is_owner,
'is_active': user.is_active,
'name': user.name,
}
for user in self._users.values()
]
credentials = [
{
'id': credential.id,
'user_id': user.id,
'auth_provider_type': credential.auth_provider_type,
'auth_provider_id': credential.auth_provider_id,
'data': credential.data,
}
for user in self._users.values()
for credential in user.credentials
]
refresh_tokens = [
{
'id': refresh_token.id,
'user_id': user.id,
'client_id': refresh_token.client_id,
'created_at': refresh_token.created_at.isoformat(),
'access_token_expiration':
refresh_token.access_token_expiration.total_seconds(),
'token': refresh_token.token,
}
for user in self._users.values()
for refresh_token in user.refresh_tokens.values()
]
access_tokens = [
{
'id': user.id,
'refresh_token_id': refresh_token.id,
'created_at': access_token.created_at.isoformat(),
'token': access_token.token,
}
for user in self._users.values()
for refresh_token in user.refresh_tokens.values()
for access_token in refresh_token.access_tokens
]
clients = [
{
'id': client.id,
'name': client.name,
'secret': client.secret,
'redirect_uris': client.redirect_uris,
}
for client in self._clients.values()
]
data = {
'users': users,
'clients': clients,
'credentials': credentials,
'access_tokens': access_tokens,
'refresh_tokens': refresh_tokens,
}
await self._store.async_save(data, delay=1)

View File

@@ -8,10 +8,10 @@ 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'
STORAGE_VERSION = 1
STORAGE_KEY = 'auth_provider.homeassistant'
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
@@ -31,14 +31,22 @@ class InvalidUser(HomeAssistantError):
class Data:
"""Hold the user data."""
def __init__(self, path, data):
def __init__(self, hass):
"""Initialize the user data store."""
self.path = path
self.hass = hass
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
self._data = None
async def async_load(self):
"""Load stored data."""
data = await self._store.async_load()
if data is None:
data = {
'salt': auth.generate_secret(),
'users': []
}
self._data = data
@property
@@ -99,14 +107,9 @@ class Data:
else:
raise InvalidUser
def save(self):
async def async_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))
await self._store.async_save(self._data)
@auth.AUTH_PROVIDERS.register('homeassistant')
@@ -121,12 +124,10 @@ class HassAuthProvider(auth.AuthProvider):
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)
data = Data(self.hass)
await data.async_load()
await self.hass.async_add_executor_job(
data.validate_login, username, password)
async def async_get_or_create_credentials(self, flow_result):
"""Get credentials based on the flow result."""
@@ -141,10 +142,6 @@ class HassAuthProvider(auth.AuthProvider):
'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."""

View File

@@ -0,0 +1,104 @@
"""
Support Legacy API password auth provider.
It will be removed when auth system production ready
"""
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,
})
CONFIG_SCHEMA = auth.AUTH_PROVIDER_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA)
LEGACY_USER = 'homeassistant'
class InvalidAuthError(HomeAssistantError):
"""Raised when submitting invalid authentication."""
@auth.AUTH_PROVIDERS.register('legacy_api_password')
class LegacyApiPasswordAuthProvider(auth.AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
DEFAULT_TITLE = 'Legacy API Password'
async def async_credential_flow(self):
"""Return a flow to login."""
return LoginFlow(self)
@callback
def async_validate_login(self, password):
"""Helper to validate a username and password."""
if not hasattr(self.hass, 'http'):
raise ValueError('http component is not loaded')
if self.hass.http.api_password is None:
raise ValueError('http component is not configured using'
' api_password')
if not hmac.compare_digest(self.hass.http.api_password.encode('utf-8'),
password.encode('utf-8')):
raise InvalidAuthError
async def async_get_or_create_credentials(self, flow_result):
"""Return LEGACY_USER always."""
for credential in await self.async_credentials():
if credential.data['username'] == LEGACY_USER:
return credential
return self.async_create_credentials({
'username': LEGACY_USER
})
async def async_user_meta_for_credentials(self, credentials):
"""
Set name as LEGACY_USER always.
Will be used to populate info when creating a new user.
"""
return {'name': LEGACY_USER}
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['password'])
except InvalidAuthError:
errors['base'] = 'invalid_auth'
if not errors:
return self.async_create_entry(
title=self._auth_provider.name,
data={}
)
schema = OrderedDict()
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
@@ -17,7 +16,7 @@ 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
@@ -53,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(
@@ -123,7 +123,6 @@ async def async_from_config_dict(config: Dict[str, Any],
components.update(hass.config_entries.async_domains())
# setup components
# pylint: disable=not-an-iterable
res = await core_components.async_setup(hass, config)
if not res:
_LOGGER.error("Home Assistant core failed to initialize. "
@@ -197,7 +196,9 @@ async 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
await 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)
@@ -211,9 +212,8 @@ async def async_from_config_file(config_path: str,
finally:
clear_secret_cache()
hass = await async_from_config_dict(
return await async_from_config_dict(
config_dict, hass, enable_log=False, skip_pip=skip_pip)
return hass
@core.callback
@@ -308,23 +308,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
async 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 = await 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

@@ -154,7 +154,6 @@ def async_setup(hass, config):
return True
# pylint: disable=no-self-use
class AlarmControlPanel(Entity):
"""An abstract class for alarm control devices."""

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

@@ -20,7 +20,7 @@ from homeassistant.const import (
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability)
CONF_RETAIN, MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -54,6 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
config.get(CONF_QOS),
config.get(CONF_RETAIN),
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY),
@@ -66,9 +67,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
def __init__(self, name, state_topic, command_topic, qos, payload_disarm,
payload_arm_home, payload_arm_away, code, availability_topic,
payload_available, payload_not_available):
def __init__(self, name, state_topic, command_topic, qos, retain,
payload_disarm, payload_arm_home, payload_arm_away, code,
availability_topic, payload_available, payload_not_available):
"""Init the MQTT Alarm Control Panel."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
@@ -77,6 +78,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self._state_topic = state_topic
self._command_topic = command_topic
self._qos = qos
self._retain = retain
self._payload_disarm = payload_disarm
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
@@ -134,7 +136,8 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
if not self._validate_code(code, 'disarming'):
return
mqtt.async_publish(
self.hass, self._command_topic, self._payload_disarm, self._qos)
self.hass, self._command_topic, self._payload_disarm, self._qos,
self._retain)
@asyncio.coroutine
def async_alarm_arm_home(self, code=None):
@@ -145,7 +148,8 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
if not self._validate_code(code, 'arming home'):
return
mqtt.async_publish(
self.hass, self._command_topic, self._payload_arm_home, self._qos)
self.hass, self._command_topic, self._payload_arm_home, self._qos,
self._retain)
@asyncio.coroutine
def async_alarm_arm_away(self, code=None):
@@ -156,7 +160,8 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
if not self._validate_code(code, 'arming away'):
return
mqtt.async_publish(
self.hass, self._command_topic, self._payload_arm_away, self._qos)
self.hass, self._command_topic, self._payload_arm_away, self._qos,
self._retain)
def _validate_code(self, code, state):
"""Validate given code."""

View File

@@ -107,7 +107,6 @@ class _DisplayCategory(object):
THERMOSTAT = "THERMOSTAT"
# Indicates the endpoint is a television.
# pylint: disable=invalid-name
TV = "TV"
@@ -1474,9 +1473,6 @@ async def async_api_set_thermostat_mode(hass, config, request, entity):
mode = mode if isinstance(mode, str) else mode['value']
operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST)
# Work around a pylint false positive due to
# https://github.com/PyCQA/pylint/issues/1830
# pylint: disable=stop-iteration-return
ha_mode = next(
(k for k, v in API_THERMOSTAT_MODES.items() if v == mode),
None

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

@@ -81,7 +81,6 @@ class APIEventStream(HomeAssistantView):
async def get(self, request):
"""Provide a streaming interface for the event bus."""
# pylint: disable=no-self-use
hass = request.app['hass']
stop_obj = object()
to_write = asyncio.Queue(loop=hass.loop)

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.8']
_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

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

@@ -16,7 +16,6 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'bbb_gpio'
# pylint: disable=no-member
def setup(hass, config):
"""Set up the BeagleBone Black GPIO component."""
# pylint: disable=import-error
@@ -34,41 +33,39 @@ def setup(hass, config):
return True
# noqa: F821
def setup_output(pin):
"""Set up a GPIO as output."""
# pylint: disable=import-error,undefined-variable
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
GPIO.setup(pin, GPIO.OUT)
def setup_input(pin, pull_mode):
"""Set up a GPIO as input."""
# pylint: disable=import-error,undefined-variable
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
GPIO.setup(pin, GPIO.IN, # noqa: F821
GPIO.PUD_DOWN if pull_mode == 'DOWN' # noqa: F821
else GPIO.PUD_UP) # noqa: F821
GPIO.setup(pin, GPIO.IN,
GPIO.PUD_DOWN if pull_mode == 'DOWN'
else GPIO.PUD_UP)
def write_output(pin, value):
"""Write a value to a GPIO."""
# pylint: disable=import-error,undefined-variable
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
GPIO.output(pin, value)
def read_input(pin):
"""Read a value from a GPIO."""
# pylint: disable=import-error,undefined-variable
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
return GPIO.input(pin) is GPIO.HIGH
def edge_detect(pin, event_callback, bounce):
"""Add detection for RISING and FALLING events."""
# pylint: disable=import-error,undefined-variable
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
GPIO.add_event_detect(
pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce)

View File

@@ -67,7 +67,6 @@ async def async_unload_entry(hass, entry):
return await hass.data[DOMAIN].async_unload_entry(entry)
# pylint: disable=no-self-use
class BinarySensorDevice(Entity):
"""Represent a binary sensor."""

View File

@@ -124,11 +124,11 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
result['check_control_messages'] = check_control_messages
elif self._attribute == 'charging_status':
result['charging_status'] = vehicle_state.charging_status.value
# pylint: disable=W0212
# pylint: disable=protected-access
result['last_charging_end_result'] = \
vehicle_state._attributes['lastChargingEndResult']
if self._attribute == 'connection_status':
# pylint: disable=W0212
# pylint: disable=protected-access
result['connection_status'] = \
vehicle_state._attributes['connectionStatus']
@@ -166,7 +166,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
# device class plug: On means device is plugged in,
# Off means device is unplugged
if self._attribute == 'connection_status':
# pylint: disable=W0212
# pylint: disable=protected-access
self._state = (vehicle_state._attributes['connectionStatus'] ==
'CONNECTED')

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

@@ -14,7 +14,8 @@ from homeassistant.components.binary_sensor import (
from homeassistant.components.digital_ocean import (
CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME,
ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY,
ATTR_REGION, ATTR_VCPUS, DATA_DIGITAL_OCEAN)
ATTR_REGION, ATTR_VCPUS, CONF_ATTRIBUTION, DATA_DIGITAL_OCEAN)
from homeassistant.const import ATTR_ATTRIBUTION
_LOGGER = logging.getLogger(__name__)
@@ -75,6 +76,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice):
def device_state_attributes(self):
"""Return the state attributes of the Digital Ocean droplet."""
return {
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_CREATED_AT: self.data.created_at,
ATTR_DROPLET_ID: self.data.id,
ATTR_DROPLET_NAME: self.data.name,

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

@@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4']
REQUIREMENTS = ['pyflic-homeassistant==0.4.dev0']
_LOGGER = logging.getLogger(__name__)

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 = []
@@ -40,7 +39,6 @@ class GC100BinarySensor(BinarySensorDevice):
def __init__(self, name, port_addr, gc100):
"""Initialize the GC100 binary sensor."""
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME
self._port_addr = port_addr
self._gc100 = gc100

View File

@@ -8,7 +8,7 @@ https://home-assistant.io/components/binary_sensor.isy994/
import asyncio
import logging
from datetime import timedelta
from typing import Callable # noqa
from typing import Callable
from homeassistant.core import callback
from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN
@@ -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."""
@@ -299,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."""
@@ -314,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

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

@@ -29,7 +29,8 @@ async def async_setup_platform(
async_add_devices=async_add_devices)
class MySensorsBinarySensor(mysensors.MySensorsEntity, BinarySensorDevice):
class MySensorsBinarySensor(
mysensors.device.MySensorsEntity, BinarySensorDevice):
"""Representation of a MySensors Binary Sensor child node."""
@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

@@ -8,7 +8,8 @@ from itertools import chain
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.nest import DATA_NEST, NestSensorDevice
from homeassistant.components.nest import (
DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice)
from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['nest']
@@ -30,12 +31,10 @@ CAMERA_BINARY_TYPES = {
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 = [
@@ -56,12 +55,19 @@ _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
@@ -76,32 +82,37 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
"for valid options.")
_LOGGER.error(wstr)
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 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)]
add_devices(sensors, True)
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:
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(NestSensorDevice, BinarySensorDevice):
@@ -122,7 +133,7 @@ class NestBinarySensor(NestSensorDevice, BinarySensorDevice):
value = getattr(self.device, self.variable)
if self.variable in STRUCTURE_BINARY_TYPES:
self._state = bool(STRUCTURE_BINARY_STATE_MAP
[self.variable][value])
[self.variable].get(value))
else:
self._state = bool(value)

View File

@@ -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 access to Netatmo binary sensor."""
netatmo = hass.components.netatmo
@@ -68,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,127 @@
"""
Integration with the Rachio Iro sprinkler system controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.rachio/
"""
from abc import abstractmethod
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO,
KEY_DEVICE_ID,
KEY_STATUS,
KEY_SUBTYPE,
SIGNAL_RACHIO_CONTROLLER_UPDATE,
STATUS_OFFLINE,
STATUS_ONLINE,
SUBTYPE_OFFLINE,
SUBTYPE_ONLINE,)
from homeassistant.helpers.dispatcher import dispatcher_connect
DEPENDENCIES = ['rachio']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Rachio binary sensors."""
devices = []
for controller in hass.data[DOMAIN_RACHIO].controllers:
devices.append(RachioControllerOnlineBinarySensor(hass, controller))
add_devices(devices)
_LOGGER.info("%d Rachio binary sensor(s) added", len(devices))
class RachioControllerBinarySensor(BinarySensorDevice):
"""Represent a binary sensor that reflects a Rachio state."""
def __init__(self, hass, controller, poll=True):
"""Set up a new Rachio controller binary sensor."""
self._controller = controller
if poll:
self._state = self._poll_update()
else:
self._state = None
dispatcher_connect(hass, SIGNAL_RACHIO_CONTROLLER_UPDATE,
self._handle_any_update)
@property
def should_poll(self) -> bool:
"""Declare that this entity pushes its state to HA."""
return False
@property
def is_on(self) -> bool:
"""Return whether the sensor has a 'true' value."""
return self._state
def _handle_any_update(self, *args, **kwargs) -> None:
"""Determine whether an update event applies to this device."""
if args[0][KEY_DEVICE_ID] != self._controller.controller_id:
# For another device
return
# For this device
self._handle_update()
@abstractmethod
def _poll_update(self, data=None) -> bool:
"""Request the state from the API."""
pass
@abstractmethod
def _handle_update(self, *args, **kwargs) -> None:
"""Handle an update to the state of this sensor."""
pass
class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
"""Represent a binary sensor that reflects if the controller is online."""
def __init__(self, hass, controller):
"""Set up a new Rachio controller online binary sensor."""
super().__init__(hass, controller, poll=False)
self._state = self._poll_update(controller.init_data)
@property
def name(self) -> str:
"""Return the name of this sensor including the controller name."""
return "{} online".format(self._controller.name)
@property
def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'connectivity'
@property
def icon(self) -> str:
"""Return the name of an icon for this sensor."""
return 'mdi:wifi-strength-4' if self.is_on\
else 'mdi:wifi-strength-off-outline'
def _poll_update(self, data=None) -> bool:
"""Request the state from the API."""
if data is None:
data = self._controller.rachio.device.get(
self._controller.controller_id)[1]
if data[KEY_STATUS] == STATUS_ONLINE:
return True
elif data[KEY_STATUS] == STATUS_OFFLINE:
return False
else:
_LOGGER.warning('"%s" reported in unknown state "%s"', self.name,
data[KEY_STATUS])
def _handle_update(self, *args, **kwargs) -> None:
"""Handle an update to the state of this sensor."""
if args[0][KEY_SUBTYPE] == SUBTYPE_ONLINE:
self._state = True
elif args[0][KEY_SUBTYPE] == SUBTYPE_OFFLINE:
self._state = False
self.schedule_update_ha_state()

View File

@@ -8,7 +8,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.rainmachine import (
BINARY_SENSORS, DATA_RAINMACHINE, DATA_UPDATE_TOPIC, TYPE_FREEZE,
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
@@ -20,7 +20,8 @@ DEPENDENCIES = ['rainmachine']
_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 RainMachine Switch platform."""
if discovery_info is None:
return
@@ -33,7 +34,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
binary_sensors.append(
RainMachineBinarySensor(rainmachine, sensor_type, name, icon))
add_devices(binary_sensors, True)
async_add_devices(binary_sensors, True)
class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
@@ -70,16 +71,16 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice):
self.rainmachine.device_mac.replace(':', ''), self._sensor_type)
@callback
def update_data(self):
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, DATA_UPDATE_TOPIC,
self.update_data)
async_dispatcher_connect(
self.hass, SENSOR_UPDATE_TOPIC, self._update_data)
def update(self):
async def async_update(self):
"""Update the state."""
if self._sensor_type == TYPE_FREEZE:
self._state = self.rainmachine.restrictions['current']['freeze']

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

@@ -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)
@@ -59,7 +58,6 @@ class RPiGPIOBinarySensor(BinarySensorDevice):
def __init__(self, name, port, pull_mode, bouncetime, invert_logic):
"""Initialize the RPi binary sensor."""
# pylint: disable=no-member
self._name = name or DEVICE_DEFAULT_NAME
self._port = port
self._pull_mode = 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

@@ -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.3']
REQUIREMENTS = ['numpy==1.14.5']
_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

@@ -13,7 +13,6 @@ DEPENDENCIES = ['wemo']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument, 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

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

@@ -28,7 +28,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
devices.append(XiaomiDoorSensor(device, gateway))
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 in ['smoke', 'sensor_smoke']:
@@ -43,10 +47,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
data_key = 'channel_0'
devices.append(XiaomiButton(device, 'Switch', data_key,
hass, gateway))
elif model in ['86sw1', 'sensor_86sw1.aq1']:
elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']:
devices.append(XiaomiButton(device, 'Wall Switch', 'channel_0',
hass, gateway))
elif model in ['86sw2', 'sensor_86sw2.aq1']:
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)',
@@ -190,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):
@@ -330,7 +334,7 @@ class XiaomiButton(XiaomiBinarySensor):
click_type = 'both'
elif value == 'shake':
click_type = 'shake'
elif value == 'long_click':
elif value in ['long_click', 'long_both_click']:
return False
else:
_LOGGER.warning("Unsupported click_type detected: %s", value)

View File

@@ -187,8 +187,8 @@ class Switch(zha.Entity, BinarySensorDevice):
if args[0] == 0xff:
rate = 10 # Should read default move rate
self._entity.move_level(-rate if args[0] else rate)
elif command_id == 0x0002: # step
# Step (technically shouldn't change on/off)
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):

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

@@ -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,14 +76,40 @@ 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
events = service.events() # pylint: disable=no-member
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')
events = await hass.async_add_job(service.events)
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()
result = events.list(**params).execute()
items = result.get('items', [])

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.
@@ -97,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 {}
@@ -129,8 +129,7 @@ async def async_get_image(hass, entity_id, timeout=10):
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 = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
@@ -142,7 +141,7 @@ def async_setup(hass, config):
SCHEMA_WS_CAMERA_THUMBNAIL
)
yield from component.async_setup(config)
await component.async_setup(config)
@callback
def update_tokens(time):
@@ -154,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]
@@ -190,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."""
@@ -198,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)
@@ -216,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."""
@@ -265,6 +272,7 @@ class Camera(Entity):
"""Return bytes of camera image."""
raise NotImplementedError()
@callback
def async_camera_image(self):
"""Return bytes of camera image.
@@ -314,6 +322,7 @@ class Camera(Entity):
except asyncio.CancelledError:
_LOGGER.debug("Stream closed by frontend.")
response = None
raise
finally:
if response is not None:
@@ -388,8 +397,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)
@@ -403,11 +411,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()
@@ -418,12 +425,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,

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

@@ -13,7 +13,6 @@ from homeassistant.components.camera import Camera
DEPENDENCIES = ['bloomsky']
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up access to BloomSky cameras."""
bloomsky = hass.components.bloomsky

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

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

@@ -46,7 +46,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 generic IP Camera."""
async_add_devices([GenericCamera(hass, config)])

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

@@ -10,12 +10,13 @@ from datetime import timedelta
from homeassistant.components.camera import Camera
from homeassistant.components.neato import (
NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN)
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['neato']
SCAN_INTERVAL = timedelta(minutes=10)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Neato Camera."""
@@ -45,7 +46,6 @@ class NeatoCleaningMap(Camera):
self.update()
return self._image
@Throttle(timedelta(seconds=10))
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

@@ -29,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 = 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():
@@ -46,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

@@ -233,6 +233,7 @@ class ProxyCamera(Camera):
_LOGGER.debug("Stream closed by frontend.")
req.close()
response = None
raise
finally:
if response is not None:

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

@@ -67,8 +67,6 @@ async def async_setup_platform(hass, config, async_add_devices,
]
for cam in config.get(CONF_CAMERAS, []):
# https://github.com/PyCQA/pylint/issues/1830
# pylint: disable=stop-iteration-return
camera = next(
(dc for dc in discovered_cameras
if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None)

View File

@@ -0,0 +1,164 @@
"""
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:
_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
if self._model == MODEL_XIAOFANG:
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)
@@ -57,68 +56,72 @@ class YiCamera(Camera):
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)
@property
def name(self):
"""Return the name of this camera."""
return self._name
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 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 _get_latest_video_url(self):
"""Retrieve the latest video file from the customized Yi FTP server."""
from ftplib import FTP, error_perm
from aioftp import Client, StatusCodeError
ftp = FTP(self.host)
ftp = Client(loop=self.hass.loop)
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
await ftp.connect(self.host)
await ftp.login(self.user, self.passwd)
except StatusCodeError as err:
raise PlatformNotReady(err)
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 ftp.change_directory(self.path)
dirs = []
for path, attrs in await ftp.list():
if attrs['type'] == 'dir' and '.' not in str(path):
dirs.append(path)
latest_dir = dirs[-1]
await 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 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
await ftp.quit()
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

View File

@@ -49,7 +49,6 @@ def _get_image_url(hass, monitor, mode):
@asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the ZoneMinder cameras."""
cameras = []

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "No s'han trobat dispositius de Google Cast a la xarxa.",
"single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3 de Google Cast."
},
"step": {
"confirm": {
"description": "Voleu configurar Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "V s\u00edti nebyly nalezena \u017e\u00e1dn\u00e1 za\u0159\u00edzen\u00ed Google Cast.",
"single_instance_allowed": "Pouze jedin\u00e1 konfigurace Google Cast je nezbytn\u00e1."
},
"step": {
"confirm": {
"description": "Chcete nastavit Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,14 @@
{
"config": {
"abort": {
"no_devices_found": "Keine Google Cast Ger\u00e4te im Netzwerk gefunden."
},
"step": {
"confirm": {
"description": "M\u00f6chten Sie Google Cast einrichten?",
"title": ""
}
},
"title": ""
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "No Google Cast devices found on the network.",
"single_instance_allowed": "Only a single configuration of Google Cast is necessary."
},
"step": {
"confirm": {
"description": "Do you want to setup Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,14 @@
{
"config": {
"abort": {
"no_devices_found": "Nem tal\u00e1lhat\u00f3k Google Cast eszk\u00f6z\u00f6k a h\u00e1l\u00f3zaton."
},
"step": {
"confirm": {
"description": "Be szeretn\u00e9d \u00e1ll\u00edtani a Google Cast szolg\u00e1ltat\u00e1st?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Nessun dispositivo Google Cast trovato in rete.",
"single_instance_allowed": "\u00c8 necessaria una sola configurazione di Google Cast."
},
"step": {
"confirm": {
"description": "Vuoi configurare Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Googgle Cast \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
"single_instance_allowed": "Google Cast\uc758 \ub2e8\uc77c \uad6c\uc131 \ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
},
"step": {
"confirm": {
"description": "Google Cast\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Keng Google Cast Apparater am Netzwierk fonnt.",
"single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun Google Cast ass n\u00e9ideg."
},
"step": {
"confirm": {
"description": "Soll Google Cast konfigur\u00e9iert ginn?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Geen Google Cast-apparaten gevonden op het netwerk.",
"single_instance_allowed": "Er is slechts \u00e9\u00e9n configuratie van Google Cast nodig."
},
"step": {
"confirm": {
"description": "Wilt u Google Cast instellen?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Ingen Google Cast enheter funnet p\u00e5 nettverket.",
"single_instance_allowed": "Kun en enkelt konfigurasjon av Google Cast er n\u00f8dvendig."
},
"step": {
"confirm": {
"description": "\u00d8nsker du \u00e5 sette opp Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 Google Cast.",
"single_instance_allowed": "Wymagana jest tylko jedna konfiguracja Google Cast."
},
"step": {
"confirm": {
"description": "Czy chcesz skonfigurowa\u0107 Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Google Cast \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.",
"single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Google Cast."
},
"step": {
"confirm": {
"description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "V omre\u017eju niso najdene naprave Google Cast.",
"single_instance_allowed": "Potrebna je samo ena konfiguracija Google Cast-a."
},
"step": {
"confirm": {
"description": "Ali \u017eelite nastaviti Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Inga Google Cast-enheter hittades i n\u00e4tverket.",
"single_instance_allowed": "Endast en enda konfiguration av Google Cast \u00e4r n\u00f6dv\u00e4ndig."
},
"step": {
"confirm": {
"description": "Vill du konfigurera Google Cast?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "Kh\u00f4ng t\u00ecm th\u1ea5y thi\u1ebft b\u1ecb Google Cast n\u00e0o tr\u00ean m\u1ea1ng.",
"single_instance_allowed": "Ch\u1ec9 c\u1ea7n m\u1ed9t c\u1ea5u h\u00ecnh duy nh\u1ea5t c\u1ee7a Google Cast l\u00e0 \u0111\u1ee7."
},
"step": {
"confirm": {
"description": "B\u1ea1n c\u00f3 mu\u1ed1n thi\u1ebft l\u1eadp Google Cast kh\u00f4ng?",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 Google Cast \u8bbe\u5907\u3002",
"single_instance_allowed": "\u53ea\u6709\u4e00\u6b21 Google Cast \u914d\u7f6e\u662f\u5fc5\u8981\u7684\u3002"
},
"step": {
"confirm": {
"description": "\u60a8\u60f3\u8981\u914d\u7f6e Google Cast \u5417\uff1f",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,15 @@
{
"config": {
"abort": {
"no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u8a2d\u5099\u3002",
"single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Google Cast \u5373\u53ef\u3002"
},
"step": {
"confirm": {
"description": "\u662f\u5426\u8981\u8a2d\u5b9a Google Cast\uff1f",
"title": "Google Cast"
}
},
"title": "Google Cast"
}
}

View File

@@ -0,0 +1,30 @@
"""Component to embed Google Cast."""
from homeassistant.helpers import config_entry_flow
DOMAIN = 'cast'
REQUIREMENTS = ['pychromecast==2.1.0']
async def async_setup(hass, config):
"""Set up the Cast component."""
hass.data[DOMAIN] = config.get(DOMAIN, {})
return True
async def async_setup_entry(hass, entry):
"""Set up Cast from a config entry."""
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
entry, 'media_player'))
return True
async def _async_has_devices(hass):
"""Return if there are devices that can be discovered."""
from pychromecast.discovery import discover_chromecasts
return await hass.async_add_job(discover_chromecasts)
config_entry_flow.register_discovery_flow(
DOMAIN, 'Google Cast', _async_has_devices)

View File

@@ -0,0 +1,15 @@
{
"config": {
"title": "Google Cast",
"step": {
"confirm": {
"title": "Google Cast",
"description": "Do you want to setup Google Cast?"
}
},
"abort": {
"single_instance_allowed": "Only a single configuration of Google Cast is necessary.",
"no_devices_found": "No Google Cast devices found on the network."
}
}
}

View File

@@ -246,7 +246,8 @@ def set_swing_mode(hass, swing_mode, entity_id=None):
async def async_setup(hass, config):
"""Set up climate devices."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component = hass.data[DOMAIN] = \
EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
async def async_away_mode_set_service(service):
@@ -456,10 +457,19 @@ async 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 ClimateDevice(Entity):
"""Representation of a climate device."""
# pylint: disable=no-self-use
@property
def state(self):
"""Return the current state."""

View File

@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices)
# pylint: disable=import-error, no-name-in-module
# pylint: disable=import-error
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of an eQ-3 Bluetooth Smart thermostat."""

View File

@@ -13,21 +13,27 @@ from homeassistant.components.fritzbox import (
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE)
STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
DEPENDENCIES = ['fritzbox']
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE)
OPERATION_LIST = [STATE_HEAT, STATE_ECO]
OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON]
MIN_TEMPERATURE = 8
MAX_TEMPERATURE = 28
# special temperatures for on/off in Fritz!Box API (modified by pyfritzhome)
ON_API_TEMPERATURE = 127.0
OFF_API_TEMPERATURE = 126.5
ON_REPORT_SET_TEMPERATURE = 30.0
OFF_REPORT_SET_TEMPERATURE = 0.0
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Fritzbox smarthome thermostat platform."""
@@ -88,6 +94,9 @@ class FritzboxThermostat(ClimateDevice):
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._target_temperature in (ON_API_TEMPERATURE,
OFF_API_TEMPERATURE):
return None
return self._target_temperature
def set_temperature(self, **kwargs):
@@ -102,9 +111,13 @@ class FritzboxThermostat(ClimateDevice):
@property
def current_operation(self):
"""Return the current operation mode."""
if self._target_temperature == ON_API_TEMPERATURE:
return STATE_ON
if self._target_temperature == OFF_API_TEMPERATURE:
return STATE_OFF
if self._target_temperature == self._comfort_temperature:
return STATE_HEAT
elif self._target_temperature == self._eco_temperature:
if self._target_temperature == self._eco_temperature:
return STATE_ECO
return STATE_MANUAL
@@ -119,6 +132,10 @@ class FritzboxThermostat(ClimateDevice):
self.set_temperature(temperature=self._comfort_temperature)
elif operation_mode == STATE_ECO:
self.set_temperature(temperature=self._eco_temperature)
elif operation_mode == STATE_OFF:
self.set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
elif operation_mode == STATE_ON:
self.set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
@property
def min_temp(self):

View File

@@ -14,8 +14,7 @@ from homeassistant.core import DOMAIN as HA_DOMAIN
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice,
ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA,
DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE,
CONF_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF,
@@ -264,20 +263,20 @@ class GenericThermostat(ClimateDevice):
@property
def min_temp(self):
"""Return the minimum temperature."""
# pylint: disable=no-member
if self._min_temp:
return self._min_temp
return DEFAULT_MIN_TEMP
# get default temp from super class
return super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
# pylint: disable=no-member
if self._max_temp:
return self._max_temp
return DEFAULT_MAX_TEMP
# Get default temp from super class
return super().max_temp
@asyncio.coroutine
def _async_sensor_changed(self, entity_id, old_state, new_state):

View File

@@ -34,7 +34,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
# pylint: disable=unused-variable
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the heatmiser thermostat."""
from heatmiserV3 import heatmiser, connection

View File

@@ -0,0 +1,130 @@
"""
Support for Homekit climate devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.homekit_controller/
"""
import logging
from homeassistant.components.homekit_controller import (
HomeKitEntity, KNOWN_ACCESSORIES)
from homeassistant.components.climate import (
ClimateDevice, STATE_HEAT, STATE_COOL, STATE_IDLE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
from homeassistant.const import TEMP_CELSIUS, STATE_OFF, ATTR_TEMPERATURE
DEPENDENCIES = ['homekit_controller']
_LOGGER = logging.getLogger(__name__)
# Map of Homekit operation modes to hass modes
MODE_HOMEKIT_TO_HASS = {
0: STATE_OFF,
1: STATE_HEAT,
2: STATE_COOL,
}
# Map of hass operation modes to homekit modes
MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Homekit climate."""
if discovery_info is not None:
accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']]
add_devices([HomeKitClimateDevice(accessory, discovery_info)], True)
class HomeKitClimateDevice(HomeKitEntity, ClimateDevice):
"""Representation of a Homekit climate device."""
def __init__(self, *args):
"""Initialise the device."""
super().__init__(*args)
self._state = None
self._current_mode = None
self._valid_modes = []
self._current_temp = None
self._target_temp = None
def update_characteristics(self, characteristics):
"""Synchronise device state with Home Assistant."""
# pylint: disable=import-error
from homekit import CharacteristicsTypes as ctypes
for characteristic in characteristics:
ctype = characteristic['type']
if ctype == ctypes.HEATING_COOLING_CURRENT:
self._state = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
if ctype == ctypes.HEATING_COOLING_TARGET:
self._chars['target_mode'] = characteristic['iid']
self._features |= SUPPORT_OPERATION_MODE
self._current_mode = MODE_HOMEKIT_TO_HASS.get(
characteristic['value'])
self._valid_modes = [MODE_HOMEKIT_TO_HASS.get(
mode) for mode in characteristic['valid-values']]
elif ctype == ctypes.TEMPERATURE_CURRENT:
self._current_temp = characteristic['value']
elif ctype == ctypes.TEMPERATURE_TARGET:
self._chars['target_temp'] = characteristic['iid']
self._features |= SUPPORT_TARGET_TEMPERATURE
self._target_temp = characteristic['value']
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
characteristics = [{'aid': self._aid,
'iid': self._chars['target_temp'],
'value': temp}]
self.put_characteristics(characteristics)
def set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
characteristics = [{'aid': self._aid,
'iid': self._chars['target_mode'],
'value': MODE_HASS_TO_HOMEKIT[operation_mode]}]
self.put_characteristics(characteristics)
@property
def state(self):
"""Return the current state."""
# If the device reports its operating mode as off, it sometimes doesn't
# report a new state.
if self._current_mode == STATE_OFF:
return STATE_OFF
if self._state == STATE_OFF and self._current_mode != STATE_OFF:
return STATE_IDLE
return self._state
@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temp
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temp
@property
def current_operation(self):
"""Return current operation ie. heat, cool, idle."""
return self._current_mode
@property
def operation_list(self):
"""Return the list of available operation modes."""
return self._valid_modes
@property
def supported_features(self):
"""Return the list of supported features."""
return self._features
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS

View File

@@ -136,7 +136,6 @@ class KNXClimate(ClimateDevice):
"""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

@@ -17,7 +17,7 @@ from homeassistant.components.climate import (
PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO,
ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AUX_HEAT)
SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
from homeassistant.components.mqtt import (
@@ -70,6 +70,9 @@ CONF_SWING_MODE_LIST = 'swing_modes'
CONF_INITIAL = 'initial'
CONF_SEND_IF_OFF = 'send_if_off'
CONF_MIN_TEMP = 'min_temp'
CONF_MAX_TEMP = 'max_temp'
SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema)
PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic,
@@ -116,12 +119,19 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float)
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the MQTT climate devices."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
template_keys = (
CONF_POWER_STATE_TEMPLATE,
CONF_MODE_STATE_TEMPLATE,
@@ -181,19 +191,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE))
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_MIN_TEMP),
config.get(CONF_MAX_TEMP))
])
class MqttClimate(MqttAvailability, ClimateDevice):
"""Representation of a demo climate device."""
"""Representation of an MQTT climate device."""
def __init__(self, hass, name, topic, value_templates, qos, retain,
mode_list, fan_mode_list, swing_mode_list,
target_temperature, away, hold, current_fan_mode,
current_swing_mode, current_operation, aux, send_if_off,
payload_on, payload_off, availability_topic,
payload_available, payload_not_available):
payload_available, payload_not_available,
min_temp, max_temp):
"""Initialize the climate device."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
@@ -219,6 +232,8 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._send_if_off = send_if_off
self._payload_on = payload_on
self._payload_off = payload_off
self._min_temp = min_temp
self._max_temp = max_temp
@asyncio.coroutine
def async_added_to_hass(self):
@@ -619,3 +634,13 @@ class MqttClimate(MqttAvailability, ClimateDevice):
support |= SUPPORT_AUX_HEAT
return support
@property
def min_temp(self):
"""Return the minimum temperature."""
return self._min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._max_temp

View File

@@ -26,9 +26,8 @@ DICT_MYS_TO_HA = {
'Off': STATE_OFF,
}
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE)
FAN_LIST = ['Auto', 'Min', 'Normal', 'Max']
OPERATION_LIST = [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
async def async_setup_platform(
@@ -39,13 +38,24 @@ async def async_setup_platform(
async_add_devices=async_add_devices)
class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice):
"""Representation of a MySensors HVAC."""
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
features = SUPPORT_OPERATION_MODE
set_req = self.gateway.const.SetReq
if set_req.V_HVAC_SPEED in self._values:
features = features | SUPPORT_FAN_MODE
if (set_req.V_HVAC_SETPOINT_COOL in self._values and
set_req.V_HVAC_SETPOINT_HEAT in self._values):
features = (
features | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
else:
features = features | SUPPORT_TARGET_TEMPERATURE
return features
@property
def assumed_state(self):
@@ -103,7 +113,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
@property
def operation_list(self):
"""List of available operation modes."""
return [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT]
return OPERATION_LIST
@property
def current_fan_mode(self):
@@ -113,7 +123,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
@property
def fan_list(self):
"""List of available fan modes."""
return ['Auto', 'Min', 'Normal', 'Max']
return FAN_LIST
async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""

View File

@@ -32,16 +32,22 @@ NEST_MODE_HEAT_COOL = 'heat-cool'
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Nest thermostat."""
if discovery_info is None:
return
"""Set up the Nest thermostat.
No longer in use.
"""
async def async_setup_entry(hass, entry, async_add_devices):
"""Set up the Nest climate device based on a config entry."""
temp_unit = hass.config.units.temperature_unit
all_devices = [NestThermostat(structure, device, temp_unit)
for structure, device in hass.data[DATA_NEST].thermostats()]
thermostats = await hass.async_add_job(hass.data[DATA_NEST].thermostats)
add_devices(all_devices, True)
all_devices = [NestThermostat(structure, device, temp_unit)
for structure, device in thermostats]
async_add_devices(all_devices, True)
class NestThermostat(ClimateDevice):

View File

@@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
netatmo = hass.components.netatmo
device = config.get(CONF_RELAY)
import lnetatmo
import pyatmo
try:
data = ThermostatData(netatmo.NETATMO_AUTH, device)
for module_name in data.get_module_names():
@@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
module_name not in config[CONF_THERMOSTAT]:
continue
add_devices([NetatmoThermostat(data, module_name)], True)
except lnetatmo.NoDevice:
except pyatmo.NoDevice:
return None
@@ -168,8 +168,8 @@ class ThermostatData(object):
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Call the NetAtmo API to update the data."""
import lnetatmo
self.thermostatdata = lnetatmo.ThermostatData(self.auth)
import pyatmo
self.thermostatdata = pyatmo.ThermostatData(self.auth)
self.target_temperature = self.thermostatdata.setpoint_temp
self.setpoint_mode = self.thermostatdata.setpoint_mode
self.current_temperature = self.thermostatdata.temp

View File

@@ -19,7 +19,7 @@ from homeassistant.components.climate import (
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
SUPPORT_ON_OFF, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
SUPPORT_ON_OFF)
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -246,13 +246,13 @@ class SensiboClimate(ClimateDevice):
def min_temp(self):
"""Return the minimum temperature."""
return self._temperatures_list[0] \
if self._temperatures_list else DEFAULT_MIN_TEMP
if self._temperatures_list else super().min_temp
@property
def max_temp(self):
"""Return the maximum temperature."""
return self._temperatures_list[-1] \
if self._temperatures_list else DEFAULT_MAX_TEMP
if self._temperatures_list else super().max_temp
@property
def unique_id(self):

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