Compare commits

..

227 Commits

Author SHA1 Message Date
Paulus Schoutsen a8b32edc8e Merge pull request #6509 from home-assistant/release-0-40
0.40
2017-03-11 11:08:40 -08:00
Greg Dowling 4a5b9db394 Discovery is a dict rather than an array. (#6525) 2017-03-11 11:06:05 -08:00
Greg Dowling 9c23178457 Append vera device id to entity id - but not name. (#6523)
* Append vera device id to entity id - but not name.

* Tidy.

* Tidy.

* Tidy after review.

* Re-order.
2017-03-11 11:06:05 -08:00
Paulus Schoutsen 764d31efcb Remove mint finance sensor (#6522) 2017-03-11 11:06:05 -08:00
Anders Melchiorsen fc90ccea36 Fix colortemp conversion for osramlightify (#6516)
* Fix colortemp conversion for osramlightify

Copied from the LIFX fix in 75df4be733.

* Fix style

* Updates from review

@armills:

While we're doing cleanup here, can you just change self._brightness,
self._rgb, self._name, self._temperature, and self._state assignments in
__init__ to None? These will get overwritten when self.update() is called, so
it's safer/cleaner to initialize them to None since it shouldn't matter if
everything is working.
2017-03-11 11:06:05 -08:00
Martin Hjelmare aab63ea22a Fix mysensors gateway windows setup (#6500) 2017-03-11 11:06:05 -08:00
Paulus Schoutsen c8b2ba6559 Version bump to 0.40 2017-03-10 23:09:44 -08:00
Paulus Schoutsen 1496f1a570 Update frontend 2017-03-10 22:52:16 -08:00
Pascal Vizeli 0ef1c3af34 Android webcam better error handling / pump library 0.4 (#6518) 2017-03-10 23:11:16 +01:00
Pascal Vizeli b0a2909835 Bugfix rpi_rf cleanup (#6513)
Add an optional extended description…
2017-03-10 14:57:03 +01:00
Craig J. Ward 0f1cad24ce Insteon lib (#6505)
* use lib with caching to reduce collisions

* use 0.43

* change requirements

* update the  lib to 0.44

* update req

* fix typo

* just keep checking

* just keep checking - switch

* use 0.45 with file cache

* requirements

* Update requirements_all.txt

* Update insteon_local.py

* Update requirements_all.txt

* Update insteon_local.py

* update library

* fix lint
2017-03-10 11:17:09 +01:00
Caleb 24630b1ebe Update to Pyunifi2.0 (#6490)
* Updated pyUnifi

* Missing comma

* Security opt-out, not opt-in

* Adjust minimal values

* Update to pyUnifi 2.0
2017-03-10 11:16:51 +01:00
Pascal Vizeli bdaae6f844 Bugfix android camera autodiscovery settings (#6510)
Add an optional extended description…
2017-03-10 10:14:05 +01:00
Paulus Schoutsen edd96c2b04 Merge remote-tracking branch 'origin/master' into dev 2017-03-09 21:32:04 -08:00
Robbie Trencheny 11c4d3892f Merge pull request #6001 from jumpkick/patch-2
Improvements for WeMo Insight switches
2017-03-09 21:14:33 -08:00
Robbie Trencheny 75817ad46d Merge pull request #6361 from GreenTurtwig/dev
Updated to catch timeout error
2017-03-09 20:39:20 -08:00
Robbie Trencheny 780cdd5f90 Merge pull request #6389 from Deinara/discord_connect_fix
Discord connect fix
2017-03-09 19:57:29 -08:00
Robbie Trencheny d70eaeb118 Merge pull request #6407 from jvolkman/patch-1
Small typo fix in setup_docker_prereqs
2017-03-09 19:55:57 -08:00
Robbie Trencheny 9e9c6d0184 Make states constants 2017-03-09 19:55:18 -08:00
Robbie Trencheny 2cdc0febf5 Merge pull request #6494 from armills/kodi-command-exceptions
Catch exceptions on kodi commands
2017-03-09 19:50:53 -08:00
Robbie Trencheny c721ec7ab0 Merge pull request #6507 from amelchio/longer-light-transitions
Increase upper limit on light transitions (#2843)
2017-03-09 19:50:22 -08:00
Robbie Trencheny 4e63e8328e Merge pull request #6506 from happyleavesaoc/enhance-moon
more moon states
2017-03-09 19:45:40 -08:00
Robbie Trencheny 8f2009a187 Merge pull request #6488 from pvizeli/pydroid-ipcam
Android IP Cam support
2017-03-09 19:41:23 -08:00
Adam Mills 529eee994b Include functools.wraps 2017-03-09 19:52:45 -05:00
Christiaan Blom 3f7dd7ed9a Added an extra space 2017-03-10 00:33:29 +01:00
Christiaan Blom b952cfe705 Added pylint ignore message and re-added .close() 2017-03-10 00:21:26 +01:00
Anders Melchiorsen b9cf4df557 Increase upper limit on light transitions (#2843)
This increases the global limit to 6535 seconds (1h48m55s) because that is
supported by all light platforms.

Some platforms support even longer transition times so the limit should
actually be platform specific.
2017-03-09 23:59:35 +01:00
happyleaves 7892297240 more moon states 2017-03-09 16:54:04 -05:00
Pascal Vizeli 20fcd1f0e2 Bugfix mqtt socket memory error (#6501)
* Bugfix mqtt socket memory error

* Fix tests

* Fix lint
2017-03-09 06:31:43 -08:00
pvizeli 8bbf13ef9f Fix lint 2017-03-09 15:10:39 +01:00
Paulus Schoutsen 855756cb2a Add first pass at Z-Wave light tests (#6483)
* Add first pass at Z-Wave light tests

* Remove unused SIGNAL_VALUE

* Lint

* Update test_init.py
2017-03-09 14:35:04 +01:00
pvizeli bbcfb9158a fix lint 2017-03-09 12:03:08 +01:00
pvizeli bcd4def0ae pump version 0.3 / make a lot of improvments 2017-03-09 12:00:50 +01:00
Robbie Trencheny ddc260b628 Add required changes to support MQTT discovery that I previously missed. See #6481. 2017-03-08 23:57:54 -08:00
Robbie Trencheny 06bc062221 MQTT should load the provided platform not DOMAIN
See #6481
2017-03-08 23:53:17 -08:00
Pascal Vizeli 21f3b62d09 fix coverage 2017-03-09 01:03:15 +01:00
Pascal Vizeli 185ccc4fc4 Fix some things 2017-03-09 01:00:57 +01:00
Adam Mills 2d337fd29a Catch exceptions on kodi commands 2017-03-08 15:54:51 -05:00
Paulus Schoutsen c625e219c7 Not always asume manufacturername is present (#6484) 2017-03-08 08:57:38 -08:00
pvizeli b1736994b7 fix lint 2017-03-08 17:57:22 +01:00
pvizeli 21feff5fd8 add available 2017-03-08 17:52:49 +01:00
pvizeli 93118fcade Android IP Cam support 2017-03-08 17:48:55 +01:00
ArrayLabs 5e1c74b430 Update pymyq requirement (#6486)
* update pymyq to 0.0.7

* update pymyq requirement

Adds additional state for stopped door.
2017-03-08 16:47:43 +01:00
Reed Riley 3d3a0a7a4f Improved iCloud 2FA support. (#5984) 2017-03-08 12:07:34 +01:00
Colin O'Dell fdd1957750 Allow configurable conditions for Pi-Hole sensor (#6465)
* Allow configurable conditions for Pi-Hole sensor

* Include all three conditions by default

* Share Pi-Hole API data across all sensors; eliminate redundant API calls
2017-03-07 23:20:30 -08:00
Robbie Trencheny 2b97449d98 Expand MQTT lights (#6481)
* Add effect support to MQTT Light

* Use effect state topic for supported_features

* Dont use rainbow as default color

* Add color_temp support to MQTT JSON Light

* Add effect to MQTT JSON light

* Support lights in MQTT discovery

* Allow discovered devices to set their platform

* Add white value support to MQTT Light

* Add white value support to MQTT JSON Light

* Remove blank line

* Add color_temp support to MQTT Template light

* Add white value support to MQTT Template Light

* Remove unused SUPPORT_MQTT_TEMPLATE and stale unused flash and transition code from MQTT Template

* Add XY Color to MQTT Light Platform

* Fix syntax

* Fix more syntax errors

* Revert "Remove unused SUPPORT_MQTT_TEMPLATE and stale unused flash and transition code from MQTT Template"

This reverts commit c03798cb63.

* MQTT Template supports flash and transition but doesnt allow templating of the values

* Add XY color support to MQTT JSON

* Proper variable names

* Only allow whitelisted MQTT platforms to be loaded via MQTT Discovery

* Minor tweaks.
2017-03-07 23:01:36 -08:00
Pascal Vizeli c937a7bcb0 Add support for remove services / Reload script support (#6441)
* Add support for remove services / Reload script support

* Reload support for scripts

* Add more unittest for services

* Add unittest for script reload

* Address paulus comments
2017-03-07 22:51:34 -08:00
Pascal Vizeli e7f442d66b Add dispatcher camera for internal image. (#6471)
* Add dispatcher camera for internal image.

* fix lint

* Add unittest

* Update dispatcher.py
2017-03-07 22:37:23 -08:00
siebert 2c5d3387f2 Fix wake_on_lan ping for Linux. (#6480) 2017-03-07 20:57:35 -08:00
Paulus Schoutsen bb4f23f8e7 Add warning for slow platforms/components (#6467)
* Add warning for slow platforms/components

* Add test for slow component setup.

* Add test for slow platform setup

* Fix tests on Py34
2017-03-07 20:31:57 -08:00
Kevin Fronczak 629b2e81ba Support for Blink Camera System (#6444)
* Passing pep8, no tests yet

* Fixed some issues with the request throttling

* Removed ability to set throttle time because it was causing more issues than it was worth

* Added blink to .coveragerc

* Changed blinkpy version

* Removed global var, fixed per PR requests

* Added services for camera, migrated switch to binary_sensor

* Added schema for service, fixed naming, removed unused function
2017-03-07 23:26:53 +01:00
Johan Bloemberg 3508f74fb2 Remove connection status state. (#6475)
Current implementation of connection status doesn't follow convention and is not properly configurable. Might be added again in the future as a full fledged entity or some other way.

For now users can rely on error logging to determine connection status.
2017-03-07 23:20:27 +01:00
Kevin Siml d16bc632da fix issue (#6470)
* fix issue

fix issue: https://community.home-assistant.io/t/error-in-new-notification-pushsafer/13308

* Update pushsafer.py

* Update requirements_all.txt

* Update pushsafer.py

* Update pushsafer.py

* Update pushsafer.py
2017-03-07 21:18:28 +01:00
Paulus Schoutsen 44d4987536 Allow testing against uvloop (#6468) 2017-03-07 10:11:41 +01:00
Paulus Schoutsen 470702261a Upgrade netdisco to 0.9.2 (#6466) 2017-03-06 22:35:49 -08:00
Josh Anderson 5fb7aa212b Send a logo with webostv notifications (#6380)
* Update to pylgtv 0.1.4

* Send icon with webostv notifications

Default to the homeassistant logo, but allow customizing it on the
component and for individual notifications
2017-03-06 20:56:31 -08:00
Barry Williams 9522fe3a92 Bumped version number for supporting lib (#6462) 2017-03-06 20:38:33 -08:00
Markus Peter ff3c90fb80 KWB Easyfire support (#6018)
* KWB Easyfire Support

* requirements, coverage

* Initialization fun

* lint

* requirements bump

* lint

* Second best validation ...

* changes

* reworked validation
2017-03-06 17:37:29 +01:00
Paulus Schoutsen 90ad54da7d Shorten recorder connection init (#6432)
* Wait up to 9 seconds

* Set number of recorder retries to 8

* Do not sleep when reporting last connection error if no retries left

* Make sure we clean up old engine if connection is retrying

* Update __init__.py
2017-03-06 12:20:12 +01:00
Marcelo Moreira de Mello 2baa838ba7 Added unittest for Ring sensor (#6447)
Add an optional extended description…
2017-03-06 12:15:08 +01:00
Pascal Vizeli a8add06a40 Bugfix samsungtv discovery (#6438) 2017-03-05 23:52:15 +01:00
Dennis de Greef 1b23b32817 Use bundled certificates if port matches mqtts (#6429)
* Use bundled certificates if port matches mqtts

* Move import requests.certs to top, since it's used in more places

* Add happy and non-happy path tests for default certificate bundle on mqtts port
2017-03-05 14:08:29 -08:00
Andrey eaaa0442e2 Add a Z-wave workaround to do full refresh on update (#6403)
* Add Zwave refresh services

* services file

* Use dispatcher

* Add zwave prefix to signal

* Add a Z-wave workaround to do full refresh on update
2017-03-05 12:55:52 -08:00
Job Vermeulen bc9f2d21c4 Tado device_tracker exception when mobile device has geofencing enabled but location is currently unknown. (#6401) 2017-03-05 21:38:14 +01:00
Adam Mills 1a139234af Revert "Use dynamic port allocation for tests" (#6436) 2017-03-05 21:14:21 +01:00
Andrey d5435cf066 Rename _scheduled_update to _update_scheduled (#6434) 2017-03-05 10:53:47 -08:00
Andrey 46ec6d6dce Delay zwave updates for 100ms to group them. (#6420)
* Add Zwave refresh services

* services file

* Use dispatcher

* Add zwave prefix to signal

* Delay zwave updates for 100ms to group them.

* Fixes

* lint

* Access _scheduled_update from loop thread only.

* More async

* Some optimizations

* Fix
2017-03-05 09:29:59 -08:00
Anders Melchiorsen 660e777f01 Ignore deleted mails in IMAP unread count (#6394) (#6395)
Message deletion in IMAP is a two step process: first delete, then expunge.
Deleting a message just sets a flag that usually makes the mail client hide
the message. It is the expunge that actually removes the message.

Thus, exclude the deleted messages so that the unread count matches up with
that of most mail clients.
2017-03-05 08:15:25 -08:00
Igor Shults de038bae65 Don't log username and password in camera url (#6390)
* Don't log username and password in camera url

* Attempt fix of tox issues

* Attempt to fix indentation issue
2017-03-05 08:07:09 -08:00
Anders Melchiorsen 7774f0ae53 Set new color before turning LIFX bulbs on (#6402)
A LIFX bulb maintains its previous color even when the light is off.
For example, if the previous color is blue and the bulb is turned on
and then set to a red color, it will transition through purple colors.

After this commit, the target color is set while the bulb is still
turned off. This overrides the previous color and brightness that the
bulb remembered. The light is then turned on with the requested
transition duration.

For the example, this gives the expected result of only going through
red colors.
2017-03-05 11:11:33 +01:00
Paulus Schoutsen 7655b6271d Better restore_state warnings (#6418) 2017-03-05 10:54:49 +01:00
Paulus Schoutsen 10bf659773 Fix unnecessary warning for ip bans.yaml (#6417) 2017-03-05 10:53:21 +01:00
Paulus Schoutsen e8a22cb4a8 Tweak recorder/restore_state (#6412)
* Tweak recorder/restore_state

* Lint
2017-03-05 10:52:08 +01:00
Paulus Schoutsen 2650c73a89 Split bootstrap into bs + setup (#6416)
* Split bootstrap into bs + setup

* Lint
2017-03-05 10:41:54 +01:00
Thibault Cohen bdf948d866 Add Mint finance sensor (#6132)
* Add Mint finance sensor

* Add retry

* Fix PR comments

* Upgrade mintapi version

* Update mint_finance.py

* Doc tweak

* Update mint_finance.py
2017-03-05 00:08:58 -08:00
Marcelo Moreira de Mello 928e025910 Added sensors to support Ring.com devices (#6419) 2017-03-05 00:03:00 -08:00
Teemu R 96aae1292b switch.tplink: catch exceptions coming from pyHS100 to avoid flooding the logs when the plug is not available (#6400) 2017-03-05 08:44:34 +01:00
Pascal Vizeli c0bf3d7f32 Restore flow on device_tracker platform (#6374)
* Restore flow on device_tracker platform

* fix flow

* fix lint
2017-03-05 08:06:53 +01:00
Paulus Schoutsen 307514e3a7 Prevent more I/O in apns (#6413) 2017-03-04 19:57:04 -08:00
Paulus Schoutsen b939626497 Fix tests no internet (#6411)
* Fix honeywell tests without internet

* Fix device tracker without internet

* Fix MFI using internet during tests

* Remove I/O from apns tests
2017-03-04 17:15:20 -08:00
Johann Kellerman 1522e67351 Restore for automation entities (#6254)
* Restore for automation entities

* coroutine

* no clue what i'm doing now

* Still passes nicely in py 3.4
2017-03-04 15:19:01 -08:00
Pascal Vizeli 8232f1ef65 Cleanup async handling (#6388)
* Cleanups unneeded blocks

* Cleanup bootstrap

* dedicated update_ha_state

* Fix imap_email_content

* fx tests

* Fix lint & spell
2017-03-04 15:10:36 -08:00
Jeremy Volkman 78f5a8a6f8 Small typo fix in setup_docker_prereqs 2017-03-04 12:39:25 -08:00
Daniel Høyer Iversen 3044aecbe9 flux led lib (#6404) 2017-03-04 21:33:24 +01:00
siebert a5081ac307 Fix wake_on_lan for german version of Windows 10 (#6397) (#6398) 2017-03-04 09:58:01 -08:00
Lev Aronsky f396a4593e Add keep-alive feature to the generic thermostat (#6040)
* Add keep-alive feature to the generic thermostat

* Comply with maximum line lengths

* Added tests for the keep-alive functionality
2017-03-04 09:42:43 -08:00
Andrey aac9f972cf Add Zwave refresh services (#6377)
* Add Zwave refresh services

* services file

* Use dispatcher

* Add zwave prefix to signal
2017-03-04 09:13:24 -08:00
Thibault Cohen aaa0944595 Add multi contracts support for Hydroquebec (#6392) 2017-03-04 09:11:58 +01:00
Paulus Schoutsen 6cca127bbf Merge pull request #6384 from home-assistant/release-0-39-3
0.39.3
2017-03-03 19:24:29 -08:00
Christiaan Blom c1f3ce78e1 Edit docstring 2017-03-04 01:48:47 +01:00
Christiaan Blom 887b53b794 Changes for Travis bot. Unused variable 'on_ready' will likely remain reported 2017-03-04 01:31:19 +01:00
Christiaan Blom a444df3fde tweaks 2017-03-04 01:03:10 +01:00
Christiaan Blom b038a1650e Resolved issue #5688 2017-03-03 23:15:03 +01:00
joe248 483556ac5b Comed Hourly Pricing sensor (#6378)
* Add ComEd RRTP price sensor

* Update wording to reflect ComEd's naming change from 'RRTP' to 'Hourly Pricing'

* Changed name of sensor source file

* Cleanup based on requested changes

* More cleanup

* small cleanups
2017-03-03 23:14:22 +01:00
Paulus Schoutsen 3e9e388745 Version bump to 0.39.3 2017-03-03 13:15:07 -08:00
Colin O'Dell ab42acf4d7 Don't initialize components which have already been discovered (#6381)
* Don't initialize components which have already been discovered (fixes #5588)

* Don't log that we've found a service unless we know it's not a duplicate

* Encode discovery data hash with JSON

This also solves the issue of trying to hash non-hashable objects like dicts

* Add test for duplicate device discovery
2017-03-03 13:13:04 -08:00
Colin O'Dell 0489ae53c4 Don't initialize components which have already been discovered (#6381)
* Don't initialize components which have already been discovered (fixes #5588)

* Don't log that we've found a service unless we know it's not a duplicate

* Encode discovery data hash with JSON

This also solves the issue of trying to hash non-hashable objects like dicts

* Add test for duplicate device discovery
2017-03-03 13:11:40 -08:00
John Mihalic 35fcc299c0 Update Hikvision Binary Sensors to latest library, remove pyDispatcher (#6231)
* Update pyHik version, remove pyDispatcher in favor of callbacks

* Fix naming

* Fix lint blank line

* Move stream thread start to HOMEASSISTANT_START event

* Bump library version to cleanup shutdown

* Fix requirements
2017-03-03 15:11:30 +01:00
Valentin Alexeev 568c549353 Update pwaqi to 3.0 to use public API (#6376)
The underlying PWAQI library version 3.0 is now using public API to
access AQICN data.
2017-03-03 14:50:54 +01:00
Paulus Schoutsen edf130b341 Z-Wave prevent I/O event loop (#6369)
* Prevent Z-Wave I/O in event loop

* Move value_handler to util class.

* Add docstring
2017-03-03 14:47:59 +02:00
Pascal Vizeli ed9e93c29f Migrate mqtt tracker and arwn sensor to async / cleanup owntrack (#6373)
* Migrate mqtt tracker and arwn sensor to async / cleanup owntrack

* Fix tests / lint
2017-03-03 12:09:10 +01:00
Pascal Vizeli 55f8ec8866 Fix possibility that have multible topic subscribe mqtt (#6372) 2017-03-03 10:05:52 +01:00
Pascal Vizeli 3e70154695 OwnTrack Async (#6363)
* Migrate owntrack to async

* fix tests
2017-03-03 09:23:58 +01:00
Andrey aa17481c94 Add Z-Wave battery level as a sensor. (#6341) 2017-03-03 09:19:06 +02:00
happyleavesaoc b53bc24a63 twilio component (#6348)
* twilio component

* add http dependency to twilio

* fire->async_fire
2017-03-03 08:14:51 +01:00
Johann Kellerman fbd0bf77c7 [recorder] Catch more startup errors #6179 (#6192)
* [recorder] Catch more startup errors #6179

* Rebase on new recorder
2017-03-02 22:44:52 -08:00
Jose Juan Montes 4da2156ebf Return None instead of raising ValueException from as_timestamp template function. (#6155) 2017-03-02 22:18:01 -08:00
Open Home Automation 8a67fcfee3 Added IPv4 data collector (#6304)
* Added IPv4 data collector

* Formatting

* Bugfix: data is in kBit/s not kByte/s
2017-03-02 22:16:50 -08:00
Pascal Vizeli 08f9793175 Restore for input_slider (#6360) 2017-03-02 08:36:26 -08:00
Pascal Vizeli a5b2fc9759 Bugfix new async_add_devices function (#6362) 2017-03-02 17:27:45 +01:00
Micha LaQua 3fa8aff78e snmp: upgrade pysnmp to 4.3.4 (#6359)
* snmp: upgrade pysnmp to 4.3.4

fixes https://github.com/home-assistant/home-assistant/issues/6238

* snmp: v4.3.4: add missing definition changes
2017-03-02 16:12:44 +01:00
Rowan 09ff9cb08e Updated to catch timeout error 2017-03-02 14:58:35 +00:00
Jan Losinski c32300a386 Bump limitlessled dependency to 1.0.5. (#6334)
This fixes issue #6295.
2017-03-02 14:18:44 +01:00
Pascal Vizeli 55dc483c91 Template switch change flow / add restore (#6356)
* Template switch change flow / add restore

* fix tests

* fix binary_sensor template
2017-03-02 14:09:53 +01:00
Andrey 597ae2e716 Zwave: Add remove/replace failed node services. (#6248)
* Zwave: Add remove/replace failed node services.

* Fix text
2017-03-02 12:36:40 +01:00
Pascal Vizeli 50887e7e2c Move dispatcher out of init. (#6355) 2017-03-02 10:20:57 +01:00
Alan Fischer 8743f23f13 Fix calendar authentication text, and handle calendar events without summaries. (#6337)
* Fixed google authorization text

* Let calendar handle events without a summary
2017-03-02 00:22:38 -08:00
Jeff Wilson 72fe50bef6 Fix command sudo not found error in dev Dockerfile (#6346) 2017-03-02 00:14:20 -08:00
Pierre Ståhl bae6333c26 Use push updates in Apple TV (#6323)
* Use push updates in Apple TV

* Fix review comments
2017-03-02 00:12:55 -08:00
martinfrancois a08539d88d Added support for multiple codes executed in a row (#5908)
* Added support for multiple codes executed in a row

now codes can be specified either by simply providing a single code, which will then be sent like usual, or multiple codes can be executed in a row, specified in a comma delimited format in the configuration.yaml. For example: 111111,222222,333333,444444 would mean 111111 would be sent first, followed by 222222 and 333333 and 444444.

* rpi_rf: added line breaks to not exceed 79 characters per line

* include validation for correct formatting of codes

added regex which only allows either a single number (like 1252456245) or a sequence of commas followed by another number.

* added line breaks to not exceed 79 characters per line

* fix for 'continuation line under-indented for visual indent'

* another try at 'continuation line under-indented for visual indent'

* changed from regex to list for easier maintainability

* removed unnecessary splitting of strings
2017-03-02 00:10:49 -08:00
Adam Mills bf7aecce90 Use dynamic ports for test instances (#6232) 2017-03-02 00:07:50 -08:00
Paulus Schoutsen e2aa024a05 Update coveragerc 2017-03-02 00:06:26 -08:00
dramamoose edd5db296d Update Formulas in Convert XY to RGB (#6322)
* Update to Current RGB D65 Conversion

As per Philips Hue https://developers.meethue.com/documentation/color-conversions-rgb-xy

* Update the source of the XYZ to RGB formulas

* Fix Whitespace

* Update Whitespace

* Update Tests for new Formulas

* Update Tests

* Update XY_Brightness_to_hsv tests

* Update test_color.py
2017-03-02 08:54:45 +01:00
Duoxilian e14d6f11c6 Additional support for ecobee hold mode (#6258)
* Integrate suggestion in #5590 by nordlead2005. This change has been
sitting in limbo for over a month, but it is a good idea. I don't
mean to step on nordlead2005's toes, but we need to make progress.

* Use defined constant for TEMPERATURE_HOLD

* Integrate handling of vacation into hold mode. Canceling vacation
hold requires an update to the external pyecobee library. Creation
of vacation is not supported (it would be straightforward in the code,
but a complex user interface would be required, similar to what is
now done in the ecobee thermostat).

* Add capability to retrieve list of defined climates from ecobee.

* The mode() method used to return the system mode in internal
representation. However, the user sees a different notation in
the ecobee thermostat. Seeing some internal name is particularly
weired with user-defined climates, where these are named "smart1",
"smart2", etc., instead of the name the user has defined. Return
the user-defined name instead. This change might break some user
interfaces but is easily remedied (e.g., use "Away" instead of
"away").

* Simplify is_away_mode_on().

* Correction of erroneously indented else statement.

* Change comment as flake8 gets confused.
2017-03-01 23:52:31 -08:00
Pascal Vizeli f3870a8a48 Template binary_sensor change flow / add restore (#6343)
* Template binary_sensor change flow / add restore

* fix lint
2017-03-02 08:50:41 +01:00
Thibault Cohen 31bf5b8ff0 Improve Honeywell US climate component (#5313)
* Improve Honeywell US climate component

* Fix tests

* Fix tests

* Add cool_away_temp and heat_away_temp for honeywell US

* Fix honeywell tests

* Fix PR comments
2017-03-01 23:49:49 -08:00
Paulus Schoutsen a7ae456a06 Merge remote-tracking branch 'origin/master' into dev 2017-03-01 23:44:11 -08:00
Reed Riley c03022efa3 Add fallback for name if userdevicename isn't set using old serialnumber logic (#6265)
Add an optional extended description…
2017-03-02 08:41:31 +01:00
Mitko Masarliev 46f5a65e68 Update Adafruit_Python_DHT to support new raspberry kernel (#6325)
* Update Adafruit_Python_DHT to support new raspberry kernel

* update Adafruit Python DHT
2017-03-02 08:39:33 +01:00
Alexander Fortin 44ec6b056e Update Vagrant provision.sh (#6236)
- Bugfix: with f63a79ee we removed `script/home-assistant@.service`
  systemd unit file, which is used by Vagrant box to start/stop hass
- simplify interaction with Vagrant, provision.sh now is the only
  entry point and doesn't need the user to touch/remove files in
  order to change provisioner behavior
2017-03-01 23:15:30 -08:00
Andrey 354007f265 Zwave optimize value_added (#6210)
* Make zwave devices listen on less network changes.

* Convert more platforms

* Remove printouts.

* Fix copy-paste

* Change default dependent list to empty list
2017-03-01 22:41:19 -08:00
Pascal Vizeli 6cb8a36cf1 Template sensor change flow / add restore (#6336) 2017-03-02 07:38:19 +02:00
Fabian Affolter 435f253be8 Upgrade py-cpuinfo to 0.2.6 (#6335) 2017-03-02 05:58:03 +01:00
Fabian Affolter 0fe41ffb00 Upgrade TwitterAPI to 2.4.5 (#6351) 2017-03-02 05:57:51 +01:00
Martin Hjelmare bafa0cc3b8 Fix mysensors callback race (#6311)
* Fix possible race at startup in mysensors callback

* Update devices via persistence before starting gateway to avoid
  two threads calling the same callback at the same time.

* Call add_devices max once per callback
2017-03-01 23:09:27 +01:00
Erik Eriksson e23aa1ccf8 sensor.dovado: compute state in update (#6340) 2017-03-01 22:57:37 +01:00
Paulus Schoutsen a9db6b16eb Merge pull request #6339 from home-assistant/release-0-39-2
0.39.2
2017-03-01 12:43:00 -08:00
Paulus Schoutsen bf30f2e9e8 Version bump to 0.39.2 2017-03-01 09:29:34 -08:00
Paulus Schoutsen ee9d50c0a5 Bump netdisco to 0.9.1 (#6338) 2017-03-01 09:29:14 -08:00
Paulus Schoutsen 6736534a52 Discovery fix (#6321)
* Fix incorrect import

* Create own discovery service

* Fix tests

* Fix hdmi_cec bad import
2017-03-01 09:28:49 -08:00
Paulus Schoutsen 4ccd819ec5 Bump netdisco to 0.9.1 (#6338) 2017-03-01 09:05:05 -08:00
Pascal Vizeli 52b1e13aca Bugfix ZigBee / Move from eventbus to dispatcher (#6333)
* Bugfix ZigBee / Move from eventbus to dispatcher

* fix lint
2017-03-01 08:57:53 -08:00
Pascal Vizeli 67f3910f03 Bugfix ZigBee / Move from eventbus to dispatcher (#6333)
* Bugfix ZigBee / Move from eventbus to dispatcher

* fix lint
2017-03-01 08:57:23 -08:00
Paulus Schoutsen 64cb3390ea Test against 3.6-dev (#6324) 2017-03-01 08:53:40 -08:00
Paulus Schoutsen 0ac4a152be Discovery fix (#6321)
* Fix incorrect import

* Create own discovery service

* Fix tests

* Fix hdmi_cec bad import
2017-03-01 07:38:49 -08:00
Pascal Vizeli 4e96e461f7 Cleanup component track_point_in_utc_time usage (#6330) 2017-03-01 07:37:48 -08:00
Stefano Scipioni 30bed8341a Telegram webhooks new text event (#6301)
* new TELEGRAM_TEXT

* telegram command event renamed in 'telegram_command'

* fire telegram_text event anyway
2017-03-01 12:15:16 +01:00
Erik Eriksson d17733427b Merge pull request #6329 from molobrakos/dovado
sensor.dovado: Upgraded library version
2017-03-01 12:02:58 +01:00
Erik Eriksson 782d2a30cd Merge pull request #6328 from molobrakos/eliqonline
sensor.eliqonline: Change to more appropriate icon
2017-03-01 11:52:44 +01:00
Paulus Schoutsen 84f30d9ef8 Bootstrap tweaks tests (#6326)
* Update strings/fix component not found message.

* Fix tests

* More tweak text
2017-02-28 23:42:31 -08:00
Johann Kellerman ac49298c8d Log errors when loading yaml (#6257) 2017-03-01 06:56:23 +02:00
jumpkick a0256e1947 Rollback netdisco to 0.8.2 to resolve #6165 (#6314)
* Rollback netdisco to 0.8.2 to resolve #6165

* Rollback netdisco to 0.8.2 to resolve #6165
2017-02-28 20:37:56 -08:00
ericgingras 7bc2e1238d Convert kpH and mpH to kph and mph (#6316)
There is no reason for the H to be capitalized. Changing it to lowercase increases consistency with other components and allows for use of the min/max sensor, which throws an error if the units of measurement are not the same.
2017-03-01 05:34:40 +01:00
Pascal Vizeli 41f558b181 Bootstrap / Component setup async (#6264)
* Bootstrap / Entiy setup async

* Cleanup add_job stuff / return task/future object

* Address paulus comments / part 1

* fix install pip

* Cleanup bootstrap / move config stuff to config.py

* Make demo async

* Further bootstrap improvement

* Address Martin's comments

* Fix initial tests

* Fix final tests

* Fix bug with prepare loader

* Remove no longer needed things

* Log error when invalid config

* More cleanup

* Cleanups platform events & fix lint

* Use a non blocking add_entities callback for platform

* Fix Autoamtion is setup befor entity is ready

* Better automation fix

* Address paulus comments

* Typo

* fix lint

* rename functions

* fix tests

* fix test

* change exceptions

* fix spell
2017-02-28 20:33:19 -08:00
Adam Mills 383b0914b3 Version bump to 0.40.0.dev0 2017-02-28 11:01:19 -05:00
Krasimir Zhelev be297c4c7e Frontier silicon (#6131)
* added frontier_silicon constant

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* added frontier_silicon constant

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* changed info to debug

* added frontier_silicon constant

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* added the frontier_silicon component

* trying to satisfy pylint

* fsapi version 0.0.6

* remove white space

* generated requirements

* added the frontier_silicon component

* cleaning up according to travis

* trying to satisfy pylint

* trying to satisfy pylint

* fsapi version 0.0.6

* with fsapi version 0.0.7

* added fsapi dependency

* yielding the FSAPI

* Removing white space from docstring

* Removing white space from an empty line

* Switching to sync

* clean up white spaces and rename device to FSAPIDevice

* trying to satisfy pylint

* changed info to debug

* added the frontier_silicon component

* fsapi version 0.0.6

* generated requirements

* pylint

* moved import requests to the method where it is being used

* add a basic unit test

* cleaned up source code

* added frontier_silicon constant

* added the frontier_silicon component

* added basic test

* added fsapi to requirements_all.txt

* added coverage omit, though a basic test was included

* added MEDIA_TYPE_MUSIC for artist and album

* removed duplicate cons

* switched fsapi call to a property, removed unecessary comment

* detailed docstring for fs_device

* added a space for the info_name - info_text separator

* reduced proeprty (fsapi) access for volume down/up
2017-02-28 09:23:07 -05:00
Pascal Vizeli aa1f64bed6 Migrate calendar setup to async. (#6305) 2017-02-28 10:49:06 +01:00
Adam Mills faf8bbcf13 Fix toggle and media_play_pause post async (#6291) 2017-02-27 19:55:34 -08:00
Open Home Automation 0fa259089d Influx fix (#6289)
* Fix: replace influxdb query by another query that is more lightweight and won't timeout

* Fix: replace influxdb query by another query that is more lightweight and won't timeout
2017-02-27 19:54:43 -08:00
Alan Fischer f7c7073cd7 Updated pyitachip2ir (#6296)
* Updated pyitachip2ir

* Updated requirements_all.txt
2017-02-27 19:52:32 -08:00
Boris K d7bf3920a5 improve history_stats accuracy (#6294) 2017-02-27 19:52:10 -08:00
Andrey 7ee75d67c5 Add temperature support for MH-Z19 CO2 sensor. (#6169)
* Add temperature support for MH-Z19 CO2 sensor.

* Remove debug printout

* More tests

* Minor fixes
2017-02-27 21:19:11 +02:00
arjenfvellinga d7db3aba36 Prevent duplicate names on Vera devices by appending the device id (#6100)
* Prevent duplicate names by prepending device id to it.

* Always append device id, not conditionally.

* Moved naming of devices

* flake8
2017-02-27 18:57:39 +00:00
Lindsay Ward 7f99e99dad Update library version for Yeelight Sunflower lights platform (fix for packaging problem with 0.0.7) (#6233) 2017-02-27 13:47:51 -05:00
Greg Dowling fbea5b4cac Merge pull request #6278 from home-assistant/fix_vera_thormostat_bug
Fix vera thermostat bug
2017-02-27 10:55:33 +00:00
Daniel Perna e6c88c05ad [sensor.dnsip] New Sensor: DNS IP (#6214)
* Added DNS IP sensor

* Removed unused import

* Added coverage

* fixed flake

* Applied suggested changes

* Removed debug code

* Switched to aiodns

* Raised scan interval

* Updating state with entity creation

* Lint

* Updated requirements_all
2017-02-27 10:45:32 +00:00
pavoni 757813411d Fix vera thermostat mode set bug 2017-02-27 10:18:22 +00:00
TimV 7dc05785cc Analog modem callerid support (#5840)
* analog-modem-callerid

* analog-modem-callerid

* analog-mod

* Updates from latest review

* Updates from latest review
2017-02-26 21:38:47 -08:00
Wolf-Bastian Pöttner d7af43b87d Add support for MAX!Cube thermostats and window shutter sensors (#6105) 2017-02-26 21:35:33 -08:00
groth-its 6ea74ce740 Fix for OSRAM lights connected to hue bridge (#6122)
* Fix for OSRAM lights connected to hue bridge

Do not send command "effect = none" to OSRAM lights
Osram lights connected to a hue bridge do not seem to handle "effect =
none" very well. Most of the times they jump to the selected color and
then change to red within a second.

Osram lights connected to a hue bridge do not handle xy values outside
of their gamut. Since they just stay at their old color value, handling
the UI is very unpredictable. Sending HSV values to the lights fixes this.

* Add tests for new util methods
2017-02-26 21:28:31 -08:00
Nate d5bdf7783e light.transition now supports float instead of int in order to be able to perform faster transitions (#6163) 2017-02-26 21:21:12 -08:00
Jose Juan Montes 65d255a626 Local file camera now supports yet inexisting files. (#6157) 2017-02-26 21:16:11 -08:00
Jeff Wilson 53a735a329 Properly report features for each hue bulb type (#6271) 2017-02-26 20:59:23 -08:00
Paulus Schoutsen 68713822fd Merge pull request #6272 from home-assistant/release-0-39-1
0.39.1
2017-02-26 20:48:57 -08:00
Pascal Vizeli 3d26ac3323 Bugfix mqtt paho client to speend time (#6266) 2017-02-26 20:31:11 -08:00
Paulus Schoutsen d487960ad8 Config fix (#6261) 2017-02-26 20:31:11 -08:00
Pascal Vizeli 9403cdd2a5 Bugfix mqtt socket error (#6256) 2017-02-26 20:31:11 -08:00
Paulus Schoutsen 1fb1a32c9a Version bump to 0.39.1 2017-02-26 20:30:21 -08:00
Pascal Vizeli 31ddcc6278 Bugfix mqtt paho client to speend time (#6266) 2017-02-26 15:28:54 -08:00
Paulus Schoutsen d789de9ea2 Config fix (#6261) 2017-02-26 15:28:12 -08:00
Scott Henning e2014eb153 Notify ciscospark (#6130)
* Adding ciscospark notifier

* Adding ciscospark notifier

* CI cleanup.

* houndci-bot changes

* ok --- a bunch of code verify changes
2017-02-26 15:04:30 -08:00
Pascal Vizeli 5932446508 Bugfix mqtt socket error (#6256) 2017-02-26 14:43:02 -08:00
Paulus Schoutsen 61909e873f Feature/reorg recorder (#6237)
* Re-organize recorder

* Fix history

* Fix history stats

* Fix restore state

* Lint

* Fix session reconfigure

* Move imports around

* Do not start recording till HASS started

* Lint

* Fix logbook

* Fix race condition recorder init

* Better reporting on errors
2017-02-26 14:38:06 -08:00
Pascal Vizeli 48cf7a4af9 Move ffmpeg to dispatcher from hass.data entity store. (#6211)
* Move ffmpeg to dispatcher from hass.data entity store.

* fix lint

* address paulus comments

* add more unittest for better coverage
2017-02-26 14:31:46 -08:00
Pierre Ståhl 9490cb4c8f Add service to change log levels (#6221)
* Add service to change log levels

* Fix review comments
2017-02-26 14:15:44 -08:00
Paulus Schoutsen 86d4d10176 Ensure we properly close HASS instances. (#6234) 2017-02-26 14:05:18 -08:00
Philipp Schmitt 7b3b755aaf Fix livebox-play interactions for Python < 3.6 (#6243) 2017-02-26 14:04:22 -08:00
Paulus Schoutsen 9afcbaed1d Fix recorder async (#6228) 2017-02-25 15:21:40 -08:00
Paulus Schoutsen 3a7cc9bb45 Update frontend 2017-02-25 15:03:24 -08:00
Paulus Schoutsen ecd1da6525 Merge pull request #6167 from kellerza/recorder_timer
Recorder async & remove start_recording event
2017-02-25 15:01:29 -08:00
Fabian Affolter 9f2719bb1f Update regex (#6216) 2017-02-25 12:55:01 -08:00
Andrey 85d0f2e861 Make glob preserve order (#6224) 2017-02-25 12:54:04 -08:00
Johann Kellerman 5d007e636b No wait for start and more async 2017-02-25 17:51:37 +02:00
Johann Kellerman 7cd6f9038c Allow 4.5min startup time for recorder 2017-02-25 17:28:53 +02:00
Fabian Affolter a80fd2f243 Fix link (#6219) 2017-02-25 13:24:43 +01:00
Erik 2487d27c45 sensor.eliqonline: Change icon 2017-02-25 12:51:48 +01:00
Erik be7162a0df sensor.dovado: Upgraded library version 2017-02-25 12:50:10 +01:00
Andrey c5a8372f13 Update flake8 and pylint to latest (#6217) 2017-02-25 09:44:22 +01:00
Pascal Vizeli 81ca978413 Move mqtt from eventbus to dispatcher / add unsub for dispatcher (#6206)
* Move mqtt from eventbus to dispatcher / add unsub for dispatcher

* Fix lint

* Fix test

* Fix lint v2

* fix dispatcher_send
2017-02-24 17:11:50 -08:00
Paulus Schoutsen d6818c7015 Fix reporting on bad login (#6201) 2017-02-24 16:33:58 -08:00
Greg Dowling df5866dd95 Merge pull request #6213 from home-assistant/bump_pyloopenergy
Bump pyloopenergy - catch socketIO exceptions.
2017-02-24 22:16:03 +00:00
pavoni 34ee2b1ae9 Bump pyloopenergy - catch socketIO exceptions. 2017-02-24 22:04:41 +00:00
Zac Hatfield Dodds 8ca897da57 Zamg weather (#5894)
* Fast & efficient updates for ZAMG weather data

ZAMG updates on the hour, so instead of checking every half-hour we can
check each minute - only after the observations are taken until
receiving them.

* sensor.zamg: test instead of whitelist for station_id

* Autodetect closest ZAMG station if not given

* ZAMG weather component, based on the sensor

* Review improvements

* Update to new ZAMG schema, add logging

Turns out it wasn't a typo, but rather an upstream schema change.  Added
better error handling to ease diagnosis in case it happens again.

* No hardcoded name
2017-02-24 22:45:46 +01:00
Lev Aronsky c7fcd98cad Test the temperature returned by RM2 (#6205)
* Test the temperature returned by RM2
* Validate fields via voluptuous
* Fixed range for humidity
2017-02-24 20:54:31 +01:00
Erik Eriksson 8aa3124aa6 sensor.speedtest: provide a default icon (#6207) 2017-02-24 18:40:52 +01:00
Andrey b27ba9660b Some zwave cleanup (#6203) 2017-02-24 16:17:27 +02:00
Lindsay Ward 9f04b55572 Update Yeelight Sunflower light platform to 0.0.6 (#6208)
Add an optional extended description…
2017-02-24 14:13:55 +01:00
Daniel Høyer Iversen c4f4a9a158 minor broadlink fix (#6202) 2017-02-24 09:49:42 +01:00
Paulus Schoutsen e2e8b43902 Default config to setup group editor (#6198) 2017-02-23 22:53:16 -08:00
Paulus Schoutsen 3a35642dc1 Remove automatically reloading group config (#6197) 2017-02-23 22:40:21 -08:00
Paulus Schoutsen 34a7aa2376 Extend test for group config 2017-02-23 21:57:48 -08:00
Paulus Schoutsen 58eb32bce4 Random test fixes (#6195)
* Store persistent errors in hass (speeds up tests)

* Fix sleepiq test dependency on test order

* Fix sleepiq validation
2017-02-23 21:44:47 -08:00
Johann Kellerman c940d26f07 Bugfix restore startup state (#6189) 2017-02-23 20:06:21 -08:00
jumpkick fc5e25a07b Incorporate comment suggestions
- Separate attribs from coffeemaker condition
- Set power units for threshold to mW to be consistent with others
- Adjust on-time labels to be more clear
2017-02-23 18:03:49 -05:00
Andrey 1d32bced1c Create zwave devices on OZW thread and only add them during discovery (#6096)
* Create zwave devices on OZW thread and only add them during discovery.

* Read and write devices dict from loop thread.

* More async

* replace callback with coroutine

* import common function instead of callin git
2017-02-23 13:06:28 -08:00
Pascal Vizeli f2a2d6bfa1 Refactory of envisalink (#6160)
* Refactory of envisalink

* remove event buss

* init dispatcher from hass.

* Move platform to new dispatcher

* fix lint

* add unittest & threadded functions

* fix copy & past error
2017-02-23 13:02:56 -08:00
Colin O'Dell 4f990ce488 Use H2 headers to split up the different sections (#6183)
Using headers makes it easier to visually differentiate between the different sections
2017-02-23 12:58:18 -08:00
Pascal Vizeli 106b7a9d8f Cleanup run_callback_threadsafe (#6187)
* Cleanup run_callback_threadsafe

* fix spell

* Revert image_processing, they need to wait for update
2017-02-23 12:57:25 -08:00
jumpkick ef87d4dad4 Update device_state_attributes only
This gets rid of the other stuff and just updates device_state_attributes, leaving the default properties alone.
2017-02-23 04:54:09 -05:00
jumpkick f6e46aecf5 Update wemo.py 2017-02-15 17:32:45 -05:00
jumpkick e9cf5f6f42 Update wemo.py 2017-02-15 16:58:11 -05:00
jumpkick e221c8a37d Update wemo.py 2017-02-15 16:57:16 -05:00
jumpkick b163544e3c Back to you travis.... 2017-02-15 16:47:02 -05:00
jumpkick a718e92708 Update wemo.py
trailing whitespace... (argh... the bot should just trim it)
2017-02-15 15:40:02 -05:00
jumpkick 44d274e428 Update wemo.py
* continuation line under-indented for visual indent
2017-02-15 15:38:41 -05:00
jumpkick c404fb7142 Update wemo.py
* Reordered datetime import
* Spaces by 4
2017-02-15 15:34:42 -05:00
jumpkick 29c7987453 Improvements for WeMo Insight switches
* Changes current power units to watts
* Adds power on times and additional totals
2017-02-14 18:29:23 -05:00
437 changed files with 10404 additions and 4435 deletions
+20 -3
View File
@@ -14,9 +14,15 @@ omit =
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
homeassistant/components/android_ip_webcam.py
homeassistant/components/*/android_ip_webcam.py
homeassistant/components/bbb_gpio.py
homeassistant/components/*/bbb_gpio.py
homeassistant/components/blink.py
homeassistant/components/*/blink.py
homeassistant/components/bloomsky.py
homeassistant/components/*/bloomsky.py
@@ -85,6 +91,10 @@ omit =
homeassistant/components/*/thinkingcleaner.py
homeassistant/components/twilio.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/vera.py
homeassistant/components/*/vera.py
@@ -132,6 +142,9 @@ omit =
homeassistant/components/zabbix.py
homeassistant/components/*/zabbix.py
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/alarm_control_panel/alarmdotcom.py
homeassistant/components/alarm_control_panel/concord232.py
homeassistant/components/alarm_control_panel/nx584.py
@@ -231,6 +244,7 @@ omit =
homeassistant/components/media_player/dunehd.py
homeassistant/components/media_player/emby.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/hdmi_cec.py
@@ -259,6 +273,7 @@ omit =
homeassistant/components/notify/aws_lambda.py
homeassistant/components/notify/aws_sns.py
homeassistant/components/notify/aws_sqs.py
homeassistant/components/notify/ciscospark.py
homeassistant/components/notify/discord.py
homeassistant/components/notify/facebook.py
homeassistant/components/notify/free_mobile.py
@@ -286,8 +301,6 @@ omit =
homeassistant/components/notify/syslog.py
homeassistant/components/notify/telegram.py
homeassistant/components/notify/telstra.py
homeassistant/components/notify/twilio_sms.py
homeassistant/components/notify/twilio_call.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
homeassistant/components/nuimo_controller.py
@@ -303,12 +316,14 @@ omit =
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/comed_hourly_pricing.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/cups.py
homeassistant/components/sensor/currencylayer.py
homeassistant/components/sensor/darksky.py
homeassistant/components/sensor/deutsche_bahn.py
homeassistant/components/sensor/dht.py
homeassistant/components/sensor/dnsip.py
homeassistant/components/sensor/dovado.py
homeassistant/components/sensor/dte_energy_bridge.py
homeassistant/components/sensor/ebox.py
@@ -333,11 +348,12 @@ omit =
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/influxdb.py
homeassistant/components/sensor/kwb.py
homeassistant/components/sensor/lastfm.py
homeassistant/components/sensor/linux_battery.py
homeassistant/components/sensor/loopenergy.py
homeassistant/components/sensor/mhz19.py
homeassistant/components/sensor/miflora.py
homeassistant/components/sensor/modem_callerid.py
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/neurio_energy.py
@@ -411,6 +427,7 @@ omit =
homeassistant/components/upnp.py
homeassistant/components/weather/bom.py
homeassistant/components/weather/openweathermap.py
homeassistant/components/weather/zamg.py
homeassistant/components/zeroconf.py
+3 -3
View File
@@ -1,16 +1,16 @@
**Description:**
## Description:
**Related issue (if applicable):** fixes #<home-assistant issue number goes here>
**Pull request in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io) with documentation (if applicable):** home-assistant/home-assistant.github.io#<home-assistant.github.io PR number goes here>
**Example entry for `configuration.yaml` (if applicable):**
## Example entry for `configuration.yaml` (if applicable):
```yaml
```
**Checklist:**
## Checklist:
If user exposed functionality or configuration variables are added/changed:
- [ ] Documentation added/updated in [home-assistant.github.io](https://github.com/home-assistant/home-assistant.github.io)
+2
View File
@@ -14,6 +14,8 @@ matrix:
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "3.6-dev"
env: TOXENV=py36
# allow_failures:
# - python: "3.5"
# env: TOXENV=typing
+34 -390
View File
@@ -4,320 +4,31 @@ import logging
import logging.handlers
import os
import sys
from time import time
from collections import OrderedDict
from types import ModuleType
from typing import Any, Optional, Dict
import voluptuous as vol
from voluptuous.humanize import humanize_error
import homeassistant.components as core_components
from homeassistant.components import persistent_notification
import homeassistant.config as conf_util
import homeassistant.core as core
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component
import homeassistant.loader as loader
import homeassistant.util.package as pkg_util
from homeassistant.util.async import (
run_coroutine_threadsafe, run_callback_threadsafe)
from homeassistant.util.logging import AsyncHandler
from homeassistant.util.yaml import clear_secret_cache
from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
event_decorators, service, config_per_platform, extract_domain_configs)
from homeassistant.helpers import event_decorators, service
from homeassistant.helpers.signal import async_register_signal_handling
_LOGGER = logging.getLogger(__name__)
ATTR_COMPONENT = 'component'
ERROR_LOG_FILENAME = 'home-assistant.log'
_PERSISTENT_ERRORS = {}
HA_COMPONENT_URL = '[{}](https://home-assistant.io/components/{}/)'
def setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies."""
return run_coroutine_threadsafe(
async_setup_component(hass, domain, config), loop=hass.loop).result()
@asyncio.coroutine
def async_setup_component(hass: core.HomeAssistant, domain: str,
config: Optional[Dict]=None) -> bool:
"""Setup a component and all its dependencies.
This method is a coroutine.
"""
if domain in hass.config.components:
_LOGGER.debug('Component %s already set up.', domain)
return True
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
if config is None:
config = {}
components = loader.load_order_component(domain)
# OrderedSet is empty if component or dependencies could not be resolved
if not components:
_async_persistent_notification(hass, domain, True)
return False
for component in components:
res = yield from _async_setup_component(hass, component, config)
if not res:
_LOGGER.error('Component %s failed to setup', component)
_async_persistent_notification(hass, component, True)
return False
return True
def _handle_requirements(hass: core.HomeAssistant, component,
name: str) -> bool:
"""Install the requirements for a component.
This method needs to run in an executor.
"""
if hass.config.skip_pip or not hasattr(component, 'REQUIREMENTS'):
return True
for req in component.REQUIREMENTS:
if not pkg_util.install_package(req, target=hass.config.path('deps')):
_LOGGER.error('Not initializing %s because could not install '
'dependency %s', name, req)
_async_persistent_notification(hass, name)
return False
return True
@asyncio.coroutine
def _async_setup_component(hass: core.HomeAssistant,
domain: str, config) -> bool:
"""Setup a component for Home Assistant.
This method is a coroutine.
"""
# pylint: disable=too-many-return-statements
if domain in hass.config.components:
return True
setup_lock = hass.data.get('setup_lock')
if setup_lock is None:
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
setup_progress = hass.data.get('setup_progress')
if setup_progress is None:
setup_progress = hass.data['setup_progress'] = []
if domain in setup_progress:
_LOGGER.error('Attempt made to setup %s during setup of %s',
domain, domain)
_async_persistent_notification(hass, domain, True)
return False
try:
# Used to indicate to discovery that a setup is ongoing and allow it
# to wait till it is done.
did_lock = False
if not setup_lock.locked():
yield from setup_lock.acquire()
did_lock = True
setup_progress.append(domain)
config = yield from async_prepare_setup_component(hass, config, domain)
if config is None:
return False
component = loader.get_component(domain)
if component is None:
_async_persistent_notification(hass, domain)
return False
async_comp = hasattr(component, 'async_setup')
try:
_LOGGER.info("Setting up %s", domain)
if async_comp:
result = yield from component.async_setup(hass, config)
else:
result = yield from hass.loop.run_in_executor(
None, component.setup, hass, config)
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error during setup of component %s', domain)
_async_persistent_notification(hass, domain, True)
return False
if result is False:
_LOGGER.error('component %s failed to initialize', domain)
_async_persistent_notification(hass, domain, True)
return False
elif result is not True:
_LOGGER.error('component %s did not return boolean if setup '
'was successful. Disabling component.', domain)
_async_persistent_notification(hass, domain, True)
loader.set_component(domain, None)
return False
hass.config.components.add(component.DOMAIN)
hass.bus.async_fire(
EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}
)
return True
finally:
setup_progress.remove(domain)
if did_lock:
setup_lock.release()
def prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config."""
return run_coroutine_threadsafe(
async_prepare_setup_component(hass, config, domain), loop=hass.loop
).result()
@asyncio.coroutine
def async_prepare_setup_component(hass: core.HomeAssistant, config: dict,
domain: str):
"""Prepare setup of a component and return processed config.
This method is a coroutine.
"""
# pylint: disable=too-many-return-statements
component = loader.get_component(domain)
missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', [])
if dep not in hass.config.components]
if missing_deps:
_LOGGER.error(
'Not initializing %s because not all dependencies loaded: %s',
domain, ", ".join(missing_deps))
return None
if hasattr(component, 'CONFIG_SCHEMA'):
try:
config = component.CONFIG_SCHEMA(config)
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
return None
elif hasattr(component, 'PLATFORM_SCHEMA'):
platforms = []
for p_name, p_config in config_per_platform(config, domain):
# Validate component specific platform schema
try:
p_validated = component.PLATFORM_SCHEMA(p_config)
except vol.Invalid as ex:
async_log_exception(ex, domain, config, hass)
continue
# Not all platform components follow same pattern for platforms
# So if p_name is None we are not going to validate platform
# (the automation component is one of them)
if p_name is None:
platforms.append(p_validated)
continue
platform = yield from async_prepare_setup_platform(
hass, config, domain, p_name)
if platform is None:
continue
# Validate platform specific schema
if hasattr(platform, 'PLATFORM_SCHEMA'):
try:
# pylint: disable=no-member
p_validated = platform.PLATFORM_SCHEMA(p_validated)
except vol.Invalid as ex:
async_log_exception(ex, '{}.{}'.format(domain, p_name),
p_validated, hass)
continue
platforms.append(p_validated)
# Create a copy of the configuration with all config for current
# component removed and add validated config back in.
filter_keys = extract_domain_configs(config, domain)
config = {key: value for key, value in config.items()
if key not in filter_keys}
config[domain] = platforms
res = yield from hass.loop.run_in_executor(
None, _handle_requirements, hass, component, domain)
if not res:
return None
return config
def prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) -> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup."""
return run_coroutine_threadsafe(
async_prepare_setup_platform(hass, config, domain, platform_name),
loop=hass.loop
).result()
@asyncio.coroutine
def async_prepare_setup_platform(hass: core.HomeAssistant, config, domain: str,
platform_name: str) \
-> Optional[ModuleType]:
"""Load a platform and makes sure dependencies are setup.
This method is a coroutine.
"""
if not loader.PREPARED:
yield from hass.loop.run_in_executor(None, loader.prepare, hass)
platform_path = PLATFORM_FORMAT.format(domain, platform_name)
platform = loader.get_platform(domain, platform_name)
# Not found
if platform is None:
_LOGGER.error('Unable to find platform %s', platform_path)
_async_persistent_notification(hass, platform_path)
return None
# Already loaded
elif platform_path in hass.config.components:
return platform
# Load dependencies
for component in getattr(platform, 'DEPENDENCIES', []):
if component in loader.DEPENDENCY_BLACKLIST:
raise HomeAssistantError(
'{} is not allowed to be a dependency.'.format(component))
res = yield from async_setup_component(hass, component, config)
if not res:
_LOGGER.error(
'Unable to prepare setup for platform %s because '
'dependency %s could not be initialized', platform_path,
component)
_async_persistent_notification(hass, platform_path, True)
return None
res = yield from hass.loop.run_in_executor(
None, _handle_requirements, hass, platform, platform_path)
if not res:
return None
return platform
FIRST_INIT_COMPONENT = set((
'recorder', 'mqtt', 'mqtt_eventstream', 'logger', 'introduction'))
def from_config_dict(config: Dict[str, Any],
@@ -339,23 +50,14 @@ def from_config_dict(config: Dict[str, Any],
hass.config.config_dir = config_dir
mount_local_lib_path(config_dir)
@asyncio.coroutine
def _async_init_from_config_dict(future):
try:
re_hass = yield from async_from_config_dict(
config, hass, config_dir, enable_log, verbose, skip_pip,
log_rotate_days)
future.set_result(re_hass)
# pylint: disable=broad-except
except Exception as exc:
future.set_exception(exc)
# run task
future = asyncio.Future(loop=hass.loop)
hass.async_add_job(_async_init_from_config_dict(future))
hass.loop.run_until_complete(future)
hass = hass.loop.run_until_complete(
async_from_config_dict(
config, hass, config_dir, enable_log, verbose, skip_pip,
log_rotate_days)
)
return future.result()
return hass
@asyncio.coroutine
@@ -372,19 +74,15 @@ def async_from_config_dict(config: Dict[str, Any],
Dynamically loads required components and its dependencies.
This method is a coroutine.
"""
start = time()
hass.async_track_tasks()
setup_lock = hass.data.get('setup_lock')
if setup_lock is None:
setup_lock = hass.data['setup_lock'] = asyncio.Lock(loop=hass.loop)
yield from setup_lock.acquire()
core_config = config.get(core.DOMAIN, {})
try:
yield from conf_util.async_process_ha_core_config(hass, core_config)
except vol.Invalid as ex:
async_log_exception(ex, 'homeassistant', core_config, hass)
conf_util.async_log_exception(ex, 'homeassistant', core_config, hass)
return None
yield from hass.loop.run_in_executor(
@@ -433,20 +131,25 @@ def async_from_config_dict(config: Dict[str, Any],
event_decorators.HASS = hass
service.HASS = hass
# Setup the components
dependency_blacklist = loader.DEPENDENCY_BLACKLIST - set(components)
# stage 1
for component in components:
if component not in FIRST_INIT_COMPONENT:
continue
hass.async_add_job(async_setup_component(hass, component, config))
for domain in loader.load_order_components(components):
if domain in dependency_blacklist:
raise HomeAssistantError(
'{} is not allowed to be a dependency'.format(domain))
yield from hass.async_block_till_done()
yield from _async_setup_component(hass, domain, config)
setup_lock.release()
# stage 2
for component in components:
if component in FIRST_INIT_COMPONENT:
continue
hass.async_add_job(async_setup_component(hass, component, config))
yield from hass.async_stop_track_tasks()
stop = time()
_LOGGER.info('Home Assistant initialized in %ss', round(stop-start, 2))
async_register_signal_handling(hass)
return hass
@@ -464,22 +167,13 @@ def from_config_file(config_path: str,
if hass is None:
hass = core.HomeAssistant()
@asyncio.coroutine
def _async_init_from_config_file(future):
try:
re_hass = yield from async_from_config_file(
config_path, hass, verbose, skip_pip, log_rotate_days)
future.set_result(re_hass)
# pylint: disable=broad-except
except Exception as exc:
future.set_exception(exc)
# run task
future = asyncio.Future(loop=hass.loop)
hass.loop.create_task(_async_init_from_config_file(future))
hass.loop.run_until_complete(future)
hass = hass.loop.run_until_complete(
async_from_config_file(
config_path, hass, verbose, skip_pip, log_rotate_days)
)
return future.result()
return hass
@asyncio.coroutine
@@ -504,7 +198,8 @@ def async_from_config_file(config_path: str,
try:
config_dict = yield from hass.loop.run_in_executor(
None, conf_util.load_yaml_config_file, config_path)
except HomeAssistantError:
except HomeAssistantError as err:
_LOGGER.error('Error loading %s: %s', config_path, err)
return None
finally:
clear_secret_cache()
@@ -588,57 +283,6 @@ def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False,
'Unable to setup error log %s (access denied)', err_log_path)
def log_exception(ex, domain, config, hass):
"""Generate log exception for config validation."""
run_callback_threadsafe(
hass.loop, async_log_exception, ex, domain, config, hass).result()
@core.callback
def _async_persistent_notification(hass: core.HomeAssistant, component: str,
link: Optional[bool]=False):
"""Print a persistent notification.
This method must be run in the event loop.
"""
_PERSISTENT_ERRORS[component] = _PERSISTENT_ERRORS.get(component) or link
_lst = [HA_COMPONENT_URL.format(name.replace('_', '-'), name)
if link else name for name, link in _PERSISTENT_ERRORS.items()]
message = ('The following components and platforms could not be set up:\n'
'* ' + '\n* '.join(list(_lst)) + '\nPlease check your config')
persistent_notification.async_create(
hass, message, 'Invalid config', 'invalid_config')
@core.callback
def async_log_exception(ex, domain, config, hass):
"""Generate log exception for config validation.
This method must be run in the event loop.
"""
message = 'Invalid config for [{}]: '.format(domain)
if hass is not None:
_async_persistent_notification(hass, domain, True)
if 'extra keys not allowed' in ex.error_message:
message += '[{}] is an invalid option for [{}]. Check: {}->{}.'\
.format(ex.path[-1], domain, domain,
'->'.join(str(m) for m in ex.path))
else:
message += '{}.'.format(humanize_error(config, ex))
domain_config = config.get(domain, config)
message += " (See {}, line {}). ".format(
getattr(domain_config, '__config_file__', '?'),
getattr(domain_config, '__line__', '?'))
if domain != 'homeassistant':
message += ('Please check the docs at '
'https://home-assistant.io/components/{}/'.format(domain))
_LOGGER.error(message)
def mount_local_lib_path(config_dir: str) -> str:
"""Add local library to Python Path.
@@ -55,7 +55,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
@callback
def alarm_keypress_handler(service):
@@ -94,10 +94,13 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
_LOGGER.debug("Setting up alarm: %s", alarm_name)
super().__init__(alarm_name, info, controller)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
async_dispatcher_connect(
hass, SIGNAL_PARTITION_UPDATE, self._update_callback)
self.hass, SIGNAL_PARTITION_UPDATE, self._update_callback)
@callback
def _update_callback(self, partition):
@@ -46,7 +46,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup the MQTT platform."""
yield from async_add_devices([MqttAlarm(
async_add_devices([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
+3 -6
View File
@@ -18,7 +18,6 @@ from homeassistant.const import (
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers import service, event
from homeassistant.util.async import run_callback_threadsafe
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -62,8 +61,7 @@ def is_on(hass, entity_id):
def turn_on(hass, entity_id):
"""Reset the alert."""
run_callback_threadsafe(
hass.loop, async_turn_on, hass, entity_id).result()
hass.add_job(async_turn_on, hass, entity_id)
@callback
@@ -76,8 +74,7 @@ def async_turn_on(hass, entity_id):
def turn_off(hass, entity_id):
"""Acknowledge alert."""
run_callback_threadsafe(
hass.loop, async_turn_off, hass, entity_id).result()
hass.add_job(async_turn_off, hass, entity_id)
@callback
@@ -90,7 +87,7 @@ def async_turn_off(hass, entity_id):
def toggle(hass, entity_id):
"""Toggle acknowledgement of alert."""
run_callback_threadsafe(hass.loop, async_toggle, hass, entity_id)
hass.add_job(async_toggle, hass, entity_id)
@callback
@@ -0,0 +1,303 @@
"""
Support for IP Webcam, an Android app that acts as a full-featured webcam.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/android_ip_webcam/
"""
import asyncio
import logging
from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD,
CONF_SENSORS, CONF_SWITCHES, CONF_TIMEOUT, CONF_SCAN_INTERVAL,
CONF_PLATFORM)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_connect)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.components.camera.mjpeg import (
CONF_MJPEG_URL, CONF_STILL_IMAGE_URL)
DOMAIN = 'android_ip_webcam'
REQUIREMENTS = ["pydroid-ipcam==0.4"]
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
DATA_IP_WEBCAM = 'android_ip_webcam'
ATTR_HOST = 'host'
ATTR_VID_CONNS = 'Video Connections'
ATTR_AUD_CONNS = 'Audio Connections'
KEY_MAP = {
'audio_connections': 'Audio Connections',
'adet_limit': 'Audio Trigger Limit',
'antibanding': 'Anti-banding',
'audio_only': 'Audio Only',
'battery_level': 'Battery Level',
'battery_temp': 'Battery Temperature',
'battery_voltage': 'Battery Voltage',
'coloreffect': 'Color Effect',
'exposure': 'Exposure Level',
'exposure_lock': 'Exposure Lock',
'ffc': 'Front-facing Camera',
'flashmode': 'Flash Mode',
'focus': 'Focus',
'focus_homing': 'Focus Homing',
'focus_region': 'Focus Region',
'focusmode': 'Focus Mode',
'gps_active': 'GPS Active',
'idle': 'Idle',
'ip_address': 'IPv4 Address',
'ipv6_address': 'IPv6 Address',
'ivideon_streaming': 'Ivideon Streaming',
'light': 'Light Level',
'mirror_flip': 'Mirror Flip',
'motion': 'Motion',
'motion_active': 'Motion Active',
'motion_detect': 'Motion Detection',
'motion_event': 'Motion Event',
'motion_limit': 'Motion Limit',
'night_vision': 'Night Vision',
'night_vision_average': 'Night Vision Average',
'night_vision_gain': 'Night Vision Gain',
'orientation': 'Orientation',
'overlay': 'Overlay',
'photo_size': 'Photo Size',
'pressure': 'Pressure',
'proximity': 'Proximity',
'quality': 'Quality',
'scenemode': 'Scene Mode',
'sound': 'Sound',
'sound_event': 'Sound Event',
'sound_timeout': 'Sound Timeout',
'torch': 'Torch',
'video_connections': 'Video Connections',
'video_chunk_len': 'Video Chunk Length',
'video_recording': 'Video Recording',
'video_size': 'Video Size',
'whitebalance': 'White Balance',
'whitebalance_lock': 'White Balance Lock',
'zoom': 'Zoom'
}
ICON_MAP = {
'audio_connections': 'mdi:speaker',
'battery_level': 'mdi:battery',
'battery_temp': 'mdi:thermometer',
'battery_voltage': 'mdi:battery-charging-100',
'exposure_lock': 'mdi:camera',
'ffc': 'mdi:camera-front-variant',
'focus': 'mdi:image-filter-center-focus',
'gps_active': 'mdi:crosshairs-gps',
'light': 'mdi:flashlight',
'motion': 'mdi:run',
'night_vision': 'mdi:weather-night',
'overlay': 'mdi:monitor',
'pressure': 'mdi:gauge',
'proximity': 'mdi:map-marker-radius',
'quality': 'mdi:quality-high',
'sound': 'mdi:speaker',
'sound_event': 'mdi:speaker',
'sound_timeout': 'mdi:speaker',
'torch': 'mdi:white-balance-sunny',
'video_chunk_len': 'mdi:video',
'video_connections': 'mdi:eye',
'video_recording': 'mdi:record-rec',
'whitebalance_lock': 'mdi:white-balance-auto'
}
SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision',
'overlay', 'torch', 'whitebalance_lock', 'video_recording']
SENSORS = ['audio_connections', 'battery_level', 'battery_temp',
'battery_voltage', 'light', 'motion', 'pressure', 'proximity',
'sound', 'video_connections']
SIGNAL_UPDATE_DATA = 'android_ip_webcam_update'
CONF_AUTO_DISCOVERY = 'auto_discovery'
CONF_MOTION_SENSOR = 'motion_sensor'
DEFAULT_AUTO_DISCOVERY = True
DEFAULT_MOTION_SENSOR = False
DEFAULT_NAME = 'IP Webcam'
DEFAULT_PORT = 8080
DEFAULT_TIMEOUT = 10
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period,
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
vol.Optional(CONF_AUTO_DISCOVERY, default=DEFAULT_AUTO_DISCOVERY):
cv.boolean,
vol.Optional(CONF_SWITCHES, default=[]):
vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
vol.Optional(CONF_SENSORS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSORS)]),
vol.Optional(CONF_MOTION_SENSOR, default=DEFAULT_MOTION_SENSOR):
cv.boolean,
})])
}, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine
def async_setup(hass, config):
"""Setup the IP Webcam component."""
from pydroid_ipcam import PyDroidIPCam
webcams = hass.data[DATA_IP_WEBCAM] = {}
websession = async_get_clientsession(hass)
@asyncio.coroutine
def async_setup_ipcamera(cam_config):
"""Setup a ip camera."""
host = cam_config[CONF_HOST]
username = cam_config.get(CONF_USERNAME)
password = cam_config.get(CONF_PASSWORD)
name = cam_config[CONF_NAME]
interval = cam_config[CONF_SCAN_INTERVAL]
switches = cam_config[CONF_SWITCHES]
sensors = cam_config[CONF_SENSORS]
motion = cam_config[CONF_MOTION_SENSOR]
# init ip webcam
cam = PyDroidIPCam(
hass.loop, websession, host, cam_config[CONF_PORT],
username=username, password=password,
timeout=cam_config[CONF_TIMEOUT]
)
@asyncio.coroutine
def async_update_data(now):
"""Update data from ipcam in SCAN_INTERVAL."""
yield from cam.update()
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
async_track_point_in_utc_time(
hass, async_update_data, utcnow() + interval)
yield from async_update_data(None)
# use autodiscovery to detect sensors/configs
if cam_config[CONF_AUTO_DISCOVERY]:
if not cam.available:
_LOGGER.error(
"Android webcam %s not found for discovery!", cam.base_url)
return
sensors = [sensor for sensor in cam.enabled_sensors
if sensor in SENSORS]
switches = [setting for setting in cam.enabled_settings
if setting in SWITCHES]
motion = True if 'motion_active' in cam.enabled_sensors else False
sensors.extend(['audio_connections', 'video_connections'])
# load platforms
webcams[host] = cam
mjpeg_camera = {
CONF_PLATFORM: 'mjpeg',
CONF_MJPEG_URL: cam.mjpeg_url,
CONF_STILL_IMAGE_URL: cam.image_url,
CONF_NAME: name,
}
if username and password:
mjpeg_camera.update({
CONF_USERNAME: username,
CONF_PASSWORD: password
})
hass.async_add_job(discovery.async_load_platform(
hass, 'camera', 'mjpeg', mjpeg_camera, config))
if sensors:
hass.async_add_job(discovery.async_load_platform(
hass, 'sensor', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
CONF_SENSORS: sensors,
}, config))
if switches:
hass.async_add_job(discovery.async_load_platform(
hass, 'switch', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
CONF_SWITCHES: switches,
}, config))
if motion:
hass.async_add_job(discovery.async_load_platform(
hass, 'binary_sensor', DOMAIN, {
CONF_HOST: host,
CONF_NAME: name,
}, config))
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
return True
class AndroidIPCamEntity(Entity):
"""The Android device running IP Webcam."""
def __init__(self, host, ipcam):
"""Initialize the data oject."""
self._host = host
self._ipcam = ipcam
@asyncio.coroutine
def async_added_to_hass(self):
"""Register update dispatcher."""
@callback
def async_ipcam_update(host):
"""Update callback."""
if self._host != host:
return
self.hass.async_add_job(self.async_update_ha_state(True))
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update)
@property
def should_poll(self):
"""Return True if entity has to be polled for state."""
return False
@property
def available(self):
"""Return True if entity is available."""
return self._ipcam.available
@property
def device_state_attributes(self):
"""Return the state attributes."""
state_attr = {ATTR_HOST: self._host}
if self._ipcam.status_data is None:
return state_attr
state_attr[ATTR_VID_CONNS] = \
self._ipcam.status_data.get('video_connections')
state_attr[ATTR_AUD_CONNS] = \
self._ipcam.status_data.get('audio_connections')
return state_attr
+24 -14
View File
@@ -11,16 +11,17 @@ import os
import voluptuous as vol
from homeassistant.bootstrap import async_prepare_setup_platform
from homeassistant.setup import async_prepare_setup_platform
from homeassistant import config as conf_util
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE)
SERVICE_TOGGLE, SERVICE_RELOAD)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
from homeassistant.loader import get_platform
from homeassistant.util.dt import utcnow
import homeassistant.helpers.config_validation as cv
@@ -28,8 +29,6 @@ import homeassistant.helpers.config_validation as cv
DOMAIN = 'automation'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEPENDENCIES = ['group']
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
CONF_ALIAS = 'alias'
@@ -52,7 +51,6 @@ DEFAULT_INITIAL_STATE = True
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_VARIABLES = 'variables'
SERVICE_TRIGGER = 'trigger'
SERVICE_RELOAD = 'reload'
_LOGGER = logging.getLogger(__name__)
@@ -226,7 +224,7 @@ class AutomationEntity(ToggleEntity):
"""Entity to show status of entity."""
def __init__(self, name, async_attach_triggers, cond_func, async_action,
hidden):
hidden, initial_state):
"""Initialize an automation entity."""
self._name = name
self._async_attach_triggers = async_attach_triggers
@@ -236,6 +234,7 @@ class AutomationEntity(ToggleEntity):
self._enabled = False
self._last_triggered = None
self._hidden = hidden
self._initial_state = initial_state
@property
def name(self):
@@ -264,6 +263,18 @@ class AutomationEntity(ToggleEntity):
"""Return True if entity is on."""
return self._enabled
@asyncio.coroutine
def async_added_to_hass(self) -> None:
"""Startup with initial state or previous state."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state is None:
if self._initial_state:
yield from self.async_enable()
else:
self._last_triggered = state.attributes.get('last_triggered')
if state.state == STATE_ON:
yield from self.async_enable()
@asyncio.coroutine
def async_turn_on(self, **kwargs) -> None:
"""Turn the entity on and update the state."""
@@ -322,7 +333,6 @@ def _async_process_config(hass, config, component):
This method is a coroutine.
"""
entities = []
tasks = []
for config_key in extract_domain_configs(config, DOMAIN):
conf = config[config_key]
@@ -332,6 +342,7 @@ def _async_process_config(hass, config, component):
list_no)
hidden = config_block[CONF_HIDE_ENTITY]
initial_state = config_block[CONF_INITIAL_STATE]
action = _async_get_action(hass, config_block.get(CONF_ACTION, {}),
name)
@@ -348,15 +359,14 @@ def _async_process_config(hass, config, component):
async_attach_triggers = partial(
_async_process_trigger, hass, config,
config_block.get(CONF_TRIGGER, []), name)
entity = AutomationEntity(name, async_attach_triggers, cond_func,
action, hidden)
if config_block[CONF_INITIAL_STATE]:
tasks.append(entity.async_enable())
config_block.get(CONF_TRIGGER, []), name
)
entity = AutomationEntity(
name, async_attach_triggers, cond_func, action, hidden,
initial_state)
entities.append(entity)
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
if entities:
yield from component.async_add_entities(entities)
@@ -0,0 +1,62 @@
"""
Support for IP Webcam binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.android_ip_webcam/
"""
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.android_ip_webcam import (
KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME)
DEPENDENCIES = ['android_ip_webcam']
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup IP Webcam binary sensors."""
if discovery_info is None:
return
host = discovery_info[CONF_HOST]
name = discovery_info[CONF_NAME]
ipcam = hass.data[DATA_IP_WEBCAM][host]
async_add_devices(
[IPWebcamBinarySensor(name, host, ipcam, 'motion_active')], True)
class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
"""Represents an IP Webcam binary sensor."""
def __init__(self, name, host, ipcam, sensor):
"""Initialize the binary sensor."""
super().__init__(host, ipcam)
self._sensor = sensor
self._mapped_name = KEY_MAP.get(self._sensor, self._sensor)
self._name = '{} {}'.format(name, self._mapped_name)
self._state = None
self._unit = None
@property
def name(self):
"""Return the name of the binary sensor, if any."""
return self._name
@property
def is_on(self):
"""True if the binary sensor is on."""
return self._state
@asyncio.coroutine
def async_update(self):
"""Retrieve latest state."""
state, _ = self._ipcam.export_sensor(self._sensor)
self._state = state == 1.0
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return 'motion'
@@ -0,0 +1,74 @@
"""
Support for Blink system camera control.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.blink/
"""
from homeassistant.components.blink import DOMAIN
from homeassistant.components.binary_sensor import BinarySensorDevice
DEPENDENCIES = ['blink']
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the blink binary sensors."""
if discovery_info is None:
return
data = hass.data[DOMAIN].blink
devs = list()
for name in data.cameras:
devs.append(BlinkCameraMotionSensor(name, data))
devs.append(BlinkSystemSensor(data))
add_devices(devs, True)
class BlinkCameraMotionSensor(BinarySensorDevice):
"""A representation of a Blink binary sensor."""
def __init__(self, name, data):
"""Initialize the sensor."""
self._name = 'blink_' + name + '_motion_enabled'
self._camera_name = name
self.data = data
self._state = self.data.cameras[self._camera_name].armed
@property
def name(self):
"""Return the name of the blink sensor."""
return self._name
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
def update(self):
"""Update sensor state."""
self.data.refresh()
self._state = self.data.cameras[self._camera_name].armed
class BlinkSystemSensor(BinarySensorDevice):
"""A representation of a Blink system sensor."""
def __init__(self, data):
"""Initialize the sensor."""
self._name = 'blink armed status'
self.data = data
self._state = self.data.arm
@property
def name(self):
"""Return the name of the blink sensor."""
return self._name.replace(" ", "_")
@property
def is_on(self):
"""Return the status of the sensor."""
return self._state
def update(self):
"""Update sensor state."""
self.data.refresh()
self._state = self.data.arm
@@ -67,7 +67,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice):
This method is called when there is an incoming packet associated
with this platform.
"""
self.update_ha_state()
self.schedule_update_ha_state()
if value2 == 0x70:
self.which = 0
self.onoff = 0
@@ -37,7 +37,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
@@ -52,8 +52,11 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
_LOGGER.debug('Setting up zone: ' + zone_name)
super().__init__(zone_name, info, controller)
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
hass, SIGNAL_ZONE_UPDATE, self._update_callback)
self.hass, SIGNAL_ZONE_UPDATE, self._update_callback)
@property
def device_state_attributes(self):
@@ -57,16 +57,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# generate sensor object
entity = FFmpegMotion(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
async_add_devices([entity])
class FFmpegBinarySensor(FFmpegBase, BinarySensorDevice):
"""A binary sensor which use ffmpeg for noise detection."""
def __init__(self, hass, config):
def __init__(self, config):
"""Constructor for binary sensor noise detection."""
super().__init__(config.get(CONF_INITIAL_STATE))
@@ -98,15 +95,19 @@ class FFmpegMotion(FFmpegBinarySensor):
"""Initialize ffmpeg motion binary sensor."""
from haffmpeg import SensorMotion
super().__init__(hass, config)
super().__init__(config)
self.ffmpeg = SensorMotion(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
@asyncio.coroutine
def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
This method is a coroutine.
"""
if entity_ids is not None and self.entity_id not in entity_ids:
return
# init config
self.ffmpeg.set_options(
time_reset=self._config.get(CONF_RESET),
@@ -116,7 +117,7 @@ class FFmpegMotion(FFmpegBinarySensor):
)
# run
return self.ffmpeg.open_sensor(
yield from self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
@@ -54,10 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
# generate sensor object
entity = FFmpegNoise(hass, manager, config)
# add to system
manager.async_register_device(entity)
yield from async_add_devices([entity])
async_add_devices([entity])
class FFmpegNoise(FFmpegBinarySensor):
@@ -67,15 +64,19 @@ class FFmpegNoise(FFmpegBinarySensor):
"""Initialize ffmpeg noise binary sensor."""
from haffmpeg import SensorNoise
super().__init__(hass, config)
super().__init__(config)
self.ffmpeg = SensorNoise(
manager.binary, hass.loop, self._async_callback)
def async_start_ffmpeg(self):
@asyncio.coroutine
def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance.
This method must be run in the event loop and returns a coroutine.
This method is a coroutine.
"""
if entity_ids is not None and self.entity_id not in entity_ids:
return
# init config
self.ffmpeg.set_options(
time_duration=self._config.get(CONF_DURATION),
@@ -84,7 +85,7 @@ class FFmpegNoise(FFmpegBinarySensor):
)
# run
return self.ffmpeg.open_sensor(
yield from self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
@@ -15,9 +15,10 @@ from homeassistant.components.binary_sensor import (
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD,
CONF_SSL, EVENT_HOMEASSISTANT_STOP, ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START,
ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE)
REQUIREMENTS = ['pyhik==0.0.7', 'pydispatcher==2.0.5']
REQUIREMENTS = ['pyhik==0.1.0']
_LOGGER = logging.getLogger(__name__)
CONF_IGNORED = 'ignored'
@@ -119,30 +120,32 @@ class HikvisionData(object):
self._password = password
# Establish camera
self._cam = HikCamera(self._url, self._port,
self._username, self._password)
self.camdata = HikCamera(self._url, self._port,
self._username, self._password)
if self._name is None:
self._name = self._cam.get_name
# Start event stream
self._cam.start_stream()
self._name = self.camdata.get_name
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.stop_hik)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.start_hik)
def stop_hik(self, event):
"""Shutdown Hikvision subscriptions and subscription thread on exit."""
self._cam.disconnect()
self.camdata.disconnect()
def start_hik(self, event):
"""Start Hikvision event stream thread."""
self.camdata.start_stream()
@property
def sensors(self):
"""Return list of available sensors and their states."""
return self._cam.current_event_states
return self.camdata.current_event_states
@property
def cam_id(self):
"""Return camera id."""
return self._cam.get_id
return self.camdata.get_id
@property
def name(self):
@@ -155,8 +158,6 @@ class HikvisionBinarySensor(BinarySensorDevice):
def __init__(self, hass, sensor, cam, delay):
"""Initialize the binary_sensor."""
from pydispatch import dispatcher
self._hass = hass
self._cam = cam
self._name = self._cam.name + ' ' + sensor
@@ -170,12 +171,8 @@ class HikvisionBinarySensor(BinarySensorDevice):
self._timer = None
# Form signal for dispatcher
signal = 'ValueChanged.{}'.format(self._cam.cam_id)
dispatcher.connect(self._update_callback,
signal=signal,
sender=self._sensor)
# Register callback function with pyHik
self._cam.camdata.add_update_callback(self._update_callback, self._id)
def _sensor_state(self):
"""Extract sensor state."""
@@ -225,13 +222,9 @@ class HikvisionBinarySensor(BinarySensorDevice):
return attr
def _update_callback(self, signal, sender):
def _update_callback(self, msg):
"""Update the sensor's state, if needed."""
_LOGGER.debug('Dispatcher callback, signal: %s, sender: %s',
signal, sender)
if sender is not self._sensor:
return
_LOGGER.debug('Callback signal from: %s', msg)
if self._delay > 0 and not self.is_on:
# Set timer to wait until updating the state
@@ -0,0 +1,76 @@
"""
Support for MAX! Window Shutter via MAX! Cube.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/maxcube/
"""
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.maxcube import MAXCUBE_HANDLE
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through all MAX! Devices and add window shutters to HASS."""
cube = hass.data[MAXCUBE_HANDLE].cube
# List of devices
devices = []
for device in cube.devices:
# Create device name by concatenating room name + device name
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
# Only add Window Shutters
if cube.is_windowshutter(device):
# add device to HASS
devices.append(MaxCubeShutter(hass, name, device.rf_address))
if len(devices) > 0:
add_devices(devices)
class MaxCubeShutter(BinarySensorDevice):
"""MAX! Cube BinarySensor device."""
def __init__(self, hass, name, rf_address):
"""Initialize MAX! Cube BinarySensorDevice."""
self._name = name
self._sensor_type = 'opening'
self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE]
self._state = STATE_UNKNOWN
@property
def should_poll(self):
"""Polling is required."""
return True
@property
def name(self):
"""Return the name of the BinarySensorDevice."""
return self._name
@property
def device_class(self):
"""Return the class of this sensor."""
return self._sensor_type
@property
def is_on(self):
"""Return true if the binary sensor is on/open."""
return self._state
def update(self):
"""Get latest data from MAX! Cube."""
self._cubehandle.update()
# Get the device we want to update
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Update our internal state
self._state = device.is_open
@@ -46,7 +46,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
yield from async_add_devices([MqttBinarySensor(
async_add_devices([MqttBinarySensor(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS),
@@ -15,12 +15,14 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS)
CONF_SENSOR_CLASS, CONF_SENSORS, CONF_DEVICE_CLASS,
EVENT_HOMEASSISTANT_START, STATE_ON)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import get_deprecated
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
@@ -66,7 +68,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.error('No sensors added')
return False
yield from async_add_devices(sensors, True)
async_add_devices(sensors, True)
return True
@@ -83,14 +85,30 @@ class BinarySensorTemplate(BinarySensorDevice):
self._device_class = device_class
self._template = value_template
self._state = None
self._entities = entity_ids
@asyncio.coroutine
def async_added_to_hass(self):
"""Register callbacks."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
"""Called when the target device changes state."""
hass.async_add_job(self.async_update_ha_state, True)
self.hass.async_add_job(self.async_update_ha_state(True))
async_track_state_change(
hass, entity_ids, template_bsensor_state_listener)
@callback
def template_bsensor_startup(event):
"""Update template on startup."""
async_track_state_change(
self.hass, self._entities, template_bsensor_state_listener)
self.hass.async_add_job(self.async_update_ha_state(True))
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_bsensor_startup)
@property
def name(self):
@@ -52,7 +52,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
limit_type = config.get(CONF_TYPE)
device_class = get_deprecated(config, CONF_DEVICE_CLASS, CONF_SENSOR_CLASS)
yield from async_add_devices(
async_add_devices(
[ThresholdSensor(hass, entity_id, name, threshold, limit_type,
device_class)], True)
return True
@@ -7,9 +7,9 @@ https://home-assistant.io/components/binary_sensor.vera/
import logging
from homeassistant.components.binary_sensor import (
BinarySensorDevice)
BinarySensorDevice, ENTITY_ID_FORMAT)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
DEPENDENCIES = ['vera']
@@ -30,6 +30,7 @@ class VeraBinarySensor(VeraDevice, BinarySensorDevice):
"""Initialize the binary_sensor."""
self._state = False
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def is_on(self):
@@ -24,7 +24,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the ZigBee binary sensor platform."""
add_devices([ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))])
add_devices(
[ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))], True)
class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice):
+13 -26
View File
@@ -10,6 +10,7 @@ import homeassistant.util.dt as dt_util
from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave
from homeassistant.components.zwave import workaround
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.components.binary_sensor import (
DOMAIN,
BinarySensorDevice)
@@ -18,31 +19,20 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Z-Wave platform for binary sensors."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
def get_device(value, **kwargs):
"""Create zwave entity device."""
device_mapping = workaround.get_device_mapping(value)
if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT:
# Default the multiplier to 4
re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4)
add_devices([
ZWaveTriggerSensor(value, "motion",
hass, re_arm_multiplier * 8)
])
return
return ZWaveTriggerSensor(value, "motion", re_arm_multiplier * 8)
if workaround.get_device_component_mapping(value) == DOMAIN:
add_devices([ZWaveBinarySensor(value, None)])
return
return ZWaveBinarySensor(value, None)
if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY:
add_devices([ZWaveBinarySensor(value, None)])
return ZWaveBinarySensor(value, None)
return None
class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
@@ -77,26 +67,23 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity):
class ZWaveTriggerSensor(ZWaveBinarySensor):
"""Representation of a stateless sensor within Z-Wave."""
def __init__(self, value, device_class, hass, re_arm_sec=60):
def __init__(self, value, device_class, re_arm_sec=60):
"""Initialize the sensor."""
super(ZWaveTriggerSensor, self).__init__(value, device_class)
self._hass = hass
self.re_arm_sec = re_arm_sec
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
# If it's active make sure that we set the timeout tracker
track_point_in_time(
self._hass, self.async_update_ha_state,
self.invalidate_after)
self.invalidate_after = None
def update_properties(self):
"""Called when a value for this entity's node has changed."""
self._state = self._value.data
# only allow this value to be true for re_arm secs
if not self.hass:
return
self.invalidate_after = dt_util.utcnow() + datetime.timedelta(
seconds=self.re_arm_sec)
track_point_in_time(
self._hass, self.async_update_ha_state,
self.hass, self.async_update_ha_state,
self.invalidate_after)
@property
+87
View File
@@ -0,0 +1,87 @@
"""
Support for Blink Home Camera System.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/blink/
"""
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (CONF_USERNAME,
CONF_PASSWORD,
ATTR_FRIENDLY_NAME,
ATTR_ARMED)
from homeassistant.helpers import discovery
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'blink'
REQUIREMENTS = ['blinkpy==0.4.4']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
}, extra=vol.ALLOW_EXTRA)
ARM_SYSTEM_SCHEMA = vol.Schema({
vol.Optional(ATTR_ARMED): cv.boolean
})
ARM_CAMERA_SCHEMA = vol.Schema({
vol.Required(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ARMED): cv.boolean
})
SNAP_PICTURE_SCHEMA = vol.Schema({
vol.Required(ATTR_FRIENDLY_NAME): cv.string
})
class BlinkSystem(object):
"""Blink System class."""
def __init__(self, config_info):
"""Initialize the system."""
import blinkpy
self.blink = blinkpy.Blink(username=config_info[DOMAIN][CONF_USERNAME],
password=config_info[DOMAIN][CONF_PASSWORD])
self.blink.setup_system()
def setup(hass, config):
"""Setup Blink System."""
hass.data[DOMAIN] = BlinkSystem(config)
discovery.load_platform(hass, 'camera', DOMAIN, {}, config)
discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config)
def snap_picture(call):
"""Take a picture."""
cameras = hass.data[DOMAIN].blink.cameras
name = call.data.get(ATTR_FRIENDLY_NAME, '')
if name in cameras:
cameras[name].snap_picture()
def arm_camera(call):
"""Arm a camera."""
cameras = hass.data[DOMAIN].blink.cameras
name = call.data.get(ATTR_FRIENDLY_NAME, '')
value = call.data.get(ATTR_ARMED, True)
if name in cameras:
cameras[name].set_motion_detect(value)
def arm_system(call):
"""Arm the system."""
value = call.data.get(ATTR_ARMED, True)
hass.data[DOMAIN].blink.arm = value
hass.data[DOMAIN].blink.refresh()
hass.services.register(DOMAIN, 'snap_picture', snap_picture,
schema=SNAP_PICTURE_SCHEMA)
hass.services.register(DOMAIN, 'arm_camera', arm_camera,
schema=ARM_CAMERA_SCHEMA)
hass.services.register(DOMAIN, 'arm_system', arm_system,
schema=ARM_SYSTEM_SCHEMA)
return True
@@ -3,8 +3,8 @@ 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
@@ -27,13 +27,13 @@ DOMAIN = 'calendar'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Track states and offer events for calendars."""
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, DOMAIN)
component.setup(config)
yield from component.async_setup(config)
return True
@@ -155,7 +155,7 @@ class CalendarEventDevice(Entity):
start = _get_date(self.data.event['start'])
end = _get_date(self.data.event['end'])
summary = self.data.event['summary']
summary = self.data.event.get('summary', '')
# check if we have an offset tag in the message
# time is HH:MM or MM
+81
View File
@@ -0,0 +1,81 @@
"""
Support for Blink system camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.blink/
"""
import logging
from datetime import timedelta
import requests
from homeassistant.components.blink import DOMAIN
from homeassistant.components.camera import Camera
from homeassistant.util import Throttle
DEPENDENCIES = ['blink']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup a Blink Camera."""
if discovery_info is None:
return
data = hass.data[DOMAIN].blink
devs = list()
for name in data.cameras:
devs.append(BlinkCamera(hass, config, data, name))
add_devices(devs)
class BlinkCamera(Camera):
"""An implementation of a Blink Camera."""
def __init__(self, hass, config, data, name):
"""Initialize a camera."""
super().__init__()
self.data = data
self.hass = hass
self._name = name
self.notifications = self.data.cameras[self._name].notifications
self.response = None
_LOGGER.info("Initialized blink camera %s", self._name)
@property
def name(self):
"""A camera name."""
return self._name
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def request_image(self):
"""An image request from Blink servers."""
_LOGGER.info("Requesting new image from blink servers")
image_url = self.check_for_motion()
header = self.data.cameras[self._name].header
self.response = requests.get(image_url, headers=header, stream=True)
def check_for_motion(self):
"""A method to check if motion has been detected since last update."""
self.data.refresh()
notifs = self.data.cameras[self._name].notifications
if notifs > self.notifications:
# We detected motion at some point
self.data.last_motion()
self.notifications = notifs
# returning motion image currently not working
# return self.data.cameras[self._name].motion['image']
elif notifs < self.notifications:
self.notifications = notifs
return self.data.camera_thumbs[self._name]
def camera_image(self):
"""Return a still image reponse from the camera."""
self.request_image()
return self.response.content
@@ -0,0 +1,67 @@
"""
Support for internal dispatcher image push to Camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.dispatcher/
"""
import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import CONF_NAME
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__)
CONF_SIGNAL = 'signal'
DEFAULT_NAME = 'Dispatcher Camera'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SIGNAL): cv.slugify,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a dispatcher camera."""
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_devices(
[DispatcherCamera(config[CONF_NAME], config[CONF_SIGNAL])])
class DispatcherCamera(Camera):
"""A dispatcher implementation of an camera."""
def __init__(self, name, signal):
"""Initialize a dispatcher camera."""
super().__init__()
self._name = name
self._signal = signal
self._image = None
@asyncio.coroutine
def async_added_to_hass(self):
"""Register dispatcher and callbacks."""
@callback
def async_update_image(image):
"""Update image from dispatcher call."""
self._image = image
async_dispatcher_connect(self.hass, self._signal, async_update_image)
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
return self._image
@property
def name(self):
"""Return the name of this device."""
return self._name
+1 -1
View File
@@ -34,7 +34,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a FFmpeg Camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)):
return
yield from async_add_devices([FFmpegCamera(hass, config)])
async_add_devices([FFmpegCamera(hass, config)])
class FFmpegCamera(Camera):
+10 -4
View File
@@ -47,15 +47,21 @@ class FoscamCamera(Camera):
port = device_info.get(CONF_PORT)
self._base_url = 'http://{}:{}/'.format(ip_address, port)
uri_template = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?' \
+ 'cmd=snapPicture2&usr={}&pwd={}'
self._username = device_info.get(CONF_USERNAME)
self._password = device_info.get(CONF_PASSWORD)
self._snap_picture_url = self._base_url \
+ 'cgi-bin/CGIProxy.fcgi?cmd=snapPicture2&usr=' \
+ self._username + '&pwd=' + self._password
self._snap_picture_url = uri_template.format(
self._username,
self._password
)
self._name = device_info.get(CONF_NAME)
_LOGGER.info('Using the following URL for %s: %s',
self._name, self._snap_picture_url)
self._name, uri_template.format('***', '***'))
def camera_image(self):
"""Return a still image reponse from the camera."""
+1 -1
View File
@@ -44,7 +44,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a generic IP Camera."""
yield from async_add_devices([GenericCamera(hass, config)])
async_add_devices([GenericCamera(hass, config)])
class GenericCamera(Camera):
@@ -20,7 +20,7 @@ CONF_FILE_PATH = 'file_path'
DEFAULT_NAME = 'Local File'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_FILE_PATH): cv.isfile,
vol.Required(CONF_FILE_PATH): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
})
@@ -31,8 +31,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# check filepath given is readable
if not os.access(file_path, os.R_OK):
_LOGGER.error("file path is not readable")
return False
_LOGGER.warning("Could not read camera %s image from file: %s",
config[CONF_NAME], file_path)
add_devices([LocalFile(config[CONF_NAME], file_path)])
@@ -49,8 +49,12 @@ class LocalFile(Camera):
def camera_image(self):
"""Return image response."""
with open(self._file_path, 'rb') as file:
return file.read()
try:
with open(self._file_path, 'rb') as file:
return file.read()
except FileNotFoundError:
_LOGGER.warning("Could not read camera %s image from file: %s",
self._name, self._file_path)
@property
def name(self):
+3 -1
View File
@@ -45,7 +45,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a MJPEG IP Camera."""
yield from async_add_devices([MjpegCamera(hass, config)])
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_devices([MjpegCamera(hass, config)])
def extract_image_from_mjpeg(stream):
+1 -1
View File
@@ -153,7 +153,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)
devices.append(device)
yield from async_add_devices(devices)
async_add_devices(devices)
@asyncio.coroutine
@@ -75,4 +75,4 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
_LOGGER.warning('No active cameras found')
return
yield from async_add_devices(cameras)
async_add_devices(cameras)
+57 -51
View File
@@ -25,6 +25,8 @@ ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time'
ATTR_RESUME_ALL = 'resume_all'
DEFAULT_RESUME_ALL = False
TEMPERATURE_HOLD = 'temp'
VACATION_HOLD = 'vacation'
DEPENDENCIES = ['ecobee']
@@ -112,6 +114,8 @@ class Thermostat(ClimateDevice):
self.thermostat_index)
self._name = self.thermostat['name']
self.hold_temp = hold_temp
self.vacation = None
self._climate_list = self.climate_list
self._operation_list = ['auto', 'auxHeatOnly', 'cool',
'heat', 'off']
self.update_without_throttle = False
@@ -187,29 +191,30 @@ class Thermostat(ClimateDevice):
def current_hold_mode(self):
"""Return current hold mode."""
events = self.thermostat['events']
if any((event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) <= 1)
or event['type'] == 'autoAway'
for event in events):
# away hold is auto away or a temporary hold from away climate
hold = 'away'
elif any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events):
# a permanent away is not considered a hold, but away_mode
hold = None
elif any(event['holdClimateRef'] == 'home' or
event['type'] == 'autoHome'
for event in events):
# home mode is auto home or any home hold
hold = 'home'
elif any(event['type'] == 'hold' and event['running']
for event in events):
hold = 'temp'
# temperature hold is any other hold not based on climate
else:
hold = None
return hold
for event in events:
if event['running']:
if event['type'] == 'hold':
if event['holdClimateRef'] == 'away':
if int(event['endDate'][0:4]) - \
int(event['startDate'][0:4]) <= 1:
# a temporary hold from away climate is a hold
return 'away'
else:
# a premanent hold from away climate is away_mode
return None
elif event['holdClimateRef'] != "":
# any other hold based on climate
return event['holdClimateRef']
else:
# any hold not based on a climate is a temp hold
return TEMPERATURE_HOLD
elif event['type'].startswith('auto'):
# all auto modes are treated as holds
return event['type'][4:].lower()
elif event['type'] == 'vacation':
self.vacation = event['name']
return VACATION_HOLD
return None
@property
def current_operation(self):
@@ -232,8 +237,11 @@ class Thermostat(ClimateDevice):
@property
def mode(self):
"""Return current mode ie. home, away, sleep."""
return self.thermostat['program']['currentClimateRef']
"""Return current mode, as the user-visible name."""
cur = self.thermostat['program']['currentClimateRef']
climates = self.thermostat['program']['climates']
current = list(filter(lambda x: x['climateRef'] == cur, climates))
return current[0]['name']
@property
def fan_min_on_time(self):
@@ -261,52 +269,44 @@ class Thermostat(ClimateDevice):
"fan": self.fan,
"mode": self.mode,
"operation": operation,
"climate_list": self.climate_list,
"fan_min_on_time": self.fan_min_on_time
}
def is_vacation_on(self):
"""Return true if vacation mode is on."""
events = self.thermostat['events']
return any(event['type'] == 'vacation' and event['running']
for event in events)
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
events = self.thermostat['events']
return any(event['holdClimateRef'] == 'away' and
int(event['endDate'][0:4])-int(event['startDate'][0:4]) > 1
for event in events)
return self.current_hold_mode == 'away'
def turn_away_mode_on(self):
"""Turn away on."""
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", 'indefinite')
self.update_without_throttle = True
self.set_hold_mode('away')
def turn_away_mode_off(self):
"""Turn away off."""
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
self.set_hold_mode(None)
def set_hold_mode(self, hold_mode):
"""Set hold mode (away, home, temp)."""
"""Set hold mode (away, home, temp, sleep, etc.)."""
hold = self.current_hold_mode
if hold == hold_mode:
# no change, so no action required
return
elif hold_mode == 'away':
self.data.ecobee.set_climate_hold(self.thermostat_index,
"away", self.hold_preference())
elif hold_mode == 'home':
self.data.ecobee.set_climate_hold(self.thermostat_index,
"home", self.hold_preference())
elif hold_mode == 'temp':
self.set_temp_hold(int(self.current_temperature))
elif hold_mode == 'None' or hold_mode is None:
if hold == VACATION_HOLD:
self.data.ecobee.delete_vacation(self.thermostat_index,
self.vacation)
else:
self.data.ecobee.resume_program(self.thermostat_index)
else:
self.data.ecobee.resume_program(self.thermostat_index)
self.update_without_throttle = True
if hold_mode == TEMPERATURE_HOLD:
self.set_temp_hold(int(self.current_temperature))
else:
self.data.ecobee.set_climate_hold(self.thermostat_index,
hold_mode,
self.hold_preference())
self.update_without_throttle = True
def set_auto_temp_hold(self, heat_temp, cool_temp):
"""Set temperature hold in auto mode."""
@@ -382,3 +382,9 @@ class Thermostat(ClimateDevice):
# as an indefinite away hold is interpreted as away_mode
else:
return 'nextTransition'
@property
def climate_list(self):
"""Return the list of climates currently available."""
climates = self.thermostat['program']['climates']
return list(map((lambda x: x['name']), climates))
@@ -16,7 +16,8 @@ from homeassistant.components.climate import (
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE)
from homeassistant.helpers import condition
from homeassistant.helpers.event import async_track_state_change
from homeassistant.helpers.event import (
async_track_state_change, async_track_time_interval)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -35,6 +36,7 @@ CONF_TARGET_TEMP = 'target_temp'
CONF_AC_MODE = 'ac_mode'
CONF_MIN_DUR = 'min_cycle_duration'
CONF_TOLERANCE = 'tolerance'
CONF_KEEP_ALIVE = 'keep_alive'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -47,6 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float),
vol.Optional(CONF_KEEP_ALIVE): vol.All(
cv.time_period, cv.positive_timedelta),
})
@@ -62,10 +66,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
ac_mode = config.get(CONF_AC_MODE)
min_cycle_duration = config.get(CONF_MIN_DUR)
tolerance = config.get(CONF_TOLERANCE)
keep_alive = config.get(CONF_KEEP_ALIVE)
yield from async_add_devices([GenericThermostat(
async_add_devices([GenericThermostat(
hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp,
target_temp, ac_mode, min_cycle_duration, tolerance)])
target_temp, ac_mode, min_cycle_duration, tolerance, keep_alive)])
class GenericThermostat(ClimateDevice):
@@ -73,7 +78,7 @@ class GenericThermostat(ClimateDevice):
def __init__(self, hass, name, heater_entity_id, sensor_entity_id,
min_temp, max_temp, target_temp, ac_mode, min_cycle_duration,
tolerance):
tolerance, keep_alive):
"""Initialize the thermostat."""
self.hass = hass
self._name = name
@@ -81,6 +86,7 @@ class GenericThermostat(ClimateDevice):
self.ac_mode = ac_mode
self.min_cycle_duration = min_cycle_duration
self._tolerance = tolerance
self._keep_alive = keep_alive
self._active = False
self._cur_temp = None
@@ -94,6 +100,10 @@ class GenericThermostat(ClimateDevice):
async_track_state_change(
hass, heater_entity_id, self._async_switch_changed)
if self._keep_alive:
async_track_time_interval(
hass, self._async_keep_alive, self._keep_alive)
sensor_state = hass.states.get(sensor_entity_id)
if sensor_state:
self._async_update_temp(sensor_state)
@@ -180,6 +190,14 @@ class GenericThermostat(ClimateDevice):
return
self.hass.async_add_job(self.async_update_ha_state())
@callback
def _async_keep_alive(self, time):
"""Called at constant intervals for keep-alive purposes."""
if self.current_operation in [STATE_COOL, STATE_HEAT]:
switch.async_turn_on(self.hass, self.heater_entity_id)
else:
switch.async_turn_off(self.hass, self.heater_entity_id)
@callback
def _async_update_temp(self, state):
"""Update thermostat with latest state from sensor."""
+145 -20
View File
@@ -6,10 +6,15 @@ https://home-assistant.io/components/climate.honeywell/
"""
import logging
import socket
import datetime
import voluptuous as vol
import requests
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA)
from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA,
ATTR_FAN_MODE, ATTR_FAN_LIST,
ATTR_OPERATION_MODE,
ATTR_OPERATION_LIST)
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE)
@@ -21,27 +26,35 @@ REQUIREMENTS = ['evohomeclient==0.2.5',
_LOGGER = logging.getLogger(__name__)
ATTR_FAN = 'fan'
ATTR_FANMODE = 'fanmode'
ATTR_SYSTEM_MODE = 'system_mode'
ATTR_CURRENT_OPERATION = 'equipment_output_status'
CONF_AWAY_TEMPERATURE = 'away_temperature'
CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature'
CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature'
CONF_REGION = 'region'
DEFAULT_AWAY_TEMPERATURE = 16
DEFAULT_COOL_AWAY_TEMPERATURE = 30
DEFAULT_HEAT_AWAY_TEMPERATURE = 16
DEFAULT_REGION = 'eu'
REGIONS = ['eu', 'us']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE):
vol.Coerce(float),
vol.Optional(CONF_AWAY_TEMPERATURE,
default=DEFAULT_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_COOL_AWAY_TEMPERATURE,
default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_HEAT_AWAY_TEMPERATURE,
default=DEFAULT_HEAT_AWAY_TEMPERATURE): vol.Coerce(float),
vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS),
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the HoneywelL thermostat."""
"""Setup the Honeywell thermostat."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
region = config.get(CONF_REGION)
@@ -88,8 +101,11 @@ def _setup_us(username, password, config, add_devices):
dev_id = config.get('thermostat')
loc_id = config.get('location')
cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE)
heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE)
add_devices([HoneywellUSThermostat(client, device)
add_devices([HoneywellUSThermostat(client, device, cool_away_temp,
heat_away_temp, username, password)
for location in client.locations_by_id.values()
for device in location.devices_by_id.values()
if ((not loc_id or location.locationid == loc_id) and
@@ -160,7 +176,7 @@ class RoundThermostat(ClimateDevice):
def turn_away_mode_on(self):
"""Turn away on.
Evohome does have a proprietary away mode, but it doesn't really work
Honeywell does have a proprietary away mode, but it doesn't really work
the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
@@ -199,10 +215,16 @@ class RoundThermostat(ClimateDevice):
class HoneywellUSThermostat(ClimateDevice):
"""Representation of a Honeywell US Thermostat."""
def __init__(self, client, device):
def __init__(self, client, device, cool_away_temp,
heat_away_temp, username, password):
"""Initialize the thermostat."""
self._client = client
self._device = device
self._cool_away_temp = cool_away_temp
self._heat_away_temp = heat_away_temp
self._away = False
self._username = username
self._password = password
@property
def is_fan_on(self):
@@ -236,7 +258,10 @@ class HoneywellUSThermostat(ClimateDevice):
@property
def current_operation(self: ClimateDevice) -> str:
"""Return current operation ie. heat, cool, idle."""
return getattr(self._device, ATTR_SYSTEM_MODE, None)
oper = getattr(self._device, ATTR_CURRENT_OPERATION, None)
if oper == "off":
oper = "idle"
return oper
def set_temperature(self, **kwargs):
"""Set target temperature."""
@@ -245,29 +270,84 @@ class HoneywellUSThermostat(ClimateDevice):
return
import somecomfort
try:
if self._device.system_mode == 'cool':
self._device.setpoint_cool = temperature
else:
self._device.setpoint_heat = temperature
# Get current mode
mode = self._device.system_mode
# Set hold if this is not the case
if getattr(self._device, "hold_{}".format(mode)) is False:
# Get next period key
next_period_key = '{}NextPeriod'.format(mode.capitalize())
# Get next period raw value
next_period = self._device.raw_ui_data.get(next_period_key)
# Get next period time
hour, minute = divmod(next_period * 15, 60)
# Set hold time
setattr(self._device,
"hold_{}".format(mode),
datetime.time(hour, minute))
# Set temperature
setattr(self._device,
"setpoint_{}".format(mode),
temperature)
except somecomfort.SomeComfortError:
_LOGGER.error('Temperature %.1f out of range', temperature)
@property
def device_state_attributes(self):
"""Return the device specific state attributes."""
return {
import somecomfort
data = {
ATTR_FAN: (self.is_fan_on and 'running' or 'idle'),
ATTR_FANMODE: self._device.fan_mode,
ATTR_SYSTEM_MODE: self._device.system_mode,
ATTR_FAN_MODE: self._device.fan_mode,
ATTR_OPERATION_MODE: self._device.system_mode,
}
data[ATTR_FAN_LIST] = somecomfort.FAN_MODES
data[ATTR_OPERATION_LIST] = somecomfort.SYSTEM_MODES
return data
@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
return self._away
def turn_away_mode_on(self):
"""Turn away on."""
pass
"""Turn away on.
Somecomfort does have a proprietary away mode, but it doesn't really
work the way it should. For example: If you set a temperature manually
it doesn't get overwritten when away mode is switched on.
"""
self._away = True
import somecomfort
try:
# Get current mode
mode = self._device.system_mode
except somecomfort.SomeComfortError:
_LOGGER.error('Can not get system mode')
return
try:
# Set permanent hold
setattr(self._device,
"hold_{}".format(mode),
True)
# Set temperature
setattr(self._device,
"setpoint_{}".format(mode),
getattr(self, "_{}_away_temp".format(mode)))
except somecomfort.SomeComfortError:
_LOGGER.error('Temperature %.1f out of range',
getattr(self, "_{}_away_temp".format(mode)))
def turn_away_mode_off(self):
"""Turn away off."""
pass
self._away = False
import somecomfort
try:
# Disabling all hold modes
self._device.hold_cool = False
self._device.hold_heat = False
except somecomfort.SomeComfortError:
_LOGGER.error('Can not stop hold mode')
def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None:
"""Set the system mode (Cool, Heat, etc)."""
@@ -276,4 +356,49 @@ class HoneywellUSThermostat(ClimateDevice):
def update(self):
"""Update the state."""
self._device.refresh()
import somecomfort
retries = 3
while retries > 0:
try:
self._device.refresh()
break
except (somecomfort.client.APIRateLimited, OSError,
requests.exceptions.ReadTimeout) as exp:
retries -= 1
if retries == 0:
raise exp
if not self._retry():
raise exp
_LOGGER.error("SomeComfort update failed, Retrying "
"- Error: %s", exp)
def _retry(self):
"""Recreate a new somecomfort client.
When we got an error, the best way to be sure that the next query
will succeed, is to recreate a new somecomfort client.
"""
import somecomfort
try:
self._client = somecomfort.SomeComfort(self._username,
self._password)
except somecomfort.AuthError:
_LOGGER.error('Failed to login to honeywell account %s',
self._username)
return False
except somecomfort.SomeComfortError as ex:
_LOGGER.error('Failed to initialize honeywell client: %s',
str(ex))
return False
devices = [device
for location in self._client.locations_by_id.values()
for device in location.devices_by_id.values()
if device.name == self._device.name]
if len(devices) != 1:
_LOGGER.error('Failed to find device %s', self._device.name)
return False
self._device = devices[0]
return True
+216
View File
@@ -0,0 +1,216 @@
"""
Support for MAX! Thermostats via MAX! Cube.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/maxcube/
"""
import socket
import logging
from homeassistant.components.climate import ClimateDevice, STATE_AUTO
from homeassistant.components.maxcube import MAXCUBE_HANDLE
from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE
from homeassistant.const import STATE_UNKNOWN
_LOGGER = logging.getLogger(__name__)
STATE_MANUAL = "manual"
STATE_BOOST = "boost"
STATE_VACATION = "vacation"
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through all MAX! Devices and add thermostats to HASS."""
cube = hass.data[MAXCUBE_HANDLE].cube
# List of devices
devices = []
for device in cube.devices:
# Create device name by concatenating room name + device name
name = "%s %s" % (cube.room_by_id(device.room_id).name, device.name)
# Only add thermostats and wallthermostats
if cube.is_thermostat(device) or cube.is_wallthermostat(device):
# Add device to HASS
devices.append(MaxCubeClimate(hass, name, device.rf_address))
# Add all devices at once
if len(devices) > 0:
add_devices(devices)
class MaxCubeClimate(ClimateDevice):
"""MAX! Cube ClimateDevice."""
def __init__(self, hass, name, rf_address):
"""Initialize MAX! Cube ClimateDevice."""
self._name = name
self._unit_of_measurement = TEMP_CELSIUS
self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST,
STATE_VACATION]
self._rf_address = rf_address
self._cubehandle = hass.data[MAXCUBE_HANDLE]
@property
def should_poll(self):
"""Polling is required."""
return True
@property
def name(self):
"""Return the name of the ClimateDevice."""
return self._name
@property
def min_temp(self):
"""Return the minimum temperature."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return minimum temperature
return self.map_temperature_max_hass(device.min_temperature)
@property
def max_temp(self):
"""Return the maximum temperature."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return maximum temperature
return self.map_temperature_max_hass(device.max_temperature)
@property
def temperature_unit(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def current_temperature(self):
"""Return the current temperature."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return current temperature
return self.map_temperature_max_hass(device.actual_temperature)
@property
def current_operation(self):
"""Return current operation (auto, manual, boost, vacation)."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Mode Mapping
return self.map_mode_max_hass(device.mode)
@property
def operation_list(self):
"""List of available operation modes."""
return self._operation_list
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
# Get the device we want (does not do any IO, just reads from memory)
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Map and return target temperature
return self.map_temperature_max_hass(device.target_temperature)
def set_temperature(self, **kwargs):
"""Set new target temperatures."""
# Fail is target temperature has not been supplied as argument
if kwargs.get(ATTR_TEMPERATURE) is None:
return False
# Determine the new target temperature
target_temperature = kwargs.get(ATTR_TEMPERATURE)
# Write the target temperature to the MAX! Cube.
device = self._cubehandle.cube.device_by_rf(self._rf_address)
cube = self._cubehandle.cube
with self._cubehandle.mutex:
try:
cube.set_target_temperature(device, target_temperature)
except (socket.timeout, socket.error):
_LOGGER.error("Setting target temperature failed")
return False
def set_operation_mode(self, operation_mode):
"""Set new operation mode."""
# Get the device we want to update
device = self._cubehandle.cube.device_by_rf(self._rf_address)
# Mode Mapping
mode = self.map_mode_hass_max(operation_mode)
# Write new mode to thermostat
if mode is None:
return False
with self._cubehandle.mutex:
try:
self._cubehandle.cube.set_mode(device, mode)
except (socket.timeout, socket.error):
_LOGGER.error("Setting operation mode failed")
return False
def update(self):
"""Get latest data from MAX! Cube."""
# Update the CubeHandle
self._cubehandle.update()
@staticmethod
def map_temperature_max_hass(temperature):
"""Map Temperature from MAX! to HASS."""
if temperature is None:
return STATE_UNKNOWN
return temperature
@staticmethod
def map_mode_hass_max(operation_mode):
"""Map HASS Operation Modes to MAX! Operation Modes."""
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
if operation_mode == STATE_AUTO:
mode = MAX_DEVICE_MODE_AUTOMATIC
elif operation_mode == STATE_MANUAL:
mode = MAX_DEVICE_MODE_MANUAL
elif operation_mode == STATE_VACATION:
mode = MAX_DEVICE_MODE_VACATION
elif operation_mode == STATE_BOOST:
mode = MAX_DEVICE_MODE_BOOST
else:
mode = None
return mode
@staticmethod
def map_mode_max_hass(mode):
"""Map MAX! Operation Modes to HASS Operation Modes."""
from maxcube.device import \
MAX_DEVICE_MODE_AUTOMATIC, \
MAX_DEVICE_MODE_MANUAL, \
MAX_DEVICE_MODE_VACATION, \
MAX_DEVICE_MODE_BOOST
if mode == MAX_DEVICE_MODE_AUTOMATIC:
operation_mode = STATE_AUTO
elif mode == MAX_DEVICE_MODE_MANUAL:
operation_mode = STATE_MANUAL
elif mode == MAX_DEVICE_MODE_VACATION:
operation_mode = STATE_VACATION
elif mode == MAX_DEVICE_MODE_BOOST:
operation_mode = STATE_BOOST
else:
operation_mode = None
return operation_mode
+3 -2
View File
@@ -7,14 +7,14 @@ https://home-assistant.io/components/switch.vera/
import logging
from homeassistant.util import convert
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate import ClimateDevice, ENTITY_ID_FORMAT
from homeassistant.const import (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
ATTR_TEMPERATURE)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
DEPENDENCIES = ['vera']
@@ -37,6 +37,7 @@ class VeraThermostat(VeraDevice, ClimateDevice):
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def current_operation(self):
+10 -13
View File
@@ -11,6 +11,7 @@ from homeassistant.components.climate import DOMAIN
from homeassistant.components.climate import ClimateDevice
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import (
TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
@@ -32,19 +33,10 @@ DEVICE_MAPPINGS = {
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Z-Wave Climate devices."""
if discovery_info is None or zwave.NETWORK is None:
_LOGGER.debug("No discovery_info=%s or no NETWORK=%s",
discovery_info, zwave.NETWORK)
return
def get_device(hass, value, **kwargs):
"""Create zwave entity device."""
temp_unit = hass.config.units.temperature_unit
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
value.set_change_verified(False)
add_devices([ZWaveClimate(value, temp_unit)])
_LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s",
discovery_info, zwave.NETWORK)
return ZWaveClimate(value, temp_unit)
class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
@@ -224,7 +216,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self.set_value(
class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT,
index=self._index, data=temperature)
self.update_ha_state()
self.schedule_update_ha_state()
def set_fan_mode(self, fan):
"""Set new target fan mode."""
@@ -254,3 +246,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
if self._fan_state:
data[ATTR_FAN_STATE] = self._fan_state
return data
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
return None
+5 -2
View File
@@ -6,7 +6,7 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.const import EVENT_COMPONENT_LOADED
from homeassistant.bootstrap import (
from homeassistant.setup import (
async_prepare_setup_platform, ATTR_COMPONENT)
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
@@ -129,5 +129,8 @@ def _read(path):
def _write(path, data):
"""Write YAML helper."""
# Do it before opening file. If dump causes error it will now not
# truncate the file.
data = dump(data)
with open(path, 'w', encoding='utf-8') as outfile:
outfile.write(dump(data))
outfile.write(data)
+1 -1
View File
@@ -54,7 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
yield from async_add_devices([MqttCover(
async_add_devices([MqttCover(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),
+2 -2
View File
@@ -14,8 +14,8 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = [
'https://github.com/arraylabs/pymyq/archive/v0.0.6.zip'
'#pymyq==0.0.6']
'https://github.com/arraylabs/pymyq/archive/v0.0.7.zip'
'#pymyq==0.0.7']
COVER_SCHEMA = vol.Schema({
vol.Required(CONF_TYPE): cv.string,
+3 -2
View File
@@ -6,9 +6,9 @@ https://home-assistant.io/components/cover.vera/
"""
import logging
from homeassistant.components.cover import CoverDevice
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
DEPENDENCIES = ['vera']
@@ -28,6 +28,7 @@ class VeraCover(VeraDevice, CoverDevice):
def __init__(self, vera_device, controller):
"""Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def current_cover_position(self):
+32 -27
View File
@@ -11,6 +11,7 @@ from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE)
from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.components.zwave import workaround
from homeassistant.components.cover import CoverDevice
@@ -19,27 +20,15 @@ _LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and return Z-Wave covers."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
def get_device(value, **kwargs):
"""Create zwave entity device."""
if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL
and value.index == 0):
value.set_change_verified(False)
add_devices([ZwaveRollershutter(value)])
return ZwaveRollershutter(value)
elif (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY or
value.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR):
if (value.type != zwave.const.TYPE_BOOL and
value.genre != zwave.const.GENRE_USER):
return
value.set_change_verified(False)
add_devices([ZwaveGarageDoor(value)])
else:
return
return ZwaveGarageDoor(value)
return None
class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
@@ -52,6 +41,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
self._node = value.node
self._open_id = None
self._close_id = None
self._current_position_id = None
self._current_position = None
self._workaround = workaround.get_device_mapping(value)
@@ -59,20 +49,35 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
_LOGGER.debug("Using workaround %s", self._workaround)
self.update_properties()
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
if not self._node.is_ready:
return None
return [self._current_position_id]
def update_properties(self):
"""Callback on data changes for node values."""
# Position value
self._current_position = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Level'], member='data')
self._open_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Open', 'Up'], member='value_id')
self._close_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
if not self._node.is_ready:
if self._current_position_id is None:
self._current_position_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Level'], member='value_id')
if self._open_id is None:
self._open_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Open', 'Up'], member='value_id')
if self._close_id is None:
self._close_id = self.get_value(
class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL,
label=['Close', 'Down'], member='value_id')
if self._open_id and self._close_id and \
self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE:
self._open_id, self._close_id = self._close_id, self._open_id
self._workaround = None
self._current_position = self._node.get_dimmer_level(
self._current_position_id)
@property
def is_closed(self):
+96 -74
View File
@@ -4,6 +4,7 @@ Sets up a demo environment that mimics interaction with devices.
For more details about this component, please refer to the documentation
https://home-assistant.io/components/demo/
"""
import asyncio
import time
import homeassistant.bootstrap as bootstrap
@@ -34,7 +35,8 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
]
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Setup a demo environment."""
group = loader.get_component('group')
configurator = loader.get_component('configurator')
@@ -44,7 +46,7 @@ def setup(hass, config):
config.setdefault(DOMAIN, {})
if config[DOMAIN].get('hide_demo_state') != 1:
hass.states.set('a.Demo_Mode', 'Enabled')
hass.states.async_set('a.Demo_Mode', 'Enabled')
# Setup sun
if not hass.config.latitude:
@@ -53,50 +55,71 @@ def setup(hass, config):
if not hass.config.longitude:
hass.config.longitude = 117.22743
bootstrap.setup_component(hass, 'sun')
tasks = [
bootstrap.async_setup_component(hass, 'sun')
]
# Setup demo platforms
demo_config = config.copy()
for component in COMPONENTS_WITH_DEMO_PLATFORM:
demo_config[component] = {CONF_PLATFORM: 'demo'}
bootstrap.setup_component(hass, component, demo_config)
tasks.append(
bootstrap.async_setup_component(hass, component, demo_config))
# Set up input select
tasks.append(bootstrap.async_setup_component(
hass, 'input_select',
{'input_select':
{'living_room_preset': {'options': ['Visitors',
'Visitors with kids',
'Home Alone']},
'who_cooks': {'icon': 'mdi:panda',
'initial': 'Anne Therese',
'name': 'Cook today',
'options': ['Paulus', 'Anne Therese']}}}))
# Set up input boolean
tasks.append(bootstrap.async_setup_component(
hass, 'input_boolean',
{'input_boolean': {'notify': {
'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}}))
# Set up input boolean
tasks.append(bootstrap.async_setup_component(
hass, 'input_slider',
{'input_slider': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
'name': 'Allowed Noise',
'unit_of_measurement': 'dB'}}}))
# Set up weblink
tasks.append(bootstrap.async_setup_component(
hass, 'weblink',
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}}))
results = yield from asyncio.gather(*tasks, loop=hass.loop)
if any(not result for result in results):
return False
# Setup example persistent notification
persistent_notification.create(
persistent_notification.async_create(
hass, 'This is an example of a persistent notification.',
title='Example Notification')
# Setup room groups
lights = sorted(hass.states.entity_ids('light'))
switches = sorted(hass.states.entity_ids('switch'))
media_players = sorted(hass.states.entity_ids('media_player'))
lights = sorted(hass.states.async_entity_ids('light'))
switches = sorted(hass.states.async_entity_ids('switch'))
media_players = sorted(hass.states.async_entity_ids('media_player'))
group.Group.create_group(hass, 'living room', [
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights'])
group.Group.create_group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance'])
group.Group.create_group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door'])
group.Group.create_group(hass, 'doors', [
'lock.front_door', 'lock.kitchen_door',
'garage_door.right_garage_door', 'garage_door.left_garage_door'])
group.Group.create_group(hass, 'automations', [
'input_select.who_cooks', 'input_boolean.notify', ])
group.Group.create_group(hass, 'people', [
'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
'device_tracker.demo_paulus'])
group.Group.create_group(hass, 'downstairs', [
'group.living_room', 'group.kitchen',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window', 'group.doors',
'thermostat.ecobee',
], view=True)
tasks2 = []
# Setup scripts
bootstrap.setup_component(
tasks2.append(bootstrap.async_setup_component(
hass, 'script',
{'script': {
'demo': {
@@ -115,10 +138,10 @@ def setup(hass, config):
'service': 'light.turn_off',
'data': {ATTR_ENTITY_ID: lights[0]}
}]
}}})
}}}))
# Setup scenes
bootstrap.setup_component(
tasks2.append(bootstrap.async_setup_component(
hass, 'scene',
{'scene': [
{'name': 'Romantic lights',
@@ -132,41 +155,37 @@ def setup(hass, config):
switches[0]: True,
switches[1]: False,
}},
]})
]}))
# Set up input select
bootstrap.setup_component(
hass, 'input_select',
{'input_select':
{'living_room_preset': {'options': ['Visitors',
'Visitors with kids',
'Home Alone']},
'who_cooks': {'icon': 'mdi:panda',
'initial': 'Anne Therese',
'name': 'Cook today',
'options': ['Paulus', 'Anne Therese']}}})
# Set up input boolean
bootstrap.setup_component(
hass, 'input_boolean',
{'input_boolean': {'notify': {'icon': 'mdi:car',
'initial': False,
'name': 'Notify Anne Therese is home'}}})
tasks2.append(group.Group.async_create_group(hass, 'living room', [
lights[1], switches[0], 'input_select.living_room_preset',
'rollershutter.living_room_window', media_players[1],
'scene.romantic_lights']))
tasks2.append(group.Group.async_create_group(hass, 'bedroom', [
lights[0], switches[1], media_players[0],
'input_slider.noise_allowance']))
tasks2.append(group.Group.async_create_group(hass, 'kitchen', [
lights[2], 'rollershutter.kitchen_window', 'lock.kitchen_door']))
tasks2.append(group.Group.async_create_group(hass, 'doors', [
'lock.front_door', 'lock.kitchen_door',
'garage_door.right_garage_door', 'garage_door.left_garage_door']))
tasks2.append(group.Group.async_create_group(hass, 'automations', [
'input_select.who_cooks', 'input_boolean.notify', ]))
tasks2.append(group.Group.async_create_group(hass, 'people', [
'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy',
'device_tracker.demo_paulus']))
tasks2.append(group.Group.async_create_group(hass, 'downstairs', [
'group.living_room', 'group.kitchen',
'scene.romantic_lights', 'rollershutter.kitchen_window',
'rollershutter.living_room_window', 'group.doors',
'thermostat.ecobee',
], view=True))
# Set up input boolean
bootstrap.setup_component(
hass, 'input_slider',
{'input_slider': {
'noise_allowance': {'icon': 'mdi:bell-ring',
'min': 0,
'max': 10,
'name': 'Allowed Noise',
'unit_of_measurement': 'dB'}}})
results = yield from asyncio.gather(*tasks2, loop=hass.loop)
if any(not result for result in results):
return False
# Set up weblink
bootstrap.setup_component(
hass, 'weblink',
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}})
# Setup configurator
configurator_ids = []
@@ -184,14 +203,17 @@ def setup(hass, config):
else:
configurator.request_done(configurator_ids[0])
request_id = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips Hue "
"with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",
submit_caption="I have pressed the button"
)
def setup_configurator():
"""Setup configurator."""
request_id = configurator.request_config(
hass, "Philips Hue", hue_configuration_callback,
description=("Press the button on the bridge to register Philips "
"Hue with Home Assistant."),
description_image="/static/images/config_philips_hue.jpg",
submit_caption="I have pressed the button"
)
configurator_ids.append(request_id)
configurator_ids.append(request_id)
hass.async_add_job(setup_configurator)
return True
@@ -14,12 +14,11 @@ import aiohttp
import async_timeout
import voluptuous as vol
from homeassistant.bootstrap import (
async_prepare_setup_platform, async_log_exception)
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import callback
from homeassistant.components import group, zone
from homeassistant.components.discovery import SERVICE_NETGEAR
from homeassistant.config import load_yaml_config_file
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import config_per_platform, discovery
@@ -133,18 +132,6 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
devices = yield from async_load_config(yaml_path, hass, consider_home)
tracker = DeviceTracker(hass, consider_home, track_new, devices)
# added_to_hass
add_tasks = [device.async_added_to_hass() for device in devices
if device.track]
if add_tasks:
yield from asyncio.wait(add_tasks, loop=hass.loop)
# update tracked devices
update_tasks = [device.async_update_ha_state() for device in devices
if device.track]
if update_tasks:
yield from asyncio.wait(update_tasks, loop=hass.loop)
@asyncio.coroutine
def async_setup_platform(p_type, p_config, disc_info=None):
"""Setup a device tracker platform."""
@@ -227,6 +214,8 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
hass.services.async_register(
DOMAIN, SERVICE_SEE, async_see_service, descriptions.get(SERVICE_SEE))
# restore
yield from tracker.async_setup_tracked_device()
return True
@@ -357,6 +346,27 @@ class DeviceTracker(object):
device.stale(now):
self.hass.async_add_job(device.async_update_ha_state(True))
@asyncio.coroutine
def async_setup_tracked_device(self):
"""Setup all not exists tracked devices.
This method is a coroutine.
"""
@asyncio.coroutine
def async_init_single_device(dev):
"""Init a single device_tracker entity."""
yield from dev.async_added_to_hass()
yield from dev.async_update_ha_state()
tasks = []
for device in self.devices.values():
if device.track and not device.last_seen:
tasks.append(self.hass.async_add_job(
async_init_single_device(device)))
if tasks:
yield from asyncio.wait(tasks, loop=self.hass.loop)
class Device(Entity):
"""Represent a tracked device."""
@@ -110,7 +110,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
_LOGGER.info("Discovered Bluetooth LE device %s", address)
see_device(address, devs[address], new_device=True)
track_point_in_utc_time(hass, update_ble, now + interval)
track_point_in_utc_time(hass, update_ble, dt_util.utcnow() + interval)
update_ble(dt_util.utcnow())
@@ -82,7 +82,8 @@ def setup_scanner(hass, config, see, discovery_info=None):
see_device((mac, result))
except bluetooth.BluetoothError:
_LOGGER.exception('Error looking up bluetooth device!')
track_point_in_utc_time(hass, update_bluetooth, now + interval)
track_point_in_utc_time(
hass, update_bluetooth, dt_util.utcnow() + interval)
update_bluetooth(dt_util.utcnow())
@@ -197,13 +197,22 @@ class Icloud(DeviceScanner):
def icloud_trusted_device_callback(self, callback_data):
"""The trusted device is chosen."""
self._trusted_device = int(callback_data.get('0', '0'))
self._trusted_device = int(callback_data.get('trusted_device'))
self._trusted_device = self.api.trusted_devices[self._trusted_device]
if not self.api.send_verification_code(self._trusted_device):
_LOGGER.error('Failed to send verification code')
self._trusted_device = None
return
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
configurator.request_done(request_id)
# Trigger the next step immediately
self.icloud_need_verification_code()
def icloud_need_trusted_device(self):
"""We need a trusted device."""
configurator = get_component('configurator')
@@ -213,7 +222,10 @@ class Icloud(DeviceScanner):
devicesstring = ''
devices = self.api.trusted_devices
for i, device in enumerate(devices):
devicesstring += "{}: {};".format(i, device.get('deviceName'))
devicename = device.get(
'deviceName',
'SMS to %s' % device.get('phoneNumber'))
devicesstring += "{}: {};".format(i, devicename)
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
@@ -223,12 +235,27 @@ class Icloud(DeviceScanner):
' the index from this list: ' + devicesstring),
entity_picture="/static/images/config_icloud.png",
submit_caption='Confirm',
fields=[{'id': '0'}]
fields=[{'id': 'trusted_device', 'name': 'Trusted Device'}]
)
def icloud_verification_callback(self, callback_data):
"""The trusted device is chosen."""
self._verification_code = callback_data.get('0')
from pyicloud.exceptions import PyiCloudException
self._verification_code = callback_data.get('code')
try:
if not self.api.validate_verification_code(
self._trusted_device, self._verification_code):
raise PyiCloudException('Unknown failure')
except PyiCloudException as error:
# Reset to the inital 2FA state to allow the user to retry
_LOGGER.error('Failed to verify verification code: %s', error)
self._trusted_device = None
self._verification_code = None
# Trigger the next step immediately
self.icloud_need_trusted_device()
if self.accountname in _CONFIGURING:
request_id = _CONFIGURING.pop(self.accountname)
configurator = get_component('configurator')
@@ -240,22 +267,17 @@ class Icloud(DeviceScanner):
if self.accountname in _CONFIGURING:
return
if self.api.send_verification_code(self._trusted_device):
self._verification_code = 'waiting'
_CONFIGURING[self.accountname] = configurator.request_config(
self.hass, 'iCloud {}'.format(self.accountname),
self.icloud_verification_callback,
description=('Please enter the validation code:'),
entity_picture="/static/images/config_icloud.png",
submit_caption='Confirm',
fields=[{'code': '0'}]
fields=[{'id': 'code', 'name': 'code'}]
)
def keep_alive(self, now):
"""Keep the api alive."""
from pyicloud.exceptions import PyiCloud2FARequiredError
if self.api is None:
self.reset_account_icloud()
@@ -263,9 +285,8 @@ class Icloud(DeviceScanner):
return
if self.api.requires_2fa:
from pyicloud.exceptions import PyiCloudException
try:
self.api.authenticate()
except PyiCloud2FARequiredError:
if self._trusted_device is None:
self.icloud_need_trusted_device()
return
@@ -274,12 +295,14 @@ class Icloud(DeviceScanner):
self.icloud_need_verification_code()
return
if self._verification_code == 'waiting':
return
self.api.authenticate()
if self.api.requires_2fa:
raise Exception('Unknown failure')
if self.api.validate_verification_code(
self._trusted_device, self._verification_code):
self._verification_code = None
self._trusted_device = None
self._verification_code = None
except PyiCloudException as error:
_LOGGER.error("Error setting up 2fa: %s", error)
else:
self.api.authenticate()
@@ -397,13 +420,13 @@ class Icloud(DeviceScanner):
try:
if devicename is not None:
if devicename in self.devices:
self.devices[devicename].update_icloud()
self.devices[devicename].location()
else:
_LOGGER.error("devicename %s unknown for account %s",
devicename, self._attrs[ATTR_ACCOUNTNAME])
else:
for device in self.devices:
self.devices[device].update_icloud()
self.devices[device].location()
except PyiCloudNoDevicesException:
_LOGGER.error('No iCloud Devices found!')
@@ -4,11 +4,13 @@ Support for tracking MQTT enabled devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt/
"""
import asyncio
import logging
import voluptuous as vol
import homeassistant.components.mqtt as mqtt
from homeassistant.core import callback
from homeassistant.const import CONF_DEVICES
from homeassistant.components.mqtt import CONF_QOS
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
@@ -23,19 +25,23 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
})
def setup_scanner(hass, config, see, discovery_info=None):
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Setup the MQTT tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}
def device_tracker_message_received(topic, payload, qos):
@callback
def async_tracker_message_received(topic, payload, qos):
"""MQTT message received."""
see(dev_id=dev_id_lookup[topic], location_name=payload)
hass.async_add_job(
async_see(dev_id=dev_id_lookup[topic], location_name=payload))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
mqtt.subscribe(hass, topic, device_tracker_message_received, qos)
yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos)
return True
@@ -4,14 +4,15 @@ Support the OwnTracks platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/
"""
import asyncio
import json
import logging
import threading
import base64
from collections import defaultdict
import voluptuous as vol
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.components.mqtt as mqtt
from homeassistant.const import STATE_HOME
@@ -19,6 +20,7 @@ from homeassistant.util import convert, slugify
from homeassistant.components import zone as zone_comp
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
DEPENDENCIES = ['mqtt']
REQUIREMENTS = ['libnacl==1.5.0']
_LOGGER = logging.getLogger(__name__)
@@ -30,16 +32,9 @@ CONF_SECRET = 'secret'
CONF_WAYPOINT_IMPORT = 'waypoints'
CONF_WAYPOINT_WHITELIST = 'waypoint_whitelist'
DEPENDENCIES = ['mqtt']
EVENT_TOPIC = 'owntracks/+/+/event'
LOCATION_TOPIC = 'owntracks/+/+'
LOCK = threading.Lock()
MOBILE_BEACONS_ACTIVE = defaultdict(list)
REGIONS_ENTERED = defaultdict(list)
VALIDATE_LOCATION = 'location'
VALIDATE_TRANSITION = 'transition'
@@ -61,7 +56,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def get_cipher():
"""Return decryption function and length of key."""
"""Return decryption function and length of key.
Async friendly.
"""
from libnacl import crypto_secretbox_KEYBYTES as KEYLEN
from libnacl.secret import SecretBox
@@ -71,13 +69,17 @@ def get_cipher():
return (KEYLEN, decrypt)
def setup_scanner(hass, config, see, discovery_info=None):
@asyncio.coroutine
def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
max_gps_accuracy = config.get(CONF_MAX_GPS_ACCURACY)
waypoint_import = config.get(CONF_WAYPOINT_IMPORT)
waypoint_whitelist = config.get(CONF_WAYPOINT_WHITELIST)
secret = config.get(CONF_SECRET)
mobile_beacons_active = defaultdict(list)
regions_entered = defaultdict(list)
def decrypt_payload(topic, ciphertext):
"""Decrypt encrypted payload."""
try:
@@ -154,7 +156,8 @@ def setup_scanner(hass, config, see, discovery_info=None):
return data
def owntracks_location_update(topic, payload, qos):
@callback
def async_owntracks_location_update(topic, payload, qos):
"""MQTT message received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typelocation
@@ -164,18 +167,17 @@ def setup_scanner(hass, config, see, discovery_info=None):
dev_id, kwargs = _parse_see_args(topic, data)
# Block updates if we're in a region
with LOCK:
if REGIONS_ENTERED[dev_id]:
_LOGGER.debug(
"location update ignored - inside region %s",
REGIONS_ENTERED[-1])
return
if regions_entered[dev_id]:
_LOGGER.debug(
"location update ignored - inside region %s",
regions_entered[-1])
return
see(**kwargs)
see_beacons(dev_id, kwargs)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
def owntracks_event_update(topic, payload, qos):
@callback
def async_owntracks_event_update(topic, payload, qos):
"""MQTT event (geofences) received."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typetransition
@@ -199,67 +201,65 @@ def setup_scanner(hass, config, see, discovery_info=None):
def enter_event():
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(slugify(location)))
with LOCK:
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = REGIONS_ENTERED[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
if zone is None and data.get('t') == 'b':
# Not a HA zone, and a beacon so assume mobile
beacons = mobile_beacons_active[dev_id]
if location not in beacons:
beacons.append(location)
_LOGGER.info("Added beacon %s", location)
else:
# Normal region
regions = regions_entered[dev_id]
if location not in regions:
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
see(**kwargs)
see_beacons(dev_id, kwargs)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
def leave_event():
"""Execute leave event."""
with LOCK:
regions = REGIONS_ENTERED[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
regions = regions_entered[dev_id]
if location in regions:
regions.remove(location)
new_region = regions[-1] if regions else None
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
see(**kwargs)
see_beacons(dev_id, kwargs)
if new_region:
# Exit to previous region
zone = hass.states.get(
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
valid_gps = True
if 'acc' in data:
if data['acc'] == 0.0:
valid_gps = False
_LOGGER.warning(
'Ignoring GPS in region exit because accuracy'
'is zero: %s',
payload)
if (max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
valid_gps = False
_LOGGER.info(
'Ignoring GPS in region exit because expected '
'GPS accuracy %s is not met: %s',
max_gps_accuracy, payload)
if valid_gps:
see(**kwargs)
see_beacons(dev_id, kwargs)
else:
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
valid_gps = True
if 'acc' in data:
if data['acc'] == 0.0:
valid_gps = False
_LOGGER.warning(
'Ignoring GPS in region exit because accuracy'
'is zero: %s',
payload)
if (max_gps_accuracy is not None and
data['acc'] > max_gps_accuracy):
valid_gps = False
_LOGGER.info(
'Ignoring GPS in region exit because expected '
'GPS accuracy %s is not met: %s',
max_gps_accuracy, payload)
if valid_gps:
hass.async_add_job(async_see(**kwargs))
async_see_beacons(dev_id, kwargs)
beacons = MOBILE_BEACONS_ACTIVE[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
beacons = mobile_beacons_active[dev_id]
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
if data['event'] == 'enter':
enter_event()
@@ -271,7 +271,8 @@ def setup_scanner(hass, config, see, discovery_info=None):
data['event'])
return
def owntracks_waypoint_update(topic, payload, qos):
@callback
def async_owntracks_waypoint_update(topic, payload, qos):
"""List of waypoints published by a user."""
# Docs on available data:
# http://owntracks.org/booklet/tech/json/#_typewaypoints
@@ -298,36 +299,43 @@ def setup_scanner(hass, config, see, discovery_info=None):
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
zone.update_ha_state()
hass.async_add_job(zone.async_update_ha_state())
def see_beacons(dev_id, kwargs_param):
@callback
def async_see_beacons(dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
# the battery state applies to the tracking device, not the beacon
kwargs.pop('battery', None)
for beacon in MOBILE_BEACONS_ACTIVE[dev_id]:
for beacon in mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
see(**kwargs)
hass.async_add_job(async_see(**kwargs))
mqtt.subscribe(hass, LOCATION_TOPIC, owntracks_location_update, 1)
mqtt.subscribe(hass, EVENT_TOPIC, owntracks_event_update, 1)
yield from mqtt.async_subscribe(
hass, LOCATION_TOPIC, async_owntracks_location_update, 1)
yield from mqtt.async_subscribe(
hass, EVENT_TOPIC, async_owntracks_event_update, 1)
if waypoint_import:
if waypoint_whitelist is None:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format('+', '+'),
owntracks_waypoint_update, 1)
yield from mqtt.async_subscribe(
hass, WAYPOINT_TOPIC.format('+', '+'),
async_owntracks_waypoint_update, 1)
else:
for whitelist_user in waypoint_whitelist:
mqtt.subscribe(hass, WAYPOINT_TOPIC.format(whitelist_user,
'+'),
owntracks_waypoint_update, 1)
yield from mqtt.async_subscribe(
hass, WAYPOINT_TOPIC.format(whitelist_user, '+'),
async_owntracks_waypoint_update, 1)
return True
def parse_topic(topic, pretty=False):
"""Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple."""
"""Parse an MQTT topic owntracks/user/dev, return (user, dev) tuple.
Async friendly.
"""
parts = topic.split('/')
dev_id_format = ''
if pretty:
@@ -340,7 +348,10 @@ def parse_topic(topic, pretty=False):
def _parse_see_args(topic, data):
"""Parse the OwnTracks location parameters, into the format see expects."""
"""Parse the OwnTracks location parameters, into the format see expects.
Async friendly.
"""
(host_name, dev_id) = parse_topic(topic, False)
kwargs = {
'dev_id': dev_id,
@@ -355,7 +366,10 @@ def _parse_see_args(topic, data):
def _set_gps_from_zone(kwargs, location, zone):
"""Set the see parameters from the zone parameters."""
"""Set the see parameters from the zone parameters.
Async friendly.
"""
if zone is not None:
kwargs['gps'] = (
zone.attributes['latitude'],
@@ -86,7 +86,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Update all the hosts on every interval time."""
for host in hosts:
host.update(see)
track_point_in_utc_time(hass, update, now + interval)
track_point_in_utc_time(hass, update, util.dt.utcnow() + interval)
return True
return update(util.dt.utcnow())
@@ -19,7 +19,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pysnmp==4.3.3']
REQUIREMENTS = ['pysnmp==4.3.4']
CONF_COMMUNITY = 'community'
CONF_AUTHKEY = 'authkey'
@@ -142,7 +142,7 @@ class TadoDeviceScanner(DeviceScanner):
# Find devices that have geofencing enabled, and are currently at home.
for mobile_device in tado_json:
if 'location' in mobile_device:
if mobile_device.get('location'):
if mobile_device['location']['atHome']:
device_id = mobile_device['id']
device_name = mobile_device['name']
@@ -13,13 +13,15 @@ import homeassistant.loader as loader
from homeassistant.components.device_tracker import (
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.const import CONF_VERIFY_SSL
# Unifi package doesn't list urllib3 as a requirement
REQUIREMENTS = ['urllib3', 'pyunifi==1.3']
REQUIREMENTS = ['pyunifi==2.0']
_LOGGER = logging.getLogger(__name__)
CONF_PORT = 'port'
CONF_SITE_ID = 'site_id'
DEFAULT_VERIFY_SSL = True
NOTIFICATION_ID = 'unifi_notification'
NOTIFICATION_TITLE = 'Unifi Device Tracker Setup'
@@ -29,7 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SITE_ID, default='default'): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PORT, default=8443): cv.port
vol.Required(CONF_PORT, default=8443): cv.port,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean,
})
@@ -42,10 +45,12 @@ def get_scanner(hass, config):
password = config[DOMAIN].get(CONF_PASSWORD)
site_id = config[DOMAIN].get(CONF_SITE_ID)
port = config[DOMAIN].get(CONF_PORT)
verify_ssl = config[DOMAIN].get(CONF_VERIFY_SSL)
persistent_notification = loader.get_component('persistent_notification')
try:
ctrl = Controller(host, username, password, port, 'v4', site_id)
ctrl = Controller(host, username, password, port, version='v4',
site_id=site_id, ssl_verify=verify_ssl)
except urllib.error.HTTPError as ex:
_LOGGER.error('Failed to connect to Unifi: %s', ex)
persistent_notification.create(
+63 -30
View File
@@ -6,20 +6,24 @@ Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
Knows which components handle certain types, will make sure they are
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
"""
import asyncio
import json
from datetime import timedelta
import logging
import threading
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==0.8.3']
REQUIREMENTS = ['netdisco==0.9.2']
DOMAIN = 'discovery'
SCAN_INTERVAL = 300 # seconds
SCAN_INTERVAL = timedelta(seconds=300)
SERVICE_NETGEAR = 'netgear_router'
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HASS_IOS_APP = 'hass_ios'
@@ -42,24 +46,28 @@ SERVICE_HANDLERS = {
'yeelight': ('light', 'yeelight'),
'flux_led': ('light', 'flux_led'),
'apple_tv': ('media_player', 'apple_tv'),
'frontier_silicon': ('media_player', 'frontier_silicon'),
'openhome': ('media_player', 'openhome'),
}
CONF_IGNORE = 'ignore'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(DOMAIN): vol.Schema({
vol.Optional(CONF_IGNORE, default=[]):
vol.All(cv.ensure_list, [vol.In(SERVICE_HANDLERS)])
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Start a discovery service."""
logger = logging.getLogger(__name__)
from netdisco.discovery import NetworkDiscovery
from netdisco.service import DiscoveryService
logger = logging.getLogger(__name__)
netdisco = NetworkDiscovery()
already_discovered = set()
# Disable zeroconf logging, it spams
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
@@ -67,37 +75,62 @@ def setup(hass, config):
# Platforms ignore by config
ignored_platforms = config[DOMAIN][CONF_IGNORE]
lock = threading.Lock()
def new_service_listener(service, info):
@asyncio.coroutine
def new_service_found(service, info):
"""Called when a new service is found."""
if service in ignored_platforms:
logger.info("Ignoring service: %s %s", service, info)
return
with lock:
logger.info("Found new service: %s %s", service, info)
comp_plat = SERVICE_HANDLERS.get(service)
comp_plat = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service.
if not comp_plat:
return
# We do not know how to handle this service.
if not comp_plat:
return
discovery_hash = json.dumps([service, info], sort_keys=True)
if discovery_hash in already_discovered:
return
component, platform = comp_plat
already_discovered.add(discovery_hash)
if platform is None:
discover(hass, service, info, component, config)
else:
load_platform(hass, component, platform, info, config)
logger.info("Found new service: %s %s", service, info)
# pylint: disable=unused-argument
def start_discovery(event):
"""Start discovering."""
netdisco = DiscoveryService(SCAN_INTERVAL)
netdisco.add_listener(new_service_listener)
netdisco.start()
component, platform = comp_plat
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_discovery)
if platform is None:
yield from async_discover(hass, service, info, component, config)
else:
yield from async_load_platform(
hass, component, platform, info, config)
@asyncio.coroutine
def scan_devices(_):
"""Scan for devices."""
results = yield from hass.loop.run_in_executor(
None, _discover, netdisco)
for result in results:
hass.async_add_job(new_service_found(*result))
async_track_point_in_utc_time(hass, scan_devices,
dt_util.utcnow() + SCAN_INTERVAL)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, scan_devices)
return True
def _discover(netdisco):
"""Discover devices."""
results = []
try:
netdisco.scan()
for disc in netdisco.discover():
for service in netdisco.get_info(disc):
results.append((disc, service))
finally:
netdisco.stop()
return results
+1 -1
View File
@@ -78,7 +78,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup MQTT fan platform."""
yield from async_add_devices([MqttFan(
async_add_devices([MqttFan(
config.get(CONF_NAME),
{
key: config.get(key) for key in (
+76 -68
View File
@@ -14,6 +14,8 @@ from homeassistant.core import callback
from homeassistant.const import (
ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.config import load_yaml_config_file
from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_connect)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
@@ -26,6 +28,10 @@ SERVICE_START = 'start'
SERVICE_STOP = 'stop'
SERVICE_RESTART = 'restart'
SIGNAL_FFMPEG_START = 'ffmpeg.start'
SIGNAL_FFMPEG_STOP = 'ffmpeg.stop'
SIGNAL_FFMPEG_RESTART = 'ffmpeg.restart'
DATA_FFMPEG = 'ffmpeg'
CONF_INITIAL_STATE = 'initial_state'
@@ -50,22 +56,25 @@ SERVICE_FFMPEG_SCHEMA = vol.Schema({
})
def start(hass, entity_id=None):
@callback
def async_start(hass, entity_id=None):
"""Start a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_START, data)
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_START, data))
def stop(hass, entity_id=None):
@callback
def async_stop(hass, entity_id=None):
"""Stop a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_STOP, data)
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_STOP, data))
def restart(hass, entity_id=None):
@callback
def async_restart(hass, entity_id=None):
"""Restart a ffmpeg process on entity."""
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
hass.services.call(DOMAIN, SERVICE_RESTART, data)
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RESTART, data))
@asyncio.coroutine
@@ -89,30 +98,12 @@ def async_setup(hass, config):
"""Handle service ffmpeg process."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
devices = [device for device in manager.entities
if device.entity_id in entity_ids]
if service.service == SERVICE_START:
async_dispatcher_send(hass, SIGNAL_FFMPEG_START, entity_ids)
elif service.service == SERVICE_STOP:
async_dispatcher_send(hass, SIGNAL_FFMPEG_STOP, entity_ids)
else:
devices = manager.entities
tasks = []
for device in devices:
if service.service == SERVICE_START:
tasks.append(device.async_start_ffmpeg())
elif service.service == SERVICE_STOP:
tasks.append(device.async_stop_ffmpeg())
else:
tasks.append(device.async_restart_ffmpeg())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
tasks.clear()
for device in devices:
tasks.append(device.async_update_ha_state())
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
async_dispatcher_send(hass, SIGNAL_FFMPEG_RESTART, entity_ids)
hass.services.async_register(
DOMAIN, SERVICE_START, async_service_handle,
@@ -140,42 +131,12 @@ class FFmpegManager(object):
self._cache = {}
self._bin = ffmpeg_bin
self._run_test = run_test
self._entities = []
@property
def binary(self):
"""Return ffmpeg binary from config."""
return self._bin
@property
def entities(self):
"""Return ffmpeg entities for services."""
return self._entities
@callback
def async_register_device(self, device):
"""Register a ffmpeg process/device."""
self._entities.append(device)
@asyncio.coroutine
def async_shutdown(event):
"""Stop ffmpeg process."""
yield from device.async_stop_ffmpeg()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown)
# start on startup
if device.initial_state:
@asyncio.coroutine
def async_start(event):
"""Start ffmpeg process."""
yield from device.async_start_ffmpeg()
yield from device.async_update_ha_state()
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start)
@asyncio.coroutine
def async_run_test(self, input_source):
"""Run test on this input. TRUE is deactivate or run correct.
@@ -208,6 +169,22 @@ class FFmpegBase(Entity):
self.ffmpeg = None
self.initial_state = initial_state
@asyncio.coroutine
def async_added_to_hass(self):
"""Register dispatcher & events.
This method is a coroutine.
"""
async_dispatcher_connect(
self.hass, SIGNAL_FFMPEG_START, self._async_start_ffmpeg)
async_dispatcher_connect(
self.hass, SIGNAL_FFMPEG_STOP, self._async_stop_ffmpeg)
async_dispatcher_connect(
self.hass, SIGNAL_FFMPEG_RESTART, self._async_restart_ffmpeg)
# register start/stop
self._async_register_events()
@property
def available(self):
"""Return True if entity is available."""
@@ -218,22 +195,53 @@ class FFmpegBase(Entity):
"""Return True if entity has to be polled for state."""
return False
def async_start_ffmpeg(self):
@asyncio.coroutine
def _async_start_ffmpeg(self, entity_ids):
"""Start a ffmpeg process.
This method must be run in the event loop and returns a coroutine.
This method is a coroutine.
"""
raise NotImplementedError()
def async_stop_ffmpeg(self):
@asyncio.coroutine
def _async_stop_ffmpeg(self, entity_ids):
"""Stop a ffmpeg process.
This method must be run in the event loop and returns a coroutine.
This method is a coroutine.
"""
return self.ffmpeg.close()
if entity_ids is None or self.entity_id in entity_ids:
yield from self.ffmpeg.close()
@asyncio.coroutine
def async_restart_ffmpeg(self):
"""Stop a ffmpeg process."""
yield from self.async_stop_ffmpeg()
yield from self.async_start_ffmpeg()
def _async_restart_ffmpeg(self, entity_ids):
"""Stop a ffmpeg process.
This method is a coroutine.
"""
if entity_ids is None or self.entity_id in entity_ids:
yield from self._async_stop_ffmpeg(None)
yield from self._async_start_ffmpeg(None)
@callback
def _async_register_events(self):
"""Register a ffmpeg process/device."""
@asyncio.coroutine
def async_shutdown_handle(event):
"""Stop ffmpeg process."""
yield from self._async_stop_ffmpeg(None)
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown_handle)
# start on startup
if not self.initial_state:
return
@asyncio.coroutine
def async_start_handle(event):
"""Start ffmpeg process."""
yield from self._async_start_ffmpeg(None)
self.hass.async_add_job(self.async_update_ha_state())
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_start_handle)
+3 -3
View File
@@ -3,13 +3,13 @@
FINGERPRINTS = {
"compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0",
"core.js": "1f7f88d8f5dada08bce1d935cfa5f33e",
"frontend.html": "ca9efa7e4506aa6b1a668703c8d0f800",
"mdi.html": "c1dde43ccf5667f687c418fc8daf9668",
"frontend.html": "418f6ef8354ce71f1b9594ee2068ebef",
"mdi.html": "65413cdf82f822bd6480e577852f0292",
"micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a",
"panels/ha-panel-config.html": "412b3e24515ffa1ee8074ce974cf4057",
"panels/ha-panel-dev-event.html": "91347dedf3b4fa9b49ccf4c0a28a03c4",
"panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d",
"panels/ha-panel-dev-service.html": "a9247f255174b084fad2c04bdb9ec7a9",
"panels/ha-panel-dev-service.html": "153aad076f98bbd626466bac50986874",
"panels/ha-panel-dev-state.html": "90f3bede9602241552ef7bb7958198c6",
"panels/ha-panel-dev-template.html": "c249a4fc18a3a6994de3d6330cfe6cbb",
"panels/ha-panel-history.html": "fdaa4d2402d49d4c8bd64a1708ab7a50",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -18,7 +18,7 @@ from voluptuous.error import Error as VoluptuousError
import homeassistant.helpers.config_validation as cv
import homeassistant.loader as loader
from homeassistant import bootstrap
from homeassistant.setup import setup_component
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import generate_entity_id
from homeassistant.helpers.event import track_time_change
@@ -118,8 +118,8 @@ def do_authentication(hass, config):
return False
persistent_notification.create(
hass, 'In order to authorize Home-Assistant to view your calendars'
'You must visit: <a href="{}" target="_blank">{}</a> and enter'
hass, 'In order to authorize Home-Assistant to view your calendars '
'you must visit: <a href="{}" target="_blank">{}</a> and enter '
'code: {}'.format(dev_flow.verification_url,
dev_flow.verification_url,
dev_flow.user_code),
@@ -223,7 +223,7 @@ def do_setup(hass, config):
setup_services(hass, track_new_found_calendars, calendar_service)
# Ensure component is loaded
bootstrap.setup_component(hass, 'calendar', config)
setup_component(hass, 'calendar', config)
for calendar in hass.data[DATA_INDEX].values():
discovery.load_platform(hass, 'calendar', DOMAIN, calendar)
+6 -9
View File
@@ -14,14 +14,13 @@ from homeassistant import config as conf_util, core as ha
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_ICON, CONF_NAME, STATE_CLOSED, STATE_HOME,
STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN, STATE_LOCKED,
STATE_UNLOCKED, STATE_UNKNOWN, ATTR_ASSUMED_STATE)
STATE_UNLOCKED, STATE_UNKNOWN, ATTR_ASSUMED_STATE, SERVICE_RELOAD)
from homeassistant.core import callback
from homeassistant.helpers.entity import Entity, async_generate_entity_id
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_state_change
import homeassistant.helpers.config_validation as cv
from homeassistant.util.async import (
run_callback_threadsafe, run_coroutine_threadsafe)
from homeassistant.util.async import run_coroutine_threadsafe
DOMAIN = 'group'
@@ -43,7 +42,6 @@ SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_VISIBLE): cv.boolean
})
SERVICE_RELOAD = 'reload'
RELOAD_SERVICE_SCHEMA = vol.Schema({})
_LOGGER = logging.getLogger(__name__)
@@ -98,7 +96,7 @@ def is_on(hass, entity_id):
def reload(hass):
"""Reload the automation from config."""
hass.services.call(DOMAIN, SERVICE_RELOAD)
hass.add_job(async_reload, hass)
@asyncio.coroutine
@@ -365,7 +363,7 @@ class Group(Entity):
def start(self):
"""Start tracking members."""
run_callback_threadsafe(self.hass.loop, self.async_start).result()
self.hass.add_job(self.async_start)
@callback
def async_start(self):
@@ -396,17 +394,16 @@ class Group(Entity):
self._state = STATE_UNKNOWN
self._async_update_group_state()
@asyncio.coroutine
def async_remove(self):
"""Remove group from HASS.
This method must be run in the event loop.
This method must be run in the event loop and returns a coroutine.
"""
if self._async_unsub_state_changed:
self._async_unsub_state_changed()
self._async_unsub_state_changed = None
yield from super().async_remove()
return super().async_remove()
@asyncio.coroutine
def _async_state_changed_listener(self, entity_id, old_state, new_state):
+1 -1
View File
@@ -13,7 +13,7 @@ from functools import reduce
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components import discovery
from homeassistant.helpers import discovery
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER
from homeassistant.components.switch import DOMAIN as SWITCH
from homeassistant.config import load_yaml_config_file
+85 -73
View File
@@ -20,6 +20,7 @@ from homeassistant.components import recorder, script
from homeassistant.components.frontend import register_built_in_panel
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import ATTR_HIDDEN
from homeassistant.components.recorder.util import session_scope, execute
_LOGGER = logging.getLogger(__name__)
@@ -34,19 +35,20 @@ SIGNIFICANT_DOMAINS = ('thermostat', 'climate')
IGNORE_DOMAINS = ('zone', 'scene',)
def last_recorder_run():
def last_recorder_run(hass):
"""Retireve the last closed recorder run from the DB."""
recorder.get_instance()
rec_runs = recorder.get_model('RecorderRuns')
with recorder.session_scope() as session:
res = recorder.query(rec_runs).order_by(rec_runs.end.desc()).first()
from homeassistant.components.recorder.models import RecorderRuns
with session_scope(hass=hass) as session:
res = (session.query(RecorderRuns)
.order_by(RecorderRuns.end.desc()).first())
if res is None:
return None
session.expunge(res)
return res
def get_significant_states(start_time, end_time=None, entity_id=None,
def get_significant_states(hass, start_time, end_time=None, entity_id=None,
filters=None):
"""
Return states changes during UTC period start_time - end_time.
@@ -55,50 +57,60 @@ def get_significant_states(start_time, end_time=None, entity_id=None,
as well as all states from certain domains (for instance
thermostat so that we get current temperature in our graphs).
"""
from homeassistant.components.recorder.models import States
entity_ids = (entity_id.lower(), ) if entity_id is not None else None
states = recorder.get_model('States')
query = recorder.query(states).filter(
(states.domain.in_(SIGNIFICANT_DOMAINS) |
(states.last_changed == states.last_updated)) &
(states.last_updated > start_time))
if filters:
query = filters.apply(query, entity_ids)
if end_time is not None:
query = query.filter(states.last_updated < end_time)
with session_scope(hass=hass) as session:
query = session.query(States).filter(
(States.domain.in_(SIGNIFICANT_DOMAINS) |
(States.last_changed == States.last_updated)) &
(States.last_updated > start_time))
states = (
state for state in recorder.execute(
query.order_by(states.entity_id, states.last_updated))
if (_is_significant(state) and
not state.attributes.get(ATTR_HIDDEN, False)))
if filters:
query = filters.apply(query, entity_ids)
return states_to_json(states, start_time, entity_id, filters)
if end_time is not None:
query = query.filter(States.last_updated < end_time)
states = (
state for state in execute(
query.order_by(States.entity_id, States.last_updated))
if (_is_significant(state) and
not state.attributes.get(ATTR_HIDDEN, False)))
return states_to_json(hass, states, start_time, entity_id, filters)
def state_changes_during_period(start_time, end_time=None, entity_id=None):
def state_changes_during_period(hass, start_time, end_time=None,
entity_id=None):
"""Return states changes during UTC period start_time - end_time."""
states = recorder.get_model('States')
query = recorder.query(states).filter(
(states.last_changed == states.last_updated) &
(states.last_changed > start_time))
from homeassistant.components.recorder.models import States
if end_time is not None:
query = query.filter(states.last_updated < end_time)
with session_scope(hass=hass) as session:
query = session.query(States).filter(
(States.last_changed == States.last_updated) &
(States.last_changed > start_time))
if entity_id is not None:
query = query.filter_by(entity_id=entity_id.lower())
if end_time is not None:
query = query.filter(States.last_updated < end_time)
states = recorder.execute(
query.order_by(states.entity_id, states.last_updated))
if entity_id is not None:
query = query.filter_by(entity_id=entity_id.lower())
return states_to_json(states, start_time, entity_id)
states = execute(
query.order_by(States.entity_id, States.last_updated))
return states_to_json(hass, states, start_time, entity_id)
def get_states(utc_point_in_time, entity_ids=None, run=None, filters=None):
def get_states(hass, utc_point_in_time, entity_ids=None, run=None,
filters=None):
"""Return the states at a specific point in time."""
from homeassistant.components.recorder.models import States
if run is None:
run = recorder.run_information(utc_point_in_time)
run = recorder.run_information(hass, utc_point_in_time)
# History did not run before utc_point_in_time
if run is None:
@@ -106,29 +118,29 @@ def get_states(utc_point_in_time, entity_ids=None, run=None, filters=None):
from sqlalchemy import and_, func
states = recorder.get_model('States')
most_recent_state_ids = recorder.query(
func.max(states.state_id).label('max_state_id')
).filter(
(states.created >= run.start) &
(states.created < utc_point_in_time) &
(~states.domain.in_(IGNORE_DOMAINS)))
if filters:
most_recent_state_ids = filters.apply(most_recent_state_ids,
entity_ids)
with session_scope(hass=hass) as session:
most_recent_state_ids = session.query(
func.max(States.state_id).label('max_state_id')
).filter(
(States.created >= run.start) &
(States.created < utc_point_in_time) &
(~States.domain.in_(IGNORE_DOMAINS)))
most_recent_state_ids = most_recent_state_ids.group_by(
states.entity_id).subquery()
if filters:
most_recent_state_ids = filters.apply(most_recent_state_ids,
entity_ids)
query = recorder.query(states).join(most_recent_state_ids, and_(
states.state_id == most_recent_state_ids.c.max_state_id))
most_recent_state_ids = most_recent_state_ids.group_by(
States.entity_id).subquery()
for state in recorder.execute(query):
if not state.attributes.get(ATTR_HIDDEN, False):
yield state
query = session.query(States).join(most_recent_state_ids, and_(
States.state_id == most_recent_state_ids.c.max_state_id))
return [state for state in execute(query)
if not state.attributes.get(ATTR_HIDDEN, False)]
def states_to_json(states, start_time, entity_id, filters=None):
def states_to_json(hass, states, start_time, entity_id, filters=None):
"""Convert SQL results into JSON friendly data structure.
This takes our state list and turns it into a JSON friendly data
@@ -143,7 +155,7 @@ def states_to_json(states, start_time, entity_id, filters=None):
entity_ids = [entity_id] if entity_id is not None else None
# Get the states at the start time
for state in get_states(start_time, entity_ids, filters=filters):
for state in get_states(hass, start_time, entity_ids, filters=filters):
state.last_changed = start_time
state.last_updated = start_time
result[state.entity_id].append(state)
@@ -154,9 +166,9 @@ def states_to_json(states, start_time, entity_id, filters=None):
return result
def get_state(utc_point_in_time, entity_id, run=None):
def get_state(hass, utc_point_in_time, entity_id, run=None):
"""Return a state at a specific point in time."""
states = list(get_states(utc_point_in_time, (entity_id,), run))
states = list(get_states(hass, utc_point_in_time, (entity_id,), run))
return states[0] if states else None
@@ -173,7 +185,6 @@ def setup(hass, config):
filters.included_entities = include[CONF_ENTITIES]
filters.included_domains = include[CONF_DOMAINS]
recorder.get_instance()
hass.http.register_view(HistoryPeriodView(filters))
register_built_in_panel(hass, 'history', 'History', 'mdi:poll-box')
@@ -223,8 +234,8 @@ class HistoryPeriodView(HomeAssistantView):
entity_id = request.GET.get('filter_entity_id')
result = yield from request.app['hass'].loop.run_in_executor(
None, get_significant_states, start_time, end_time, entity_id,
self.filters)
None, get_significant_states, request.app['hass'], start_time,
end_time, entity_id, self.filters)
result = result.values()
if _LOGGER.isEnabledFor(logging.DEBUG):
elapsed = time.perf_counter() - timer_start
@@ -254,41 +265,42 @@ class Filters(object):
* if include and exclude is defined - select the entities specified in
the include and filter out the ones from the exclude list.
"""
states = recorder.get_model('States')
from homeassistant.components.recorder.models import States
# specific entities requested - do not in/exclude anything
if entity_ids is not None:
return query.filter(states.entity_id.in_(entity_ids))
query = query.filter(~states.domain.in_(IGNORE_DOMAINS))
return query.filter(States.entity_id.in_(entity_ids))
query = query.filter(~States.domain.in_(IGNORE_DOMAINS))
filter_query = None
# filter if only excluded domain is configured
if self.excluded_domains and not self.included_domains:
filter_query = ~states.domain.in_(self.excluded_domains)
filter_query = ~States.domain.in_(self.excluded_domains)
if self.included_entities:
filter_query &= states.entity_id.in_(self.included_entities)
filter_query &= States.entity_id.in_(self.included_entities)
# filter if only included domain is configured
elif not self.excluded_domains and self.included_domains:
filter_query = states.domain.in_(self.included_domains)
filter_query = States.domain.in_(self.included_domains)
if self.included_entities:
filter_query |= states.entity_id.in_(self.included_entities)
filter_query |= States.entity_id.in_(self.included_entities)
# filter if included and excluded domain is configured
elif self.excluded_domains and self.included_domains:
filter_query = ~states.domain.in_(self.excluded_domains)
filter_query = ~States.domain.in_(self.excluded_domains)
if self.included_entities:
filter_query &= (states.domain.in_(self.included_domains) |
states.entity_id.in_(self.included_entities))
filter_query &= (States.domain.in_(self.included_domains) |
States.entity_id.in_(self.included_entities))
else:
filter_query &= (states.domain.in_(self.included_domains) & ~
states.domain.in_(self.excluded_domains))
filter_query &= (States.domain.in_(self.included_domains) & ~
States.domain.in_(self.excluded_domains))
# no domain filter just included entities
elif not self.excluded_domains and not self.included_domains and \
self.included_entities:
filter_query = states.entity_id.in_(self.included_entities)
filter_query = States.entity_id.in_(self.included_entities)
if filter_query is not None:
query = query.filter(filter_query)
# finally apply excluded entities filter if configured
if self.excluded_entities:
query = query.filter(~states.entity_id.in_(self.excluded_entities))
query = query.filter(~States.entity_id.in_(self.excluded_entities))
return query
+5 -3
View File
@@ -4,6 +4,7 @@ from collections import defaultdict
from datetime import datetime
from ipaddress import ip_address
import logging
import os
from aiohttp.web_exceptions import HTTPForbidden, HTTPUnauthorized
import voluptuous as vol
@@ -115,13 +116,14 @@ def load_ip_bans_config(path: str):
"""Loading list of banned IPs from config file."""
ip_list = []
if not os.path.isfile(path):
return ip_list
try:
list_ = load_yaml_config_file(path)
except FileNotFoundError:
return []
except HomeAssistantError as err:
_LOGGER.error('Unable to load %s: %s', path, str(err))
return []
return ip_list
for ip_ban, ip_info in list_.items():
try:
@@ -60,7 +60,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME)
))
yield from async_add_devices(entities)
async_add_devices(entities)
class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
@@ -54,7 +54,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera.get(CONF_NAME)
))
yield from async_add_devices(entities)
async_add_devices(entities)
class ImageProcessingFaceEntity(ImageProcessingEntity):
@@ -108,8 +108,7 @@ class ImageProcessingFaceEntity(ImageProcessingEntity):
def process_faces(self, faces, total):
"""Send event with detected faces and store data."""
run_callback_threadsafe(
self.hass.loop, self.async_process_faces, faces, total
).result()
self.hass.loop, self.async_process_faces, faces, total).result()
@callback
def async_process_faces(self, faces, total):
@@ -66,7 +66,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera[CONF_ENTITY_ID], params, confidence, camera.get(CONF_NAME)
))
yield from async_add_devices(entities)
async_add_devices(entities)
class OpenAlprCloudEntity(ImageProcessingAlprEntity):
@@ -70,7 +70,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
camera[CONF_ENTITY_ID], command, confidence, camera.get(CONF_NAME)
))
yield from async_add_devices(entities)
async_add_devices(entities)
class ImageProcessingAlprEntity(ImageProcessingEntity):
+1 -1
View File
@@ -85,7 +85,7 @@ def setup(hass, config):
try:
influx = InfluxDBClient(**kwargs)
influx.query("SELECT * FROM /.*/ LIMIT 1;")
influx.query("SHOW DIAGNOSTICS;")
except exceptions.InfluxDBClientError as exc:
_LOGGER.error("Database host is not accessible due to '%s', please "
"check your entries in the configuration file and that "
+13
View File
@@ -14,6 +14,7 @@ from homeassistant.const import (
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
DOMAIN = 'input_slider'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
@@ -165,6 +166,18 @@ class InputSlider(Entity):
ATTR_STEP: self._step
}
@asyncio.coroutine
def async_added_to_hass(self):
"""Called when entity about to be added to hass."""
state = yield from async_get_last_state(self.hass, self.entity_id)
if not state:
return
num_value = float(state.state)
if num_value < self._minimum or num_value > self._maximum:
return
self._current_value = num_value
@asyncio.coroutine
def async_select_value(self, value):
"""Select new value."""
+1 -1
View File
@@ -13,7 +13,7 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, CONF_HOST, CONF_PORT, CONF_TIMEOUT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['insteonlocal==0.39']
REQUIREMENTS = ['insteonlocal==0.48']
_LOGGER = logging.getLogger(__name__)
+1 -1
View File
@@ -231,7 +231,7 @@ class ISYDevice(Entity):
# pylint: disable=unused-argument
def on_update(self, event: object) -> None:
"""Handle the update event from the ISY994 Node."""
self.update_ha_state()
self.schedule_update_ha_state()
@property
def domain(self) -> str:
+5 -8
View File
@@ -24,8 +24,6 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import async_restore_state
import homeassistant.util.color as color_util
from homeassistant.util.async import run_callback_threadsafe
DOMAIN = "light"
SCAN_INTERVAL = timedelta(seconds=30)
@@ -88,7 +86,7 @@ PROP_TO_ATTR = {
}
# Service call validation schemas
VALID_TRANSITION = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=900))
VALID_TRANSITION = vol.All(vol.Coerce(float), vol.Clamp(min=0, max=6553))
VALID_BRIGHTNESS = vol.All(vol.Coerce(int), vol.Clamp(min=0, max=255))
LIGHT_TURN_ON_SCHEMA = vol.Schema({
@@ -145,10 +143,10 @@ def turn_on(hass, entity_id=None, transition=None, brightness=None,
rgb_color=None, xy_color=None, color_temp=None, white_value=None,
profile=None, flash=None, effect=None, color_name=None):
"""Turn all or specified light on."""
run_callback_threadsafe(
hass.loop, async_turn_on, hass, entity_id, transition, brightness,
hass.add_job(
async_turn_on, hass, entity_id, transition, brightness,
rgb_color, xy_color, color_temp, white_value,
profile, flash, effect, color_name).result()
profile, flash, effect, color_name)
@callback
@@ -178,8 +176,7 @@ def async_turn_on(hass, entity_id=None, transition=None, brightness=None,
def turn_off(hass, entity_id=None, transition=None):
"""Turn all or specified light off."""
run_callback_threadsafe(
hass.loop, async_turn_off, hass, entity_id, transition).result()
hass.add_job(async_turn_off, hass, entity_id, transition)
@callback
+1 -1
View File
@@ -106,4 +106,4 @@ class EnOceanLight(enocean.EnOceanDevice, Light):
"""Update the internal state of this device."""
self._brightness = math.floor(val / 100.0 * 256.0)
self._on_state = bool(val != 0)
self.update_ha_state()
self.schedule_update_ha_state()
+1 -1
View File
@@ -18,7 +18,7 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['flux_led==0.13']
REQUIREMENTS = ['flux_led==0.15']
_LOGGER = logging.getLogger(__name__)
+38 -14
View File
@@ -47,9 +47,19 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
PHUE_CONFIG_FILE = 'phue.conf'
SUPPORT_HUE = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT |
SUPPORT_FLASH | SUPPORT_RGB_COLOR | SUPPORT_TRANSITION |
SUPPORT_XY_COLOR)
SUPPORT_HUE_ON_OFF = (SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_FLASH)
SUPPORT_HUE_DIMMABLE = (SUPPORT_HUE_ON_OFF | SUPPORT_BRIGHTNESS)
SUPPORT_HUE_COLOR_TEMP = (SUPPORT_HUE_DIMMABLE | SUPPORT_COLOR_TEMP)
SUPPORT_HUE_COLOR = (SUPPORT_HUE_DIMMABLE | SUPPORT_EFFECT |
SUPPORT_RGB_COLOR | SUPPORT_XY_COLOR)
SUPPORT_HUE_EXTENDED = (SUPPORT_HUE_COLOR_TEMP | SUPPORT_HUE_COLOR)
SUPPORT_HUE = {
'Extended color light': SUPPORT_HUE_EXTENDED,
'Color light': SUPPORT_HUE_COLOR,
'Dimmable light': SUPPORT_HUE_DIMMABLE,
'Color temperature light': SUPPORT_HUE_COLOR_TEMP
}
CONF_ALLOW_IN_EMULATED_HUE = "allow_in_emulated_hue"
DEFAULT_ALLOW_IN_EMULATED_HUE = True
@@ -354,7 +364,7 @@ class HueLight(Light):
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_HUE
return SUPPORT_HUE.get(self.info.get('type'), SUPPORT_HUE_EXTENDED)
@property
def effect_list(self):
@@ -366,15 +376,30 @@ class HueLight(Light):
command = {'on': True}
if ATTR_TRANSITION in kwargs:
command['transitiontime'] = kwargs[ATTR_TRANSITION] * 10
command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)
if ATTR_XY_COLOR in kwargs:
command['xy'] = kwargs[ATTR_XY_COLOR]
if self.info.get('manufacturername') == "OSRAM":
hsv = color_util.color_xy_brightness_to_hsv(
*kwargs[ATTR_XY_COLOR],
ibrightness=self.info['bri'])
command['hue'] = hsv[0]
command['sat'] = hsv[1]
command['bri'] = hsv[2]
else:
command['xy'] = kwargs[ATTR_XY_COLOR]
elif ATTR_RGB_COLOR in kwargs:
xyb = color_util.color_RGB_to_xy(
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
command['xy'] = xyb[0], xyb[1]
command['bri'] = xyb[2]
if self.info.get('manufacturername') == "OSRAM":
hsv = color_util.color_RGB_to_hsv(
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
command['hue'] = hsv[0]
command['sat'] = hsv[1]
command['bri'] = hsv[2]
else:
xyb = color_util.color_RGB_to_xy(
*(int(val) for val in kwargs[ATTR_RGB_COLOR]))
command['xy'] = xyb[0], xyb[1]
command['bri'] = xyb[2]
if ATTR_BRIGHTNESS in kwargs:
command['bri'] = kwargs[ATTR_BRIGHTNESS]
@@ -401,7 +426,8 @@ class HueLight(Light):
command['hue'] = random.randrange(0, 65535)
command['sat'] = random.randrange(150, 254)
elif self.bridge_type == 'hue':
command['effect'] = 'none'
if self.info.get('manufacturername') != "OSRAM":
command['effect'] = 'none'
self._command_func(self.light_id, command)
@@ -410,9 +436,7 @@ class HueLight(Light):
command = {'on': False}
if ATTR_TRANSITION in kwargs:
# Transition time is in 1/10th seconds and cannot exceed
# 900 seconds.
command['transitiontime'] = min(9000, kwargs[ATTR_TRANSITION] * 10)
command['transitiontime'] = int(kwargs[ATTR_TRANSITION] * 10)
flash = kwargs.get(ATTR_FLASH)
@@ -152,6 +152,10 @@ class InsteonLocalDimmerDevice(Light):
def update(self):
"""Update state of the light."""
resp = self.node.status(0)
while 'error' in resp and resp['error'] is True:
resp = self.node.status(0)
if 'cmd2' in resp:
self._value = int(resp['cmd2'], 16)
+10 -8
View File
@@ -98,7 +98,7 @@ class LIFX(object):
ipaddr, name, power, hue, sat, bri, kel)
bulb.set_power(power)
bulb.set_color(hue, sat, bri, kel)
bulb.update_ha_state()
bulb.schedule_update_ha_state()
def on_color(self, ipaddr, hue, sat, bri, kel):
"""Initialize the light."""
@@ -106,7 +106,7 @@ class LIFX(object):
if bulb is not None:
bulb.set_color(hue, sat, bri, kel)
bulb.update_ha_state()
bulb.schedule_update_ha_state()
def on_power(self, ipaddr, power):
"""Initialize the light."""
@@ -114,7 +114,7 @@ class LIFX(object):
if bulb is not None:
bulb.set_power(power)
bulb.update_ha_state()
bulb.schedule_update_ha_state()
# pylint: disable=unused-argument
def poll(self, now):
@@ -202,7 +202,7 @@ class LIFXLight(Light):
def turn_on(self, **kwargs):
"""Turn the device on."""
if ATTR_TRANSITION in kwargs:
fade = kwargs[ATTR_TRANSITION] * 1000
fade = int(kwargs[ATTR_TRANSITION] * 1000)
else:
fade = 0
@@ -230,15 +230,17 @@ class LIFXLight(Light):
hue, saturation, brightness, kelvin, fade)
if self._power == 0:
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, 0)
self._liffylights.set_power(self._ip, 65535, fade)
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, fade)
else:
self._liffylights.set_color(self._ip, hue, saturation,
brightness, kelvin, fade)
def turn_off(self, **kwargs):
"""Turn the device off."""
if ATTR_TRANSITION in kwargs:
fade = kwargs[ATTR_TRANSITION] * 1000
fade = int(kwargs[ATTR_TRANSITION] * 1000)
else:
fade = 0
@@ -17,7 +17,7 @@ from homeassistant.components.light import (
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light, PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['limitlessled==1.0.4']
REQUIREMENTS = ['limitlessled==1.0.5']
_LOGGER = logging.getLogger(__name__)
@@ -143,7 +143,7 @@ def state(new_state):
pipeline.on()
# Set transition time.
if ATTR_TRANSITION in kwargs:
transition_time = kwargs[ATTR_TRANSITION]
transition_time = int(kwargs[ATTR_TRANSITION])
# Do group type-specific work.
function(self, transition_time, pipeline, **kwargs)
# Update state.
+207 -41
View File
@@ -12,83 +12,122 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_COLOR_TEMP, SUPPORT_BRIGHTNESS,
SUPPORT_RGB_COLOR, SUPPORT_COLOR_TEMP, Light)
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_RGB_COLOR,
ATTR_WHITE_VALUE, ATTR_XY_COLOR, Light, SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_RGB_COLOR,
SUPPORT_WHITE_VALUE, SUPPORT_XY_COLOR)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON, CONF_STATE, CONF_BRIGHTNESS, CONF_RGB,
CONF_COLOR_TEMP)
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME,
CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON,
CONF_RGB, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_STATE_VALUE_TEMPLATE = 'state_value_template'
CONF_BRIGHTNESS_STATE_TOPIC = 'brightness_state_topic'
CONF_BRIGHTNESS_COMMAND_TOPIC = 'brightness_command_topic'
CONF_BRIGHTNESS_VALUE_TEMPLATE = 'brightness_value_template'
CONF_RGB_STATE_TOPIC = 'rgb_state_topic'
CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic'
CONF_RGB_VALUE_TEMPLATE = 'rgb_value_template'
CONF_BRIGHTNESS_SCALE = 'brightness_scale'
CONF_COLOR_TEMP_STATE_TOPIC = 'color_temp_state_topic'
CONF_BRIGHTNESS_STATE_TOPIC = 'brightness_state_topic'
CONF_BRIGHTNESS_VALUE_TEMPLATE = 'brightness_value_template'
CONF_COLOR_TEMP_COMMAND_TOPIC = 'color_temp_command_topic'
CONF_COLOR_TEMP_STATE_TOPIC = 'color_temp_state_topic'
CONF_COLOR_TEMP_VALUE_TEMPLATE = 'color_temp_value_template'
CONF_EFFECT_COMMAND_TOPIC = 'effect_command_topic'
CONF_EFFECT_LIST = 'effect_list'
CONF_EFFECT_STATE_TOPIC = 'effect_state_topic'
CONF_EFFECT_VALUE_TEMPLATE = 'effect_value_template'
CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic'
CONF_RGB_STATE_TOPIC = 'rgb_state_topic'
CONF_RGB_VALUE_TEMPLATE = 'rgb_value_template'
CONF_STATE_VALUE_TEMPLATE = 'state_value_template'
CONF_XY_COMMAND_TOPIC = 'xy_command_topic'
CONF_XY_STATE_TOPIC = 'xy_state_topic'
CONF_XY_VALUE_TEMPLATE = 'xy_value_template'
CONF_WHITE_VALUE_COMMAND_TOPIC = 'white_value_command_topic'
CONF_WHITE_VALUE_SCALE = 'white_value_scale'
CONF_WHITE_VALUE_STATE_TOPIC = 'white_value_state_topic'
CONF_WHITE_VALUE_TEMPLATE = 'white_value_template'
DEFAULT_NAME = 'MQTT Light'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_OPTIMISTIC = False
DEFAULT_BRIGHTNESS_SCALE = 255
DEFAULT_NAME = 'MQTT Light'
DEFAULT_OPTIMISTIC = False
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_WHITE_VALUE_SCALE = 255
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_BRIGHTNESS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_BRIGHTNESS_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_BRIGHTNESS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGB_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_BRIGHTNESS_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_BRIGHTNESS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_EFFECT_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_EFFECT_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_RGB_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_RGB_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_RGB_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_WHITE_VALUE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_WHITE_VALUE_SCALE, default=DEFAULT_WHITE_VALUE_SCALE):
vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_WHITE_VALUE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_XY_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_XY_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Add MQTT Light."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
config.setdefault(
CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE))
yield from async_add_devices([MqttLight(
async_add_devices([MqttLight(
config.get(CONF_NAME),
config.get(CONF_EFFECT_LIST),
{
key: config.get(key) for key in (
CONF_STATE_TOPIC,
CONF_COMMAND_TOPIC,
CONF_BRIGHTNESS_STATE_TOPIC,
CONF_BRIGHTNESS_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_RGB_COMMAND_TOPIC,
CONF_BRIGHTNESS_STATE_TOPIC,
CONF_COLOR_TEMP_COMMAND_TOPIC,
CONF_COLOR_TEMP_STATE_TOPIC,
CONF_COLOR_TEMP_COMMAND_TOPIC
CONF_COMMAND_TOPIC,
CONF_EFFECT_COMMAND_TOPIC,
CONF_EFFECT_STATE_TOPIC,
CONF_RGB_COMMAND_TOPIC,
CONF_RGB_STATE_TOPIC,
CONF_STATE_TOPIC,
CONF_WHITE_VALUE_COMMAND_TOPIC,
CONF_WHITE_VALUE_STATE_TOPIC,
CONF_XY_COMMAND_TOPIC,
CONF_XY_STATE_TOPIC,
)
},
{
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE),
CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE),
CONF_EFFECT: config.get(CONF_EFFECT_VALUE_TEMPLATE),
CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE),
CONF_COLOR_TEMP: config.get(CONF_COLOR_TEMP_VALUE_TEMPLATE)
CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE),
CONF_WHITE_VALUE: config.get(CONF_WHITE_VALUE_TEMPLATE),
CONF_XY: config.get(CONF_XY_VALUE_TEMPLATE),
},
config.get(CONF_QOS),
config.get(CONF_RETAIN),
@@ -98,16 +137,19 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
},
config.get(CONF_OPTIMISTIC),
config.get(CONF_BRIGHTNESS_SCALE),
config.get(CONF_WHITE_VALUE_SCALE),
)])
class MqttLight(Light):
"""MQTT light."""
def __init__(self, name, topic, templates, qos, retain, payload,
optimistic, brightness_scale):
def __init__(self, name, effect_list, topic, templates, qos,
retain, payload, optimistic, brightness_scale,
white_value_scale):
"""Initialize MQTT light."""
self._name = name
self._effect_list = effect_list
self._topic = topic
self._qos = qos
self._retain = retain
@@ -120,11 +162,21 @@ class MqttLight(Light):
optimistic or topic[CONF_BRIGHTNESS_STATE_TOPIC] is None)
self._optimistic_color_temp = (
optimistic or topic[CONF_COLOR_TEMP_STATE_TOPIC] is None)
self._optimistic_effect = (
optimistic or topic[CONF_EFFECT_STATE_TOPIC] is None)
self._optimistic_white_value = (
optimistic or topic[CONF_WHITE_VALUE_STATE_TOPIC] is None)
self._optimistic_xy = \
optimistic or topic[CONF_XY_STATE_TOPIC] is None
self._brightness_scale = brightness_scale
self._white_value_scale = white_value_scale
self._state = False
self._brightness = None
self._rgb = None
self._color_temp = None
self._effect = None
self._white_value = None
self._xy = None
self._supported_features = 0
self._supported_features |= (
topic[CONF_RGB_COMMAND_TOPIC] is not None and SUPPORT_RGB_COLOR)
@@ -134,6 +186,14 @@ class MqttLight(Light):
self._supported_features |= (
topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None and
SUPPORT_COLOR_TEMP)
self._supported_features |= (
topic[CONF_EFFECT_STATE_TOPIC] is not None and
SUPPORT_EFFECT)
self._supported_features |= (
topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None and
SUPPORT_WHITE_VALUE)
self._supported_features |= (
topic[CONF_XY_COMMAND_TOPIC] is not None and SUPPORT_XY_COLOR)
@asyncio.coroutine
def async_added_to_hass(self):
@@ -215,6 +275,57 @@ class MqttLight(Light):
else:
self._color_temp = None
@callback
def effect_received(topic, payload, qos):
"""A new MQTT message for effect has been received."""
self._effect = templates[CONF_EFFECT](payload)
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_EFFECT_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_EFFECT_STATE_TOPIC],
effect_received, self._qos)
self._effect = 'none'
if self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None:
self._effect = 'none'
else:
self._effect = None
@callback
def white_value_received(topic, payload, qos):
"""A new MQTT message for the white value has been received."""
device_value = float(templates[CONF_WHITE_VALUE](payload))
percent_white = device_value / self._white_value_scale
self._white_value = int(percent_white * 255)
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_WHITE_VALUE_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_WHITE_VALUE_STATE_TOPIC],
white_value_received, self._qos)
self._white_value = 255
elif self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None:
self._white_value = 255
else:
self._white_value = None
@callback
def xy_received(topic, payload, qos):
"""A new MQTT message has been received."""
self._xy = [float(val) for val in
templates[CONF_XY](payload).split(',')]
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_XY_STATE_TOPIC] is not None:
yield from mqtt.async_subscribe(
self.hass, self._topic[CONF_XY_STATE_TOPIC], xy_received,
self._qos)
self._xy = [1, 1]
if self._topic[CONF_XY_COMMAND_TOPIC] is not None:
self._xy = [1, 1]
else:
self._xy = None
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
@@ -230,6 +341,16 @@ class MqttLight(Light):
"""Return the color temperature in mired."""
return self._color_temp
@property
def white_value(self):
"""Return the white property."""
return self._white_value
@property
def xy_color(self):
"""Return the RGB color value."""
return self._xy
@property
def should_poll(self):
"""No polling needed for a MQTT light."""
@@ -250,6 +371,16 @@ class MqttLight(Light):
"""Return true if we do optimistic updates."""
return self._optimistic
@property
def effect_list(self):
"""Return the list of supported effects."""
return self._effect_list
@property
def effect(self):
"""Return the current effect."""
return self._effect
@property
def supported_features(self):
"""Flag supported features."""
@@ -297,6 +428,41 @@ class MqttLight(Light):
self._color_temp = kwargs[ATTR_COLOR_TEMP]
should_update = True
if ATTR_EFFECT in kwargs and \
self._topic[CONF_EFFECT_COMMAND_TOPIC] is not None:
effect = kwargs[ATTR_EFFECT]
if effect in self._effect_list:
mqtt.async_publish(
self.hass, self._topic[CONF_EFFECT_COMMAND_TOPIC],
effect, self._qos, self._retain)
if self._optimistic_effect:
self._effect = kwargs[ATTR_EFFECT]
should_update = True
if ATTR_WHITE_VALUE in kwargs and \
self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC] is not None:
percent_white = float(kwargs[ATTR_WHITE_VALUE]) / 255
device_white_value = int(percent_white * self._white_value_scale)
mqtt.async_publish(
self.hass, self._topic[CONF_WHITE_VALUE_COMMAND_TOPIC],
device_white_value, self._qos, self._retain)
if self._optimistic_white_value:
self._white_value = kwargs[ATTR_WHITE_VALUE]
should_update = True
if ATTR_XY_COLOR in kwargs and \
self._topic[CONF_XY_COMMAND_TOPIC] is not None:
mqtt.async_publish(
self.hass, self._topic[CONF_XY_COMMAND_TOPIC],
'{},{}'.format(*kwargs[ATTR_XY_COLOR]), self._qos,
self._retain)
if self._optimistic_xy:
self._xy = kwargs[ATTR_XY_COLOR]
should_update = True
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC], self._payload['on'],
self._qos, self._retain)
+169 -26
View File
@@ -12,11 +12,14 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_TRANSITION, PLATFORM_SCHEMA,
ATTR_FLASH, FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_FLASH,
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light)
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
ATTR_RGB_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR,
FLASH_LONG, FLASH_SHORT, Light, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_RGB_COLOR,
SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, SUPPORT_XY_COLOR)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_BRIGHTNESS, CONF_RGB)
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT,
CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
import homeassistant.helpers.config_validation as cv
@@ -27,42 +30,53 @@ DOMAIN = 'mqtt_json'
DEPENDENCIES = ['mqtt']
DEFAULT_BRIGHTNESS = False
DEFAULT_COLOR_TEMP = False
DEFAULT_EFFECT = False
DEFAULT_FLASH_TIME_LONG = 10
DEFAULT_FLASH_TIME_SHORT = 2
DEFAULT_NAME = 'MQTT JSON Light'
DEFAULT_OPTIMISTIC = False
DEFAULT_BRIGHTNESS = False
DEFAULT_RGB = False
DEFAULT_FLASH_TIME_SHORT = 2
DEFAULT_FLASH_TIME_LONG = 10
DEFAULT_WHITE_VALUE = False
DEFAULT_XY = False
CONF_EFFECT_LIST = 'effect_list'
CONF_FLASH_TIME_SHORT = 'flash_time_short'
CONF_FLASH_TIME_LONG = 'flash_time_long'
SUPPORT_MQTT_JSON = (SUPPORT_BRIGHTNESS | SUPPORT_FLASH | SUPPORT_RGB_COLOR |
SUPPORT_TRANSITION)
CONF_FLASH_TIME_SHORT = 'flash_time_short'
# Stealing some of these from the base MQTT configs.
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS):
vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_BRIGHTNESS, default=DEFAULT_BRIGHTNESS): cv.boolean,
vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean,
vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean,
vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_FLASH_TIME_SHORT, default=DEFAULT_FLASH_TIME_SHORT):
cv.positive_int,
vol.Optional(CONF_FLASH_TIME_LONG, default=DEFAULT_FLASH_TIME_LONG):
cv.positive_int
cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS):
vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_RGB, default=DEFAULT_RGB): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean,
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a MQTT JSON Light."""
yield from async_add_devices([MqttJson(
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
async_add_devices([MqttJson(
config.get(CONF_NAME),
config.get(CONF_EFFECT_LIST),
{
key: config.get(key) for key in (
CONF_STATE_TOPIC,
@@ -73,7 +87,11 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_RETAIN),
config.get(CONF_OPTIMISTIC),
config.get(CONF_BRIGHTNESS),
config.get(CONF_COLOR_TEMP),
config.get(CONF_EFFECT),
config.get(CONF_RGB),
config.get(CONF_WHITE_VALUE),
config.get(CONF_XY),
{
key: config.get(key) for key in (
CONF_FLASH_TIME_SHORT,
@@ -86,10 +104,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class MqttJson(Light):
"""Representation of a MQTT JSON light."""
def __init__(self, name, topic, qos, retain, optimistic, brightness, rgb,
def __init__(self, name, effect_list, topic, qos, retain, optimistic,
brightness, color_temp, effect, rgb, white_value, xy,
flash_times):
"""Initialize MQTT JSON light."""
self._name = name
self._effect_list = effect_list
self._topic = topic
self._qos = qos
self._retain = retain
@@ -100,13 +120,45 @@ class MqttJson(Light):
else:
self._brightness = None
if color_temp:
self._color_temp = 150
else:
self._color_temp = None
if effect:
self._effect = 'none'
else:
self._effect = None
if rgb:
self._rgb = [0, 0, 0]
else:
self._rgb = None
if white_value:
self._white_value = 255
else:
self._white_value = None
if xy:
self._xy = [1, 1]
else:
self._xy = None
self._flash_times = flash_times
self._supported_features = (SUPPORT_TRANSITION | SUPPORT_FLASH)
self._supported_features |= (rgb is not None and SUPPORT_RGB_COLOR)
self._supported_features |= (brightness is not None and
SUPPORT_BRIGHTNESS)
self._supported_features |= (color_temp is not None and
SUPPORT_COLOR_TEMP)
self._supported_features |= (effect is not None and
SUPPORT_EFFECT)
self._supported_features |= (white_value is not None and
SUPPORT_WHITE_VALUE)
self._supported_features |= (xy is not None and SUPPORT_XY_COLOR)
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe mqtt events.
@@ -133,7 +185,7 @@ class MqttJson(Light):
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid color value received")
_LOGGER.warning("Invalid RGB color value received")
if self._brightness is not None:
try:
@@ -143,6 +195,41 @@ class MqttJson(Light):
except ValueError:
_LOGGER.warning('Invalid brightness value received')
if self._color_temp is not None:
try:
self._color_temp = int(values['color_temp'])
except KeyError:
pass
except ValueError:
_LOGGER.warning('Invalid color temp value received')
if self._effect is not None:
try:
self._effect = values['effect']
except KeyError:
pass
except ValueError:
_LOGGER.warning('Invalid effect value received')
if self._white_value is not None:
try:
self._white_value = int(values['white_value'])
except KeyError:
pass
except ValueError:
_LOGGER.warning('Invalid white value value received')
if self._xy is not None:
try:
x_color = float(values['color']['x'])
y_color = float(values['color']['y'])
self._xy = [x_color, y_color]
except KeyError:
pass
except ValueError:
_LOGGER.warning("Invalid XY color value received")
self.hass.async_add_job(self.async_update_ha_state())
if self._topic[CONF_STATE_TOPIC] is not None:
@@ -155,11 +242,36 @@ class MqttJson(Light):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def color_temp(self):
"""Return the color temperature in mired."""
return self._color_temp
@property
def effect(self):
"""Return the current effect."""
return self._effect
@property
def effect_list(self):
"""Return the list of supported effects."""
return self._effect_list
@property
def rgb_color(self):
"""Return the RGB color value."""
return self._rgb
@property
def white_value(self):
"""Return the white property."""
return self._white_value
@property
def xy_color(self):
"""Return the XY color value."""
return self._xy
@property
def should_poll(self):
"""No polling needed for a MQTT light."""
@@ -183,7 +295,7 @@ class MqttJson(Light):
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_MQTT_JSON
return self._supported_features
@asyncio.coroutine
def async_turn_on(self, **kwargs):
@@ -215,7 +327,7 @@ class MqttJson(Light):
message['flash'] = self._flash_times[CONF_FLASH_TIME_SHORT]
if ATTR_TRANSITION in kwargs:
message['transition'] = kwargs[ATTR_TRANSITION]
message['transition'] = int(kwargs[ATTR_TRANSITION])
if ATTR_BRIGHTNESS in kwargs:
message['brightness'] = int(kwargs[ATTR_BRIGHTNESS])
@@ -224,6 +336,37 @@ class MqttJson(Light):
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
if ATTR_COLOR_TEMP in kwargs:
message['color_temp'] = int(kwargs[ATTR_COLOR_TEMP])
if self._optimistic:
self._color_temp = kwargs[ATTR_COLOR_TEMP]
should_update = True
if ATTR_EFFECT in kwargs:
message['effect'] = kwargs[ATTR_EFFECT]
if self._optimistic:
self._effect = kwargs[ATTR_EFFECT]
should_update = True
if ATTR_WHITE_VALUE in kwargs:
message['white_value'] = int(kwargs[ATTR_WHITE_VALUE])
if self._optimistic:
self._white_value = kwargs[ATTR_WHITE_VALUE]
should_update = True
if ATTR_XY_COLOR in kwargs:
message['color'] = {
'x': kwargs[ATTR_XY_COLOR][0],
'y': kwargs[ATTR_XY_COLOR][1]
}
if self._optimistic:
self._xy = kwargs[ATTR_XY_COLOR]
should_update = True
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC], json.dumps(message),
self._qos, self._retain)
@@ -245,7 +388,7 @@ class MqttJson(Light):
message = {'state': 'OFF'}
if ATTR_TRANSITION in kwargs:
message['transition'] = kwargs[ATTR_TRANSITION]
message['transition'] = int(kwargs[ATTR_TRANSITION])
mqtt.async_publish(
self.hass, self._topic[CONF_COMMAND_TOPIC], json.dumps(message),
+100 -35
View File
@@ -11,9 +11,10 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.components.mqtt as mqtt
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_EFFECT, ATTR_FLASH, ATTR_RGB_COLOR, ATTR_TRANSITION,
PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, SUPPORT_FLASH,
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light)
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
ATTR_RGB_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, Light, PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH,
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE)
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, STATE_ON, STATE_OFF
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
@@ -28,43 +29,47 @@ DEPENDENCIES = ['mqtt']
DEFAULT_NAME = 'MQTT Template Light'
DEFAULT_OPTIMISTIC = False
CONF_EFFECT_LIST = "effect_list"
CONF_COMMAND_ON_TEMPLATE = 'command_on_template'
CONF_COMMAND_OFF_TEMPLATE = 'command_off_template'
CONF_STATE_TEMPLATE = 'state_template'
CONF_BRIGHTNESS_TEMPLATE = 'brightness_template'
CONF_RED_TEMPLATE = 'red_template'
CONF_GREEN_TEMPLATE = 'green_template'
CONF_BLUE_TEMPLATE = 'blue_template'
CONF_BRIGHTNESS_TEMPLATE = 'brightness_template'
CONF_COLOR_TEMP_TEMPLATE = 'color_temp_template'
CONF_COMMAND_OFF_TEMPLATE = 'command_off_template'
CONF_COMMAND_ON_TEMPLATE = 'command_on_template'
CONF_EFFECT_LIST = 'effect_list'
CONF_EFFECT_TEMPLATE = 'effect_template'
SUPPORT_MQTT_TEMPLATE = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_FLASH |
SUPPORT_RGB_COLOR | SUPPORT_TRANSITION)
CONF_GREEN_TEMPLATE = 'green_template'
CONF_RED_TEMPLATE = 'red_template'
CONF_STATE_TEMPLATE = 'state_template'
CONF_WHITE_VALUE_TEMPLATE = 'white_value_template'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_BRIGHTNESS_TEMPLATE): cv.template,
vol.Optional(CONF_RED_TEMPLATE): cv.template,
vol.Optional(CONF_GREEN_TEMPLATE): cv.template,
vol.Optional(CONF_BLUE_TEMPLATE): cv.template,
vol.Optional(CONF_BRIGHTNESS_TEMPLATE): cv.template,
vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_TEMPLATE): cv.template,
vol.Optional(CONF_GREEN_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_RED_TEMPLATE): cv.template,
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS):
vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean
})
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Setup a MQTT Template light."""
yield from async_add_devices([MqttTemplate(
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
async_add_devices([MqttTemplate(
hass,
config.get(CONF_NAME),
config.get(CONF_EFFECT_LIST),
@@ -76,14 +81,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
},
{
key: config.get(key) for key in (
CONF_COMMAND_ON_TEMPLATE,
CONF_COMMAND_OFF_TEMPLATE,
CONF_STATE_TEMPLATE,
CONF_BRIGHTNESS_TEMPLATE,
CONF_RED_TEMPLATE,
CONF_GREEN_TEMPLATE,
CONF_BLUE_TEMPLATE,
CONF_EFFECT_TEMPLATE
CONF_BRIGHTNESS_TEMPLATE,
CONF_COLOR_TEMP_TEMPLATE,
CONF_COMMAND_OFF_TEMPLATE,
CONF_COMMAND_ON_TEMPLATE,
CONF_EFFECT_TEMPLATE,
CONF_GREEN_TEMPLATE,
CONF_RED_TEMPLATE,
CONF_STATE_TEMPLATE,
CONF_WHITE_VALUE_TEMPLATE,
)
},
config.get(CONF_OPTIMISTIC),
@@ -114,6 +121,16 @@ class MqttTemplate(Light):
else:
self._brightness = None
if self._templates[CONF_COLOR_TEMP_TEMPLATE] is not None:
self._color_temp = 255
else:
self._color_temp = None
if self._templates[CONF_WHITE_VALUE_TEMPLATE] is not None:
self._white_value = 255
else:
self._white_value = None
if (self._templates[CONF_RED_TEMPLATE] is not None and
self._templates[CONF_GREEN_TEMPLATE] is not None and
self._templates[CONF_BLUE_TEMPLATE] is not None):
@@ -156,6 +173,16 @@ class MqttTemplate(Light):
except ValueError:
_LOGGER.warning('Invalid brightness value received')
# read color temperature
if self._color_temp is not None:
try:
self._color_temp = int(
self._templates[CONF_COLOR_TEMP_TEMPLATE].
async_render_with_possible_json_value(payload)
)
except ValueError:
_LOGGER.warning('Invalid color temperature value received')
# read color
if self._rgb is not None:
try:
@@ -171,6 +198,16 @@ class MqttTemplate(Light):
except ValueError:
_LOGGER.warning('Invalid color value received')
# read white value
if self._white_value is not None:
try:
self._white_value = int(
self._templates[CONF_WHITE_VALUE_TEMPLATE].
async_render_with_possible_json_value(payload)
)
except ValueError:
_LOGGER.warning('Invalid white value received')
# read effect
if self._templates[CONF_EFFECT_TEMPLATE] is not None:
effect = self._templates[CONF_EFFECT_TEMPLATE].\
@@ -194,11 +231,21 @@ class MqttTemplate(Light):
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def color_temp(self):
"""Return the color temperature in mired."""
return self._color_temp
@property
def rgb_color(self):
"""Return the RGB color value [int, int, int]."""
return self._rgb
@property
def white_value(self):
"""Return the white property."""
return self._white_value
@property
def should_poll(self):
"""Return True if entity has to be polled for state.
@@ -250,6 +297,13 @@ class MqttTemplate(Light):
if self._optimistic:
self._brightness = kwargs[ATTR_BRIGHTNESS]
# color_temp
if ATTR_COLOR_TEMP in kwargs:
values['color_temp'] = int(kwargs[ATTR_COLOR_TEMP])
if self._optimistic:
self._color_temp = kwargs[ATTR_COLOR_TEMP]
# color
if ATTR_RGB_COLOR in kwargs:
values['red'] = kwargs[ATTR_RGB_COLOR][0]
@@ -259,6 +313,13 @@ class MqttTemplate(Light):
if self._optimistic:
self._rgb = kwargs[ATTR_RGB_COLOR]
# white value
if ATTR_WHITE_VALUE in kwargs:
values['white_value'] = int(kwargs[ATTR_WHITE_VALUE])
if self._optimistic:
self._white_value = kwargs[ATTR_WHITE_VALUE]
# effect
if ATTR_EFFECT in kwargs:
values['effect'] = kwargs.get(ATTR_EFFECT)
@@ -269,7 +330,7 @@ class MqttTemplate(Light):
# transition
if ATTR_TRANSITION in kwargs:
values['transition'] = kwargs[ATTR_TRANSITION]
values['transition'] = int(kwargs[ATTR_TRANSITION])
mqtt.async_publish(
self.hass, self._topics[CONF_COMMAND_TOPIC],
@@ -293,7 +354,7 @@ class MqttTemplate(Light):
# transition
if ATTR_TRANSITION in kwargs:
values['transition'] = kwargs[ATTR_TRANSITION]
values['transition'] = int(kwargs[ATTR_TRANSITION])
mqtt.async_publish(
self.hass, self._topics[CONF_COMMAND_TOPIC],
@@ -307,12 +368,16 @@ class MqttTemplate(Light):
@property
def supported_features(self):
"""Flag supported features."""
features = 0
features = (SUPPORT_FLASH | SUPPORT_TRANSITION)
if self._brightness is not None:
features = features | SUPPORT_BRIGHTNESS
if self._rgb is not None:
features = features | SUPPORT_RGB_COLOR
if self._effect_list is not None:
features = features | SUPPORT_EFFECT
if self._color_temp is not None:
features = features | SUPPORT_COLOR_TEMP
if self._white_value is not None:
features = features | SUPPORT_WHITE_VALUE
return features
+10 -14
View File
@@ -17,6 +17,8 @@ from homeassistant.components.light import (
Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_RGB_COLOR,
ATTR_TRANSITION, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT,
SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, PLATFORM_SCHEMA)
from homeassistant.util.color import (
color_temperature_mired_to_kelvin, color_temperature_kelvin_to_mired)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/tfriedel/python-lightify/archive/'
@@ -24,10 +26,6 @@ REQUIREMENTS = ['https://github.com/tfriedel/python-lightify/archive/'
_LOGGER = logging.getLogger(__name__)
TEMP_MIN = 2000 # lightify minimum temperature
TEMP_MAX = 6500 # lightify maximum temperature
TEMP_MIN_HASS = 154 # home assistant minimum temperature
TEMP_MAX_HASS = 500 # home assistant maximum temperature
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
@@ -93,10 +91,10 @@ class OsramLightifyLight(Light):
self._light = light
self._light_id = light_id
self.update_lights = update_lights
self._brightness = 0
self._rgb = (0, 0, 0)
self._name = ""
self._temperature = TEMP_MIN
self._brightness = None
self._rgb = None
self._name = None
self._temperature = None
self._state = False
self.update()
@@ -145,7 +143,7 @@ class OsramLightifyLight(Light):
self._state = self._light.on()
if ATTR_TRANSITION in kwargs:
transition = kwargs[ATTR_TRANSITION] * 10
transition = int(kwargs[ATTR_TRANSITION] * 10)
_LOGGER.debug("turn_on requested transition time for light:"
" %s is: %s ",
self._name, transition)
@@ -164,8 +162,7 @@ class OsramLightifyLight(Light):
if ATTR_COLOR_TEMP in kwargs:
color_t = kwargs[ATTR_COLOR_TEMP]
kelvin = int(((TEMP_MAX - TEMP_MIN) * (color_t - TEMP_MIN_HASS) /
(TEMP_MAX_HASS - TEMP_MIN_HASS)) + TEMP_MIN)
kelvin = int(color_temperature_mired_to_kelvin(color_t))
_LOGGER.debug("turn_on requested set_temperature for light:"
" %s: %s ", self._name, kelvin)
self._light.set_temperature(kelvin, transition)
@@ -196,7 +193,7 @@ class OsramLightifyLight(Light):
_LOGGER.debug("turn_off Attempting to turn off light: %s ",
self._name)
if ATTR_TRANSITION in kwargs:
transition = kwargs[ATTR_TRANSITION] * 10
transition = int(kwargs[ATTR_TRANSITION] * 10)
_LOGGER.debug("turn_off requested transition time for light:"
" %s is: %s ",
self._name, transition)
@@ -218,6 +215,5 @@ class OsramLightifyLight(Light):
self._name = self._light.name()
self._rgb = self._light.rgb()
o_temp = self._light.temp()
self._temperature = int(TEMP_MIN_HASS + (TEMP_MAX_HASS - TEMP_MIN_HASS)
* (o_temp - TEMP_MIN) / (TEMP_MAX - TEMP_MIN))
self._temperature = color_temperature_kelvin_to_mired(o_temp)
self._state = self._light.on()
+16 -3
View File
@@ -117,7 +117,7 @@ def devices_from_config(domain_config, hass=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the Rflink light platform."""
yield from async_add_devices(devices_from_config(config, hass))
async_add_devices(devices_from_config(config, hass))
# Add new (unconfigured) devices to user desired group
if config[CONF_NEW_DEVICES_GROUP]:
@@ -136,7 +136,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
device_config = config[CONF_DEVICE_DEFAULTS]
device = entity_class(device_id, hass, **device_config)
yield from async_add_devices([device])
async_add_devices([device])
# Register entity to listen to incoming Rflink events
hass.data[DATA_ENTITY_LOOKUP][
@@ -156,7 +156,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class RflinkLight(SwitchableRflinkDevice, Light):
"""Representation of a Rflink light."""
pass
@property
def entity_id(self):
"""Return entity id."""
return "light.{}".format(self.name)
class DimmableRflinkLight(SwitchableRflinkDevice, Light):
@@ -164,6 +167,11 @@ class DimmableRflinkLight(SwitchableRflinkDevice, Light):
_brightness = 255
@property
def entity_id(self):
"""Return entity id."""
return "light.{}".format(self.name)
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the device on."""
@@ -202,6 +210,11 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light):
_brightness = 255
@property
def entity_id(self):
"""Return entity id."""
return "light.{}".format(self.name)
@asyncio.coroutine
def async_turn_on(self, **kwargs):
"""Turn the device on and set dim level."""
+1 -1
View File
@@ -106,7 +106,7 @@ class SCSGateLight(Light):
return
self._toggled = message.toggled
self.update_ha_state()
self.schedule_update_ha_state()
command = "off"
if self._toggled:
+3 -2
View File
@@ -7,10 +7,10 @@ https://home-assistant.io/components/light.vera/
import logging
from homeassistant.components.light import (
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light)
ATTR_BRIGHTNESS, ENTITY_ID_FORMAT, Light, SUPPORT_BRIGHTNESS)
from homeassistant.const import (STATE_OFF, STATE_ON)
from homeassistant.components.vera import (
VeraDevice, VERA_DEVICES, VERA_CONTROLLER)
VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
_LOGGER = logging.getLogger(__name__)
@@ -33,6 +33,7 @@ class VeraLight(VeraDevice, Light):
"""Initialize the light."""
self._state = False
VeraDevice.__init__(self, vera_device, controller)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property
def brightness(self):
+3 -3
View File
@@ -259,7 +259,7 @@ class YeelightLight(Light):
_LOGGER.error("Flash supported currently only in RGB mode.")
return
transition = self.config[CONF_TRANSITION]
transition = int(self.config[CONF_TRANSITION])
if flash == FLASH_LONG:
count = 1
duration = transition * 5
@@ -288,9 +288,9 @@ class YeelightLight(Light):
rgb = kwargs.get(ATTR_RGB_COLOR)
flash = kwargs.get(ATTR_FLASH)
duration = self.config[CONF_TRANSITION] # in ms
duration = int(self.config[CONF_TRANSITION]) # in ms
if ATTR_TRANSITION in kwargs: # passed kwarg overrides config
duration = kwargs.get(ATTR_TRANSITION) * 1000 # kwarg in s
duration = int(kwargs.get(ATTR_TRANSITION) * 1000) # kwarg in s
self._bulb.turn_on(duration=duration)
@@ -2,9 +2,7 @@
Support for Yeelight Sunflower color bulbs (not Yeelight Blue or WiFi).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.yeelight-sunflower
Uses the yeelightsunflower library:
https://github.com/lindsaymarkward/python-yeelight-sunflower
https://home-assistant.io/components/light.yeelightsunflower
"""
import logging
import voluptuous as vol
@@ -17,34 +15,29 @@ from homeassistant.components.light import (Light,
from homeassistant.const import CONF_HOST
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['yeelightsunflower==0.0.6']
SUPPORT_YEELIGHT_SUNFLOWER = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR)
REQUIREMENTS = ['yeelightsunflower==0.0.8']
_LOGGER = logging.getLogger(__name__)
# Validate the user's configuration
SUPPORT_YEELIGHT_SUNFLOWER = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Yeelight Sunflower Light platform."""
"""Set up the Yeelight Sunflower Light platform."""
import yeelightsunflower
# Assign configuration variables.
# The configuration check takes care they are present.
host = config.get(CONF_HOST)
# Setup connection with Yeelight Sunflower hub
hub = yeelightsunflower.Hub(host)
# Verify that hub is responsive
if not hub.available:
_LOGGER.error('Could not connect to Yeelight Sunflower hub')
return False
# Add devices
add_devices(SunflowerBulb(light) for light in hub.get_lights())
+14 -20
View File
@@ -13,6 +13,7 @@ from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \
ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \
SUPPORT_RGB_COLOR, DOMAIN, Light
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.util.color import HASS_COLOR_MAX, HASS_COLOR_MIN, \
color_temperature_mired_to_kelvin, color_temperature_to_rgb, \
@@ -48,38 +49,25 @@ SUPPORT_ZWAVE_COLORTEMP = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR
| SUPPORT_COLOR_TEMP)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Find and add Z-Wave lights."""
if discovery_info is None or zwave.NETWORK is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.const.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.const.ATTR_VALUE_ID]]
def get_device(node, value, node_config, **kwargs):
"""Create zwave entity device."""
name = '{}.{}'.format(DOMAIN, zwave.object_id(value))
node_config = hass.data[zwave.DATA_DEVICE_CONFIG].get(name)
refresh = node_config.get(zwave.CONF_REFRESH_VALUE)
delay = node_config.get(zwave.CONF_REFRESH_DELAY)
_LOGGER.debug('name=%s node_config=%s CONF_REFRESH_VALUE=%s'
' CONF_REFRESH_DELAY=%s', name, node_config,
refresh, delay)
if value.command_class != zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL:
return
if value.type != zwave.const.TYPE_BYTE:
return
if value.genre != zwave.const.GENRE_USER:
return
value.set_change_verified(False)
if node.has_command_class(zwave.const.COMMAND_CLASS_SWITCH_COLOR):
add_devices([ZwaveColorLight(value, refresh, delay)])
return ZwaveColorLight(value, refresh, delay)
else:
add_devices([ZwaveDimmer(value, refresh, delay)])
return ZwaveDimmer(value, refresh, delay)
def brightness_state(value):
"""Return the brightness and state."""
if value.data > 0:
return (value.data / 99) * 255, STATE_ON
return round((value.data / 99) * 255, 0), STATE_ON
else:
return 0, STATE_OFF
@@ -119,7 +107,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
# Brightness
self._brightness, self._state = brightness_state(self._value)
def value_changed(self, value):
def value_changed(self):
"""Called when a value for this entity's node has changed."""
if self._refresh_value:
if self._refreshing:
@@ -136,7 +124,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light):
self._timer = Timer(self._delay, _refresh_value)
self._timer.start()
return
super().value_changed(value)
super().value_changed()
@property
def brightness(self):
@@ -200,6 +188,12 @@ class ZwaveColorLight(ZwaveDimmer):
self._value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED)
self._get_color_values()
@property
def dependent_value_ids(self):
"""List of value IDs a device depends on."""
return [val.value_id for val in [
self._value_color, self._value_color_channels] if val]
def _get_color_values(self):
"""Search for color values available on this node."""
from openzwave.network import ZWaveNetwork
+1 -1
View File
@@ -47,7 +47,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
yield from async_add_devices([MqttLock(
async_add_devices([MqttLock(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
config.get(CONF_COMMAND_TOPIC),

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